aboutsummaryrefslogtreecommitdiffstats
path: root/examples/quickcontrols/chattutorial
diff options
context:
space:
mode:
Diffstat (limited to 'examples/quickcontrols/chattutorial')
-rw-r--r--examples/quickcontrols/chattutorial/CMakeLists.txt16
-rw-r--r--examples/quickcontrols/chattutorial/chapter1/CMakeLists.txt49
-rw-r--r--examples/quickcontrols/chattutorial/chapter1/Main.qml23
-rw-r--r--examples/quickcontrols/chattutorial/chapter1/chapter1.pro15
-rw-r--r--examples/quickcontrols/chattutorial/chapter1/main.cpp16
-rw-r--r--examples/quickcontrols/chattutorial/chapter1/qmldir2
-rw-r--r--examples/quickcontrols/chattutorial/chapter1/qtquickcontrols2.conf2
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/CMakeLists.txt62
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/Main.qml50
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/chapter2.pro27
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein.pngbin0 -> 1669 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein@2x.pngbin0 -> 5657 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein@3x.pngbin0 -> 11804 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein@4x.pngbin0 -> 19989 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway.pngbin0 -> 2255 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway@2x.pngbin0 -> 6375 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway@3x.pngbin0 -> 13723 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway@4x.pngbin0 -> 24109 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude.pngbin0 -> 2937 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude@2x.pngbin0 -> 9239 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude@3x.pngbin0 -> 18163 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude@4x.pngbin0 -> 28614 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/images/images.qrc16
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/main.cpp16
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/qmldir2
-rw-r--r--examples/quickcontrols/chattutorial/chapter2/qtquickcontrols2.conf2
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/CMakeLists.txt64
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/ContactPage.qml47
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/ConversationPage.qml102
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/Main.qml18
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/chapter3.pro29
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein.pngbin0 -> 1669 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein@2x.pngbin0 -> 5657 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein@3x.pngbin0 -> 11804 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein@4x.pngbin0 -> 19989 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway.pngbin0 -> 2255 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway@2x.pngbin0 -> 6375 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway@3x.pngbin0 -> 13723 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway@4x.pngbin0 -> 24109 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude.pngbin0 -> 2937 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude@2x.pngbin0 -> 9239 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude@3x.pngbin0 -> 18163 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude@4x.pngbin0 -> 28614 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/images/images.qrc16
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/main.cpp16
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/qmldir4
-rw-r--r--examples/quickcontrols/chattutorial/chapter3/qtquickcontrols2.conf2
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/CMakeLists.txt67
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/ContactPage.qml51
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/ConversationPage.qml128
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/Main.qml18
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/chapter4.pro39
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein.pngbin0 -> 1669 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein@2x.pngbin0 -> 5657 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein@3x.pngbin0 -> 11804 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein@4x.pngbin0 -> 19989 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway.pngbin0 -> 2255 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway@2x.pngbin0 -> 6375 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway@3x.pngbin0 -> 13723 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway@4x.pngbin0 -> 24109 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude.pngbin0 -> 2937 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude@2x.pngbin0 -> 9239 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude@3x.pngbin0 -> 18163 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude@4x.pngbin0 -> 28614 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/images/images.qrc16
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/main.cpp46
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/qmldir5
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/qtquickcontrols2.conf2
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/sqlcontactmodel.cpp43
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/sqlcontactmodel.h19
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/sqlconversationmodel.cpp107
-rw-r--r--examples/quickcontrols/chattutorial/chapter4/sqlconversationmodel.h34
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/+Material/ChatToolBar.qml9
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/CMakeLists.txt69
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/ChatToolBar.qml7
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/ContactPage.qml51
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/ConversationPage.qml127
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/Main.qml18
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/chapter5.pro41
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein.pngbin0 -> 1669 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein@2x.pngbin0 -> 5657 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein@3x.pngbin0 -> 11804 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein@4x.pngbin0 -> 19989 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway.pngbin0 -> 2255 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway@2x.pngbin0 -> 6375 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway@3x.pngbin0 -> 13723 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway@4x.pngbin0 -> 24109 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude.pngbin0 -> 2937 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude@2x.pngbin0 -> 9239 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude@3x.pngbin0 -> 18163 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude@4x.pngbin0 -> 28614 bytes
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/images/images.qrc16
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/main.cpp46
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/qmldir6
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/qtquickcontrols2.conf10
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/sqlcontactmodel.cpp43
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/sqlcontactmodel.h19
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/sqlconversationmodel.cpp107
-rw-r--r--examples/quickcontrols/chattutorial/chapter5/sqlconversationmodel.h34
-rw-r--r--examples/quickcontrols/chattutorial/chattutorial.pro8
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter1.pngbin0 -> 4798 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter2-listview-header.gifbin0 -> 176936 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter2.pngbin0 -> 20119 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter3-listview-header.gifbin0 -> 262432 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter3-view-margins.pngbin0 -> 2960 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter3.gifbin0 -> 169856 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter4-long-message.pngbin0 -> 13079 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter4-message-timestamp.pngbin0 -> 9750 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter4.gifbin0 -> 102536 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-material-dark.pngbin0 -> 19787 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-material-test.pngbin0 -> 17337 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-material.pngbin0 -> 17475 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-universal-dark.pngbin0 -> 15520 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-universal.pngbin0 -> 16575 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-material-dark.pngbin0 -> 14723 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-material-test.pngbin0 -> 2688 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-material.pngbin0 -> 2861 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-universal-dark.pngbin0 -> 8590 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-universal.pngbin0 -> 9627 bytes
-rw-r--r--examples/quickcontrols/chattutorial/doc/src/qtquickcontrols-chattutorial.qdoc821
120 files changed, 2603 insertions, 0 deletions
diff --git a/examples/quickcontrols/chattutorial/CMakeLists.txt b/examples/quickcontrols/chattutorial/CMakeLists.txt
new file mode 100644
index 0000000000..83744b78cf
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/CMakeLists.txt
@@ -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()
diff --git a/examples/quickcontrols/chattutorial/chapter1/CMakeLists.txt b/examples/quickcontrols/chattutorial/chapter1/CMakeLists.txt
new file mode 100644
index 0000000000..c17c054418
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter1/CMakeLists.txt
@@ -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)
+
+set(CMAKE_AUTOMOC ON)
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick)
+
+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})
diff --git a/examples/quickcontrols/chattutorial/chapter1/Main.qml b/examples/quickcontrols/chattutorial/chapter1/Main.qml
new file mode 100644
index 0000000000..19496f44eb
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter1/Main.qml
@@ -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
+ }
+ }
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter1/chapter1.pro b/examples/quickcontrols/chattutorial/chapter1/chapter1.pro
new file mode 100644
index 0000000000..323b0709d4
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter1/chapter1.pro
@@ -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
diff --git a/examples/quickcontrols/chattutorial/chapter1/main.cpp b/examples/quickcontrols/chattutorial/chapter1/main.cpp
new file mode 100644
index 0000000000..73d4bf3b63
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter1/main.cpp
@@ -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();
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter1/qmldir b/examples/quickcontrols/chattutorial/chapter1/qmldir
new file mode 100644
index 0000000000..1044557225
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter1/qmldir
@@ -0,0 +1,2 @@
+module chattutorial
+Main 1.0 Main.qml
diff --git a/examples/quickcontrols/chattutorial/chapter1/qtquickcontrols2.conf b/examples/quickcontrols/chattutorial/chapter1/qtquickcontrols2.conf
new file mode 100644
index 0000000000..db9486764e
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter1/qtquickcontrols2.conf
@@ -0,0 +1,2 @@
+[Controls]
+Style=Basic
diff --git a/examples/quickcontrols/chattutorial/chapter2/CMakeLists.txt b/examples/quickcontrols/chattutorial/chapter2/CMakeLists.txt
new file mode 100644
index 0000000000..b6e37b4449
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/CMakeLists.txt
@@ -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)
+
+set(CMAKE_AUTOMOC ON)
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick)
+
+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})
diff --git a/examples/quickcontrols/chattutorial/chapter2/Main.qml b/examples/quickcontrols/chattutorial/chapter2/Main.qml
new file mode 100644
index 0000000000..daa4360fb9
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/Main.qml
@@ -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"
+ }
+ }
+ }
+ }
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter2/chapter2.pro b/examples/quickcontrols/chattutorial/chapter2/chapter2.pro
new file mode 100644
index 0000000000..88f70ce08a
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/chapter2.pro
@@ -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
diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein.png b/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein.png
new file mode 100644
index 0000000000..7c44b90b8d
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein@2x.png b/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein@2x.png
new file mode 100644
index 0000000000..6ce9c39b6c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein@2x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein@3x.png b/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein@3x.png
new file mode 100644
index 0000000000..aab6a6a162
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein@3x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein@4x.png b/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein@4x.png
new file mode 100644
index 0000000000..3611284df4
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein@4x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway.png b/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway.png
new file mode 100644
index 0000000000..3ac8992fd9
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway@2x.png b/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway@2x.png
new file mode 100644
index 0000000000..122d9f1e53
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway@2x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway@3x.png b/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway@3x.png
new file mode 100644
index 0000000000..2fe9c2cc1c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway@3x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway@4x.png b/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway@4x.png
new file mode 100644
index 0000000000..96fb6788b5
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway@4x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude.png b/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude.png
new file mode 100644
index 0000000000..907e38bc61
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude@2x.png b/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude@2x.png
new file mode 100644
index 0000000000..6837796a83
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude@2x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude@3x.png b/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude@3x.png
new file mode 100644
index 0000000000..29af422ad3
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude@3x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude@4x.png b/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude@4x.png
new file mode 100644
index 0000000000..bc35eeaa79
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude@4x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter2/images/images.qrc b/examples/quickcontrols/chattutorial/chapter2/images/images.qrc
new file mode 100644
index 0000000000..9eda6aa29c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/images/images.qrc
@@ -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>
diff --git a/examples/quickcontrols/chattutorial/chapter2/main.cpp b/examples/quickcontrols/chattutorial/chapter2/main.cpp
new file mode 100644
index 0000000000..73d4bf3b63
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/main.cpp
@@ -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();
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter2/qmldir b/examples/quickcontrols/chattutorial/chapter2/qmldir
new file mode 100644
index 0000000000..1044557225
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/qmldir
@@ -0,0 +1,2 @@
+module chattutorial
+Main 1.0 Main.qml
diff --git a/examples/quickcontrols/chattutorial/chapter2/qtquickcontrols2.conf b/examples/quickcontrols/chattutorial/chapter2/qtquickcontrols2.conf
new file mode 100644
index 0000000000..db9486764e
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter2/qtquickcontrols2.conf
@@ -0,0 +1,2 @@
+[Controls]
+Style=Basic
diff --git a/examples/quickcontrols/chattutorial/chapter3/CMakeLists.txt b/examples/quickcontrols/chattutorial/chapter3/CMakeLists.txt
new file mode 100644
index 0000000000..a101f3ebb3
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/CMakeLists.txt
@@ -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)
+
+set(CMAKE_AUTOMOC ON)
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick)
+
+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})
diff --git a/examples/quickcontrols/chattutorial/chapter3/ContactPage.qml b/examples/quickcontrols/chattutorial/chapter3/ContactPage.qml
new file mode 100644
index 0000000000..939d911964
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/ContactPage.qml
@@ -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"
+ }
+ }
+ }
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter3/ConversationPage.qml b/examples/quickcontrols/chattutorial/chapter3/ConversationPage.qml
new file mode 100644
index 0000000000..9a4f1861bd
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/ConversationPage.qml
@@ -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
+ }
+ }
+ }
+ }
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter3/Main.qml b/examples/quickcontrols/chattutorial/chapter3/Main.qml
new file mode 100644
index 0000000000..bb968e9f7a
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/Main.qml
@@ -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 {}
+ }
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter3/chapter3.pro b/examples/quickcontrols/chattutorial/chapter3/chapter3.pro
new file mode 100644
index 0000000000..b0eebae94a
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/chapter3.pro
@@ -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
diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein.png b/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein.png
new file mode 100644
index 0000000000..7c44b90b8d
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein@2x.png b/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein@2x.png
new file mode 100644
index 0000000000..6ce9c39b6c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein@2x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein@3x.png b/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein@3x.png
new file mode 100644
index 0000000000..aab6a6a162
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein@3x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein@4x.png b/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein@4x.png
new file mode 100644
index 0000000000..3611284df4
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein@4x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway.png b/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway.png
new file mode 100644
index 0000000000..3ac8992fd9
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway@2x.png b/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway@2x.png
new file mode 100644
index 0000000000..122d9f1e53
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway@2x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway@3x.png b/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway@3x.png
new file mode 100644
index 0000000000..2fe9c2cc1c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway@3x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway@4x.png b/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway@4x.png
new file mode 100644
index 0000000000..96fb6788b5
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway@4x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude.png b/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude.png
new file mode 100644
index 0000000000..907e38bc61
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude@2x.png b/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude@2x.png
new file mode 100644
index 0000000000..6837796a83
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude@2x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude@3x.png b/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude@3x.png
new file mode 100644
index 0000000000..29af422ad3
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude@3x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude@4x.png b/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude@4x.png
new file mode 100644
index 0000000000..bc35eeaa79
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude@4x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter3/images/images.qrc b/examples/quickcontrols/chattutorial/chapter3/images/images.qrc
new file mode 100644
index 0000000000..9eda6aa29c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/images/images.qrc
@@ -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>
diff --git a/examples/quickcontrols/chattutorial/chapter3/main.cpp b/examples/quickcontrols/chattutorial/chapter3/main.cpp
new file mode 100644
index 0000000000..73d4bf3b63
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/main.cpp
@@ -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();
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter3/qmldir b/examples/quickcontrols/chattutorial/chapter3/qmldir
new file mode 100644
index 0000000000..16e455a37b
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/qmldir
@@ -0,0 +1,4 @@
+module chattutorial
+ContactPage 1.0 ContactPage.qml
+ConversationPage 1.0 ConversationPage.qml
+Main 1.0 Main.qml
diff --git a/examples/quickcontrols/chattutorial/chapter3/qtquickcontrols2.conf b/examples/quickcontrols/chattutorial/chapter3/qtquickcontrols2.conf
new file mode 100644
index 0000000000..db9486764e
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter3/qtquickcontrols2.conf
@@ -0,0 +1,2 @@
+[Controls]
+Style=Basic
diff --git a/examples/quickcontrols/chattutorial/chapter4/CMakeLists.txt b/examples/quickcontrols/chattutorial/chapter4/CMakeLists.txt
new file mode 100644
index 0000000000..2d5b930e45
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/CMakeLists.txt
@@ -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)
+
+set(CMAKE_AUTOMOC ON)
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick Sql)
+
+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})
diff --git a/examples/quickcontrols/chattutorial/chapter4/ContactPage.qml b/examples/quickcontrols/chattutorial/chapter4/ContactPage.qml
new file mode 100644
index 0000000000..2ed2243289
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/ContactPage.qml
@@ -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"
+ }
+ }
+ }
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter4/ConversationPage.qml b/examples/quickcontrols/chattutorial/chapter4/ConversationPage.qml
new file mode 100644
index 0000000000..87ed487c66
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/ConversationPage.qml
@@ -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 = ""
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter4/Main.qml b/examples/quickcontrols/chattutorial/chapter4/Main.qml
new file mode 100644
index 0000000000..bb968e9f7a
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/Main.qml
@@ -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 {}
+ }
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter4/chapter4.pro b/examples/quickcontrols/chattutorial/chapter4/chapter4.pro
new file mode 100644
index 0000000000..4f40fd726b
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/chapter4.pro
@@ -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
diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein.png b/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein.png
new file mode 100644
index 0000000000..7c44b90b8d
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein@2x.png b/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein@2x.png
new file mode 100644
index 0000000000..6ce9c39b6c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein@2x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein@3x.png b/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein@3x.png
new file mode 100644
index 0000000000..aab6a6a162
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein@3x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein@4x.png b/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein@4x.png
new file mode 100644
index 0000000000..3611284df4
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein@4x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway.png b/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway.png
new file mode 100644
index 0000000000..3ac8992fd9
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway@2x.png b/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway@2x.png
new file mode 100644
index 0000000000..122d9f1e53
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway@2x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway@3x.png b/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway@3x.png
new file mode 100644
index 0000000000..2fe9c2cc1c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway@3x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway@4x.png b/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway@4x.png
new file mode 100644
index 0000000000..96fb6788b5
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway@4x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude.png b/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude.png
new file mode 100644
index 0000000000..907e38bc61
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude@2x.png b/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude@2x.png
new file mode 100644
index 0000000000..6837796a83
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude@2x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude@3x.png b/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude@3x.png
new file mode 100644
index 0000000000..29af422ad3
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude@3x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude@4x.png b/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude@4x.png
new file mode 100644
index 0000000000..bc35eeaa79
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude@4x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter4/images/images.qrc b/examples/quickcontrols/chattutorial/chapter4/images/images.qrc
new file mode 100644
index 0000000000..9eda6aa29c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/images/images.qrc
@@ -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>
diff --git a/examples/quickcontrols/chattutorial/chapter4/main.cpp b/examples/quickcontrols/chattutorial/chapter4/main.cpp
new file mode 100644
index 0000000000..bcb6c1e923
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/main.cpp
@@ -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();
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter4/qmldir b/examples/quickcontrols/chattutorial/chapter4/qmldir
new file mode 100644
index 0000000000..27f99a4777
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/qmldir
@@ -0,0 +1,5 @@
+module chattutorial
+ContactPage 1.0 ContactPage.qml
+ConversationPage 1.0 ConversationPage.qml
+Main 1.0 Main.qml
+typeinfo chapter4.qmltypes
diff --git a/examples/quickcontrols/chattutorial/chapter4/qtquickcontrols2.conf b/examples/quickcontrols/chattutorial/chapter4/qtquickcontrols2.conf
new file mode 100644
index 0000000000..db9486764e
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/qtquickcontrols2.conf
@@ -0,0 +1,2 @@
+[Controls]
+Style=Basic
diff --git a/examples/quickcontrols/chattutorial/chapter4/sqlcontactmodel.cpp b/examples/quickcontrols/chattutorial/chapter4/sqlcontactmodel.cpp
new file mode 100644
index 0000000000..189924deec
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/sqlcontactmodel.cpp
@@ -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()));
+}
diff --git a/examples/quickcontrols/chattutorial/chapter4/sqlcontactmodel.h b/examples/quickcontrols/chattutorial/chapter4/sqlcontactmodel.h
new file mode 100644
index 0000000000..c7f9a154eb
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/sqlcontactmodel.h
@@ -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
diff --git a/examples/quickcontrols/chattutorial/chapter4/sqlconversationmodel.cpp b/examples/quickcontrols/chattutorial/chapter4/sqlconversationmodel.cpp
new file mode 100644
index 0000000000..5be01de52c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/sqlconversationmodel.cpp
@@ -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();
+}
diff --git a/examples/quickcontrols/chattutorial/chapter4/sqlconversationmodel.h b/examples/quickcontrols/chattutorial/chapter4/sqlconversationmodel.h
new file mode 100644
index 0000000000..b4917c0eff
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter4/sqlconversationmodel.h
@@ -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
diff --git a/examples/quickcontrols/chattutorial/chapter5/+Material/ChatToolBar.qml b/examples/quickcontrols/chattutorial/chapter5/+Material/ChatToolBar.qml
new file mode 100644
index 0000000000..1b46cfec4e
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/+Material/ChatToolBar.qml
@@ -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
+}
diff --git a/examples/quickcontrols/chattutorial/chapter5/CMakeLists.txt b/examples/quickcontrols/chattutorial/chapter5/CMakeLists.txt
new file mode 100644
index 0000000000..fa20c96d2d
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/CMakeLists.txt
@@ -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)
+
+set(CMAKE_AUTOMOC ON)
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick Sql)
+
+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})
diff --git a/examples/quickcontrols/chattutorial/chapter5/ChatToolBar.qml b/examples/quickcontrols/chattutorial/chapter5/ChatToolBar.qml
new file mode 100644
index 0000000000..af3a7a3f8c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/ChatToolBar.qml
@@ -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 {
+}
diff --git a/examples/quickcontrols/chattutorial/chapter5/ContactPage.qml b/examples/quickcontrols/chattutorial/chapter5/ContactPage.qml
new file mode 100644
index 0000000000..d7254a52b3
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/ContactPage.qml
@@ -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"
+ }
+ }
+ }
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter5/ConversationPage.qml b/examples/quickcontrols/chattutorial/chapter5/ConversationPage.qml
new file mode 100644
index 0000000000..db45deac5b
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/ConversationPage.qml
@@ -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 = ""
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter5/Main.qml b/examples/quickcontrols/chattutorial/chapter5/Main.qml
new file mode 100644
index 0000000000..bb968e9f7a
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/Main.qml
@@ -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 {}
+ }
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter5/chapter5.pro b/examples/quickcontrols/chattutorial/chapter5/chapter5.pro
new file mode 100644
index 0000000000..aba1e33cdb
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/chapter5.pro
@@ -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
diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein.png b/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein.png
new file mode 100644
index 0000000000..7c44b90b8d
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein@2x.png b/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein@2x.png
new file mode 100644
index 0000000000..6ce9c39b6c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein@2x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein@3x.png b/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein@3x.png
new file mode 100644
index 0000000000..aab6a6a162
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein@3x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein@4x.png b/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein@4x.png
new file mode 100644
index 0000000000..3611284df4
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein@4x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway.png b/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway.png
new file mode 100644
index 0000000000..3ac8992fd9
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway@2x.png b/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway@2x.png
new file mode 100644
index 0000000000..122d9f1e53
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway@2x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway@3x.png b/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway@3x.png
new file mode 100644
index 0000000000..2fe9c2cc1c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway@3x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway@4x.png b/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway@4x.png
new file mode 100644
index 0000000000..96fb6788b5
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway@4x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude.png b/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude.png
new file mode 100644
index 0000000000..907e38bc61
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude@2x.png b/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude@2x.png
new file mode 100644
index 0000000000..6837796a83
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude@2x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude@3x.png b/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude@3x.png
new file mode 100644
index 0000000000..29af422ad3
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude@3x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude@4x.png b/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude@4x.png
new file mode 100644
index 0000000000..bc35eeaa79
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude@4x.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/chapter5/images/images.qrc b/examples/quickcontrols/chattutorial/chapter5/images/images.qrc
new file mode 100644
index 0000000000..9eda6aa29c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/images/images.qrc
@@ -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>
diff --git a/examples/quickcontrols/chattutorial/chapter5/main.cpp b/examples/quickcontrols/chattutorial/chapter5/main.cpp
new file mode 100644
index 0000000000..bcb6c1e923
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/main.cpp
@@ -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();
+}
+
diff --git a/examples/quickcontrols/chattutorial/chapter5/qmldir b/examples/quickcontrols/chattutorial/chapter5/qmldir
new file mode 100644
index 0000000000..4ae7752b55
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/qmldir
@@ -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
diff --git a/examples/quickcontrols/chattutorial/chapter5/qtquickcontrols2.conf b/examples/quickcontrols/chattutorial/chapter5/qtquickcontrols2.conf
new file mode 100644
index 0000000000..c8a8eeeb05
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/qtquickcontrols2.conf
@@ -0,0 +1,10 @@
+[Controls]
+Style=Basic
+
+[Material]
+Primary=Indigo
+Accent=Indigo
+Theme=Dark
+
+[Universal]
+Theme=Dark
diff --git a/examples/quickcontrols/chattutorial/chapter5/sqlcontactmodel.cpp b/examples/quickcontrols/chattutorial/chapter5/sqlcontactmodel.cpp
new file mode 100644
index 0000000000..189924deec
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/sqlcontactmodel.cpp
@@ -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()));
+}
diff --git a/examples/quickcontrols/chattutorial/chapter5/sqlcontactmodel.h b/examples/quickcontrols/chattutorial/chapter5/sqlcontactmodel.h
new file mode 100644
index 0000000000..c7f9a154eb
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/sqlcontactmodel.h
@@ -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
diff --git a/examples/quickcontrols/chattutorial/chapter5/sqlconversationmodel.cpp b/examples/quickcontrols/chattutorial/chapter5/sqlconversationmodel.cpp
new file mode 100644
index 0000000000..5be01de52c
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/sqlconversationmodel.cpp
@@ -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();
+}
diff --git a/examples/quickcontrols/chattutorial/chapter5/sqlconversationmodel.h b/examples/quickcontrols/chattutorial/chapter5/sqlconversationmodel.h
new file mode 100644
index 0000000000..b4917c0eff
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chapter5/sqlconversationmodel.h
@@ -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
diff --git a/examples/quickcontrols/chattutorial/chattutorial.pro b/examples/quickcontrols/chattutorial/chattutorial.pro
new file mode 100644
index 0000000000..1c70b2653e
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/chattutorial.pro
@@ -0,0 +1,8 @@
+TEMPLATE = subdirs
+
+SUBDIRS += \
+ chapter1 \
+ chapter2 \
+ chapter3 \
+ chapter4 \
+ chapter5
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter1.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter1.png
new file mode 100644
index 0000000000..aa3c4bb5be
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter1.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter2-listview-header.gif b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter2-listview-header.gif
new file mode 100644
index 0000000000..17096519b0
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter2-listview-header.gif
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter2.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter2.png
new file mode 100644
index 0000000000..af25cff94e
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter2.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter3-listview-header.gif b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter3-listview-header.gif
new file mode 100644
index 0000000000..07eb93b191
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter3-listview-header.gif
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter3-view-margins.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter3-view-margins.png
new file mode 100644
index 0000000000..1c8ffbf46e
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter3-view-margins.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter3.gif b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter3.gif
new file mode 100644
index 0000000000..1763b1f3a5
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter3.gif
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter4-long-message.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter4-long-message.png
new file mode 100644
index 0000000000..b2f4c2adf8
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter4-long-message.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter4-message-timestamp.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter4-message-timestamp.png
new file mode 100644
index 0000000000..ea75b1dce5
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter4-message-timestamp.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter4.gif b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter4.gif
new file mode 100644
index 0000000000..dd47c4cbf8
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter4.gif
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-material-dark.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-material-dark.png
new file mode 100644
index 0000000000..8b8f05faa1
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-material-dark.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-material-test.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-material-test.png
new file mode 100644
index 0000000000..29e149df9f
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-material-test.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-material.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-material.png
new file mode 100644
index 0000000000..23d744e7be
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-material.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-universal-dark.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-universal-dark.png
new file mode 100644
index 0000000000..2aef535c39
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-universal-dark.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-universal.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-universal.png
new file mode 100644
index 0000000000..c18a341b42
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-contacts-universal.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-material-dark.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-material-dark.png
new file mode 100644
index 0000000000..f15c7d7d76
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-material-dark.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-material-test.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-material-test.png
new file mode 100644
index 0000000000..b33fc74ea6
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-material-test.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-material.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-material.png
new file mode 100644
index 0000000000..31833164d8
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-material.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-universal-dark.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-universal-dark.png
new file mode 100644
index 0000000000..b4bd7e9f4f
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-universal-dark.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-universal.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-universal.png
new file mode 100644
index 0000000000..09ed5ceb94
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols-chattutorial-chapter5-conversations-universal.png
Binary files differ
diff --git a/examples/quickcontrols/chattutorial/doc/src/qtquickcontrols-chattutorial.qdoc b/examples/quickcontrols/chattutorial/doc/src/qtquickcontrols-chattutorial.qdoc
new file mode 100644
index 0000000000..a42ef81575
--- /dev/null
+++ b/examples/quickcontrols/chattutorial/doc/src/qtquickcontrols-chattutorial.qdoc
@@ -0,0 +1,821 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+\page qtquickcontrols-chattutorial-example.html
+\keyword Qt Quick Controls - Chat Tutorial
+\title Qt Quick Controls - Chat Tutorial
+\keyword Qt Quick Controls 2 - Chat Tutorial
+\brief Tutorial about writing a basic chat client using Qt Quick Controls.
+\ingroup qtquickcontrols-examples
+
+This tutorial shows how to write a basic chat application using Qt Quick
+Controls. It will also explain how to integrate an SQL database into a Qt
+application.
+
+\section1 Chapter 1: Setting Up
+
+When setting up a new project, it's easiest to use
+\l {Qt Creator Manual}{Qt Creator}. For this project, we chose the
+\l {Qt Creator: Creating Qt Quick Projects}{Qt Quick application} template, which creates a
+basic "Hello World" application with the following files:
+
+\list
+\li \c CMakeLists.txt - Instructs CMake how our project should be built
+\li \c Main.qml - Provides a default UI containing an empty Window
+\li \c main.cpp - Loads \c main.qml
+\li \c qtquickcontrols2.conf - Tells the application which style it should use
+\endlist
+
+\section2 main.cpp
+
+The default code in \c main.cpp has two includes:
+
+\quotefromfile chattutorial/chapter1/main.cpp
+\skipto include
+\printline include
+\printline include
+
+The first gives us access to QGuiApplication. All Qt applications require
+an application object, but the precise type depends on what the application
+does. QCoreApplication is sufficient for non-graphical applications.
+QGuiApplication is sufficient for graphical applications that do not use
+\l {Qt Widgets}, while QApplication is required for those that do.
+
+The second include makes QQmlApplicationEngine available, allowing us to
+load our QML.
+
+Within \c main(), we set up the application object and QML engine:
+
+\skipto main
+\printuntil }
+
+\l QQmlApplicationEngine is a convenient wrapper over QQmlEngine, providing the
+\l {QQmlApplicationEngine::}{loadFromModule} function to easily load QML for an
+application. It also adds some convenience for using \l {Using File Selectors
+with Qt Quick Controls}{file selectors}.
+
+Once we've set up things in C++, we can move on to the user interface in QML.
+
+\section2 Main.qml
+
+Let's modify the default QML code to suit our needs.
+
+\quotefromfile chattutorial/chapter1/Main.qml
+\skipto import
+\printuntil import QtQuick.Controls
+
+You'll notice that the \l {Qt Quick} module has already been imported. This
+gives us access to graphical primitives such as \l Item, \l Rectangle, \l Text,
+and so on. For the full list of types, see the \l {Qt Quick QML Types}
+documentation.
+
+Add an import of the Qt Quick Controls module. Amongst other things, this
+provides access to \l ApplicationWindow, which will replace the existing
+root type, \c Window:
+
+\skipto ApplicationWindow
+\printuntil visible: true
+\dots
+\skipto }
+\skipuntil }
+\skipuntil }
+\printuntil }
+
+ApplicationWindow is a \l Window with some added convenience for creating a
+\l {ApplicationWindow::}{header} and a \l {ApplicationWindow::}{footer}.
+It also provides the foundation for \l {Popup}{popups} and supports some
+basic styling, such as the background \l {Window::}{color}.
+
+There are three properties that are almost always set when using
+ApplicationWindow: \l {Window::}{width}, \l {Window::}{height}, and
+\l {Window::}{visible}.
+Once we've set these, we have a properly sized, empty window ready to be
+filled with content.
+
+\note The \c title property from the default code is removed.
+
+The first \e "screen" in our application will be a list of contacts. It would
+be nice to have some text at the top of each screen that describes its purpose.
+The header and footer properties of ApplicationWindow could work in
+this situation. They have some characteristics that make them ideal for
+items that should be displayed on every screen of an application:
+
+\list
+\li They are anchored to the top and bottom of the window, respectively.
+\li They fill the width of the window.
+\endlist
+
+However, when the contents of the header and footer varies depending on
+which screen the user is viewing, it is much easier to use \l Page.
+For now, we'll just add one page, but in the next chapter, we'll demonstrate
+how to navigate between several pages.
+
+\quotefromfile chattutorial/chapter1/Main.qml
+\skipto Page
+\printuntil }
+\printuntil }
+
+First, we add a Page, which is sized to occupy all the space on the window
+using the \l {Item::}{anchors.fill} property.
+
+Then, we assign a \l Label to its \l {Page::}{header} property. Label extends
+the primitive \l Text item from the Qt Quick module by adding
+\l{Styling Qt Quick Controls}{styling} and \l {Control::}{font} inheritance.
+This means that a Label can look different depending on which style is in use,
+and can also propagate its pixel size to its children.
+
+We want some distance between the top of the application window and the text,
+so we set the \l {Text::padding}{padding} property. This allocates extra
+space on each side of the label (within its bounds). We can also explicitly set
+the \l {Text::}{topPadding} and \l {Text::}{bottomPadding} properties instead.
+
+We set the text of the label using the \c qsTr() function, which ensures that
+the text can be translated by \l {Writing Source Code for Translation}{Qt's
+translation system}. It's a good practice to follow for text that is visible to
+the end users of your application.
+
+By default, text is vertically aligned to the top of its bounds, while the
+horizontal alignment depends on the natural direction of the text; for example,
+text that is read from left to right will be aligned to the left. If we
+used these defaults, our text would be at the top-left corner of the window.
+This is not desirable for a header, so we align the text to the center of its
+bounds, both horizontally and vertically.
+
+\section2 The Project File
+
+The \c CMakeLists.txt file contains all of the information needed by \l {Build
+with CMake}{CMake} to build our project into an executable that we can run.
+
+For an in-depth explanation of this file, see \l {Building a QML application}.
+
+Here is what our application currently looks like when run:
+
+\borderedimage qtquickcontrols-chattutorial-chapter1.png
+
+\section1 Chapter 2: Lists
+
+In this chapter, we'll explain how to create a list of interactive items using
+\l ListView and \l ItemDelegate.
+
+ListView comes from the Qt Quick module, and displays a list of items
+populated from a \l {Models and Views in Qt Quick}{model}. ItemDelegate comes from
+the Qt Quick Controls module, and provides a standard view item for use in views
+and controls such as ListView and \l ComboBox. For example, each ItemDelegate
+can display text, be checked on and off, and react to mouse clicks.
+
+Here is our ListView:
+
+\quotefromfile chattutorial/chapter2/Main.qml
+\dots 8
+\codeline
+\skipto ListView
+\printuntil }
+\printuntil }
+\printuntil }
+\codeline
+\dots 8
+
+\section2 Sizing and Positioning
+
+The first thing we do is set a size for the view. It should fill the available
+space on the page, so we use \l {Item::}{anchors.fill}. Note that
+Page ensures that its header and footer have enough of their own space
+reserved, so the view in this case will sit below the header, for example.
+
+Next, we set \l {Flickable::leftMargin}{margins} around the ListView to put
+some distance between it and the edges of the window. The margin properties
+reserve space within the bounds of the view, which means that the empty areas
+can still be \e "flicked" by the user.
+
+The items should be nicely spaced out within the view, so the
+\l {ListView::}{spacing} property is set to \c 20.
+
+\section2 Model
+
+In order to quickly populate the view with some items, we've used a JavaScript
+array as the model. One of the greatest strengths of QML is its ability to
+make prototyping an application extremely quick, and this is an example of
+that. It's also possible to simply assign a \l {Integers as Models}{number} to
+the model property to indicate how many items you need. For example, if you
+assign \c 10 to the \c model property, each item's display text will be a
+number from \c 0 to \c 9.
+
+However, once the application gets past the prototype stage, it quickly becomes
+necessary to use some real data. For this, it's best to use a proper C++ model
+by \l {QAbstractItemModel}{subclassing QAbstractItemModel}.
+
+\section2 Delegate
+
+On to the \l {ListView::}{delegate}. We assign the corresponding text from the
+model to the \l {AbstractButton::text}{text} property of ItemDelegate. The exact
+manner in which the data from the model is made available to each delegate
+depends on the type of model used. See \l {Models and Views in Qt Quick} for
+more information.
+
+In our application, the width of each item in the view should be the same
+as the width of the view. This ensures that the user has a lot of room with
+which to select a contact from the list, which is an important factor on
+devices with small touch screens, like mobile phones. However, the width of the
+view includes our \c 48 pixel margins, so we must account for that in our
+assignment to the width property.
+
+Next, we define an \l Image. This will display a picture of the user's contact.
+The image will be \c 40 pixels wide and \c 40 pixels high. We'll base the
+height of the delegate on the image's height, so that we don't have any empty
+vertical space.
+
+\borderedimage qtquickcontrols-chattutorial-chapter2.png
+
+
+\section1 Chapter 3: Navigation
+
+In this chapter, you'll learn how to use \l StackView to navigate between pages
+in an application. Here's the revised \c main.qml:
+
+\quotefromfile chattutorial/chapter3/Main.qml
+\skipto import
+\printuntil }
+\printuntil }
+\printuntil }
+
+\section2 Navigating with StackView
+
+As its name suggests, StackView provides stack-based navigation. The last item
+to be \e "pushed" onto the stack is the first one to be removed, and the
+top-most item is always the one that is visible.
+
+In the same manner as we did with Page, we tell the StackView to fill the
+application window. The only thing left to do after that is to give it an item
+to display, via \l {StackView::}{initialItem}. StackView accepts
+\l {Item}{items}, \l {Component}{components} and \l [QML]{url}{URLs}.
+
+You'll notice that we moved the code for the contact list into
+\c ContactPage.qml. It's a good idea to do this as soon as you have a general
+idea of which screens your application will contain. Doing so not only makes
+your code easier to read, but ensures that items are only instantiated from
+a given component when completely necessary, reducing memory usage.
+
+\note Qt Creator provides several convenient \l {http://doc.qt.io/qtcreator/creator-editor-refactoring.html#refactoring-qml-code}{refactoring options for QML},
+one of which allows you to move a block of code into a separate file
+ (\c {Alt + Enter > Move Component into Separate File}).
+
+Another thing to consider when using ListView is whether to refer to it by
+\c id, or use the attached \l {ListView::view}{ListView.view}
+property. The best approach depends on a few different factors. Giving the
+view an id will result in shorter and more efficient binding expressions, as
+the attached property has a very small amount of overhead. However, if you plan
+on reusing the delegate in other views, it is better to use the attached
+properties to avoid tying the delegate to a particular view. For example, using
+the attached properties, the \c width assignment in our delegate becomes:
+
+\code
+width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin
+\endcode
+
+In chapter 2, we added a ListView below the header. If you run the application
+for that chapter, you'll see that the contents of the view can be scrolled over
+the top of the header:
+
+\borderedimage qtquickcontrols-chattutorial-chapter2-listview-header.gif
+
+This is not that nice, especially if the text in the
+delegates is long enough that it reaches the text in the header. What we
+ideally want to do is to have a solid block of color under the header text, but
+\e {above} the view. This ensures that the listview contents can't visually
+interfere with the header contents. Note that it's also possible to achieve
+this by setting the \l {Item::}{clip} property of the view to \c true, but
+doing so \l {Clipping}{can affect performance}.
+
+\l ToolBar is the right tool for this job. It is a container of both
+application-wide and context-sensitive actions and controls, such as navigation
+buttons and search fields. Best of all, it has a background color that, as
+usual, comes from the application style. Here it is in action:
+
+\quotefromfile chattutorial/chapter3/ContactPage.qml
+\skipto header
+\printuntil }
+\printuntil }
+
+\borderedimage qtquickcontrols-chattutorial-chapter3-listview-header.gif
+
+It has no layout of its own, so we center the label within it ourselves.
+
+The rest of the code is the same as it was in chapter 2, except that we've
+taken advantage of the \l {AbstractButton::}{clicked} signal to push the next
+page onto the stackview:
+
+\skipto onClicked
+\printline onClicked
+
+When pushing a \l Component or \l [QML] url onto StackView, it's often
+necessary to initialize the (eventually) instantiated item with some variables.
+StackView's \l {StackView::push}{push()} function accounts for this, by taking a JavaScript object
+as the second argument. We use this to provide the next page with a contact's
+name, which it then uses to display the relevant conversation. Note the
+\c {root.StackView.view.push} syntax; this is necessary because of how
+\l {A Note About Accessing Attached Properties and Signal Handlers}
+{attached properties} work.
+
+Let's step through \c ConversationPage.qml, beginning with the imports:
+
+\quotefromfile chattutorial/chapter3/ConversationPage.qml
+\skipto import
+\printline import
+\printline import
+\printline import
+
+These are the same as before, except for the addition of the \c QtQuick.Layouts
+import, which we'll cover shortly.
+
+\skipto Page
+\printuntil }
+\printuntil }
+\printuntil }
+\dots 4
+
+The root item of this component is another Page, which has a custom property
+called \c inConversationWith. For now, this property will simply determine what
+the label in the header displays. Later on, we'll use it in the SQL query that
+populates the list of messages in the conversation.
+
+To allow the user to go back to the Contact page, we add a \l ToolButton that
+calls \l {StackView::pop}{pop()} when clicked. A \l ToolButton is functionally
+similar to \l Button, but provides a look that is more suitable within a
+ToolBar.
+
+There are two ways of laying out items in QML: \l {Item Positioners}
+and \l {Qt Quick Layouts}. Item positioners (\l Row, \l Column, and so on) are
+useful for situations where the size of items is known or fixed, and all that
+is required is to neatly position them in a certain formation. The layouts in
+Qt Quick Layouts can both position and resize items, making them well suited
+for resizable user interfaces. Below, we use \l ColumnLayout to vertically
+lay out a ListView and a \l Pane:
+
+\skipto ColumnLayout
+\printto Layout.margins
+\codeline
+\dots 12
+\codeline
+\skipuntil ScrollBar
+\printline }
+\codeline
+\dots 8
+\codeline
+\printuntil Layout.fillWidth: true
+\dots 12
+\skipuntil }
+\skipuntil }
+\skipuntil }
+\skipuntil }
+\printline }
+
+Pane is basically a rectangle whose color comes from the application's style.
+It is similar to \l Frame, with the only difference being that it has no stroke
+around its border.
+
+Items that are direct children of a layout have various
+\l {Layout}{attached properties} available to them. We use
+\l {Layout::fillWidth}{Layout.fillWidth} and
+\l {Layout::fillHeight}{Layout.fillHeight} on the ListView to ensure
+that it takes as much space within the ColumnLayout as it can. The
+same is done for the Pane. As ColumnLayout is a vertical layout, there
+aren't any items to the left or right of each child, so this will result in
+each item consuming the entire width of the layout.
+
+On the other hand, the \l {Layout::fillHeight}{Layout.fillHeight} statement in
+the ListView will enable it to occupy the remaining space that is left after
+accommodating the Pane.
+
+Let's look at the listview in detail:
+
+\quotefromfile chattutorial/chapter3/ConversationPage.qml
+\skipto ListView
+\printuntil ScrollBar
+\printuntil }
+
+After filling the width and height of its parent, we also set some margins on
+the view. This gives us a nice alignment with the placeholder text in the
+"compose message" field:
+
+\borderedimage qtquickcontrols-chattutorial-chapter3-view-margins.png
+
+Next, we set \l {ListView::}{displayMarginBeginning} and \l
+{ListView::}{displayMarginEnd}. These properties ensure that the delegates
+outside the bounds of the view do not disappear while scrolling at the edges of
+the view. It's easiest to understand this by commenting out the properties and
+seeing what happens when scrolling the view.
+
+We then flip the vertical direction of the view, so that first items are at the
+bottom. The delegates are spaced out by 12 pixels, and a \e "dummy" model is
+assigned for testing purposes, until we implement the real model in chapter 4.
+
+Within the delegate, we declare a \l Row as the root item, as we want the
+avatar to be followed by the message contents, as shown in the image above.
+
+Messages sent by the user should be distinguished from those sent by a contact.
+For now, we set a dummy property \c sentByMe, which simply uses the index
+of the delegate to alternate between different authors. Using this property,
+we distinguish between different authors in three ways:
+
+\list
+\li Messages sent by the user are aligned to the right side of the screen
+by setting \c anchors.right to \c listView.contentItem.right.
+
+\li By setting the \c visible property of the avatar (which is simply a
+Rectangle for now) based on \c sentByMe, we only show it if the message was
+sent by a contact.
+
+\li We change the color of the rectangle depending on the author. Since we
+do not want to display dark text on a dark background, and vice versa, we also
+set the text color depending on who the author is. In chapter 5, we'll see how
+styling takes care of matters like this for us.
+\endlist
+
+At the bottom of the screen, we place a \l TextArea item to allow multi-line
+text input, and a button to send the message. We use Pane to cover the area
+under these two items, in the same way that we use ToolBar to prevent the
+contents of the listview from interfering with the page header:
+
+\skipto Pane
+\printuntil }
+\printuntil }
+\printuntil }
+\printuntil }
+
+The TextArea should fill the available width of the screen. We assign some
+placeholder text to provide a visual cue to the user as to where they should
+begin typing. The text within the input area is wrapped to ensure that it
+does not go outside of the screen.
+
+Finally, the button is only enabled when there is actually a message to send.
+
+\borderedimage qtquickcontrols-chattutorial-chapter3.gif
+
+
+\section1 Chapter 4: Models
+
+In chapter 4, we'll take you through the process of creating both read-only and
+read-write SQL models in C++ and exposing them to QML to populate views.
+
+\section2 QSqlQueryModel
+
+In order to keep the tutorial simple, we've chosen to make the list of user
+contacts non-editable. \l QSqlQueryModel is the logical choice for this
+purpose, as it provides a read-only data model for SQL result sets.
+
+Let's take a look at our \c SqlContactModel class that derives from
+QSqlQueryModel:
+
+\quotefromfile chattutorial/chapter4/sqlcontactmodel.h
+\skipto #include
+\printuntil };
+
+There's not much going on here, so let's move on to the \c .cpp file:
+
+\quotefromfile chattutorial/chapter4/sqlcontactmodel.cpp
+\skipto #include
+\printuntil }
+\printuntil }
+\printuntil }
+
+We include the header file of our class and those that we require from Qt. We
+then define a static function named \c createTable() that we'll use to create
+the SQL table (if it doesn't already exist), and then populate it with some
+dummy contacts.
+
+The call to \l {QSqlDatabase::database}{database()} might look a little bit
+confusing because we have not set up a specific database yet. If no connection
+name is passed to this function, it will return a \e {"default connection"},
+whose creation we will cover soon.
+
+\skipto SqlContactModel
+\printuntil }
+
+In the constructor, we call \c createTable(). We then construct a query that
+will be used to populate the model. In this case, we are simply interested in
+all rows of the \c Contacts table.
+
+\section2 QSqlTableModel
+
+\c SqlConversationModel is more complex:
+
+\quotefromfile chattutorial/chapter4/sqlconversationmodel.h
+\skipto #include
+\printuntil };
+
+We use both the \c Q_PROPERTY and \c Q_INVOKABLE macros, and therefore we must
+let \l {Using the Meta-Object Compiler (moc)}{moc} know by using the \c
+Q_OBJECT macro.
+
+The \c recipient property will be set from QML to let the model know which
+conversation it should retrieve messages for.
+
+We override the \l {QSqlTableModel::data}{data()} and
+\l {QAbstractItemModel::}{roleNames()} functions so that we can use our
+custom roles in QML.
+
+We also define the \c sendMessage() function that we want to call from
+QML, hence the \c Q_INVOKABLE macro.
+
+Let's take a look at the \c .cpp file:
+
+\quotefromfile chattutorial/chapter4/sqlconversationmodel.cpp
+\skipto #include
+\printuntil }
+\printuntil }
+\printuntil }
+
+This is very similar to \c sqlcontactmodel.cpp, with the exception that we are
+now operating on the \c Conversations table. We also define
+\c conversationsTableName as a static const variable, as we use it in a couple
+of places throughout the file.
+
+\skipto SqlConversationModel
+\printuntil }
+
+As with \c SqlContactModel, the first thing that we do in the constructor is
+create the table. We tell QSqlTableModel the name of the table we'll be using
+via the \l {QSqlTableModel::setTable}{setTable()} function. To ensure that the
+latest messages in the conversation are shown first, we sort the query results
+by the \c timestamp field in descending order. This goes hand in hand with
+setting ListView's \l {ListView::}{verticalLayoutDirection} property to
+\c ListView.BottomToTop (which we covered in chapter 3).
+
+\skipto ::recipient(
+\printuntil }
+\printuntil }
+
+In \c setRecipient(), we set a filter over the results returned from
+the database.
+
+\skipto ::data(
+\printuntil }
+
+The \c data() function falls back to QSqlTableModel's implementation if the
+role is not a custom user role. If the role is a user role, we can subtract
+Qt::UserRole from it to get the index of that field and then use that to find
+the value that we need to return.
+
+\skipto ::roleNames(
+\printuntil }
+
+In \c roleNames(), we return a mapping of our custom role values to role names.
+This enables us to use these roles in QML. It can be useful to declare an enum
+to hold all of the role values, but since we don't refer to any specific value
+in code outside of this function, we don't bother.
+
+\skipto ::sendMessage(
+\printuntil }
+
+The \c sendMessage() function uses the given \c recipient and a \c message to
+insert a new record into the database. Due to our usage
+of \l QSqlTableModel::OnManualSubmit, we must manually call
+\l {QSqlTableModel::submitAll}{submitAll()}.
+
+\section2 Connecting to the Database and Registering Types With QML
+
+Now that we've established the model classes, let's take a look at \c main.cpp:
+
+\quotefromfile chattutorial/chapter4/main.cpp
+\skipto #include
+\printuntil return app.exec();
+\printuntil }
+
+\c connectToDatabase() creates the connection to the SQLite database, creating
+the actual file if it doesn't already exist.
+
+Within \c main(), we call \l {qmlRegisterType}{qmlRegisterType()} to
+register our models as types within QML.
+
+\section2 Using the Models in QML
+
+Now that we have the models available as QML types, there are some minor
+changes to be done to \c ContactPage.qml. To be able to use the types,
+we must first import them using the URI we set in \c main.cpp:
+
+\quotefromfile chattutorial/chapter4/ContactPage.qml
+\skipto import chattutorial
+\printline import chattutorial
+
+We then replace the dummy model with the proper one:
+
+\skipto model: SqlContactModel {}
+\printline model: SqlContactModel {}
+
+Within the delegate, we use a different syntax for accessing the model data:
+
+\skipto text: model.display
+\printline text: model.display
+
+In \c ConversationPage.qml, we add the same \c chattutorial import, and replace
+the dummy model:
+
+\quotefromfile chattutorial/chapter4/ConversationPage.qml
+\skipto model: SqlConversationModel {
+\printuntil }
+
+Within the model, we set the \c recipient property to the name of the contact
+for which the page is being displayed.
+
+The root delegate item changes from a Row to a Column, to accommodate the
+timestamp that we want to display below every message:
+
+\skipto delegate: Column {
+\printuntil Label {
+\printuntil }
+\printuntil }
+\printuntil }
+\printuntil }
+\printuntil }
+
+\borderedimage qtquickcontrols-chattutorial-chapter4-message-timestamp.png
+
+Now that we have a proper model, we can use its \c recipient role in the
+expression for the \c sentByMe property.
+
+The Rectangle that was used for the avatar has been converted into an Image.
+The image has its own implicit size, so we don't need to specify it explicitly.
+As before, we only show the avatar when the author isn't the user, except this
+time we set the \c source of the image to an empty URL instead of using the
+\c visible property.
+
+We want each message background to be slightly wider (12 pixels each side) than
+its text. However, if it's too long, we want to limit its width to the edge
+of the listview, hence the usage of \c Math.min(). When the message wasn't sent
+by us, an avatar will always come before it, so we account for that by
+subtracting the width of the avatar and the row spacing.
+
+For example, in the image above, the implicit width of the message text is the
+smaller value. However, in the image below, the message text is quite long, so
+the smaller value (the width of the view) is chosen, ensuring that the text
+stops at the opposite edge of the screen:
+
+\borderedimage qtquickcontrols-chattutorial-chapter4-long-message.png
+
+In order to display the timestamp for each message that we discussed earlier,
+we use a Label. The date and time are formatted with
+\l {QtQml::Qt::formatDateTime}{Qt.formatDateTime()}, using a custom format.
+
+The \e "send" button must now react to being clicked:
+
+\skipto Button
+\printuntil }
+\printuntil }
+
+First, we call the invokable \c sendMessage() function of the model, which
+inserts a new row into the Conversations database table. Then, we clear the
+text field to make way for future input.
+
+\borderedimage qtquickcontrols-chattutorial-chapter4.gif
+
+
+\section1 Chapter 5: Styling
+
+Styles in Qt Quick Controls are designed to work on any platform. In this
+chapter, we'll do some minor visual tweaks to make sure our application
+looks good when run with the \l {Basic Style}{Basic},
+\l {Material Style}{Material}, and \l {Universal Style}{Universal} styles.
+
+So far, we've just been testing the application with the Basic style. If we
+run it with the \l {Material Style}, for example, we'll immediately see some issues.
+Here is the Contacts page:
+
+\borderedimage qtquickcontrols-chattutorial-chapter5-contacts-material-test.png
+
+The header text is black on a dark blue background, which is very difficult to
+read. The same thing occurs with the Conversations page:
+
+\borderedimage qtquickcontrols-chattutorial-chapter5-conversations-material-test.png
+
+The solution is to tell the toolbar that it should use the \e "Dark" theme, so
+that this information is propagated to its children, allowing them to switch
+their text color to something lighter. The simplest way of doing so is to
+import the Material style directly and use the Material attached property:
+
+\code
+ import QtQuick.Controls.Material 2.12
+
+ // ...
+
+ header: ToolBar {
+ Material.theme: Material.Dark
+
+ // ...
+ }
+\endcode
+
+However, this brings with it a hard dependency to the Material style; the
+Material style plugin \e must be deployed with the application, even if the
+target device doesn't use it, otherwise the QML engine will fail to find the
+import.
+
+Instead, it is better to rely on Qt Quick Controls's built-in support for
+\l {Using File Selectors with Qt Quick Controls}{style-based file selectors}.
+To do this, we must move the ToolBar out into its own file. We'll call it
+\c ChatToolBar.qml. This will be the \e "default" version of the file, which
+means that it will be used when the \l {Basic Style}{Basic style} (which is the
+style that is used when none is specified) is in
+use. Here's the new file:
+
+\quotefromfile chattutorial/chapter5/ChatToolBar.qml
+\skipto import
+\printuntil }
+
+As we only use the ToolBar type within this file, we only need the
+Qt Quick Controls import. The code itself has not changed from how it was
+in \c ContactPage.qml, which is how it should be; for the default version
+of the file, nothing needs to be different.
+
+Back in \c ContactPage.qml, we update the code to use the new type:
+
+\quotefromfile chattutorial/chapter5/ContactPage.qml
+\skipto ToolBar
+\printuntil }
+\printuntil }
+
+Now we need to add the Material version of the toolbar. File selectors expect
+variants of a file to be in appropriately named directories that exist
+alongside the default version of the file. This means that we need to add a
+folder named "+Material" in the same directory that ChatToolBar.qml is in:
+the root folder. The "+" is required by \l QFileSelector as a way of ensuring
+that the selection feature is not accidentally triggered.
+
+Here's \c +Material/ChatToolBar.qml:
+
+\quotefromfile chattutorial/chapter5/+Material/ChatToolBar.qml
+\skipto import
+\printuntil }
+
+We'll make the same changes to \c ConversationPage.qml:
+
+\quotefromfile chattutorial/chapter5/ConversationPage.qml
+\skipto header: ChatToolBar
+\printuntil }
+\printuntil }
+\printuntil }
+
+Now both pages look correct:
+
+\borderedimage qtquickcontrols-chattutorial-chapter5-contacts-material.png
+\borderedimage qtquickcontrols-chattutorial-chapter5-conversations-material.png
+
+Let's try out the Universal style:
+
+\borderedimage qtquickcontrols-chattutorial-chapter5-contacts-universal.png
+\borderedimage qtquickcontrols-chattutorial-chapter5-conversations-universal.png
+
+No issues there. For a relatively simple application such as this one, there
+should be very few adjustments necessary when switching styles.
+
+Now let's try each style's dark theme. The Basic style has no dark theme, as
+it would add a slight overhead to a style that is designed to be as performant
+as possible. We'll test out the Material style first, so add an entry to
+\c qtquickcontrols2.conf that tells it to use its dark theme:
+
+\code
+[Material]
+Primary=Indigo
+Accent=Indigo
+Theme=Dark
+\endcode
+
+Once this is done, build and run the application. This is what you should see:
+
+\borderedimage qtquickcontrols-chattutorial-chapter5-contacts-material-dark.png
+\borderedimage qtquickcontrols-chattutorial-chapter5-conversations-material-dark.png
+
+Both pages look fine. Now add an entry for the Universal style:
+
+\code
+[universal]
+Theme=Dark
+\endcode
+
+After building and running the application, you should see these results:
+
+\borderedimage qtquickcontrols-chattutorial-chapter5-contacts-universal-dark.png
+\borderedimage qtquickcontrols-chattutorial-chapter5-conversations-universal-dark.png
+
+
+\section1 Summary
+
+In this tutorial, we've taken you through the following steps of writing a
+basic application using Qt Quick Controls:
+
+\list
+\li Creating a new project using Qt Creator.
+\li Setting up a basic ApplicationWindow.
+\li Defining headers and footers with Page.
+\li Displaying content in a ListView.
+\li Refactoring components into their own files.
+\li Navigating between screens with StackView.
+\li Using layouts to allow an application to resize gracefully.
+\li Implementing both custom read-only and writable models that integrate an
+SQL database into the application.
+\li Integrating C++ with QML via \l Q_PROPERTY, \l Q_INVOKABLE, and
+\l qmlRegisterType().
+\li Testing and configuring multiple styles.
+\endlist
+
+*/