diff options
author | Mitch Curtis <[email protected]> | 2022-11-18 15:15:16 +0800 |
---|---|---|
committer | Mitch Curtis <[email protected]> | 2022-12-01 10:26:20 +0800 |
commit | 4bd87b903b355b53e3105ba1ae7c154c4e55cdaf (patch) | |
tree | cc2edb597f0d5871302eb86e9dda78217384a5aa /examples/quickcontrols/chattutorial | |
parent | 786e1748d4469c135a922a221024f3f9c421c0de (diff) |
Remove "2" from Qt Quick Controls directories
Qt Quick Controls 2 was named that way because it was a follow-up to
Qt Quick Controls 1.x. Now that Qt Quick Controls 1 is no longer
supported, we don't need to have "2" in the name. Work on this was
already started for the documentation in
1abdfe5d5a052f2298b7bf657513dfa7e0c66a56.
By doing this renaming a few weeks before feature freeze, it won't
affect the release but still results in as little time possible spent
manually fixing conflicts in cherry-picks from non-LTS releases as a
result of the renaming.
This patch does the following:
- Renames directories.
- Adapts CMakeLists.txt and other files to account for the new paths.
A follow-up patch will handle documentation.
It does not touch library names or other user-facing stuff, as that
will have to be done in Qt 7.
Task-number: QTBUG-95413
Change-Id: I170d8db19033ee71e495ff0c5c1a517a41ed7634
Reviewed-by: Mitch Curtis <[email protected]>
Diffstat (limited to 'examples/quickcontrols/chattutorial')
115 files changed, 2558 insertions, 0 deletions
diff --git a/examples/quickcontrols/chattutorial/CMakeLists.txt b/examples/quickcontrols/chattutorial/CMakeLists.txt new file mode 100644 index 0000000000..4da558f2f5 --- /dev/null +++ b/examples/quickcontrols/chattutorial/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_example(chapter1) +qt_internal_add_example(chapter2) +qt_internal_add_example(chapter3) +if (TARGET Qt::Sql) + qt_internal_add_example(chapter4) + qt_internal_add_example(chapter5) +endif() diff --git a/examples/quickcontrols/chattutorial/chapter1/CMakeLists.txt b/examples/quickcontrols/chattutorial/chapter1/CMakeLists.txt new file mode 100644 index 0000000000..3320012cb4 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter1/CMakeLists.txt @@ -0,0 +1,46 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(chapter1 LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/quickcontrols2/chattutorial/chapter1") + +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 + Qt::Core + Qt::Gui + Qt::Qml + Qt::Quick +) + +qt_add_qml_module(chattutorial-chapter1 + URI chapter1 + VERSION 1.0 + QML_FILES + "main.qml" +) + +qt6_add_resources(chattutorial-chapter1 "conf" + PREFIX + "/" + FILES + "qtquickcontrols2.conf" +) + +install(TARGETS chattutorial-chapter1 + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/quickcontrols/chattutorial/chapter1/chapter1.pro b/examples/quickcontrols/chattutorial/chapter1/chapter1.pro new file mode 100644 index 0000000000..6b50809f20 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter1/chapter1.pro @@ -0,0 +1,14 @@ +TEMPLATE = app + +QT += qml quick +CONFIG += c++11 + +SOURCES += main.cpp + +resources.files = main.qml +resources.prefix = chapter1/ +RESOURCES += resources \ + qtquickcontrols2.conf + +target.path = $$[QT_INSTALL_EXAMPLES]/quickcontrols2/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..3752f91d90 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter1/main.cpp @@ -0,0 +1,16 @@ +// Copyright (C) 2017 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.load(QUrl(QStringLiteral("qrc:/chapter1/main.qml"))); + + return app.exec(); +} + diff --git a/examples/quickcontrols/chattutorial/chapter1/main.qml b/examples/quickcontrols/chattutorial/chapter1/main.qml new file mode 100644 index 0000000000..d0651b5816 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter1/main.qml @@ -0,0 +1,23 @@ +// Copyright (C) 2017 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/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..c011336802 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/CMakeLists.txt @@ -0,0 +1,59 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(chapter2 LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/quickcontrols2/chattutorial/chapter2") + +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 + Qt::Core + Qt::Gui + Qt::Qml + Qt::Quick +) + +qt_add_qml_module(chattutorial-chapter2 + URI chapter2 + VERSION 1.0 + QML_FILES + "main.qml" + RESOURCES + "images/Albert_Einstein.png" + "images/[email protected]" + "images/[email protected]" + "images/[email protected]" + "images/Ernest_Hemingway.png" + "images/[email protected]" + "images/[email protected]" + "images/[email protected]" + "images/Hans_Gude.png" + "images/[email protected]" + "images/[email protected]" + "images/[email protected]" +) + +qt6_add_resources(chattutorial-chapter2 "conf" + PREFIX + "/" + FILES + "qtquickcontrols2.conf" +) + +install(TARGETS chattutorial-chapter2 + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/quickcontrols/chattutorial/chapter2/chapter2.pro b/examples/quickcontrols/chattutorial/chapter2/chapter2.pro new file mode 100644 index 0000000000..15429b7c89 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/chapter2.pro @@ -0,0 +1,27 @@ +TEMPLATE = app + +QT += qml quick +CONFIG += c++11 + +SOURCES += main.cpp + +resources.files = \ + images/Albert_Einstein.png \ + images/[email protected] \ + images/[email protected] \ + images/[email protected] \ + images/Ernest_Hemingway.png \ + images/[email protected] \ + images/[email protected] \ + images/[email protected] \ + images/Hans_Gude.png \ + images/[email protected] \ + images/[email protected] \ + images/[email protected] \ + main.qml +resources.prefix = chapter2/ +RESOURCES += resources \ + qtquickcontrols2.conf + +target.path = $$[QT_INSTALL_EXAMPLES]/quickcontrols2/chattutorial/chapter2 +INSTALLS += target diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein.png b/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein.png Binary files differnew file mode 100644 index 0000000000..7c44b90b8d --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/images/Albert_Einstein.png diff --git a/examples/quickcontrols/chattutorial/chapter2/images/[email protected] b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] Binary files differnew file mode 100644 index 0000000000..6ce9c39b6c --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter2/images/[email protected] b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] Binary files differnew file mode 100644 index 0000000000..aab6a6a162 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter2/images/[email protected] b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] Binary files differnew file mode 100644 index 0000000000..3611284df4 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway.png b/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway.png Binary files differnew file mode 100644 index 0000000000..3ac8992fd9 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/images/Ernest_Hemingway.png diff --git a/examples/quickcontrols/chattutorial/chapter2/images/[email protected] b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] Binary files differnew file mode 100644 index 0000000000..122d9f1e53 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter2/images/[email protected] b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] Binary files differnew file mode 100644 index 0000000000..2fe9c2cc1c --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter2/images/[email protected] b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] Binary files differnew file mode 100644 index 0000000000..96fb6788b5 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude.png b/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude.png Binary files differnew file mode 100644 index 0000000000..907e38bc61 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/images/Hans_Gude.png diff --git a/examples/quickcontrols/chattutorial/chapter2/images/[email protected] b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] Binary files differnew file mode 100644 index 0000000000..6837796a83 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter2/images/[email protected] b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] Binary files differnew file mode 100644 index 0000000000..29af422ad3 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter2/images/[email protected] b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] Binary files differnew file mode 100644 index 0000000000..bc35eeaa79 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/images/[email protected] 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>[email protected]</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>Ernest_Hemingway.png</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>Hans_Gude.png</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>[email protected]</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..eeb87becca --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/main.cpp @@ -0,0 +1,16 @@ +// Copyright (C) 2017 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.load(QUrl(QStringLiteral("qrc:/chapter2/main.qml"))); + + return app.exec(); +} + diff --git a/examples/quickcontrols/chattutorial/chapter2/main.qml b/examples/quickcontrols/chattutorial/chapter2/main.qml new file mode 100644 index 0000000000..851b482b01 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter2/main.qml @@ -0,0 +1,45 @@ +// Copyright (C) 2017 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 + } + + 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 { + text: modelData + width: listView.width - listView.leftMargin - listView.rightMargin + height: avatar.implicitHeight + leftPadding: avatar.implicitWidth + 32 + + Image { + id: avatar + source: "images/" + modelData.replace(" ", "_") + ".png" + } + } + } + } +} + 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..92545ec1d0 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/CMakeLists.txt @@ -0,0 +1,62 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(chapter3 LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/quickcontrols2/chattutorial/chapter3") + +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 + Qt::Core + Qt::Gui + Qt::Qml + Qt::Quick +) + +# Resources: +qt_add_qml_module(chattutorial-chapter3 + URI chapter3 + VERSION 1.0 + QML_FILES + "ContactPage.qml" + "ConversationPage.qml" + "main.qml" + RESOURCES + "images/Albert_Einstein.png" + "images/[email protected]" + "images/[email protected]" + "images/[email protected]" + "images/Ernest_Hemingway.png" + "images/[email protected]" + "images/[email protected]" + "images/[email protected]" + "images/Hans_Gude.png" + "images/[email protected]" + "images/[email protected]" + "images/[email protected]" +) + +qt6_add_resources(chattutorial-chapter3 "conf" + PREFIX + "/" + FILES + "qtquickcontrols2.conf" +) + +install(TARGETS chattutorial-chapter3 + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/quickcontrols/chattutorial/chapter3/ContactPage.qml b/examples/quickcontrols/chattutorial/chapter3/ContactPage.qml new file mode 100644 index 0000000000..88979a40b2 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/ContactPage.qml @@ -0,0 +1,41 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +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 { + text: modelData + width: listView.width - listView.leftMargin - listView.rightMargin + height: avatar.implicitHeight + leftPadding: avatar.implicitWidth + 32 + onClicked: root.StackView.view.push("ConversationPage.qml", { inConversationWith: modelData }) + + Image { + id: avatar + source: "images/" + 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..e61d014e6e --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/ConversationPage.qml @@ -0,0 +1,96 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +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: 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 { + readonly property bool sentByMe: index % 2 == 0 + + anchors.right: sentByMe ? listView.contentItem.right : undefined + spacing: 6 + + Rectangle { + id: avatar + width: height + height: parent.height + color: "grey" + visible: !sentByMe + } + + Rectangle { + width: 80 + height: 40 + color: sentByMe ? "lightgrey" : "steelblue" + + Label { + anchors.centerIn: parent + text: index + color: sentByMe ? "black" : "white" + } + } + } + + ScrollBar.vertical: ScrollBar {} + } + + Pane { + id: pane + Layout.fillWidth: true + + 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 + } + } + } + } +} + diff --git a/examples/quickcontrols/chattutorial/chapter3/chapter3.pro b/examples/quickcontrols/chattutorial/chapter3/chapter3.pro new file mode 100644 index 0000000000..42adc69fe9 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/chapter3.pro @@ -0,0 +1,29 @@ +TEMPLATE = app + +QT += qml quick +CONFIG += c++11 + +SOURCES += main.cpp + +resources.files = \ + ContactPage.qml \ + ConversationPage.qml \ + images/Albert_Einstein.png \ + images/[email protected] \ + images/[email protected] \ + images/[email protected] \ + images/Ernest_Hemingway.png \ + images/[email protected] \ + images/[email protected] \ + images/[email protected] \ + images/Hans_Gude.png \ + images/[email protected] \ + images/[email protected] \ + images/[email protected] \ + main.qml +resources.prefix = chapter3/ +RESOURCES += resources \ + qtquickcontrols2.conf + +target.path = $$[QT_INSTALL_EXAMPLES]/quickcontrols2/chattutorial/chapter3 +INSTALLS += target diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein.png b/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein.png Binary files differnew file mode 100644 index 0000000000..7c44b90b8d --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/images/Albert_Einstein.png diff --git a/examples/quickcontrols/chattutorial/chapter3/images/[email protected] b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] Binary files differnew file mode 100644 index 0000000000..6ce9c39b6c --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter3/images/[email protected] b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] Binary files differnew file mode 100644 index 0000000000..aab6a6a162 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter3/images/[email protected] b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] Binary files differnew file mode 100644 index 0000000000..3611284df4 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway.png b/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway.png Binary files differnew file mode 100644 index 0000000000..3ac8992fd9 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/images/Ernest_Hemingway.png diff --git a/examples/quickcontrols/chattutorial/chapter3/images/[email protected] b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] Binary files differnew file mode 100644 index 0000000000..122d9f1e53 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter3/images/[email protected] b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] Binary files differnew file mode 100644 index 0000000000..2fe9c2cc1c --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter3/images/[email protected] b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] Binary files differnew file mode 100644 index 0000000000..96fb6788b5 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude.png b/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude.png Binary files differnew file mode 100644 index 0000000000..907e38bc61 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/images/Hans_Gude.png diff --git a/examples/quickcontrols/chattutorial/chapter3/images/[email protected] b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] Binary files differnew file mode 100644 index 0000000000..6837796a83 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter3/images/[email protected] b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] Binary files differnew file mode 100644 index 0000000000..29af422ad3 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter3/images/[email protected] b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] Binary files differnew file mode 100644 index 0000000000..bc35eeaa79 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/images/[email protected] 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>[email protected]</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>Ernest_Hemingway.png</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>Hans_Gude.png</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>[email protected]</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..4347d3ff6c --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/main.cpp @@ -0,0 +1,16 @@ +// Copyright (C) 2017 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.load(QUrl(QStringLiteral("qrc:/chapter3/main.qml"))); + + return app.exec(); +} + diff --git a/examples/quickcontrols/chattutorial/chapter3/main.qml b/examples/quickcontrols/chattutorial/chapter3/main.qml new file mode 100644 index 0000000000..da080e3a45 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter3/main.qml @@ -0,0 +1,19 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +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/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..024430a495 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/CMakeLists.txt @@ -0,0 +1,65 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(chapter4 LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/quickcontrols2/chattutorial/chapter4") + +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 + Qt::Core + Qt::Gui + Qt::Qml + Qt::Quick + Qt::Sql +) + +# Resources: +qt_add_qml_module(chattutorial-chapter4 + URI chapter4 + VERSION 1.0 + QML_FILES + "ContactPage.qml" + "ConversationPage.qml" + "main.qml" + RESOURCES + "images/Albert_Einstein.png" + "images/[email protected]" + "images/[email protected]" + "images/[email protected]" + "images/Ernest_Hemingway.png" + "images/[email protected]" + "images/[email protected]" + "images/[email protected]" + "images/Hans_Gude.png" + "images/[email protected]" + "images/[email protected]" + "images/[email protected]" +) + +qt6_add_resources(chattutorial-chapter4 "conf" + PREFIX + "/" + FILES + "qtquickcontrols2.conf" +) + +install(TARGETS chattutorial-chapter4 + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/quickcontrols/chattutorial/chapter4/ContactPage.qml b/examples/quickcontrols/chattutorial/chapter4/ContactPage.qml new file mode 100644 index 0000000000..739d0b5402 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/ContactPage.qml @@ -0,0 +1,43 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +import io.qt.examples.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 { + text: model.display + width: listView.width - listView.leftMargin - listView.rightMargin + height: avatar.implicitHeight + leftPadding: avatar.implicitWidth + 32 + onClicked: root.StackView.view.push("ConversationPage.qml", { inConversationWith: model.display }) + + Image { + id: avatar + source: "images/" + 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..ef5b959aec --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/ConversationPage.qml @@ -0,0 +1,118 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +import io.qt.examples.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: 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: inConversationWith + } + delegate: Column { + anchors.right: sentByMe ? listView.contentItem.right : undefined + spacing: 6 + + readonly property bool sentByMe: model.recipient !== "Me" + + Row { + id: messageRow + spacing: 6 + anchors.right: sentByMe ? parent.right : undefined + + Image { + id: avatar + source: !sentByMe ? "images/" + model.author.replace(" ", "_") + ".png" : "" + } + + Rectangle { + width: Math.min(messageText.implicitWidth + 24, + listView.width - (!sentByMe ? avatar.width + messageRow.spacing : 0)) + height: messageText.implicitHeight + 24 + color: sentByMe ? "lightgrey" : "steelblue" + + Label { + id: messageText + text: model.message + color: sentByMe ? "black" : "white" + anchors.fill: parent + anchors.margins: 12 + wrapMode: Label.Wrap + } + } + } + + Label { + id: timestampText + text: Qt.formatDateTime(model.timestamp, "d MMM hh:mm") + color: "lightgrey" + anchors.right: sentByMe ? parent.right : undefined + } + } + + ScrollBar.vertical: ScrollBar {} + } + + Pane { + id: pane + Layout.fillWidth: true + + 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 + onClicked: { + listView.model.sendMessage(inConversationWith, messageField.text); + messageField.text = ""; + } + } + } + } + } +} + diff --git a/examples/quickcontrols/chattutorial/chapter4/chapter4.pro b/examples/quickcontrols/chattutorial/chapter4/chapter4.pro new file mode 100644 index 0000000000..ae8141f7f1 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/chapter4.pro @@ -0,0 +1,34 @@ +TEMPLATE = app + +QT += qml quick sql +CONFIG += c++11 + +HEADERS += sqlcontactmodel.h \ + sqlconversationmodel.h + +SOURCES += main.cpp \ + sqlcontactmodel.cpp \ + sqlconversationmodel.cpp + +resources.files = \ + ContactPage.qml \ + ConversationPage.qml \ + images/Albert_Einstein.png \ + images/[email protected] \ + images/[email protected] \ + images/[email protected] \ + images/Ernest_Hemingway.png \ + images/[email protected] \ + images/[email protected] \ + images/[email protected] \ + images/Hans_Gude.png \ + images/[email protected] \ + images/[email protected] \ + images/[email protected] \ + main.qml +resources.prefix = chapter4/ +RESOURCES += resources \ + qtquickcontrols2.conf + +target.path = $$[QT_INSTALL_EXAMPLES]/quickcontrols2/chattutorial/chapter4 +INSTALLS += target diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein.png b/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein.png Binary files differnew file mode 100644 index 0000000000..7c44b90b8d --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/images/Albert_Einstein.png diff --git a/examples/quickcontrols/chattutorial/chapter4/images/[email protected] b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] Binary files differnew file mode 100644 index 0000000000..6ce9c39b6c --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter4/images/[email protected] b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] Binary files differnew file mode 100644 index 0000000000..aab6a6a162 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter4/images/[email protected] b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] Binary files differnew file mode 100644 index 0000000000..3611284df4 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway.png b/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway.png Binary files differnew file mode 100644 index 0000000000..3ac8992fd9 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/images/Ernest_Hemingway.png diff --git a/examples/quickcontrols/chattutorial/chapter4/images/[email protected] b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] Binary files differnew file mode 100644 index 0000000000..122d9f1e53 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter4/images/[email protected] b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] Binary files differnew file mode 100644 index 0000000000..2fe9c2cc1c --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter4/images/[email protected] b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] Binary files differnew file mode 100644 index 0000000000..96fb6788b5 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude.png b/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude.png Binary files differnew file mode 100644 index 0000000000..907e38bc61 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/images/Hans_Gude.png diff --git a/examples/quickcontrols/chattutorial/chapter4/images/[email protected] b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] Binary files differnew file mode 100644 index 0000000000..6837796a83 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter4/images/[email protected] b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] Binary files differnew file mode 100644 index 0000000000..29af422ad3 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter4/images/[email protected] b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] Binary files differnew file mode 100644 index 0000000000..bc35eeaa79 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/images/[email protected] 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>[email protected]</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>Ernest_Hemingway.png</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>Hans_Gude.png</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>[email protected]</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..0ca281c7d9 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/main.cpp @@ -0,0 +1,52 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include <QtCore> +#include <QGuiApplication> +#include <QSqlDatabase> +#include <QSqlError> +#include <QtQml> + +#include "sqlcontactmodel.h" +#include "sqlconversationmodel.h" + +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); + + qmlRegisterType<SqlContactModel>("io.qt.examples.chattutorial", 1, 0, "SqlContactModel"); + qmlRegisterType<SqlConversationModel>("io.qt.examples.chattutorial", 1, 0, "SqlConversationModel"); + + connectToDatabase(); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/chapter4/main.qml"))); + if (engine.rootObjects().isEmpty()) + return -1; + + return app.exec(); +} + diff --git a/examples/quickcontrols/chattutorial/chapter4/main.qml b/examples/quickcontrols/chattutorial/chapter4/main.qml new file mode 100644 index 0000000000..da080e3a45 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/main.qml @@ -0,0 +1,19 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +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/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..0bccc0d7af --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/sqlcontactmodel.cpp @@ -0,0 +1,43 @@ +// Copyright (C) 2017 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(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..a6d24a4774 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/sqlcontactmodel.h @@ -0,0 +1,15 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SQLCONTACTMODEL_H +#define SQLCONTACTMODEL_H + +#include <QSqlQueryModel> + +class SqlContactModel : public QSqlQueryModel +{ +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..1a1594b094 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/sqlconversationmodel.cpp @@ -0,0 +1,107 @@ +// Copyright (C) 2017 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..8a3cdd4c43 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter4/sqlconversationmodel.h @@ -0,0 +1,32 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SQLCONVERSATIONMODEL_H +#define SQLCONVERSATIONMODEL_H + +#include <QSqlTableModel> + +class SqlConversationModel : public QSqlTableModel +{ + Q_OBJECT + 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..3fa10c7f13 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/+Material/ChatToolBar.qml @@ -0,0 +1,9 @@ +// Copyright (C) 2017 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..657684fc34 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/CMakeLists.txt @@ -0,0 +1,67 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(chapter5 LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/quickcontrols2/chattutorial/chapter5") + +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 + Qt::Core + Qt::Gui + Qt::Qml + Qt::Quick + Qt::Sql +) + +# Resources: +qt_add_qml_module(chattutorial-chapter5 + URI chapter5 + VERSION 1.0 + QML_FILES + "+Material/ChatToolBar.qml" + "ChatToolBar.qml" + "ContactPage.qml" + "ConversationPage.qml" + "main.qml" + RESOURCES + "images/Albert_Einstein.png" + "images/[email protected]" + "images/[email protected]" + "images/[email protected]" + "images/Ernest_Hemingway.png" + "images/[email protected]" + "images/[email protected]" + "images/[email protected]" + "images/Hans_Gude.png" + "images/[email protected]" + "images/[email protected]" + "images/[email protected]" +) + +qt6_add_resources(chattutorial-chapter5 "conf" + PREFIX + "/" + FILES + "qtquickcontrols2.conf" +) + +install(TARGETS chattutorial-chapter5 + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/quickcontrols/chattutorial/chapter5/ChatToolBar.qml b/examples/quickcontrols/chattutorial/chapter5/ChatToolBar.qml new file mode 100644 index 0000000000..73f2c655a9 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/ChatToolBar.qml @@ -0,0 +1,7 @@ +// Copyright (C) 2017 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..920c824090 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/ContactPage.qml @@ -0,0 +1,43 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Controls + +import io.qt.examples.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 { + text: model.display + width: listView.width - listView.leftMargin - listView.rightMargin + height: avatar.implicitHeight + leftPadding: avatar.implicitWidth + 32 + onClicked: root.StackView.view.push("ConversationPage.qml", { inConversationWith: model.display }) + + Image { + id: avatar + source: "images/" + 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..6e209b7a85 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/ConversationPage.qml @@ -0,0 +1,117 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +import io.qt.examples.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: 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: inConversationWith + } + delegate: Column { + anchors.right: sentByMe ? listView.contentItem.right : undefined + spacing: 6 + + readonly property bool sentByMe: model.recipient !== "Me" + + Row { + id: messageRow + spacing: 6 + anchors.right: sentByMe ? parent.right : undefined + + Image { + id: avatar + source: !sentByMe ? "images/" + model.author.replace(" ", "_") + ".png" : "" + } + + Rectangle { + width: Math.min(messageText.implicitWidth + 24, listView.width - avatar.width - messageRow.spacing) + height: messageText.implicitHeight + 24 + color: sentByMe ? "lightgrey" : "steelblue" + + Label { + id: messageText + text: model.message + color: sentByMe ? "black" : "white" + anchors.fill: parent + anchors.margins: 12 + wrapMode: Label.Wrap + } + } + } + + Label { + id: timestampText + text: Qt.formatDateTime(model.timestamp, "d MMM hh:mm") + color: "lightgrey" + anchors.right: sentByMe ? parent.right : undefined + } + } + + ScrollBar.vertical: ScrollBar {} + } + + Pane { + id: pane + Layout.fillWidth: true + + 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 + onClicked: { + listView.model.sendMessage(inConversationWith, messageField.text); + messageField.text = ""; + } + } + } + } + } +} + diff --git a/examples/quickcontrols/chattutorial/chapter5/chapter5.pro b/examples/quickcontrols/chattutorial/chapter5/chapter5.pro new file mode 100644 index 0000000000..625642d7b5 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/chapter5.pro @@ -0,0 +1,36 @@ +TEMPLATE = app + +QT += qml quick sql +CONFIG += c++11 + +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/[email protected] \ + images/[email protected] \ + images/[email protected] \ + images/Ernest_Hemingway.png \ + images/[email protected] \ + images/[email protected] \ + images/[email protected] \ + images/Hans_Gude.png \ + images/[email protected] \ + images/[email protected] \ + images/[email protected] \ + main.qml +resources.prefix = chapter5/ +RESOURCES += resources \ + qtquickcontrols2.conf + +target.path = $$[QT_INSTALL_EXAMPLES]/quickcontrols2/chattutorial/chapter5 +INSTALLS += target diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein.png b/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein.png Binary files differnew file mode 100644 index 0000000000..7c44b90b8d --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/images/Albert_Einstein.png diff --git a/examples/quickcontrols/chattutorial/chapter5/images/[email protected] b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] Binary files differnew file mode 100644 index 0000000000..6ce9c39b6c --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter5/images/[email protected] b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] Binary files differnew file mode 100644 index 0000000000..aab6a6a162 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter5/images/[email protected] b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] Binary files differnew file mode 100644 index 0000000000..3611284df4 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway.png b/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway.png Binary files differnew file mode 100644 index 0000000000..3ac8992fd9 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/images/Ernest_Hemingway.png diff --git a/examples/quickcontrols/chattutorial/chapter5/images/[email protected] b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] Binary files differnew file mode 100644 index 0000000000..122d9f1e53 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter5/images/[email protected] b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] Binary files differnew file mode 100644 index 0000000000..2fe9c2cc1c --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter5/images/[email protected] b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] Binary files differnew file mode 100644 index 0000000000..96fb6788b5 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude.png b/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude.png Binary files differnew file mode 100644 index 0000000000..907e38bc61 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/images/Hans_Gude.png diff --git a/examples/quickcontrols/chattutorial/chapter5/images/[email protected] b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] Binary files differnew file mode 100644 index 0000000000..6837796a83 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter5/images/[email protected] b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] Binary files differnew file mode 100644 index 0000000000..29af422ad3 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] diff --git a/examples/quickcontrols/chattutorial/chapter5/images/[email protected] b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] Binary files differnew file mode 100644 index 0000000000..bc35eeaa79 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/images/[email protected] 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>[email protected]</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>Ernest_Hemingway.png</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>Hans_Gude.png</file> + <file>[email protected]</file> + <file>[email protected]</file> + <file>[email protected]</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..cfa5aa6f57 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/main.cpp @@ -0,0 +1,52 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include <QtCore> +#include <QGuiApplication> +#include <QSqlDatabase> +#include <QSqlError> +#include <QtQml> + +#include "sqlcontactmodel.h" +#include "sqlconversationmodel.h" + +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); + + qmlRegisterType<SqlContactModel>("io.qt.examples.chattutorial", 1, 0, "SqlContactModel"); + qmlRegisterType<SqlConversationModel>("io.qt.examples.chattutorial", 1, 0, "SqlConversationModel"); + + connectToDatabase(); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/chapter5/main.qml"))); + if (engine.rootObjects().isEmpty()) + return -1; + + return app.exec(); +} + diff --git a/examples/quickcontrols/chattutorial/chapter5/main.qml b/examples/quickcontrols/chattutorial/chapter5/main.qml new file mode 100644 index 0000000000..da080e3a45 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/main.qml @@ -0,0 +1,19 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +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/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..0bccc0d7af --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/sqlcontactmodel.cpp @@ -0,0 +1,43 @@ +// Copyright (C) 2017 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(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..a6d24a4774 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/sqlcontactmodel.h @@ -0,0 +1,15 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SQLCONTACTMODEL_H +#define SQLCONTACTMODEL_H + +#include <QSqlQueryModel> + +class SqlContactModel : public QSqlQueryModel +{ +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..1a1594b094 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/sqlconversationmodel.cpp @@ -0,0 +1,107 @@ +// Copyright (C) 2017 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..8a3cdd4c43 --- /dev/null +++ b/examples/quickcontrols/chattutorial/chapter5/sqlconversationmodel.h @@ -0,0 +1,32 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef SQLCONVERSATIONMODEL_H +#define SQLCONVERSATIONMODEL_H + +#include <QSqlTableModel> + +class SqlConversationModel : public QSqlTableModel +{ + Q_OBJECT + 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/qtquickcontrols2-chattutorial-chapter1.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter1.png Binary files differnew file mode 100644 index 0000000000..aa3c4bb5be --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter1.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter2-listview-header.gif b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter2-listview-header.gif Binary files differnew file mode 100644 index 0000000000..17096519b0 --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter2-listview-header.gif diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter2.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter2.png Binary files differnew file mode 100644 index 0000000000..af25cff94e --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter2.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter3-listview-header.gif b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter3-listview-header.gif Binary files differnew file mode 100644 index 0000000000..07eb93b191 --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter3-listview-header.gif diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter3-view-margins.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter3-view-margins.png Binary files differnew file mode 100644 index 0000000000..1c8ffbf46e --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter3-view-margins.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter3.gif b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter3.gif Binary files differnew file mode 100644 index 0000000000..1763b1f3a5 --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter3.gif diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter4-long-message.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter4-long-message.png Binary files differnew file mode 100644 index 0000000000..b2f4c2adf8 --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter4-long-message.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter4-message-timestamp.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter4-message-timestamp.png Binary files differnew file mode 100644 index 0000000000..ea75b1dce5 --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter4-message-timestamp.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter4.gif b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter4.gif Binary files differnew file mode 100644 index 0000000000..dd47c4cbf8 --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter4.gif diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-material-dark.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-material-dark.png Binary files differnew file mode 100644 index 0000000000..8b8f05faa1 --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-material-dark.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-material-test.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-material-test.png Binary files differnew file mode 100644 index 0000000000..29e149df9f --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-material-test.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-material.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-material.png Binary files differnew file mode 100644 index 0000000000..23d744e7be --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-material.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-universal-dark.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-universal-dark.png Binary files differnew file mode 100644 index 0000000000..2aef535c39 --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-universal-dark.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-universal.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-universal.png Binary files differnew file mode 100644 index 0000000000..c18a341b42 --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-contacts-universal.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-material-dark.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-material-dark.png Binary files differnew file mode 100644 index 0000000000..f15c7d7d76 --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-material-dark.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-material-test.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-material-test.png Binary files differnew file mode 100644 index 0000000000..b33fc74ea6 --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-material-test.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-material.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-material.png Binary files differnew file mode 100644 index 0000000000..31833164d8 --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-material.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-universal-dark.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-universal-dark.png Binary files differnew file mode 100644 index 0000000000..b4bd7e9f4f --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-universal-dark.png diff --git a/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-universal.png b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-universal.png Binary files differnew file mode 100644 index 0000000000..09ed5ceb94 --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/images/qtquickcontrols2-chattutorial-chapter5-conversations-universal.png diff --git a/examples/quickcontrols/chattutorial/doc/src/qtquickcontrols2-chattutorial.qdoc b/examples/quickcontrols/chattutorial/doc/src/qtquickcontrols2-chattutorial.qdoc new file mode 100644 index 0000000000..52228b900d --- /dev/null +++ b/examples/quickcontrols/chattutorial/doc/src/qtquickcontrols2-chattutorial.qdoc @@ -0,0 +1,874 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! +\example chattutorial +\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 qtquickcontrols2-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 MainForm.ui.qml - Defines the default UI +\li \c main.qml - Embeds the default UI in a Window +\li \c qml.qrc - Lists the \c .qml files that are built into the binary +\li \c main.cpp - Loads \c main.qml +\li \c chattutorial.pro - Provides the qmake configuration +\endlist + +\note Delete the \c MainForm.ui.qml and \c qml.qrc files from the project, as +we will not use them in this tutorial. + +\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, along with +some useful functions required for making C++ types accessible from QML. + +Within \c main(), we set up the application object and QML engine: + +\skipto main +\printuntil } + +It begins with enabling \l {High DPI}{high DPI scaling}, which is not +part of the default code. It is necessary to do so before the application +object is constructed. + +After that's done, we construct the application object, passing any application +arguments provided by the user. + +Next, the QML engine is created. \l QQmlApplicationEngine is a convenient +wrapper over QQmlEngine, providing the \l {QQmlApplicationEngine::load}{load()} +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 + +First, import the \l {Qt Quick} module. 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. + +Next, import 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 } +\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 } + +We replace the default \c{MainForm {...}} code block with 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 .pro or \l {Creating Project Files}{project} file contains all of the +information needed by \l {qmake Manual}{qmake} to generate a Makefile, which is +then used to compile and link the application. + +\quotefromfile chattutorial/chapter1/chapter1.pro +\printline TEMPLATE + +The first line tells \c qmake which kind of project this is. We're building an +application, so we use the \c app template. + +\printline QT + +The next line declares the Qt libraries that we want to use from C++. + +\printline CONFIG + +This line states that a C++11 compatible compiler is required to build the +project. + +\printline SOURCES + +The \c SOURCES variable lists all of the source files that should be compiled. +A similar variable, \c HEADERS, is available for header files. + +\printuntil qtquickcontrols2 + +The next line tells \c qmake that we have a collection of +\l {The Qt Resource System}{resources} that should be built into the +executable. + +\skipto target +\printline + +This line replaces deployment settings that come with the default project file. +It determines where the example is copied, on running "\c{make install}". + +Now we can build and run the application: + +\borderedimage qtquickcontrols2-chattutorial-chapter1.png + +\noautolist +\generatelist examplefiles .*chapter1.* + +\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 qtquickcontrols2-chattutorial-chapter2.png + +\generatelist examplefiles .*(chapter2|shared).* +\generatelist exampleimages .*shared.*(Einstein|Hemingway|Gude)\.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 qtquickcontrols2-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 qtquickcontrols2-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 qtquickcontrols2-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 qtquickcontrols2-chattutorial-chapter3.gif + +\generatelist examplefiles .*(chapter3|shared).* +\generatelist exampleimages .*shared.*(Einstein|Hemingway|Gude)\.png + +\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 io.qt.examples.chattutorial +\printline import io.qt.examples.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 qtquickcontrols2-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 qtquickcontrols2-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 qtquickcontrols2-chattutorial-chapter4.gif + +\generatelist examplefiles .*(chapter4|shared).* +\generatelist exampleimages + +\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 qtquickcontrols2-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 qtquickcontrols2-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 qtquickcontrols2-chattutorial-chapter5-contacts-material.png +\borderedimage qtquickcontrols2-chattutorial-chapter5-conversations-material.png + +Let's try out the Universal style: + +\borderedimage qtquickcontrols2-chattutorial-chapter5-contacts-universal.png +\borderedimage qtquickcontrols2-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 qtquickcontrols2-chattutorial-chapter5-contacts-material-dark.png +\borderedimage qtquickcontrols2-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 qtquickcontrols2-chattutorial-chapter5-contacts-universal-dark.png +\borderedimage qtquickcontrols2-chattutorial-chapter5-conversations-universal-dark.png + +\generatelist examplefiles .*(chapter5|shared).* +\generatelist exampleimages + +\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 + +*/ |