fix: comprehensive boot warnings and exceptions — fixable silenced, unfixable diagnosed

Build system (5 gaps hardened):
- COOKBOOK_OFFLINE defaults to true (fork-mode)
- normalize_patch handles diff -ruN format
- New 'repo validate-patches' command (25/25 relibc patches)
- 14 patched Qt/Wayland/display recipes added to protected list
- relibc archive regenerated with current patch chain

Boot fixes (fixable):
- Full ISO EFI partition: 16 MiB → 1 MiB (matches mini, BIOS hardcoded 2 MiB offset)
- D-Bus system bus: absolute /usr/bin/dbus-daemon path (was skipped)
- redbear-sessiond: absolute /usr/bin/redbear-sessiond path (was skipped)
- daemon framework: silenced spurious INIT_NOTIFY warnings for oneshot_async services (P0-daemon-silence-init-notify.patch)
- udev-shim: demoted INIT_NOTIFY warning to INFO (expected for oneshot_async)
- relibc: comprehensive named semaphores (sem_open/close/unlink) replacing upstream todo!() stubs
- greeterd: Wayland socket timeout 15s → 30s (compositor DRM wait)
- greeter-ui: built and linked (header guard unification, sem_compat stubs removed)
- mc: un-ignored in both configs, fixed glib/libiconv/pcre2 transitive deps
- greeter config: removed stale keymapd dependency from display/greeter services
- prefix toolchain: relibc headers synced, _RELIBC_STDLIB_H guard unified

