diff options
Diffstat (limited to 'examples/corelib')
144 files changed, 2637 insertions, 2514 deletions
diff --git a/examples/corelib/CMakeLists.txt b/examples/corelib/CMakeLists.txt index 638db2dda9..38a883b4ea 100644 --- a/examples/corelib/CMakeLists.txt +++ b/examples/corelib/CMakeLists.txt @@ -1,17 +1,14 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause add_subdirectory(ipc) add_subdirectory(mimetypes) add_subdirectory(serialization) add_subdirectory(tools) add_subdirectory(platform) -if(QT_FEATURE_permissions) - add_subdirectory(permissions) -endif() if(QT_FEATURE_thread) add_subdirectory(threads) endif() if(QT_FEATURE_widgets) - add_subdirectory(bindableproperties) + qt_internal_add_example(bindableproperties) endif() diff --git a/examples/corelib/bindableproperties/CMakeLists.txt b/examples/corelib/bindableproperties/CMakeLists.txt index c6d9076fd8..07a2b7a2cf 100644 --- a/examples/corelib/bindableproperties/CMakeLists.txt +++ b/examples/corelib/bindableproperties/CMakeLists.txt @@ -1,2 +1,33 @@ -qt_internal_add_example(bindablesubscription) -qt_internal_add_example(subscription) +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(bindableproperties LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_standard_project_setup() + +add_subdirectory(shared) +add_subdirectory(subscription) +add_subdirectory(bindablesubscription) + +install(TARGETS subscription bindablesubscription + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET subscription + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR +) +install(SCRIPT ${deploy_script}) + +qt_generate_deploy_app_script( + TARGET bindablesubscription + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR +) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/bindableproperties/bindablesubscription/CMakeLists.txt b/examples/corelib/bindableproperties/bindablesubscription/CMakeLists.txt index c4a2e5fcfc..2734f44a17 100644 --- a/examples/corelib/bindableproperties/bindablesubscription/CMakeLists.txt +++ b/examples/corelib/bindableproperties/bindablesubscription/CMakeLists.txt @@ -1,50 +1,15 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -cmake_minimum_required(VERSION 3.16) -project(bindablesubscription LANGUAGES CXX) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/bindableproperties/bindablesubscription") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) - -qt_standard_project_setup() +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause qt_add_executable(bindablesubscription - ../shared/subscriptionwindow.cpp ../shared/subscriptionwindow.h ../shared/subscriptionwindow.ui main.cpp - bindablesubscription.cpp bindablesubscription.h - bindableuser.cpp bindableuser.h + bindablesubscription.cpp + bindablesubscription.h + bindableuser.cpp + bindableuser.h ) target_link_libraries(bindablesubscription PRIVATE - Qt6::Core - Qt6::Gui - Qt6::Widgets + bindableproperties_shared ) -# Resources: -set(countries_resource_files - "../shared/finland.png" - "../shared/germany.png" - "../shared/norway.png" -) - -qt_add_resources(bindablesubscription "countries" - PREFIX - "/" - BASE - "../shared" - FILES - ${countries_resource_files} -) - -install(TARGETS bindablesubscription - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/corelib/bindableproperties/bindablesubscription/bindablesubscription.cpp b/examples/corelib/bindableproperties/bindablesubscription/bindablesubscription.cpp index a52b68f85b..32f4194635 100644 --- a/examples/corelib/bindableproperties/bindablesubscription/bindablesubscription.cpp +++ b/examples/corelib/bindableproperties/bindablesubscription/bindablesubscription.cpp @@ -10,7 +10,8 @@ BindableSubscription::BindableSubscription(BindableUser *user) : m_user(user) { Q_ASSERT(user); - m_price.setBinding([this] { return qRound(calculateDiscount() * m_duration * basePrice()); }); + m_price.setBinding( + [this] { return qRound(calculateDiscount() * int(m_duration) * basePrice()); }); m_isValid.setBinding([this] { return m_user->country() != BindableUser::Country::AnyCountry && m_user->age() > 12; @@ -38,8 +39,7 @@ double BindableSubscription::calculateDiscount() const case Yearly: return 0.6; } - Q_ASSERT(false); - return -1; + Q_UNREACHABLE_RETURN(-1); } int BindableSubscription::basePrice() const diff --git a/examples/corelib/bindableproperties/bindablesubscription/bindablesubscription.h b/examples/corelib/bindableproperties/bindablesubscription/bindablesubscription.h index 3406693b94..03870d0617 100644 --- a/examples/corelib/bindableproperties/bindablesubscription/bindablesubscription.h +++ b/examples/corelib/bindableproperties/bindablesubscription/bindablesubscription.h @@ -4,7 +4,7 @@ #ifndef BINDABLESUBSCRIPTION_H #define BINDABLESUBSCRIPTION_H -#include <QPointer> +#include <QBindable> #include <QProperty> class BindableUser; diff --git a/examples/corelib/bindableproperties/bindablesubscription/bindableuser.h b/examples/corelib/bindableproperties/bindablesubscription/bindableuser.h index d172a7cb22..6bb9bcdcb5 100644 --- a/examples/corelib/bindableproperties/bindablesubscription/bindableuser.h +++ b/examples/corelib/bindableproperties/bindablesubscription/bindableuser.h @@ -4,6 +4,7 @@ #ifndef BINDABLEUSER_H #define BINDABLEUSER_H +#include <QBindable> #include <QLocale> #include <QProperty> diff --git a/examples/corelib/bindableproperties/bindablesubscription/main.cpp b/examples/corelib/bindableproperties/bindablesubscription/main.cpp index 6cf73c1337..466f487b8e 100644 --- a/examples/corelib/bindableproperties/bindablesubscription/main.cpp +++ b/examples/corelib/bindableproperties/bindablesubscription/main.cpp @@ -6,11 +6,15 @@ #include "bindableuser.h" #include <QApplication> -#include <QButtonGroup> +#include <QBindable> #include <QLabel> +#include <QLocale> #include <QPushButton> #include <QRadioButton> #include <QSpinBox> +#include <QString> + +using namespace Qt::StringLiterals; int main(int argc, char *argv[]) { @@ -19,41 +23,42 @@ int main(int argc, char *argv[]) BindableSubscription subscription(&user); SubscriptionWindow w; + // clazy:excludeall=lambda-in-connect + // when subscription is out of scope so is window // Initialize subscription data - QRadioButton *monthly = w.findChild<QRadioButton *>("btnMonthly"); - QObject::connect(monthly, &QRadioButton::clicked, [&] { + QRadioButton *monthly = w.findChild<QRadioButton *>(u"btnMonthly"_s); + QObject::connect(monthly, &QRadioButton::clicked, monthly, [&] { subscription.setDuration(BindableSubscription::Monthly); }); - QRadioButton *quarterly = w.findChild<QRadioButton *>("btnQuarterly"); - QObject::connect(quarterly, &QRadioButton::clicked, [&] { + QRadioButton *quarterly = w.findChild<QRadioButton *>(u"btnQuarterly"_s); + QObject::connect(quarterly, &QRadioButton::clicked, quarterly, [&] { subscription.setDuration(BindableSubscription::Quarterly); }); - QRadioButton *yearly = w.findChild<QRadioButton *>("btnYearly"); - QObject::connect(yearly, &QRadioButton::clicked, [&] { + QRadioButton *yearly = w.findChild<QRadioButton *>(u"btnYearly"_s); + QObject::connect(yearly, &QRadioButton::clicked, yearly, [&] { subscription.setDuration(BindableSubscription::Yearly); }); // Initialize user data - QPushButton *germany = w.findChild<QPushButton *>("btnGermany"); - QObject::connect(germany, &QPushButton::clicked, [&] { + QPushButton *germany = w.findChild<QPushButton *>(u"btnGermany"_s); + QObject::connect(germany, &QPushButton::clicked, germany, [&] { user.setCountry(BindableUser::Country::Germany); }); - QPushButton *finland = w.findChild<QPushButton *>("btnFinland"); - QObject::connect(finland, &QPushButton::clicked, [&] { + QPushButton *finland = w.findChild<QPushButton *>(u"btnFinland"_s); + QObject::connect(finland, &QPushButton::clicked, finland, [&] { user.setCountry(BindableUser::Country::Finland); }); - QPushButton *norway = w.findChild<QPushButton *>("btnNorway"); - QObject::connect(norway, &QPushButton::clicked, [&] { + QPushButton *norway = w.findChild<QPushButton *>(u"btnNorway"_s); + QObject::connect(norway, &QPushButton::clicked, norway, [&] { user.setCountry(BindableUser::Country::Norway); }); - QSpinBox *ageSpinBox = w.findChild<QSpinBox *>("ageSpinBox"); - QObject::connect(ageSpinBox, &QSpinBox::valueChanged, [&](int value) { - user.setAge(value); - }); + QSpinBox *ageSpinBox = w.findChild<QSpinBox *>(u"ageSpinBox"_s); + QBindable<int> ageBindable(ageSpinBox, "value"); + user.bindableAge().setBinding([ageBindable](){ return ageBindable.value();}); - QLabel *priceDisplay = w.findChild<QLabel *>("priceDisplay"); + QLabel *priceDisplay = w.findChild<QLabel *>(u"priceDisplay"_s); // Track price changes //! [update-ui] diff --git a/examples/corelib/bindableproperties/doc/src/bindableproperties.qdoc b/examples/corelib/bindableproperties/doc/src/bindableproperties.qdoc index e63662dfbb..476522b086 100644 --- a/examples/corelib/bindableproperties/doc/src/bindableproperties.qdoc +++ b/examples/corelib/bindableproperties/doc/src/bindableproperties.qdoc @@ -3,7 +3,8 @@ /*! \example bindableproperties - \title Bindable Properties Example + \examplecategory {Data Processing & I/O} + \title Bindable Properties \brief Demonstrates how the usage of bindable properties can simplify your C++ code. diff --git a/examples/corelib/bindableproperties/shared/CMakeLists.txt b/examples/corelib/bindableproperties/shared/CMakeLists.txt new file mode 100644 index 0000000000..efc85e5d4d --- /dev/null +++ b/examples/corelib/bindableproperties/shared/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +add_library(bindableproperties_shared STATIC + subscriptionwindow.cpp + subscriptionwindow.h + subscriptionwindow.ui +) + +target_link_libraries(bindableproperties_shared PUBLIC + Qt6::Core + Qt6::Gui + Qt6::Widgets +) + +qt_add_resources(bindableproperties_shared "countries" + PREFIX + "/" + FILES + "finland.png" + "germany.png" + "norway.png" +) diff --git a/examples/corelib/bindableproperties/subscription/CMakeLists.txt b/examples/corelib/bindableproperties/subscription/CMakeLists.txt index 0dd027fc24..91b9340fbf 100644 --- a/examples/corelib/bindableproperties/subscription/CMakeLists.txt +++ b/examples/corelib/bindableproperties/subscription/CMakeLists.txt @@ -1,50 +1,12 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -cmake_minimum_required(VERSION 3.16) -project(subscription LANGUAGES CXX) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/bindableproperties/subscription") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) - -qt_standard_project_setup() +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause qt_add_executable(subscription - ../shared/subscriptionwindow.cpp ../shared/subscriptionwindow.h ../shared/subscriptionwindow.ui main.cpp subscription.cpp subscription.h user.cpp user.h ) target_link_libraries(subscription PRIVATE - Qt6::Core - Qt6::Gui - Qt6::Widgets -) - -# Resources: -set(countries_resource_files - "../shared/finland.png" - "../shared/germany.png" - "../shared/norway.png" -) - -qt_add_resources(subscription "countries" - PREFIX - "/" - BASE - "../shared" - FILES - ${countries_resource_files} -) - -install(TARGETS subscription - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + bindableproperties_shared ) diff --git a/examples/corelib/bindableproperties/subscription/main.cpp b/examples/corelib/bindableproperties/subscription/main.cpp index 1f41486728..3f98da7467 100644 --- a/examples/corelib/bindableproperties/subscription/main.cpp +++ b/examples/corelib/bindableproperties/subscription/main.cpp @@ -6,11 +6,14 @@ #include "user.h" #include <QApplication> -#include <QButtonGroup> #include <QLabel> +#include <QLocale> #include <QPushButton> #include <QRadioButton> #include <QSpinBox> +#include <QString> + +using namespace Qt::StringLiterals; int main(int argc, char *argv[]) { @@ -24,65 +27,65 @@ int main(int argc, char *argv[]) SubscriptionWindow w; // Initialize subscription data - QRadioButton *monthly = w.findChild<QRadioButton *>("btnMonthly"); + QRadioButton *monthly = w.findChild<QRadioButton *>(u"btnMonthly"_s); QObject::connect(monthly, &QRadioButton::clicked, &subscription, [&] { subscription.setDuration(Subscription::Monthly); }); - QRadioButton *quarterly = w.findChild<QRadioButton *>("btnQuarterly"); + QRadioButton *quarterly = w.findChild<QRadioButton *>(u"btnQuarterly"_s); QObject::connect(quarterly, &QRadioButton::clicked, &subscription, [&] { subscription.setDuration(Subscription::Quarterly); }); - QRadioButton *yearly = w.findChild<QRadioButton *>("btnYearly"); + QRadioButton *yearly = w.findChild<QRadioButton *>(u"btnYearly"_s); QObject::connect(yearly, &QRadioButton::clicked, &subscription, [&] { subscription.setDuration(Subscription::Yearly); }); // Initialize user data - QPushButton *germany = w.findChild<QPushButton *>("btnGermany"); + QPushButton *germany = w.findChild<QPushButton *>(u"btnGermany"_s); QObject::connect(germany, &QPushButton::clicked, &user, [&] { user.setCountry(User::Country::Germany); }); - QPushButton *finland = w.findChild<QPushButton *>("btnFinland"); + QPushButton *finland = w.findChild<QPushButton *>(u"btnFinland"_s); QObject::connect(finland, &QPushButton::clicked, &user, [&] { user.setCountry(User::Country::Finland); }); - QPushButton *norway = w.findChild<QPushButton *>("btnNorway"); + QPushButton *norway = w.findChild<QPushButton *>(u"btnNorway"_s); QObject::connect(norway, &QPushButton::clicked, &user, [&] { user.setCountry(User::Country::Norway); }); - QSpinBox *ageSpinBox = w.findChild<QSpinBox *>("ageSpinBox"); + QSpinBox *ageSpinBox = w.findChild<QSpinBox *>(u"ageSpinBox"_s); QObject::connect(ageSpinBox, &QSpinBox::valueChanged, &user, [&](int value) { user.setAge(value); }); // Initialize price data - QLabel *priceDisplay = w.findChild<QLabel *>("priceDisplay"); + QLabel *priceDisplay = w.findChild<QLabel *>(u"priceDisplay"_s); priceDisplay->setText(QString::number(subscription.price())); priceDisplay->setEnabled(subscription.isValid()); // Track the price changes //! [connect-price-changed] - QObject::connect(&subscription, &Subscription::priceChanged, [&] { + QObject::connect(&subscription, &Subscription::priceChanged, priceDisplay, [&] { QLocale lc{QLocale::AnyLanguage, user.country()}; priceDisplay->setText(lc.toCurrencyString(subscription.price() / subscription.duration())); }); //! [connect-price-changed] //! [connect-validity-changed] - QObject::connect(&subscription, &Subscription::isValidChanged, [&] { + QObject::connect(&subscription, &Subscription::isValidChanged, priceDisplay, [&] { priceDisplay->setEnabled(subscription.isValid()); }); //! [connect-validity-changed] //! [connect-user] - QObject::connect(&user, &User::countryChanged, [&] { + QObject::connect(&user, &User::countryChanged, &subscription, [&] { subscription.calculatePrice(); subscription.updateValidity(); }); - QObject::connect(&user, &User::ageChanged, [&] { + QObject::connect(&user, &User::ageChanged, &subscription, [&] { subscription.updateValidity(); }); //! [connect-user] diff --git a/examples/corelib/bindableproperties/subscription/subscription.cpp b/examples/corelib/bindableproperties/subscription/subscription.cpp index 5d040f76d9..85cc5798cc 100644 --- a/examples/corelib/bindableproperties/subscription/subscription.cpp +++ b/examples/corelib/bindableproperties/subscription/subscription.cpp @@ -15,7 +15,7 @@ void Subscription::calculatePrice() { const auto oldPrice = m_price; - m_price = qRound(calculateDiscount() * m_duration * basePrice()); + m_price = qRound(calculateDiscount() * int(m_duration) * basePrice()); if (m_price != oldPrice) emit priceChanged(); } diff --git a/examples/corelib/ipc/CMakeLists.txt b/examples/corelib/ipc/CMakeLists.txt index d1cfc7bc1b..270f71faec 100644 --- a/examples/corelib/ipc/CMakeLists.txt +++ b/examples/corelib/ipc/CMakeLists.txt @@ -1,10 +1,10 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause if(NOT TARGET Qt6::Widgets) return() endif() -if(QT_FEATURE_sharedmemory) +if(QT_FEATURE_sharedmemory AND QT_FEATURE_systemsemaphore) qt_internal_add_example(sharedmemory) endif() if(QT_FEATURE_localserver AND TARGET Qt6::Network) diff --git a/examples/corelib/ipc/doc/images/localfortuneserver-example.png b/examples/corelib/ipc/doc/images/localfortuneserver-example.png Binary files differindex 2f04c7528e..778c7c85b0 100644 --- a/examples/corelib/ipc/doc/images/localfortuneserver-example.png +++ b/examples/corelib/ipc/doc/images/localfortuneserver-example.png diff --git a/examples/corelib/ipc/doc/src/localfortuneclient.qdoc b/examples/corelib/ipc/doc/src/localfortuneclient.qdoc index f7fa46c932..a2bdb69b8b 100644 --- a/examples/corelib/ipc/doc/src/localfortuneclient.qdoc +++ b/examples/corelib/ipc/doc/src/localfortuneclient.qdoc @@ -3,14 +3,16 @@ /*! \example ipc/localfortuneclient - \title Local Fortune Client Example + \examplecategory {Connectivity} + \title Local Fortune Client \ingroup examples-ipc \brief Demonstrates using QLocalSocket for a simple local service client. The Local Fortune Client example shows how to create a client for a simple local service using QLocalSocket. It is intended to be run alongside the - \l{Local Fortune Server Example}. + \l{Local Fortune Server} example. - \image localfortuneclient-example.png Screenshot of the Local Fortune Client example + \image localfortuneclient-example.png Screenshot of the Local Fortune Client + example */ diff --git a/examples/corelib/ipc/doc/src/localfortuneserver.qdoc b/examples/corelib/ipc/doc/src/localfortuneserver.qdoc index 281fd3b4be..6b359a8680 100644 --- a/examples/corelib/ipc/doc/src/localfortuneserver.qdoc +++ b/examples/corelib/ipc/doc/src/localfortuneserver.qdoc @@ -3,13 +3,14 @@ /*! \example ipc/localfortuneserver - \title Local Fortune Server Example + \examplecategory {Connectivity} + \title Local Fortune Server \ingroup examples-ipc \brief Demonstrates using QLocalServer and QLocalSocket for serving a simple local service. The Local Fortune Server example shows how to create a server for a simple local service. It is intended to be run alongside the - \l{Local Fortune Client Example} + \l{Local Fortune Client} example. \image localfortuneserver-example.png Screenshot of the Local Fortune Server example */ diff --git a/examples/corelib/ipc/doc/src/sharedmemory.qdoc b/examples/corelib/ipc/doc/src/sharedmemory.qdoc index 7ea4ffb25d..80645f3495 100644 --- a/examples/corelib/ipc/doc/src/sharedmemory.qdoc +++ b/examples/corelib/ipc/doc/src/sharedmemory.qdoc @@ -3,10 +3,11 @@ /*! \example ipc/sharedmemory - \title Shared Memory Example + \examplecategory {Data Processing & I/O} + \title IPC: Shared Memory \ingroup examples-ipc - \brief Demonstrates doing inter-process communication using shared memory with - the QSharedMemory class. + \brief Demonstrates how to share image data between different processes + using the Shared Memory IPC mechanism. The Shared Memory example shows how to use the QSharedMemory class to implement inter-process communication using shared memory. To diff --git a/examples/corelib/ipc/localfortuneclient/CMakeLists.txt b/examples/corelib/ipc/localfortuneclient/CMakeLists.txt index b3337e1f46..f3f2b13f92 100644 --- a/examples/corelib/ipc/localfortuneclient/CMakeLists.txt +++ b/examples/corelib/ipc/localfortuneclient/CMakeLists.txt @@ -1,15 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(localfortuneclient LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/ipc/localfortuneclient") - find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets) qt_standard_project_setup() @@ -32,7 +26,14 @@ target_link_libraries(localfortuneclient PRIVATE ) install(TARGETS localfortuneclient - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET localfortuneclient + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/ipc/localfortuneclient/client.cpp b/examples/corelib/ipc/localfortuneclient/client.cpp index 31f8cf475b..b71409560b 100644 --- a/examples/corelib/ipc/localfortuneclient/client.cpp +++ b/examples/corelib/ipc/localfortuneclient/client.cpp @@ -1,14 +1,19 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include <QtWidgets> -#include <QtNetwork> - #include "client.h" +#include <QDialogButtonBox> +#include <QGridLayout> +#include <QGuiApplication> +#include <QMessageBox> +#include <QTimer> + +using namespace Qt::StringLiterals; + Client::Client(QWidget *parent) : QDialog(parent), - hostLineEdit(new QLineEdit("fortune")), + hostLineEdit(new QLineEdit(u"fortune"_s)), getFortuneButton(new QPushButton(tr("Get Fortune"))), statusLabel(new QLabel(tr("This examples requires that you run the " "Local Fortune Server example as well."))), @@ -28,7 +33,7 @@ Client::Client(QWidget *parent) buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole); in.setDevice(socket); - in.setVersion(QDataStream::Qt_5_10); + in.setVersion(QDataStream::Qt_6_0); connect(hostLineEdit, &QLineEdit::textChanged, this, &Client::enableGetFortuneButton); @@ -58,20 +63,14 @@ void Client::requestNewFortune() void Client::readFortune() { - if (blockSize == 0) { - // Relies on the fact that QDataStream serializes a quint32 into - // sizeof(quint32) bytes - if (socket->bytesAvailable() < (int)sizeof(quint32)) - return; - in >> blockSize; - } - - if (socket->bytesAvailable() < blockSize || in.atEnd()) - return; + in.startTransaction(); QString nextFortune; in >> nextFortune; + if (!in.commitTransaction()) + return; + if (nextFortune == currentFortune) { QTimer::singleShot(0, this, &Client::requestNewFortune); return; diff --git a/examples/corelib/ipc/localfortuneclient/client.h b/examples/corelib/ipc/localfortuneclient/client.h index c7275252fe..b4c949a21a 100644 --- a/examples/corelib/ipc/localfortuneclient/client.h +++ b/examples/corelib/ipc/localfortuneclient/client.h @@ -4,15 +4,12 @@ #ifndef CLIENT_H #define CLIENT_H -#include <QDialog> #include <QDataStream> +#include <QDialog> +#include <QLabel> +#include <QLineEdit> #include <QLocalSocket> - -QT_BEGIN_NAMESPACE -class QLabel; -class QLineEdit; -class QPushButton; -QT_END_NAMESPACE +#include <QPushButton> class Client : public QDialog { diff --git a/examples/corelib/ipc/localfortuneclient/main.cpp b/examples/corelib/ipc/localfortuneclient/main.cpp index 3c2a7b284c..f52807ec48 100644 --- a/examples/corelib/ipc/localfortuneclient/main.cpp +++ b/examples/corelib/ipc/localfortuneclient/main.cpp @@ -1,10 +1,10 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include <QApplication> - #include "client.h" +#include <QApplication> + int main(int argc, char *argv[]) { QApplication app(argc, argv); diff --git a/examples/corelib/ipc/localfortuneserver/CMakeLists.txt b/examples/corelib/ipc/localfortuneserver/CMakeLists.txt index 411fc04eb5..75f037171c 100644 --- a/examples/corelib/ipc/localfortuneserver/CMakeLists.txt +++ b/examples/corelib/ipc/localfortuneserver/CMakeLists.txt @@ -1,15 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(localfortuneserver LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/ipc/localfortuneserver") - find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets) qt_standard_project_setup() @@ -32,7 +26,14 @@ target_link_libraries(localfortuneserver PRIVATE ) install(TARGETS localfortuneserver - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET localfortuneserver + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/ipc/localfortuneserver/main.cpp b/examples/corelib/ipc/localfortuneserver/main.cpp index 291a6b3f22..c18fa17dfb 100644 --- a/examples/corelib/ipc/localfortuneserver/main.cpp +++ b/examples/corelib/ipc/localfortuneserver/main.cpp @@ -1,10 +1,10 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include <QApplication> - #include "server.h" +#include <QApplication> + int main(int argc, char *argv[]) { QApplication app(argc, argv); diff --git a/examples/corelib/ipc/localfortuneserver/server.cpp b/examples/corelib/ipc/localfortuneserver/server.cpp index bfdf425f5d..a602a57f72 100644 --- a/examples/corelib/ipc/localfortuneserver/server.cpp +++ b/examples/corelib/ipc/localfortuneserver/server.cpp @@ -3,27 +3,33 @@ #include "server.h" -#include <QtWidgets> -#include <QtNetwork> +#include <QDialogButtonBox> +#include <QGuiApplication> +#include <QHBoxLayout> +#include <QLabel> +#include <QLineEdit> +#include <QLocalSocket> +#include <QMessageBox> +#include <QPushButton> +#include <QRandomGenerator> + +using namespace Qt::StringLiterals; + +static const QString idleStateText = QObject::tr("Press \"Listen\" to start the server"); Server::Server(QWidget *parent) - : QDialog(parent) + : QDialog(parent), + server(new QLocalServer(this)), + hostLineEdit(new QLineEdit(u"fortune"_s)), + statusLabel(new QLabel(idleStateText)), + listenButton(new QPushButton(tr("Listen"))), + stopListeningButton(new QPushButton(tr("Stop Listening"))) { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - server = new QLocalServer(this); - if (!server->listen("fortune")) { - QMessageBox::critical(this, tr("Local Fortune Server"), - tr("Unable to start the server: %1.") - .arg(server->errorString())); - close(); - return; - } - - QLabel *statusLabel = new QLabel; statusLabel->setWordWrap(true); - statusLabel->setText(tr("The server is running.\n" - "Run the Local Fortune Client example now.")); + + stopListeningButton->setDisabled(true); fortunes << tr("You've been leading a dog's life. Stay off the furniture.") << tr("You've got to think about tomorrow.") @@ -33,31 +39,73 @@ Server::Server(QWidget *parent) << tr("You cannot kill time without injuring eternity.") << tr("Computers are not intelligent. They only think they are."); + QLabel *hostLabel = new QLabel(tr("Server name:")); + + connect(server, &QLocalServer::newConnection, this, &Server::sendFortune); + connect(hostLineEdit, &QLineEdit::textChanged, this, &Server::toggleListenButton); + connect(listenButton, &QPushButton::clicked, this, &Server::listenToServer); + connect(stopListeningButton, &QPushButton::clicked,this, &Server::stopListening); + QPushButton *quitButton = new QPushButton(tr("Quit")); quitButton->setAutoDefault(false); connect(quitButton, &QPushButton::clicked, this, &Server::close); - connect(server, &QLocalServer::newConnection, this, &Server::sendFortune); - QHBoxLayout *buttonLayout = new QHBoxLayout; - buttonLayout->addStretch(1); - buttonLayout->addWidget(quitButton); - buttonLayout->addStretch(1); + QDialogButtonBox *buttonBox = new QDialogButtonBox; + buttonBox->addButton(listenButton, QDialogButtonBox::ActionRole); + buttonBox->addButton(stopListeningButton, QDialogButtonBox::ActionRole); + buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole); - QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->addWidget(statusLabel); - mainLayout->addLayout(buttonLayout); + QGridLayout *mainLayout = new QGridLayout(this); + mainLayout->addWidget(hostLabel, 0, 0); + mainLayout->addWidget(hostLineEdit, 0, 1); + mainLayout->addWidget(statusLabel, 2, 0, 3, 2); + mainLayout->addWidget(buttonBox, 10, 0, 2, 2); setWindowTitle(QGuiApplication::applicationDisplayName()); + hostLineEdit->setFocus(); +} + +void Server::listenToServer() +{ + name = hostLineEdit->text(); + if (!server->listen(name)) { + QMessageBox::critical(this, tr("Local Fortune Server"), + tr("Unable to start the server: %1.") + .arg(server->errorString())); + name.clear(); + return; + } + statusLabel->setText(tr("The server is running.\n" + "Run the Local Fortune Client example now.")); + toggleListenButton(); +} + +void Server::stopListening() +{ + server->close(); + name.clear(); + statusLabel->setText(idleStateText); + toggleListenButton(); +} + +void Server::toggleListenButton() +{ + if (server->isListening()) { + listenButton->setDisabled(true); + stopListeningButton->setEnabled(true); + } else { + listenButton->setEnabled(!hostLineEdit->text().isEmpty()); + stopListeningButton->setDisabled(true); + } } void Server::sendFortune() { QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); - out.setVersion(QDataStream::Qt_5_10); + out.setVersion(QDataStream::Qt_6_5); const int fortuneIndex = QRandomGenerator::global()->bounded(0, fortunes.size()); const QString &message = fortunes.at(fortuneIndex); - out << quint32(message.size()); out << message; QLocalSocket *clientConnection = server->nextPendingConnection(); diff --git a/examples/corelib/ipc/localfortuneserver/server.h b/examples/corelib/ipc/localfortuneserver/server.h index 26e4792347..1efba8fca9 100644 --- a/examples/corelib/ipc/localfortuneserver/server.h +++ b/examples/corelib/ipc/localfortuneserver/server.h @@ -4,27 +4,33 @@ #ifndef SERVER_H #define SERVER_H +#include <QApplication> #include <QDialog> - -QT_BEGIN_NAMESPACE -class QLabel; -class QPushButton; -class QLocalServer; -QT_END_NAMESPACE +#include <QLabel> +#include <QLineEdit> +#include <QLocalServer> +#include <QPushButton> class Server : public QDialog { - Q_OBJECT + Q_DECLARE_TR_FUNCTIONS(Server) public: explicit Server(QWidget *parent = nullptr); -private slots: +private: void sendFortune(); + void toggleListenButton(); + void listenToServer(); + void stopListening(); -private: QLocalServer *server; + QLineEdit *hostLineEdit; + QLabel *statusLabel; + QPushButton *listenButton; + QPushButton *stopListeningButton; QStringList fortunes; + QString name; }; #endif diff --git a/examples/corelib/ipc/sharedmemory/CMakeLists.txt b/examples/corelib/ipc/sharedmemory/CMakeLists.txt index 21f5ff339b..db94911fc6 100644 --- a/examples/corelib/ipc/sharedmemory/CMakeLists.txt +++ b/examples/corelib/ipc/sharedmemory/CMakeLists.txt @@ -1,15 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(sharedmemory LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/ipc/sharedmemory") - find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) qt_standard_project_setup() @@ -31,7 +25,14 @@ target_link_libraries(sharedmemory PRIVATE ) install(TARGETS sharedmemory - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET sharedmemory + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/ipc/sharedmemory/dialog.cpp b/examples/corelib/ipc/sharedmemory/dialog.cpp index b656cc0c67..4e8f93a77b 100644 --- a/examples/corelib/ipc/sharedmemory/dialog.cpp +++ b/examples/corelib/ipc/sharedmemory/dialog.cpp @@ -2,8 +2,12 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "dialog.h" -#include <QFileDialog> + #include <QBuffer> +#include <QFileDialog> +#include <QNativeIpcKey> + +using namespace Qt::StringLiterals; /*! \class Dialog @@ -29,8 +33,9 @@ each button. */ //! [0] + Dialog::Dialog(QWidget *parent) - : QDialog(parent), sharedMemory("QSharedMemoryExample") + : QDialog(parent), sharedMemory(QNativeIpcKey(u"QSharedMemoryExample"_s)) { ui.setupUi(this); connect(ui.loadFromFileButton, &QPushButton::clicked, diff --git a/examples/corelib/ipc/sharedmemory/dialog.h b/examples/corelib/ipc/sharedmemory/dialog.h index 0f8abaa8b6..679af423ff 100644 --- a/examples/corelib/ipc/sharedmemory/dialog.h +++ b/examples/corelib/ipc/sharedmemory/dialog.h @@ -6,6 +6,7 @@ #include <QDialog> #include <QSharedMemory> + #include "ui_dialog.h" //! [0] @@ -13,21 +14,21 @@ class Dialog : public QDialog { Q_OBJECT - public: +public: Dialog(QWidget *parent = nullptr); - public slots: +public slots: void loadFromFile(); void loadFromMemory(); - private: +private: void detach(); - private: +private: Ui::Dialog ui; QSharedMemory sharedMemory; }; //! [0] -#endif +#endif // DIALOG_H diff --git a/examples/corelib/ipc/sharedmemory/main.cpp b/examples/corelib/ipc/sharedmemory/main.cpp index bf5bd457ae..ffdd656502 100644 --- a/examples/corelib/ipc/sharedmemory/main.cpp +++ b/examples/corelib/ipc/sharedmemory/main.cpp @@ -1,9 +1,10 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include <QApplication> #include "dialog.h" +#include <QApplication> + //! [0] int main(int argc, char *argv[]) { diff --git a/examples/corelib/mimetypes/CMakeLists.txt b/examples/corelib/mimetypes/CMakeLists.txt index 9ba3873bf4..3b395090a2 100644 --- a/examples/corelib/mimetypes/CMakeLists.txt +++ b/examples/corelib/mimetypes/CMakeLists.txt @@ -1,5 +1,5 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause if(TARGET Qt6::Widgets) qt_internal_add_example(mimetypebrowser) diff --git a/examples/corelib/mimetypes/doc/src/mimetypebrowser.qdoc b/examples/corelib/mimetypes/doc/src/mimetypebrowser.qdoc index 17c8d765a8..cc76abe2e5 100644 --- a/examples/corelib/mimetypes/doc/src/mimetypebrowser.qdoc +++ b/examples/corelib/mimetypes/doc/src/mimetypebrowser.qdoc @@ -3,8 +3,9 @@ /*! \example mimetypes/mimetypebrowser + \examplecategory {Data Processing & I/O} \ingroup examples-mimetype - \title MIME Type Browser Example + \title MIME Type Browser \brief Shows the hierarchy of MIME types and can be used to determine the MIME type of a file. diff --git a/examples/corelib/mimetypes/mimetypebrowser/CMakeLists.txt b/examples/corelib/mimetypes/mimetypebrowser/CMakeLists.txt index c03ccae085..b514965ff9 100644 --- a/examples/corelib/mimetypes/mimetypebrowser/CMakeLists.txt +++ b/examples/corelib/mimetypes/mimetypebrowser/CMakeLists.txt @@ -1,15 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(mimetypebrowser LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/mimetypes/mimetypebrowser") - find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) qt_standard_project_setup() @@ -32,7 +26,14 @@ target_link_libraries(mimetypebrowser PRIVATE ) install(TARGETS mimetypebrowser - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET mimetypebrowser + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/mimetypes/mimetypebrowser/main.cpp b/examples/corelib/mimetypes/mimetypebrowser/main.cpp index 9aaad7b836..03c905e6af 100644 --- a/examples/corelib/mimetypes/mimetypebrowser/main.cpp +++ b/examples/corelib/mimetypes/mimetypebrowser/main.cpp @@ -4,10 +4,8 @@ #include "mainwindow.h" #include <QApplication> -#include <QScreen> - #include <QCommandLineParser> -#include <QCommandLineOption> +#include <QScreen> int main(int argc, char *argv[]) { diff --git a/examples/corelib/mimetypes/mimetypebrowser/mainwindow.cpp b/examples/corelib/mimetypes/mimetypebrowser/mainwindow.cpp index 07cb3872d4..0163fec574 100644 --- a/examples/corelib/mimetypes/mimetypebrowser/mainwindow.cpp +++ b/examples/corelib/mimetypes/mimetypebrowser/mainwindow.cpp @@ -4,23 +4,18 @@ #include "mainwindow.h" #include "mimetypemodel.h" -#include <QAction> #include <QApplication> #include <QFileDialog> +#include <QFileInfo> #include <QInputDialog> +#include <QItemSelectionModel> #include <QMenu> #include <QMenuBar> #include <QMessageBox> -#include <QPlainTextEdit> -#include <QSplitter> -#include <QStatusBar> -#include <QTextEdit> -#include <QTreeView> - -#include <QFileInfo> -#include <QItemSelectionModel> #include <QMimeDatabase> #include <QMimeType> +#include <QSplitter> +#include <QStatusBar> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) @@ -45,7 +40,8 @@ MainWindow::MainWindow(QWidget *parent) findAction->setShortcuts(QKeySequence::Find); m_findNextAction = findMenu->addAction(tr("Find &Next"), this, &MainWindow::findNext); m_findNextAction->setShortcuts(QKeySequence::FindNext); - m_findPreviousAction = findMenu->addAction(tr("Find &Previous"), this, &MainWindow::findPrevious); + m_findPreviousAction = findMenu->addAction(tr("Find &Previous"), this, + &MainWindow::findPrevious); m_findPreviousAction->setShortcuts(QKeySequence::FindPrevious); menuBar()->addMenu(tr("&About"))->addAction(tr("&About Qt"), qApp, &QApplication::aboutQt); @@ -54,8 +50,8 @@ MainWindow::MainWindow(QWidget *parent) setCentralWidget(centralSplitter); m_treeView->setUniformRowHeights(true); m_treeView->setModel(m_model); - - const auto items = m_model->findItems("application/octet-stream", Qt::MatchContains | Qt::MatchFixedString | Qt::MatchRecursive); + const auto flags = Qt::MatchContains | Qt::MatchFixedString | Qt::MatchRecursive; + const auto items = m_model->findItems("application/octet-stream", flags); if (!items.isEmpty()) m_treeView->expand(m_model->indexFromItem(items.constFirst())); @@ -93,7 +89,8 @@ void MainWindow::detectFile() const QModelIndex index = mimeType.isValid() ? m_model->indexForMimeType(mimeType.name()) : QModelIndex(); if (index.isValid()) { - statusBar()->showMessage(tr("\"%1\" is of type \"%2\"").arg(fi.fileName(), mimeType.name())); + statusBar()->showMessage(tr("\"%1\" is of type \"%2\"").arg(fi.fileName(), + mimeType.name())); selectAndGoTo(index); } else { QMessageBox::information(this, tr("Unknown File Type"), @@ -138,8 +135,8 @@ void MainWindow::find() m_findMatches.clear(); m_findIndex = 0; - const QList<QStandardItem *> items = - m_model->findItems(value, Qt::MatchContains | Qt::MatchFixedString | Qt::MatchRecursive); + const auto flags = Qt::MatchContains | Qt::MatchFixedString | Qt::MatchRecursive; + const QList<QStandardItem *> items = m_model->findItems(value, flags); for (const QStandardItem *item : items) m_findMatches.append(m_model->indexFromItem(item)); statusBar()->showMessage(tr("%n mime types match \"%1\".", 0, m_findMatches.size()).arg(value)); diff --git a/examples/corelib/mimetypes/mimetypebrowser/mainwindow.h b/examples/corelib/mimetypes/mimetypebrowser/mainwindow.h index 4554d0873d..04827529fa 100644 --- a/examples/corelib/mimetypes/mimetypebrowser/mainwindow.h +++ b/examples/corelib/mimetypes/mimetypebrowser/mainwindow.h @@ -4,12 +4,11 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include <QAction> #include <QMainWindow> #include <QModelIndexList> - -QT_FORWARD_DECLARE_CLASS(QAction) -QT_FORWARD_DECLARE_CLASS(QTextEdit) -QT_FORWARD_DECLARE_CLASS(QTreeView) +#include <QTextEdit> +#include <QTreeView> class MimetypeModel; diff --git a/examples/corelib/mimetypes/mimetypebrowser/mimetypemodel.h b/examples/corelib/mimetypes/mimetypebrowser/mimetypemodel.h index b1c84a7797..061ede3c47 100644 --- a/examples/corelib/mimetypes/mimetypebrowser/mimetypemodel.h +++ b/examples/corelib/mimetypes/mimetypebrowser/mimetypemodel.h @@ -4,14 +4,14 @@ #ifndef MIMETYPEMODEL_H #define MIMETYPEMODEL_H -#include <QStandardItemModel> +#include <QCoreApplication> #include <QHash> - -QT_FORWARD_DECLARE_CLASS(QMimeType) +#include <QMimeType> +#include <QStandardItemModel> class MimetypeModel : public QStandardItemModel { - Q_OBJECT + Q_DECLARE_TR_FUNCTIONS(MimetypeModel) public: enum Columns { NameColumn, ColumnCount }; diff --git a/examples/corelib/permissions/CMakeLists.txt b/examples/corelib/permissions/CMakeLists.txt deleted file mode 100644 index 39a207b1ce..0000000000 --- a/examples/corelib/permissions/CMakeLists.txt +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -cmake_minimum_required(VERSION 3.16) -project(permissions LANGUAGES CXX) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/permissions") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) - -qt_standard_project_setup() - -qt_add_executable(permissions - MANUAL_FINALIZATION - main.cpp - android/AndroidManifest.xml -) - -set_target_properties(permissions PROPERTIES - MACOSX_BUNDLE TRUE - MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist" - MACOSX_BUNDLE_GUI_IDENTIFIER "io.qt.examples.permissions" - QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android" -) - -target_link_libraries(permissions PRIVATE - Qt6::Core - Qt6::Gui - Qt6::Widgets -) - -install(TARGETS permissions - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) - -if(APPLE AND NOT CMAKE_GENERATOR STREQUAL "Xcode") - add_custom_command(TARGET permissions - POST_BUILD COMMAND codesign -s - permissions.app) -endif() - -qt_finalize_executable(permissions) diff --git a/examples/corelib/permissions/Info.plist b/examples/corelib/permissions/Info.plist deleted file mode 100644 index 57625d03dc..0000000000 --- a/examples/corelib/permissions/Info.plist +++ /dev/null @@ -1,59 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> -<plist version="1.0"> -<dict> - <key>CFBundleInfoDictionaryVersion</key> - <string>6.0</string> - <key>CFBundlePackageType</key> - <string>APPL</string> - - <key>CFBundleName</key> - <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string> - <key>CFBundleIdentifier</key> - <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string> - <key>CFBundleExecutable</key> - <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string> - - <key>CFBundleVersion</key> - <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string> - <key>CFBundleShortVersionString</key> - <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string> - - <key>LSMinimumSystemVersion</key> - <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string> - - <key>NSHumanReadableCopyright</key> - <string>${MACOSX_BUNDLE_COPYRIGHT}</string> - - <key>CFBundleIconFile</key> - <string>${MACOSX_BUNDLE_ICON_FILE}</string> - - <key>CFBundleDevelopmentRegion</key> - <string>English</string> - - <key>NSSupportsAutomaticGraphicsSwitching</key> - <true/> - - <key>NSBluetoothAlwaysUsageDescription</key> - <string>Testing BluetoothAlways</string> - <key>NSCalendarsUsageDescription</key> - <string>Testing Calendars</string> - <key>NSCameraUsageDescription</key> - <string>Testing Camera</string> - <key>NSContactsUsageDescription</key> - <string>Testing Contacts</string> - <key>NSHealthShareUsageDescription</key> - <string>Testing HealthShare</string> - <key>NSHealthUpdateUsageDescription</key> - <string>Testing HealthUpdate</string> - <key>NSLocationUsageDescription</key> - <string>Testing Location on macOS</string> - <key>NSLocationWhenInUseUsageDescription</key> - <string>Testing Location when in use on iOS</string> - <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> - <string>Testing Location always and when in use on iOS</string> - <key>NSMicrophoneUsageDescription</key> - <string>Testing Microphone</string> - -</dict> -</plist> diff --git a/examples/corelib/permissions/android/AndroidManifest.xml b/examples/corelib/permissions/android/AndroidManifest.xml deleted file mode 100644 index 557ec8007e..0000000000 --- a/examples/corelib/permissions/android/AndroidManifest.xml +++ /dev/null @@ -1,53 +0,0 @@ -<?xml version="1.0"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="org.qtproject.example" - android:installLocation="auto" - android:versionCode="-- %%INSERT_VERSION_CODE%% --" - android:versionName="-- %%INSERT_VERSION_NAME%% --"> - <uses-permission android:name="android.permission.CAMERA" /> - <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> - <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> - <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> - <uses-permission android:name="android.permission.RECORD_AUDIO" /> - <uses-permission android:name="android.permission.BLUETOOTH" /> - <uses-permission android:name="android.permission.READ_CONTACTS" /> - <uses-permission android:name="android.permission.WRITE_CONTACTS" /> - <uses-permission android:name="android.permission.READ_CALENDAR" /> - <uses-permission android:name="android.permission.WRITE_CALENDAR" /> - <!-- %%INSERT_PERMISSIONS --> - <!-- %%INSERT_FEATURES --> - <supports-screens - android:anyDensity="true" - android:largeScreens="true" - android:normalScreens="true" - android:smallScreens="true" /> - <application - android:name="org.qtproject.qt.android.bindings.QtApplication" - android:hardwareAccelerated="true" - android:label="-- %%INSERT_APP_NAME%% --" - android:requestLegacyExternalStorage="true" - android:allowNativeHeapPointerTagging="false" - android:allowBackup="true" - android:fullBackupOnly="false"> - <activity - android:name="org.qtproject.qt.android.bindings.QtActivity" - android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" - android:label="-- %%INSERT_APP_NAME%% --" - android:launchMode="singleTop" - android:screenOrientation="unspecified" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - - <meta-data - android:name="android.app.lib_name" - android:value="-- %%INSERT_APP_LIB_NAME%% --" /> - - <meta-data - android:name="android.app.extract_android_style" - android:value="minimal" /> - </activity> - </application> -</manifest> diff --git a/examples/corelib/permissions/main.cpp b/examples/corelib/permissions/main.cpp deleted file mode 100644 index 913aed2fec..0000000000 --- a/examples/corelib/permissions/main.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include <QtCore/qmetaobject.h> -#include <QtWidgets/qapplication.h> -#include <QtWidgets/qwidget.h> -#include <QtWidgets/qpushbutton.h> -#include <QtWidgets/qlayout.h> -#include <QtWidgets/qmessagebox.h> - -#if !QT_CONFIG(permissions) -#error "This example requires the permissions feature, which is not enabled on this platform" -#endif - -#include <QtCore/qpermissions.h> - -class PermissionWidget : public QWidget -{ - Q_OBJECT -public: - explicit PermissionWidget(QWidget *parent = nullptr) : QWidget(parent) - { - QVBoxLayout *layout = new QVBoxLayout(this); - - static const QPermission permissions[] = { - QCameraPermission{}, - QMicrophonePermission{}, - QBluetoothPermission{}, - QContactsPermission{}, - QCalendarPermission{}, - QLocationPermission{} - }; - - for (auto permission : permissions) { - auto permissionName = QString::fromLatin1(permission.type().name()); - QPushButton *button = new QPushButton(permissionName.sliced(1, permissionName.length() - 11)); - connect(button, &QPushButton::clicked, this, &PermissionWidget::buttonClicked); - button->setProperty("permission", QVariant::fromValue(permission)); - layout->addWidget(button); - } - - QPalette pal = palette(); - pal.setBrush(QPalette::Window, QGradient(QGradient::HappyAcid)); - setPalette(pal); - } - -private: - void buttonClicked() - { - auto *button = static_cast<QPushButton*>(sender()); - - auto permission = button->property("permission").value<QPermission>(); - Q_ASSERT(permission.type().isValid()); - - switch (qApp->checkPermission(permission)) { - case Qt::PermissionStatus::Undetermined: - qApp->requestPermission(permission, this, - [button](const QPermission &permission) { - Q_UNUSED(permission); - emit button->clicked(); // Try again - } - ); - return; - case Qt::PermissionStatus::Denied: - QMessageBox::warning(this, button->text(), - tr("Permission is needed to use %1. Please grant permission "\ - "to this application in the system settings.").arg(button->text())); - return; - case Qt::PermissionStatus::Granted: - break; // Proceed - } - - // All good, can use the feature - QMessageBox::information(this, button->text(), - tr("Accessing %1").arg(button->text())); - } -}; - -int main(int argc, char **argv) -{ - QApplication app(argc, argv); - PermissionWidget widget; - widget.show(); - return app.exec(); -} - -#include "main.moc" diff --git a/examples/corelib/platform/CMakeLists.txt b/examples/corelib/platform/CMakeLists.txt index 8eeda13095..b195713074 100644 --- a/examples/corelib/platform/CMakeLists.txt +++ b/examples/corelib/platform/CMakeLists.txt @@ -1,6 +1,6 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause if(ANDROID) - add_subdirectory(androidnotifier) + qt_internal_add_example(androidnotifier) endif() diff --git a/examples/corelib/platform/androidnotifier/CMakeLists.txt b/examples/corelib/platform/androidnotifier/CMakeLists.txt index e5271edd79..b356b15a43 100644 --- a/examples/corelib/platform/androidnotifier/CMakeLists.txt +++ b/examples/corelib/platform/androidnotifier/CMakeLists.txt @@ -1,5 +1,5 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(androidnotifier LANGUAGES CXX) @@ -8,16 +8,10 @@ if(NOT ANDROID) message(FATAL_ERROR "Example only works on Android") endif() -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - find_package(Qt6 REQUIRED COMPONENTS Widgets) qt_standard_project_setup() -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/platform/androidnotifier") - qt_add_executable(androidnotifier MANUAL_FINALIZATION main.cpp @@ -28,6 +22,7 @@ qt_add_executable(androidnotifier ) target_link_libraries(androidnotifier PRIVATE + Qt6::CorePrivate Qt6::Widgets ) @@ -49,7 +44,14 @@ qt_add_resources(androidnotifier "main" ) install(TARGETS androidnotifier - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET androidnotifier + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/platform/androidnotifier/android/AndroidManifest.xml b/examples/corelib/platform/androidnotifier/android/AndroidManifest.xml index 4850a8e8e6..1d3cf1325b 100644 --- a/examples/corelib/platform/androidnotifier/android/AndroidManifest.xml +++ b/examples/corelib/platform/androidnotifier/android/AndroidManifest.xml @@ -4,12 +4,9 @@ android:installLocation="auto" android:versionCode="1" android:versionName="1.0"> - <!-- The comment below will be replaced with dependencies permissions upon deployment. - Remove the comment if you do not require these default permissions. --> - <!-- %%INSERT_PERMISSIONS --> - <!-- The comment below will be replaced with dependencies permissions upon deployment. - Remove the comment if you do not require these default features. --> + <!-- %%INSERT_PERMISSIONS --> + <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <!-- %%INSERT_FEATURES --> <supports-screens @@ -23,13 +20,15 @@ android:hardwareAccelerated="true" android:label="Qt Notifier" android:requestLegacyExternalStorage="true" - android:icon="@drawable/icon"> + android:icon="@drawable/icon" + android:allowBackup="true" + android:fullBackupOnly="false"> <activity android:name="org.qtproject.qt.android.bindings.QtActivity" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" - android:label="Qt Notifier" android:launchMode="singleTop" - android:screenOrientation="unspecified"> + android:screenOrientation="unspecified" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> @@ -38,14 +37,6 @@ <meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/> - - <meta-data - android:name="android.app.background_running" - android:value="false"/> - - <meta-data - android:name="android.app.extract_android_style" - android:value="none" /> </activity> </application> </manifest> diff --git a/examples/corelib/platform/androidnotifier/androidnotifier.pro b/examples/corelib/platform/androidnotifier/androidnotifier.pro index f1650c1911..7e5b845e10 100644 --- a/examples/corelib/platform/androidnotifier/androidnotifier.pro +++ b/examples/corelib/platform/androidnotifier/androidnotifier.pro @@ -1,5 +1,5 @@ -QT += core gui -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets +QT += core gui widgets +QT += core-private # For Notification permission request SOURCES += \ main.cpp \ diff --git a/examples/corelib/platform/androidnotifier/doc/src/androidnotifier-example.qdoc b/examples/corelib/platform/androidnotifier/doc/src/androidnotifier-example.qdoc index 4bc0f7668e..1315ad7762 100644 --- a/examples/corelib/platform/androidnotifier/doc/src/androidnotifier-example.qdoc +++ b/examples/corelib/platform/androidnotifier/doc/src/androidnotifier-example.qdoc @@ -4,7 +4,9 @@ /*! \title Qt Android Notifier \example platform/androidnotifier + \meta tag {widgets,android,notification} \brief Demonstrates calling Java code from Qt in an Android application. + \ingroup androidplatform \image androidnotifier.png diff --git a/examples/corelib/platform/androidnotifier/main.cpp b/examples/corelib/platform/androidnotifier/main.cpp index 33e77c7018..07eff5d2b0 100644 --- a/examples/corelib/platform/androidnotifier/main.cpp +++ b/examples/corelib/platform/androidnotifier/main.cpp @@ -41,11 +41,11 @@ int main(int argc, char *argv[]) widget.setLayout(&mainLayout); //! [Connect button signals] - QObject::connect(&happyButton, &QPushButton::clicked, []() { + QObject::connect(&happyButton, &QPushButton::clicked, &happyButton, []() { NotificationClient().setNotification("The user is happy!"); }); - QObject::connect(&sadButton, &QPushButton::clicked, []() { + QObject::connect(&sadButton, &QPushButton::clicked, &happyButton, []() { NotificationClient().setNotification("The user is sad!"); }); //! [Connect button signals] diff --git a/examples/corelib/platform/androidnotifier/notificationclient.cpp b/examples/corelib/platform/androidnotifier/notificationclient.cpp index af1cb7322a..aa6093c29c 100644 --- a/examples/corelib/platform/androidnotifier/notificationclient.cpp +++ b/examples/corelib/platform/androidnotifier/notificationclient.cpp @@ -5,10 +5,22 @@ #include <QtCore/qjniobject.h> #include <QtCore/qcoreapplication.h> +#include <QtCore/private/qandroidextras_p.h> + +using namespace Qt::StringLiterals; NotificationClient::NotificationClient(QObject *parent) : QObject(parent) { + if (QNativeInterface::QAndroidApplication::sdkVersion() >= __ANDROID_API_T__) { + const auto notificationPermission = "android.permission.POST_NOTIFICATIONS"_L1; + auto requestResult = QtAndroidPrivate::requestPermission(notificationPermission); + if (requestResult.result() != QtAndroidPrivate::Authorized) { + qWarning() << "Failed to acquire permission to post notifications " + "(required for Android 13+)"; + } + } + connect(this, &NotificationClient::notificationChanged, this, &NotificationClient::updateAndroidNotification); } diff --git a/examples/corelib/serialization/CMakeLists.txt b/examples/corelib/serialization/CMakeLists.txt index 0110eb8fff..e32e4df441 100644 --- a/examples/corelib/serialization/CMakeLists.txt +++ b/examples/corelib/serialization/CMakeLists.txt @@ -1,9 +1,11 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -qt_internal_add_example(cbordump) -qt_internal_add_example(convert) -qt_internal_add_example(savegame) -if(TARGET Qt6::Network AND TARGET Qt6::Widgets) - qt_internal_add_example(rsslisting) +if(NOT ANDROID) + qt_internal_add_example(cbordump) + qt_internal_add_example(convert) + qt_internal_add_example(savegame) +endif() +if(TARGET Qt6::Widgets) + qt_internal_add_example(streambookmarks) endif() diff --git a/examples/corelib/serialization/cbordump/CMakeLists.txt b/examples/corelib/serialization/cbordump/CMakeLists.txt index 813b02b9c0..b2c3a536e3 100644 --- a/examples/corelib/serialization/cbordump/CMakeLists.txt +++ b/examples/corelib/serialization/cbordump/CMakeLists.txt @@ -1,15 +1,13 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(cbordump LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") +if (ANDROID) + message(FATAL_ERROR "This project cannot be built on Android.") endif() -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/cbordump") - find_package(Qt6 REQUIRED COMPONENTS Core) qt_standard_project_setup() @@ -23,7 +21,14 @@ target_link_libraries(cbordump PRIVATE ) install(TARGETS cbordump - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET cbordump + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/serialization/cbordump/cbortag.py b/examples/corelib/serialization/cbordump/cbortag.py new file mode 100755 index 0000000000..26a0f969e4 --- /dev/null +++ b/examples/corelib/serialization/cbordump/cbortag.py @@ -0,0 +1,188 @@ +#!/bin/env python3 +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +"""Digest cbor-tags.xml file into code for insertion into main.cpp + +See main.cpp's comment on how to regenerate its GENERATED CODE. +See ./cbortag.py --help for further details on how to invoke. +You can import this is a module without invoking the script. +""" + +def firstChild(parent, tag): + """Return parent's first child element with the given tag.""" + return next(node for node in parent.childNodes + if node.nodeType == parent.ELEMENT_NODE and node.nodeName == tag) + +def nodeAttrIs(node, attr, seek): + """Checks whether the node has a given value for an attribute + + Takes the node to check, the name of the attribute and the value + to check against. Returns true if the node does have that value + for the named attribute.""" + if node.nodeType != node.ELEMENT_NODE: + return False + if node.attributes is None or attr not in node.attributes: + return False + return node.attributes[attr].value == seek + +def getRfcValue(node): + """Extract RFC reference from an <xref type="rfc" ...> element + + Some of these have a reference including section details as the + body of the element, otherwise the data attribute should identify + the RFC. If neither is found, an empty string is returned.""" + if node.childNodes: + return node.childNodes[0].nodeValue # Maybe accumulate several children ? + if node.attributes is None or 'data' not in node.attributes: + return '' + return node.attributes['data'].value + +def readRegistry(filename): + """Handles the XML parsing and returns the relevant parts. + + Single argument is the path to the cbor-tags.xml file; returns a + twople of the title element's text and an interator over the + record nodes. Checks some things are as expected while doing so.""" + from xml.dom.minidom import parse + doc = parse(filename).documentElement + assert nodeAttrIs(doc, 'id', 'cbor-tags') + title = firstChild(doc, 'title').childNodes[0].nodeValue + registry = firstChild(doc, 'registry') + assert nodeAttrIs(registry, 'id', 'tags') + records = (node for node in registry.childNodes if node.nodeName == 'record') + return title, records + +def digest(record): + """Digest a single record from cbor-tags.xml + + If the record is not of interest, returns the twople (None, None). + For records of interest, returns (n, t) where n is the numeric tag + code of the record and t is a text describing it. If the record, + or its semantics field, has an xref child with type="rfc", the RFC + mentioned there is included with the text of the semantics; such a + record is of interest, provided it has a semantics field and no + dash in its value. Records with a value field containing a dash + (indicating a range) are not of interest. Records with a value of + 256 or above are only of interest if they include an RFC.""" + data = {} + for kid in record.childNodes: + if kid.nodeName == 'xref': + if not nodeAttrIs(kid, 'type', 'rfc'): + continue + rfc = getRfcValue(kid) + if rfc: + # Potentially stomping one taken from semantics + data['rfc'] = rfc + elif kid.nodeName == 'semantics': + text = rfc = '' + for part in kid.childNodes: + if part.nodeType == kid.TEXT_NODE: + text += part.nodeValue + elif part.nodeType == kid.ELEMENT_NODE: + if part.nodeName != 'xref' or not nodeAttrIs(part, 'type', 'rfc'): + continue # potentially append content to text + assert not rfc, ('Duplicate RFC ?', rfc, part) + rfc = getRfcValue(part) + if rfc: + if text.endswith('()'): + text = text[:-2].rstrip() + if 'rfc' not in data: + data['rfc'] = rfc + data['semantics'] = ' '.join(text.split()) + elif kid.nodeName == 'value': + data['value'] = kid.childNodes[0].nodeValue + text = data.get('semantics') + if not text or 'value' not in data or '-' in data['value']: + return None, None + value = int(data['value']) + if 'rfc' in data: + rfc = data["rfc"].replace('rfc', 'RFC') + text = f'{text} [{rfc}]' + elif value >= 256: + return None, None + return value, text + +def entries(records): + """Digest each record of interest into a value and text. + + The value and text form the raw material of the tagDescriptions + array in main.cpp; see digest for which records are retained.""" + for record in records: + value, text = digest(record) + if value is not None: + yield value, text + +def marginBound(text, prior, left, right): + """Split up a string literal for tidy display. + + The first parameter, text, is the content of the string literal; + quotes shall be added. It may be split into several fragments, + each quoted, so as to abide by line length constraints. + + The remaining parameters are integers: prior is the text already + present on the line before text is to be added; left is the width + of the left margin for all subsequent lines; and right is the + right margin to stay within, where possible. The returned string + is either a space with the whole quoted text following, to fit on + the line already started to length prior, or a sequence of quoted + strings, each preceded by a newline and indent of width left.""" + if prior + 3 + len(text) < right: # 1 for space, 2 for quotes + return f' "{text}"' + width = right - left - 2 # 2 for the quotes + words = iter(text.split(' ')) + lines, current = [''], [next(words)] + for word in words: + if len(word) + sum(len(w) + 1 for w in current) > width: + line = ' '.join(current) + lines.append(f'"{line}"') + current = ['', word] + else: + current.append(word) + line = ' '.join(current) + lines.append(f'"{line}"') + return ('\n' + ' ' * left).join(lines) + +def main(argv, speak): + """Takes care of driving the process. + + Takes the command-line argument list (whose first entry is the + name of this script) and standard output (or compatible stream of + your choosing) to which to write data. If the --out option is + specified in the arguments, the file it names is used in place of + this output stream.""" + from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter + parser = ArgumentParser( + description='Digest cbor-tags.xml into code to insert in main.cpp', + formatter_class=ArgumentDefaultsHelpFormatter) + parser.add_argument('path', help='path of the cbor-tags.xml file', + default='cbor-tags.xml') + parser.add_argument('--out', help='file to write instead of standard output') + args = parser.parse_args(argv[1:]) + emit = (open(args.out) if args.out else speak).write + + title, records = readRegistry(args.path) + emit(f"""\ +struct CborTagDescription +{{ + QCborTag tag; + const char *description; // with space and parentheses +}}; + +// {title} +static const CborTagDescription tagDescriptions[] = {{ + // from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml +""") + + for value, text in sorted(entries(records)): + prior = f' {{ QCborTag({value}),' + body = marginBound(f' ({text})', len(prior), 6, 96) + emit(f"{prior}{body} }},\n") + + emit("""\ + { QCborTag(-1), nullptr } +}; +""") + +if __name__ == '__main__': + import sys + sys.exit(main(sys.argv, sys.stdout)) diff --git a/examples/corelib/serialization/cbordump/doc/images/cbordump.png b/examples/corelib/serialization/cbordump/doc/images/cbordump.png Binary files differindex 72232c1a95..d5ff1da0bf 100644 --- a/examples/corelib/serialization/cbordump/doc/images/cbordump.png +++ b/examples/corelib/serialization/cbordump/doc/images/cbordump.png diff --git a/examples/corelib/serialization/cbordump/doc/src/cbordump.qdoc b/examples/corelib/serialization/cbordump/doc/src/cbordump.qdoc index af7ffb7149..a4dc01116f 100644 --- a/examples/corelib/serialization/cbordump/doc/src/cbordump.qdoc +++ b/examples/corelib/serialization/cbordump/doc/src/cbordump.qdoc @@ -3,6 +3,8 @@ /*! \example serialization/cbordump + \examplecategory {Data Processing & I/O} + \meta tag {network} \title Parsing and displaying CBOR data \brief A demonstration of how to parse files in CBOR format. diff --git a/examples/corelib/serialization/cbordump/main.cpp b/examples/corelib/serialization/cbordump/main.cpp index a53bbdebda..03c940452e 100644 --- a/examples/corelib/serialization/cbordump/main.cpp +++ b/examples/corelib/serialization/cbordump/main.cpp @@ -14,87 +14,137 @@ #include <stdarg.h> #include <stdio.h> +using namespace Qt::StringLiterals; + /* * To regenerate: * curl -O https://www.iana.org/assignments/cbor-tags/cbor-tags.xml - * xsltproc tag-transform.xslt cbor-tags.xml + * ./cbortag.py cbor-tags.xml + * + * The XHTML URL mentioned in the comment below is a human-readable version of + * the same resource. */ // GENERATED CODE struct CborTagDescription { QCborTag tag; - const char *description; // with space and parentheses + const char *description; // with space and parentheses }; -// CBOR Tags +// Concise Binary Object Representation (CBOR) Tags static const CborTagDescription tagDescriptions[] = { // from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml - { QCborTag(0), " (Standard date/time string; see Section 2.4.1 [RFC7049])" }, - { QCborTag(1), " (Epoch-based date/time; see Section 2.4.1 [RFC7049])" }, - { QCborTag(2), " (Positive bignum; see Section 2.4.2 [RFC7049])" }, - { QCborTag(3), " (Negative bignum; see Section 2.4.2 [RFC7049])" }, - { QCborTag(4), " (Decimal fraction; see Section 2.4.3 [RFC7049])" }, - { QCborTag(5), " (Bigfloat; see Section 2.4.3 [RFC7049])" }, - { QCborTag(16), " (COSE Single Recipient Encrypted Data Object [RFC8152])" }, - { QCborTag(17), " (COSE Mac w/o Recipients Object [RFC8152])" }, - { QCborTag(18), " (COSE Single Signer Data Object [RFC8152])" }, - { QCborTag(21), " (Expected conversion to base64url encoding; see Section 2.4.4.2 [RFC7049])" }, - { QCborTag(22), " (Expected conversion to base64 encoding; see Section 2.4.4.2 [RFC7049])" }, - { QCborTag(23), " (Expected conversion to base16 encoding; see Section 2.4.4.2 [RFC7049])" }, - { QCborTag(24), " (Encoded CBOR data item; see Section 2.4.4.1 [RFC7049])" }, + { QCborTag(0), " (Standard date/time string; see Section 3.4.1 [RFC8949])" }, + { QCborTag(1), " (Epoch-based date/time; see Section 3.4.2 [RFC8949])" }, + { QCborTag(2), " (Positive bignum; see Section 3.4.3 [RFC8949])" }, + { QCborTag(3), " (Negative bignum; see Section 3.4.3 [RFC8949])" }, + { QCborTag(4), " (Decimal fraction; see Section 3.4.4 [RFC8949])" }, + { QCborTag(5), " (Bigfloat; see Section 3.4.4 [RFC8949])" }, + { QCborTag(16), " (COSE Single Recipient Encrypted Data Object [RFC9052])" }, + { QCborTag(17), " (COSE Mac w/o Recipients Object [RFC9052])" }, + { QCborTag(18), " (COSE Single Signer Data Object [RFC9052])" }, + { QCborTag(19), " (COSE standalone V2 countersignature [RFC9338])" }, + { QCborTag(21), + " (Expected conversion to base64url encoding; see Section 3.4.5.2 [RFC8949])" }, + { QCborTag(22), " (Expected conversion to base64 encoding; see Section 3.4.5.2 [RFC8949])" }, + { QCborTag(23), " (Expected conversion to base16 encoding; see Section 3.4.5.2 [RFC8949])" }, + { QCborTag(24), " (Encoded CBOR data item; see Section 3.4.5.1 [RFC8949])" }, { QCborTag(25), " (reference the nth previously seen string)" }, { QCborTag(26), " (Serialised Perl object with classname and constructor arguments)" }, - { QCborTag(27), " (Serialised language-independent object with type name and constructor arguments)" }, + { QCborTag(27), + " (Serialised language-independent object with type name and constructor arguments)" }, { QCborTag(28), " (mark value as (potentially) shared)" }, { QCborTag(29), " (reference nth marked value)" }, { QCborTag(30), " (Rational number)" }, - { QCborTag(32), " (URI; see Section 2.4.4.3 [RFC7049])" }, - { QCborTag(33), " (base64url; see Section 2.4.4.3 [RFC7049])" }, - { QCborTag(34), " (base64; see Section 2.4.4.3 [RFC7049])" }, + { QCborTag(31), " (Absent value in a CBOR Array)" }, + { QCborTag(32), " (URI; see Section 3.4.5.3 [RFC8949])" }, + { QCborTag(33), " (base64url; see Section 3.4.5.3 [RFC8949])" }, + { QCborTag(34), " (base64; see Section 3.4.5.3 [RFC8949])" }, { QCborTag(35), " (Regular expression; see Section 2.4.4.3 [RFC7049])" }, - { QCborTag(36), " (MIME message; see Section 2.4.4.3 [RFC7049])" }, - { QCborTag(37), " (Binary UUID ( section 4.1.2))" }, - { QCborTag(38), " (Language-tagged string)" }, + { QCborTag(36), " (MIME message; see Section 3.4.5.3 [RFC8949])" }, + { QCborTag(37), " (Binary UUID [RFC4122, Section 4.1.2])" }, + { QCborTag(38), " (Language-tagged string [RFC9290, Appendix A])" }, { QCborTag(39), " (Identifier)" }, - { QCborTag(61), " (CBOR Web Token (CWT))" }, - { QCborTag(96), " (COSE Encrypted Data Object [RFC8152])" }, - { QCborTag(97), " (COSE MACed Data Object [RFC8152])" }, - { QCborTag(98), " (COSE Signed Data Object [RFC8152])" }, - { QCborTag(256), " (mark value as having string references)" }, - { QCborTag(257), " (Binary MIME message)" }, - { QCborTag(258), " (Mathematical finite set)" }, - { QCborTag(260), " (Network Address (IPv4 or IPv6 or MAC Address))" }, - { QCborTag(264), " (Decimal fraction with arbitrary exponent)" }, - { QCborTag(265), " (Bigfloat with arbitrary exponent)" }, - { QCborTag(1001), " (extended time)" }, - { QCborTag(1002), " (duration)" }, - { QCborTag(1003), " (period)" }, - { QCborTag(22098), " (hint that indicates an additional level of indirection)" }, - { QCborTag(55799), " (Self-describe CBOR; see Section 2.4.5 [RFC7049])" }, - { QCborTag(15309736), " (RAINS Message)" }, + { QCborTag(40), " (Multi-dimensional Array, row-major order [RFC8746])" }, + { QCborTag(41), " (Homogeneous Array [RFC8746])" }, + { QCborTag(42), " (IPLD content identifier)" }, + { QCborTag(43), " (YANG bits datatype; see Section 6.7. [RFC9254])" }, + { QCborTag(44), " (YANG enumeration datatype; see Section 6.6. [RFC9254])" }, + { QCborTag(45), " (YANG identityref datatype; see Section 6.10. [RFC9254])" }, + { QCborTag(46), " (YANG instance-identifier datatype; see Section 6.13. [RFC9254])" }, + { QCborTag(47), " (YANG Schema Item iDentifier (sid); see Section 3.2. [RFC9254])" }, + { QCborTag(52), " (IPv4, [prefixlen,IPv4], [IPv4,prefixpart] [RFC9164])" }, + { QCborTag(54), " (IPv6, [prefixlen,IPv6], [IPv6,prefixpart] [RFC9164])" }, + { QCborTag(61), " (CBOR Web Token (CWT) [RFC8392])" }, + { QCborTag(63), " (Encoded CBOR Sequence [RFC8742])" }, + { QCborTag(64), " (uint8 Typed Array [RFC8746])" }, + { QCborTag(65), " (uint16, big endian, Typed Array [RFC8746])" }, + { QCborTag(66), " (uint32, big endian, Typed Array [RFC8746])" }, + { QCborTag(67), " (uint64, big endian, Typed Array [RFC8746])" }, + { QCborTag(68), " (uint8 Typed Array, clamped arithmetic [RFC8746])" }, + { QCborTag(69), " (uint16, little endian, Typed Array [RFC8746])" }, + { QCborTag(70), " (uint32, little endian, Typed Array [RFC8746])" }, + { QCborTag(71), " (uint64, little endian, Typed Array [RFC8746])" }, + { QCborTag(72), " (sint8 Typed Array [RFC8746])" }, + { QCborTag(73), " (sint16, big endian, Typed Array [RFC8746])" }, + { QCborTag(74), " (sint32, big endian, Typed Array [RFC8746])" }, + { QCborTag(75), " (sint64, big endian, Typed Array [RFC8746])" }, + { QCborTag(76), " ((reserved) [RFC8746])" }, + { QCborTag(77), " (sint16, little endian, Typed Array [RFC8746])" }, + { QCborTag(78), " (sint32, little endian, Typed Array [RFC8746])" }, + { QCborTag(79), " (sint64, little endian, Typed Array [RFC8746])" }, + { QCborTag(80), " (IEEE 754 binary16, big endian, Typed Array [RFC8746])" }, + { QCborTag(81), " (IEEE 754 binary32, big endian, Typed Array [RFC8746])" }, + { QCborTag(82), " (IEEE 754 binary64, big endian, Typed Array [RFC8746])" }, + { QCborTag(83), " (IEEE 754 binary128, big endian, Typed Array [RFC8746])" }, + { QCborTag(84), " (IEEE 754 binary16, little endian, Typed Array [RFC8746])" }, + { QCborTag(85), " (IEEE 754 binary32, little endian, Typed Array [RFC8746])" }, + { QCborTag(86), " (IEEE 754 binary64, little endian, Typed Array [RFC8746])" }, + { QCborTag(87), " (IEEE 754 binary128, little endian, Typed Array [RFC8746])" }, + { QCborTag(96), " (COSE Encrypted Data Object [RFC9052])" }, + { QCborTag(97), " (COSE MACed Data Object [RFC9052])" }, + { QCborTag(98), " (COSE Signed Data Object [RFC9052])" }, + { QCborTag(100), " (Number of days since the epoch date 1970-01-01 [RFC8943])" }, + { QCborTag(101), " (alternatives as given by the uint + 128; see Section 9.1)" }, + { QCborTag(103), " (Geographic Coordinates)" }, + { QCborTag(104), " (Geographic Coordinate Reference System WKT or EPSG number)" }, + { QCborTag(110), " (relative object identifier (BER encoding); SDNV sequence [RFC9090])" }, + { QCborTag(111), " (object identifier (BER encoding) [RFC9090])" }, + { QCborTag(112), " (object identifier (BER encoding), relative to 1.3.6.1.4.1 [RFC9090])" }, + { QCborTag(120), " (Internet of Things Data Point)" }, + { QCborTag(260), + " (Network Address (IPv4 or IPv6 or MAC Address) (DEPRECATED in favor of 52 and 54 for IP" + " addresses) [RFC9164])" }, + { QCborTag(261), + " (Network Address Prefix (IPv4 or IPv6 Address + Mask Length) (DEPRECATED in favor of 52" + " and 54 for IP addresses) [RFC9164])" }, + { QCborTag(271), + " (DDoS Open Threat Signaling (DOTS) signal channel object, as defined in [RFC9132])" }, + { QCborTag(1004), " (full-date string [RFC8943])" }, + { QCborTag(1040), " (Multi-dimensional Array, column-major order [RFC8746])" }, + { QCborTag(55799), " (Self-described CBOR; see Section 3.4.6 [RFC8949])" }, + { QCborTag(55800), " (indicates that the file contains CBOR Sequences [RFC9277])" }, + { QCborTag(55801), + " (indicates that the file starts with a CBOR-Labeled Non-CBOR Data label. [RFC9277])" }, { QCborTag(-1), nullptr } }; // END GENERATED CODE enum { // See RFC 7049 section 2. - SmallValueBitLength = 5, - SmallValueMask = (1 << SmallValueBitLength) - 1, /* 0x1f */ - Value8Bit = 24, - Value16Bit = 25, - Value32Bit = 26, - Value64Bit = 27 + SmallValueBitLength = 5, + SmallValueMask = (1 << SmallValueBitLength) - 1, /* 0x1f */ + Value8Bit = 24, + Value16Bit = 25, + Value32Bit = 26, + Value64Bit = 27 }; //! [0] struct CborDumper { - enum DumpOption { - ShowCompact = 0x01, - ShowWidthIndicators = 0x02, - ShowAnnotated = 0x04 - }; + enum DumpOption { ShowCompact = 0x01, ShowWidthIndicators = 0x02, ShowAnnotated = 0x04 }; Q_DECLARE_FLAGS(DumpOptions, DumpOption) CborDumper(QFile *f, DumpOptions opts_); @@ -131,8 +181,7 @@ static int cborNumberSize(quint64 value) return normalSize; } -CborDumper::CborDumper(QFile *f, DumpOptions opts_) - : opts(opts_) +CborDumper::CborDumper(QFile *f, DumpOptions opts_) : opts(opts_) { // try to mmap the file, this is faster char *ptr = reinterpret_cast<char *>(f->map(0, f->size(), QFile::MapPrivateOption)); @@ -179,7 +228,8 @@ QCborError CborDumper::dump() return err; } -template <typename T> static inline bool canConvertTo(double v) +template<typename T> +static inline bool canConvertTo(double v) { using TypeInfo = std::numeric_limits<T>; // The [conv.fpint] (7.10 Floating-integral conversions) section of the @@ -200,31 +250,32 @@ template <typename T> static inline bool canConvertTo(double v) return v == floor(v); } -static QString fpToString(double v, const char *suffix) +static QString fpToString(double v, QLatin1StringView suffix = ""_L1) { if (qIsInf(v)) - return v < 0 ? QStringLiteral("-inf") : QStringLiteral("inf"); + return v < 0 ? "-inf"_L1 : "inf"_L1; if (qIsNaN(v)) - return QStringLiteral("nan"); + return "nan"_L1; if (canConvertTo<qint64>(v)) - return QString::number(qint64(v)) + ".0" + suffix; + return QString::number(qint64(v)) + ".0"_L1 + suffix; if (canConvertTo<quint64>(v)) - return QString::number(quint64(v)) + ".0" + suffix; + return QString::number(quint64(v)) + ".0"_L1 + suffix; QString s = QString::number(v, 'g', QLocale::FloatingPointShortest); - if (!s.contains('.') && !s.contains('e')) - s += '.'; - s += suffix; + if (!s.contains(u'.') && !s.contains(u'e')) + s += u'.'; + if (suffix.size()) + s += suffix; return s; }; void CborDumper::dumpOne(int nestingLevel) { - QString indent(1, QLatin1Char(' ')); + QString indent(1, u' '); QString indented = indent; if (!opts.testFlag(ShowCompact)) { - indent = QLatin1Char('\n') + QString(4 * nestingLevel, QLatin1Char(' ')); - indented = QLatin1Char('\n') + QString(4 + 4 * nestingLevel, QLatin1Char(' ')); + indent = u'\n' + QString(4 * nestingLevel, u' '); + indented = u'\n' + QString(4 + 4 * nestingLevel, u' '); } switch (reader.type()) { @@ -264,7 +315,7 @@ void CborDumper::dumpOne(int nestingLevel) printStringWidthIndicator(r.data.size()); r = reader.readByteArray(); - comma = QLatin1Char(',') + indented; + comma = u',' + indented; } } else { auto r = reader.readString(); @@ -273,7 +324,7 @@ void CborDumper::dumpOne(int nestingLevel) printStringWidthIndicator(r.data.toUtf8().size()); r = reader.readString(); - comma = QLatin1Char(',') + indented; + comma = u',' + indented; } } @@ -329,7 +380,7 @@ void CborDumper::dumpOne(int nestingLevel) if (reader.next()) { printWidthIndicator(quint64(tag)); printf("("); - dumpOne(nestingLevel); // same level! + dumpOne(nestingLevel); // same level! printf(")"); } @@ -361,15 +412,15 @@ void CborDumper::dumpOne(int nestingLevel) break; case QCborStreamReader::Float16: - printf("%s", qPrintable(fpToString(reader.toFloat16(), "f16"))); + printf("%s", qPrintable(fpToString(reader.toFloat16(), "f16"_L1))); reader.next(); break; case QCborStreamReader::Float: - printf("%s", qPrintable(fpToString(reader.toFloat(), "f"))); + printf("%s", qPrintable(fpToString(reader.toFloat(), "f"_L1))); reader.next(); break; case QCborStreamReader::Double: - printf("%s", qPrintable(fpToString(reader.toDouble(), ""))); + printf("%s", qPrintable(fpToString(reader.toDouble()))); reader.next(); break; case QCborStreamReader::Invalid: @@ -394,7 +445,7 @@ void CborDumper::dumpOneDetailed(int nestingLevel) if (cborNumberSize(value) != actualSize) printf(" (overlong)"); }; - auto print = [=](const char *descr, const char *fmt, ...) { + auto print = [&](const char *descr, const char *fmt, ...) { qint64 prevOffset = offset; offset = reader.currentOffset(); if (prevOffset == offset) @@ -422,13 +473,13 @@ void CborDumper::dumpOneDetailed(int nestingLevel) }; auto printFp = [=](const char *descr, double d) { - QString s = fpToString(d, ""); + QString s = fpToString(d); if (s.size() <= 6) return print(descr, "%s", qPrintable(s)); return print(descr, "%a", d); }; - auto printString = [=](const char *descr) { + auto printString = [&](const char *descr) { constexpr qsizetype ChunkSizeLimit = std::numeric_limits<int>::max(); QByteArray indent(nestingLevel * 2, ' '); const char *chunkStr = (reader.isLengthKnown() ? "" : "chunk "); @@ -437,7 +488,7 @@ void CborDumper::dumpOneDetailed(int nestingLevel) qsizetype size = reader.currentStringChunkSize(); if (size < 0) - return; // error + return; // error if (size >= ChunkSizeLimit) { fprintf(stderr, "String length too big, %lli\n", qint64(size)); exit(EXIT_FAILURE); @@ -482,7 +533,7 @@ void CborDumper::dumpOneDetailed(int nestingLevel) printf(" %s%s", indent.constData(), section.toHex(' ').constData()); // print the decode - QByteArray spaces(width > 0 ? width - section.size() * 3 + 1: 0, ' '); + QByteArray spaces(width > 0 ? width - section.size() * 3 + 1 : 0, ' '); printf("%s # \"", spaces.constData()); auto ptr = reinterpret_cast<const uchar *>(section.constData()); for (int j = 0; j < section.size(); ++j) @@ -494,7 +545,7 @@ void CborDumper::dumpOneDetailed(int nestingLevel) // get the next chunk size = reader.currentStringChunkSize(); if (size < 0) - return; // error + return; // error if (size >= ChunkSizeLimit) { fprintf(stderr, "String length too big, %lli\n", qint64(size)); exit(EXIT_FAILURE); @@ -633,7 +684,9 @@ void CborDumper::printByteArray(const QByteArray &ba) break; case quint8(QCborKnownTags::ExpectedBase64url): - printf("b64'%s'", ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals).constData()); + printf("b64'%s'", + ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + .constData()); break; } } @@ -674,23 +727,20 @@ int main(int argc, char *argv[]) setlocale(LC_ALL, "C"); QCommandLineParser parser; - parser.setApplicationDescription(QStringLiteral("CBOR Dumper tool")); + parser.setApplicationDescription("CBOR Dumper tool"_L1); parser.addHelpOption(); - QCommandLineOption compact({QStringLiteral("c"), QStringLiteral("compact")}, - QStringLiteral("Use compact form (no line breaks)")); + QCommandLineOption compact({"c"_L1, "compact"_L1}, "Use compact form (no line breaks)"_L1); parser.addOption(compact); - QCommandLineOption showIndicators({QStringLiteral("i"), QStringLiteral("indicators")}, - QStringLiteral("Show indicators for width of lengths and integrals")); + QCommandLineOption showIndicators({ "i"_L1, "indicators"_L1 }, + "Show indicators for width of lengths and integrals"_L1); parser.addOption(showIndicators); - QCommandLineOption verbose({QStringLiteral("a"), QStringLiteral("annotated")}, - QStringLiteral("Show bytes and annotated decoding")); + QCommandLineOption verbose({"a"_L1, "annotated"_L1}, "Show bytes and annotated decoding"_L1); parser.addOption(verbose); - parser.addPositionalArgument(QStringLiteral("[source]"), - QStringLiteral("CBOR file to read from")); + parser.addPositionalArgument("[source]"_L1, "CBOR file to read from"_L1); parser.process(app); diff --git a/examples/corelib/serialization/cbordump/tag-transform.xslt b/examples/corelib/serialization/cbordump/tag-transform.xslt deleted file mode 100644 index 3cc1b9b293..0000000000 --- a/examples/corelib/serialization/cbordump/tag-transform.xslt +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0"?> -<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:a="http://www.iana.org/assignments" xmlns="http://www.iana.org/assignments" xmlns:_="http://www.iana.org/assignments" xmlns:DEFAULT="http://www.iana.org/assignments" version="1.0"> -<xsl:output omit-xml-declaration="yes" indent="no" method="text"/> -<xsl:template match="/a:registry[@id='cbor-tags']">struct CborTagDescription -{ - QCborTag tag; - const char *description; // with space and parentheses -}; - -// <xsl:value-of select="a:registry/a:title"/> -static const CborTagDescription tagDescriptions[] = { - // from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml -<xsl:for-each select="a:registry/a:record"> - <xsl:sort select="a:value" data-type="number"/> - <xsl:if test="a:semantics != ''"> - <xsl:call-template name="row"/> - </xsl:if> - </xsl:for-each> { QCborTag(-1), nullptr } -}; -</xsl:template> -<xsl:template name="row"> { QCborTag(<xsl:value-of select="a:value"/>), " (<xsl:value-of select="a:semantics"/> <xsl:call-template name="xref"/>)" }, -</xsl:template> -<xsl:template name="xref"><xsl:if test="a:xref/@type = 'rfc'"> [<xsl:value-of select="translate(a:xref/@data,'rfc','RFC')"/>]</xsl:if> -</xsl:template> -</xsl:stylesheet> diff --git a/examples/corelib/serialization/convert/CMakeLists.txt b/examples/corelib/serialization/convert/CMakeLists.txt index 5a10a78a5a..24ad5bbb6a 100644 --- a/examples/corelib/serialization/convert/CMakeLists.txt +++ b/examples/corelib/serialization/convert/CMakeLists.txt @@ -1,27 +1,27 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(convert LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") +if (ANDROID) + message(FATAL_ERROR "This project cannot be built on Android.") endif() -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/convert") - find_package(Qt6 REQUIRED COMPONENTS Core) qt_standard_project_setup() qt_add_executable(convert cborconverter.cpp cborconverter.h - converter.h + converter.cpp converter.h datastreamconverter.cpp datastreamconverter.h + debugtextdumper.cpp debugtextdumper.h jsonconverter.cpp jsonconverter.h main.cpp nullconverter.cpp nullconverter.h textconverter.cpp textconverter.h + variantorderedmap.h xmlconverter.cpp xmlconverter.h ) @@ -30,7 +30,14 @@ target_link_libraries(convert PRIVATE ) install(TARGETS convert - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET convert + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/serialization/convert/cborconverter.cpp b/examples/corelib/serialization/convert/cborconverter.cpp index 85ca8c12ed..969f2741e0 100644 --- a/examples/corelib/serialization/convert/cborconverter.cpp +++ b/examples/corelib/serialization/convert/cborconverter.cpp @@ -2,20 +2,24 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "cborconverter.h" +#include "variantorderedmap.h" +#include <QCborArray> +#include <QCborMap> #include <QCborStreamReader> #include <QCborStreamWriter> -#include <QCborMap> -#include <QCborArray> #include <QCborValue> #include <QDataStream> -#include <QFloat16> +#include <QDebug> #include <QFile> +#include <QFloat16> #include <QMetaType> #include <QTextStream> #include <stdio.h> +using namespace Qt::StringLiterals; + static CborConverter cborConverter; static CborDiagnosticDumper cborDiagnosticDumper; @@ -55,9 +59,9 @@ QT_END_NAMESPACE // non-string keys in CBOR maps (QVariantMap can't handle those). Instead, we // have our own set of converter functions so we can keep the keys properly. +//! [0] static QVariant convertCborValue(const QCborValue &value); -//! [0] static QVariant convertCborMap(const QCborMap &map) { VariantOrderedMap result; @@ -85,8 +89,9 @@ static QVariant convertCborValue(const QCborValue &value) return value.toVariant(); } //! [0] -enum TrimFloatingPoint { Double, Float, Float16 }; + //! [1] +enum TrimFloatingPoint { Double, Float, Float16 }; static QCborValue convertFromVariant(const QVariant &v, TrimFloatingPoint fpTrimming) { if (v.userType() == QMetaType::QVariantList) { @@ -118,41 +123,28 @@ static QCborValue convertFromVariant(const QVariant &v, TrimFloatingPoint fpTrim } //! [1] -QString CborDiagnosticDumper::name() +QString CborDiagnosticDumper::name() const { - return QStringLiteral("cbor-dump"); + return "cbor-dump"_L1; } -Converter::Direction CborDiagnosticDumper::directions() +Converter::Directions CborDiagnosticDumper::directions() const { - return Out; + return Direction::Out; } -Converter::Options CborDiagnosticDumper::outputOptions() +Converter::Options CborDiagnosticDumper::outputOptions() const { return SupportsArbitraryMapKeys; } -const char *CborDiagnosticDumper::optionsHelp() +const char *CborDiagnosticDumper::optionsHelp() const { return diagnosticHelp; } -bool CborDiagnosticDumper::probeFile(QIODevice *f) -{ - Q_UNUSED(f); - return false; -} - -QVariant CborDiagnosticDumper::loadFile(QIODevice *f, Converter *&outputConverter) -{ - Q_UNREACHABLE(); - Q_UNUSED(f); - Q_UNUSED(outputConverter); - return QVariant(); -} - -void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const { QCborValue::DiagnosticNotationOptions opts = QCborValue::LineWrapped; for (const QString &s : options) { @@ -175,14 +167,12 @@ void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents, cons } } - fprintf(stderr, "Unknown CBOR diagnostic option '%s'. Available options are:\n%s", - qPrintable(s), diagnosticHelp); - exit(EXIT_FAILURE); + qFatal("Unknown CBOR diagnostic option '%s'. Available options are:\n%s", + qPrintable(s), diagnosticHelp); } QTextStream out(f); - out << convertFromVariant(contents, Double).toDiagnosticNotation(opts) - << Qt::endl; + out << convertFromVariant(contents, Double).toDiagnosticNotation(opts) << Qt::endl; } CborConverter::CborConverter() @@ -190,37 +180,36 @@ CborConverter::CborConverter() qRegisterMetaType<QCborTag>(); } -QString CborConverter::name() +QString CborConverter::name() const { return "cbor"; } -Converter::Direction CborConverter::directions() +Converter::Directions CborConverter::directions() const { - return InOut; + return Direction::InOut; } -Converter::Options CborConverter::outputOptions() +Converter::Options CborConverter::outputOptions() const { return SupportsArbitraryMapKeys; } -const char *CborConverter::optionsHelp() +const char *CborConverter::optionsHelp() const { return cborOptionHelp; } -bool CborConverter::probeFile(QIODevice *f) +bool CborConverter::probeFile(QIODevice *f) const { if (QFile *file = qobject_cast<QFile *>(f)) { - if (file->fileName().endsWith(QLatin1String(".cbor"))) + if (file->fileName().endsWith(".cbor"_L1)) return true; } return f->isReadable() && f->peek(3) == QByteArray("\xd9\xd9\xf7", 3); } -//! [2] -QVariant CborConverter::loadFile(QIODevice *f, Converter *&outputConverter) +QVariant CborConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const { const char *ptr = nullptr; if (auto file = qobject_cast<QFile *>(f)) @@ -237,28 +226,25 @@ QVariant CborConverter::loadFile(QIODevice *f, Converter *&outputConverter) QCborValue contents = QCborValue::fromCbor(reader); qint64 offset = reader.currentOffset(); if (reader.lastError()) { - fprintf(stderr, "Error loading CBOR contents (byte %lld): %s\n", offset, - qPrintable(reader.lastError().toString())); - fprintf(stderr, " bytes: %s\n", - (ptr ? mapped.mid(offset, 9) : f->read(9)).toHex(' ').constData()); - exit(EXIT_FAILURE); + qFatal().nospace() + << "Error loading CBOR contents (byte " << offset + << "): " << reader.lastError().toString() + << "\n bytes: " << (ptr ? mapped.mid(offset, 9) : f->read(9)); } else if (offset < mapped.size() || (!ptr && f->bytesAvailable())) { - fprintf(stderr, "Warning: bytes remaining at the end of the CBOR stream\n"); + qWarning("Warning: bytes remaining at the end of the CBOR stream"); } if (outputConverter == nullptr) outputConverter = &cborDiagnosticDumper; - else if (outputConverter == null) + else if (isNull(outputConverter)) return QVariant(); else if (!outputConverter->outputOptions().testFlag(SupportsArbitraryMapKeys)) return contents.toVariant(); return convertCborValue(contents); } -//! [2] -//! [3] -void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) + +void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) const { - //! [3] bool useSignature = true; bool useIntegers = true; enum { Yes, No, Always } useFloat16 = Yes, useFloat = Yes; @@ -313,13 +299,13 @@ void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStri } } - fprintf(stderr, "Unknown CBOR format option '%s'. Valid options are:\n%s", - qPrintable(s), cborOptionHelp); - exit(EXIT_FAILURE); + qFatal("Unknown CBOR format option '%s'. Valid options are:\n%s", + qPrintable(s), cborOptionHelp); } - //! [4] - QCborValue v = convertFromVariant(contents, - useFloat16 == Always ? Float16 : useFloat == Always ? Float : Double); + + QCborValue v = + convertFromVariant(contents, + useFloat16 == Always ? Float16 : useFloat == Always ? Float : Double); QCborStreamWriter writer(f); if (useSignature) writer.append(QCborKnownTags::Signature); @@ -333,4 +319,3 @@ void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStri opts |= QCborValue::UseFloat16; v.toCbor(writer, opts); } -//! [4] diff --git a/examples/corelib/serialization/convert/cborconverter.h b/examples/corelib/serialization/convert/cborconverter.h index d19c9eb33e..db68f99fda 100644 --- a/examples/corelib/serialization/convert/cborconverter.h +++ b/examples/corelib/serialization/convert/cborconverter.h @@ -10,13 +10,12 @@ class CborDiagnosticDumper : public Converter { // Converter interface public: - QString name() override; - Direction directions() override; - Options outputOptions() override; - const char *optionsHelp() override; - bool probeFile(QIODevice *f) override; - QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; - void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; + QString name() const override; + Directions directions() const override; + Options outputOptions() const override; + const char *optionsHelp() const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; class CborConverter : public Converter @@ -26,13 +25,14 @@ public: // Converter interface public: - QString name() override; - Direction directions() override; - Options outputOptions() override; - const char *optionsHelp() override; - bool probeFile(QIODevice *f) override; - QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; - void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; + QString name() const override; + Directions directions() const override; + Options outputOptions() const override; + const char *optionsHelp() const override; + bool probeFile(QIODevice *f) const override; + QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; #endif // CBORCONVERTER_H diff --git a/examples/corelib/serialization/convert/convert.pro b/examples/corelib/serialization/convert/convert.pro index 4c6b0b557a..7592de7a22 100644 --- a/examples/corelib/serialization/convert/convert.pro +++ b/examples/corelib/serialization/convert/convert.pro @@ -11,18 +11,22 @@ target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/convert INSTALLS += target SOURCES += main.cpp \ + converter.cpp \ cborconverter.cpp \ - jsonconverter.cpp \ datastreamconverter.cpp \ + debugtextdumper.cpp \ + jsonconverter.cpp \ + nullconverter.cpp \ textconverter.cpp \ - xmlconverter.cpp \ - nullconverter.cpp + xmlconverter.cpp HEADERS += \ converter.h \ cborconverter.h \ - jsonconverter.h \ datastreamconverter.h \ + debugtextdumper.h \ + jsonconverter.h \ + nullconverter.h \ textconverter.h \ - xmlconverter.h \ - nullconverter.h + variantorderedmap.h \ + xmlconverter.h diff --git a/examples/corelib/serialization/convert/converter.cpp b/examples/corelib/serialization/convert/converter.cpp new file mode 100644 index 0000000000..a57f305971 --- /dev/null +++ b/examples/corelib/serialization/convert/converter.cpp @@ -0,0 +1,44 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "converter.h" + +//! [0] +Converter::Converter() +{ + converters().append(this); +} + +Converter::~Converter() +{ + converters().removeAll(this); +} + +QList<const Converter *> &Converter::converters() +{ + Q_CONSTINIT static QList<const Converter *> store; + return store; +} + +const QList<const Converter *> &Converter::allConverters() +{ + return converters(); +} +//! [0] + +// Some virtual methods that Converter classes needn't override, when not relevant: +Converter::Options Converter::outputOptions() const { return {}; } +const char *Converter::optionsHelp() const { return nullptr; } +bool Converter::probeFile(QIODevice *) const { return false; } + +// The virtual method they should override if they claim to support In: +QVariant Converter::loadFile(QIODevice *, const Converter *&outputConverter) const +{ + Q_ASSERT(!directions().testFlag(Converter::Direction::In)); + // For those that don't, this should never be called. + Q_UNIMPLEMENTED(); + // But every implementation should at least do this: + if (!outputConverter) + outputConverter = this; + return QVariant(); +} diff --git a/examples/corelib/serialization/convert/converter.h b/examples/corelib/serialization/convert/converter.h index 4da4d47267..40b7575a1e 100644 --- a/examples/corelib/serialization/convert/converter.h +++ b/examples/corelib/serialization/convert/converter.h @@ -5,53 +5,41 @@ #define CONVERTER_H #include <QIODevice> -#include <QPair> -#include <QVariant> -#include <QVariantMap> #include <QList> +#include <QStringList> +#include <QVariant> -class VariantOrderedMap : public QList<QPair<QVariant, QVariant>> -{ -public: - VariantOrderedMap() = default; - VariantOrderedMap(const QVariantMap &map) - { - reserve(map.size()); - for (auto it = map.begin(); it != map.end(); ++it) - append({it.key(), it.value()}); - } -}; -using Map = VariantOrderedMap; -Q_DECLARE_METATYPE(Map) - +//! [0] class Converter { + static QList<const Converter *> &converters(); protected: Converter(); + static bool isNull(const Converter *converter); // in nullconverter.cpp public: - static Converter *null; + static const QList<const Converter *> &allConverters(); - enum Direction { - In = 1, Out = 2, InOut = 3 - }; + enum class Direction { In = 1, Out = 2, InOut = In | Out }; + Q_DECLARE_FLAGS(Directions, Direction) - enum Option { - SupportsArbitraryMapKeys = 0x01 - }; + enum Option { SupportsArbitraryMapKeys = 0x01 }; Q_DECLARE_FLAGS(Options, Option) virtual ~Converter() = 0; - virtual QString name() = 0; - virtual Direction directions() = 0; - virtual Options outputOptions() = 0; - virtual const char *optionsHelp() = 0; - virtual bool probeFile(QIODevice *f) = 0; - virtual QVariant loadFile(QIODevice *f, Converter *&outputConverter) = 0; - virtual void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) = 0; + virtual QString name() const = 0; + virtual Directions directions() const = 0; + virtual Options outputOptions() const; + virtual const char *optionsHelp() const; + virtual bool probeFile(QIODevice *f) const; + virtual QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const; + virtual void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const = 0; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(Converter::Directions) Q_DECLARE_OPERATORS_FOR_FLAGS(Converter::Options) +//! [0] #endif // CONVERTER_H diff --git a/examples/corelib/serialization/convert/datastreamconverter.cpp b/examples/corelib/serialization/convert/datastreamconverter.cpp index 451688e378..ce28fcb98e 100644 --- a/examples/corelib/serialization/convert/datastreamconverter.cpp +++ b/examples/corelib/serialization/convert/datastreamconverter.cpp @@ -2,20 +2,22 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "datastreamconverter.h" +#include "debugtextdumper.h" +#include "variantorderedmap.h" #include <QDataStream> -#include <QDebug> -#include <QTextStream> + +using namespace Qt::StringLiterals; static const char dataStreamOptionHelp[] = "byteorder=host|big|little Byte order to use.\n" - "version=<n> QDataStream version (default: Qt 5.0).\n" + "version=<n> QDataStream version (default: Qt 6.0).\n" ; static const char signature[] = "qds"; -static DataStreamDumper dataStreamDumper; -static DataStreamConverter DataStreamConverter; +static DataStreamConverter dataStreamConverter; +static DebugTextDumper debugTextDumper; QDataStream &operator<<(QDataStream &ds, const VariantOrderedMap &map) { @@ -42,126 +44,44 @@ QDataStream &operator>>(QDataStream &ds, VariantOrderedMap &map) return ds; } - -static QString dumpVariant(const QVariant &v, const QString &indent = QLatin1String("\n")) -{ - QString result; - QString indented = indent + QLatin1String(" "); - - int type = v.userType(); - if (type == qMetaTypeId<VariantOrderedMap>() || type == QMetaType::QVariantMap) { - const auto map = (type == QMetaType::QVariantMap) ? - VariantOrderedMap(v.toMap()) : qvariant_cast<VariantOrderedMap>(v); - - result = QLatin1String("Map {"); - for (const auto &pair : map) { - result += indented + dumpVariant(pair.first, indented); - result.chop(1); // remove comma - result += QLatin1String(" => ") + dumpVariant(pair.second, indented); - - } - result.chop(1); // remove comma - result += indent + QLatin1String("},"); - } else if (type == QMetaType::QVariantList) { - const QVariantList list = v.toList(); - - result = QLatin1String("List ["); - for (const auto &item : list) - result += indented + dumpVariant(item, indented); - result.chop(1); // remove comma - result += indent + QLatin1String("],"); - } else { - QDebug debug(&result); - debug.nospace() << v << ','; - } - return result; -} - -QString DataStreamDumper::name() -{ - return QStringLiteral("datastream-dump"); -} - -Converter::Direction DataStreamDumper::directions() -{ - return Out; -} - -Converter::Options DataStreamDumper::outputOptions() -{ - return SupportsArbitraryMapKeys; -} - -const char *DataStreamDumper::optionsHelp() -{ - return nullptr; -} - -bool DataStreamDumper::probeFile(QIODevice *f) -{ - Q_UNUSED(f); - return false; -} - -QVariant DataStreamDumper::loadFile(QIODevice *f, Converter *&outputConverter) -{ - Q_UNREACHABLE(); - Q_UNUSED(f); - Q_UNUSED(outputConverter); - return QVariant(); -} - -void DataStreamDumper::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) -{ - Q_UNUSED(options); - QString s = dumpVariant(contents); - s[s.size() - 1] = QLatin1Char('\n'); // replace the comma with newline - - QTextStream out(f); - out << s; -} - DataStreamConverter::DataStreamConverter() { qRegisterMetaType<VariantOrderedMap>(); } -QString DataStreamConverter::name() +QString DataStreamConverter::name() const { - return QStringLiteral("datastream"); + return "datastream"_L1; } -Converter::Direction DataStreamConverter::directions() +Converter::Directions DataStreamConverter::directions() const { - return InOut; + return Direction::InOut; } -Converter::Options DataStreamConverter::outputOptions() +Converter::Options DataStreamConverter::outputOptions() const { return SupportsArbitraryMapKeys; } -const char *DataStreamConverter::optionsHelp() +const char *DataStreamConverter::optionsHelp() const { return dataStreamOptionHelp; } -bool DataStreamConverter::probeFile(QIODevice *f) +bool DataStreamConverter::probeFile(QIODevice *f) const { return f->isReadable() && f->peek(sizeof(signature) - 1) == signature; } -QVariant DataStreamConverter::loadFile(QIODevice *f, Converter *&outputConverter) +QVariant DataStreamConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const { if (!outputConverter) - outputConverter = &dataStreamDumper; + outputConverter = &debugTextDumper; char c; - if (f->read(sizeof(signature) -1) != signature || - !f->getChar(&c) || (c != 'l' && c != 'B')) { - fprintf(stderr, "Could not load QDataStream file: invalid signature.\n"); - exit(EXIT_FAILURE); - } + if (f->read(sizeof(signature) - 1) != signature || !f->getChar(&c) || (c != 'l' && c != 'B')) + qFatal("Could not load QDataStream file: invalid signature."); QDataStream ds(f); ds.setByteOrder(c == 'l' ? QDataStream::LittleEndian : QDataStream::BigEndian); @@ -175,9 +95,10 @@ QVariant DataStreamConverter::loadFile(QIODevice *f, Converter *&outputConverter return result; } -void DataStreamConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +void DataStreamConverter::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const { - QDataStream::Version version = QDataStream::Qt_5_0; + QDataStream::Version version = QDataStream::Qt_6_0; auto order = QDataStream::ByteOrder(QSysInfo::ByteOrder); for (const QString &option : options) { const QStringList pair = option.split('='); @@ -202,18 +123,16 @@ void DataStreamConverter::saveFile(QIODevice *f, const QVariant &contents, const continue; } - fprintf(stderr, "Invalid version number '%s': must be a number from 1 to %d.\n", - qPrintable(pair.last()), QDataStream::Qt_DefaultCompiledVersion); - exit(EXIT_FAILURE); + qFatal("Invalid version number '%s': must be a number from 1 to %d.", + qPrintable(pair.last()), QDataStream::Qt_DefaultCompiledVersion); } } - fprintf(stderr, "Unknown QDataStream formatting option '%s'. Available options are:\n%s", + qFatal("Unknown QDataStream formatting option '%s'. Available options are:\n%s", qPrintable(option), dataStreamOptionHelp); - exit(EXIT_FAILURE); } - char c = order == QDataStream::LittleEndian ? 'l' : 'B'; + char c = order == QDataStream::LittleEndian ? 'l' : 'B'; f->write(signature); f->write(&c, 1); diff --git a/examples/corelib/serialization/convert/datastreamconverter.h b/examples/corelib/serialization/convert/datastreamconverter.h index 95c8861e0e..201f3c4754 100644 --- a/examples/corelib/serialization/convert/datastreamconverter.h +++ b/examples/corelib/serialization/convert/datastreamconverter.h @@ -6,19 +6,6 @@ #include "converter.h" -class DataStreamDumper : public Converter -{ - // Converter interface -public: - QString name() override; - Direction directions() override; - Options outputOptions() override; - const char *optionsHelp() override; - bool probeFile(QIODevice *f) override; - QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; - void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; -}; - class DataStreamConverter : public Converter { public: @@ -26,13 +13,14 @@ public: // Converter interface public: - QString name() override; - Direction directions() override; - Options outputOptions() override; - const char *optionsHelp() override; - bool probeFile(QIODevice *f) override; - QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; - void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; + QString name() const override; + Directions directions() const override; + Options outputOptions() const override; + const char *optionsHelp() const override; + bool probeFile(QIODevice *f) const override; + QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; #endif // DATASTREAMCONVERTER_H diff --git a/examples/corelib/serialization/convert/debugtextdumper.cpp b/examples/corelib/serialization/convert/debugtextdumper.cpp new file mode 100644 index 0000000000..f010bd8e2a --- /dev/null +++ b/examples/corelib/serialization/convert/debugtextdumper.cpp @@ -0,0 +1,74 @@ +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "debugtextdumper.h" +#include "variantorderedmap.h" + +#include <QDebug> +#include <QTextStream> + +using namespace Qt::StringLiterals; + +// Static instance is declared in datastreamconverter.cpp, since it uses it. + +static QString dumpVariant(const QVariant &v, const QString &indent = "\n"_L1) +{ + QString result; + QString indented = indent + " "_L1; + + int type = v.userType(); + if (type == qMetaTypeId<VariantOrderedMap>() || type == QMetaType::QVariantMap) { + const auto map = (type == QMetaType::QVariantMap) ? VariantOrderedMap(v.toMap()) + : qvariant_cast<VariantOrderedMap>(v); + + result = "Map {"_L1; + for (const auto &pair : map) { + result += indented + dumpVariant(pair.first, indented); + result.chop(1); // remove comma + result += " => "_L1 + dumpVariant(pair.second, indented); + } + result.chop(1); // remove comma + result += indent + "},"_L1; + } else if (type == QMetaType::QVariantList) { + const QVariantList list = v.toList(); + + result = "List ["_L1; + for (const auto &item : list) + result += indented + dumpVariant(item, indented); + result.chop(1); // remove comma + result += indent + "],"_L1; + } else { + QDebug debug(&result); + debug.nospace() << v << ','; + } + return result; +} + +QString DebugTextDumper::name() const +{ + return "debugtext-dump"_L1; +} + +Converter::Directions DebugTextDumper::directions() const +{ + return Direction::Out; +} + +Converter::Options DebugTextDumper::outputOptions() const +{ + return SupportsArbitraryMapKeys; +} + +void DebugTextDumper::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const +{ + if (!options.isEmpty()) { + qFatal("Unknown option '%s' to debug text output. This format has no options.", + qPrintable(options.first())); + } + QString s = dumpVariant(contents); + s[s.size() - 1] = u'\n'; // replace the comma with newline + + QTextStream out(f); + out << s; +} diff --git a/examples/corelib/serialization/convert/debugtextdumper.h b/examples/corelib/serialization/convert/debugtextdumper.h new file mode 100644 index 0000000000..7d3d762104 --- /dev/null +++ b/examples/corelib/serialization/convert/debugtextdumper.h @@ -0,0 +1,20 @@ +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef DEBUGTEXTDUMPER_H +#define DEBUGTEXTDUMPER_H + +#include "converter.h" + +class DebugTextDumper : public Converter +{ + // Converter interface +public: + QString name() const override; + Directions directions() const override; + Options outputOptions() const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; +}; + +#endif // DEBUGTEXTDUMPER_H diff --git a/examples/corelib/serialization/convert/doc/images/convert.png b/examples/corelib/serialization/convert/doc/images/convert.png Binary files differindex 8d6816a626..53e05b4333 100644 --- a/examples/corelib/serialization/convert/doc/images/convert.png +++ b/examples/corelib/serialization/convert/doc/images/convert.png diff --git a/examples/corelib/serialization/convert/doc/src/convert.qdoc b/examples/corelib/serialization/convert/doc/src/convert.qdoc index dc3264a469..187e81a85e 100644 --- a/examples/corelib/serialization/convert/doc/src/convert.qdoc +++ b/examples/corelib/serialization/convert/doc/src/convert.qdoc @@ -3,78 +3,154 @@ /*! \example serialization/convert - \title Convert Example + \examplecategory {Data Processing & I/O} + \meta tag {network} + \title Serialization Converter - \brief The Convert example demonstrates how to convert between different - serialization formats. + \brief How to convert between different serialization formats. - The Convert example converts between the serialization formats JSON, CBOR, - XML, QDataStream and text. It can also auto detect the format being used. - Not all formats support both input and output, and they have different - sets of which types they support. QDataStream and XML are the richest, - followed by CBOR, then JSON, and then the plain text one. + This example converts between JSON, CBOR, XML, QDataStream and some simple + text formats. It can auto-detect the format being used, or be told which + format to use. Not all formats support both input and output, and they have + different sets of which content datatypes they support. QDataStream and XML + are the richest, followed by CBOR, then JSON, and then the plain text + formats. Conversion via the less capable formats is apt to lose structure + from the data. \image convert.png + \sa {Parsing and displaying CBOR data}, {Saving and Loading a Game} + \section1 The Converter Class - The Converter class is the abstract superclass for all the converters to - and from all the formats. They all convert to and from the QVariant class, - which is used to represent all the datastructures internally. - The name() function returns the name of the converter. The directions() - function is used to determine if a converter can be used for input, output, - or both. The outputOptions() and optionsHelp() functions are used to get - and query which options are used by the different converters. The - probeFile() function is used to determine if a file has the same file - format as the converter. The loadFile() function deserializes the given - file, while the saveFile() serializes to the given file. + The Converter class is the abstract superclass for all the converters to and + from all the formats. They all convert from or to the QVariant class, which + is used to represent all the datastructures internally. - \section1 The CborConverter Class + \snippet serialization/convert/converter.h 0 - The CborConverter class shows how to serialize to and from the CBOR-format. - There is also a CborDiagnosticDumper class to output in CBOR diagnostic - notation. That is similar to JSON, but not exactly, because it allows - displaying the contents of a CBOR stream losslessly, while a conversion - to JSON is lossy. + The Converter constructor and destructor manage a list of available + converters used by the main program so that it knows what converters are + available. Each converter type defines a static instance that ensures it is + constructed and thus available to the main program via this list. The \c + allConverters() method provides \c main()'s code with access to the list. - The convertCborValue() function is used to convert a QCborValue to a - QVariant. It uses the helper functions convertCborMap() and - convertCborArray(). - \snippet serialization/convert/cborconverter.cpp 0 - - A CBOR-file is read using loadFile() function. - \snippet serialization/convert/cborconverter.cpp 2 - - The convertFromVariant() function is used to convert a QVariant to a - QCborValue. - \snippet serialization/convert/cborconverter.cpp 1 + \snippet serialization/convert/converter.cpp 0 - A CBOR-file is written using the saveFile() function. - \snippet serialization/convert/cborconverter.cpp 3 - \snippet serialization/convert/cborconverter.cpp 4 + The name() function returns the name of the converter. The directions() + function is used to determine if a converter can be used for input, output, + or both. These enable the main program to report what converters are + available in its help text for the command-line options to select input and + output formats. + + \snippet serialization/convert/main.cpp 0 + + The optionsHelp() function is used to report the various command-line + options supported by the available formats, when queried using its \c + {--format-options <format>} command-line option. + + \snippet serialization/convert/main.cpp 1 + + The outputOptions() function reports the output capabilities of a converter. + At present the only optional feature is support for arbitrary keys in + mappings from keys to values. An input converter's loadFile() can use this + information to tailor the form in which it presents the data it has read, to + be as faithfully represented by the output converter as its capabilities + permit. + + The probeFile() function is used to determine if a file matches the format + of the converter. The main program uses this to determine what format to use + when reading or writing a file, based on its name and potentially content, + when the user has not specified the format to use on the command-line. + + The loadFile() function deserializes data. The caller tells loadFile() which + serializer it intends to use, so that loadFile() can query its + outputOptions() to determine the form in which to represent the loaded data. + If the caller hasn't settled on a choice of output converter, loadFile() + supplies it with a default output converter suitable to the data it is + returning. + + The saveFile() function serializes data. It is passed options from the + command-line, as described by loadHelp(), that can tune the details of how + it represents the data when saving to file. + + Both loadFile() and saveFile() can be used with an arbitrary \l QIODevice. + This means that a Converter could also be used with a network socket or + other source of data, to read from or write to. In the present program, the + main program always passes a \l QFile, accessing either a file on disk or + one of the standard streams of the process. + + \section2 The Available Converters + + Several converters are supported, illustrating how the converter program + could be adapted to other formats, should the need arise. See the source + code for each for its details. The CBOR converters serve as a relatively + full-featured illustration of the ways converters can work, that we'll look + into in more detail below. This table summarizes the available converters: + + \table + \header \li Class \li mode \li format + \row \li CborConverter \li In/Out \li CBOR + \row \li CborDiagnosticDumper \li Out \li CBOR diagnostic + \row \li DataStreamConverter \li In/Out \li QDataStream + \row \li DebugTextDumper \li Out \li Lossless, non-standard, human-readable + \row \li JsonConverter \li In/Out \li JSON + \row \li NullConverter \li Out \li No output + \row \li TextConverter \li In/Out \li Structured plain text + \row \li XmlConverter \li In/Out \li XML + \endtable + + Those that support input use themselves as loadFile()'s fallback converter, + except for the CBOR and QDataStream converters, which use their respective + output-only dumper companion classes. The null converter can be used as + output converter when running the program for the sake of any validation or + verification that an input converter may perform. + + \section2 The CborConverter and CborDiagnosticDumper Classes + + The CborConverter class supports serializing to and from the CBOR format. + It supports various options to configure the output of floating point values + and a \c{signature} option to determine whether to start its output with a + CBOR tag that serves as a file header, identifying the file as containing + CBOR data. - \sa {CBOR Support in Qt} + There is also a CborDiagnosticDumper class to output in CBOR diagnostic + notation. It does not support loading data. The form of its output can be + configured using two options. One selects whether to use the (more verbose) + extended CBOR diagnostic format. The other control whether each CBOR value + appears on a separate line. - \section1 The DataStreamConverter Class + The plain diagnostic notation is similar to JSON, but not exactly, because + it supports displaying the contents of a CBOR stream losslessly, while a + conversion to JSON can be lossy. CborConverter's loadFile() uses + CborDiagnosticDumper for the fallback output converter, if its caller hasn't + determined the output format for itself. - The DataStreamConverter class is used to serialize to and from the - QDataStream format. There is also the DataStreamDumper class for outputting - the data lossless in a non-standardized human readable format. + The convertCborValue(), convertCborMap() and convertCborArray() helper + functions are used to convert a QCborValue to a QVariant, for the benefit of + CborConverter::loadFile(). - \section1 The JsonConverter Class + \snippet serialization/convert/cborconverter.cpp 0 - The JsonConverter class is used to serialize to and from the JSON-format. - \sa {JSON Support in Qt} + The convertFromVariant() function is used to convert a QVariant to a + QCborValue for output by the \c saveFile() of either class. - \section1 The XmlConverter Class + \snippet serialization/convert/cborconverter.cpp 1 - The XmlConverter class is used to serialize to and from the XML-format. + \sa {CBOR Support in Qt} - \section1 The TextConverter Class + \section1 The convert program - The TextConverter class is used to serialize to and from a text format. + The \c main() function sets up a \l QApplication and a \l QCommandLineParser + to make sense of the options the user has specified and provide help if the + user asks for it. It uses the values obtained for the various \l + QCommandLineOption instances describing the user's choices, plus the + positional arguments for file names, to prepare the converters it will use. - \section1 The NullConverter Class + It then uses its input converter to load data (and possibly resolve its + choice of output converter, if it hasn't selected one yet) and its output + converter to serialize that data, taking account of any output options the + user has supplied on the command-line. - The NullConverter class is an output serializer that does nothing. + \snippet serialization/convert/main.cpp 2 */ diff --git a/examples/corelib/serialization/convert/jsonconverter.cpp b/examples/corelib/serialization/convert/jsonconverter.cpp index f52c9db554..1b59ed5c1f 100644 --- a/examples/corelib/serialization/convert/jsonconverter.cpp +++ b/examples/corelib/serialization/convert/jsonconverter.cpp @@ -9,49 +9,39 @@ #include <QJsonObject> #include <QJsonValue> +using namespace Qt::StringLiterals; + static JsonConverter jsonConverter; -static const char jsonOptionHelp[] = - "compact=no|yes Use compact JSON form.\n"; +static const char jsonOptionHelp[] = "compact=no|yes Use compact JSON form.\n"; static QJsonDocument convertFromVariant(const QVariant &v) { QJsonDocument doc = QJsonDocument::fromVariant(v); - if (!doc.isObject() && !doc.isArray()) { - fprintf(stderr, "Could not convert contents to JSON.\n"); - exit(EXIT_FAILURE); - } + if (!doc.isObject() && !doc.isArray()) + qFatal("Could not convert contents to JSON."); return doc; } -JsonConverter::JsonConverter() -{ -} - -QString JsonConverter::name() -{ - return "json"; -} - -Converter::Direction JsonConverter::directions() +QString JsonConverter::name() const { - return InOut; + return "json"_L1; } -Converter::Options JsonConverter::outputOptions() +Converter::Directions JsonConverter::directions() const { - return {}; + return Direction::InOut; } -const char *JsonConverter::optionsHelp() +const char *JsonConverter::optionsHelp() const { return jsonOptionHelp; } -bool JsonConverter::probeFile(QIODevice *f) +bool JsonConverter::probeFile(QIODevice *f) const { if (QFile *file = qobject_cast<QFile *>(f)) { - if (file->fileName().endsWith(QLatin1String(".json"))) + if (file->fileName().endsWith(".json"_L1)) return true; } @@ -62,7 +52,7 @@ bool JsonConverter::probeFile(QIODevice *f) return false; } -QVariant JsonConverter::loadFile(QIODevice *f, Converter *&outputConverter) +QVariant JsonConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const { if (!outputConverter) outputConverter = this; @@ -78,27 +68,26 @@ QVariant JsonConverter::loadFile(QIODevice *f, Converter *&outputConverter) if (doc.isNull()) doc = QJsonDocument::fromJson(f->readAll(), &error); if (error.error) { - fprintf(stderr, "Could not parse JSON content: offset %d: %s", - error.offset, qPrintable(error.errorString())); - exit(EXIT_FAILURE); + qFatal("Could not parse JSON content: offset %d: %s", + error.offset, qPrintable(error.errorString())); } - if (outputConverter == null) + if (isNull(outputConverter)) return QVariant(); return doc.toVariant(); } -void JsonConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +void JsonConverter::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const { QJsonDocument::JsonFormat format = QJsonDocument::Indented; for (const QString &s : options) { - if (s == QLatin1String("compact=no")) { + if (s == "compact=no"_L1) { format = QJsonDocument::Indented; - } else if (s == QLatin1String("compact=yes")) { + } else if (s == "compact=yes"_L1) { format = QJsonDocument::Compact; } else { - fprintf(stderr, "Unknown option '%s' to JSON output. Valid options are:\n%s", - qPrintable(s), jsonOptionHelp); - exit(EXIT_FAILURE); + qFatal("Unknown option '%s' to JSON output. Valid options are:\n%s", + qPrintable(s), jsonOptionHelp); } } diff --git a/examples/corelib/serialization/convert/jsonconverter.h b/examples/corelib/serialization/convert/jsonconverter.h index 40430a6b70..e1dd1ecdb4 100644 --- a/examples/corelib/serialization/convert/jsonconverter.h +++ b/examples/corelib/serialization/convert/jsonconverter.h @@ -8,18 +8,15 @@ class JsonConverter : public Converter { -public: - JsonConverter(); - // Converter interface public: - QString name() override; - Direction directions() override; - Options outputOptions() override; - const char *optionsHelp() override; - bool probeFile(QIODevice *f) override; - QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; - void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; + QString name() const override; + Directions directions() const override; + const char *optionsHelp() const override; + bool probeFile(QIODevice *f) const override; + QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; #endif // JSONCONVERTER_H diff --git a/examples/corelib/serialization/convert/main.cpp b/examples/corelib/serialization/convert/main.cpp index 00c626e1c8..d3021fadca 100644 --- a/examples/corelib/serialization/convert/main.cpp +++ b/examples/corelib/serialization/convert/main.cpp @@ -1,4 +1,5 @@ // Copyright (C) 2018 Intel Corporation. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "converter.h" @@ -11,177 +12,144 @@ #include <stdio.h> -static QList<Converter *> *availableConverters; +using namespace Qt::StringLiterals; -Converter::Converter() +static const Converter *prepareConverter(QString format, Converter::Direction direction, + QFile *stream) { - if (!availableConverters) - availableConverters = new QList<Converter *>; - availableConverters->append(this); -} + const bool out = direction == Converter::Direction::Out; + const QIODevice::OpenMode mode = out + ? QIODevice::WriteOnly | QIODevice::Truncate + : QIODevice::ReadOnly; + const char *dirn = out ? "output" : "input"; + + if (stream->fileName().isEmpty()) + stream->open(out ? stdout : stdin, mode); + else + stream->open(mode); + + if (!stream->isOpen()) { + qFatal("Could not open \"%s\" for %s: %s", + qPrintable(stream->fileName()), dirn, qPrintable(stream->errorString())); + } else if (format == "auto"_L1) { + for (const Converter *conv : Converter::allConverters()) { + if (conv->directions().testFlag(direction) && conv->probeFile(stream)) + return conv; + } + if (out) // Failure to identify output format can be remedied by loadFile(). + return nullptr; -Converter::~Converter() -{ - availableConverters->removeAll(this); + // Input format, however, we must know before we can call that: + qFatal("Could not determine input format. Specify it with the -I option."); + } else { + for (const Converter *conv : Converter::allConverters()) { + if (conv->name() == format) { + if (!conv->directions().testFlag(direction)) { + qWarning("File format \"%s\" cannot be used for %s", + qPrintable(format), dirn); + continue; // on the off chance there's another with the same name + } + return conv; + } + } + qFatal("Unknown %s file format \"%s\"", dirn, qPrintable(format)); + } + Q_UNREACHABLE_RETURN(nullptr); } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); +//! [0] QStringList inputFormats; QStringList outputFormats; - for (Converter *conv : std::as_const(*availableConverters)) { + for (const Converter *conv : Converter::allConverters()) { auto direction = conv->directions(); QString name = conv->name(); - if (direction & Converter::In) + if (direction.testFlag(Converter::Direction::In)) inputFormats << name; - if (direction & Converter::Out) + if (direction.testFlag(Converter::Direction::Out)) outputFormats << name; } +//! [0] inputFormats.sort(); outputFormats.sort(); - inputFormats.prepend("auto"); - outputFormats.prepend("auto"); + inputFormats.prepend("auto"_L1); + outputFormats.prepend("auto"_L1); QCommandLineParser parser; - parser.setApplicationDescription(QStringLiteral("Qt file format conversion tool")); + parser.setApplicationDescription("Qt serialization format conversion tool"_L1); parser.addHelpOption(); - QCommandLineOption inputFormatOption(QStringList{"I", "input-format"}); - inputFormatOption.setDescription(QLatin1String("Select the input format for the input file. Available formats: ") + - inputFormats.join(", ")); - inputFormatOption.setValueName("format"); + QCommandLineOption inputFormatOption(QStringList{ "I"_L1, "input-format"_L1 }); + inputFormatOption.setDescription( + "Select the input format for the input file. Available formats: "_L1 + + inputFormats.join(", "_L1)); + inputFormatOption.setValueName("format"_L1); inputFormatOption.setDefaultValue(inputFormats.constFirst()); parser.addOption(inputFormatOption); - QCommandLineOption outputFormatOption(QStringList{"O", "output-format"}); - outputFormatOption.setDescription(QLatin1String("Select the output format for the output file. Available formats: ") + - outputFormats.join(", ")); - outputFormatOption.setValueName("format"); + QCommandLineOption outputFormatOption(QStringList{ "O"_L1, "output-format"_L1 }); + outputFormatOption.setDescription( + "Select the output format for the output file. Available formats: "_L1 + + outputFormats.join(", "_L1)); + outputFormatOption.setValueName("format"_L1); outputFormatOption.setDefaultValue(outputFormats.constFirst()); parser.addOption(outputFormatOption); - QCommandLineOption optionOption(QStringList{"o", "option"}); - optionOption.setDescription(QStringLiteral("Format-specific options. Use --format-options to find out what options are available.")); - optionOption.setValueName("options..."); + QCommandLineOption optionOption(QStringList{ "o"_L1, "option"_L1 }); + optionOption.setDescription( + "Format-specific options. Use --format-options to find out what options are available."_L1); + optionOption.setValueName("options..."_L1); optionOption.setDefaultValues({}); parser.addOption(optionOption); - QCommandLineOption formatOptionsOption("format-options"); - formatOptionsOption.setDescription(QStringLiteral("Prints the list of valid options for --option for the converter format <format>.")); - formatOptionsOption.setValueName("format"); + QCommandLineOption formatOptionsOption("format-options"_L1); + formatOptionsOption.setDescription( + "Prints the list of valid options for --option for the converter format <format>."_L1); + formatOptionsOption.setValueName("format"_L1); parser.addOption(formatOptionsOption); - parser.addPositionalArgument(QStringLiteral("[source]"), - QStringLiteral("File to read from (stdin if none)")); - parser.addPositionalArgument(QStringLiteral("[destination]"), - QStringLiteral("File to write to (stdout if none)")); + parser.addPositionalArgument("[source]"_L1, "File to read from (stdin if none)"_L1); + parser.addPositionalArgument("[destination]"_L1, "File to write to (stdout if none)"_L1); parser.process(app); if (parser.isSet(formatOptionsOption)) { QString format = parser.value(formatOptionsOption); - for (Converter *conv : std::as_const(*availableConverters)) { +//! [1] + for (const Converter *conv : Converter::allConverters()) { if (conv->name() == format) { const char *help = conv->optionsHelp(); - if (help) - printf("The following options are available for format '%s':\n\n%s", qPrintable(format), help); - else - printf("Format '%s' supports no options.\n", qPrintable(format)); + if (help) { + qInfo("The following options are available for format '%s':\n\n%s", + qPrintable(format), help); + } else { + qInfo("Format '%s' supports no options.", qPrintable(format)); + } return EXIT_SUCCESS; } } +//! [1] - fprintf(stderr, "Unknown file format '%s'\n", qPrintable(format)); - return EXIT_FAILURE; - } - - Converter *inconv = nullptr; - QString format = parser.value(inputFormatOption); - if (format != "auto") { - for (Converter *conv : std::as_const(*availableConverters)) { - if (conv->name() == format) { - inconv = conv; - break; - } - } - - if (!inconv) { - fprintf(stderr, "Unknown file format \"%s\"\n", qPrintable(format)); - return EXIT_FAILURE; - } - } - - Converter *outconv = nullptr; - format = parser.value(outputFormatOption); - if (format != "auto") { - for (Converter *conv : std::as_const(*availableConverters)) { - if (conv->name() == format) { - outconv = conv; - break; - } - } - - if (!outconv) { - fprintf(stderr, "Unknown file format \"%s\"\n", qPrintable(format)); - return EXIT_FAILURE; - } + qFatal("Unknown file format '%s'", qPrintable(format)); } +//! [2] QStringList files = parser.positionalArguments(); QFile input(files.value(0)); QFile output(files.value(1)); + const Converter *inconv = prepareConverter(parser.value(inputFormatOption), + Converter::Direction::In, &input); + const Converter *outconv = prepareConverter(parser.value(outputFormatOption), + Converter::Direction::Out, &output); - if (input.fileName().isEmpty()) - input.open(stdin, QIODevice::ReadOnly); - else - input.open(QIODevice::ReadOnly); - if (!input.isOpen()) { - fprintf(stderr, "Could not open \"%s\" for reading: %s\n", - qPrintable(input.fileName()), qPrintable(input.errorString())); - return EXIT_FAILURE; - } - - if (output.fileName().isEmpty()) - output.open(stdout, QIODevice::WriteOnly | QIODevice::Truncate); - else - output.open(QIODevice::WriteOnly | QIODevice::Truncate); - if (!output.isOpen()) { - fprintf(stderr, "Could not open \"%s\" for writing: %s\n", - qPrintable(output.fileName()), qPrintable(output.errorString())); - return EXIT_FAILURE; - } - - if (!inconv) { - // probe the input to find a file format - for (Converter *conv : std::as_const(*availableConverters)) { - if (conv->directions() & Converter::In && conv->probeFile(&input)) { - inconv = conv; - break; - } - } - - if (!inconv) { - fprintf(stderr, "Could not determine input format. pass -I option.\n"); - return EXIT_FAILURE; - } - } - - if (!outconv) { - // probe the output to find a file format - for (Converter *conv : std::as_const(*availableConverters)) { - if (conv->directions() & Converter::Out && conv->probeFile(&output)) { - outconv = conv; - break; - } - } - } - - // now finally perform the conversion + // Now finally perform the conversion: QVariant data = inconv->loadFile(&input, outconv); - Q_ASSERT_X(outconv, "Converter Tool", + Q_ASSERT_X(outconv, "Serialization Converter", "Internal error: converter format did not provide default"); outconv->saveFile(&output, data, parser.values(optionOption)); return EXIT_SUCCESS; +//! [2] } diff --git a/examples/corelib/serialization/convert/nullconverter.cpp b/examples/corelib/serialization/convert/nullconverter.cpp index a3f0bcd99b..fb8be5c944 100644 --- a/examples/corelib/serialization/convert/nullconverter.cpp +++ b/examples/corelib/serialization/convert/nullconverter.cpp @@ -3,48 +3,35 @@ #include "nullconverter.h" -static NullConverter nullConverter; -Converter* Converter::null = &nullConverter; - -QString NullConverter::name() -{ - return QLatin1String("null"); -} - -Converter::Direction NullConverter::directions() -{ - return Out; -} +using namespace Qt::StringLiterals; -Converter::Options NullConverter::outputOptions() +static NullConverter nullConverter; +bool Converter::isNull(const Converter *converter) { - return SupportsArbitraryMapKeys; + return converter == &nullConverter; } -const char *NullConverter::optionsHelp() +QString NullConverter::name() const { - return nullptr; + return "null"_L1; } -bool NullConverter::probeFile(QIODevice *f) +Converter::Directions NullConverter::directions() const { - Q_UNUSED(f); - return false; + return Direction::Out; } -QVariant NullConverter::loadFile(QIODevice *f, Converter *&outputConverter) +Converter::Options NullConverter::outputOptions() const { - Q_UNUSED(f); - Q_UNUSED(outputConverter); - outputConverter = this; - return QVariant(); + return SupportsArbitraryMapKeys; } -void NullConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +void NullConverter::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const { if (!options.isEmpty()) { - fprintf(stderr, "Unknown option '%s' to null output. This format has no options.\n", qPrintable(options.first())); - exit(EXIT_FAILURE); + qFatal("Unknown option '%s' to null output. This format has no options.", + qPrintable(options.first())); } Q_UNUSED(f); diff --git a/examples/corelib/serialization/convert/nullconverter.h b/examples/corelib/serialization/convert/nullconverter.h index b2c69593f5..1bdf9f22f7 100644 --- a/examples/corelib/serialization/convert/nullconverter.h +++ b/examples/corelib/serialization/convert/nullconverter.h @@ -10,13 +10,11 @@ class NullConverter : public Converter { // Converter interface public: - QString name() override; - Direction directions() override; - Options outputOptions() override; - const char *optionsHelp() override; - bool probeFile(QIODevice *f) override; - QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; - void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; + QString name() const override; + Directions directions() const override; + Options outputOptions() const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; #endif // NULLCONVERTER_H diff --git a/examples/corelib/serialization/convert/textconverter.cpp b/examples/corelib/serialization/convert/textconverter.cpp index b02ce12f66..1f8b4f9c19 100644 --- a/examples/corelib/serialization/convert/textconverter.cpp +++ b/examples/corelib/serialization/convert/textconverter.cpp @@ -6,6 +6,8 @@ #include <QFile> #include <QTextStream> +using namespace Qt::StringLiterals; + static void dumpVariant(QTextStream &out, const QVariant &v) { switch (v.userType()) { @@ -42,68 +44,52 @@ static void dumpVariant(QTextStream &out, const QVariant &v) } } -QString TextConverter::name() -{ - return QStringLiteral("text"); -} - -Converter::Direction TextConverter::directions() -{ - return InOut; -} - -Converter::Options TextConverter::outputOptions() +QString TextConverter::name() const { - return {}; + return "text"_L1; } -const char *TextConverter::optionsHelp() +Converter::Directions TextConverter::directions() const { - return nullptr; + return Direction::InOut; } -bool TextConverter::probeFile(QIODevice *f) +bool TextConverter::probeFile(QIODevice *f) const { if (QFile *file = qobject_cast<QFile *>(f)) - return file->fileName().endsWith(QLatin1String(".txt")); + return file->fileName().endsWith(".txt"_L1); return false; } -QVariant TextConverter::loadFile(QIODevice *f, Converter *&outputConverter) +QVariant TextConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const { if (!outputConverter) outputConverter = this; QVariantList list; QTextStream in(f); - QString line ; + QString line; while (!in.atEnd()) { in.readLineInto(&line); - bool ok; - qint64 v = line.toLongLong(&ok); - if (ok) { - list.append(v); - continue; - } - double d = line.toDouble(&ok); - if (ok) { + if (qint64 v = line.toLongLong(&ok); ok) + list.append(v); + else if (double d = line.toDouble(&ok); ok) list.append(d); - continue; - } - - list.append(line); + else + list.append(line); } return list; } -void TextConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +void TextConverter::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const { if (!options.isEmpty()) { - fprintf(stderr, "Unknown option '%s' to text output. This format has no options.\n", qPrintable(options.first())); - exit(EXIT_FAILURE); + qFatal("Unknown option '%s' to text output. This format has no options.", + qPrintable(options.first())); } QTextStream out(f); diff --git a/examples/corelib/serialization/convert/textconverter.h b/examples/corelib/serialization/convert/textconverter.h index 6379ffc82f..526f295517 100644 --- a/examples/corelib/serialization/convert/textconverter.h +++ b/examples/corelib/serialization/convert/textconverter.h @@ -8,16 +8,14 @@ class TextConverter : public Converter { - // Converter interface public: - QString name() override; - Direction directions() override; - Options outputOptions() override; - const char *optionsHelp() override; - bool probeFile(QIODevice *f) override; - QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; - void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; + QString name() const override; + Directions directions() const override; + bool probeFile(QIODevice *f) const override; + QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; #endif // TEXTCONVERTER_H diff --git a/examples/corelib/serialization/convert/variantorderedmap.h b/examples/corelib/serialization/convert/variantorderedmap.h new file mode 100644 index 0000000000..c65316b182 --- /dev/null +++ b/examples/corelib/serialization/convert/variantorderedmap.h @@ -0,0 +1,24 @@ +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef VARIANTORDEREDMAP_H +#define VARIANTORDEREDMAP_H + +#include <QList> +#include <QPair> +#include <QVariant> +#include <QVariantMap> + +class VariantOrderedMap : public QList<QPair<QVariant, QVariant>> +{ +public: + VariantOrderedMap() = default; + VariantOrderedMap(const QVariantMap &map) + { + reserve(map.size()); + for (auto it = map.begin(); it != map.end(); ++it) + append({it.key(), it.value()}); + } +}; + +#endif // VARIANTORDEREDMAP_H diff --git a/examples/corelib/serialization/convert/xmlconverter.cpp b/examples/corelib/serialization/convert/xmlconverter.cpp index 080528f678..ef71fecb9f 100644 --- a/examples/corelib/serialization/convert/xmlconverter.cpp +++ b/examples/corelib/serialization/convert/xmlconverter.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "xmlconverter.h" +#include "variantorderedmap.h" #include <QBitArray> #include <QtCborCommon> @@ -13,8 +14,9 @@ #include <QXmlStreamReader> #include <QXmlStreamWriter> -static const char xmlOptionHelp[] = - "compact=no|yes Use compact XML form.\n"; +using namespace Qt::StringLiterals; + +static const char xmlOptionHelp[] = "compact=no|yes Use compact XML form.\n"; static XmlConverter xmlConverter; @@ -23,7 +25,7 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options static QVariantList listFromXml(QXmlStreamReader &xml, Converter::Options options) { QVariantList list; - while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == QLatin1String("list"))) { + while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == "list"_L1)) { xml.readNext(); switch (xml.tokenType()) { case QXmlStreamReader::StartElement: @@ -47,20 +49,19 @@ static QVariantList listFromXml(QXmlStreamReader &xml, Converter::Options option break; } - fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", - xml.lineNumber(), xml.columnNumber(), - qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(), + qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); } xml.readNext(); return list; } -static VariantOrderedMap::value_type mapEntryFromXml(QXmlStreamReader &xml, Converter::Options options) +static VariantOrderedMap::value_type mapEntryFromXml(QXmlStreamReader &xml, + Converter::Options options) { QVariant key, value; - while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == QLatin1String("entry"))) { + while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == "entry"_L1)) { xml.readNext(); switch (xml.tokenType()) { case QXmlStreamReader::StartElement: @@ -89,10 +90,8 @@ static VariantOrderedMap::value_type mapEntryFromXml(QXmlStreamReader &xml, Conv break; } - fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", - xml.lineNumber(), xml.columnNumber(), - qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(), + qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); } return { key, value }; @@ -103,11 +102,11 @@ static QVariant mapFromXml(QXmlStreamReader &xml, Converter::Options options) QVariantMap map1; VariantOrderedMap map2; - while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == QLatin1String("map"))) { + while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == "map"_L1)) { xml.readNext(); switch (xml.tokenType()) { case QXmlStreamReader::StartElement: - if (xml.name() == QLatin1String("entry")) { + if (xml.name() == "entry"_L1) { auto pair = mapEntryFromXml(xml, options); if (options & Converter::SupportsArbitraryMapKeys) map2.append(pair); @@ -134,10 +133,8 @@ static QVariant mapFromXml(QXmlStreamReader &xml, Converter::Options options) break; } - fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", - xml.lineNumber(), xml.columnNumber(), - qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(), + qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); } xml.readNext(); @@ -149,18 +146,17 @@ static QVariant mapFromXml(QXmlStreamReader &xml, Converter::Options options) static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options) { QStringView name = xml.name(); - if (name == QLatin1String("list")) + if (name == "list"_L1) return listFromXml(xml, options); - if (name == QLatin1String("map")) + if (name == "map"_L1) return mapFromXml(xml, options); - if (name != QLatin1String("value")) { - fprintf(stderr, "%lld:%lld: Invalid XML key '%s'.\n", - xml.lineNumber(), xml.columnNumber(), qPrintable(name.toString())); - exit(EXIT_FAILURE); + if (name != "value"_L1) { + qFatal("%lld:%lld: Invalid XML key '%s'.", + xml.lineNumber(), xml.columnNumber(), qPrintable(name.toString())); } QXmlStreamAttributes attrs = xml.attributes(); - QStringView type = attrs.value(QLatin1String("type")); + QStringView type = attrs.value("type"_L1); forever { xml.readNext(); @@ -169,10 +165,8 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options if (xml.isCDATA() || xml.isCharacters() || xml.isEndElement()) break; - fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", - xml.lineNumber(), xml.columnNumber(), - qPrintable(xml.tokenString()), qPrintable(name.toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(), + qPrintable(xml.tokenString()), qPrintable(name.toString())); } QStringView text = xml.text(); @@ -180,45 +174,43 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options text = text.trimmed(); QVariant result; - bool ok; if (type.isEmpty()) { // ok - } else if (type == QLatin1String("number")) { + } else if (type == "number"_L1) { // try integer first + bool ok; qint64 v = text.toLongLong(&ok); if (ok) { result = v; } else { // let's see floating point double d = text.toDouble(&ok); - result = d; if (!ok) { - fprintf(stderr, "%lld:%lld: Invalid XML: could not interpret '%s' as a number.\n", - xml.lineNumber(), xml.columnNumber(), qPrintable(text.toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML: could not interpret '%s' as a number.", + xml.lineNumber(), xml.columnNumber(), qPrintable(text.toString())); } + result = d; } - } else if (type == QLatin1String("bytes")) { + } else if (type == "bytes"_L1) { QByteArray data = text.toLatin1(); QStringView encoding = attrs.value("encoding"); - if (encoding == QLatin1String("base64url")) { + if (encoding == "base64url"_L1) { result = QByteArray::fromBase64(data, QByteArray::Base64UrlEncoding); - } else if (encoding == QLatin1String("hex")) { + } else if (encoding == "hex"_L1) { result = QByteArray::fromHex(data); - } else if (encoding.isEmpty() || encoding == QLatin1String("base64")) { + } else if (encoding.isEmpty() || encoding == "base64"_L1) { result = QByteArray::fromBase64(data); } else { - fprintf(stderr, "%lld:%lld: Invalid XML: unknown encoding '%s' for bytes.\n", - xml.lineNumber(), xml.columnNumber(), qPrintable(encoding.toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML: unknown encoding '%s' for bytes.", + xml.lineNumber(), xml.columnNumber(), qPrintable(encoding.toString())); } - } else if (type == QLatin1String("string")) { + } else if (type == "string"_L1) { result = text.toString(); - } else if (type == QLatin1String("null")) { + } else if (type == "null"_L1) { result = QVariant::fromValue(nullptr); - } else if (type == QLatin1String("CBOR simple type")) { + } else if (type == "CBOR simple type"_L1) { result = QVariant::fromValue(QCborSimpleType(text.toShort())); - } else if (type == QLatin1String("bits")) { + } else if (type == "bits"_L1) { QBitArray ba; ba.resize(text.size()); qsizetype n = 0; @@ -229,36 +221,33 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options } else if (c == '0') { ++n; } else if (!c.isSpace()) { - fprintf(stderr, "%lld:%lld: Invalid XML: invalid bit string '%s'.\n", - xml.lineNumber(), xml.columnNumber(), qPrintable(text.toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML: invalid bit string '%s'.", + xml.lineNumber(), xml.columnNumber(), qPrintable(text.toString())); } } ba.resize(n); result = ba; } else { int id = QMetaType::UnknownType; - if (type == QLatin1String("datetime")) + if (type == "datetime"_L1) id = QMetaType::QDateTime; - else if (type == QLatin1String("url")) + else if (type == "url"_L1) id = QMetaType::QUrl; - else if (type == QLatin1String("uuid")) + else if (type == "uuid"_L1) id = QMetaType::QUuid; - else if (type == QLatin1String("regex")) + else if (type == "regex"_L1) id = QMetaType::QRegularExpression; else id = QMetaType::fromName(type.toLatin1()).id(); if (id == QMetaType::UnknownType) { - fprintf(stderr, "%lld:%lld: Invalid XML: unknown type '%s'.\n", - xml.lineNumber(), xml.columnNumber(), qPrintable(type.toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML: unknown type '%s'.", + xml.lineNumber(), xml.columnNumber(), qPrintable(type.toString())); } result = text.toString(); if (!result.convert(QMetaType(id))) { - fprintf(stderr, "%lld:%lld: Invalid XML: could not parse content as type '%s'.\n", - xml.lineNumber(), xml.columnNumber(), qPrintable(type.toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML: could not parse content as type '%s'.", + xml.lineNumber(), xml.columnNumber(), qPrintable(type.toString())); } } @@ -267,10 +256,8 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options } while (xml.isComment() || xml.isWhitespace()); if (!xml.isEndElement()) { - fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", - xml.lineNumber(), xml.columnNumber(), - qPrintable(xml.tokenString()), qPrintable(name.toString())); - exit(EXIT_FAILURE); + qFatal("%lld:%lld: Invalid XML %s '%s'.", xml.lineNumber(), xml.columnNumber(), + qPrintable(xml.tokenString()), qPrintable(name.toString())); } xml.readNext(); @@ -287,9 +274,9 @@ static void variantToXml(QXmlStreamWriter &xml, const QVariant &v) variantToXml(xml, v); xml.writeEndElement(); } else if (type == QMetaType::QVariantMap || type == qMetaTypeId<VariantOrderedMap>()) { - const VariantOrderedMap map = (type == QMetaType::QVariantMap) ? - VariantOrderedMap(v.toMap()) : - qvariant_cast<VariantOrderedMap>(v); + const VariantOrderedMap map = (type == QMetaType::QVariantMap) + ? VariantOrderedMap(v.toMap()) + : qvariant_cast<VariantOrderedMap>(v); xml.writeStartElement("map"); for (const auto &pair : map) { @@ -301,7 +288,7 @@ static void variantToXml(QXmlStreamWriter &xml, const QVariant &v) xml.writeEndElement(); } else { xml.writeStartElement("value"); - QString typeString = QStringLiteral("type"); + QString typeString = "type"_L1; switch (type) { case QMetaType::Short: case QMetaType::UShort: @@ -390,8 +377,7 @@ static void variantToXml(QXmlStreamWriter &xml, const QVariant &v) xml.writeAttribute(typeString, QString::fromLatin1(typeName)); xml.writeCharacters(copy.toString()); } else { - fprintf(stderr, "XML: don't know how to serialize type '%s'.\n", typeName); - exit(EXIT_FAILURE); + qFatal("XML: don't know how to serialize type '%s'.", typeName); } } } @@ -399,37 +385,37 @@ static void variantToXml(QXmlStreamWriter &xml, const QVariant &v) } } -QString XmlConverter::name() +QString XmlConverter::name() const { - return QStringLiteral("xml"); + return "xml"_L1; } -Converter::Direction XmlConverter::directions() +Converter::Directions XmlConverter::directions() const { - return InOut; + return Direction::InOut; } -Converter::Options XmlConverter::outputOptions() +Converter::Options XmlConverter::outputOptions() const { return SupportsArbitraryMapKeys; } -const char *XmlConverter::optionsHelp() +const char *XmlConverter::optionsHelp() const { return xmlOptionHelp; } -bool XmlConverter::probeFile(QIODevice *f) +bool XmlConverter::probeFile(QIODevice *f) const { if (QFile *file = qobject_cast<QFile *>(f)) { - if (file->fileName().endsWith(QLatin1String(".xml"))) + if (file->fileName().endsWith(".xml"_L1)) return true; } return f->isReadable() && f->peek(5) == "<?xml"; } -QVariant XmlConverter::loadFile(QIODevice *f, Converter *&outputConverter) +QVariant XmlConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const { if (!outputConverter) outputConverter = this; @@ -437,26 +423,24 @@ QVariant XmlConverter::loadFile(QIODevice *f, Converter *&outputConverter) QXmlStreamReader xml(f); xml.readNextStartElement(); QVariant v = variantFromXml(xml, outputConverter->outputOptions()); - if (xml.hasError()) { - fprintf(stderr, "XML error: %s", qPrintable(xml.errorString())); - exit(EXIT_FAILURE); - } + if (xml.hasError()) + qFatal("XML error: %s", qPrintable(xml.errorString())); return v; } -void XmlConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +void XmlConverter::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const { bool compact = false; for (const QString &s : options) { - if (s == QLatin1String("compact=no")) { + if (s == "compact=no"_L1) { compact = false; - } else if (s == QLatin1String("compact=yes")) { + } else if (s == "compact=yes"_L1) { compact = true; } else { - fprintf(stderr, "Unknown option '%s' to XML output. Valid options are:\n%s", - qPrintable(s), xmlOptionHelp); - exit(EXIT_FAILURE); + qFatal("Unknown option '%s' to XML output. Valid options are:\n%s", + qPrintable(s), xmlOptionHelp); } } diff --git a/examples/corelib/serialization/convert/xmlconverter.h b/examples/corelib/serialization/convert/xmlconverter.h index 19bde6c7c6..4888b0616f 100644 --- a/examples/corelib/serialization/convert/xmlconverter.h +++ b/examples/corelib/serialization/convert/xmlconverter.h @@ -10,13 +10,14 @@ class XmlConverter : public Converter { // Converter interface public: - QString name() override; - Direction directions() override; - Options outputOptions() override; - const char *optionsHelp() override; - bool probeFile(QIODevice *f) override; - QVariant loadFile(QIODevice *f, Converter *&outputConverter) override; - void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override; + QString name() const override; + Directions directions() const override; + Options outputOptions() const override; + const char *optionsHelp() const override; + bool probeFile(QIODevice *f) const override; + QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override; + void saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const override; }; #endif // XMLCONVERTER_H diff --git a/examples/corelib/serialization/rsslisting/CMakeLists.txt b/examples/corelib/serialization/rsslisting/CMakeLists.txt deleted file mode 100644 index 405a01ce56..0000000000 --- a/examples/corelib/serialization/rsslisting/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -cmake_minimum_required(VERSION 3.16) -project(rsslisting LANGUAGES CXX) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/rsslisting") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets) - -qt_standard_project_setup() - -qt_add_executable(rsslisting - main.cpp - rsslisting.cpp rsslisting.h -) - -set_target_properties(rsslisting PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(rsslisting PRIVATE - Qt6::Core - Qt6::Gui - Qt6::Network - Qt6::Widgets -) - -install(TARGETS rsslisting - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/corelib/serialization/rsslisting/main.cpp b/examples/corelib/serialization/rsslisting/main.cpp deleted file mode 100644 index 9337e5f352..0000000000 --- a/examples/corelib/serialization/rsslisting/main.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -/* -main.cpp - -Provides the main function for the RSS news reader example. -*/ - -#include <QtWidgets> - -#include "rsslisting.h" - -/*! - Create an application and a main widget. Open the main widget for - user input, and exit with an appropriate return value when it is - closed. -*/ - -int main(int argc, char **argv) -{ - QApplication app(argc, argv); - RSSListing *rsslisting = new RSSListing; - rsslisting->show(); - return app.exec(); -} diff --git a/examples/corelib/serialization/rsslisting/rsslisting.cpp b/examples/corelib/serialization/rsslisting/rsslisting.cpp deleted file mode 100644 index 9269be4080..0000000000 --- a/examples/corelib/serialization/rsslisting/rsslisting.cpp +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -/* -rsslisting.cpp - -Provides a widget for displaying news items from RDF news sources. -RDF is an XML-based format for storing items of information (see -http://www.w3.org/RDF/ for details). - -The widget itself provides a simple user interface for specifying -the URL of a news source, and controlling the downloading of news. - -The widget downloads and parses the XML asynchronously, feeding the -data to an XML reader in pieces. This allows the user to interrupt -its operation, and also allows very large data sources to be read. -*/ - - -#include <QtCore> -#include <QtWidgets> -#include <QtNetwork> - -#include "rsslisting.h" - - -/* - Constructs an RSSListing widget with a simple user interface, and sets - up the XML reader to use a custom handler class. - - The user interface consists of a line edit, a push button, and a - list view widget. The line edit is used for entering the URLs of news - sources; the push button starts the process of reading the - news. -*/ - -RSSListing::RSSListing(QWidget *parent) - : QWidget(parent), currentReply(0) -{ - - lineEdit = new QLineEdit(this); - lineEdit->setText("http://blog.qt.io/feed/"); - - fetchButton = new QPushButton(tr("Fetch"), this); - - treeWidget = new QTreeWidget(this); - connect(treeWidget, &QTreeWidget::itemActivated, - this, &RSSListing::itemActivated); - QStringList headerLabels; - headerLabels << tr("Title") << tr("Link"); - treeWidget->setHeaderLabels(headerLabels); - treeWidget->header()->setSectionResizeMode(QHeaderView::ResizeToContents); - - connect(&manager, &QNetworkAccessManager::finished, - this, &RSSListing::finished); - - connect(lineEdit, &QLineEdit::returnPressed, this, &RSSListing::fetch); - connect(fetchButton, &QPushButton::clicked, this, &RSSListing::fetch); - - QVBoxLayout *layout = new QVBoxLayout(this); - - QHBoxLayout *hboxLayout = new QHBoxLayout; - - hboxLayout->addWidget(lineEdit); - hboxLayout->addWidget(fetchButton); - - layout->addLayout(hboxLayout); - layout->addWidget(treeWidget); - - setWindowTitle(tr("RSS listing example")); - resize(640,480); -} - -/* - Starts the network request and connects the needed signals -*/ -void RSSListing::get(const QUrl &url) -{ - QNetworkRequest request(url); - if (currentReply) { - currentReply->disconnect(this); - currentReply->deleteLater(); - } - currentReply = manager.get(request); - connect(currentReply, &QNetworkReply::readyRead, this, &RSSListing::readyRead); - connect(currentReply, &QNetworkReply::metaDataChanged, this, &RSSListing::metaDataChanged); - connect(currentReply, &QNetworkReply::errorOccurred, this, &RSSListing::error); -} - -/* - Starts fetching data from a news source specified in the line - edit widget. - - The line edit is made read only to prevent the user from modifying its - contents during the fetch; this is only for cosmetic purposes. - The fetch button is disabled, the list view is cleared, and we - define the last list view item to be 0, meaning that there are no - existing items in the list. - - A URL is created with the raw contents of the line edit and - a get is initiated. -*/ - -void RSSListing::fetch() -{ - lineEdit->setReadOnly(true); - fetchButton->setEnabled(false); - treeWidget->clear(); - - xml.clear(); - - QUrl url(lineEdit->text()); - get(url); -} - -void RSSListing::metaDataChanged() -{ - QUrl redirectionTarget = currentReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); - if (redirectionTarget.isValid()) { - get(redirectionTarget); - } -} - -/* - Reads data received from the RDF source. - - We read all the available data, and pass it to the XML - stream reader. Then we call the XML parsing function. -*/ - -void RSSListing::readyRead() -{ - int statusCode = currentReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (statusCode >= 200 && statusCode < 300) { - QByteArray data = currentReply->readAll(); - xml.addData(data); - parseXml(); - } -} - -/* - Finishes processing an HTTP request. - - The default behavior is to keep the text edit read only. - - If an error has occurred, the user interface is made available - to the user for further input, allowing a new fetch to be - started. - - If the HTTP get request has finished, we make the - user interface available to the user for further input. -*/ - -void RSSListing::finished(QNetworkReply *reply) -{ - Q_UNUSED(reply); - lineEdit->setReadOnly(false); - fetchButton->setEnabled(true); -} - - -/* - Parses the XML data and creates treeWidget items accordingly. -*/ -void RSSListing::parseXml() -{ - while (!xml.atEnd()) { - xml.readNext(); - if (xml.isStartElement()) { - if (xml.name() == u"item") - linkString = xml.attributes().value("rss:about").toString(); - currentTag = xml.name().toString(); - } else if (xml.isEndElement()) { - if (xml.name() == u"item") { - - QTreeWidgetItem *item = new QTreeWidgetItem; - item->setText(0, titleString); - item->setText(1, linkString); - treeWidget->addTopLevelItem(item); - - titleString.clear(); - linkString.clear(); - } - - } else if (xml.isCharacters() && !xml.isWhitespace()) { - if (currentTag == "title") - titleString += xml.text(); - else if (currentTag == "link") - linkString += xml.text(); - } - } - if (xml.error() && xml.error() != QXmlStreamReader::PrematureEndOfDocumentError) { - qWarning() << "XML ERROR:" << xml.lineNumber() << ": " << xml.errorString(); - } -} - -/* - Open the link in the browser -*/ -void RSSListing::itemActivated(QTreeWidgetItem * item) -{ - QDesktopServices::openUrl(QUrl(item->text(1))); -} - -void RSSListing::error(QNetworkReply::NetworkError) -{ - qWarning("error retrieving RSS feed"); - currentReply->disconnect(this); - currentReply->deleteLater(); - currentReply = 0; -} diff --git a/examples/corelib/serialization/rsslisting/rsslisting.h b/examples/corelib/serialization/rsslisting/rsslisting.h deleted file mode 100644 index 81c655f677..0000000000 --- a/examples/corelib/serialization/rsslisting/rsslisting.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef RSSLISTING_H -#define RSSLISTING_H - -#include <QNetworkAccessManager> -#include <QNetworkReply> -#include <QWidget> -#include <QBuffer> -#include <QXmlStreamReader> -#include <QUrl> - - -QT_BEGIN_NAMESPACE -class QLineEdit; -class QTreeWidget; -class QTreeWidgetItem; -class QPushButton; -QT_END_NAMESPACE - -class RSSListing : public QWidget -{ - Q_OBJECT -public: - RSSListing(QWidget *widget = nullptr); - -public slots: - void fetch(); - void finished(QNetworkReply *reply); - void readyRead(); - void metaDataChanged(); - void itemActivated(QTreeWidgetItem * item); - void error(QNetworkReply::NetworkError); - -private: - void parseXml(); - void get(const QUrl &url); - - QXmlStreamReader xml; - QString currentTag; - QString linkString; - QString titleString; - - QNetworkAccessManager manager; - QNetworkReply *currentReply; - - QLineEdit *lineEdit; - QTreeWidget *treeWidget; - QPushButton *fetchButton; - -}; - -#endif - diff --git a/examples/corelib/serialization/rsslisting/rsslisting.pro b/examples/corelib/serialization/rsslisting/rsslisting.pro deleted file mode 100644 index 7619755b8f..0000000000 --- a/examples/corelib/serialization/rsslisting/rsslisting.pro +++ /dev/null @@ -1,8 +0,0 @@ -HEADERS += rsslisting.h -SOURCES += main.cpp rsslisting.cpp -QT += network widgets -requires(qtConfig(treewidget)) - -# install -target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/rsslisting -INSTALLS += target diff --git a/examples/corelib/serialization/savegame/CMakeLists.txt b/examples/corelib/serialization/savegame/CMakeLists.txt index 8871a9d687..14621ccc23 100644 --- a/examples/corelib/serialization/savegame/CMakeLists.txt +++ b/examples/corelib/serialization/savegame/CMakeLists.txt @@ -1,15 +1,13 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(savegame LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") +if (ANDROID) + message(FATAL_ERROR "This project cannot be built on Android.") endif() -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/savegame") - find_package(Qt6 REQUIRED COMPONENTS Core) qt_standard_project_setup() @@ -26,7 +24,14 @@ target_link_libraries(savegame PRIVATE ) install(TARGETS savegame - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET savegame + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/serialization/savegame/character.cpp b/examples/corelib/serialization/savegame/character.cpp index 7be737f308..863fcb9153 100644 --- a/examples/corelib/serialization/savegame/character.cpp +++ b/examples/corelib/serialization/savegame/character.cpp @@ -6,15 +6,10 @@ #include <QMetaEnum> #include <QTextStream> -Character::Character() - = default; - -Character::Character(const QString &name, - int level, - Character::ClassType classType) : - mName(name), - mLevel(level), - mClassType(classType) +Character::Character() = default; + +Character::Character(const QString &name, int level, Character::ClassType classType) + : mName(name), mLevel(level), mClassType(classType) { } @@ -48,35 +43,41 @@ void Character::setClassType(Character::ClassType classType) mClassType = classType; } -//! [0] -void Character::read(const QJsonObject &json) +//! [fromJson] +Character Character::fromJson(const QJsonObject &json) { - if (json.contains("name") && json["name"].isString()) - mName = json["name"].toString(); + Character result; + + if (const QJsonValue v = json["name"]; v.isString()) + result.mName = v.toString(); + + if (const QJsonValue v = json["level"]; v.isDouble()) + result.mLevel = v.toInt(); - if (json.contains("level") && json["level"].isDouble()) - mLevel = json["level"].toInt(); + if (const QJsonValue v = json["classType"]; v.isDouble()) + result.mClassType = ClassType(v.toInt()); - if (json.contains("classType") && json["classType"].isDouble()) - mClassType = ClassType(json["classType"].toInt()); + return result; } -//! [0] +//! [fromJson] -//! [1] -void Character::write(QJsonObject &json) const +//! [toJson] +QJsonObject Character::toJson() const { + QJsonObject json; json["name"] = mName; json["level"] = mLevel; json["classType"] = mClassType; + return json; } -//! [1] +//! [toJson] -void Character::print(int indentation) const +void Character::print(QTextStream &s, int indentation) const { const QString indent(indentation * 2, ' '); - QTextStream(stdout) << indent << "Name:\t" << mName << "\n"; - QTextStream(stdout) << indent << "Level:\t" << mLevel << "\n"; + const QString className = QMetaEnum::fromType<ClassType>().valueToKey(mClassType); - QString className = QMetaEnum::fromType<ClassType>().valueToKey(mClassType); - QTextStream(stdout) << indent << "Class:\t" << className << "\n"; + s << indent << "Name:\t" << mName << "\n" + << indent << "Level:\t" << mLevel << "\n" + << indent << "Class:\t" << className << "\n"; } diff --git a/examples/corelib/serialization/savegame/character.h b/examples/corelib/serialization/savegame/character.h index 4dc25139a6..0504750320 100644 --- a/examples/corelib/serialization/savegame/character.h +++ b/examples/corelib/serialization/savegame/character.h @@ -8,15 +8,15 @@ #include <QObject> #include <QString> +QT_FORWARD_DECLARE_CLASS(QTextStream) + //! [0] class Character { Q_GADGET public: - enum ClassType { - Warrior, Mage, Archer - }; + enum ClassType { Warrior, Mage, Archer }; Q_ENUM(ClassType) Character(); @@ -31,10 +31,11 @@ public: ClassType classType() const; void setClassType(ClassType classType); - void read(const QJsonObject &json); - void write(QJsonObject &json) const; + static Character fromJson(const QJsonObject &json); + QJsonObject toJson() const; + + void print(QTextStream &s, int indentation = 0) const; - void print(int indentation = 0) const; private: QString mName; int mLevel = 0; diff --git a/examples/corelib/serialization/savegame/doc/src/savegame.qdoc b/examples/corelib/serialization/savegame/doc/src/savegame.qdoc index 5302582fcc..46fca15b62 100644 --- a/examples/corelib/serialization/savegame/doc/src/savegame.qdoc +++ b/examples/corelib/serialization/savegame/doc/src/savegame.qdoc @@ -3,18 +3,19 @@ /*! \example serialization/savegame - \title JSON Save Game Example + \examplecategory {Data Processing & I/O} + \title Saving and Loading a Game - \brief The JSON Save Game example demonstrates how to save and load a - small game using QJsonDocument, QJsonObject and QJsonArray. + \brief How to save and load a game using Qt's JSON or CBOR classes. Many games provide save functionality, so that the player's progress through the game can be saved and loaded at a later time. The process of saving a - game generally involves serializing each game object's member variables - to a file. Many formats can be used for this purpose, one of which is JSON. - With QJsonDocument, you also have the ability to serialize a document in a - \l {RFC 7049} {CBOR} format, which is great if you - don't want the save file to be readable, or if you need to keep the file size down. + game generally involves serializing each game object's member variables to a + file. Many formats can be used for this purpose, one of which is JSON. With + QJsonDocument, you also have the ability to serialize a document in a \l + {RFC 7049} {CBOR} format, which is great if you don't want the save file to + be easy to read (but see \l {Parsing and displaying CBOR data} for how it \e + can be read), or if you need to keep the file size down. In this example, we'll demonstrate how to save and load a simple game to and from JSON and binary formats. @@ -24,45 +25,83 @@ The Character class represents a non-player character (NPC) in our game, and stores the player's name, level, and class type. - It provides read() and write() functions to serialise its member variables. + It provides static fromJson() and non-static toJson() functions to + serialise itself. + + \note This pattern (fromJson()/toJson()) works because QJsonObjects can be + constructed independent of an owning QJsonDocument, and because the data + types being (de)serialized here are value types, so can be copied. When + serializing to another format — for example XML or QDataStream, which require passing + a document-like object — or when the object identity is important (QObject + subclasses, for example), other patterns may be more suitable. See the + \l{dombookmarks} example for XML, and the implementation of + \l QListWidgetItem::read() and \l QListWidgetItem::write() + for idiomatic QDataStream serialization. The \c{print()} functions in this example + are good examples of QTextStream serialization, even though they, of course, lack + the deserialization side. \snippet serialization/savegame/character.h 0 - Of particular interest to us are the read and write function + Of particular interest to us are the fromJson() and toJson() function implementations: - \snippet serialization/savegame/character.cpp 0 + \snippet serialization/savegame/character.cpp fromJson - In the read() function, we assign Character's members values from the - QJsonObject argument. You can use either \l QJsonObject::operator[]() or - QJsonObject::value() to access values within the JSON object; both are - const functions and return QJsonValue::Undefined if the key is invalid. We - check if the keys are valid before attempting to read them with - QJsonObject::contains(). + In the fromJson() function, we construct a local \c result Character object + and assign \c{result}'s members values from the QJsonObject argument. You + can use either \l QJsonObject::operator[]() or QJsonObject::value() to + access values within the JSON object; both are const functions and return + QJsonValue::Undefined if the key is invalid. In particular, the \c{is...} + functions (for example \l QJsonValue::isString(), \l + QJsonValue::isDouble()) return \c false for QJsonValue::Undefined, so we + can check for existence as well as the correct type in a single lookup. - \snippet serialization/savegame/character.cpp 1 + If a value does not exist in the JSON object, or has the wrong type, we + don't write to the corresponding \c result member, either, thereby + preserving any values the default constructor may have set. This means + default values are centrally defined in one location (the default + constructor) and need not be repeated in serialisation code + (\l{https://en.wikipedia.org/wiki/Don%27t_repeat_yourself}{DRY}). - In the write() function, we do the reverse of the read() function; assign - values from the Character object to the JSON object. As with accessing - values, there are two ways to set values on a QJsonObject: - \l QJsonObject::operator[]() and QJsonObject::insert(). Both will override - any existing value at the given key. + Observe the use of + \l{https://en.cppreference.com/w/cpp/language/if#If_statements_with_initializer} + {C++17 if-with-initializer} to separate scoping and checking of the variable \c v. + This means we can keep the variable name short, because its scope is limited. - Next up is the Level class: + Compare that to the naïve approach using \c QJsonObject::contains(): + + \badcode + if (json.contains("name") && json["name"].isString()) + result.mName = json["name"].toString(); + \endcode + + which, beside being less readable, requires a total of three lookups (no, + the compiler will \e not optimize these into one), so is three times + slower and repeats \c{"name"} three times (violating the DRY principle). + + \snippet serialization/savegame/character.cpp toJson + + In the toJson() function, we do the reverse of the fromJson() function; + assign values from the Character object to a new JSON object we then + return. As with accessing values, there are two ways to set values on a + QJsonObject: \l QJsonObject::operator[]() and \l QJsonObject::insert(). + Both will override any existing value at the given key. + + \section1 The Level Class \snippet serialization/savegame/level.h 0 - We want to have several levels in our game, each with several NPCs, so we - keep a QList of Character objects. We also provide the familiar read() and - write() functions. + We want the levels in our game to each each have several NPCs, so we keep a QList + of Character objects. We also provide the familiar fromJson() and toJson() + functions. - \snippet serialization/savegame/level.cpp 0 + \snippet serialization/savegame/level.cpp fromJson - Containers can be written and read to and from JSON using QJsonArray. In our + Containers can be written to and read from JSON using QJsonArray. In our case, we construct a QJsonArray from the value associated with the key \c "npcs". Then, for each QJsonValue element in the array, we call - toObject() to get the Character's JSON object. The Character object can then - read their JSON and be appended to our NPC array. + toObject() to get the Character's JSON object. Character::fromJson() can + then turn that QJSonObject into a Character object to append to our NPC array. \note \l{Container Classes}{Associate containers} can be written by storing the key in each value object (if it's not already). With this approach, the @@ -70,11 +109,13 @@ element is used as the key to construct the container when reading it back in. - \snippet serialization/savegame/level.cpp 1 + \snippet serialization/savegame/level.cpp toJson - Again, the write() function is similar to the read() function, except + Again, the toJson() function is similar to the fromJson() function, except reversed. + \section1 The Game Class + Having established the Character and Level classes, we can move on to the Game class: @@ -86,26 +127,43 @@ Next, we provide accessors for the player and levels. We then expose three functions: newGame(), saveGame() and loadGame(). - The read() and write() functions are used by saveGame() and loadGame(). + The read() and toJson() functions are used by saveGame() and loadGame(). + + \div{class="admonition note"}\b{Note:} + Despite \c Game being a value class, we assume that the author wants a game to have + identity, much like your main window would have. We therefore don't use a + static fromJson() function, which would create a new object, but a read() + function we can call on existing objects. There's a 1:1 correspondence + between read() and fromJson(), in that one can be implemented in terms of + the other: + + \code + void read(const QJsonObject &json) { *this = fromJson(json); } + static Game fromObject(const QJsonObject &json) { Game g; g.read(json); return g; } + \endcode - \snippet serialization/savegame/game.cpp 0 + We just use what's more convenient for callers of the functions. + \enddiv + + \snippet serialization/savegame/game.cpp newGame To setup a new game, we create the player and populate the levels and their NPCs. - \snippet serialization/savegame/game.cpp 1 + \snippet serialization/savegame/game.cpp read - The first thing we do in the read() function is tell the player to read - itself. We then clear the level array so that calling loadGame() on the - same Game object twice doesn't result in old levels hanging around. + The read() function starts by replacing the player with the + one read from JSON. We then clear() the level array so that calling + loadGame() on the same Game object twice doesn't result in old levels + hanging around. We then populate the level array by reading each Level from a QJsonArray. - \snippet serialization/savegame/game.cpp 2 + \snippet serialization/savegame/game.cpp toJson - We write the game to JSON similarly to how we write Level. + Writing the game to JSON is similar to writing a level. - \snippet serialization/savegame/game.cpp 3 + \snippet serialization/savegame/game.cpp loadGame When loading a saved game in loadGame(), the first thing we do is open the save file based on which format it was saved to; \c "save.json" for JSON, @@ -119,14 +177,16 @@ After constructing the QJsonDocument, we instruct the Game object to read itself and then return \c true to indicate success. - \snippet serialization/savegame/game.cpp 4 + \snippet serialization/savegame/game.cpp saveGame Not surprisingly, saveGame() looks very much like loadGame(). We determine the file extension based on the format, print a warning and return \c false if the opening of the file fails. We then write the Game object to a - QJsonDocument, and call either QJsonDocument::toJson() or to - QJsonDocument::toBinaryData() to save the game, depending on which format - was specified. + QJsonObject. To save the game in the format that was specified, we + convert the JSON object into either a QJsonDocument for a subsequent + QJsonDocument::toJson() call, or a QCborValue for QCborValue::toCbor(). + + \section1 Tying It All Together We are now ready to enter main(): diff --git a/examples/corelib/serialization/savegame/game.cpp b/examples/corelib/serialization/savegame/game.cpp index 21dedf9482..f99ecb8b51 100644 --- a/examples/corelib/serialization/savegame/game.cpp +++ b/examples/corelib/serialization/savegame/game.cpp @@ -11,6 +11,8 @@ #include <QRandomGenerator> #include <QTextStream> +using namespace Qt::StringLiterals; + Character Game::player() const { return mPlayer; @@ -21,52 +23,45 @@ QList<Level> Game::levels() const return mLevels; } -//! [0] +//! [newGame] void Game::newGame() { mPlayer = Character(); - mPlayer.setName(QStringLiteral("Hero")); + mPlayer.setName("Hero"_L1); mPlayer.setClassType(Character::Archer); mPlayer.setLevel(QRandomGenerator::global()->bounded(15, 21)); mLevels.clear(); mLevels.reserve(2); - Level village(QStringLiteral("Village")); + Level village("Village"_L1); QList<Character> villageNpcs; villageNpcs.reserve(2); - villageNpcs.append(Character(QStringLiteral("Barry the Blacksmith"), - QRandomGenerator::global()->bounded(8, 11), - Character::Warrior)); - villageNpcs.append(Character(QStringLiteral("Terry the Trader"), - QRandomGenerator::global()->bounded(6, 8), - Character::Warrior)); + villageNpcs.append(Character("Barry the Blacksmith"_L1, + QRandomGenerator::global()->bounded(8, 11), Character::Warrior)); + villageNpcs.append(Character("Terry the Trader"_L1, + QRandomGenerator::global()->bounded(6, 8), Character::Warrior)); village.setNpcs(villageNpcs); mLevels.append(village); - Level dungeon(QStringLiteral("Dungeon")); + Level dungeon("Dungeon"_L1); QList<Character> dungeonNpcs; dungeonNpcs.reserve(3); - dungeonNpcs.append(Character(QStringLiteral("Eric the Evil"), - QRandomGenerator::global()->bounded(18, 26), - Character::Mage)); - dungeonNpcs.append(Character(QStringLiteral("Eric's Left Minion"), - QRandomGenerator::global()->bounded(5, 7), - Character::Warrior)); - dungeonNpcs.append(Character(QStringLiteral("Eric's Right Minion"), - QRandomGenerator::global()->bounded(4, 9), - Character::Warrior)); + dungeonNpcs.append(Character("Eric the Evil"_L1, + QRandomGenerator::global()->bounded(18, 26), Character::Mage)); + dungeonNpcs.append(Character("Eric's Left Minion"_L1, + QRandomGenerator::global()->bounded(5, 7), Character::Warrior)); + dungeonNpcs.append(Character("Eric's Right Minion"_L1, + QRandomGenerator::global()->bounded(4, 9), Character::Warrior)); dungeon.setNpcs(dungeonNpcs); mLevels.append(dungeon); } -//! [0] +//! [newGame] -//! [3] +//! [loadGame] bool Game::loadGame(Game::SaveFormat saveFormat) { - QFile loadFile(saveFormat == Json - ? QStringLiteral("save.json") - : QStringLiteral("save.dat")); + QFile loadFile(saveFormat == Json ? "save.json"_L1 : "save.dat"_L1); if (!loadFile.open(QIODevice::ReadOnly)) { qWarning("Couldn't open save file."); @@ -76,85 +71,72 @@ bool Game::loadGame(Game::SaveFormat saveFormat) QByteArray saveData = loadFile.readAll(); QJsonDocument loadDoc(saveFormat == Json - ? QJsonDocument::fromJson(saveData) - : QJsonDocument(QCborValue::fromCbor(saveData).toMap().toJsonObject())); + ? QJsonDocument::fromJson(saveData) + : QJsonDocument(QCborValue::fromCbor(saveData).toMap().toJsonObject())); read(loadDoc.object()); - QTextStream(stdout) << "Loaded save for " - << loadDoc["player"]["name"].toString() - << " using " - << (saveFormat != Json ? "CBOR" : "JSON") << "...\n"; + QTextStream(stdout) << "Loaded save for " << loadDoc["player"]["name"].toString() + << " using " << (saveFormat != Json ? "CBOR" : "JSON") << "...\n"; return true; } -//! [3] +//! [loadGame] -//! [4] +//! [saveGame] bool Game::saveGame(Game::SaveFormat saveFormat) const { - QFile saveFile(saveFormat == Json - ? QStringLiteral("save.json") - : QStringLiteral("save.dat")); + QFile saveFile(saveFormat == Json ? "save.json"_L1 : "save.dat"_L1); if (!saveFile.open(QIODevice::WriteOnly)) { qWarning("Couldn't open save file."); return false; } - QJsonObject gameObject; - write(gameObject); - saveFile.write(saveFormat == Json - ? QJsonDocument(gameObject).toJson() - : QCborValue::fromJsonValue(gameObject).toCbor()); + QJsonObject gameObject = toJson(); + saveFile.write(saveFormat == Json ? QJsonDocument(gameObject).toJson() + : QCborValue::fromJsonValue(gameObject).toCbor()); return true; } -//! [4] +//! [saveGame] -//! [1] +//! [read] void Game::read(const QJsonObject &json) { - if (json.contains("player") && json["player"].isObject()) - mPlayer.read(json["player"].toObject()); + if (const QJsonValue v = json["player"]; v.isObject()) + mPlayer = Character::fromJson(v.toObject()); - if (json.contains("levels") && json["levels"].isArray()) { - QJsonArray levelArray = json["levels"].toArray(); + if (const QJsonValue v = json["levels"]; v.isArray()) { + const QJsonArray levels = v.toArray(); mLevels.clear(); - mLevels.reserve(levelArray.size()); - for (const QJsonValue &v : levelArray) { - QJsonObject levelObject = v.toObject(); - Level level; - level.read(levelObject); - mLevels.append(level); - } + mLevels.reserve(levels.size()); + for (const QJsonValue &level : levels) + mLevels.append(Level::fromJson(level.toObject())); } } -//! [1] +//! [read] -//! [2] -void Game::write(QJsonObject &json) const +//! [toJson] +QJsonObject Game::toJson() const { - QJsonObject playerObject; - mPlayer.write(playerObject); - json["player"] = playerObject; - - QJsonArray levelArray; - for (const Level &level : mLevels) { - QJsonObject levelObject; - level.write(levelObject); - levelArray.append(levelObject); - } - json["levels"] = levelArray; + QJsonObject json; + json["player"] = mPlayer.toJson(); + + QJsonArray levels; + for (const Level &level : mLevels) + levels.append(level.toJson()); + json["levels"] = levels; + return json; } -//! [2] +//! [toJson] -void Game::print(int indentation) const +void Game::print(QTextStream &s, int indentation) const { const QString indent(indentation * 2, ' '); - QTextStream(stdout) << indent << "Player\n"; - mPlayer.print(indentation + 1); + s << indent << "Player\n"; + mPlayer.print(s, indentation + 1); - QTextStream(stdout) << indent << "Levels\n"; + s << indent << "Levels\n"; for (const Level &level : mLevels) - level.print(indentation + 1); + level.print(s, indentation + 1); } diff --git a/examples/corelib/serialization/savegame/game.h b/examples/corelib/serialization/savegame/game.h index 0e91343d27..5ba5952923 100644 --- a/examples/corelib/serialization/savegame/game.h +++ b/examples/corelib/serialization/savegame/game.h @@ -10,13 +10,13 @@ #include <QJsonObject> #include <QList> +QT_FORWARD_DECLARE_CLASS(QTextStream) + //! [0] class Game { public: - enum SaveFormat { - Json, Binary - }; + enum SaveFormat { Json, Binary }; Character player() const; QList<Level> levels() const; @@ -26,9 +26,10 @@ public: bool saveGame(SaveFormat saveFormat) const; void read(const QJsonObject &json); - void write(QJsonObject &json) const; + QJsonObject toJson() const; + + void print(QTextStream &s, int indentation = 0) const; - void print(int indentation = 0) const; private: Character mPlayer; QList<Level> mLevels; diff --git a/examples/corelib/serialization/savegame/level.cpp b/examples/corelib/serialization/savegame/level.cpp index c2f88c3434..f30d35e57f 100644 --- a/examples/corelib/serialization/savegame/level.cpp +++ b/examples/corelib/serialization/savegame/level.cpp @@ -6,9 +6,7 @@ #include <QJsonArray> #include <QTextStream> -Level::Level(const QString &name) : mName(name) -{ -} +Level::Level(const QString &name) : mName(name) { } QString Level::name() const { @@ -25,46 +23,43 @@ void Level::setNpcs(const QList<Character> &npcs) mNpcs = npcs; } -//! [0] -void Level::read(const QJsonObject &json) +//! [fromJson] +Level Level::fromJson(const QJsonObject &json) { - if (json.contains("name") && json["name"].isString()) - mName = json["name"].toString(); + Level result; + + if (const QJsonValue v = json["name"]; v.isString()) + result.mName = v.toString(); - if (json.contains("npcs") && json["npcs"].isArray()) { - QJsonArray npcArray = json["npcs"].toArray(); - mNpcs.clear(); - mNpcs.reserve(npcArray.size()); - for (const QJsonValue &v : npcArray) { - QJsonObject npcObject = v.toObject(); - Character npc; - npc.read(npcObject); - mNpcs.append(npc); - } + if (const QJsonValue v = json["npcs"]; v.isArray()) { + const QJsonArray npcs = v.toArray(); + result.mNpcs.reserve(npcs.size()); + for (const QJsonValue &npc : npcs) + result.mNpcs.append(Character::fromJson(npc.toObject())); } + + return result; } -//! [0] +//! [fromJson] -//! [1] -void Level::write(QJsonObject &json) const +//! [toJson] +QJsonObject Level::toJson() const { + QJsonObject json; json["name"] = mName; QJsonArray npcArray; - for (const Character &npc : mNpcs) { - QJsonObject npcObject; - npc.write(npcObject); - npcArray.append(npcObject); - } + for (const Character &npc : mNpcs) + npcArray.append(npc.toJson()); json["npcs"] = npcArray; + return json; } -//! [1] +//! [toJson] -void Level::print(int indentation) const +void Level::print(QTextStream &s, int indentation) const { const QString indent(indentation * 2, ' '); - QTextStream(stdout) << indent << "Name:\t" << mName << "\n"; - QTextStream(stdout) << indent << "NPCs:\n"; + s << indent << "Name:\t" << mName << "\n" << indent << "NPCs:\n"; for (const Character &character : mNpcs) - character.print(2); + character.print(s, indentation + 1); } diff --git a/examples/corelib/serialization/savegame/level.h b/examples/corelib/serialization/savegame/level.h index e09e2c9f3c..e487e55ae3 100644 --- a/examples/corelib/serialization/savegame/level.h +++ b/examples/corelib/serialization/savegame/level.h @@ -9,6 +9,8 @@ #include <QJsonObject> #include <QList> +QT_FORWARD_DECLARE_CLASS(QTextStream) + //! [0] class Level { @@ -21,10 +23,11 @@ public: QList<Character> npcs() const; void setNpcs(const QList<Character> &npcs); - void read(const QJsonObject &json); - void write(QJsonObject &json) const; + static Level fromJson(const QJsonObject &json); + QJsonObject toJson() const; + + void print(QTextStream &s, int indentation = 0) const; - void print(int indentation = 0) const; private: QString mName; QList<Character> mNpcs; diff --git a/examples/corelib/serialization/savegame/main.cpp b/examples/corelib/serialization/savegame/main.cpp index 408b08dbc9..3fc0f3af10 100644 --- a/examples/corelib/serialization/savegame/main.cpp +++ b/examples/corelib/serialization/savegame/main.cpp @@ -4,30 +4,34 @@ #include "game.h" #include <QCoreApplication> +#include <QStringList> +#include <QString> #include <QTextStream> +using namespace Qt::StringLiterals; // for _L1 + //! [0] int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); - QStringList args = QCoreApplication::arguments(); - bool newGame = true; - if (args.length() > 1) - newGame = (args[1].toLower() != QStringLiteral("load")); - bool json = true; - if (args.length() > 2) - json = (args[2].toLower() != QStringLiteral("binary")); + + const QStringList args = QCoreApplication::arguments(); + const bool newGame + = args.size() <= 1 || QString::compare(args[1], "load"_L1, Qt::CaseInsensitive) != 0; + const bool json + = args.size() <= 2 || QString::compare(args[2], "binary"_L1, Qt::CaseInsensitive) != 0; Game game; if (newGame) game.newGame(); else if (!game.loadGame(json ? Game::Json : Game::Binary)) - return 1; + return 1; // Game is played; changes are made... //! [0] //! [1] - QTextStream(stdout) << "Game ended in the following state:\n"; - game.print(); + QTextStream s(stdout); + s << "Game ended in the following state:\n"; + game.print(s); if (!game.saveGame(json ? Game::Json : Game::Binary)) return 1; diff --git a/examples/corelib/serialization/serialization.pro b/examples/corelib/serialization/serialization.pro index f36b467783..e20fcb57fd 100644 --- a/examples/corelib/serialization/serialization.pro +++ b/examples/corelib/serialization/serialization.pro @@ -5,6 +5,5 @@ SUBDIRS = \ savegame qtHaveModule(widgets) { - qtHaveModule(network): SUBDIRS += \ - rsslisting + SUBDIRS += streambookmarks } diff --git a/examples/corelib/serialization/streambookmarks/CMakeLists.txt b/examples/corelib/serialization/streambookmarks/CMakeLists.txt new file mode 100644 index 0000000000..bad55fa661 --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/CMakeLists.txt @@ -0,0 +1,40 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(streambookmarks LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_standard_project_setup() + +qt_add_executable(streambookmarks + main.cpp + mainwindow.cpp mainwindow.h + xbelreader.cpp xbelreader.h + xbelwriter.cpp xbelwriter.h +) + +set_target_properties(streambookmarks PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(streambookmarks PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Widgets +) + +install(TARGETS streambookmarks + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET streambookmarks + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR +) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/serialization/streambookmarks/doc/images/filemenu.png b/examples/corelib/serialization/streambookmarks/doc/images/filemenu.png Binary files differnew file mode 100644 index 0000000000..1a92895ccf --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/doc/images/filemenu.png diff --git a/examples/corelib/serialization/streambookmarks/doc/images/helpmenu.png b/examples/corelib/serialization/streambookmarks/doc/images/helpmenu.png Binary files differnew file mode 100644 index 0000000000..baa98bee96 --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/doc/images/helpmenu.png diff --git a/examples/corelib/serialization/streambookmarks/doc/images/screenshot.png b/examples/corelib/serialization/streambookmarks/doc/images/screenshot.png Binary files differnew file mode 100644 index 0000000000..422873b6d0 --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/doc/images/screenshot.png diff --git a/examples/corelib/serialization/streambookmarks/doc/src/qxmlstreambookmarks.qdoc b/examples/corelib/serialization/streambookmarks/doc/src/qxmlstreambookmarks.qdoc new file mode 100644 index 0000000000..8e32dd8d0b --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/doc/src/qxmlstreambookmarks.qdoc @@ -0,0 +1,223 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example serialization/streambookmarks + \examplecategory {Data Processing & I/O} + \meta tag {network} + \title QXmlStream Bookmarks Example + \brief Demonstrates how to read and write XBEL files. + \ingroup xml-examples + + The QXmlStream Bookmarks example provides a viewer for XML Bookmark Exchange + Language (XBEL) files. It can read bookmarks using Qt's QXmlStreamReader and + write them back out again using QXmlStreamWriter. As this example aims to + show how to use these reader and writer types, it provides no means to open + a bookmark, add a new one, or merge two bookmark files, and only minimal + scope for editing bookmarks. None the less, it could surely be extended with + such features, if desired. + + \image screenshot.png + + \section1 XbelWriter Class Definition + + The \c XbelWriter class takes a \l{QTreeWidget}{tree widget} describing a + hierarchy of folders containing bookmarks. Its \c writeFile() provides the + means to write out this hierarchy, in XBEL format, to a given output device. + + Internally, it records the tree widget it was given and packages a private + instance of QXmlStreamWriter, which provides it with the means to stream + XML. It has an internal \c writeItem() to write each item in its tree. + + \snippet serialization/streambookmarks/xbelwriter.h 0 + + \section1 XbelWriter Class Implementation + + The \c XbelWriter constructor accepts the \a treeWidget it will describe. It + stores that and enables \l{QXmlStreamWriter}'s auto-formatting property. + This last splits the data into several lines, with indentation to indicate + the structure of the tree, which makes the XML output easier to read. + + \snippet serialization/streambookmarks/xbelwriter.cpp 0 + + The \c writeFile() function accepts a QIODevice object and directs its + QXmlStreamWriter member to write to this device, using \c setDevice(). This + function then writes the document type definition(DTD), the start element, + the version, and delegates writing of each of the \c{treeWidget}'s top-level + items to \c writeItem(). Finally, it closes the document and returns. + + \snippet serialization/streambookmarks/xbelwriter.cpp 1 + + The \c writeItem() function accepts a QTreeWidgetItem object and writes to + its XML stream a representation of the object, which depends on its \c + UserRole, which can be one of a \c{"folder"}, \c{"bookmark"}, + or \c{"separator"}. Within each folder, it calls itself recursively on each + child item, to recursively include a representation of each child within the + folder's XML element. + + \snippet serialization/streambookmarks/xbelwriter.cpp 2 + + \section1 XbelReader Class Definition + + The \c XbelReader takes a \l{QTreeWidget}{tree widget} to populate with + items describing a bookmark hierarchy. It supports reading XBEL data from a + QIODevice as a source of these items. If parsing of the XBEL data fails, it + can report what went wrong. + + Internally, it records the QTreeWidget that it will populate and packages an + instance of QXmlStreamReader, the companion class to QXmlStreamWriter, which + it will use to read XBEL data. + + \snippet serialization/streambookmarks/xbelreader.h 0 + + \section1 XbelReader Class Implementation + + Since the XBEL reader is only concerned with reading XML elements, it makes + extensive use of the \l{QXmlStreamReader::}{readNextStartElement()} + convenience function. + + The \c XbelReader constructor requires a QTreeWidget that it will populate. + It populates the tree widget's style with suitable icons: a folder icon that + changes form to indicate whether each folder as open or closed; and a + standard file icon for the individual bookmarks within those folders. + + \snippet serialization/streambookmarks/xbelreader.cpp 0 + + The \c read() function accepts a QIODevice. It directs its QXmlStreamReader + member to read content from that device. Note that the XML input must be + well-formed to be accepted by QXmlStreamReader. First it reads the outer + structure and verifies the content is an XBEL 1.0 file; if it is, \c read() + delegates the actual reading of content to the internal \c readXBEL(). + + Otherwise, the \l{QXmlStreamReader::}{raiseError()} function is used to + record an error message. The reader itself may also do the same if it + encounters errors in the input. When \c read() has finished, it returns + true if there were no errors. + + \snippet serialization/streambookmarks/xbelreader.cpp 1 + + If \c read() returns false, its caller can obtain a description of the + error, complete with line and column number within the stream, by calling + the \c errorString() function. + + \snippet serialization/streambookmarks/xbelreader.cpp 2 + + The \c readXBEL() function reads the name of a startElement and calls the + appropriate function to read it, depending on whether if its tag name + is \c{"folder"}, \c{"bookmark"} or \c{"separator"}. Any other elements + encountered are skipped. The function starts with a precondition, verifying + that the XML reader has just opened an \c{"xbel"} element. + + \snippet serialization/streambookmarks/xbelreader.cpp 3 + + The \c readBookmark() function creates a new editable item representing a + single bookmark. It records the XML \c{"href"} attribute of the current + element as second column text of the item and provisionally sets its first + column text to \c{"Unknown title"} before scanning the rest of the element + for a title element to over-ride that, skipping any unrecognized child + elements. + + \snippet serialization/streambookmarks/xbelreader.cpp 5 + + The \c readTitle() function reads a bookmark's title and records it as the + title (first column text) of the item for which it was called. + + \snippet serialization/streambookmarks/xbelreader.cpp 6 + + The \c readSeparator() function creates a separator and sets its flags. The + separator item's text is set to 30 centered dots. The rest of the element is + then skipped using \l{QXmlStreamReader::}{skipCurrentElement()}. + + \snippet serialization/streambookmarks/xbelreader.cpp 6 + + The \c readFolder() function creates an item and iterates the content of the + folder element, adding children to this item to represent the contents of + the folder element. The loop over folder content is similar in form to the + one in \c readXBEL(), save that it now accepts a title element to set the + title of the folder. + + \snippet serialization/streambookmarks/xbelreader.cpp 7 + + The \c createChildItem() helper function creates a new tree widget item + that's either a child of the given item or, if no parent item is given, a + direct child of the tree widget. It sets the new item's \c UserRole to the + tag name of the current XML element, matching how XbelWriter::writeFile() + uses that \c UserRole. + + \snippet serialization/streambookmarks/xbelreader.cpp 8 + + \section1 MainWindow Class Definition + + The \c MainWindow class is a subclass of QMainWindow, with a \c File menu + and a \c Help menu. + + \snippet serialization/streambookmarks/mainwindow.h 0 + + \section1 MainWindow Class Implementation + + The \c MainWindow constructor sets up its QTreeWidget object, \c treeWidget, + as its own central widget, with column headings for the title and location + of each book-mark. It configures a custom menu that enables the user to + perform actions on individual bookmarks within the tree widget. + + It invokes \c createMenus() to set up its own menus and their corresponding + actions. It sets its title, announces itself as ready and sets its size to a + reasonable proportion of the available screen space. + + \snippet serialization/streambookmarks/mainwindow.cpp 0 + + A custom menu, triggered when the user right-clicks on a bookmark, provides + for copying the bookmark as a link or directing a desktop browser to open + the URL it references. This menu is implemented (when relevant features are + enabled) by \c onCustomContextMenuRequested(). + + \snippet serialization/streambookmarks/mainwindow.cpp 1 + + The \c createMenus() function creates the \c fileMenu and \c helpMenu and + adds QAction objects to them, bound variously to the \c open(), \c saveAs() + and \c about() functions, along with QWidget::close() and + QApplication::aboutQt(). The connections are as shown below: + + \snippet serialization/streambookmarks/mainwindow.cpp 2 + + This creates the menu shown in the screenshots below: + + \table + \row + \li \inlineimage filemenu.png + \li \inlineimage helpmenu.png + \endtable + + The \c open() function, when triggered, offers the user a file dialog to use + to select a bookmarks file. If a file is selected, it is parsed using an \c + XBelReader to populate the \c treeWidget with bookmarks. If problems arise + with opening or parsing the file, a suitable warning message is displayed to + the user, including file name and error message. Otherwise, the bookmarks + read from the file are displayed and the window's status bar briefly reports + that the file has been loaded. + + \snippet serialization/streambookmarks/mainwindow.cpp 3 + + The \c saveAs() function displays a QFileDialog, prompting the user for a \c + fileName, to which to save a copy of the bookmarks data. Similar to the \c + open() function, this function also displays a warning message if the file + cannot be written to. + + \snippet serialization/streambookmarks/mainwindow.cpp 4 + + The \c about() function displays a QMessageBox with a brief description of + the example, or general information about Qt and the version of it in use. + + \snippet serialization/streambookmarks/mainwindow.cpp 5 + + \section1 \c{main()} Function + + The \c main() function instantiates \c MainWindow and invokes the \c show() + function to display it, then its \c open(), as this is most likely what the + user shall want to do first. + + \snippet serialization/streambookmarks/main.cpp 0 + + See the \l{https://pyxml.sourceforge.net/topics/xbel/} {XML Bookmark + Exchange Language Resource Page} for more information about XBEL files. +*/ diff --git a/examples/corelib/serialization/streambookmarks/jennifer.xbel b/examples/corelib/serialization/streambookmarks/jennifer.xbel new file mode 100644 index 0000000000..d504236830 --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/jennifer.xbel @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE xbel> +<xbel version="1.0"> + <folder folded="no"> + <title>Qt Resources</title> + <bookmark href="https://www.qt.io/"> + <title>Qt home page</title> + </bookmark> + <bookmark href="https://www.qt.io/contact-us/partners"> + <title>Qt Partners</title> + </bookmark> + <bookmark href="https://www.qt.io/qt-professional-services"> + <title>Professional Services</title> + </bookmark> + <bookmark href="https://doc.qt.io/"> + <title>Qt Documentation</title> + </bookmark> + <folder folded="yes"> + <title>Community Resources</title> + <bookmark href="https://contribute.qt-project.org"> + <title>The Qt Project</title> + </bookmark> + <bookmark href="https://www.qtcentre.org/content/"> + <title>Qt Centre</title> + </bookmark> + <bookmark href="https://forum.qt.io/"> + <title>Forum.Qt.org</title> + </bookmark> + <bookmark href="https://digitalfanatics.org/projects/qt_tutorial/"> + <title>The Independent Qt Tutorial</title> + </bookmark> + <bookmark href="https://www.qtforum.de/"> + <title>German Qt Forum</title> + </bookmark> + <bookmark href="https://www.qt-dev.com/"> + <title>Korean Qt Community Site</title> + </bookmark> + <bookmark href="http://www.prog.org.ru/"> + <title>Russian Qt Forum</title> + </bookmark> + </folder> + </folder> + <folder folded="no"> + <title>Online Dictionaries</title> + <bookmark href="https://www.dictionary.com/"> + <title>Dictionary.com</title> + </bookmark> + <bookmark href="https://www.merriam-webster.com/"> + <title>Merriam-Webster Online</title> + </bookmark> + <bookmark href="https://dictionary.cambridge.org/"> + <title>Cambridge Dictionaries Online</title> + </bookmark> + <bookmark href="https://www.onelook.com/"> + <title>OneLook Dictionary Search</title> + </bookmark> + <separator/> + <bookmark href="https://dict.tu-chemnitz.de/"> + <title>BEOLINGUS, a service of TU Chemnitz</title> + </bookmark> + <separator/> + <bookmark href="http://atilf.atilf.fr/tlf.htm"> + <title>Trésor de la Langue Française informatisé</title> + </bookmark> + <bookmark href="https://www.dictionnaire-academie.fr/"> + <title>Dictionnaire de l'Académie Française</title> + </bookmark> + </folder> +</xbel> diff --git a/examples/corelib/serialization/streambookmarks/main.cpp b/examples/corelib/serialization/streambookmarks/main.cpp new file mode 100644 index 0000000000..0fd317de43 --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/main.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" + +#include <QApplication> + +//! [0] +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + MainWindow mainWin; + mainWin.show(); + mainWin.open(); + return app.exec(); +} +//! [0] diff --git a/examples/corelib/serialization/streambookmarks/mainwindow.cpp b/examples/corelib/serialization/streambookmarks/mainwindow.cpp new file mode 100644 index 0000000000..a863f77ab7 --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/mainwindow.cpp @@ -0,0 +1,151 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" +#include "xbelreader.h" +#include "xbelwriter.h" + +#include <QFileDialog> +#include <QHeaderView> +#include <QMenuBar> +#include <QMessageBox> +#include <QStatusBar> +#include <QTreeWidget> + +#include <QAction> +#if QT_CONFIG(clipboard) +# include <QClipboard> +#endif +#include <QDesktopServices> +#include <QApplication> +#include <QScreen> + +using namespace Qt::StringLiterals; + +//! [0] +MainWindow::MainWindow() : treeWidget(new QTreeWidget) +{ + treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch); + treeWidget->setHeaderLabels(QStringList{tr("Title"), tr("Location")}); +#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu) + treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); + connect(treeWidget, &QWidget::customContextMenuRequested, + this, &MainWindow::onCustomContextMenuRequested); +#endif + setCentralWidget(treeWidget); + + createMenus(); + + statusBar()->showMessage(tr("Ready")); + + setWindowTitle(tr("QXmlStream Bookmarks")); + const QSize availableSize = screen()->availableGeometry().size(); + resize(availableSize.width() / 2, availableSize.height() / 3); +} +//! [0] + +//! [1] +#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu) +void MainWindow::onCustomContextMenuRequested(const QPoint &pos) +{ + const QTreeWidgetItem *item = treeWidget->itemAt(pos); + if (!item) + return; + const QString url = item->text(1); + QMenu contextMenu; + QAction *copyAction = contextMenu.addAction(tr("Copy Link to Clipboard")); + QAction *openAction = contextMenu.addAction(tr("Open")); + QAction *action = contextMenu.exec(treeWidget->viewport()->mapToGlobal(pos)); + if (action == copyAction) + QGuiApplication::clipboard()->setText(url); + else if (action == openAction) + QDesktopServices::openUrl(QUrl(url)); +} +#endif // QT_CONFIG(clipboard) && QT_CONFIG(contextmenu) +//! [1] + +//! [2] +void MainWindow::createMenus() +{ + QMenu *fileMenu = menuBar()->addMenu(tr("&File")); + QAction *openAct = fileMenu->addAction(tr("&Open..."), this, &MainWindow::open); + openAct->setShortcuts(QKeySequence::Open); + + QAction *saveAsAct = fileMenu->addAction(tr("&Save As..."), this, &MainWindow::saveAs); + saveAsAct->setShortcuts(QKeySequence::SaveAs); + + QAction *exitAct = fileMenu->addAction(tr("E&xit"), this, &QWidget::close); + exitAct->setShortcuts(QKeySequence::Quit); + + menuBar()->addSeparator(); + + QMenu *helpMenu = menuBar()->addMenu(tr("&Help")); + helpMenu->addAction(tr("&About"), this, &MainWindow::about); + helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); +} +//! [2] + +//! [3] +void MainWindow::open() +{ + QFileDialog fileDialog(this, tr("Open Bookmark File"), QDir::currentPath()); + fileDialog.setMimeTypeFilters({"application/x-xbel"_L1}); + if (fileDialog.exec() != QDialog::Accepted) + return; + + treeWidget->clear(); + + const QString fileName = fileDialog.selectedFiles().constFirst(); + QFile file(fileName); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + QMessageBox::warning(this, tr("QXmlStream Bookmarks"), + tr("Cannot read file %1:\n%2.") + .arg(QDir::toNativeSeparators(fileName), file.errorString())); + return; + } + + XbelReader reader(treeWidget); + if (!reader.read(&file)) { + QMessageBox::warning( + this, tr("QXmlStream Bookmarks"), + tr("Parse error in file %1:\n\n%2") + .arg(QDir::toNativeSeparators(fileName), reader.errorString())); + } else { + statusBar()->showMessage(tr("File loaded"), 2000); + } +} +//! [3] + +//! [4] +void MainWindow::saveAs() +{ + QFileDialog fileDialog(this, tr("Save Bookmark File"), QDir::currentPath()); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + fileDialog.setDefaultSuffix("xbel"_L1); + fileDialog.setMimeTypeFilters({"application/x-xbel"_L1}); + if (fileDialog.exec() != QDialog::Accepted) + return; + + const QString fileName = fileDialog.selectedFiles().constFirst(); + QFile file(fileName); + if (!file.open(QFile::WriteOnly | QFile::Text)) { + QMessageBox::warning(this, tr("QXmlStream Bookmarks"), + tr("Cannot write file %1:\n%2.") + .arg(QDir::toNativeSeparators(fileName), file.errorString())); + return; + } + + XbelWriter writer(treeWidget); + if (writer.writeFile(&file)) + statusBar()->showMessage(tr("File saved"), 2000); +} +//! [4] + +//! [5] +void MainWindow::about() +{ + QMessageBox::about(this, tr("About QXmlStream Bookmarks"), + tr("The <b>QXmlStream Bookmarks</b> example demonstrates how to use Qt's " + "QXmlStream classes to read and write XML documents.")); +} +//! [5] diff --git a/examples/corelib/serialization/streambookmarks/mainwindow.h b/examples/corelib/serialization/streambookmarks/mainwindow.h new file mode 100644 index 0000000000..d9efe6b5a5 --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/mainwindow.h @@ -0,0 +1,35 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QMainWindow> + +QT_BEGIN_NAMESPACE +class QTreeWidget; +QT_END_NAMESPACE + +//! [0] +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + +public slots: + void open(); + void saveAs(); + void about(); +#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu) + void onCustomContextMenuRequested(const QPoint &pos); +#endif +private: + void createMenus(); + + QTreeWidget *const treeWidget; +}; +//! [0] + +#endif diff --git a/examples/corelib/serialization/streambookmarks/streambookmarks.pro b/examples/corelib/serialization/streambookmarks/streambookmarks.pro new file mode 100644 index 0000000000..34d2caae82 --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/streambookmarks.pro @@ -0,0 +1,15 @@ +HEADERS = mainwindow.h \ + xbelreader.h \ + xbelwriter.h +SOURCES = main.cpp \ + mainwindow.cpp \ + xbelreader.cpp \ + xbelwriter.cpp +QT += widgets +requires(qtConfig(filedialog)) + +EXAMPLE_FILES = jennifer.xbel + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/streambookmarks +INSTALLS += target diff --git a/examples/corelib/serialization/streambookmarks/xbelreader.cpp b/examples/corelib/serialization/streambookmarks/xbelreader.cpp new file mode 100644 index 0000000000..c622cf6642 --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/xbelreader.cpp @@ -0,0 +1,140 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "xbelreader.h" + +#include <QStyle> +#include <QTreeWidget> + +using namespace Qt::StringLiterals; + +//! [0] +XbelReader::XbelReader(QTreeWidget *treeWidget) : treeWidget(treeWidget) +{ + QStyle *style = treeWidget->style(); + + folderIcon.addPixmap(style->standardPixmap(QStyle::SP_DirClosedIcon), QIcon::Normal, + QIcon::Off); + folderIcon.addPixmap(style->standardPixmap(QStyle::SP_DirOpenIcon), QIcon::Normal, QIcon::On); + bookmarkIcon.addPixmap(style->standardPixmap(QStyle::SP_FileIcon)); +} +//! [0] + +//! [1] +bool XbelReader::read(QIODevice *device) +{ + xml.setDevice(device); + + if (xml.readNextStartElement()) { + if (xml.name() == "xbel"_L1 && xml.attributes().value("version"_L1) == "1.0"_L1) + readXBEL(); + else + xml.raiseError(QObject::tr("The file is not an XBEL version 1.0 file.")); + } + + return !xml.error(); +} +//! [1] + +//! [2] +QString XbelReader::errorString() const +{ + return QObject::tr("%1\nLine %2, column %3") + .arg(xml.errorString()) + .arg(xml.lineNumber()) + .arg(xml.columnNumber()); +} +//! [2] + +//! [3] +void XbelReader::readXBEL() +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "xbel"_L1); + + while (xml.readNextStartElement()) { + if (xml.name() == "folder"_L1) + readFolder(nullptr); + else if (xml.name() == "bookmark"_L1) + readBookmark(nullptr); + else if (xml.name() == "separator"_L1) + readSeparator(nullptr); + else + xml.skipCurrentElement(); + } +} +//! [3] + +//! [4] +void XbelReader::readBookmark(QTreeWidgetItem *item) +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "bookmark"_L1); + + QTreeWidgetItem *bookmark = createChildItem(item); + bookmark->setFlags(bookmark->flags() | Qt::ItemIsEditable); + bookmark->setIcon(0, bookmarkIcon); + bookmark->setText(0, QObject::tr("Unknown title")); + bookmark->setText(1, xml.attributes().value("href"_L1).toString()); + + while (xml.readNextStartElement()) { + if (xml.name() == "title"_L1) + readTitle(bookmark); + else + xml.skipCurrentElement(); + } +} +//! [4] + +//! [5] +void XbelReader::readTitle(QTreeWidgetItem *item) +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "title"_L1); + item->setText(0, xml.readElementText()); +} +//! [5] + +//! [6] +void XbelReader::readSeparator(QTreeWidgetItem *item) +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "separator"_L1); + constexpr char16_t midDot = u'\xB7'; + static const QString dots(30, midDot); + + QTreeWidgetItem *separator = createChildItem(item); + separator->setFlags(item ? item->flags() & ~Qt::ItemIsSelectable : Qt::ItemFlags{}); + separator->setText(0, dots); + xml.skipCurrentElement(); +} +//! [6] + +//! [7] +void XbelReader::readFolder(QTreeWidgetItem *item) +{ + Q_ASSERT(xml.isStartElement() && xml.name() == "folder"_L1); + + QTreeWidgetItem *folder = createChildItem(item); + bool folded = xml.attributes().value("folded"_L1) != "no"_L1; + folder->setExpanded(!folded); + + while (xml.readNextStartElement()) { + if (xml.name() == "title"_L1) + readTitle(folder); + else if (xml.name() == "folder"_L1) + readFolder(folder); + else if (xml.name() == "bookmark"_L1) + readBookmark(folder); + else if (xml.name() == "separator"_L1) + readSeparator(folder); + else + xml.skipCurrentElement(); + } +} +//! [7] + +//! [8] +QTreeWidgetItem *XbelReader::createChildItem(QTreeWidgetItem *item) +{ + QTreeWidgetItem *childItem = item ? new QTreeWidgetItem(item) : new QTreeWidgetItem(treeWidget); + childItem->setData(0, Qt::UserRole, xml.name().toString()); + return childItem; +} +//! [8] diff --git a/examples/corelib/serialization/streambookmarks/xbelreader.h b/examples/corelib/serialization/streambookmarks/xbelreader.h new file mode 100644 index 0000000000..a3fa59d813 --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/xbelreader.h @@ -0,0 +1,45 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef XBELREADER_H +#define XBELREADER_H + +#include <QIcon> +#include <QXmlStreamReader> + +QT_BEGIN_NAMESPACE +class QTreeWidget; +class QTreeWidgetItem; +QT_END_NAMESPACE + +//! [0] +class XbelReader +{ +public: +//! [1] + XbelReader(QTreeWidget *treeWidget); +//! [1] + + bool read(QIODevice *device); + QString errorString() const; + +private: +//! [2] + void readXBEL(); + void readTitle(QTreeWidgetItem *item); + void readSeparator(QTreeWidgetItem *item); + void readFolder(QTreeWidgetItem *item); + void readBookmark(QTreeWidgetItem *item); + + QTreeWidgetItem *createChildItem(QTreeWidgetItem *item); + + QXmlStreamReader xml; + QTreeWidget *treeWidget; +//! [2] + + QIcon folderIcon; + QIcon bookmarkIcon; +}; +//! [0] + +#endif diff --git a/examples/corelib/serialization/streambookmarks/xbelwriter.cpp b/examples/corelib/serialization/streambookmarks/xbelwriter.cpp new file mode 100644 index 0000000000..e50f47a5a5 --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/xbelwriter.cpp @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "xbelwriter.h" + +#include <QTreeWidget> + +using namespace Qt::StringLiterals; + +//! [0] +XbelWriter::XbelWriter(const QTreeWidget *treeWidget) : treeWidget(treeWidget) +{ + xml.setAutoFormatting(true); +} +//! [0] + +//! [1] +bool XbelWriter::writeFile(QIODevice *device) +{ + xml.setDevice(device); + + xml.writeStartDocument(); + xml.writeDTD("<!DOCTYPE xbel>"_L1); + xml.writeStartElement("xbel"_L1); + xml.writeAttribute("version"_L1, "1.0"_L1); + for (int i = 0; i < treeWidget->topLevelItemCount(); ++i) + writeItem(treeWidget->topLevelItem(i)); + + xml.writeEndDocument(); + return true; +} +//! [1] + +//! [2] +void XbelWriter::writeItem(const QTreeWidgetItem *item) +{ + QString tagName = item->data(0, Qt::UserRole).toString(); + if (tagName == "folder"_L1) { + bool folded = !item->isExpanded(); + xml.writeStartElement(tagName); + xml.writeAttribute("folded"_L1, folded ? "yes"_L1 : "no"_L1); + xml.writeTextElement("title"_L1, item->text(0)); + for (int i = 0; i < item->childCount(); ++i) + writeItem(item->child(i)); + xml.writeEndElement(); + } else if (tagName == "bookmark"_L1) { + xml.writeStartElement(tagName); + if (!item->text(1).isEmpty()) + xml.writeAttribute("href"_L1, item->text(1)); + xml.writeTextElement("title"_L1, item->text(0)); + xml.writeEndElement(); + } else if (tagName == "separator"_L1) { + xml.writeEmptyElement(tagName); + } +} +//! [2] diff --git a/examples/corelib/serialization/streambookmarks/xbelwriter.h b/examples/corelib/serialization/streambookmarks/xbelwriter.h new file mode 100644 index 0000000000..ec95315c4b --- /dev/null +++ b/examples/corelib/serialization/streambookmarks/xbelwriter.h @@ -0,0 +1,28 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef XBELWRITER_H +#define XBELWRITER_H + +#include <QXmlStreamWriter> + +QT_BEGIN_NAMESPACE +class QTreeWidget; +class QTreeWidgetItem; +QT_END_NAMESPACE + +//! [0] +class XbelWriter +{ +public: + explicit XbelWriter(const QTreeWidget *treeWidget); + bool writeFile(QIODevice *device); + +private: + void writeItem(const QTreeWidgetItem *item); + QXmlStreamWriter xml; + const QTreeWidget *treeWidget; +}; +//! [0] + +#endif diff --git a/examples/corelib/threads/CMakeLists.txt b/examples/corelib/threads/CMakeLists.txt index 4ced9a50e5..670821a77d 100644 --- a/examples/corelib/threads/CMakeLists.txt +++ b/examples/corelib/threads/CMakeLists.txt @@ -1,8 +1,10 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -qt_internal_add_example(semaphores) -qt_internal_add_example(waitconditions) +if(NOT ANDROID) + qt_internal_add_example(semaphores) + qt_internal_add_example(waitconditions) +endif() if(TARGET Qt6::Widgets) qt_internal_add_example(mandelbrot) qt_internal_add_example(queuedcustomtype) diff --git a/examples/corelib/threads/doc/src/mandelbrot.qdoc b/examples/corelib/threads/doc/src/mandelbrot.qdoc index fa9a7fc3f6..6081912e69 100644 --- a/examples/corelib/threads/doc/src/mandelbrot.qdoc +++ b/examples/corelib/threads/doc/src/mandelbrot.qdoc @@ -3,7 +3,8 @@ /*! \example threads/mandelbrot - \title Mandelbrot Example + \examplecategory {Data Processing & I/O} + \title Mandelbrot \ingroup qtconcurrent-mtexamples \brief The Mandelbrot example demonstrates multi-thread programming @@ -151,7 +152,7 @@ it needs to access \c{RenderThread}'s member variables (e.g., in \c render()). - The \c forever keyword is, like \c foreach, a Qt pseudo-keyword. + The \c forever keyword is a Qt pseudo-keyword. \snippet threads/mandelbrot/renderthread.cpp 4 \snippet threads/mandelbrot/renderthread.cpp 5 diff --git a/examples/corelib/threads/doc/src/queuedcustomtype.qdoc b/examples/corelib/threads/doc/src/queuedcustomtype.qdoc index df904cc438..8d56695c79 100644 --- a/examples/corelib/threads/doc/src/queuedcustomtype.qdoc +++ b/examples/corelib/threads/doc/src/queuedcustomtype.qdoc @@ -3,8 +3,8 @@ /*! \example threads/queuedcustomtype - \title Queued Custom Type Example - \brief Demonstrates multi-thread programming using Qt. + \examplecategory {Data Processing & I/O} + \title Queued Custom Type \ingroup qtconcurrent-mtexamples \brief The Queued Custom Type example shows how to send custom types between @@ -18,20 +18,15 @@ \section1 Overview - In the \l{Custom Type Example}, we showed how to integrate custom types with - the meta-object system, enabling them to be stored in QVariant objects, written - out in debugging information and used in signal-slot communication. - - In this example, we create a new value class, \c Block, and register it - with the meta-object system to enable us to send instances of it between - threads using queued signals and slots. + In this example, we create a value class, \c Block, and register it with + the meta-object system to enable us to send instances of it between threads + using queued signals and slots. \section1 The Block Class - The \c Block class is similar to the \c Message class described in the - \l{Custom Type Example}. It provides the default constructor, copy - constructor and destructor in the public section of the class that the - meta-object system requires. It describes a colored rectangle. + The \c Block class provides the default constructor, copy constructor, and + a destructor in the public section of the class as required by the + meta-object system. The class describes a colored rectangle. \snippet threads/queuedcustomtype/block.h custom type definition and meta-type declaration @@ -127,9 +122,7 @@ This example showed how a custom type can be registered with the meta-object system so that it can be used with signal-slot connections - between threads. For ordinary communication involving direct signals and - slots, it is enough to simply declare the type in the way described in the - \l{Custom Type Example}. + between threads. In practice, both the Q_DECLARE_METATYPE() macro and the qRegisterMetaType() template function can be used to register custom types, but diff --git a/examples/corelib/threads/doc/src/semaphores.qdoc b/examples/corelib/threads/doc/src/semaphores.qdoc index b8e1ab1b52..f5ff90b014 100644 --- a/examples/corelib/threads/doc/src/semaphores.qdoc +++ b/examples/corelib/threads/doc/src/semaphores.qdoc @@ -3,13 +3,13 @@ /*! \example threads/semaphores - \title Semaphores Example - \brief Demonstrates multi-thread programming using Qt. + \examplecategory {Data Processing & I/O} + \title Producer and Consumer using Semaphores \ingroup qtconcurrent-mtexamples - \brief The Semaphores example shows how to use QSemaphore to control - access to a circular buffer shared by a producer thread and a - consumer thread. + \brief The Producer and Consumer using Semaphores example shows how + to use QSemaphore to control access to a circular buffer shared + by a producer thread and a consumer thread. The producer writes data to the buffer until it reaches the end of the buffer, at which point it restarts from the beginning, @@ -30,7 +30,7 @@ An alternative to using QSemaphore to solve the producer-consumer problem is to use QWaitCondition and QMutex. This is what the - \l{Wait Conditions Example} does. + \l{Producer and Consumer using Wait Conditions} example does. \section1 Global Variables diff --git a/examples/corelib/threads/doc/src/waitconditions.qdoc b/examples/corelib/threads/doc/src/waitconditions.qdoc index ae9e767c13..d46442d079 100644 --- a/examples/corelib/threads/doc/src/waitconditions.qdoc +++ b/examples/corelib/threads/doc/src/waitconditions.qdoc @@ -3,13 +3,13 @@ /*! \example threads/waitconditions - \title Wait Conditions Example - \brief Demonstrates multi-thread programming using Qt. + \examplecategory {Data Processing & I/O} + \title Producer and Consumer using Wait Conditions \ingroup qtconcurrent-mtexamples - \brief The Wait Conditions example shows how to use QWaitCondition and - QMutex to control access to a circular buffer shared by a - producer thread and a consumer thread. + \brief The Producer and Consumer using Wait Conditions example shows + how to use QWaitCondition and QMutex to control access to a circular + buffer shared by a producer thread and a consumer thread. The producer writes data to the buffer until it reaches the end of the buffer, at which point it restarts from the beginning, @@ -30,7 +30,7 @@ An alternative to using QWaitCondition and QMutex to solve the producer-consumer problem is to use QSemaphore. This is what the - \l{Semaphores Example} does. + \l{Producer and Consumer using Semaphores} example does. \section1 Global Variables diff --git a/examples/corelib/threads/mandelbrot/CMakeLists.txt b/examples/corelib/threads/mandelbrot/CMakeLists.txt index 9c5a553966..be296918cc 100644 --- a/examples/corelib/threads/mandelbrot/CMakeLists.txt +++ b/examples/corelib/threads/mandelbrot/CMakeLists.txt @@ -1,15 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(mandelbrot LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/threads/mandelbrot") - find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) qt_standard_project_setup() @@ -32,7 +26,14 @@ target_link_libraries(mandelbrot PRIVATE ) install(TARGETS mandelbrot - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET mandelbrot + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/threads/mandelbrot/main.cpp b/examples/corelib/threads/mandelbrot/main.cpp index d0d4680978..8aafebf7d7 100644 --- a/examples/corelib/threads/mandelbrot/main.cpp +++ b/examples/corelib/threads/mandelbrot/main.cpp @@ -2,15 +2,14 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "mandelbrotwidget.h" +#include "renderthread.h" #include <QApplication> - -#include <QScreen> - #include <QCommandLineParser> #include <QCommandLineOption> #include <QDebug> -#include <QRect> + +using namespace Qt::StringLiterals; //! [0] int main(int argc, char *argv[]) @@ -18,10 +17,10 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); QCommandLineParser parser; - parser.setApplicationDescription("Qt Mandelbrot Example"); + parser.setApplicationDescription(u"Qt Mandelbrot Example"_s); parser.addHelpOption(); parser.addVersionOption(); - QCommandLineOption passesOption("passes", "Number of passes (1-8)", "passes"); + QCommandLineOption passesOption(u"passes"_s, u"Number of passes (1-8)"_s, u"passes"_s); parser.addOption(passesOption); parser.process(app); diff --git a/examples/corelib/threads/mandelbrot/mandelbrotwidget.cpp b/examples/corelib/threads/mandelbrot/mandelbrotwidget.cpp index e0f33a2b0b..bbe694831d 100644 --- a/examples/corelib/threads/mandelbrot/mandelbrotwidget.cpp +++ b/examples/corelib/threads/mandelbrot/mandelbrotwidget.cpp @@ -4,19 +4,20 @@ #include "mandelbrotwidget.h" #include <QGesture> +#include <QGestureEvent> #include <QKeyEvent> #include <QPainter> #include <math.h> //! [0] -const double DefaultCenterX = -0.637011; -const double DefaultCenterY = -0.0395159; -const double DefaultScale = 0.00403897; +constexpr double DefaultCenterX = -0.637011; +constexpr double DefaultCenterY = -0.0395159; +constexpr double DefaultScale = 0.00403897; -const double ZoomInFactor = 0.8; -const double ZoomOutFactor = 1 / ZoomInFactor; -const int ScrollStep = 20; +constexpr double ZoomInFactor = 0.8; +constexpr double ZoomOutFactor = 1 / ZoomInFactor; +constexpr int ScrollStep = 20; //! [0] //! [1] @@ -46,7 +47,8 @@ void MandelbrotWidget::paintEvent(QPaintEvent * /* event */) if (pixmap.isNull()) { painter.setPen(Qt::white); - painter.drawText(rect(), Qt::AlignCenter|Qt::TextWordWrap, tr("Rendering initial image, please wait...")); + painter.drawText(rect(), Qt::AlignCenter|Qt::TextWordWrap, + tr("Rendering initial image, please wait...")); //! [2] //! [3] return; //! [3] //! [4] @@ -60,47 +62,47 @@ void MandelbrotWidget::paintEvent(QPaintEvent * /* event */) //! [6] //! [7] } else { //! [7] //! [8] - auto previewPixmap = qFuzzyCompare(pixmap.devicePixelRatio(), qreal(1)) + const auto previewPixmap = qFuzzyCompare(pixmap.devicePixelRatio(), qreal(1)) ? pixmap : pixmap.scaled(pixmap.deviceIndependentSize().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation); - double scaleFactor = pixmapScale / curScale; - int newWidth = int(previewPixmap.width() * scaleFactor); - int newHeight = int(previewPixmap.height() * scaleFactor); - int newX = pixmapOffset.x() + (previewPixmap.width() - newWidth) / 2; - int newY = pixmapOffset.y() + (previewPixmap.height() - newHeight) / 2; + const double scaleFactor = pixmapScale / curScale; + const int newWidth = int(previewPixmap.width() * scaleFactor); + const int newHeight = int(previewPixmap.height() * scaleFactor); + const int newX = pixmapOffset.x() + (previewPixmap.width() - newWidth) / 2; + const int newY = pixmapOffset.y() + (previewPixmap.height() - newHeight) / 2; painter.save(); painter.translate(newX, newY); painter.scale(scaleFactor, scaleFactor); - QRectF exposed = painter.transform().inverted().mapRect(rect()).adjusted(-1, -1, 1, 1); + const QRectF exposed = painter.transform().inverted().mapRect(rect()) + .adjusted(-1, -1, 1, 1); painter.drawPixmap(exposed, previewPixmap, exposed); painter.restore(); } //! [8] //! [9] - QFontMetrics metrics = painter.fontMetrics(); + const QFontMetrics metrics = painter.fontMetrics(); if (!info.isEmpty()){ - int infoWidth = metrics.horizontalAdvance(info); - int infoHeight = metrics.height(); + const int infoWidth = metrics.horizontalAdvance(info); + const int infoHeight = (infoWidth/width() + 1) * (metrics.height() + 5); painter.setPen(Qt::NoPen); painter.setBrush(QColor(0, 0, 0, 127)); - infoHeight = (infoWidth/width()+1) * (infoHeight + 5); painter.drawRect((width() - infoWidth) / 2 - 5, 0, infoWidth + 10, infoHeight); painter.setPen(Qt::white); painter.drawText(rect(), Qt::AlignHCenter|Qt::AlignTop|Qt::TextWordWrap, info); } - int helpWidth = metrics.horizontalAdvance(help); - int helpHeight = metrics.height(); + const int helpWidth = metrics.horizontalAdvance(help); + const int helpHeight = (helpWidth/width() + 1) * (metrics.height() + 5); painter.setPen(Qt::NoPen); painter.setBrush(QColor(0, 0, 0, 127)); - helpHeight = (helpWidth/width()+1) * (helpHeight + 5); - painter.drawRect((width() - helpWidth) / 2 - 5, height()-helpHeight, helpWidth + 10, helpHeight); + painter.drawRect((width() - helpWidth) / 2 - 5, height()-helpHeight, helpWidth + 10, + helpHeight); painter.setPen(Qt::white); painter.drawText(rect(), Qt::AlignHCenter|Qt::AlignBottom|Qt::TextWordWrap, help); @@ -184,8 +186,8 @@ void MandelbrotWidget::mouseReleaseEvent(QMouseEvent *event) lastDragPos = QPoint(); const auto pixmapSize = pixmap.deviceIndependentSize().toSize(); - int deltaX = (width() - pixmapSize.width()) / 2 - pixmapOffset.x(); - int deltaY = (height() - pixmapSize.height()) / 2 - pixmapOffset.y(); + const int deltaX = (width() - pixmapSize.width()) / 2 - pixmapOffset.x(); + const int deltaY = (height() - pixmapSize.height()) / 2 - pixmapOffset.y(); scroll(deltaX, deltaY); } } diff --git a/examples/corelib/threads/mandelbrot/mandelbrotwidget.h b/examples/corelib/threads/mandelbrot/mandelbrotwidget.h index 23c3a2bf40..0d0fce56b3 100644 --- a/examples/corelib/threads/mandelbrot/mandelbrotwidget.h +++ b/examples/corelib/threads/mandelbrot/mandelbrotwidget.h @@ -4,16 +4,20 @@ #ifndef MANDELBROTWIDGET_H #define MANDELBROTWIDGET_H -#include <QGestureEvent> +#include "renderthread.h" + +#include <QCoreApplication> #include <QPixmap> #include <QWidget> -#include "renderthread.h" +QT_BEGIN_NAMESPACE +class QGestureEvent; +QT_END_NAMESPACE //! [0] class MandelbrotWidget : public QWidget { - Q_OBJECT + Q_DECLARE_TR_FUNCTIONS(MandelbrotWidget) public: MandelbrotWidget(QWidget *parent = nullptr); @@ -33,11 +37,9 @@ protected: bool event(QEvent *event) override; #endif -private slots: +private: void updatePixmap(const QImage &image, double scaleFactor); void zoom(double zoomFactor); - -private: void scroll(int deltaX, int deltaY); #ifndef QT_NO_GESTURES bool gestureEvent(QGestureEvent *event); diff --git a/examples/corelib/threads/mandelbrot/renderthread.cpp b/examples/corelib/threads/mandelbrot/renderthread.cpp index 9e6c884f91..77a14a6ac1 100644 --- a/examples/corelib/threads/mandelbrot/renderthread.cpp +++ b/examples/corelib/threads/mandelbrot/renderthread.cpp @@ -4,7 +4,6 @@ #include "renderthread.h" #include <QImage> - #include <QElapsedTimer> #include <QTextStream> @@ -70,16 +69,16 @@ void RenderThread::run() //! [3] //! [4] - int halfWidth = resultSize.width() / 2; + const int halfWidth = resultSize.width() / 2; //! [4] //! [5] - int halfHeight = resultSize.height() / 2; + const int halfHeight = resultSize.height() / 2; QImage image(resultSize, QImage::Format_RGB32); image.setDevicePixelRatio(devicePixelRatio); int pass = 0; while (pass < numPasses) { const int MaxIterations = (1 << (2 * pass + 6)) + 32; - const int Limit = 4; + constexpr int Limit = 4; bool allBlack = true; timer.restart(); diff --git a/examples/corelib/threads/mandelbrot/renderthread.h b/examples/corelib/threads/mandelbrot/renderthread.h index 6e509eee94..ecd8ae084d 100644 --- a/examples/corelib/threads/mandelbrot/renderthread.h +++ b/examples/corelib/threads/mandelbrot/renderthread.h @@ -49,7 +49,7 @@ private: bool restart = false; bool abort = false; - enum { ColormapSize = 512 }; + static constexpr int ColormapSize = 512; uint colormap[ColormapSize]; }; //! [0] diff --git a/examples/corelib/threads/queuedcustomtype/CMakeLists.txt b/examples/corelib/threads/queuedcustomtype/CMakeLists.txt index 5a247bacc9..725c268615 100644 --- a/examples/corelib/threads/queuedcustomtype/CMakeLists.txt +++ b/examples/corelib/threads/queuedcustomtype/CMakeLists.txt @@ -1,15 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(queuedcustomtype LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/threads/queuedcustomtype") - find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) qt_standard_project_setup() @@ -33,7 +27,14 @@ target_link_libraries(queuedcustomtype PRIVATE ) install(TARGETS queuedcustomtype - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET queuedcustomtype + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/threads/queuedcustomtype/main.cpp b/examples/corelib/threads/queuedcustomtype/main.cpp index 0cf019990a..0a14cb48e1 100644 --- a/examples/corelib/threads/queuedcustomtype/main.cpp +++ b/examples/corelib/threads/queuedcustomtype/main.cpp @@ -1,12 +1,17 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include <QApplication> -#include <QPainter> -#include <QTime> #include "block.h" #include "window.h" +#include <QApplication> +#include <QBrush> +#include <QImage> +#include <QPainter> +#include <QPen> +#include <QPointF> +#include <QRect> + QImage createImage(int width, int height) { QImage image(width, height, QImage::Format_RGB16); @@ -43,8 +48,8 @@ QImage createImage(int width, int height) int x = 0; int y = 0; - int starWidth = image.width()/3; - int starHeight = image.height()/3; + const int starWidth = image.width()/3; + const int starHeight = image.height()/3; QRect rect(x, y, starWidth, starHeight); diff --git a/examples/corelib/threads/queuedcustomtype/renderthread.cpp b/examples/corelib/threads/queuedcustomtype/renderthread.cpp index d7b2172608..34a439f3fe 100644 --- a/examples/corelib/threads/queuedcustomtype/renderthread.cpp +++ b/examples/corelib/threads/queuedcustomtype/renderthread.cpp @@ -1,22 +1,19 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#include "block.h" #include "renderthread.h" #include <QRandomGenerator> +#include <QRgb> RenderThread::RenderThread(QObject *parent) : QThread(parent) { - m_abort = false; } RenderThread::~RenderThread() { - mutex.lock(); - m_abort = true; - mutex.unlock(); - wait(); } @@ -27,27 +24,26 @@ void RenderThread::processImage(const QImage &image) return; m_image = image; - m_abort = false; start(); } void RenderThread::run() { - int size = qMax(m_image.width()/20, m_image.height()/20); + const int size = qMax(m_image.width()/20, m_image.height()/20); for (int s = size; s > 0; --s) { for (int c = 0; c < 400; ++c) { //![processing the image (start)] - int x1 = qMax(0, QRandomGenerator::global()->bounded(m_image.width()) - s/2); - int x2 = qMin(x1 + s/2 + 1, m_image.width()); - int y1 = qMax(0, QRandomGenerator::global()->bounded(m_image.height()) - s/2); - int y2 = qMin(y1 + s/2 + 1, m_image.height()); + const int x1 = qMax(0, QRandomGenerator::global()->bounded(m_image.width()) - s/2); + const int x2 = qMin(x1 + s/2 + 1, m_image.width()); + const int y1 = qMax(0, QRandomGenerator::global()->bounded(m_image.height()) - s/2); + const int y2 = qMin(y1 + s/2 + 1, m_image.height()); int n = 0; int red = 0; int green = 0; int blue = 0; for (int i = y1; i < y2; ++i) { for (int j = x1; j < x2; ++j) { - QRgb pixel = m_image.pixel(j, i); + const QRgb pixel = m_image.pixel(j, i); red += qRed(pixel); green += qGreen(pixel); blue += qBlue(pixel); @@ -55,20 +51,13 @@ void RenderThread::run() } } //![processing the image (finish)] - Block block(QRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1), + const Block block(QRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1), QColor(red/n, green/n, blue/n)); emit sendBlock(block); - if (m_abort) + if (isInterruptionRequested()) return; msleep(10); } } } //![processing the image (finish)] - -void RenderThread::stopProcess() -{ - mutex.lock(); - m_abort = true; - mutex.unlock(); -} diff --git a/examples/corelib/threads/queuedcustomtype/renderthread.h b/examples/corelib/threads/queuedcustomtype/renderthread.h index afbc70ec75..c3152e4081 100644 --- a/examples/corelib/threads/queuedcustomtype/renderthread.h +++ b/examples/corelib/threads/queuedcustomtype/renderthread.h @@ -5,9 +5,9 @@ #define RENDERTHREAD_H #include <QImage> -#include <QMutex> #include <QThread> -#include "block.h" + +class Block; //! [RenderThread class definition] class RenderThread : public QThread @@ -23,16 +23,11 @@ public: signals: void sendBlock(const Block &block); -public slots: - void stopProcess(); - protected: void run(); private: - bool m_abort; QImage m_image; - QMutex mutex; }; //! [RenderThread class definition] diff --git a/examples/corelib/threads/queuedcustomtype/window.cpp b/examples/corelib/threads/queuedcustomtype/window.cpp index 1adfe7ed38..db12133206 100644 --- a/examples/corelib/threads/queuedcustomtype/window.cpp +++ b/examples/corelib/threads/queuedcustomtype/window.cpp @@ -1,8 +1,17 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#include "block.h" +#include "renderthread.h" #include "window.h" -#include <QtWidgets> + +#include <QFileDialog> +#include <QGuiApplication> +#include <QHBoxLayout> +#include <QImageReader> +#include <QPainter> +#include <QScreen> +#include <QVBoxLayout> //! [Window constructor start] Window::Window(QWidget *parent) @@ -20,7 +29,7 @@ Window::Window(QWidget *parent) connect(loadButton, &QPushButton::clicked, this, QOverload<>::of(&Window::loadImage)); connect(resetButton, &QPushButton::clicked, - thread, &RenderThread::stopProcess); + thread, &RenderThread::requestInterruption); connect(thread, &RenderThread::finished, this, &Window::resetUi); //! [set up widgets and connections] //! [connecting signal with custom type] @@ -51,13 +60,13 @@ void Window::loadImage() if (format.toLower() == format) formats.append(QLatin1String("*.") + QString::fromLatin1(format)); - QString newPath = QFileDialog::getOpenFileName(this, tr("Open Image"), + const QString newPath = QFileDialog::getOpenFileName(this, tr("Open Image"), path, tr("Image files (%1)").arg(formats.join(' '))); if (newPath.isEmpty()) return; - QImage image(newPath); + const QImage image(newPath); if (!image.isNull()) { loadImage(image); path = newPath; @@ -67,7 +76,7 @@ void Window::loadImage() void Window::loadImage(const QImage &image) { QImage useImage; - QRect space = QGuiApplication::primaryScreen()->availableGeometry(); + const QRect space = QGuiApplication::primaryScreen()->availableGeometry(); if (image.width() > 0.75*space.width() || image.height() > 0.75*space.height()) useImage = image.scaled(0.75*space.width(), 0.75*space.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); diff --git a/examples/corelib/threads/queuedcustomtype/window.h b/examples/corelib/threads/queuedcustomtype/window.h index b1ab4f6c24..a4f995696e 100644 --- a/examples/corelib/threads/queuedcustomtype/window.h +++ b/examples/corelib/threads/queuedcustomtype/window.h @@ -4,13 +4,14 @@ #ifndef WINDOW_H #define WINDOW_H +#include <QImage> +#include <QLabel> +#include <QPixmap> +#include <QPushButton> #include <QWidget> -#include "renderthread.h" -QT_BEGIN_NAMESPACE -class QLabel; -class QPushButton; -QT_END_NAMESPACE +class Block; +class RenderThread; //! [Window class definition] class Window : public QWidget diff --git a/examples/corelib/threads/semaphores/CMakeLists.txt b/examples/corelib/threads/semaphores/CMakeLists.txt index bccf6e1e37..a096616e5a 100644 --- a/examples/corelib/threads/semaphores/CMakeLists.txt +++ b/examples/corelib/threads/semaphores/CMakeLists.txt @@ -1,15 +1,13 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(semaphores LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") +if (ANDROID) + message(FATAL_ERROR "This project cannot be built on Android.") endif() -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/threads/semaphores") - find_package(Qt6 REQUIRED COMPONENTS Core) qt_standard_project_setup() @@ -23,7 +21,14 @@ target_link_libraries(semaphores PRIVATE ) install(TARGETS semaphores - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET semaphores + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/threads/semaphores/semaphores.cpp b/examples/corelib/threads/semaphores/semaphores.cpp index 5a49555110..103f331b0c 100644 --- a/examples/corelib/threads/semaphores/semaphores.cpp +++ b/examples/corelib/threads/semaphores/semaphores.cpp @@ -7,9 +7,9 @@ #include <stdlib.h> //! [0] -const int DataSize = 100000; +constexpr int DataSize = 100000; -const int BufferSize = 8192; +constexpr int BufferSize = 8192; char buffer[BufferSize]; QSemaphore freeBytes(BufferSize); diff --git a/examples/corelib/threads/waitconditions/CMakeLists.txt b/examples/corelib/threads/waitconditions/CMakeLists.txt index 45818e2bfc..5fc44a079b 100644 --- a/examples/corelib/threads/waitconditions/CMakeLists.txt +++ b/examples/corelib/threads/waitconditions/CMakeLists.txt @@ -1,15 +1,13 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(waitconditions LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") +if (ANDROID) + message(FATAL_ERROR "This project cannot be built on Android.") endif() -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/threads/waitconditions") - find_package(Qt6 REQUIRED COMPONENTS Core) qt_standard_project_setup() @@ -23,7 +21,14 @@ target_link_libraries(waitconditions PRIVATE ) install(TARGETS waitconditions - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET waitconditions + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/tools/CMakeLists.txt b/examples/corelib/tools/CMakeLists.txt index 0ec145da29..15188b0219 100644 --- a/examples/corelib/tools/CMakeLists.txt +++ b/examples/corelib/tools/CMakeLists.txt @@ -1,9 +1,7 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause if(NOT TARGET Qt6::Widgets) return() endif() qt_internal_add_example(contiguouscache) -qt_internal_add_example(customtype) -qt_internal_add_example(customtypesending) diff --git a/examples/corelib/tools/contiguouscache/CMakeLists.txt b/examples/corelib/tools/contiguouscache/CMakeLists.txt index 1a23099968..2fab4803c1 100644 --- a/examples/corelib/tools/contiguouscache/CMakeLists.txt +++ b/examples/corelib/tools/contiguouscache/CMakeLists.txt @@ -1,15 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(contiguouscache LANGUAGES CXX) -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/tools/contiguouscache") - find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) qt_standard_project_setup() @@ -31,7 +25,14 @@ target_link_libraries(contiguouscache PRIVATE ) install(TARGETS contiguouscache - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +qt_generate_deploy_app_script( + TARGET contiguouscache + OUTPUT_SCRIPT deploy_script + NO_UNSUPPORTED_PLATFORM_ERROR ) +install(SCRIPT ${deploy_script}) diff --git a/examples/corelib/tools/contiguouscache/randomlistmodel.cpp b/examples/corelib/tools/contiguouscache/randomlistmodel.cpp index 3997be2198..4832f0ae24 100644 --- a/examples/corelib/tools/contiguouscache/randomlistmodel.cpp +++ b/examples/corelib/tools/contiguouscache/randomlistmodel.cpp @@ -3,16 +3,12 @@ #include "randomlistmodel.h" #include <QRandomGenerator> -static const int bufferSize(500); -static const int lookAhead(100); -static const int halfLookAhead(lookAhead/2); +static constexpr int bufferSize(500); +static constexpr int lookAhead(100); +static constexpr int halfLookAhead(lookAhead / 2); RandomListModel::RandomListModel(QObject *parent) -: QAbstractListModel(parent), m_rows(bufferSize), m_count(10000) -{ -} - -RandomListModel::~RandomListModel() + : QAbstractListModel(parent), m_rows(bufferSize), m_count(10000) { } @@ -31,14 +27,14 @@ QVariant RandomListModel::data(const QModelIndex &index, int role) const if (row > m_rows.lastIndex()) { if (row - m_rows.lastIndex() > lookAhead) - cacheRows(row-halfLookAhead, qMin(m_count, row+halfLookAhead)); + cacheRows(row - halfLookAhead, qMin(m_count, row + halfLookAhead)); else while (row > m_rows.lastIndex()) - m_rows.append(fetchRow(m_rows.lastIndex()+1)); + m_rows.append(fetchRow(m_rows.lastIndex() + 1)); } else if (row < m_rows.firstIndex()) { if (m_rows.firstIndex() - row > lookAhead) - cacheRows(qMax(0, row-halfLookAhead), row+halfLookAhead); + cacheRows(qMax(0, row - halfLookAhead), row + halfLookAhead); else while (row < m_rows.firstIndex()) - m_rows.prepend(fetchRow(m_rows.firstIndex()-1)); + m_rows.prepend(fetchRow(m_rows.firstIndex() - 1)); } return m_rows.at(row); diff --git a/examples/corelib/tools/contiguouscache/randomlistmodel.h b/examples/corelib/tools/contiguouscache/randomlistmodel.h index 72a6e05f7c..b95acdf3f9 100644 --- a/examples/corelib/tools/contiguouscache/randomlistmodel.h +++ b/examples/corelib/tools/contiguouscache/randomlistmodel.h @@ -13,7 +13,6 @@ class RandomListModel : public QAbstractListModel Q_OBJECT public: RandomListModel(QObject *parent = nullptr); - ~RandomListModel(); int rowCount(const QModelIndex & = QModelIndex()) const override; QVariant data(const QModelIndex &, int) const override; diff --git a/examples/corelib/tools/customtype/CMakeLists.txt b/examples/corelib/tools/customtype/CMakeLists.txt deleted file mode 100644 index 21c9003f56..0000000000 --- a/examples/corelib/tools/customtype/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -cmake_minimum_required(VERSION 3.16) -project(customtype LANGUAGES CXX) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/tools/customtype") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) - -qt_standard_project_setup() - -qt_add_executable(customtype - main.cpp - message.cpp message.h -) - -set_target_properties(customtype PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(customtype PRIVATE - Qt6::Core - Qt6::Gui - Qt6::Widgets -) - -install(TARGETS customtype - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/corelib/tools/customtype/customtype.pro b/examples/corelib/tools/customtype/customtype.pro deleted file mode 100644 index 0e0fe9b1a5..0000000000 --- a/examples/corelib/tools/customtype/customtype.pro +++ /dev/null @@ -1,8 +0,0 @@ -HEADERS = message.h -SOURCES = main.cpp \ - message.cpp -QT += widgets - -# install -target.path = $$[QT_INSTALL_EXAMPLES]/corelib/tools/customtype -INSTALLS += target diff --git a/examples/corelib/tools/customtype/main.cpp b/examples/corelib/tools/customtype/main.cpp deleted file mode 100644 index e37aa0dab8..0000000000 --- a/examples/corelib/tools/customtype/main.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include <QCoreApplication> -#include <QDebug> -#include <QVariant> -#include "message.h" - -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - QStringList headers; - headers << "Subject: Hello World" - << "From: address@example.com"; - QString body = "This is a test.\r\n"; - -//! [printing a custom type] - Message message(body, headers); - qDebug() << "Original:" << message; -//! [printing a custom type] - -//! [storing a custom value] - QVariant stored; - stored.setValue(message); -//! [storing a custom value] - - qDebug() << "Stored:" << stored; - -//! [retrieving a custom value] - Message retrieved = qvariant_cast<Message>(stored); - qDebug() << "Retrieved:" << retrieved; - retrieved = qvariant_cast<Message>(stored); - qDebug() << "Retrieved:" << retrieved; -//! [retrieving a custom value] - - return 0; -} diff --git a/examples/corelib/tools/customtype/message.cpp b/examples/corelib/tools/customtype/message.cpp deleted file mode 100644 index eb0e4b8d7a..0000000000 --- a/examples/corelib/tools/customtype/message.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "message.h" - -#include <QDebug> - -Message::Message(const QString &body, const QStringList &headers) - : m_body(body), m_headers(headers) -{ -} - -//! [custom type streaming operator] -QDebug operator<<(QDebug dbg, const Message &message) -{ - QDebugStateSaver saver(dbg); - QList<QStringView> pieces = message.body().split(u"\r\n", Qt::SkipEmptyParts); - if (pieces.isEmpty()) - dbg.nospace() << "Message()"; - else if (pieces.size() == 1) - dbg.nospace() << "Message(" << pieces.first() << ")"; - else - dbg.nospace() << "Message(" << pieces.first() << " ...)"; - return dbg; -} -//! [custom type streaming operator] - -//! [getter functions] -QStringView Message::body() const -{ - return m_body; -} - -QStringList Message::headers() const -{ - return m_headers; -} -//! [getter functions] diff --git a/examples/corelib/tools/customtype/message.h b/examples/corelib/tools/customtype/message.h deleted file mode 100644 index d359f79161..0000000000 --- a/examples/corelib/tools/customtype/message.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef MESSAGE_H -#define MESSAGE_H - -#include <QMetaType> -#include <QStringList> - -//! [custom type definition] -class Message -{ -public: - Message() = default; - ~Message() = default; - Message(const Message &) = default; - Message &operator=(const Message &) = default; - - Message(const QString &body, const QStringList &headers); - - QStringView body() const; - QStringList headers() const; - -private: - QString m_body; - QStringList m_headers; -}; -//! [custom type definition] - -//! [custom type meta-type declaration] -Q_DECLARE_METATYPE(Message); -//! [custom type meta-type declaration] - -//! [custom type streaming operator] -QDebug operator<<(QDebug dbg, const Message &message); -//! [custom type streaming operator] - -#endif diff --git a/examples/corelib/tools/customtypesending/CMakeLists.txt b/examples/corelib/tools/customtypesending/CMakeLists.txt deleted file mode 100644 index a993d81160..0000000000 --- a/examples/corelib/tools/customtypesending/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -cmake_minimum_required(VERSION 3.16) -project(customtypesending LANGUAGES CXX) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/tools/customtypesending") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) - -qt_standard_project_setup() - -qt_add_executable(customtypesending - main.cpp - message.cpp message.h - window.cpp window.h -) - -set_target_properties(customtypesending PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(customtypesending PRIVATE - Qt6::Core - Qt6::Gui - Qt6::Widgets -) - -install(TARGETS customtypesending - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/corelib/tools/customtypesending/customtypesending.pro b/examples/corelib/tools/customtypesending/customtypesending.pro deleted file mode 100644 index da351ce828..0000000000 --- a/examples/corelib/tools/customtypesending/customtypesending.pro +++ /dev/null @@ -1,10 +0,0 @@ -HEADERS = message.h \ - window.h -SOURCES = main.cpp \ - message.cpp \ - window.cpp -QT += widgets - -# install -target.path = $$[QT_INSTALL_EXAMPLES]/corelib/tools/customtypesending -INSTALLS += target diff --git a/examples/corelib/tools/customtypesending/main.cpp b/examples/corelib/tools/customtypesending/main.cpp deleted file mode 100644 index 94f41d73a8..0000000000 --- a/examples/corelib/tools/customtypesending/main.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include <QApplication> -#include "message.h" -#include "window.h" - -//! [main function] -int main(int argc, char *argv[]) -{ - QApplication app(argc, argv); - - QStringList headers; - headers << "Subject: Hello World" - << "From: address@example.com"; - QString body = "This is a test.\r\n"; - Message message(body, headers); - - Window window1; - window1.setMessage(message); - - Window window2; - QObject::connect(&window1, &Window::messageSent, - &window2, &Window::setMessage); - QObject::connect(&window2, &Window::messageSent, - &window1, &Window::setMessage); - window1.show(); - window2.show(); - return app.exec(); -} -//! [main function] diff --git a/examples/corelib/tools/customtypesending/message.cpp b/examples/corelib/tools/customtypesending/message.cpp deleted file mode 100644 index dfb5c5359a..0000000000 --- a/examples/corelib/tools/customtypesending/message.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "message.h" - -Message::Message(const QString &body, const QStringList &headers) - : m_body(body), m_headers(headers) -{ -} - -QString Message::body() const -{ - return m_body; -} - -QStringList Message::headers() const -{ - return m_headers; -} diff --git a/examples/corelib/tools/customtypesending/message.h b/examples/corelib/tools/customtypesending/message.h deleted file mode 100644 index 2df50115c4..0000000000 --- a/examples/corelib/tools/customtypesending/message.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef MESSAGE_H -#define MESSAGE_H - -#include <QMetaType> -#include <QStringList> - -//! [custom type definition] -class Message -{ -public: - Message() = default; - ~Message() = default; - Message(const Message &) = default; - Message &operator=(const Message &) = default; - - Message(const QString &body, const QStringList &headers); - - QString body() const; - QStringList headers() const; - -private: - QString m_body; - QStringList m_headers; -}; -//! [custom type definition] - -//! [custom type meta-type declaration] -Q_DECLARE_METATYPE(Message); -//! [custom type meta-type declaration] - -#endif diff --git a/examples/corelib/tools/customtypesending/window.cpp b/examples/corelib/tools/customtypesending/window.cpp deleted file mode 100644 index f294af3ec2..0000000000 --- a/examples/corelib/tools/customtypesending/window.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include <QtWidgets> -#include "window.h" - -//! [Window constructor] -Window::Window(QWidget *parent) - : QWidget(parent), editor(new QTextEdit(this)) -{ - QPushButton *sendButton = new QPushButton(tr("&Send message")); - - connect(sendButton, &QPushButton::clicked, - this, &Window::sendMessage); - - QHBoxLayout *buttonLayout = new QHBoxLayout; - buttonLayout->addStretch(); - buttonLayout->addWidget(sendButton); - buttonLayout->addStretch(); - - QVBoxLayout *layout = new QVBoxLayout(this); - layout->addWidget(editor); - layout->addLayout(buttonLayout); - - setWindowTitle(tr("Custom Type Sending")); -} -//! [Window constructor] - -//! [sending a message] -void Window::sendMessage() -{ - thisMessage = Message(editor->toPlainText(), thisMessage.headers()); - emit messageSent(thisMessage); -} -//! [sending a message] - -//! [receiving a message] -void Window::setMessage(const Message &message) -{ - thisMessage = message; - editor->setPlainText(thisMessage.body()); -} -//! [receiving a message] diff --git a/examples/corelib/tools/customtypesending/window.h b/examples/corelib/tools/customtypesending/window.h deleted file mode 100644 index a3a318e382..0000000000 --- a/examples/corelib/tools/customtypesending/window.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef WINDOW_H -#define WINDOW_H - -#include <QWidget> -#include "message.h" - -QT_FORWARD_DECLARE_CLASS(QTextEdit) - -//! [Window class definition] -class Window : public QWidget -{ - Q_OBJECT - -public: - Window(QWidget *parent = nullptr); - -signals: - void messageSent(const Message &message); - -public slots: - void setMessage(const Message &message); - -private slots: - void sendMessage(); - -private: - Message thisMessage; - QTextEdit *editor; -}; -//! [Window class definition] - -#endif diff --git a/examples/corelib/tools/doc/src/contiguouscache.qdoc b/examples/corelib/tools/doc/src/contiguouscache.qdoc index bb8616818f..9fc572927b 100644 --- a/examples/corelib/tools/doc/src/contiguouscache.qdoc +++ b/examples/corelib/tools/doc/src/contiguouscache.qdoc @@ -4,6 +4,7 @@ /*! \example tools/contiguouscache \title Contiguous Cache Example + \examplecategory {Data Processing & I/O} \brief The Contiguous Cache example shows how to use QContiguousCache to manage memory usage for very large models. In some environments memory is limited and, even when it diff --git a/examples/corelib/tools/doc/src/customtype.qdoc b/examples/corelib/tools/doc/src/customtype.qdoc deleted file mode 100644 index 55d29609ce..0000000000 --- a/examples/corelib/tools/doc/src/customtype.qdoc +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example tools/customtype - \title Custom Type Example - - \brief The Custom Type example shows how to integrate a custom type into Qt's - meta-object system. - - Contents: - - \tableofcontents - - \section1 Overview - - Qt provides a range of standard value types that are used to provide - rich and meaningful APIs. These types are integrated with the meta-object - system, enabling them to be stored in QVariant objects, written out in - debugging information and sent between components in signal-slot - communication. - - Custom types can also be integrated with the meta-object system as long as - they are written to conform to some simple guidelines. In this example, we - introduce a simple \c Message class, we describe how we make it work with - QVariant, and we show how it can be extended to generate a printable - representation of itself for use in debugging output. - - \section1 The Message Class Definition - - The \c Message class is a simple value class that contains two pieces - of information (a QString and a QStringList), each of which can be read - using trivial getter functions: - - \snippet tools/customtype/message.h custom type definition - - The default constructor, copy constructor and destructor are - all required, and must be public, if the type is to be integrated into the - meta-object system. Other than this, we are free to implement whatever we - need to make the type do what we want, so we also include a constructor - that lets us set the type's data members. - - To enable the type to be used with QVariant, we declare it using the - Q_DECLARE_METATYPE() macro: - - \snippet tools/customtype/message.h custom type meta-type declaration - - We do not need to write any additional code to accompany this macro. - - To allow us to see a readable description of each \c Message object when it - is sent to the debug output stream, we define a streaming operator: - - \snippet tools/customtype/message.h custom type streaming operator - - This facility is useful if you need to insert tracing statements in your - code for debugging purposes. - - \section1 The Message Class Implementation - - The streaming operator is implemented in the following way: - - \snippet tools/customtype/message.cpp custom type streaming operator - - Here, we want to represent each value depending on how many lines are stored - in the message body. We stream text to the QDebug object passed to the - operator and return the QDebug object obtained from its maybeSpace() member - function; this is described in more detail in the - \l{Creating Custom Qt Types#Making the Type Printable}{Creating Custom Qt Types} - document. - - We include the code for the getter functions for completeness: - - \snippet tools/customtype/message.cpp getter functions - - With the type fully defined, implemented, and integrated with the - meta-object system, we can now use it. - - \section1 Using the Message - - In the example's \c{main()} function, we show how a \c Message object can - be printed to the console by sending it to the debug stream: - - \snippet tools/customtype/main.cpp printing a custom type - - You can use the type with QVariant in exactly the same way as you would - use standard Qt value types. Here's how to store a value using the - QVariant::setValue() function: - - \snippet tools/customtype/main.cpp storing a custom value - - Alternatively, the QVariant::fromValue() function can be used if - you are using a compiler without support for member template - functions. - - The value can be retrieved using the QVariant::value() member template - function: - - \snippet tools/customtype/main.cpp retrieving a custom value - - \section1 Further Reading - - The custom \c Message type can also be used with direct signal-slot - connections. - - To register a custom type for use with queued signals and slots, such as - those used in cross-thread communication, see the - \l{Queued Custom Type Example}. - - More information on using custom types with Qt can be found in the - \l{Creating Custom Qt Types} document. -*/ diff --git a/examples/corelib/tools/tools.pro b/examples/corelib/tools/tools.pro index 6fb4a1214f..618628d274 100644 --- a/examples/corelib/tools/tools.pro +++ b/examples/corelib/tools/tools.pro @@ -1,6 +1,4 @@ requires(qtHaveModule(widgets)) TEMPLATE = subdirs -SUBDIRS = contiguouscache \ - customtype \ - customtypesending +SUBDIRS = contiguouscache |