diff options
Diffstat (limited to 'examples')
1569 files changed, 10118 insertions, 54708 deletions
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 3e75b65f6e..e7ed9033f4 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,39 +1,40 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause qt_examples_build_begin(EXTERNAL_BUILD) +add_compile_definitions(QT_NO_CONTEXTLESS_CONNECT) + add_subdirectory(corelib) add_subdirectory(embedded) -add_subdirectory(qpa) -if(TARGET Qt::DBus) +if(TARGET Qt6::DBus) add_subdirectory(dbus) endif() -if(TARGET Qt::Network) +if(TARGET Qt6::Network) add_subdirectory(network) endif() -if(TARGET Qt::Test) +if(TARGET Qt6::Test) add_subdirectory(qtestlib) endif() -if(TARGET Qt::Concurrent) +if(TARGET Qt6::Concurrent) add_subdirectory(qtconcurrent) endif() -if(TARGET Qt::Sql) +if(TARGET Qt6::Sql) add_subdirectory(sql) endif() -if(TARGET Qt::Widgets) +if(TARGET Qt6::Widgets) add_subdirectory(widgets) endif() -if(TARGET Qt::Xml) +if(TARGET Qt6::Xml) add_subdirectory(xml) endif() -if(TARGET Qt::Gui) +if(TARGET Qt6::Gui) add_subdirectory(gui) endif() -if(QT_FEATURE_opengl AND TARGET Qt::Gui) +if(QT_FEATURE_opengl AND TARGET Qt6::Gui) add_subdirectory(opengl) endif() -if(QT_FEATURE_vulkan AND TARGET Qt::Gui) +if(QT_FEATURE_vulkan AND TARGET Qt6::Gui) add_subdirectory(vulkan) endif() diff --git a/examples/corelib/CMakeLists.txt b/examples/corelib/CMakeLists.txt index e176d9aede..7b0aad9d82 100644 --- a/examples/corelib/CMakeLists.txt +++ b/examples/corelib/CMakeLists.txt @@ -1,7 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause -add_subdirectory(bindableproperties) add_subdirectory(ipc) add_subdirectory(mimetypes) add_subdirectory(serialization) @@ -10,3 +9,6 @@ add_subdirectory(platform) if(QT_FEATURE_thread) add_subdirectory(threads) endif() +if(QT_FEATURE_widgets) + add_subdirectory(bindableproperties) +endif() diff --git a/examples/corelib/bindableproperties/CMakeLists.txt b/examples/corelib/bindableproperties/CMakeLists.txt index c6d9076fd8..9e23bf9b65 100644 --- a/examples/corelib/bindableproperties/CMakeLists.txt +++ b/examples/corelib/bindableproperties/CMakeLists.txt @@ -1,2 +1,25 @@ -qt_internal_add_example(bindablesubscription) -qt_internal_add_example(subscription) +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(bindableproperties LANGUAGES CXX) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/bindableproperties") + +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 + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/corelib/bindableproperties/bindablesubscription/CMakeLists.txt b/examples/corelib/bindableproperties/bindablesubscription/CMakeLists.txt index bfc794b4b5..d7967e6763 100644 --- a/examples/corelib/bindableproperties/bindablesubscription/CMakeLists.txt +++ b/examples/corelib/bindableproperties/bindablesubscription/CMakeLists.txt @@ -1,51 +1,15 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -cmake_minimum_required(VERSION 3.16) -project(bindablesubscription LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) - -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) +# SPDX-License-Identifier: 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 PUBLIC - Qt::Core - Qt::Gui - Qt::Widgets +target_link_libraries(bindablesubscription PRIVATE + 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 1b65c87f5f..32f4194635 100644 --- a/examples/corelib/bindableproperties/bindablesubscription/bindablesubscription.cpp +++ b/examples/corelib/bindableproperties/bindablesubscription/bindablesubscription.cpp @@ -10,10 +10,11 @@ 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::None && m_user->age() > 12; + return m_user->country() != BindableUser::Country::AnyCountry && m_user->age() > 12; }); } @@ -38,14 +39,13 @@ double BindableSubscription::calculateDiscount() const case Yearly: return 0.6; } - Q_ASSERT(false); - return -1; + Q_UNREACHABLE_RETURN(-1); } int BindableSubscription::basePrice() const { - if (m_user->country() == BindableUser::None) + if (m_user->country() == BindableUser::Country::AnyCountry) return 0; - return (m_user->country() == BindableUser::Norway) ? 100 : 80; + return (m_user->country() == BindableUser::Country::Norway) ? 100 : 80; } diff --git a/examples/corelib/bindableproperties/bindablesubscription/bindablesubscription.h b/examples/corelib/bindableproperties/bindablesubscription/bindablesubscription.h index b135618bec..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; @@ -14,7 +14,7 @@ class BindableUser; class BindableSubscription { public: - enum Duration { Monthly = 1, Quarterly = 4, Yearly = 12 }; + enum Duration { Monthly = 1, Quarterly = 3, Yearly = 12 }; BindableSubscription(BindableUser *user); BindableSubscription(const BindableSubscription &) = delete; diff --git a/examples/corelib/bindableproperties/bindablesubscription/bindableuser.h b/examples/corelib/bindableproperties/bindablesubscription/bindableuser.h index b7e44d52ec..6bb9bcdcb5 100644 --- a/examples/corelib/bindableproperties/bindablesubscription/bindableuser.h +++ b/examples/corelib/bindableproperties/bindablesubscription/bindableuser.h @@ -4,6 +4,8 @@ #ifndef BINDABLEUSER_H #define BINDABLEUSER_H +#include <QBindable> +#include <QLocale> #include <QProperty> //! [bindable-user-class] @@ -11,13 +13,9 @@ class BindableUser { public: - enum Country { - None, - Finland, - Germany, - Norway, - }; + using Country = QLocale::Territory; +public: BindableUser() = default; BindableUser(const BindableUser &) = delete; @@ -30,7 +28,7 @@ public: QBindable<int> bindableAge() { return &m_age; } private: - QProperty<Country> m_country { None }; + QProperty<Country> m_country { QLocale::AnyTerritory }; QProperty<int> m_age { 0 }; }; diff --git a/examples/corelib/bindableproperties/bindablesubscription/main.cpp b/examples/corelib/bindableproperties/bindablesubscription/main.cpp index c861f8af4c..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,46 +23,48 @@ 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, [&] { - user.setCountry(BindableUser::Germany); + 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, [&] { - user.setCountry(BindableUser::Finland); + 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, [&] { - user.setCountry(BindableUser::Norway); + 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] auto priceChangeHandler = subscription.bindablePrice().subscribe([&] { - priceDisplay->setText(QString::number(subscription.price())); + QLocale lc{QLocale::AnyLanguage, user.country()}; + priceDisplay->setText(lc.toCurrencyString(subscription.price() / subscription.duration())); }); auto priceValidHandler = subscription.bindableIsValid().subscribe([&] { diff --git a/examples/corelib/bindableproperties/doc/src/bindableproperties.qdoc b/examples/corelib/bindableproperties/doc/src/bindableproperties.qdoc index 644693acb8..e63662dfbb 100644 --- a/examples/corelib/bindableproperties/doc/src/bindableproperties.qdoc +++ b/examples/corelib/bindableproperties/doc/src/bindableproperties.qdoc @@ -15,7 +15,7 @@ \image bindable_properties_example.png - \section1 Modelling Subscription System with Signal/Slot Approach + \section1 Modeling Subscription System with Signal/Slot Approach Let's first consider the usual pre-Qt 6 implementation. To model the subscription service the \c Subscription class is used: @@ -48,7 +48,7 @@ \note Both methods need to check if the data is actually changed and only then emit the signals. \c setDuration() also needs to recalculate - the price, when the duration has changed. + the price when the duration has changed. The \c Subscription is not valid unless the user has a valid country and age, so the validity is updated in the following way: @@ -63,25 +63,25 @@ \snippet bindableproperties/subscription/user.cpp user-setters In the \c main() function we initialize instances of \c User and - \c Subsrcription: + \c Subscription: \snippet bindableproperties/subscription/main.cpp init - And do the proper signal-slot connections, to update the \c user and - \c subsrcription data when UI elements change. That is straightforward, + And do the proper signal-slot connections to update the \c user and + \c subscription data when UI elements change. That is straightforward, so we will skip this part. - Next, we connect to \c Subscription::priceChanged(), to update the price + Next, we connect to \c Subscription::priceChanged() to update the price in the UI when the price changes. \snippet bindableproperties/subscription/main.cpp connect-price-changed - We also connect to \c Subscription::isValidChanged(), to disable the price + We also connect to \c Subscription::isValidChanged() to disable the price display if the subscription isn't valid. \snippet bindableproperties/subscription/main.cpp connect-validity-changed - Because the subsrcription price and validity also depend on the user's + Because the subscription price and validity also depend on the user's country and age, we also need to connect to the \c User::countryChanged() and \c User::ageChanged() signals and update \c subscription accordingly. @@ -90,12 +90,12 @@ This works, but there are some problems: \list - \li There's a lot of boilerplate code for the signal-slot connections, - to be able to react to changes to \c user or \c subscription. If any of - the dependencies of the price changes, we need to remember to emit the - corresponding notifier signals, to recalculate the price and update it in + \li There's a lot of boilerplate code for the signal-slot connections + in order to properly track changes to both \c user and \c subscription. + If any of the dependencies of the price changes, we need to remember to emit the + corresponding notifier signals, recalculate the price, and update it in the UI. - \li If more dependencies for price calculation are added in future, we'll + \li If more dependencies for price calculation are added in the future, we'll need to add more signal-slot connections and make sure all the dependencies are properly updated whenever any of them changes. The overall complexity will grow, and the code will become harder to maintain. @@ -109,7 +109,7 @@ Now let's see how the \l {Qt Bindable Properties} can help to solve the same problem. First, let's have a look at the \c BindableSubscription class, - which is similar to the \c Subscription class, but is implemented using the + which is similar to the \c Subscription class, but is implemented using bindable properties: \snippet bindableproperties/bindablesubscription/bindablesubscription.h bindable-subscription-class @@ -156,7 +156,7 @@ changes the value. The subscriptions will stay alive as long as the corresponding handlers are alive. - Also note that the copy constructors of both \c BindableSubsrciption and + Also note that the copy constructors of both \c BindableSubscription and \c BindableUser are disabled, since it's not defined what should happen with their bindings when copying. @@ -166,7 +166,7 @@ \list \li The boilerplate code for the signal-slot connections is removed, the dependencies are now tracked automatically. - \li The code is easier to maintain. Adding more dependencies in future + \li The code is easier to maintain. Adding more dependencies in the future will only require adding the corresponding bindable properties and setting the binding expressions that reflect the relationships between each other. \li The \c Subscription and \c User classes don't depend on the metaobject diff --git a/examples/corelib/bindableproperties/shared/CMakeLists.txt b/examples/corelib/bindableproperties/shared/CMakeLists.txt new file mode 100644 index 0000000000..dee3ecc136 --- /dev/null +++ b/examples/corelib/bindableproperties/shared/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: 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 b4d5923b43..15e41ad77e 100644 --- a/examples/corelib/bindableproperties/subscription/CMakeLists.txt +++ b/examples/corelib/bindableproperties/subscription/CMakeLists.txt @@ -1,51 +1,12 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -cmake_minimum_required(VERSION 3.16) -project(subscription LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) - -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) +# SPDX-License-Identifier: 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 PUBLIC - Qt::Core - Qt::Gui - Qt::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}" +target_link_libraries(subscription PRIVATE + bindableproperties_shared ) diff --git a/examples/corelib/bindableproperties/subscription/main.cpp b/examples/corelib/bindableproperties/subscription/main.cpp index 8b3f37ab7c..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,64 +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::Germany); + 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::Finland); + 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::Norway); + 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, [&] { - priceDisplay->setText(QString::number(subscription.price())); + 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 16bb6cfdd6..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(); } @@ -57,10 +57,10 @@ double Subscription::calculateDiscount() const int Subscription::basePrice() const { - if (m_user->country() == User::None) + if (m_user->country() == User::Country::AnyTerritory) return 0; - return (m_user->country() == User::Norway) ? 100 : 80; + return (m_user->country() == User::Country::Norway) ? 100 : 80; } //! [calculate-base-price] @@ -70,7 +70,7 @@ int Subscription::basePrice() const void Subscription::updateValidity() { bool isValid = m_isValid; - m_isValid = m_user->country() != User::None && m_user->age() > 12; + m_isValid = m_user->country() != User::Country::AnyTerritory && m_user->age() > 12; if (m_isValid != isValid) emit isValidChanged(); diff --git a/examples/corelib/bindableproperties/subscription/subscription.h b/examples/corelib/bindableproperties/subscription/subscription.h index 5fa705772b..8f0d34e948 100644 --- a/examples/corelib/bindableproperties/subscription/subscription.h +++ b/examples/corelib/bindableproperties/subscription/subscription.h @@ -15,7 +15,7 @@ class Subscription : public QObject { Q_OBJECT public: - enum Duration { Monthly = 1, Quarterly = 4, Yearly = 12 }; + enum Duration { Monthly = 1, Quarterly = 3, Yearly = 12 }; Subscription(User *user); diff --git a/examples/corelib/bindableproperties/subscription/user.h b/examples/corelib/bindableproperties/subscription/user.h index 1e16f9e901..dd32a4fe23 100644 --- a/examples/corelib/bindableproperties/subscription/user.h +++ b/examples/corelib/bindableproperties/subscription/user.h @@ -4,6 +4,7 @@ #ifndef USER_H #define USER_H +#include <QLocale> #include <QObject> //! [user-class] @@ -13,13 +14,9 @@ class User : public QObject Q_OBJECT public: - enum Country { - None, - Finland, - Germany, - Norway, - }; + using Country = QLocale::Territory; +public: Country country() const { return m_country; } void setCountry(Country country); @@ -31,8 +28,8 @@ signals: void ageChanged(); private: - Country m_country = Country::None; - int m_age = 0; + Country m_country { QLocale::AnyTerritory }; + int m_age { 0 }; }; //! [user-class] diff --git a/examples/corelib/ipc/CMakeLists.txt b/examples/corelib/ipc/CMakeLists.txt index 4b799fc429..d1cfc7bc1b 100644 --- a/examples/corelib/ipc/CMakeLists.txt +++ b/examples/corelib/ipc/CMakeLists.txt @@ -1,13 +1,13 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause -if(NOT TARGET Qt::Widgets) +if(NOT TARGET Qt6::Widgets) return() endif() if(QT_FEATURE_sharedmemory) qt_internal_add_example(sharedmemory) endif() -if(QT_FEATURE_localserver AND TARGET Qt::Network) +if(QT_FEATURE_localserver AND TARGET Qt6::Network) qt_internal_add_example(localfortuneserver) qt_internal_add_example(localfortuneclient) endif() diff --git a/examples/corelib/ipc/doc/src/sharedmemory.qdoc b/examples/corelib/ipc/doc/src/sharedmemory.qdoc index 7ea4ffb25d..b06849cee3 100644 --- a/examples/corelib/ipc/doc/src/sharedmemory.qdoc +++ b/examples/corelib/ipc/doc/src/sharedmemory.qdoc @@ -3,10 +3,10 @@ /*! \example ipc/sharedmemory - \title Shared Memory Example + \title 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 5f0cdc288a..b3337e1f46 100644 --- a/examples/corelib/ipc/localfortuneclient/CMakeLists.txt +++ b/examples/corelib/ipc/localfortuneclient/CMakeLists.txt @@ -1,11 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(localfortuneclient LANGUAGES CXX) -set(CMAKE_AUTOMOC ON) - if(NOT DEFINED INSTALL_EXAMPLESDIR) set(INSTALL_EXAMPLESDIR "examples") endif() @@ -14,6 +12,8 @@ set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/ipc/localfortuneclient") find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets) +qt_standard_project_setup() + qt_add_executable(localfortuneclient client.cpp client.h main.cpp @@ -24,11 +24,11 @@ set_target_properties(localfortuneclient PROPERTIES MACOSX_BUNDLE TRUE ) -target_link_libraries(localfortuneclient PUBLIC - Qt::Core - Qt::Gui - Qt::Network - Qt::Widgets +target_link_libraries(localfortuneclient PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Network + Qt6::Widgets ) install(TARGETS localfortuneclient diff --git a/examples/corelib/ipc/localfortuneclient/client.cpp b/examples/corelib/ipc/localfortuneclient/client.cpp index 31f8cf475b..dcc7e0f0c2 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."))), 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 8119e066ea..411fc04eb5 100644 --- a/examples/corelib/ipc/localfortuneserver/CMakeLists.txt +++ b/examples/corelib/ipc/localfortuneserver/CMakeLists.txt @@ -1,11 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(localfortuneserver LANGUAGES CXX) -set(CMAKE_AUTOMOC ON) - if(NOT DEFINED INSTALL_EXAMPLESDIR) set(INSTALL_EXAMPLESDIR "examples") endif() @@ -14,6 +12,8 @@ set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/ipc/localfortuneserver") find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets) +qt_standard_project_setup() + qt_add_executable(localfortuneserver main.cpp server.cpp server.h @@ -24,11 +24,11 @@ set_target_properties(localfortuneserver PROPERTIES MACOSX_BUNDLE TRUE ) -target_link_libraries(localfortuneserver PUBLIC - Qt::Core - Qt::Gui - Qt::Network - Qt::Widgets +target_link_libraries(localfortuneserver PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Network + Qt6::Widgets ) install(TARGETS localfortuneserver diff --git a/examples/corelib/ipc/sharedmemory/CMakeLists.txt b/examples/corelib/ipc/sharedmemory/CMakeLists.txt index b75a350beb..21f5ff339b 100644 --- a/examples/corelib/ipc/sharedmemory/CMakeLists.txt +++ b/examples/corelib/ipc/sharedmemory/CMakeLists.txt @@ -1,12 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(sharedmemory LANGUAGES CXX) -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) - if(NOT DEFINED INSTALL_EXAMPLESDIR) set(INSTALL_EXAMPLESDIR "examples") endif() @@ -15,6 +12,8 @@ set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/ipc/sharedmemory") find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) +qt_standard_project_setup() + qt_add_executable(sharedmemory dialog.cpp dialog.h dialog.ui main.cpp @@ -25,10 +24,10 @@ set_target_properties(sharedmemory PROPERTIES MACOSX_BUNDLE TRUE ) -target_link_libraries(sharedmemory PUBLIC - Qt::Core - Qt::Gui - Qt::Widgets +target_link_libraries(sharedmemory PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Widgets ) install(TARGETS sharedmemory diff --git a/examples/corelib/ipc/sharedmemory/dialog.cpp b/examples/corelib/ipc/sharedmemory/dialog.cpp index 67e4f012a3..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, @@ -89,8 +94,13 @@ void Dialog::loadFromFile() int size = buffer.size(); if (!sharedMemory.create(size)) { - ui.label->setText(tr("Unable to create shared memory segment.")); - return; + if (sharedMemory.error() == QSharedMemory::AlreadyExists) { + sharedMemory.attach(); + } else { + ui.label->setText(tr("Unable to create or attach to shared memory segment: %1") + .arg(sharedMemory.errorString())); + return; + } } sharedMemory.lock(); char *to = (char*)sharedMemory.data(); 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 fbdf742435..9ba3873bf4 100644 --- a/examples/corelib/mimetypes/CMakeLists.txt +++ b/examples/corelib/mimetypes/CMakeLists.txt @@ -1,6 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause -if(TARGET Qt::Widgets) +if(TARGET Qt6::Widgets) qt_internal_add_example(mimetypebrowser) endif() 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 0df4aa7000..c03ccae085 100644 --- a/examples/corelib/mimetypes/mimetypebrowser/CMakeLists.txt +++ b/examples/corelib/mimetypes/mimetypebrowser/CMakeLists.txt @@ -1,11 +1,9 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(mimetypebrowser LANGUAGES CXX) -set(CMAKE_AUTOMOC ON) - if(NOT DEFINED INSTALL_EXAMPLESDIR) set(INSTALL_EXAMPLESDIR "examples") endif() @@ -14,6 +12,8 @@ set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/mimetypes/mimetypebrowser find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) +qt_standard_project_setup() + qt_add_executable(mimetypebrowser main.cpp mainwindow.cpp mainwindow.h @@ -25,10 +25,10 @@ set_target_properties(mimetypebrowser PROPERTIES MACOSX_BUNDLE FALSE ) -target_link_libraries(mimetypebrowser PUBLIC - Qt::Core - Qt::Gui - Qt::Widgets +target_link_libraries(mimetypebrowser PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Widgets ) install(TARGETS mimetypebrowser 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/platform/CMakeLists.txt b/examples/corelib/platform/CMakeLists.txt index f5b77c62b3..b86b1f0f9e 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: LicenseRef-Qt-Commercial OR BSD-3-Clause +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: 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 f6187f1d4c..e5271edd79 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: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(androidnotifier LANGUAGES CXX) @@ -8,14 +8,14 @@ if(NOT ANDROID) message(FATAL_ERROR "Example only works on Android") endif() -set(CMAKE_AUTOMOC ON) - 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 @@ -23,6 +23,8 @@ qt_add_executable(androidnotifier main.cpp notificationclient.cpp notificationclient.h + android/src/org/qtproject/example/androidnotifier/NotificationClient.java + android/AndroidManifest.xml ) target_link_libraries(androidnotifier PRIVATE diff --git a/examples/corelib/platform/androidnotifier/android/AndroidManifest.xml b/examples/corelib/platform/androidnotifier/android/AndroidManifest.xml index 4850a8e8e6..b2d9cc7680 100644 --- a/examples/corelib/platform/androidnotifier/android/AndroidManifest.xml +++ b/examples/corelib/platform/androidnotifier/android/AndroidManifest.xml @@ -23,13 +23,16 @@ 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"/> diff --git a/examples/corelib/platform/androidnotifier/doc/src/androidnotifier-example.qdoc b/examples/corelib/platform/androidnotifier/doc/src/androidnotifier-example.qdoc index 8d2a9a47e4..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 @@ -47,7 +49,7 @@ The call to the Java meethod use \l QJniObject which relies on the Java Native Interface (JNI) APIs to communicate with Java. Also, in the previous snippet, - we are passing the app's context object which the the static Java code can use + we are passing the app's context object, which the static Java code can use to tap into the app's specific properties and APIs. To make sure our smiley buttons do what they are supposed to, we add the diff --git a/examples/corelib/serialization/CMakeLists.txt b/examples/corelib/serialization/CMakeLists.txt index 6778e4b460..8f6d57eba4 100644 --- a/examples/corelib/serialization/CMakeLists.txt +++ b/examples/corelib/serialization/CMakeLists.txt @@ -1,6 +1,11 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause -qt_internal_add_example(cbordump) -qt_internal_add_example(convert) -qt_internal_add_example(savegame) +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 91e88875c1..77e4a601a3 100644 --- a/examples/corelib/serialization/cbordump/CMakeLists.txt +++ b/examples/corelib/serialization/cbordump/CMakeLists.txt @@ -1,10 +1,12 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(cbordump LANGUAGES CXX) -set(CMAKE_AUTOMOC ON) +if (ANDROID) + message(FATAL_ERROR "This project cannot be built on Android.") +endif() if(NOT DEFINED INSTALL_EXAMPLESDIR) set(INSTALL_EXAMPLESDIR "examples") @@ -14,12 +16,14 @@ set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/cbordump") find_package(Qt6 REQUIRED COMPONENTS Core) +qt_standard_project_setup() + qt_add_executable(cbordump main.cpp ) -target_link_libraries(cbordump PUBLIC - Qt::Core +target_link_libraries(cbordump PRIVATE + Qt6::Core ) install(TARGETS cbordump 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 differnew file mode 100644 index 0000000000..d5ff1da0bf --- /dev/null +++ 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 new file mode 100644 index 0000000000..a4dc01116f --- /dev/null +++ b/examples/corelib/serialization/cbordump/doc/src/cbordump.qdoc @@ -0,0 +1,55 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \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. + + This example shows how to use the QCborStreamReader class directly to parse + CBOR content. The \c cbordump program reads content in CBOR format from + files or standard input and dumps the decoded content to stdout in a + human-readable format. It can output in CBOR diagnostic notation (which is + similar to JSON), or it can produce a verbose output where each byte input + is displayed with its encoding beside it. + + \sa QCborStreamReader + + \image cbordump.png + + \section1 The CborDumper Class + + The CborDumper class contains a QCborStreamReader object that is initialized + using the QFile object argument passed to the CborDumper constructor. Based + on the arguments the dump function calls either dumpOne() or + dumpOneDetailed() to dump the contents to standard output, + + \snippet serialization/cbordump/main.cpp 0 + + \section2 The dumpOne() Function + + Switching on QCborStreamReader::type() enables printing appropriate to the + type of the current value in the stream. If the type is an array or map, the + value's content is iterated over, and for each entry the dumpOne() function + is called recursively with a higher indentation argument. If the type is a + tag, it is printed out and dumpOne() is called once without increasing the + indentation argument. + + \section2 The dumpOneDetailed() Function + + This function dumps out both the incoming bytes and the decoded contents + on the same line. It uses lambda functions to print out the bytes and + decoded content, but otherwise has a similar structure as dumpOne(). + + \section1 CborTagDescription + + The \c tagDescriptions table, describing the CBOR tags available, is + automatically generated from an XML file available from the iana.org + website. When \c dumpOneDetailed() reports a tag, it uses its description + from this table. + + \sa {CBOR Support in Qt} +*/ diff --git a/examples/corelib/serialization/cbordump/main.cpp b/examples/corelib/serialization/cbordump/main.cpp index 126a5c5833..03c940452e 100644 --- a/examples/corelib/serialization/cbordump/main.cpp +++ b/examples/corelib/serialization/cbordump/main.cpp @@ -14,86 +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_); @@ -113,6 +164,7 @@ private: qint64 offset = 0; DumpOptions opts; }; +//! [0] Q_DECLARE_OPERATORS_FOR_FLAGS(CborDumper::DumpOptions) static int cborNumberSize(quint64 value) @@ -129,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)); @@ -177,51 +228,54 @@ 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 // standard says only exact conversions are guaranteed. Converting // integrals to floating-point with loss of precision has implementation- // defined behavior whether the next higher or next lower is returned; // converting FP to integral is UB if it can't be represented.; - static_assert(std::numeric_limits<T>::is_integer); + static_assert(TypeInfo::is_integer); - double supremum = ldexp(1, std::numeric_limits<T>::digits); + double supremum = ldexp(1, TypeInfo::digits); if (v >= supremum) return false; - if (v < std::numeric_limits<T>::min()) // either zero or a power of two, so it's exact + if (v < TypeInfo::min()) // either zero or a power of two, so it's exact return false; // we're in range 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()) { @@ -261,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(); @@ -270,7 +324,7 @@ void CborDumper::dumpOne(int nestingLevel) printStringWidthIndicator(r.data.toUtf8().size()); r = reader.readString(); - comma = QLatin1Char(',') + indented; + comma = u',' + indented; } } @@ -326,7 +380,7 @@ void CborDumper::dumpOne(int nestingLevel) if (reader.next()) { printWidthIndicator(quint64(tag)); printf("("); - dumpOne(nestingLevel); // same level! + dumpOne(nestingLevel); // same level! printf(")"); } @@ -358,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: @@ -391,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) @@ -419,13 +473,14 @@ 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 "); int width = 48 - indent.size(); @@ -433,8 +488,8 @@ void CborDumper::dumpOneDetailed(int nestingLevel) qsizetype size = reader.currentStringChunkSize(); if (size < 0) - return; // error - if (size >= std::numeric_limits<int>::max()) { + return; // error + if (size >= ChunkSizeLimit) { fprintf(stderr, "String length too big, %lli\n", qint64(size)); exit(EXIT_FAILURE); } @@ -478,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) @@ -490,8 +545,8 @@ void CborDumper::dumpOneDetailed(int nestingLevel) // get the next chunk size = reader.currentStringChunkSize(); if (size < 0) - return; // error - if (size >= std::numeric_limits<int>::max()) { + return; // error + if (size >= ChunkSizeLimit) { fprintf(stderr, "String length too big, %lli\n", qint64(size)); exit(EXIT_FAILURE); } @@ -629,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; } } @@ -670,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); @@ -701,7 +755,7 @@ int main(int argc, char *argv[]) QStringList files = parser.positionalArguments(); if (files.isEmpty()) files << "-"; - for (const QString &file : qAsConst(files)) { + for (const QString &file : std::as_const(files)) { QFile f(file); if (file == "-" ? f.open(stdin, QIODevice::ReadOnly) : f.open(QIODevice::ReadOnly)) { if (files.size() > 1) 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 4a647c90f8..b5b960c005 100644 --- a/examples/corelib/serialization/convert/CMakeLists.txt +++ b/examples/corelib/serialization/convert/CMakeLists.txt @@ -1,10 +1,12 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(convert LANGUAGES CXX) -set(CMAKE_AUTOMOC ON) +if (ANDROID) + message(FATAL_ERROR "This project cannot be built on Android.") +endif() if(NOT DEFINED INSTALL_EXAMPLESDIR) set(INSTALL_EXAMPLESDIR "examples") @@ -14,10 +16,13 @@ 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 datastreamconverter.cpp datastreamconverter.h + debugtextdumper.cpp debugtextdumper.h jsonconverter.cpp jsonconverter.h main.cpp nullconverter.cpp nullconverter.h @@ -25,8 +30,8 @@ qt_add_executable(convert xmlconverter.cpp xmlconverter.h ) -target_link_libraries(convert PUBLIC - Qt::Core +target_link_libraries(convert PRIVATE + Qt6::Core ) install(TARGETS convert diff --git a/examples/corelib/serialization/convert/cborconverter.cpp b/examples/corelib/serialization/convert/cborconverter.cpp index 0f49de2551..f6a4ee35d2 100644 --- a/examples/corelib/serialization/convert/cborconverter.cpp +++ b/examples/corelib/serialization/convert/cborconverter.cpp @@ -3,23 +3,25 @@ #include "cborconverter.h" +#include <QCborArray> +#include <QCborMap> #include <QCborStreamReader> #include <QCborStreamWriter> -#include <QCborMap> -#include <QCborArray> #include <QCborValue> #include <QDataStream> -#include <QFloat16> #include <QFile> +#include <QFloat16> #include <QMetaType> #include <QTextStream> #include <stdio.h> +using namespace Qt::StringLiterals; + static CborConverter cborConverter; static CborDiagnosticDumper cborDiagnosticDumper; -static const char optionHelp[] = +static const char cborOptionHelp[] = "convert-float-to-int=yes|no Write integers instead of floating point, if no\n" " loss of precision occurs on conversion.\n" "float16=yes|always|no Write using half-precision floating point.\n" @@ -57,6 +59,7 @@ QT_END_NAMESPACE static QVariant convertCborValue(const QCborValue &value); +//! [0] static QVariant convertCborMap(const QCborMap &map) { VariantOrderedMap result; @@ -83,8 +86,9 @@ static QVariant convertCborValue(const QCborValue &value) return convertCborMap(value.toMap()); return value.toVariant(); } - +//! [0] enum TrimFloatingPoint { Double, Float, Float16 }; +//! [1] static QCborValue convertFromVariant(const QVariant &v, TrimFloatingPoint fpTrimming) { if (v.userType() == QMetaType::QVariantList) { @@ -114,34 +118,35 @@ static QCborValue convertFromVariant(const QVariant &v, TrimFloatingPoint fpTrim return QCborValue::fromVariant(v); } +//! [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) +bool CborDiagnosticDumper::probeFile(QIODevice *f) const { Q_UNUSED(f); return false; } -QVariant CborDiagnosticDumper::loadFile(QIODevice *f, Converter *&outputConverter) +QVariant CborDiagnosticDumper::loadFile(QIODevice *f, const Converter *&outputConverter) const { Q_UNREACHABLE(); Q_UNUSED(f); @@ -149,7 +154,8 @@ QVariant CborDiagnosticDumper::loadFile(QIODevice *f, Converter *&outputConverte 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) { @@ -178,8 +184,7 @@ void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents, cons } QTextStream out(f); - out << convertFromVariant(contents, Double).toDiagnosticNotation(opts) - << Qt::endl; + out << convertFromVariant(contents, Double).toDiagnosticNotation(opts) << Qt::endl; } CborConverter::CborConverter() @@ -187,36 +192,37 @@ 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 optionHelp; + 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); } -QVariant CborConverter::loadFile(QIODevice *f, Converter *&outputConverter) +//! [2] +QVariant CborConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const { const char *ptr = nullptr; if (auto file = qobject_cast<QFile *>(f)) @@ -250,9 +256,11 @@ QVariant CborConverter::loadFile(QIODevice *f, Converter *&outputConverter) return contents.toVariant(); return convertCborValue(contents); } - -void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) +//! [2] +//! [3] +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; @@ -308,12 +316,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), optionHelp); + qPrintable(s), cborOptionHelp); exit(EXIT_FAILURE); } - - QCborValue v = convertFromVariant(contents, - useFloat16 == Always ? Float16 : useFloat == Always ? Float : Double); + //! [4] + QCborValue v = + convertFromVariant(contents, + useFloat16 == Always ? Float16 : useFloat == Always ? Float : Double); QCborStreamWriter writer(f); if (useSignature) writer.append(QCborKnownTags::Signature); @@ -327,4 +336,4 @@ 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..ff03185656 100644 --- a/examples/corelib/serialization/convert/cborconverter.h +++ b/examples/corelib/serialization/convert/cborconverter.h @@ -10,13 +10,14 @@ 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; + 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; }; class CborConverter : public Converter @@ -26,13 +27,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..5f4d8c203e 100644 --- a/examples/corelib/serialization/convert/convert.pro +++ b/examples/corelib/serialization/convert/convert.pro @@ -12,17 +12,19 @@ INSTALLS += target SOURCES += main.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 + xmlconverter.h diff --git a/examples/corelib/serialization/convert/converter.h b/examples/corelib/serialization/convert/converter.h index 4da4d47267..300f837959 100644 --- a/examples/corelib/serialization/convert/converter.h +++ b/examples/corelib/serialization/convert/converter.h @@ -5,10 +5,10 @@ #define CONVERTER_H #include <QIODevice> +#include <QList> #include <QPair> #include <QVariant> #include <QVariantMap> -#include <QList> class VariantOrderedMap : public QList<QPair<QVariant, QVariant>> { @@ -32,26 +32,25 @@ protected: public: static Converter *null; - 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 = 0; + virtual const char *optionsHelp() const = 0; + virtual bool probeFile(QIODevice *f) const = 0; + virtual QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const = 0; + 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) #endif // CONVERTER_H diff --git a/examples/corelib/serialization/convert/datastreamconverter.cpp b/examples/corelib/serialization/convert/datastreamconverter.cpp index 9322d97fef..0cce9dd286 100644 --- a/examples/corelib/serialization/convert/datastreamconverter.cpp +++ b/examples/corelib/serialization/convert/datastreamconverter.cpp @@ -2,20 +2,21 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "datastreamconverter.h" +#include "debugtextdumper.h" #include <QDataStream> -#include <QDebug> -#include <QTextStream> -static const char optionHelp[] = +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,123 +43,43 @@ 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 optionHelp; + 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')) { + 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); } @@ -175,9 +96,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('='); @@ -209,11 +131,11 @@ void DataStreamConverter::saveFile(QIODevice *f, const QVariant &contents, const } fprintf(stderr, "Unknown QDataStream formatting option '%s'. Available options are:\n%s", - qPrintable(option), optionHelp); + 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..dfe3e5624a --- /dev/null +++ b/examples/corelib/serialization/convert/debugtextdumper.cpp @@ -0,0 +1,89 @@ +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "debugtextdumper.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; +} + +const char *DebugTextDumper::optionsHelp() const +{ + return nullptr; +} + +bool DebugTextDumper::probeFile(QIODevice *f) const +{ + Q_UNUSED(f); + return false; +} + +QVariant DebugTextDumper::loadFile(QIODevice *f, const Converter *&outputConverter) const +{ + Q_UNREACHABLE(); + Q_UNUSED(f); + Q_UNUSED(outputConverter); + return QVariant(); +} + +void DebugTextDumper::saveFile(QIODevice *f, const QVariant &contents, + const QStringList &options) const +{ + Q_UNUSED(options); + 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..636e6ccb3d --- /dev/null +++ b/examples/corelib/serialization/convert/debugtextdumper.h @@ -0,0 +1,23 @@ +// 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; + 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 // DEBUGTEXTDUMPER_H diff --git a/examples/corelib/serialization/convert/doc/images/convert.png b/examples/corelib/serialization/convert/doc/images/convert.png Binary files differnew file mode 100644 index 0000000000..53e05b4333 --- /dev/null +++ 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 new file mode 100644 index 0000000000..0557aa1836 --- /dev/null +++ b/examples/corelib/serialization/convert/doc/src/convert.qdoc @@ -0,0 +1,82 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example serialization/convert + \examplecategory {Data Processing & I/O} + \meta tag {network} + \title Convert Example + + \brief The Convert example demonstrates 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. + + \image convert.png + + \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. + + \section1 The CborConverter Class + + 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 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 + + A CBOR-file is written using the saveFile() function. + \snippet serialization/convert/cborconverter.cpp 3 + \snippet serialization/convert/cborconverter.cpp 4 + + \sa {CBOR Support in Qt} + + \section1 The DataStreamConverter Class + + The DataStreamConverter class is used to serialize to and from the + QDataStream format. There is also the DebugTextDumper class for outputting + the data lossless in a non-standardized human readable format. + + \section1 The JsonConverter Class + + The JsonConverter class is used to serialize to and from the JSON-format. + \sa {JSON Support in Qt} + + \section1 The XmlConverter Class + + The XmlConverter class is used to serialize to and from the XML-format. + + \section1 The TextConverter Class + + The TextConverter class is used to serialize to and from a text format. + + \section1 The NullConverter Class + + The NullConverter class is an output serializer that does nothing. +*/ diff --git a/examples/corelib/serialization/convert/jsonconverter.cpp b/examples/corelib/serialization/convert/jsonconverter.cpp index 26f48661a9..b6a6a1dd40 100644 --- a/examples/corelib/serialization/convert/jsonconverter.cpp +++ b/examples/corelib/serialization/convert/jsonconverter.cpp @@ -9,10 +9,11 @@ #include <QJsonObject> #include <QJsonValue> +using namespace Qt::StringLiterals; + static JsonConverter jsonConverter; -static const char optionHelp[] = - "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) { @@ -24,34 +25,30 @@ static QJsonDocument convertFromVariant(const QVariant &v) return doc; } -JsonConverter::JsonConverter() -{ -} - -QString JsonConverter::name() +QString JsonConverter::name() const { - return "json"; + return "json"_L1; } -Converter::Direction JsonConverter::directions() +Converter::Directions JsonConverter::directions() const { - return InOut; + return Direction::InOut; } -Converter::Options JsonConverter::outputOptions() +Converter::Options JsonConverter::outputOptions() const { return {}; } -const char *JsonConverter::optionsHelp() +const char *JsonConverter::optionsHelp() const { - return optionHelp; + 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 +59,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; @@ -87,16 +84,18 @@ QVariant JsonConverter::loadFile(QIODevice *f, Converter *&outputConverter) 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), optionHelp); + fprintf(stderr, "Unknown option '%s' to JSON output. Valid options are:\n%s", + qPrintable(s), jsonOptionHelp); exit(EXIT_FAILURE); } } diff --git a/examples/corelib/serialization/convert/jsonconverter.h b/examples/corelib/serialization/convert/jsonconverter.h index 40430a6b70..acff1433be 100644 --- a/examples/corelib/serialization/convert/jsonconverter.h +++ b/examples/corelib/serialization/convert/jsonconverter.h @@ -8,18 +8,16 @@ 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; + 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 // JSONCONVERTER_H diff --git a/examples/corelib/serialization/convert/main.cpp b/examples/corelib/serialization/convert/main.cpp index c234a28f1b..ddb5a49eed 100644 --- a/examples/corelib/serialization/convert/main.cpp +++ b/examples/corelib/serialization/convert/main.cpp @@ -11,12 +11,14 @@ #include <stdio.h> -static QList<Converter *> *availableConverters; +using namespace Qt::StringLiterals; + +static QList<const Converter *> *availableConverters; Converter::Converter() { if (!availableConverters) - availableConverters = new QList<Converter *>; + availableConverters = new QList<const Converter *>; availableConverters->append(this); } @@ -31,64 +33,68 @@ int main(int argc, char *argv[]) QStringList inputFormats; QStringList outputFormats; - for (Converter *conv : qAsConst(*availableConverters)) { + for (const Converter *conv : std::as_const(*availableConverters)) { 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; } 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 file 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 : qAsConst(*availableConverters)) { + for (const Converter *conv : std::as_const(*availableConverters)) { 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 + 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)); + } return EXIT_SUCCESS; } } @@ -97,34 +103,36 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } - Converter *inconv = nullptr; + const Converter *inconv = nullptr; QString format = parser.value(inputFormatOption); - if (format != "auto") { - for (Converter *conv : qAsConst(*availableConverters)) { + if (format != "auto"_L1) { + for (const Converter *conv : std::as_const(*availableConverters)) { if (conv->name() == format) { inconv = conv; break; } } - if (!inconv) { - fprintf(stderr, "Unknown file format \"%s\"\n", qPrintable(format)); + if (!inconv || !inconv->directions().testFlag(Converter::Direction::In)) { + fprintf(stderr, inconv ? "File format \"%s\" cannot be used for input\n" + : "Unknown input file format \"%s\"\n", qPrintable(format)); return EXIT_FAILURE; } } - Converter *outconv = nullptr; + const Converter *outconv = nullptr; format = parser.value(outputFormatOption); - if (format != "auto") { - for (Converter *conv : qAsConst(*availableConverters)) { + if (format != "auto"_L1) { + for (const Converter *conv : std::as_const(*availableConverters)) { if (conv->name() == format) { outconv = conv; break; } } - if (!outconv) { - fprintf(stderr, "Unknown file format \"%s\"\n", qPrintable(format)); + if (!outconv || !outconv->directions().testFlag(Converter::Direction::Out)) { + fprintf(stderr, outconv ? "File format \"%s\" cannot be used for output\n" + : "Unknown output file format \"%s\"\n", qPrintable(format)); return EXIT_FAILURE; } } @@ -155,8 +163,9 @@ int main(int argc, char *argv[]) if (!inconv) { // probe the input to find a file format - for (Converter *conv : qAsConst(*availableConverters)) { - if (conv->directions() & Converter::In && conv->probeFile(&input)) { + for (const Converter *conv : std::as_const(*availableConverters)) { + if (conv->directions().testFlag(Converter::Direction::In) + && conv->probeFile(&input)) { inconv = conv; break; } @@ -170,12 +179,14 @@ int main(int argc, char *argv[]) if (!outconv) { // probe the output to find a file format - for (Converter *conv : qAsConst(*availableConverters)) { - if (conv->directions() & Converter::Out && conv->probeFile(&output)) { + for (const Converter *conv : std::as_const(*availableConverters)) { + if (conv->directions().testFlag(Converter::Direction::Out) + && conv->probeFile(&output)) { outconv = conv; break; } } + // If that failed, loadFile() shall supply a fallback. } // now finally perform the conversion diff --git a/examples/corelib/serialization/convert/nullconverter.cpp b/examples/corelib/serialization/convert/nullconverter.cpp index a3f0bcd99b..aababe6779 100644 --- a/examples/corelib/serialization/convert/nullconverter.cpp +++ b/examples/corelib/serialization/convert/nullconverter.cpp @@ -3,36 +3,38 @@ #include "nullconverter.h" +using namespace Qt::StringLiterals; + static NullConverter nullConverter; -Converter* Converter::null = &nullConverter; +Converter *Converter::null = &nullConverter; -QString NullConverter::name() +QString NullConverter::name() const { - return QLatin1String("null"); + return "null"_L1; } -Converter::Direction NullConverter::directions() +Converter::Directions NullConverter::directions() const { - return Out; + return Direction::Out; } -Converter::Options NullConverter::outputOptions() +Converter::Options NullConverter::outputOptions() const { return SupportsArbitraryMapKeys; } -const char *NullConverter::optionsHelp() +const char *NullConverter::optionsHelp() const { return nullptr; } -bool NullConverter::probeFile(QIODevice *f) +bool NullConverter::probeFile(QIODevice *f) const { Q_UNUSED(f); return false; } -QVariant NullConverter::loadFile(QIODevice *f, Converter *&outputConverter) +QVariant NullConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const { Q_UNUSED(f); Q_UNUSED(outputConverter); @@ -40,10 +42,12 @@ QVariant NullConverter::loadFile(QIODevice *f, Converter *&outputConverter) return QVariant(); } -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())); + fprintf(stderr, "Unknown option '%s' to null output. This format has no options.\n", + qPrintable(options.first())); exit(EXIT_FAILURE); } diff --git a/examples/corelib/serialization/convert/nullconverter.h b/examples/corelib/serialization/convert/nullconverter.h index b2c69593f5..fb215c313c 100644 --- a/examples/corelib/serialization/convert/nullconverter.h +++ b/examples/corelib/serialization/convert/nullconverter.h @@ -10,13 +10,14 @@ 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; + 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 // NULLCONVERTER_H diff --git a/examples/corelib/serialization/convert/textconverter.cpp b/examples/corelib/serialization/convert/textconverter.cpp index b02ce12f66..2df2423bb6 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,67 +44,62 @@ static void dumpVariant(QTextStream &out, const QVariant &v) } } -QString TextConverter::name() +QString TextConverter::name() const { - return QStringLiteral("text"); + return "text"_L1; } -Converter::Direction TextConverter::directions() +Converter::Directions TextConverter::directions() const { - return InOut; + return Direction::InOut; } -Converter::Options TextConverter::outputOptions() +Converter::Options TextConverter::outputOptions() const { return {}; } -const char *TextConverter::optionsHelp() +const char *TextConverter::optionsHelp() const { return nullptr; } -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())); + fprintf(stderr, "Unknown option '%s' to text output. This format has no options.\n", + qPrintable(options.first())); exit(EXIT_FAILURE); } diff --git a/examples/corelib/serialization/convert/textconverter.h b/examples/corelib/serialization/convert/textconverter.h index 6379ffc82f..46e5500d9a 100644 --- a/examples/corelib/serialization/convert/textconverter.h +++ b/examples/corelib/serialization/convert/textconverter.h @@ -8,16 +8,16 @@ 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; + 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 // TEXTCONVERTER_H diff --git a/examples/corelib/serialization/convert/xmlconverter.cpp b/examples/corelib/serialization/convert/xmlconverter.cpp index d4a11257c6..b625595fc7 100644 --- a/examples/corelib/serialization/convert/xmlconverter.cpp +++ b/examples/corelib/serialization/convert/xmlconverter.cpp @@ -13,8 +13,9 @@ #include <QXmlStreamReader> #include <QXmlStreamWriter> -static const char optionHelp[] = - "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 +24,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,8 +48,7 @@ static QVariantList listFromXml(QXmlStreamReader &xml, Converter::Options option break; } - fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", - xml.lineNumber(), xml.columnNumber(), + fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", xml.lineNumber(), xml.columnNumber(), qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); exit(EXIT_FAILURE); } @@ -57,10 +57,11 @@ static QVariantList listFromXml(QXmlStreamReader &xml, Converter::Options option 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,8 +90,7 @@ static VariantOrderedMap::value_type mapEntryFromXml(QXmlStreamReader &xml, Conv break; } - fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", - xml.lineNumber(), xml.columnNumber(), + fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", xml.lineNumber(), xml.columnNumber(), qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); exit(EXIT_FAILURE); } @@ -103,11 +103,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,8 +134,7 @@ static QVariant mapFromXml(QXmlStreamReader &xml, Converter::Options options) break; } - fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", - xml.lineNumber(), xml.columnNumber(), + fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", xml.lineNumber(), xml.columnNumber(), qPrintable(xml.tokenString()), qPrintable(xml.name().toString())); exit(EXIT_FAILURE); } @@ -149,18 +148,18 @@ 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")) { + if (name != "value"_L1) { fprintf(stderr, "%lld:%lld: Invalid XML key '%s'.\n", xml.lineNumber(), xml.columnNumber(), qPrintable(name.toString())); exit(EXIT_FAILURE); } QXmlStreamAttributes attrs = xml.attributes(); - QStringView type = attrs.value(QLatin1String("type")); + QStringView type = attrs.value("type"_L1); forever { xml.readNext(); @@ -169,8 +168,7 @@ 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(), + fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", xml.lineNumber(), xml.columnNumber(), qPrintable(xml.tokenString()), qPrintable(name.toString())); exit(EXIT_FAILURE); } @@ -180,45 +178,45 @@ 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); } + 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); } - } 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; @@ -238,13 +236,13 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options 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(); @@ -267,8 +265,7 @@ 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(), + fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", xml.lineNumber(), xml.columnNumber(), qPrintable(xml.tokenString()), qPrintable(name.toString())); exit(EXIT_FAILURE); } @@ -287,9 +284,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 +298,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: @@ -399,37 +396,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 optionHelp; + 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; @@ -445,16 +442,18 @@ QVariant XmlConverter::loadFile(QIODevice *f, Converter *&outputConverter) 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), optionHelp); + fprintf(stderr, "Unknown option '%s' to XML output. Valid options are:\n%s", + qPrintable(s), xmlOptionHelp); exit(EXIT_FAILURE); } } 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/savegame/CMakeLists.txt b/examples/corelib/serialization/savegame/CMakeLists.txt index 5ccc14ad31..ab3d9a709f 100644 --- a/examples/corelib/serialization/savegame/CMakeLists.txt +++ b/examples/corelib/serialization/savegame/CMakeLists.txt @@ -1,10 +1,12 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(savegame LANGUAGES CXX) -set(CMAKE_AUTOMOC ON) +if (ANDROID) + message(FATAL_ERROR "This project cannot be built on Android.") +endif() if(NOT DEFINED INSTALL_EXAMPLESDIR) set(INSTALL_EXAMPLESDIR "examples") @@ -14,6 +16,8 @@ set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/savegame") find_package(Qt6 REQUIRED COMPONENTS Core) +qt_standard_project_setup() + qt_add_executable(savegame character.cpp character.h game.cpp game.h @@ -21,8 +25,8 @@ qt_add_executable(savegame main.cpp ) -target_link_libraries(savegame PUBLIC - Qt::Core +target_link_libraries(savegame PRIVATE + Qt6::Core ) install(TARGETS savegame diff --git a/examples/corelib/serialization/savegame/character.cpp b/examples/corelib/serialization/savegame/character.cpp index 43be084d3e..863fcb9153 100644 --- a/examples/corelib/serialization/savegame/character.cpp +++ b/examples/corelib/serialization/savegame/character.cpp @@ -6,17 +6,10 @@ #include <QMetaEnum> #include <QTextStream> -Character::Character() : - mLevel(0), - mClassType(Warrior) { -} +Character::Character() = default; -Character::Character(const QString &name, - int level, - Character::ClassType classType) : - mName(name), - mLevel(level), - mClassType(classType) +Character::Character(const QString &name, int level, Character::ClassType classType) + : mName(name), mLevel(level), mClassType(classType) { } @@ -50,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 dfb3837331..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; + Q_GADGET public: - enum ClassType { - Warrior, Mage, Archer - }; + enum ClassType { Warrior, Mage, Archer }; Q_ENUM(ClassType) Character(); @@ -31,14 +31,15 @@ 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; - ClassType mClassType; + int mLevel = 0; + ClassType mClassType = Warrior; }; //! [0] diff --git a/examples/corelib/serialization/savegame/doc/src/savegame.qdoc b/examples/corelib/serialization/savegame/doc/src/savegame.qdoc index 0246ae48bb..14d99ccace 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 + \examplecategory {Data Processing & I/O} \title JSON Save Game Example - \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(): @@ -158,5 +218,5 @@ human-readable JSON files, but you also have the option to use a binary format if it's required, \e without rewriting any code. - \sa {JSON Support in Qt}, {Data Storage} + \sa {JSON Support in Qt}, {CBOR Support in Qt}, {Data Input Output} */ diff --git a/examples/corelib/serialization/savegame/game.cpp b/examples/corelib/serialization/savegame/game.cpp index 111fbfc6ea..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 (int levelIndex = 0; levelIndex < levelArray.size(); ++levelIndex) { - QJsonObject levelObject = levelArray[levelIndex].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"; - for (Level level : mLevels) - level.print(indentation + 1); + s << indent << "Levels\n"; + for (const Level &level : mLevels) + level.print(s, indentation + 1); } diff --git a/examples/corelib/serialization/savegame/game.h b/examples/corelib/serialization/savegame/game.h index 266e764a80..5ba5952923 100644 --- a/examples/corelib/serialization/savegame/game.h +++ b/examples/corelib/serialization/savegame/game.h @@ -4,19 +4,19 @@ #ifndef GAME_H #define GAME_H +#include "character.h" +#include "level.h" + #include <QJsonObject> #include <QList> -#include "character.h" -#include "level.h" +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 04edadf7f6..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 (int npcIndex = 0; npcIndex < npcArray.size(); ++npcIndex) { - QJsonObject npcObject = npcArray[npcIndex].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 4c0a8aed89..e487e55ae3 100644 --- a/examples/corelib/serialization/savegame/level.h +++ b/examples/corelib/serialization/savegame/level.h @@ -4,27 +4,30 @@ #ifndef LEVEL_H #define LEVEL_H +#include "character.h" + #include <QJsonObject> #include <QList> -#include "character.h" +QT_FORWARD_DECLARE_CLASS(QTextStream) //! [0] class Level { public: Level() = default; - Level(const QString &name); + explicit Level(const QString &name); QString name() const; 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 c9e713c126..3fc0f3af10 100644 --- a/examples/corelib/serialization/savegame/main.cpp +++ b/examples/corelib/serialization/savegame/main.cpp @@ -1,32 +1,37 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#include "game.h" + #include <QCoreApplication> +#include <QStringList> +#include <QString> #include <QTextStream> -#include "game.h" +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 7651444f19..e20fcb57fd 100644 --- a/examples/corelib/serialization/serialization.pro +++ b/examples/corelib/serialization/serialization.pro @@ -3,3 +3,7 @@ SUBDIRS = \ cbordump \ convert \ savegame + +qtHaveModule(widgets) { + SUBDIRS += streambookmarks +} diff --git a/examples/xml/streambookmarks/CMakeLists.txt b/examples/corelib/serialization/streambookmarks/CMakeLists.txt index 6f28dead4c..1d0ab8690b 100644 --- a/examples/xml/streambookmarks/CMakeLists.txt +++ b/examples/corelib/serialization/streambookmarks/CMakeLists.txt @@ -1,18 +1,18 @@ # Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +# SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16) project(streambookmarks LANGUAGES CXX) -set(CMAKE_AUTOMOC ON) - if(NOT DEFINED INSTALL_EXAMPLESDIR) set(INSTALL_EXAMPLESDIR "examples") endif() -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/xml/streambookmarks") +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/streambookmarks") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Xml) +qt_standard_project_setup() qt_add_executable(streambookmarks main.cpp @@ -26,11 +26,10 @@ set_target_properties(streambookmarks PROPERTIES MACOSX_BUNDLE TRUE ) -target_link_libraries(streambookmarks PUBLIC - Qt::Core - Qt::Gui - Qt::Widgets - Qt::Xml +target_link_libraries(streambookmarks PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Widgets ) install(TARGETS streambookmarks 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/xml/streambookmarks/jennifer.xbel b/examples/corelib/serialization/streambookmarks/jennifer.xbel index 2501c118af..d504236830 100644 --- a/examples/xml/streambookmarks/jennifer.xbel +++ b/examples/corelib/serialization/streambookmarks/jennifer.xbel @@ -3,66 +3,66 @@ <xbel version="1.0"> <folder folded="no"> <title>Qt Resources</title> - <bookmark href="http://qt.io/"> + <bookmark href="https://www.qt.io/"> <title>Qt home page</title> </bookmark> - <bookmark href="https://www.qt.io/partners/"> + <bookmark href="https://www.qt.io/contact-us/partners"> <title>Qt Partners</title> </bookmark> - <bookmark href="https://www.qt.io/qt-training/"> - <title>Training</title> + <bookmark href="https://www.qt.io/qt-professional-services"> + <title>Professional Services</title> </bookmark> - <bookmark href="http://doc.qt.io/"> - <title>Qt 5 documentation</title> - </bookmark> - <bookmark href="http://qt-project.org/faq/"> - <title>Frequently Asked Questions</title> + <bookmark href="https://doc.qt.io/"> + <title>Qt Documentation</title> </bookmark> <folder folded="yes"> <title>Community Resources</title> - <bookmark href="http://www.qtcentre.org/content/"> + <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="http://www.qtforum.org/"> - <title>QtForum.org</title> + <bookmark href="https://forum.qt.io/"> + <title>Forum.Qt.org</title> </bookmark> - <bookmark href="http://digitalfanatics.org/projects/qt_tutorial/"> + <bookmark href="https://digitalfanatics.org/projects/qt_tutorial/"> <title>The Independent Qt Tutorial</title> </bookmark> - <bookmark href="http://www.qtforum.de/"> + <bookmark href="https://www.qtforum.de/"> <title>German Qt Forum</title> </bookmark> - <bookmark href="http://www.korone.net/"> + <bookmark href="https://www.qt-dev.com/"> <title>Korean Qt Community Site</title> </bookmark> - <bookmark href="http://prog.org.ru/"> + <bookmark href="http://www.prog.org.ru/"> <title>Russian Qt Forum</title> </bookmark> </folder> </folder> <folder folded="no"> <title>Online Dictionaries</title> - <bookmark href="http://www.dictionary.com/"> + <bookmark href="https://www.dictionary.com/"> <title>Dictionary.com</title> </bookmark> - <bookmark href="http://www.m-w.com/"> + <bookmark href="https://www.merriam-webster.com/"> <title>Merriam-Webster Online</title> </bookmark> - <bookmark href="http://dictionary.cambridge.org/"> + <bookmark href="https://dictionary.cambridge.org/"> <title>Cambridge Dictionaries Online</title> </bookmark> - <bookmark href="http://www.onelook.com/"> + <bookmark href="https://www.onelook.com/"> <title>OneLook Dictionary Search</title> </bookmark> <separator/> - <bookmark href="http://dict.tu-chemnitz.de/"> - <title>TU Chemnitz German-English Dictionary</title> + <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="http://dictionnaires.atilf.fr/dictionnaires/ACADEMIE/"> + <bookmark href="https://www.dictionnaire-academie.fr/"> <title>Dictionnaire de l'Académie Française</title> </bookmark> </folder> diff --git a/examples/xml/streambookmarks/main.cpp b/examples/corelib/serialization/streambookmarks/main.cpp index 75b5d646c6..0fd317de43 100644 --- a/examples/xml/streambookmarks/main.cpp +++ b/examples/corelib/serialization/streambookmarks/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 "mainwindow.h" +#include <QApplication> + //! [0] int main(int argc, char *argv[]) { diff --git a/examples/xml/streambookmarks/mainwindow.cpp b/examples/corelib/serialization/streambookmarks/mainwindow.cpp index 644681d706..a863f77ab7 100644 --- a/examples/xml/streambookmarks/mainwindow.cpp +++ b/examples/corelib/serialization/streambookmarks/mainwindow.cpp @@ -1,22 +1,33 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include <QtWidgets> - #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() +MainWindow::MainWindow() : treeWidget(new QTreeWidget) { - QStringList labels; - labels << tr("Title") << tr("Location"); - - treeWidget = new QTreeWidget; treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch); - treeWidget->setHeaderLabels(labels); -#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD) + 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); @@ -33,7 +44,8 @@ MainWindow::MainWindow() } //! [0] -#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD) +//! [1] +#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu) void MainWindow::onCustomContextMenuRequested(const QPoint &pos) { const QTreeWidgetItem *item = treeWidget->itemAt(pos); @@ -49,59 +61,77 @@ void MainWindow::onCustomContextMenuRequested(const QPoint &pos) else if (action == openAction) QDesktopServices::openUrl(QUrl(url)); } -#endif // !QT_NO_CONTEXTMENU && !QT_NO_CLIPBOARD - +#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() { - QString fileName = - QFileDialog::getOpenFileName(this, tr("Open Bookmark File"), - QDir::currentPath(), - tr("XBEL Files (*.xbel *.xml)")); - if (fileName.isEmpty()) + 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())); + .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())); + 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); } - } -//! [1] +//! [3] -//! [2] +//! [4] void MainWindow::saveAs() { - QString fileName = - QFileDialog::getSaveFileName(this, tr("Save Bookmark File"), - QDir::currentPath(), - tr("XBEL Files (*.xbel *.xml)")); - if (fileName.isEmpty()) + 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())); + .arg(QDir::toNativeSeparators(fileName), file.errorString())); return; } @@ -109,34 +139,13 @@ void MainWindow::saveAs() if (writer.writeFile(&file)) statusBar()->showMessage(tr("File saved"), 2000); } -//! [2] - -//! [3] -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.")); -} -//! [3] +//! [4] //! [5] -void MainWindow::createMenus() +void MainWindow::about() { - 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, &QCoreApplication::quit); + 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/xml/streambookmarks/mainwindow.h b/examples/corelib/serialization/streambookmarks/mainwindow.h index 7a4a922e43..d9efe6b5a5 100644 --- a/examples/xml/streambookmarks/mainwindow.h +++ b/examples/corelib/serialization/streambookmarks/mainwindow.h @@ -22,13 +22,13 @@ public slots: void open(); void saveAs(); void about(); -#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD) +#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu) void onCustomContextMenuRequested(const QPoint &pos); #endif private: void createMenus(); - QTreeWidget *treeWidget; + QTreeWidget *const treeWidget; }; //! [0] diff --git a/examples/xml/streambookmarks/streambookmarks.pro b/examples/corelib/serialization/streambookmarks/streambookmarks.pro index 8c2fc3fa19..34d2caae82 100644 --- a/examples/xml/streambookmarks/streambookmarks.pro +++ b/examples/corelib/serialization/streambookmarks/streambookmarks.pro @@ -5,11 +5,11 @@ SOURCES = main.cpp \ mainwindow.cpp \ xbelreader.cpp \ xbelwriter.cpp -QT += xml widgets +QT += widgets requires(qtConfig(filedialog)) -EXAMPLE_FILES = frank.xbel jennifer.xbel +EXAMPLE_FILES = jennifer.xbel # install -target.path = $$[QT_INSTALL_EXAMPLES]/xml/streambookmarks +target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/streambookmarks INSTALLS += target diff --git a/examples/xml/streambookmarks/xbelreader.cpp b/examples/corelib/serialization/streambookmarks/xbelreader.cpp index 74e25f12a8..c622cf6642 100644 --- a/examples/xml/streambookmarks/xbelreader.cpp +++ b/examples/corelib/serialization/streambookmarks/xbelreader.cpp @@ -1,20 +1,21 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -#include <QtWidgets> - #include "xbelreader.h" +#include <QStyle> +#include <QTreeWidget> + +using namespace Qt::StringLiterals; + //! [0] -XbelReader::XbelReader(QTreeWidget *treeWidget) - : treeWidget(treeWidget) +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); + 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] @@ -25,12 +26,10 @@ bool XbelReader::read(QIODevice *device) xml.setDevice(device); if (xml.readNextStartElement()) { - if (xml.name() == QLatin1String("xbel") - && xml.attributes().value(versionAttribute()) == QLatin1String("1.0")) { + if (xml.name() == "xbel"_L1 && xml.attributes().value("version"_L1) == "1.0"_L1) readXBEL(); - } else { + else xml.raiseError(QObject::tr("The file is not an XBEL version 1.0 file.")); - } } |