Unfixable (diagnosed, upstream):
- i2c-hidd: abort on no-I2C-hardware (QEMU) — process::exit → relibc abort
- kded6/greeter-ui: page fault 0x8 — Qt library null deref
- Thread panics fd != -1 — Rust std library on Redox
- DHCP timeout / eth0 MAC — QEMU user-mode networking
- hwrngd/thermald — no hardware RNG/thermal in VM
- live preload allocation — BIOS memory fragmentation, continues on demand
This commit is contained in:
2026-05-05 20:20:37 +01:00
parent a5f97b6632
commit f31522130f
81834 changed files with 11051982 additions and 108 deletions
@@ -0,0 +1,16 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
qt_internal_add_example(chapter1)
qt_internal_add_example(chapter2)
qt_internal_add_example(chapter3)
if (TARGET Qt6::Sql)
qt_internal_add_example(chapter4)
if(TARGET chattutorial-chapter4)
set_target_properties(chattutorial-chapter4 PROPERTIES UNITY_BUILD OFF)
endif()
qt_internal_add_example(chapter5)
if(TARGET chattutorial-chapter5)
set_target_properties(chattutorial-chapter5 PROPERTIES UNITY_BUILD OFF)
endif()
endif()
@@ -0,0 +1,49 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(chapter1 LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick)
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(chattutorial-chapter1 WIN32 MACOSX_BUNDLE
main.cpp
)
target_link_libraries(chattutorial-chapter1 PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Qml
Qt6::Quick
)
qt_policy(SET QTP0001 NEW)
qt_add_qml_module(chattutorial-chapter1
URI chattutorial
QML_FILES
"Main.qml"
)
qt6_add_resources(chattutorial-chapter1 "conf"
PREFIX
"/"
FILES
"qtquickcontrols2.conf"
)
install(TARGETS chattutorial-chapter1
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
qt_generate_deploy_qml_app_script(
TARGET chattutorial-chapter1
OUTPUT_SCRIPT deploy_script
MACOS_BUNDLE_POST_BUILD
NO_UNSUPPORTED_PLATFORM_ERROR
DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
)
install(SCRIPT ${deploy_script})
@@ -0,0 +1,23 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
ApplicationWindow {
width: 540
height: 960
visible: true
Page {
anchors.fill: parent
header: Label {
padding: 10
text: qsTr("Contacts")
font.pixelSize: 20
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
@@ -0,0 +1,15 @@
TEMPLATE = app
QT += qml quick
SOURCES += main.cpp
resources.files = \
Main.qml \
qmldir
resources.prefix = qt/qml/chattutorial/
RESOURCES += resources \
qtquickcontrols2.conf
target.path = $$[QT_INSTALL_EXAMPLES]/quickcontrols/chattutorial/chapter1
INSTALLS += target
@@ -0,0 +1,16 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.loadFromModule("chattutorial", "Main");
return app.exec();
}
@@ -0,0 +1,2 @@
module chattutorial
Main 1.0 Main.qml
@@ -0,0 +1,62 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(chapter2 LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick)
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(chattutorial-chapter2 WIN32 MACOSX_BUNDLE
main.cpp
)
target_link_libraries(chattutorial-chapter2 PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Qml
Qt6::Quick
)
qt_policy(SET QTP0001 NEW)
qt_add_qml_module(chattutorial-chapter2
URI chattutorial
QML_FILES
"Main.qml"
RESOURCES
"images/Albert_Einstein.png"
"images/Albert_Einstein@2x.png"
"images/Albert_Einstein@3x.png"
"images/Albert_Einstein@4x.png"
"images/Ernest_Hemingway.png"
"images/Ernest_Hemingway@2x.png"
"images/Ernest_Hemingway@3x.png"
"images/Ernest_Hemingway@4x.png"
"images/Hans_Gude.png"
"images/Hans_Gude@2x.png"
"images/Hans_Gude@3x.png"
"images/Hans_Gude@4x.png"
)
qt6_add_resources(chattutorial-chapter2 "conf"
PREFIX
"/"
FILES
"qtquickcontrols2.conf"
)
install(TARGETS chattutorial-chapter2
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
qt_generate_deploy_qml_app_script(
TARGET chattutorial-chapter2
OUTPUT_SCRIPT deploy_script
MACOS_BUNDLE_POST_BUILD
NO_UNSUPPORTED_PLATFORM_ERROR
DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
)
install(SCRIPT ${deploy_script})
@@ -0,0 +1,50 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
ApplicationWindow {
width: 540
height: 960
visible: true
Page {
anchors.fill: parent
header: Label {
padding: 10
text: qsTr("Contacts")
font.pixelSize: 20
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
ListView {
id: listView
anchors.fill: parent
topMargin: 48
leftMargin: 48
bottomMargin: 48
rightMargin: 48
spacing: 20
model: ["Albert Einstein", "Ernest Hemingway", "Hans Gude"]
delegate: ItemDelegate {
id: contactDelegate
text: modelData
width: listView.width - listView.leftMargin - listView.rightMargin
height: avatar.implicitHeight
leftPadding: avatar.implicitWidth + 32
required property string modelData
Image {
id: avatar
source: "images/" + contactDelegate.modelData.replace(" ", "_") + ".png"
}
}
}
}
}
@@ -0,0 +1,27 @@
TEMPLATE = app
QT += qml quick
SOURCES += main.cpp
resources.files = \
images/Albert_Einstein.png \
images/Albert_Einstein@2x.png \
images/Albert_Einstein@3x.png \
images/Albert_Einstein@4x.png \
images/Ernest_Hemingway.png \
images/Ernest_Hemingway@2x.png \
images/Ernest_Hemingway@3x.png \
images/Ernest_Hemingway@4x.png \
images/Hans_Gude.png \
images/Hans_Gude@2x.png \
images/Hans_Gude@3x.png \
images/Hans_Gude@4x.png \
Main.qml \
qmldir
resources.prefix = qt/qml/chattutorial/
RESOURCES += resources \
qtquickcontrols2.conf
target.path = $$[QT_INSTALL_EXAMPLES]/quickcontrols/chattutorial/chapter2
INSTALLS += target
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@@ -0,0 +1,16 @@
<RCC>
<qresource prefix="/">
<file>Albert_Einstein.png</file>
<file>Albert_Einstein@2x.png</file>
<file>Albert_Einstein@3x.png</file>
<file>Albert_Einstein@4x.png</file>
<file>Ernest_Hemingway.png</file>
<file>Ernest_Hemingway@2x.png</file>
<file>Ernest_Hemingway@3x.png</file>
<file>Ernest_Hemingway@4x.png</file>
<file>Hans_Gude.png</file>
<file>Hans_Gude@2x.png</file>
<file>Hans_Gude@3x.png</file>
<file>Hans_Gude@4x.png</file>
</qresource>
</RCC>
@@ -0,0 +1,16 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.loadFromModule("chattutorial", "Main");
return app.exec();
}
@@ -0,0 +1,2 @@
module chattutorial
Main 1.0 Main.qml
@@ -0,0 +1,64 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(chapter3 LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick)
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(chattutorial-chapter3 WIN32 MACOSX_BUNDLE
main.cpp
)
target_link_libraries(chattutorial-chapter3 PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Qml
Qt6::Quick
)
qt_policy(SET QTP0001 NEW)
qt_add_qml_module(chattutorial-chapter3
URI chattutorial
QML_FILES
"ContactPage.qml"
"ConversationPage.qml"
"Main.qml"
RESOURCES
"images/Albert_Einstein.png"
"images/Albert_Einstein@2x.png"
"images/Albert_Einstein@3x.png"
"images/Albert_Einstein@4x.png"
"images/Ernest_Hemingway.png"
"images/Ernest_Hemingway@2x.png"
"images/Ernest_Hemingway@3x.png"
"images/Ernest_Hemingway@4x.png"
"images/Hans_Gude.png"
"images/Hans_Gude@2x.png"
"images/Hans_Gude@3x.png"
"images/Hans_Gude@4x.png"
)
qt6_add_resources(chattutorial-chapter3 "conf"
PREFIX
"/"
FILES
"qtquickcontrols2.conf"
)
install(TARGETS chattutorial-chapter3
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
qt_generate_deploy_qml_app_script(
TARGET chattutorial-chapter3
OUTPUT_SCRIPT deploy_script
MACOS_BUNDLE_POST_BUILD
NO_UNSUPPORTED_PLATFORM_ERROR
DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
)
install(SCRIPT ${deploy_script})
@@ -0,0 +1,47 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
Page {
id: root
header: ToolBar {
Label {
text: qsTr("Contacts")
font.pixelSize: 20
anchors.centerIn: parent
}
}
ListView {
id: listView
anchors.fill: parent
topMargin: 48
leftMargin: 48
bottomMargin: 48
rightMargin: 48
spacing: 20
model: ["Albert Einstein", "Ernest Hemingway", "Hans Gude"]
delegate: ItemDelegate {
id: contactDelegate
text: modelData
width: listView.width - listView.leftMargin - listView.rightMargin
height: avatar.implicitHeight
leftPadding: avatar.implicitWidth + 32
required property string modelData
onClicked: root.StackView.view.push("ConversationPage.qml", { inConversationWith: modelData })
Image {
id: avatar
source: "images/" + contactDelegate.modelData.replace(" ", "_") + ".png"
}
}
}
}
@@ -0,0 +1,102 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Page {
id: root
property string inConversationWith
header: ToolBar {
ToolButton {
text: qsTr("Back")
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
onClicked: root.StackView.view.pop()
}
Label {
id: pageTitle
text: root.inConversationWith
font.pixelSize: 20
anchors.centerIn: parent
}
}
ColumnLayout {
anchors.fill: parent
ListView {
id: listView
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: pane.leftPadding + messageField.leftPadding
displayMarginBeginning: 40
displayMarginEnd: 40
verticalLayoutDirection: ListView.BottomToTop
spacing: 12
model: 10
delegate: Row {
id: messageDelegate
anchors.right: sentByMe ? listView.contentItem.right : undefined
spacing: 6
required property int index
readonly property bool sentByMe: index % 2 == 0
Rectangle {
id: avatar
width: height
height: parent.height
color: "grey"
visible: !messageDelegate.sentByMe
}
Rectangle {
width: 80
height: 40
color: messageDelegate.sentByMe ? "lightgrey" : "steelblue"
Label {
anchors.centerIn: parent
text: messageDelegate.index
color: messageDelegate.sentByMe ? "black" : "white"
}
}
}
ScrollBar.vertical: ScrollBar {}
}
Pane {
id: pane
Layout.fillWidth: true
Layout.fillHeight: false
RowLayout {
width: parent.width
TextArea {
id: messageField
Layout.fillWidth: true
placeholderText: qsTr("Compose message")
wrapMode: TextArea.Wrap
}
Button {
id: sendButton
text: qsTr("Send")
enabled: messageField.length > 0
Layout.fillWidth: false
}
}
}
}
}
@@ -0,0 +1,18 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick.Controls
ApplicationWindow {
id: window
width: 540
height: 960
visible: true
StackView {
id: stackView
anchors.fill: parent
initialItem: ContactPage {}
}
}
@@ -0,0 +1,29 @@
TEMPLATE = app
QT += qml quick
SOURCES += main.cpp
resources.files = \
ContactPage.qml \
ConversationPage.qml \
images/Albert_Einstein.png \
images/Albert_Einstein@2x.png \
images/Albert_Einstein@3x.png \
images/Albert_Einstein@4x.png \
images/Ernest_Hemingway.png \
images/Ernest_Hemingway@2x.png \
images/Ernest_Hemingway@3x.png \
images/Ernest_Hemingway@4x.png \
images/Hans_Gude.png \
images/Hans_Gude@2x.png \
images/Hans_Gude@3x.png \
images/Hans_Gude@4x.png \
Main.qml \
qmldir
resources.prefix = qt/qml/chattutorial/
RESOURCES += resources \
qtquickcontrols2.conf
target.path = $$[QT_INSTALL_EXAMPLES]/quickcontrols/chattutorial/chapter3
INSTALLS += target
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@@ -0,0 +1,16 @@
<RCC>
<qresource prefix="/">
<file>Albert_Einstein.png</file>
<file>Albert_Einstein@2x.png</file>
<file>Albert_Einstein@3x.png</file>
<file>Albert_Einstein@4x.png</file>
<file>Ernest_Hemingway.png</file>
<file>Ernest_Hemingway@2x.png</file>
<file>Ernest_Hemingway@3x.png</file>
<file>Ernest_Hemingway@4x.png</file>
<file>Hans_Gude.png</file>
<file>Hans_Gude@2x.png</file>
<file>Hans_Gude@3x.png</file>
<file>Hans_Gude@4x.png</file>
</qresource>
</RCC>
@@ -0,0 +1,16 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.loadFromModule("chattutorial", "Main");
return app.exec();
}
@@ -0,0 +1,4 @@
module chattutorial
ContactPage 1.0 ContactPage.qml
ConversationPage 1.0 ConversationPage.qml
Main 1.0 Main.qml
@@ -0,0 +1,67 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(chapter4 LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick Sql)
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(chattutorial-chapter4 WIN32 MACOSX_BUNDLE
main.cpp
sqlcontactmodel.cpp sqlcontactmodel.h
sqlconversationmodel.cpp sqlconversationmodel.h
)
target_link_libraries(chattutorial-chapter4 PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Qml
Qt6::Quick
Qt6::Sql
)
qt_policy(SET QTP0001 NEW)
qt_add_qml_module(chattutorial-chapter4
URI chattutorial
QML_FILES
"ContactPage.qml"
"ConversationPage.qml"
"Main.qml"
RESOURCES
"images/Albert_Einstein.png"
"images/Albert_Einstein@2x.png"
"images/Albert_Einstein@3x.png"
"images/Albert_Einstein@4x.png"
"images/Ernest_Hemingway.png"
"images/Ernest_Hemingway@2x.png"
"images/Ernest_Hemingway@3x.png"
"images/Ernest_Hemingway@4x.png"
"images/Hans_Gude.png"
"images/Hans_Gude@2x.png"
"images/Hans_Gude@3x.png"
"images/Hans_Gude@4x.png"
)
qt6_add_resources(chattutorial-chapter4 "conf"
PREFIX
"/"
FILES
"qtquickcontrols2.conf"
)
install(TARGETS chattutorial-chapter4
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
qt_generate_deploy_qml_app_script(
TARGET chattutorial-chapter4
OUTPUT_SCRIPT deploy_script
MACOS_BUNDLE_POST_BUILD
NO_UNSUPPORTED_PLATFORM_ERROR
DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
)
install(SCRIPT ${deploy_script})
@@ -0,0 +1,51 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import chattutorial
Page {
id: root
header: ToolBar {
Label {
text: qsTr("Contacts")
font.pixelSize: 20
anchors.centerIn: parent
}
}
ListView {
id: listView
anchors.fill: parent
topMargin: 48
leftMargin: 48
bottomMargin: 48
rightMargin: 48
spacing: 20
model: SqlContactModel {}
delegate: ItemDelegate {
id: contactDelegate
text: model.display
width: listView.width - listView.leftMargin - listView.rightMargin
height: avatar.implicitHeight
leftPadding: avatar.implicitWidth + 32
// Use "model" rather than the specific "display" role, because it
// would conflict with the display property of ItemDelegate.
required property var model
onClicked: root.StackView.view.push("ConversationPage.qml", { inConversationWith: model.display })
Image {
id: avatar
source: "images/" + contactDelegate.model.display.replace(" ", "_") + ".png"
}
}
}
}
@@ -0,0 +1,128 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import chattutorial
Page {
id: root
property string inConversationWith
header: ToolBar {
ToolButton {
text: qsTr("Back")
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
onClicked: root.StackView.view.pop()
}
Label {
id: pageTitle
text: root.inConversationWith
font.pixelSize: 20
anchors.centerIn: parent
}
}
ColumnLayout {
anchors.fill: parent
ListView {
id: listView
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: pane.leftPadding + messageField.leftPadding
displayMarginBeginning: 40
displayMarginEnd: 40
verticalLayoutDirection: ListView.BottomToTop
spacing: 12
model: SqlConversationModel {
recipient: root.inConversationWith
}
delegate: Column {
id: conversationDelegate
anchors.right: sentByMe ? listView.contentItem.right : undefined
spacing: 6
required property string author
required property string recipient
required property date timestamp
required property string message
readonly property bool sentByMe: recipient !== "Me"
Row {
id: messageRow
spacing: 6
anchors.right: conversationDelegate.sentByMe ? parent.right : undefined
Image {
id: avatar
source: !conversationDelegate.sentByMe
? "images/" + conversationDelegate.author.replace(" ", "_") + ".png" : ""
}
Rectangle {
width: Math.min(messageText.implicitWidth + 24,
listView.width - (!conversationDelegate.sentByMe ? avatar.width + messageRow.spacing : 0))
height: messageText.implicitHeight + 24
color: conversationDelegate.sentByMe ? "lightgrey" : "steelblue"
Label {
id: messageText
text: conversationDelegate.message
color: conversationDelegate.sentByMe ? "black" : "white"
anchors.fill: parent
anchors.margins: 12
wrapMode: Label.Wrap
}
}
}
Label {
id: timestampText
text: Qt.formatDateTime(conversationDelegate.timestamp, "d MMM hh:mm")
color: "lightgrey"
anchors.right: conversationDelegate.sentByMe ? parent.right : undefined
}
}
ScrollBar.vertical: ScrollBar {}
}
Pane {
id: pane
Layout.fillWidth: true
Layout.fillHeight: false
RowLayout {
width: parent.width
TextArea {
id: messageField
Layout.fillWidth: true
placeholderText: qsTr("Compose message")
wrapMode: TextArea.Wrap
}
Button {
id: sendButton
text: qsTr("Send")
enabled: messageField.length > 0
Layout.fillWidth: false
onClicked: {
listView.model.sendMessage(root.inConversationWith, messageField.text)
messageField.text = ""
}
}
}
}
}
}
@@ -0,0 +1,18 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick.Controls
ApplicationWindow {
id: window
width: 540
height: 960
visible: true
StackView {
id: stackView
anchors.fill: parent
initialItem: ContactPage {}
}
}
@@ -0,0 +1,39 @@
TEMPLATE = app
QT += qml quick sql
CONFIG += c++11 qmltypes
QML_IMPORT_PATH = $$pwd/.
QML_IMPORT_NAME = chattutorial
QML_IMPORT_MAJOR_VERSION = 1
HEADERS += sqlcontactmodel.h \
sqlconversationmodel.h
SOURCES += main.cpp \
sqlcontactmodel.cpp \
sqlconversationmodel.cpp
resources.files = \
ContactPage.qml \
ConversationPage.qml \
images/Albert_Einstein.png \
images/Albert_Einstein@2x.png \
images/Albert_Einstein@3x.png \
images/Albert_Einstein@4x.png \
images/Ernest_Hemingway.png \
images/Ernest_Hemingway@2x.png \
images/Ernest_Hemingway@3x.png \
images/Ernest_Hemingway@4x.png \
images/Hans_Gude.png \
images/Hans_Gude@2x.png \
images/Hans_Gude@3x.png \
images/Hans_Gude@4x.png \
Main.qml \
qmldir
resources.prefix = qt/qml/chattutorial/
RESOURCES += resources \
qtquickcontrols2.conf
target.path = $$[QT_INSTALL_EXAMPLES]/quickcontrols/chattutorial/chapter4
INSTALLS += target
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@@ -0,0 +1,16 @@
<RCC>
<qresource prefix="/">
<file>Albert_Einstein.png</file>
<file>Albert_Einstein@2x.png</file>
<file>Albert_Einstein@3x.png</file>
<file>Albert_Einstein@4x.png</file>
<file>Ernest_Hemingway.png</file>
<file>Ernest_Hemingway@2x.png</file>
<file>Ernest_Hemingway@3x.png</file>
<file>Ernest_Hemingway@4x.png</file>
<file>Hans_Gude.png</file>
<file>Hans_Gude@2x.png</file>
<file>Hans_Gude@3x.png</file>
<file>Hans_Gude@4x.png</file>
</qresource>
</RCC>
@@ -0,0 +1,46 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtCore>
#include <QGuiApplication>
#include <QSqlDatabase>
#include <QSqlError>
#include <QtQml>
static void connectToDatabase()
{
QSqlDatabase database = QSqlDatabase::database();
if (!database.isValid()) {
database = QSqlDatabase::addDatabase("QSQLITE");
if (!database.isValid())
qFatal("Cannot add database: %s", qPrintable(database.lastError().text()));
}
const QDir writeDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
if (!writeDir.mkpath("."))
qFatal("Failed to create writable directory at %s", qPrintable(writeDir.absolutePath()));
// Ensure that we have a writable location on all devices.
const QString fileName = writeDir.absolutePath() + "/chat-database.sqlite3";
// When using the SQLite driver, open() will create the SQLite database if it doesn't exist.
database.setDatabaseName(fileName);
if (!database.open()) {
qFatal("Cannot open database: %s", qPrintable(database.lastError().text()));
QFile::remove(fileName);
}
}
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
connectToDatabase();
QQmlApplicationEngine engine;
engine.loadFromModule("chattutorial", "Main");
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
@@ -0,0 +1,5 @@
module chattutorial
ContactPage 1.0 ContactPage.qml
ConversationPage 1.0 ConversationPage.qml
Main 1.0 Main.qml
typeinfo chapter4.qmltypes
@@ -0,0 +1,43 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "sqlcontactmodel.h"
#include <QDebug>
#include <QSqlError>
#include <QSqlQuery>
static void createTable()
{
if (QSqlDatabase::database().tables().contains(QStringLiteral("Contacts"))) {
// The table already exists; we don't need to do anything.
return;
}
QSqlQuery query;
if (!query.exec(
"CREATE TABLE IF NOT EXISTS 'Contacts' ("
" 'name' TEXT NOT NULL,"
" PRIMARY KEY(name)"
")")) {
qFatal("Failed to query database: %s", qPrintable(query.lastError().text()));
}
query.exec("INSERT INTO Contacts VALUES('Albert Einstein')");
query.exec("INSERT INTO Contacts VALUES('Ernest Hemingway')");
query.exec("INSERT INTO Contacts VALUES('Hans Gude')");
}
SqlContactModel::SqlContactModel(QObject *parent) :
QSqlQueryModel(parent)
{
createTable();
QSqlQuery query;
if (!query.exec("SELECT * FROM Contacts"))
qFatal("Contacts SELECT query failed: %s", qPrintable(query.lastError().text()));
setQuery(std::move(query));
if (lastError().isValid())
qFatal("Cannot set query on SqlContactModel: %s", qPrintable(lastError().text()));
}
@@ -0,0 +1,19 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef SQLCONTACTMODEL_H
#define SQLCONTACTMODEL_H
#include <QQmlEngine>
#include <QSqlQueryModel>
class SqlContactModel : public QSqlQueryModel
{
Q_OBJECT
QML_ELEMENT
public:
SqlContactModel(QObject *parent = nullptr);
};
#endif // SQLCONTACTMODEL_H
@@ -0,0 +1,107 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "sqlconversationmodel.h"
#include <QDateTime>
#include <QDebug>
#include <QSqlError>
#include <QSqlRecord>
#include <QSqlQuery>
static const char *conversationsTableName = "Conversations";
static void createTable()
{
if (QSqlDatabase::database().tables().contains(conversationsTableName)) {
// The table already exists; we don't need to do anything.
return;
}
QSqlQuery query;
if (!query.exec(
"CREATE TABLE IF NOT EXISTS 'Conversations' ("
"'author' TEXT NOT NULL,"
"'recipient' TEXT NOT NULL,"
"'timestamp' TEXT NOT NULL,"
"'message' TEXT NOT NULL,"
"FOREIGN KEY('author') REFERENCES Contacts ( name ),"
"FOREIGN KEY('recipient') REFERENCES Contacts ( name )"
")")) {
qFatal("Failed to query database: %s", qPrintable(query.lastError().text()));
}
query.exec("INSERT INTO Conversations VALUES('Me', 'Ernest Hemingway', '2016-01-07T14:36:06', 'Hello!')");
query.exec("INSERT INTO Conversations VALUES('Ernest Hemingway', 'Me', '2016-01-07T14:36:16', 'Good afternoon.')");
query.exec("INSERT INTO Conversations VALUES('Me', 'Albert Einstein', '2016-01-01T11:24:53', 'Hi!')");
query.exec("INSERT INTO Conversations VALUES('Albert Einstein', 'Me', '2016-01-07T14:36:16', 'Good morning.')");
query.exec("INSERT INTO Conversations VALUES('Hans Gude', 'Me', '2015-11-20T06:30:02', 'God morgen. Har du fått mitt maleri?')");
query.exec("INSERT INTO Conversations VALUES('Me', 'Hans Gude', '2015-11-20T08:21:03', 'God morgen, Hans. Ja, det er veldig fint. Tusen takk! "
"Hvor mange timer har du brukt på den?')");
}
SqlConversationModel::SqlConversationModel(QObject *parent) :
QSqlTableModel(parent)
{
createTable();
setTable(conversationsTableName);
setSort(2, Qt::DescendingOrder);
// Ensures that the model is sorted correctly after submitting a new row.
setEditStrategy(QSqlTableModel::OnManualSubmit);
}
QString SqlConversationModel::recipient() const
{
return m_recipient;
}
void SqlConversationModel::setRecipient(const QString &recipient)
{
if (recipient == m_recipient)
return;
m_recipient = recipient;
const QString filterString = QString::fromLatin1(
"(recipient = '%1' AND author = 'Me') OR (recipient = 'Me' AND author='%1')").arg(m_recipient);
setFilter(filterString);
select();
emit recipientChanged();
}
QVariant SqlConversationModel::data(const QModelIndex &index, int role) const
{
if (role < Qt::UserRole)
return QSqlTableModel::data(index, role);
const QSqlRecord sqlRecord = record(index.row());
return sqlRecord.value(role - Qt::UserRole);
}
QHash<int, QByteArray> SqlConversationModel::roleNames() const
{
QHash<int, QByteArray> names;
names[Qt::UserRole] = "author";
names[Qt::UserRole + 1] = "recipient";
names[Qt::UserRole + 2] = "timestamp";
names[Qt::UserRole + 3] = "message";
return names;
}
void SqlConversationModel::sendMessage(const QString &recipient, const QString &message)
{
const QString timestamp = QDateTime::currentDateTime().toString(Qt::ISODate);
QSqlRecord newRecord = record();
newRecord.setValue("author", "Me");
newRecord.setValue("recipient", recipient);
newRecord.setValue("timestamp", timestamp);
newRecord.setValue("message", message);
if (!insertRecord(rowCount(), newRecord)) {
qWarning() << "Failed to send message:" << lastError().text();
return;
}
submitAll();
}
@@ -0,0 +1,34 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef SQLCONVERSATIONMODEL_H
#define SQLCONVERSATIONMODEL_H
#include <QQmlEngine>
#include <QSqlTableModel>
class SqlConversationModel : public QSqlTableModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QString recipient READ recipient WRITE setRecipient NOTIFY recipientChanged)
public:
SqlConversationModel(QObject *parent = nullptr);
QString recipient() const;
void setRecipient(const QString &recipient);
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void sendMessage(const QString &recipient, const QString &message);
signals:
void recipientChanged();
private:
QString m_recipient;
};
#endif // SQLCONVERSATIONMODEL_H
@@ -0,0 +1,9 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick.Controls
import QtQuick.Controls.Material
ToolBar {
Material.theme: Material.Dark
}
@@ -0,0 +1,69 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(chapter5 LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick Sql)
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(chattutorial-chapter5 WIN32 MACOSX_BUNDLE
main.cpp
sqlcontactmodel.cpp sqlcontactmodel.h
sqlconversationmodel.cpp sqlconversationmodel.h
)
target_link_libraries(chattutorial-chapter5 PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Qml
Qt6::Quick
Qt6::Sql
)
qt_policy(SET QTP0001 NEW)
qt_add_qml_module(chattutorial-chapter5
URI chattutorial
QML_FILES
"+Material/ChatToolBar.qml"
"ChatToolBar.qml"
"ContactPage.qml"
"ConversationPage.qml"
"Main.qml"
RESOURCES
"images/Albert_Einstein.png"
"images/Albert_Einstein@2x.png"
"images/Albert_Einstein@3x.png"
"images/Albert_Einstein@4x.png"
"images/Ernest_Hemingway.png"
"images/Ernest_Hemingway@2x.png"
"images/Ernest_Hemingway@3x.png"
"images/Ernest_Hemingway@4x.png"
"images/Hans_Gude.png"
"images/Hans_Gude@2x.png"
"images/Hans_Gude@3x.png"
"images/Hans_Gude@4x.png"
)
qt6_add_resources(chattutorial-chapter5 "conf"
PREFIX
"/"
FILES
"qtquickcontrols2.conf"
)
install(TARGETS chattutorial-chapter5
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
qt_generate_deploy_qml_app_script(
TARGET chattutorial-chapter5
OUTPUT_SCRIPT deploy_script
MACOS_BUNDLE_POST_BUILD
NO_UNSUPPORTED_PLATFORM_ERROR
DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
)
install(SCRIPT ${deploy_script})
@@ -0,0 +1,7 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick.Controls
ToolBar {
}
@@ -0,0 +1,51 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import chattutorial
Page {
id: root
header: ChatToolBar {
Label {
text: qsTr("Contacts")
font.pixelSize: 20
anchors.centerIn: parent
}
}
ListView {
id: listView
anchors.fill: parent
topMargin: 48
leftMargin: 48
bottomMargin: 48
rightMargin: 48
spacing: 20
model: SqlContactModel {}
delegate: ItemDelegate {
id: contactDelegate
text: model.display
width: listView.width - listView.leftMargin - listView.rightMargin
height: avatar.implicitHeight
leftPadding: avatar.implicitWidth + 32
// Use "model" rather than the specific "display" role, because it
// would conflict with the display property of ItemDelegate.
required property var model
onClicked: root.StackView.view.push("ConversationPage.qml", { inConversationWith: model.display })
Image {
id: avatar
source: "images/" + contactDelegate.model.display.replace(" ", "_") + ".png"
}
}
}
}
@@ -0,0 +1,127 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import chattutorial
Page {
id: root
property string inConversationWith
header: ChatToolBar {
ToolButton {
text: qsTr("Back")
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
onClicked: root.StackView.view.pop()
}
Label {
id: pageTitle
text: root.inConversationWith
font.pixelSize: 20
anchors.centerIn: parent
}
}
ColumnLayout {
anchors.fill: parent
ListView {
id: listView
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: pane.leftPadding + messageField.leftPadding
displayMarginBeginning: 40
displayMarginEnd: 40
verticalLayoutDirection: ListView.BottomToTop
spacing: 12
model: SqlConversationModel {
recipient: root.inConversationWith
}
delegate: Column {
id: conversationDelegate
anchors.right: sentByMe ? listView.contentItem.right : undefined
spacing: 6
required property string author
required property string recipient
required property date timestamp
required property string message
readonly property bool sentByMe: recipient !== "Me"
Row {
id: messageRow
spacing: 6
anchors.right: conversationDelegate.sentByMe ? parent.right : undefined
Image {
id: avatar
source: !conversationDelegate.sentByMe
? "images/" + conversationDelegate.author.replace(" ", "_") + ".png" : ""
}
Rectangle {
width: Math.min(messageText.implicitWidth + 24, listView.width - avatar.width - messageRow.spacing)
height: messageText.implicitHeight + 24
color: conversationDelegate.sentByMe ? "lightgrey" : "steelblue"
Label {
id: messageText
text: conversationDelegate.message
color: conversationDelegate.sentByMe ? "black" : "white"
anchors.fill: parent
anchors.margins: 12
wrapMode: Label.Wrap
}
}
}
Label {
id: timestampText
text: Qt.formatDateTime(conversationDelegate.timestamp, "d MMM hh:mm")
color: "lightgrey"
anchors.right: conversationDelegate.sentByMe ? parent.right : undefined
}
}
ScrollBar.vertical: ScrollBar {}
}
Pane {
id: pane
Layout.fillWidth: true
Layout.fillHeight: false
RowLayout {
width: parent.width
TextArea {
id: messageField
Layout.fillWidth: true
placeholderText: qsTr("Compose message")
wrapMode: TextArea.Wrap
}
Button {
id: sendButton
text: qsTr("Send")
enabled: messageField.length > 0
Layout.fillWidth: false
onClicked: {
listView.model.sendMessage(root.inConversationWith, messageField.text)
messageField.text = ""
}
}
}
}
}
}
@@ -0,0 +1,18 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick.Controls
ApplicationWindow {
id: window
width: 540
height: 960
visible: true
StackView {
id: stackView
anchors.fill: parent
initialItem: ContactPage {}
}
}
@@ -0,0 +1,41 @@
TEMPLATE = app
QT += qml quick sql
CONFIG += c++11 qmltypes
QML_IMPORT_PATH = $$pwd/.
QML_IMPORT_NAME = chattutorial
QML_IMPORT_MAJOR_VERSION = 1
HEADERS += sqlcontactmodel.h \
sqlconversationmodel.h
SOURCES += main.cpp \
sqlcontactmodel.cpp \
sqlconversationmodel.cpp
resources.files = \
+Material/ChatToolBar.qml \
ChatToolBar.qml \
ContactPage.qml \
ConversationPage.qml \
images/Albert_Einstein.png \
images/Albert_Einstein@2x.png \
images/Albert_Einstein@3x.png \
images/Albert_Einstein@4x.png \
images/Ernest_Hemingway.png \
images/Ernest_Hemingway@2x.png \
images/Ernest_Hemingway@3x.png \
images/Ernest_Hemingway@4x.png \
images/Hans_Gude.png \
images/Hans_Gude@2x.png \
images/Hans_Gude@3x.png \
images/Hans_Gude@4x.png \
Main.qml \
qmldir
resources.prefix = qt/qml/chattutorial/
RESOURCES += resources \
qtquickcontrols2.conf
target.path = $$[QT_INSTALL_EXAMPLES]/quickcontrols/chattutorial/chapter5
INSTALLS += target
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@@ -0,0 +1,16 @@
<RCC>
<qresource prefix="/">
<file>Albert_Einstein.png</file>
<file>Albert_Einstein@2x.png</file>
<file>Albert_Einstein@3x.png</file>
<file>Albert_Einstein@4x.png</file>
<file>Ernest_Hemingway.png</file>
<file>Ernest_Hemingway@2x.png</file>
<file>Ernest_Hemingway@3x.png</file>
<file>Ernest_Hemingway@4x.png</file>
<file>Hans_Gude.png</file>
<file>Hans_Gude@2x.png</file>
<file>Hans_Gude@3x.png</file>
<file>Hans_Gude@4x.png</file>
</qresource>
</RCC>
@@ -0,0 +1,46 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtCore>
#include <QGuiApplication>
#include <QSqlDatabase>
#include <QSqlError>
#include <QtQml>
static void connectToDatabase()
{
QSqlDatabase database = QSqlDatabase::database();
if (!database.isValid()) {
database = QSqlDatabase::addDatabase("QSQLITE");
if (!database.isValid())
qFatal("Cannot add database: %s", qPrintable(database.lastError().text()));
}
const QDir writeDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
if (!writeDir.mkpath("."))
qFatal("Failed to create writable directory at %s", qPrintable(writeDir.absolutePath()));
// Ensure that we have a writable location on all devices.
const QString fileName = writeDir.absolutePath() + "/chat-database.sqlite3";
// When using the SQLite driver, open() will create the SQLite database if it doesn't exist.
database.setDatabaseName(fileName);
if (!database.open()) {
qFatal("Cannot open database: %s", qPrintable(database.lastError().text()));
QFile::remove(fileName);
}
}
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
connectToDatabase();
QQmlApplicationEngine engine;
engine.loadFromModule("chattutorial", "Main");
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
@@ -0,0 +1,6 @@
module chattutorial
ChatToolBar 1.0 ChatToolBar.qml
ContactPage 1.0 ContactPage.qml
ConversationPage 1.0 ConversationPage.qml
Main 1.0 Main.qml
typeinfo chapter5.qmltypes
@@ -0,0 +1,10 @@
[Controls]
Style=Basic
[Material]
Primary=Indigo
Accent=Indigo
Theme=Dark
[Universal]
Theme=Dark
@@ -0,0 +1,43 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "sqlcontactmodel.h"
#include <QDebug>
#include <QSqlError>
#include <QSqlQuery>
static void createTable()
{
if (QSqlDatabase::database().tables().contains(QStringLiteral("Contacts"))) {
// The table already exists; we don't need to do anything.
return;
}
QSqlQuery query;
if (!query.exec(
"CREATE TABLE IF NOT EXISTS 'Contacts' ("
" 'name' TEXT NOT NULL,"
" PRIMARY KEY(name)"
")")) {
qFatal("Failed to query database: %s", qPrintable(query.lastError().text()));
}
query.exec("INSERT INTO Contacts VALUES('Albert Einstein')");
query.exec("INSERT INTO Contacts VALUES('Ernest Hemingway')");
query.exec("INSERT INTO Contacts VALUES('Hans Gude')");
}
SqlContactModel::SqlContactModel(QObject *parent) :
QSqlQueryModel(parent)
{
createTable();
QSqlQuery query;
if (!query.exec("SELECT * FROM Contacts"))
qFatal("Contacts SELECT query failed: %s", qPrintable(query.lastError().text()));
setQuery(std::move(query));
if (lastError().isValid())
qFatal("Cannot set query on SqlContactModel: %s", qPrintable(lastError().text()));
}
@@ -0,0 +1,19 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef SQLCONTACTMODEL_H
#define SQLCONTACTMODEL_H
#include <QQmlEngine>
#include <QSqlQueryModel>
class SqlContactModel : public QSqlQueryModel
{
Q_OBJECT
QML_ELEMENT
public:
SqlContactModel(QObject *parent = nullptr);
};
#endif // SQLCONTACTMODEL_H
@@ -0,0 +1,107 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "sqlconversationmodel.h"
#include <QDateTime>
#include <QDebug>
#include <QSqlError>
#include <QSqlRecord>
#include <QSqlQuery>
static const char *conversationsTableName = "Conversations";
static void createTable()
{
if (QSqlDatabase::database().tables().contains(conversationsTableName)) {
// The table already exists; we don't need to do anything.
return;
}
QSqlQuery query;
if (!query.exec(
"CREATE TABLE IF NOT EXISTS 'Conversations' ("
"'author' TEXT NOT NULL,"
"'recipient' TEXT NOT NULL,"
"'timestamp' TEXT NOT NULL,"
"'message' TEXT NOT NULL,"
"FOREIGN KEY('author') REFERENCES Contacts ( name ),"
"FOREIGN KEY('recipient') REFERENCES Contacts ( name )"
")")) {
qFatal("Failed to query database: %s", qPrintable(query.lastError().text()));
}
query.exec("INSERT INTO Conversations VALUES('Me', 'Ernest Hemingway', '2016-01-07T14:36:06', 'Hello!')");
query.exec("INSERT INTO Conversations VALUES('Ernest Hemingway', 'Me', '2016-01-07T14:36:16', 'Good afternoon.')");
query.exec("INSERT INTO Conversations VALUES('Me', 'Albert Einstein', '2016-01-01T11:24:53', 'Hi!')");
query.exec("INSERT INTO Conversations VALUES('Albert Einstein', 'Me', '2016-01-07T14:36:16', 'Good morning.')");
query.exec("INSERT INTO Conversations VALUES('Hans Gude', 'Me', '2015-11-20T06:30:02', 'God morgen. Har du fått mitt maleri?')");
query.exec("INSERT INTO Conversations VALUES('Me', 'Hans Gude', '2015-11-20T08:21:03', 'God morgen, Hans. Ja, det er veldig fint. Tusen takk! "
"Hvor mange timer har du brukt på den?')");
}
SqlConversationModel::SqlConversationModel(QObject *parent) :
QSqlTableModel(parent)
{
createTable();
setTable(conversationsTableName);
setSort(2, Qt::DescendingOrder);
// Ensures that the model is sorted correctly after submitting a new row.
setEditStrategy(QSqlTableModel::OnManualSubmit);
}
QString SqlConversationModel::recipient() const
{
return m_recipient;
}
void SqlConversationModel::setRecipient(const QString &recipient)
{
if (recipient == m_recipient)
return;
m_recipient = recipient;
const QString filterString = QString::fromLatin1(
"(recipient = '%1' AND author = 'Me') OR (recipient = 'Me' AND author='%1')").arg(m_recipient);
setFilter(filterString);
select();
emit recipientChanged();
}
QVariant SqlConversationModel::data(const QModelIndex &index, int role) const
{
if (role < Qt::UserRole)
return QSqlTableModel::data(index, role);
const QSqlRecord sqlRecord = record(index.row());
return sqlRecord.value(role - Qt::UserRole);
}
QHash<int, QByteArray> SqlConversationModel::roleNames() const
{
QHash<int, QByteArray> names;
names[Qt::UserRole] = "author";
names[Qt::UserRole + 1] = "recipient";
names[Qt::UserRole + 2] = "timestamp";
names[Qt::UserRole + 3] = "message";
return names;
}
void SqlConversationModel::sendMessage(const QString &recipient, const QString &message)
{
const QString timestamp = QDateTime::currentDateTime().toString(Qt::ISODate);
QSqlRecord newRecord = record();
newRecord.setValue("author", "Me");
newRecord.setValue("recipient", recipient);
newRecord.setValue("timestamp", timestamp);
newRecord.setValue("message", message);
if (!insertRecord(rowCount(), newRecord)) {
qWarning() << "Failed to send message:" << lastError().text();
return;
}
submitAll();
}
@@ -0,0 +1,34 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef SQLCONVERSATIONMODEL_H
#define SQLCONVERSATIONMODEL_H
#include <QQmlEngine>
#include <QSqlTableModel>
class SqlConversationModel : public QSqlTableModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QString recipient READ recipient WRITE setRecipient NOTIFY recipientChanged)
public:
SqlConversationModel(QObject *parent = nullptr);
QString recipient() const;
void setRecipient(const QString &recipient);
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void sendMessage(const QString &recipient, const QString &message);
signals:
void recipientChanged();
private:
QString m_recipient;
};
#endif // SQLCONVERSATIONMODEL_H
@@ -0,0 +1,8 @@
TEMPLATE = subdirs
SUBDIRS += \
chapter1 \
chapter2 \
chapter3 \
chapter4 \
chapter5

Some files were not shown because too many files have changed in this diff Show More