qt 6.5.1 original
17
examples/corelib/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
add_subdirectory(ipc)
|
||||
add_subdirectory(mimetypes)
|
||||
add_subdirectory(serialization)
|
||||
add_subdirectory(tools)
|
||||
add_subdirectory(platform)
|
||||
if(QT_FEATURE_permissions)
|
||||
add_subdirectory(permissions)
|
||||
endif()
|
||||
if(QT_FEATURE_thread)
|
||||
add_subdirectory(threads)
|
||||
endif()
|
||||
if(QT_FEATURE_widgets)
|
||||
add_subdirectory(bindableproperties)
|
||||
endif()
|
2
examples/corelib/bindableproperties/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
qt_internal_add_example(bindablesubscription)
|
||||
qt_internal_add_example(subscription)
|
@ -0,0 +1,4 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = \
|
||||
bindablesubscription \
|
||||
subscription
|
@ -0,0 +1,50 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(bindablesubscription LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/bindableproperties/bindablesubscription")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(bindablesubscription
|
||||
../shared/subscriptionwindow.cpp ../shared/subscriptionwindow.h ../shared/subscriptionwindow.ui
|
||||
main.cpp
|
||||
bindablesubscription.cpp bindablesubscription.h
|
||||
bindableuser.cpp bindableuser.h
|
||||
)
|
||||
|
||||
target_link_libraries(bindablesubscription PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
# 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}"
|
||||
)
|
@ -0,0 +1,51 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "bindablesubscription.h"
|
||||
#include "bindableuser.h"
|
||||
|
||||
//! [binding-expressions]
|
||||
|
||||
BindableSubscription::BindableSubscription(BindableUser *user) : m_user(user)
|
||||
{
|
||||
Q_ASSERT(user);
|
||||
|
||||
m_price.setBinding([this] { return qRound(calculateDiscount() * m_duration * basePrice()); });
|
||||
|
||||
m_isValid.setBinding([this] {
|
||||
return m_user->country() != BindableUser::Country::AnyCountry && m_user->age() > 12;
|
||||
});
|
||||
}
|
||||
|
||||
//! [binding-expressions]
|
||||
|
||||
//! [set-duration]
|
||||
|
||||
void BindableSubscription::setDuration(Duration newDuration)
|
||||
{
|
||||
m_duration = newDuration;
|
||||
}
|
||||
|
||||
//! [set-duration]
|
||||
|
||||
double BindableSubscription::calculateDiscount() const
|
||||
{
|
||||
switch (m_duration) {
|
||||
case Monthly:
|
||||
return 1;
|
||||
case Quarterly:
|
||||
return 0.9;
|
||||
case Yearly:
|
||||
return 0.6;
|
||||
}
|
||||
Q_ASSERT(false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int BindableSubscription::basePrice() const
|
||||
{
|
||||
if (m_user->country() == BindableUser::Country::AnyCountry)
|
||||
return 0;
|
||||
|
||||
return (m_user->country() == BindableUser::Country::Norway) ? 100 : 80;
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef BINDABLESUBSCRIPTION_H
|
||||
#define BINDABLESUBSCRIPTION_H
|
||||
|
||||
#include <QPointer>
|
||||
#include <QProperty>
|
||||
|
||||
class BindableUser;
|
||||
|
||||
//! [bindable-subscription-class]
|
||||
|
||||
class BindableSubscription
|
||||
{
|
||||
public:
|
||||
enum Duration { Monthly = 1, Quarterly = 3, Yearly = 12 };
|
||||
|
||||
BindableSubscription(BindableUser *user);
|
||||
BindableSubscription(const BindableSubscription &) = delete;
|
||||
|
||||
int price() const { return m_price; }
|
||||
QBindable<int> bindablePrice() { return &m_price; }
|
||||
|
||||
Duration duration() const { return m_duration; }
|
||||
void setDuration(Duration newDuration);
|
||||
QBindable<Duration> bindableDuration() { return &m_duration; }
|
||||
|
||||
bool isValid() const { return m_isValid; }
|
||||
QBindable<bool> bindableIsValid() { return &m_isValid; }
|
||||
|
||||
private:
|
||||
double calculateDiscount() const;
|
||||
int basePrice() const;
|
||||
|
||||
BindableUser *m_user;
|
||||
QProperty<Duration> m_duration { Monthly };
|
||||
QProperty<int> m_price { 0 };
|
||||
QProperty<bool> m_isValid { false };
|
||||
};
|
||||
|
||||
//! [bindable-subscription-class]
|
||||
|
||||
#endif // BNDABLESUBSCRIPTION_H
|
@ -0,0 +1,22 @@
|
||||
QT += widgets
|
||||
TARGET = bindablesubscription
|
||||
|
||||
SOURCES += main.cpp \
|
||||
bindablesubscription.cpp \
|
||||
bindableuser.cpp \
|
||||
../shared/subscriptionwindow.cpp
|
||||
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/bindableproperties/bindablesubscription
|
||||
INSTALLS += target
|
||||
|
||||
FORMS += \
|
||||
../shared/subscriptionwindow.ui
|
||||
|
||||
HEADERS += \
|
||||
bindablesubscription.h \
|
||||
bindableuser.h \
|
||||
../shared/subscriptionwindow.h
|
||||
|
||||
RESOURCES += \
|
||||
../shared/countries.qrc
|
||||
|
@ -0,0 +1,18 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "bindableuser.h"
|
||||
|
||||
//! [bindable-user-setters]
|
||||
|
||||
void BindableUser::setCountry(Country country)
|
||||
{
|
||||
m_country = country;
|
||||
}
|
||||
|
||||
void BindableUser::setAge(int age)
|
||||
{
|
||||
m_age = age;
|
||||
}
|
||||
|
||||
//! [bindable-user-setters]
|
@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef BINDABLEUSER_H
|
||||
#define BINDABLEUSER_H
|
||||
|
||||
#include <QLocale>
|
||||
#include <QProperty>
|
||||
|
||||
//! [bindable-user-class]
|
||||
|
||||
class BindableUser
|
||||
{
|
||||
public:
|
||||
using Country = QLocale::Territory;
|
||||
|
||||
public:
|
||||
BindableUser() = default;
|
||||
BindableUser(const BindableUser &) = delete;
|
||||
|
||||
Country country() const { return m_country; }
|
||||
void setCountry(Country country);
|
||||
QBindable<Country> bindableCountry() { return &m_country; }
|
||||
|
||||
int age() const { return m_age; }
|
||||
void setAge(int age);
|
||||
QBindable<int> bindableAge() { return &m_age; }
|
||||
|
||||
private:
|
||||
QProperty<Country> m_country { QLocale::AnyTerritory };
|
||||
QProperty<int> m_age { 0 };
|
||||
};
|
||||
|
||||
//! [bindable-user-class]
|
||||
|
||||
#endif // BINDABLEUSER_H
|
@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "../shared/subscriptionwindow.h"
|
||||
#include "bindablesubscription.h"
|
||||
#include "bindableuser.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QButtonGroup>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QSpinBox>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
BindableUser user;
|
||||
BindableSubscription subscription(&user);
|
||||
|
||||
SubscriptionWindow w;
|
||||
|
||||
// Initialize subscription data
|
||||
QRadioButton *monthly = w.findChild<QRadioButton *>("btnMonthly");
|
||||
QObject::connect(monthly, &QRadioButton::clicked, [&] {
|
||||
subscription.setDuration(BindableSubscription::Monthly);
|
||||
});
|
||||
QRadioButton *quarterly = w.findChild<QRadioButton *>("btnQuarterly");
|
||||
QObject::connect(quarterly, &QRadioButton::clicked, [&] {
|
||||
subscription.setDuration(BindableSubscription::Quarterly);
|
||||
});
|
||||
QRadioButton *yearly = w.findChild<QRadioButton *>("btnYearly");
|
||||
QObject::connect(yearly, &QRadioButton::clicked, [&] {
|
||||
subscription.setDuration(BindableSubscription::Yearly);
|
||||
});
|
||||
|
||||
// Initialize user data
|
||||
QPushButton *germany = w.findChild<QPushButton *>("btnGermany");
|
||||
QObject::connect(germany, &QPushButton::clicked, [&] {
|
||||
user.setCountry(BindableUser::Country::Germany);
|
||||
});
|
||||
QPushButton *finland = w.findChild<QPushButton *>("btnFinland");
|
||||
QObject::connect(finland, &QPushButton::clicked, [&] {
|
||||
user.setCountry(BindableUser::Country::Finland);
|
||||
});
|
||||
QPushButton *norway = w.findChild<QPushButton *>("btnNorway");
|
||||
QObject::connect(norway, &QPushButton::clicked, [&] {
|
||||
user.setCountry(BindableUser::Country::Norway);
|
||||
});
|
||||
|
||||
QSpinBox *ageSpinBox = w.findChild<QSpinBox *>("ageSpinBox");
|
||||
QObject::connect(ageSpinBox, &QSpinBox::valueChanged, [&](int value) {
|
||||
user.setAge(value);
|
||||
});
|
||||
|
||||
QLabel *priceDisplay = w.findChild<QLabel *>("priceDisplay");
|
||||
|
||||
// Track price changes
|
||||
//! [update-ui]
|
||||
auto priceChangeHandler = subscription.bindablePrice().subscribe([&] {
|
||||
QLocale lc{QLocale::AnyLanguage, user.country()};
|
||||
priceDisplay->setText(lc.toCurrencyString(subscription.price() / subscription.duration()));
|
||||
});
|
||||
|
||||
auto priceValidHandler = subscription.bindableIsValid().subscribe([&] {
|
||||
priceDisplay->setEnabled(subscription.isValid());
|
||||
});
|
||||
//! [update-ui]
|
||||
|
||||
w.show();
|
||||
return a.exec();
|
||||
}
|
After Width: | Height: | Size: 18 KiB |
@ -0,0 +1,178 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example bindableproperties
|
||||
\title Bindable Properties Example
|
||||
\brief Demonstrates how the usage of bindable properties can simplify
|
||||
your C++ code.
|
||||
|
||||
In this example we will demonstrate two approaches for expressing the
|
||||
relationships between different objects depending on each other:
|
||||
signal/slot connection-based and bindable property-based. For this
|
||||
purpose we will consider a subscription service model to calculate the
|
||||
cost of the subscription.
|
||||
|
||||
\image bindable_properties_example.png
|
||||
|
||||
\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:
|
||||
|
||||
\snippet bindableproperties/subscription/subscription.h subscription-class
|
||||
|
||||
It stores the information about the subscription and provides corresponding
|
||||
getters, setters, and notifier signals for informing the listeners about the
|
||||
subscription information changes. It also keeps a pointer to an instance of
|
||||
the \c User class.
|
||||
|
||||
The price of the subscription is calculated based on the duration of the
|
||||
subscription:
|
||||
|
||||
\snippet bindableproperties/subscription/subscription.cpp calculate-discount
|
||||
|
||||
And user's location:
|
||||
|
||||
\snippet bindableproperties/subscription/subscription.cpp calculate-base-price
|
||||
|
||||
When the price changes, the \c priceChanged() signal is emitted, to notify the
|
||||
listeners about the change:
|
||||
|
||||
\snippet bindableproperties/subscription/subscription.cpp calculate-price
|
||||
|
||||
Similarly, when the duration of the subscription changes, the \c durationChanged()
|
||||
signal is emitted.
|
||||
|
||||
\snippet bindableproperties/subscription/subscription.cpp set-duration
|
||||
|
||||
\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 \c Subscription is not valid unless the user has a valid country and
|
||||
age, so the validity is updated in the following way:
|
||||
|
||||
\snippet bindableproperties/subscription/subscription.cpp update-validity
|
||||
|
||||
The \c User class is simple: it stores country and age of the user and
|
||||
provides the corresponding getters, setters, and notifier signals:
|
||||
|
||||
\snippet bindableproperties/subscription/user.h user-class
|
||||
|
||||
\snippet bindableproperties/subscription/user.cpp user-setters
|
||||
|
||||
In the \c main() function we initialize instances of \c User and
|
||||
\c Subscription:
|
||||
|
||||
\snippet bindableproperties/subscription/main.cpp init
|
||||
|
||||
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
|
||||
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
|
||||
display if the subscription isn't valid.
|
||||
|
||||
\snippet bindableproperties/subscription/main.cpp connect-validity-changed
|
||||
|
||||
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.
|
||||
|
||||
\snippet bindableproperties/subscription/main.cpp connect-user
|
||||
|
||||
This works, but there are some problems:
|
||||
|
||||
\list
|
||||
\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 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.
|
||||
\li The \c Subscription and \c User classes depend on the metaobject system
|
||||
to be able to use the signal/slot mechanism.
|
||||
\endlist
|
||||
|
||||
Can we do better?
|
||||
|
||||
\section1 Modeling Subscription System with Bindbable Properties
|
||||
|
||||
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
|
||||
bindable properties:
|
||||
|
||||
\snippet bindableproperties/bindablesubscription/bindablesubscription.h bindable-subscription-class
|
||||
|
||||
The first difference we can notice, is that the data fields are now wrapped
|
||||
inside \l QProperty classes, and the notifier signals (and as a consequence the
|
||||
dependency from the metaobject system) are gone, and new methods returning a
|
||||
\l QBindable for each \l QProperty are added instead. The \c calculatePrice()
|
||||
and \c updateValidty() methods are also removed. We'll see below why they aren't
|
||||
needed anymore.
|
||||
|
||||
The \c BindableUser class differs from the \c User class in a similar way:
|
||||
|
||||
\snippet bindableproperties/bindablesubscription/bindableuser.h bindable-user-class
|
||||
|
||||
The second difference is in the implementation of these classes. First of
|
||||
all, the dependencies between \c subscription and \c user are now tracked via
|
||||
binding expressions:
|
||||
|
||||
\snippet bindableproperties/bindablesubscription/bindablesubscription.cpp binding-expressions
|
||||
|
||||
Behind the scenes the bindable properties track the dependency changes and
|
||||
update the property's value whenever a change is detected. So if, for example,
|
||||
user's country or age is changed, subscription's price and validity will be
|
||||
updated automatically.
|
||||
|
||||
Another difference is that the setters are now trivial:
|
||||
|
||||
\snippet bindableproperties/bindablesubscription/bindablesubscription.cpp set-duration
|
||||
|
||||
\snippet bindableproperties/bindablesubscription/bindableuser.cpp bindable-user-setters
|
||||
|
||||
There's no need to check inside the setters if the property's value has
|
||||
actually changed, \l QProperty already does that. The dependent properties
|
||||
will be notified about the change only if the value has actually changed.
|
||||
|
||||
The code for updating the information about the price in the UI is also
|
||||
simplified:
|
||||
|
||||
\snippet bindableproperties/bindablesubscription/main.cpp update-ui
|
||||
|
||||
We subscribe to changes via \c bindablePrice() and \c bindableIsValid()
|
||||
and update the price display accordingly when any of these properties
|
||||
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 BindableSubscription and
|
||||
\c BindableUser are disabled, since it's not defined what should happen
|
||||
with their bindings when copying.
|
||||
|
||||
As you can see, the code became much simpler, and the problems mentioned
|
||||
above are solved:
|
||||
|
||||
\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 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
|
||||
system anymore. Of course, you can still expose them to the metaobject
|
||||
system and add \l {Q_PROPERTY}s if you need, and have the advantages of
|
||||
bindable properties both in \c C++ and \c QML code. You can use the
|
||||
\l QObjectBindableProperty class for that.
|
||||
\endlist
|
||||
*/
|
7
examples/corelib/bindableproperties/shared/countries.qrc
Normal file
@ -0,0 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>germany.png</file>
|
||||
<file>norway.png</file>
|
||||
<file>finland.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
BIN
examples/corelib/bindableproperties/shared/finland.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
examples/corelib/bindableproperties/shared/germany.png
Normal file
After Width: | Height: | Size: 483 B |
BIN
examples/corelib/bindableproperties/shared/norway.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
@ -0,0 +1,16 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "subscriptionwindow.h"
|
||||
#include "ui_subscriptionwindow.h"
|
||||
|
||||
SubscriptionWindow::SubscriptionWindow(QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::SubscriptionWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
SubscriptionWindow::~SubscriptionWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef SUBSCRIPTIONWINDOW_H
|
||||
#define SUBSCRIPTIONWINDOW_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui {
|
||||
class SubscriptionWindow;
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class User;
|
||||
|
||||
class SubscriptionWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SubscriptionWindow(QWidget *parent = nullptr);
|
||||
~SubscriptionWindow();
|
||||
|
||||
private:
|
||||
Ui::SubscriptionWindow *ui;
|
||||
};
|
||||
|
||||
#endif // SUBSCRIPTIONWINDOW_H
|
280
examples/corelib/bindableproperties/shared/subscriptionwindow.ui
Normal file
@ -0,0 +1,280 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SubscriptionWindow</class>
|
||||
<widget class="QWidget" name="SubscriptionWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>639</width>
|
||||
<height>269</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Subscription</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0,0,0">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnGermany">
|
||||
<property name="toolTip">
|
||||
<string>Germany</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/germany.png</normaloff>:/germany.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnNorway">
|
||||
<property name="toolTip">
|
||||
<string>Norway</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/norway.png</normaloff>:/norway.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnFinland">
|
||||
<property name="toolTip">
|
||||
<string>Finland</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/finland.png</normaloff>:/finland.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="ageLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Age</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="ageSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="intervalLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Interval</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="btnMonthly">
|
||||
<property name="text">
|
||||
<string>Monthly</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="btnQuarterly">
|
||||
<property name="text">
|
||||
<string>Quarterly</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="btnYearly">
|
||||
<property name="text">
|
||||
<string>Yearly</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="priceLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>14</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Price/month</string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="priceDisplay">
|
||||
<property name="text">
|
||||
<string>0.0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -0,0 +1,50 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(subscription LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/bindableproperties/subscription")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(subscription
|
||||
../shared/subscriptionwindow.cpp ../shared/subscriptionwindow.h ../shared/subscriptionwindow.ui
|
||||
main.cpp
|
||||
subscription.cpp subscription.h
|
||||
user.cpp user.h
|
||||
)
|
||||
|
||||
target_link_libraries(subscription PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
# Resources:
|
||||
set(countries_resource_files
|
||||
"../shared/finland.png"
|
||||
"../shared/germany.png"
|
||||
"../shared/norway.png"
|
||||
)
|
||||
|
||||
qt_add_resources(subscription "countries"
|
||||
PREFIX
|
||||
"/"
|
||||
BASE
|
||||
"../shared"
|
||||
FILES
|
||||
${countries_resource_files}
|
||||
)
|
||||
|
||||
install(TARGETS subscription
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
92
examples/corelib/bindableproperties/subscription/main.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "../shared/subscriptionwindow.h"
|
||||
#include "subscription.h"
|
||||
#include "user.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QButtonGroup>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QSpinBox>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
|
||||
//! [init]
|
||||
User user;
|
||||
Subscription subscription(&user);
|
||||
//! [init]
|
||||
|
||||
SubscriptionWindow w;
|
||||
|
||||
// Initialize subscription data
|
||||
QRadioButton *monthly = w.findChild<QRadioButton *>("btnMonthly");
|
||||
QObject::connect(monthly, &QRadioButton::clicked, &subscription, [&] {
|
||||
subscription.setDuration(Subscription::Monthly);
|
||||
});
|
||||
QRadioButton *quarterly = w.findChild<QRadioButton *>("btnQuarterly");
|
||||
QObject::connect(quarterly, &QRadioButton::clicked, &subscription, [&] {
|
||||
subscription.setDuration(Subscription::Quarterly);
|
||||
});
|
||||
QRadioButton *yearly = w.findChild<QRadioButton *>("btnYearly");
|
||||
QObject::connect(yearly, &QRadioButton::clicked, &subscription, [&] {
|
||||
subscription.setDuration(Subscription::Yearly);
|
||||
});
|
||||
|
||||
// Initialize user data
|
||||
QPushButton *germany = w.findChild<QPushButton *>("btnGermany");
|
||||
QObject::connect(germany, &QPushButton::clicked, &user, [&] {
|
||||
user.setCountry(User::Country::Germany);
|
||||
});
|
||||
QPushButton *finland = w.findChild<QPushButton *>("btnFinland");
|
||||
QObject::connect(finland, &QPushButton::clicked, &user, [&] {
|
||||
user.setCountry(User::Country::Finland);
|
||||
});
|
||||
QPushButton *norway = w.findChild<QPushButton *>("btnNorway");
|
||||
QObject::connect(norway, &QPushButton::clicked, &user, [&] {
|
||||
user.setCountry(User::Country::Norway);
|
||||
});
|
||||
|
||||
QSpinBox *ageSpinBox = w.findChild<QSpinBox *>("ageSpinBox");
|
||||
QObject::connect(ageSpinBox, &QSpinBox::valueChanged, &user, [&](int value) {
|
||||
user.setAge(value);
|
||||
});
|
||||
|
||||
// Initialize price data
|
||||
QLabel *priceDisplay = w.findChild<QLabel *>("priceDisplay");
|
||||
priceDisplay->setText(QString::number(subscription.price()));
|
||||
priceDisplay->setEnabled(subscription.isValid());
|
||||
|
||||
// Track the price changes
|
||||
|
||||
//! [connect-price-changed]
|
||||
QObject::connect(&subscription, &Subscription::priceChanged, [&] {
|
||||
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, [&] {
|
||||
priceDisplay->setEnabled(subscription.isValid());
|
||||
});
|
||||
//! [connect-validity-changed]
|
||||
|
||||
//! [connect-user]
|
||||
QObject::connect(&user, &User::countryChanged, [&] {
|
||||
subscription.calculatePrice();
|
||||
subscription.updateValidity();
|
||||
});
|
||||
|
||||
QObject::connect(&user, &User::ageChanged, [&] {
|
||||
subscription.updateValidity();
|
||||
});
|
||||
//! [connect-user]
|
||||
|
||||
w.show();
|
||||
return a.exec();
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "subscription.h"
|
||||
#include "user.h"
|
||||
|
||||
Subscription::Subscription(User *user) : m_user(user)
|
||||
{
|
||||
Q_ASSERT(user);
|
||||
}
|
||||
|
||||
//! [calculate-price]
|
||||
|
||||
void Subscription::calculatePrice()
|
||||
{
|
||||
const auto oldPrice = m_price;
|
||||
|
||||
m_price = qRound(calculateDiscount() * m_duration * basePrice());
|
||||
if (m_price != oldPrice)
|
||||
emit priceChanged();
|
||||
}
|
||||
|
||||
//! [calculate-price]
|
||||
|
||||
//! [set-duration]
|
||||
|
||||
void Subscription::setDuration(Duration newDuration)
|
||||
{
|
||||
if (newDuration != m_duration) {
|
||||
m_duration = newDuration;
|
||||
calculatePrice();
|
||||
emit durationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
//! [set-duration]
|
||||
|
||||
//! [calculate-discount]
|
||||
|
||||
double Subscription::calculateDiscount() const
|
||||
{
|
||||
switch (m_duration) {
|
||||
case Monthly:
|
||||
return 1;
|
||||
case Quarterly:
|
||||
return 0.9;
|
||||
case Yearly:
|
||||
return 0.6;
|
||||
}
|
||||
Q_ASSERT(false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
//! [calculate-discount]
|
||||
|
||||
//! [calculate-base-price]
|
||||
|
||||
int Subscription::basePrice() const
|
||||
{
|
||||
if (m_user->country() == User::Country::AnyTerritory)
|
||||
return 0;
|
||||
|
||||
return (m_user->country() == User::Country::Norway) ? 100 : 80;
|
||||
}
|
||||
|
||||
//! [calculate-base-price]
|
||||
|
||||
//! [update-validity]
|
||||
|
||||
void Subscription::updateValidity()
|
||||
{
|
||||
bool isValid = m_isValid;
|
||||
m_isValid = m_user->country() != User::Country::AnyTerritory && m_user->age() > 12;
|
||||
|
||||
if (m_isValid != isValid)
|
||||
emit isValidChanged();
|
||||
}
|
||||
|
||||
//! [update-validity]
|
@ -0,0 +1,48 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef SUBSCRIPTION_H
|
||||
#define SUBSCRIPTION_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
class User;
|
||||
|
||||
//! [subscription-class]
|
||||
|
||||
class Subscription : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Duration { Monthly = 1, Quarterly = 3, Yearly = 12 };
|
||||
|
||||
Subscription(User *user);
|
||||
|
||||
void calculatePrice();
|
||||
int price() const { return m_price; }
|
||||
|
||||
Duration duration() const { return m_duration; }
|
||||
void setDuration(Duration newDuration);
|
||||
|
||||
bool isValid() const { return m_isValid; }
|
||||
void updateValidity();
|
||||
|
||||
signals:
|
||||
void priceChanged();
|
||||
void durationChanged();
|
||||
void isValidChanged();
|
||||
|
||||
private:
|
||||
double calculateDiscount() const;
|
||||
int basePrice() const;
|
||||
|
||||
QPointer<User> m_user;
|
||||
Duration m_duration = Monthly;
|
||||
int m_price = 0;
|
||||
bool m_isValid = false;
|
||||
};
|
||||
|
||||
//! [subscription-class]
|
||||
|
||||
#endif // SUBSCRIPTION_H
|
@ -0,0 +1,22 @@
|
||||
QT += widgets
|
||||
TARGET = subscription
|
||||
|
||||
SOURCES += main.cpp \
|
||||
subscription.cpp \
|
||||
user.cpp \
|
||||
../shared/subscriptionwindow.cpp
|
||||
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/bindableproperties/subscription
|
||||
INSTALLS += target
|
||||
|
||||
FORMS += \
|
||||
../shared/subscriptionwindow.ui
|
||||
|
||||
HEADERS += \
|
||||
subscription.h \
|
||||
user.h \
|
||||
../shared/subscriptionwindow.h
|
||||
|
||||
RESOURCES += \
|
||||
../shared/countries.qrc
|
||||
|
24
examples/corelib/bindableproperties/subscription/user.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "user.h"
|
||||
|
||||
//! [user-setters]
|
||||
|
||||
void User::setCountry(Country country)
|
||||
{
|
||||
if (m_country != country) {
|
||||
m_country = country;
|
||||
emit countryChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void User::setAge(int age)
|
||||
{
|
||||
if (m_age != age) {
|
||||
m_age = age;
|
||||
emit ageChanged();
|
||||
}
|
||||
}
|
||||
|
||||
//! [user-setters]
|
36
examples/corelib/bindableproperties/subscription/user.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef USER_H
|
||||
#define USER_H
|
||||
|
||||
#include <QLocale>
|
||||
#include <QObject>
|
||||
|
||||
//! [user-class]
|
||||
|
||||
class User : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using Country = QLocale::Territory;
|
||||
|
||||
public:
|
||||
Country country() const { return m_country; }
|
||||
void setCountry(Country country);
|
||||
|
||||
int age() const { return m_age; }
|
||||
void setAge(int age);
|
||||
|
||||
signals:
|
||||
void countryChanged();
|
||||
void ageChanged();
|
||||
|
||||
private:
|
||||
Country m_country { QLocale::AnyTerritory };
|
||||
int m_age { 0 };
|
||||
};
|
||||
|
||||
//! [user-class]
|
||||
#endif // USER_H
|
11
examples/corelib/corelib.pro
Normal file
@ -0,0 +1,11 @@
|
||||
TEMPLATE = subdirs
|
||||
CONFIG += no_docs_target
|
||||
|
||||
SUBDIRS = \
|
||||
ipc \
|
||||
mimetypes \
|
||||
serialization \
|
||||
tools \
|
||||
platform
|
||||
|
||||
qtConfig(thread): SUBDIRS += threads
|
13
examples/corelib/ipc/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
if(NOT TARGET Qt6::Widgets)
|
||||
return()
|
||||
endif()
|
||||
if(QT_FEATURE_sharedmemory)
|
||||
qt_internal_add_example(sharedmemory)
|
||||
endif()
|
||||
if(QT_FEATURE_localserver AND TARGET Qt6::Network)
|
||||
qt_internal_add_example(localfortuneserver)
|
||||
qt_internal_add_example(localfortuneclient)
|
||||
endif()
|
5
examples/corelib/ipc/README
Normal file
@ -0,0 +1,5 @@
|
||||
These examples demonstrate IPC support in Qt.
|
||||
|
||||
|
||||
Documentation for these examples can be found via the Examples
|
||||
link in the main Qt documentation.
|
BIN
examples/corelib/ipc/doc/images/localfortuneclient-example.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
examples/corelib/ipc/doc/images/localfortuneserver-example.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
examples/corelib/ipc/doc/images/sharedmemory-example_1.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
examples/corelib/ipc/doc/images/sharedmemory-example_2.png
Normal file
After Width: | Height: | Size: 22 KiB |
16
examples/corelib/ipc/doc/src/localfortuneclient.qdoc
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example ipc/localfortuneclient
|
||||
\title Local Fortune Client Example
|
||||
\ingroup examples-ipc
|
||||
\brief Demonstrates using QLocalSocket for a simple local service client.
|
||||
|
||||
The Local Fortune Client example shows how to create a client for a simple
|
||||
local service using QLocalSocket. It is intended to be run alongside the
|
||||
\l{Local Fortune Server Example}.
|
||||
|
||||
\image localfortuneclient-example.png Screenshot of the Local Fortune Client example
|
||||
|
||||
*/
|
15
examples/corelib/ipc/doc/src/localfortuneserver.qdoc
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example ipc/localfortuneserver
|
||||
\title Local Fortune Server Example
|
||||
\ingroup examples-ipc
|
||||
\brief Demonstrates using QLocalServer and QLocalSocket for serving a simple local service.
|
||||
|
||||
The Local Fortune Server example shows how to create a server for a simple
|
||||
local service. It is intended to be run alongside the
|
||||
\l{Local Fortune Client Example}
|
||||
|
||||
\image localfortuneserver-example.png Screenshot of the Local Fortune Server example
|
||||
*/
|
107
examples/corelib/ipc/doc/src/sharedmemory.qdoc
Normal file
@ -0,0 +1,107 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example ipc/sharedmemory
|
||||
\title Shared Memory Example
|
||||
\ingroup examples-ipc
|
||||
\brief Demonstrates doing inter-process communication using shared memory with
|
||||
the QSharedMemory class.
|
||||
|
||||
The Shared Memory example shows how to use the QSharedMemory class
|
||||
to implement inter-process communication using shared memory. To
|
||||
build the example, run make. To run the example, start two instances
|
||||
of the executable. The main() function creates an \l {QApplication}
|
||||
{application} and an instance of our example's Dialog class. The
|
||||
dialog is displayed and then control is passed to the application in
|
||||
the standard way.
|
||||
|
||||
\snippet ipc/sharedmemory/main.cpp 0
|
||||
|
||||
Two instances of class Dialog appear.
|
||||
|
||||
\image sharedmemory-example_1.png Screenshot of the Shared Memory example
|
||||
|
||||
Class Dialog inherits QDialog. It encapsulates the user interface
|
||||
and an instance of QSharedMemory. It also has two public slots,
|
||||
loadFromFile() and loadFromMemory() that correspond to the two
|
||||
buttons on the dialog.
|
||||
|
||||
\snippet ipc/sharedmemory/dialog.h 0
|
||||
|
||||
The constructor builds the user interface widgets and connects the
|
||||
clicked() signal of each button to the corresponding slot function.
|
||||
|
||||
\snippet ipc/sharedmemory/dialog.cpp 0
|
||||
|
||||
Note that "QSharedMemoryExample" is passed to the \l {QSharedMemory}
|
||||
{QSharedMemory()} constructor to be used as the key. This will be
|
||||
used by the system as the identifier of the underlying shared memory
|
||||
segment.
|
||||
|
||||
Click the \tt {Load Image From File...} button on one of the
|
||||
dialogs. The loadFromFile() slot is invoked. First, it tests whether
|
||||
a shared memory segment is already attached to the process. If so,
|
||||
that segment is detached from the process, so we can be assured of
|
||||
starting off the example correctly.
|
||||
|
||||
\snippet ipc/sharedmemory/dialog.cpp 1
|
||||
|
||||
The user is then asked to select an image file using
|
||||
QFileDialog::getOpenFileName(). The selected file is loaded into a
|
||||
QImage. Using a QImage lets us ensure that the selected file is a
|
||||
valid image, and it also allows us to immediately display the image
|
||||
in the dialog using setPixmap().
|
||||
|
||||
Next the image is streamed into a QBuffer using a QDataStream. This
|
||||
gives us the size, which we then use to \l {QSharedMemory::}
|
||||
{create()} our shared memory segment. Creating a shared memory
|
||||
segment automatically \l {QSharedMemory::attach()} {attaches} the
|
||||
segment to the process. Using a QBuffer here lets us get a pointer
|
||||
to the image data, which we then use to do a memcopy() from the
|
||||
QBuffer into the shared memory segment.
|
||||
|
||||
\snippet ipc/sharedmemory/dialog.cpp 2
|
||||
|
||||
Note that we \l {QSharedMemory::} {lock()} the shared memory segment
|
||||
before we copy into it, and we \l {QSharedMemory::} {unlock()} it
|
||||
again immediately after the copy. This ensures we have exclusive
|
||||
access to the shared memory segment to do our memcopy(). If some
|
||||
other process has the segment lock, then our process will block
|
||||
until the lock becomes available.
|
||||
|
||||
Note also that the function does not \l {QSharedMemory::} {detach()}
|
||||
from the shared memory segment after the memcopy() and
|
||||
unlock(). Recall that when the last process detaches from a shared
|
||||
memory segment, the segment is released by the operating
|
||||
system. Since this process only one that is attached to the shared
|
||||
memory segment at the moment, if loadFromFile() detached from the
|
||||
shared memory segment, the segment would be destroyed before we get
|
||||
to the next step.
|
||||
|
||||
When the function returns, if the file you selected was qt.png, your
|
||||
first dialog looks like this.
|
||||
|
||||
\image sharedmemory-example_2.png Screenshot of the Shared Memory example
|
||||
|
||||
In the second dialog, click the \tt {Display Image From Shared
|
||||
Memory} button. The loadFromMemory() slot is invoked. It first \l
|
||||
{QSharedMemory::attach()} {attaches} the process to the same shared
|
||||
memory segment created by the first process. Then it \l
|
||||
{QSharedMemory::lock()} {locks} the segment for exclusive access and
|
||||
links a QBuffer to the image data in the shared memory segment. It
|
||||
then streams the data into a QImage and \l {QSharedMemory::unlock()}
|
||||
{unlocks} the segment.
|
||||
|
||||
\snippet ipc/sharedmemory/dialog.cpp 3
|
||||
|
||||
In this case, the function does \l {QSharedMemory::} {detach()} from
|
||||
the segment, because now we are effectively finished using
|
||||
it. Finally, the QImage is displayed. At this point, both dialogs
|
||||
should be showing the same image. When you close the first dialog,
|
||||
the Dialog destructor calls the QSharedMemory destructor, which
|
||||
detaches from the shared memory segment. Since this is the last
|
||||
process to be detached from the segment, the operating system will
|
||||
now release the shared memory.
|
||||
|
||||
*/
|
10
examples/corelib/ipc/ipc.pro
Normal file
@ -0,0 +1,10 @@
|
||||
requires(qtHaveModule(widgets))
|
||||
|
||||
TEMPLATE = subdirs
|
||||
|
||||
qtConfig(sharedmemory): SUBDIRS = sharedmemory
|
||||
qtHaveModule(network) {
|
||||
QT_FOR_CONFIG += network
|
||||
|
||||
qtConfig(localserver): SUBDIRS += localfortuneserver localfortuneclient
|
||||
}
|
38
examples/corelib/ipc/localfortuneclient/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(localfortuneclient LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/ipc/localfortuneclient")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(localfortuneclient
|
||||
client.cpp client.h
|
||||
main.cpp
|
||||
)
|
||||
|
||||
set_target_properties(localfortuneclient PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(localfortuneclient PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS localfortuneclient
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
115
examples/corelib/ipc/localfortuneclient/client.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtNetwork>
|
||||
|
||||
#include "client.h"
|
||||
|
||||
Client::Client(QWidget *parent)
|
||||
: QDialog(parent),
|
||||
hostLineEdit(new QLineEdit("fortune")),
|
||||
getFortuneButton(new QPushButton(tr("Get Fortune"))),
|
||||
statusLabel(new QLabel(tr("This examples requires that you run the "
|
||||
"Local Fortune Server example as well."))),
|
||||
socket(new QLocalSocket(this))
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
QLabel *hostLabel = new QLabel(tr("&Server name:"));
|
||||
hostLabel->setBuddy(hostLineEdit);
|
||||
|
||||
statusLabel->setWordWrap(true);
|
||||
|
||||
getFortuneButton->setDefault(true);
|
||||
QPushButton *quitButton = new QPushButton(tr("Quit"));
|
||||
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox;
|
||||
buttonBox->addButton(getFortuneButton, QDialogButtonBox::ActionRole);
|
||||
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
|
||||
|
||||
in.setDevice(socket);
|
||||
in.setVersion(QDataStream::Qt_5_10);
|
||||
|
||||
connect(hostLineEdit, &QLineEdit::textChanged,
|
||||
this, &Client::enableGetFortuneButton);
|
||||
connect(getFortuneButton, &QPushButton::clicked,
|
||||
this, &Client::requestNewFortune);
|
||||
connect(quitButton, &QPushButton::clicked, this, &Client::close);
|
||||
connect(socket, &QLocalSocket::readyRead, this, &Client::readFortune);
|
||||
connect(socket, &QLocalSocket::errorOccurred, this, &Client::displayError);
|
||||
|
||||
QGridLayout *mainLayout = new QGridLayout(this);
|
||||
mainLayout->addWidget(hostLabel, 0, 0);
|
||||
mainLayout->addWidget(hostLineEdit, 0, 1);
|
||||
mainLayout->addWidget(statusLabel, 2, 0, 1, 2);
|
||||
mainLayout->addWidget(buttonBox, 3, 0, 1, 2);
|
||||
|
||||
setWindowTitle(QGuiApplication::applicationDisplayName());
|
||||
hostLineEdit->setFocus();
|
||||
}
|
||||
|
||||
void Client::requestNewFortune()
|
||||
{
|
||||
getFortuneButton->setEnabled(false);
|
||||
blockSize = 0;
|
||||
socket->abort();
|
||||
socket->connectToServer(hostLineEdit->text());
|
||||
}
|
||||
|
||||
void Client::readFortune()
|
||||
{
|
||||
if (blockSize == 0) {
|
||||
// Relies on the fact that QDataStream serializes a quint32 into
|
||||
// sizeof(quint32) bytes
|
||||
if (socket->bytesAvailable() < (int)sizeof(quint32))
|
||||
return;
|
||||
in >> blockSize;
|
||||
}
|
||||
|
||||
if (socket->bytesAvailable() < blockSize || in.atEnd())
|
||||
return;
|
||||
|
||||
QString nextFortune;
|
||||
in >> nextFortune;
|
||||
|
||||
if (nextFortune == currentFortune) {
|
||||
QTimer::singleShot(0, this, &Client::requestNewFortune);
|
||||
return;
|
||||
}
|
||||
|
||||
currentFortune = nextFortune;
|
||||
statusLabel->setText(currentFortune);
|
||||
getFortuneButton->setEnabled(true);
|
||||
}
|
||||
|
||||
void Client::displayError(QLocalSocket::LocalSocketError socketError)
|
||||
{
|
||||
switch (socketError) {
|
||||
case QLocalSocket::ServerNotFoundError:
|
||||
QMessageBox::information(this, tr("Local Fortune Client"),
|
||||
tr("The host was not found. Please make sure "
|
||||
"that the server is running and that the "
|
||||
"server name is correct."));
|
||||
break;
|
||||
case QLocalSocket::ConnectionRefusedError:
|
||||
QMessageBox::information(this, tr("Local Fortune Client"),
|
||||
tr("The connection was refused by the peer. "
|
||||
"Make sure the fortune server is running, "
|
||||
"and check that the server name "
|
||||
"is correct."));
|
||||
break;
|
||||
case QLocalSocket::PeerClosedError:
|
||||
break;
|
||||
default:
|
||||
QMessageBox::information(this, tr("Local Fortune Client"),
|
||||
tr("The following error occurred: %1.")
|
||||
.arg(socket->errorString()));
|
||||
}
|
||||
|
||||
getFortuneButton->setEnabled(true);
|
||||
}
|
||||
|
||||
void Client::enableGetFortuneButton()
|
||||
{
|
||||
getFortuneButton->setEnabled(!hostLineEdit->text().isEmpty());
|
||||
}
|
42
examples/corelib/ipc/localfortuneclient/client.h
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CLIENT_H
|
||||
#define CLIENT_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QDataStream>
|
||||
#include <QLocalSocket>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class Client : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Client(QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void requestNewFortune();
|
||||
void readFortune();
|
||||
void displayError(QLocalSocket::LocalSocketError socketError);
|
||||
void enableGetFortuneButton();
|
||||
|
||||
private:
|
||||
QLineEdit *hostLineEdit;
|
||||
QPushButton *getFortuneButton;
|
||||
QLabel *statusLabel;
|
||||
|
||||
QLocalSocket *socket;
|
||||
QDataStream in;
|
||||
quint32 blockSize;
|
||||
|
||||
QString currentFortune;
|
||||
};
|
||||
|
||||
#endif
|
@ -0,0 +1,8 @@
|
||||
HEADERS = client.h
|
||||
SOURCES = client.cpp \
|
||||
main.cpp
|
||||
QT += network widgets
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/ipc/localfortuneclient
|
||||
INSTALLS += target
|
15
examples/corelib/ipc/localfortuneclient/main.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "client.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
QGuiApplication::setApplicationDisplayName(Client::tr("Local Fortune Client"));
|
||||
Client client;
|
||||
client.show();
|
||||
return app.exec();
|
||||
}
|
38
examples/corelib/ipc/localfortuneserver/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(localfortuneserver LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/ipc/localfortuneserver")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(localfortuneserver
|
||||
main.cpp
|
||||
server.cpp server.h
|
||||
)
|
||||
|
||||
set_target_properties(localfortuneserver PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(localfortuneserver PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS localfortuneserver
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
HEADERS = server.h
|
||||
SOURCES = server.cpp \
|
||||
main.cpp
|
||||
QT += network widgets
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/ipc/localfortuneserver
|
||||
INSTALLS += target
|
15
examples/corelib/ipc/localfortuneserver/main.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "server.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
QGuiApplication::setApplicationDisplayName(Server::tr("Local Fortune Server"));
|
||||
Server server;
|
||||
server.show();
|
||||
return app.exec();
|
||||
}
|
70
examples/corelib/ipc/localfortuneserver/server.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "server.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtNetwork>
|
||||
|
||||
Server::Server(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
server = new QLocalServer(this);
|
||||
if (!server->listen("fortune")) {
|
||||
QMessageBox::critical(this, tr("Local Fortune Server"),
|
||||
tr("Unable to start the server: %1.")
|
||||
.arg(server->errorString()));
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
QLabel *statusLabel = new QLabel;
|
||||
statusLabel->setWordWrap(true);
|
||||
statusLabel->setText(tr("The server is running.\n"
|
||||
"Run the Local Fortune Client example now."));
|
||||
|
||||
fortunes << tr("You've been leading a dog's life. Stay off the furniture.")
|
||||
<< tr("You've got to think about tomorrow.")
|
||||
<< tr("You will be surprised by a loud noise.")
|
||||
<< tr("You will feel hungry again in another hour.")
|
||||
<< tr("You might have mail.")
|
||||
<< tr("You cannot kill time without injuring eternity.")
|
||||
<< tr("Computers are not intelligent. They only think they are.");
|
||||
|
||||
QPushButton *quitButton = new QPushButton(tr("Quit"));
|
||||
quitButton->setAutoDefault(false);
|
||||
connect(quitButton, &QPushButton::clicked, this, &Server::close);
|
||||
connect(server, &QLocalServer::newConnection, this, &Server::sendFortune);
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(quitButton);
|
||||
buttonLayout->addStretch(1);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->addWidget(statusLabel);
|
||||
mainLayout->addLayout(buttonLayout);
|
||||
|
||||
setWindowTitle(QGuiApplication::applicationDisplayName());
|
||||
}
|
||||
|
||||
void Server::sendFortune()
|
||||
{
|
||||
QByteArray block;
|
||||
QDataStream out(&block, QIODevice::WriteOnly);
|
||||
out.setVersion(QDataStream::Qt_5_10);
|
||||
const int fortuneIndex = QRandomGenerator::global()->bounded(0, fortunes.size());
|
||||
const QString &message = fortunes.at(fortuneIndex);
|
||||
out << quint32(message.size());
|
||||
out << message;
|
||||
|
||||
QLocalSocket *clientConnection = server->nextPendingConnection();
|
||||
connect(clientConnection, &QLocalSocket::disconnected,
|
||||
clientConnection, &QLocalSocket::deleteLater);
|
||||
|
||||
clientConnection->write(block);
|
||||
clientConnection->flush();
|
||||
clientConnection->disconnectFromServer();
|
||||
}
|
30
examples/corelib/ipc/localfortuneserver/server.h
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
class QLocalServer;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class Server : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Server(QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void sendFortune();
|
||||
|
||||
private:
|
||||
QLocalServer *server;
|
||||
QStringList fortunes;
|
||||
};
|
||||
|
||||
#endif
|
37
examples/corelib/ipc/sharedmemory/CMakeLists.txt
Normal file
@ -0,0 +1,37 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(sharedmemory LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/ipc/sharedmemory")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(sharedmemory
|
||||
dialog.cpp dialog.h dialog.ui
|
||||
main.cpp
|
||||
)
|
||||
|
||||
set_target_properties(sharedmemory PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(sharedmemory PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS sharedmemory
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
155
examples/corelib/ipc/sharedmemory/dialog.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "dialog.h"
|
||||
#include <QFileDialog>
|
||||
#include <QBuffer>
|
||||
|
||||
/*!
|
||||
\class Dialog
|
||||
|
||||
\brief This class is a simple example of how to use QSharedMemory.
|
||||
|
||||
It is a simple dialog that presents a few buttons. To compile the
|
||||
example, run make in qt/examples/ipc. Then run the executable twice
|
||||
to create two processes running the dialog. In one of the processes,
|
||||
press the button to load an image into a shared memory segment, and
|
||||
then select an image file to load. Once the first process has loaded
|
||||
and displayed the image, in the second process, press the button to
|
||||
read the same image from shared memory. The second process displays
|
||||
the same image loaded from its new loaction in shared memory.
|
||||
*/
|
||||
|
||||
/*!
|
||||
The class contains a data member \l {QSharedMemory} {sharedMemory},
|
||||
which is initialized with the key "QSharedMemoryExample" to force
|
||||
all instances of Dialog to access the same shared memory segment.
|
||||
The constructor also connects the clicked() signal from each of the
|
||||
three dialog buttons to the slot function appropriate for handling
|
||||
each button.
|
||||
*/
|
||||
//! [0]
|
||||
Dialog::Dialog(QWidget *parent)
|
||||
: QDialog(parent), sharedMemory("QSharedMemoryExample")
|
||||
{
|
||||
ui.setupUi(this);
|
||||
connect(ui.loadFromFileButton, &QPushButton::clicked,
|
||||
this, &Dialog::loadFromFile);
|
||||
connect(ui.loadFromSharedMemoryButton, &QPushButton::clicked,
|
||||
this, &Dialog::loadFromMemory);
|
||||
setWindowTitle(tr("SharedMemory Example"));
|
||||
}
|
||||
//! [0]
|
||||
|
||||
/*!
|
||||
This slot function is called when the \tt {Load Image From File...}
|
||||
button is pressed on the firs Dialog process. First, it tests
|
||||
whether the process is already connected to a shared memory segment
|
||||
and, if so, detaches from that segment. This ensures that we always
|
||||
start the example from the beginning if we run it multiple times
|
||||
with the same two Dialog processes. After detaching from an existing
|
||||
shared memory segment, the user is prompted to select an image file.
|
||||
The selected file is loaded into a QImage. The QImage is displayed
|
||||
in the Dialog and streamed into a QBuffer with a QDataStream.
|
||||
|
||||
Next, it gets a new shared memory segment from the system big enough
|
||||
to hold the image data in the QBuffer, and it locks the segment to
|
||||
prevent the second Dialog process from accessing it. Then it copies
|
||||
the image from the QBuffer into the shared memory segment. Finally,
|
||||
it unlocks the shared memory segment so the second Dialog process
|
||||
can access it.
|
||||
|
||||
After this function runs, the user is expected to press the \tt
|
||||
{Load Image from Shared Memory} button on the second Dialog process.
|
||||
|
||||
\sa loadFromMemory()
|
||||
*/
|
||||
//! [1]
|
||||
void Dialog::loadFromFile()
|
||||
{
|
||||
if (sharedMemory.isAttached())
|
||||
detach();
|
||||
|
||||
ui.label->setText(tr("Select an image file"));
|
||||
QString fileName = QFileDialog::getOpenFileName(0, QString(), QString(),
|
||||
tr("Images (*.png *.xpm *.jpg)"));
|
||||
QImage image;
|
||||
if (!image.load(fileName)) {
|
||||
ui.label->setText(tr("Selected file is not an image, please select another."));
|
||||
return;
|
||||
}
|
||||
ui.label->setPixmap(QPixmap::fromImage(image));
|
||||
//! [1] //! [2]
|
||||
|
||||
// load into shared memory
|
||||
QBuffer buffer;
|
||||
buffer.open(QBuffer::ReadWrite);
|
||||
QDataStream out(&buffer);
|
||||
out << image;
|
||||
int size = buffer.size();
|
||||
|
||||
if (!sharedMemory.create(size)) {
|
||||
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();
|
||||
const char *from = buffer.data().data();
|
||||
memcpy(to, from, qMin(sharedMemory.size(), size));
|
||||
sharedMemory.unlock();
|
||||
}
|
||||
//! [2]
|
||||
|
||||
/*!
|
||||
This slot function is called in the second Dialog process, when the
|
||||
user presses the \tt {Load Image from Shared Memory} button. First,
|
||||
it attaches the process to the shared memory segment created by the
|
||||
first Dialog process. Then it locks the segment for exclusive
|
||||
access, copies the image data from the segment into a QBuffer, and
|
||||
streams the QBuffer into a QImage. Then it unlocks the shared memory
|
||||
segment, detaches from it, and finally displays the QImage in the
|
||||
Dialog.
|
||||
|
||||
\sa loadFromFile()
|
||||
*/
|
||||
//! [3]
|
||||
void Dialog::loadFromMemory()
|
||||
{
|
||||
if (!sharedMemory.attach()) {
|
||||
ui.label->setText(tr("Unable to attach to shared memory segment.\n" \
|
||||
"Load an image first."));
|
||||
return;
|
||||
}
|
||||
|
||||
QBuffer buffer;
|
||||
QDataStream in(&buffer);
|
||||
QImage image;
|
||||
|
||||
sharedMemory.lock();
|
||||
buffer.setData((char*)sharedMemory.constData(), sharedMemory.size());
|
||||
buffer.open(QBuffer::ReadOnly);
|
||||
in >> image;
|
||||
sharedMemory.unlock();
|
||||
|
||||
sharedMemory.detach();
|
||||
ui.label->setPixmap(QPixmap::fromImage(image));
|
||||
}
|
||||
//! [3]
|
||||
|
||||
/*!
|
||||
This private function is called by the destructor to detach the
|
||||
process from its shared memory segment. When the last process
|
||||
detaches from a shared memory segment, the system releases the
|
||||
shared memory.
|
||||
*/
|
||||
void Dialog::detach()
|
||||
{
|
||||
if (!sharedMemory.detach())
|
||||
ui.label->setText(tr("Unable to detach from shared memory."));
|
||||
}
|
||||
|
33
examples/corelib/ipc/sharedmemory/dialog.h
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef DIALOG_H
|
||||
#define DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QSharedMemory>
|
||||
#include "ui_dialog.h"
|
||||
|
||||
//! [0]
|
||||
class Dialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Dialog(QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void loadFromFile();
|
||||
void loadFromMemory();
|
||||
|
||||
private:
|
||||
void detach();
|
||||
|
||||
private:
|
||||
Ui::Dialog ui;
|
||||
QSharedMemory sharedMemory;
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
||||
|
47
examples/corelib/ipc/sharedmemory/dialog.ui
Normal file
@ -0,0 +1,47 @@
|
||||
<ui version="4.0" >
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>451</width>
|
||||
<height>322</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QPushButton" name="loadFromFileButton" >
|
||||
<property name="text" >
|
||||
<string>Load Image From File...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>Launch two of these dialogs. In the first, press the top button and load an image from a file. In the second, press the bottom button and display the loaded image from shared memory.</string>
|
||||
</property>
|
||||
<property name="alignment" >
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QPushButton" name="loadFromSharedMemoryButton" >
|
||||
<property name="text" >
|
||||
<string>Display Image From Shared Memory</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
BIN
examples/corelib/ipc/sharedmemory/image.png
Normal file
After Width: | Height: | Size: 10 KiB |
16
examples/corelib/ipc/sharedmemory/main.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
#include "dialog.h"
|
||||
|
||||
//! [0]
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication application(argc, argv);
|
||||
Dialog dialog;
|
||||
dialog.show();
|
||||
return application.exec();
|
||||
}
|
||||
//! [0]
|
||||
|
BIN
examples/corelib/ipc/sharedmemory/qt.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
16
examples/corelib/ipc/sharedmemory/sharedmemory.pro
Normal file
@ -0,0 +1,16 @@
|
||||
QT += widgets
|
||||
requires(qtConfig(filedialog))
|
||||
|
||||
SOURCES += main.cpp \
|
||||
dialog.cpp
|
||||
|
||||
HEADERS += dialog.h
|
||||
|
||||
# Forms and resources
|
||||
FORMS += dialog.ui
|
||||
|
||||
EXAMPLE_FILES = *.png
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/ipc/sharedmemory
|
||||
INSTALLS += target
|
6
examples/corelib/mimetypes/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
if(TARGET Qt6::Widgets)
|
||||
qt_internal_add_example(mimetypebrowser)
|
||||
endif()
|
BIN
examples/corelib/mimetypes/doc/images/mimetypebrowser.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
30
examples/corelib/mimetypes/doc/src/mimetypebrowser.qdoc
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example mimetypes/mimetypebrowser
|
||||
\ingroup examples-mimetype
|
||||
\title MIME Type Browser Example
|
||||
|
||||
\brief Shows the hierarchy of MIME types and
|
||||
can be used to determine the MIME type of a file.
|
||||
|
||||
\image mimetypebrowser.png Screenshot of the MIME Type Browser Example
|
||||
|
||||
\e {MIME Type Browser} is intended to be a tool for exploring MIME types
|
||||
rather than an example showing the typical usage of Qt's MIME API.
|
||||
|
||||
\include examples-run.qdocinc
|
||||
|
||||
\section1 Main Window
|
||||
|
||||
The main window consists of a tree view displaying the hierarchy of MIME types
|
||||
based on the model MimetypeModel inheriting QStandardItemModel on the left and
|
||||
a QTextBrowser for showing detailed information about the selected MIME type
|
||||
on the right.
|
||||
|
||||
It has a main menu with an option \uicontrol{File/Detect File Type}, which
|
||||
lets you pick a file and then displays its MIME type.
|
||||
|
||||
For more information, see QMimeType and QMimeDatabase.
|
||||
*/
|
38
examples/corelib/mimetypes/mimetypebrowser/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(mimetypebrowser LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/mimetypes/mimetypebrowser")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(mimetypebrowser
|
||||
main.cpp
|
||||
mainwindow.cpp mainwindow.h
|
||||
mimetypemodel.cpp mimetypemodel.h
|
||||
)
|
||||
|
||||
set_target_properties(mimetypebrowser PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE FALSE
|
||||
)
|
||||
|
||||
target_link_libraries(mimetypebrowser PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS mimetypebrowser
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
29
examples/corelib/mimetypes/mimetypebrowser/main.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QScreen>
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QCommandLineOption>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
QCoreApplication::setApplicationVersion(QT_VERSION_STR);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
|
||||
parser.process(app);
|
||||
|
||||
MainWindow mainWindow;
|
||||
const QRect availableGeometry = mainWindow.screen()->availableGeometry();
|
||||
mainWindow.resize(availableGeometry.width() / 3, availableGeometry.height() / 2);
|
||||
mainWindow.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
149
examples/corelib/mimetypes/mimetypebrowser/mainwindow.cpp
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include "mimetypemodel.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
#include <QMessageBox>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QSplitter>
|
||||
#include <QStatusBar>
|
||||
#include <QTextEdit>
|
||||
#include <QTreeView>
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QItemSelectionModel>
|
||||
#include <QMimeDatabase>
|
||||
#include <QMimeType>
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
, m_model(new MimetypeModel(this))
|
||||
, m_treeView(new QTreeView(this))
|
||||
, m_detailsText(new QTextEdit(this))
|
||||
, m_findIndex(0)
|
||||
{
|
||||
setWindowTitle(tr("Qt Mime Database Browser"));
|
||||
|
||||
QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
|
||||
QAction *detectFileAction =
|
||||
fileMenu->addAction(tr("&Detect File Type..."), this, &MainWindow::detectFile);
|
||||
detectFileAction->setShortcuts(QKeySequence::Open);
|
||||
|
||||
QAction *exitAction = fileMenu->addAction(tr("E&xit"), qApp, &QApplication::quit);
|
||||
exitAction->setShortcuts(QKeySequence::Quit);
|
||||
|
||||
QMenu *findMenu = menuBar()->addMenu(tr("&Edit"));
|
||||
QAction *findAction =
|
||||
findMenu->addAction(tr("&Find..."), this, &MainWindow::find);
|
||||
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->setShortcuts(QKeySequence::FindPrevious);
|
||||
|
||||
menuBar()->addMenu(tr("&About"))->addAction(tr("&About Qt"), qApp, &QApplication::aboutQt);
|
||||
|
||||
QSplitter *centralSplitter = new QSplitter(this);
|
||||
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);
|
||||
if (!items.isEmpty())
|
||||
m_treeView->expand(m_model->indexFromItem(items.constFirst()));
|
||||
|
||||
connect(m_treeView->selectionModel(), &QItemSelectionModel::currentChanged,
|
||||
this, &MainWindow::currentChanged);
|
||||
centralSplitter->addWidget(m_treeView);
|
||||
m_detailsText->setReadOnly(true);
|
||||
centralSplitter->addWidget(m_detailsText);
|
||||
|
||||
updateFindActions();
|
||||
}
|
||||
|
||||
void MainWindow::currentChanged(const QModelIndex &index)
|
||||
{
|
||||
if (index.isValid())
|
||||
m_detailsText->setText(MimetypeModel::formatMimeTypeInfo(m_model->mimeType(index)));
|
||||
else
|
||||
m_detailsText->clear();
|
||||
}
|
||||
|
||||
void MainWindow::selectAndGoTo(const QModelIndex &index)
|
||||
{
|
||||
m_treeView->scrollTo(index, QAbstractItemView::PositionAtCenter);
|
||||
m_treeView->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
void MainWindow::detectFile()
|
||||
{
|
||||
const QString fileName = QFileDialog::getOpenFileName(this, tr("Choose File"));
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
QMimeDatabase mimeDatabase;
|
||||
const QFileInfo fi(fileName);
|
||||
const QMimeType mimeType = mimeDatabase.mimeTypeForFile(fi);
|
||||
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()));
|
||||
selectAndGoTo(index);
|
||||
} else {
|
||||
QMessageBox::information(this, tr("Unknown File Type"),
|
||||
tr("The type of %1 could not be determined.")
|
||||
.arg(QDir::toNativeSeparators(fileName)));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::updateFindActions()
|
||||
{
|
||||
const bool findNextPreviousEnabled = m_findMatches.size() > 1;
|
||||
m_findNextAction->setEnabled(findNextPreviousEnabled);
|
||||
m_findPreviousAction->setEnabled(findNextPreviousEnabled);
|
||||
}
|
||||
|
||||
void MainWindow::findPrevious()
|
||||
{
|
||||
if (--m_findIndex < 0)
|
||||
m_findIndex = m_findMatches.size() - 1;
|
||||
if (m_findIndex >= 0)
|
||||
selectAndGoTo(m_findMatches.at(m_findIndex));
|
||||
}
|
||||
|
||||
void MainWindow::findNext()
|
||||
{
|
||||
if (++m_findIndex >= m_findMatches.size())
|
||||
m_findIndex = 0;
|
||||
if (m_findIndex < m_findMatches.size())
|
||||
selectAndGoTo(m_findMatches.at(m_findIndex));
|
||||
}
|
||||
|
||||
void MainWindow::find()
|
||||
{
|
||||
QInputDialog inputDialog(this);
|
||||
inputDialog.setWindowTitle(tr("Find"));
|
||||
inputDialog.setLabelText(tr("Text:"));
|
||||
if (inputDialog.exec() != QDialog::Accepted)
|
||||
return;
|
||||
const QString value = inputDialog.textValue().trimmed();
|
||||
if (value.isEmpty())
|
||||
return;
|
||||
|
||||
m_findMatches.clear();
|
||||
m_findIndex = 0;
|
||||
const QList<QStandardItem *> items =
|
||||
m_model->findItems(value, Qt::MatchContains | Qt::MatchFixedString | Qt::MatchRecursive);
|
||||
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));
|
||||
updateFindActions();
|
||||
if (!m_findMatches.isEmpty())
|
||||
selectAndGoTo(m_findMatches.constFirst());
|
||||
}
|
42
examples/corelib/mimetypes/mimetypebrowser/mainwindow.h
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QModelIndexList>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QAction)
|
||||
QT_FORWARD_DECLARE_CLASS(QTextEdit)
|
||||
QT_FORWARD_DECLARE_CLASS(QTreeView)
|
||||
|
||||
class MimetypeModel;
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void currentChanged(const QModelIndex &);
|
||||
void detectFile();
|
||||
void find();
|
||||
void findNext();
|
||||
void findPrevious();
|
||||
|
||||
private:
|
||||
void selectAndGoTo(const QModelIndex &index);
|
||||
void updateFindActions();
|
||||
|
||||
MimetypeModel *m_model;
|
||||
QTreeView *m_treeView;
|
||||
QTextEdit *m_detailsText;
|
||||
QAction *m_findNextAction;
|
||||
QAction *m_findPreviousAction;
|
||||
QModelIndexList m_findMatches;
|
||||
int m_findIndex;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
@ -0,0 +1,17 @@
|
||||
TEMPLATE = app
|
||||
QT += widgets
|
||||
requires(qtConfig(treeview))
|
||||
CONFIG -= app_bundle
|
||||
CONFIG += c++11
|
||||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
mimetypemodel.cpp \
|
||||
mainwindow.cpp
|
||||
|
||||
HEADERS += \
|
||||
mimetypemodel.h \
|
||||
mainwindow.h
|
||||
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/mimetypes/mimetypebrowser
|
||||
INSTALLS += target
|
164
examples/corelib/mimetypes/mimetypebrowser/mimetypemodel.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "mimetypemodel.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QIcon>
|
||||
#include <QMimeDatabase>
|
||||
#include <QTextStream>
|
||||
#include <QVariant>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
Q_DECLARE_METATYPE(QMimeType)
|
||||
|
||||
typedef QList<QStandardItem *> StandardItemList;
|
||||
|
||||
enum { mimeTypeRole = Qt::UserRole + 1, iconQueriedRole = Qt::UserRole + 2 };
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
bool operator<(const QMimeType &t1, const QMimeType &t2)
|
||||
{
|
||||
return t1.name() < t2.name();
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
static StandardItemList createRow(const QMimeType &t)
|
||||
{
|
||||
const QVariant v = QVariant::fromValue(t);
|
||||
QStandardItem *nameItem = new QStandardItem(t.name());
|
||||
const Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
|
||||
nameItem->setData(v, mimeTypeRole);
|
||||
nameItem->setData(QVariant(false), iconQueriedRole);
|
||||
nameItem->setFlags(flags);
|
||||
nameItem->setToolTip(t.comment());
|
||||
return StandardItemList{nameItem};
|
||||
}
|
||||
|
||||
MimetypeModel::MimetypeModel(QObject *parent)
|
||||
: QStandardItemModel(0, ColumnCount, parent)
|
||||
{
|
||||
setHorizontalHeaderLabels(QStringList{tr("Name")});
|
||||
populate();
|
||||
}
|
||||
|
||||
QVariant MimetypeModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role != Qt::DecorationRole || !index.isValid() || index.data(iconQueriedRole).toBool())
|
||||
return QStandardItemModel::data(index, role);
|
||||
QStandardItem *item = itemFromIndex(index);
|
||||
const QString iconName = qvariant_cast<QMimeType>(item->data(mimeTypeRole)).iconName();
|
||||
if (!iconName.isEmpty())
|
||||
item->setIcon(QIcon::fromTheme(iconName));
|
||||
item->setData(QVariant(true), iconQueriedRole);
|
||||
return item->icon();
|
||||
}
|
||||
|
||||
QMimeType MimetypeModel::mimeType(const QModelIndex &index) const
|
||||
{
|
||||
return qvariant_cast<QMimeType>(index.data(mimeTypeRole));
|
||||
}
|
||||
|
||||
void MimetypeModel::populate()
|
||||
{
|
||||
typedef QList<QMimeType>::Iterator Iterator;
|
||||
|
||||
QMimeDatabase mimeDatabase;
|
||||
QList<QMimeType> allTypes = mimeDatabase.allMimeTypes();
|
||||
|
||||
// Move top level types to rear end of list, sort this partition,
|
||||
// create top level items and truncate the list.
|
||||
Iterator end = allTypes.end();
|
||||
const Iterator topLevelStart =
|
||||
std::stable_partition(allTypes.begin(), end,
|
||||
[](const QMimeType &t) { return !t.parentMimeTypes().isEmpty(); });
|
||||
std::stable_sort(topLevelStart, end);
|
||||
for (Iterator it = topLevelStart; it != end; ++it) {
|
||||
const StandardItemList row = createRow(*it);
|
||||
appendRow(row);
|
||||
m_nameIndexHash.insert(it->name(), indexFromItem(row.constFirst()));
|
||||
}
|
||||
allTypes.erase(topLevelStart, end);
|
||||
|
||||
while (!allTypes.isEmpty()) {
|
||||
// Find a type inheriting one that is already in the model.
|
||||
end = allTypes.end();
|
||||
auto nameIndexIt = m_nameIndexHash.constEnd();
|
||||
for (Iterator it = allTypes.begin(); it != end; ++it) {
|
||||
nameIndexIt = m_nameIndexHash.constFind(it->parentMimeTypes().constFirst());
|
||||
if (nameIndexIt != m_nameIndexHash.constEnd())
|
||||
break;
|
||||
}
|
||||
if (nameIndexIt == m_nameIndexHash.constEnd()) {
|
||||
qWarning() << "Orphaned mime types:" << allTypes;
|
||||
break;
|
||||
}
|
||||
|
||||
// Move types inheriting the parent type to rear end of list, sort this partition,
|
||||
// append the items to parent and truncate the list.
|
||||
const QString &parentName = nameIndexIt.key();
|
||||
const Iterator start =
|
||||
std::stable_partition(allTypes.begin(), end, [parentName](const QMimeType &t)
|
||||
{ return !t.parentMimeTypes().contains(parentName); });
|
||||
std::stable_sort(start, end);
|
||||
QStandardItem *parentItem = itemFromIndex(nameIndexIt.value());
|
||||
for (Iterator it = start; it != end; ++it) {
|
||||
const StandardItemList row = createRow(*it);
|
||||
parentItem->appendRow(row);
|
||||
m_nameIndexHash.insert(it->name(), indexFromItem(row.constFirst()));
|
||||
}
|
||||
allTypes.erase(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
QTextStream &operator<<(QTextStream &stream, const QStringList &list)
|
||||
{
|
||||
for (int i = 0, size = list.size(); i < size; ++i) {
|
||||
if (i)
|
||||
stream << ", ";
|
||||
stream << list.at(i);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
QString MimetypeModel::formatMimeTypeInfo(const QMimeType &t)
|
||||
{
|
||||
QString result;
|
||||
QTextStream str(&result);
|
||||
str << "<html><head/><body><h3><center>" << t.name() << "</center></h3><br><table>";
|
||||
|
||||
const QStringList &aliases = t.aliases();
|
||||
if (!aliases.isEmpty())
|
||||
str << "<tr><td>Aliases:</td><td>" << " (" << aliases << ')';
|
||||
|
||||
str << "</td></tr>"
|
||||
<< "<tr><td>Comment:</td><td>" << t.comment() << "</td></tr>"
|
||||
<< "<tr><td>Icon name:</td><td>" << t.iconName() << "</td></tr>"
|
||||
<< "<tr><td>Generic icon name</td><td>" << t.genericIconName() << "</td></tr>";
|
||||
|
||||
const QString &filter = t.filterString();
|
||||
if (!filter.isEmpty())
|
||||
str << "<tr><td>Filter:</td><td>" << t.filterString() << "</td></tr>";
|
||||
|
||||
const QStringList &patterns = t.globPatterns();
|
||||
if (!patterns.isEmpty())
|
||||
str << "<tr><td>Glob patterns:</td><td>" << patterns << "</td></tr>";
|
||||
|
||||
const QStringList &parentMimeTypes = t.parentMimeTypes();
|
||||
if (!parentMimeTypes.isEmpty())
|
||||
str << "<tr><td>Parent types:</td><td>" << t.parentMimeTypes() << "</td></tr>";
|
||||
|
||||
QStringList suffixes = t.suffixes();
|
||||
if (!suffixes.isEmpty()) {
|
||||
str << "<tr><td>Suffixes:</td><td>";
|
||||
const QString &preferredSuffix = t.preferredSuffix();
|
||||
if (!preferredSuffix.isEmpty()) {
|
||||
suffixes.removeOne(preferredSuffix);
|
||||
str << "<b>" << preferredSuffix << "</b> ";
|
||||
}
|
||||
str << suffixes << "</td></tr>";
|
||||
}
|
||||
str << "</table></body></html>";
|
||||
return result;
|
||||
}
|
37
examples/corelib/mimetypes/mimetypebrowser/mimetypemodel.h
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef MIMETYPEMODEL_H
|
||||
#define MIMETYPEMODEL_H
|
||||
|
||||
#include <QStandardItemModel>
|
||||
#include <QHash>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QMimeType)
|
||||
|
||||
class MimetypeModel : public QStandardItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Columns { NameColumn, ColumnCount };
|
||||
|
||||
explicit MimetypeModel(QObject *parent = nullptr);
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
QMimeType mimeType(const QModelIndex &) const;
|
||||
|
||||
QModelIndex indexForMimeType(const QString &name) const
|
||||
{ return m_nameIndexHash.value(name); }
|
||||
|
||||
static QString formatMimeTypeInfo(const QMimeType &);
|
||||
|
||||
private:
|
||||
typedef QHash<QString, QModelIndex> NameIndexHash;
|
||||
|
||||
void populate();
|
||||
|
||||
NameIndexHash m_nameIndexHash;
|
||||
};
|
||||
|
||||
#endif // MIMETYPEMODEL_H
|
4
examples/corelib/mimetypes/mimetypes.pro
Normal file
@ -0,0 +1,4 @@
|
||||
TEMPLATE = subdirs
|
||||
|
||||
qtHaveModule(widgets): SUBDIRS += \
|
||||
mimetypebrowser
|
46
examples/corelib/permissions/CMakeLists.txt
Normal file
@ -0,0 +1,46 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(permissions LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/permissions")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(permissions
|
||||
MANUAL_FINALIZATION
|
||||
main.cpp
|
||||
)
|
||||
|
||||
set_target_properties(permissions PROPERTIES
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "io.qt.examples.permissions"
|
||||
QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android"
|
||||
)
|
||||
|
||||
target_link_libraries(permissions PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS permissions
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
||||
|
||||
if(APPLE AND NOT CMAKE_GENERATOR STREQUAL "Xcode")
|
||||
add_custom_command(TARGET permissions
|
||||
POST_BUILD COMMAND codesign -s - permissions.app)
|
||||
endif()
|
||||
|
||||
qt_finalize_executable(permissions)
|
59
examples/corelib/permissions/Info.plist
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
|
||||
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>Testing BluetoothAlways</string>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>Testing Calendars</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Testing Camera</string>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>Testing Contacts</string>
|
||||
<key>NSHealthShareUsageDescription</key>
|
||||
<string>Testing HealthShare</string>
|
||||
<key>NSHealthUpdateUsageDescription</key>
|
||||
<string>Testing HealthUpdate</string>
|
||||
<key>NSLocationUsageDescription</key>
|
||||
<string>Testing Location on macOS</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Testing Location when in use on iOS</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>Testing Location always and when in use on iOS</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Testing Microphone</string>
|
||||
|
||||
</dict>
|
||||
</plist>
|
53
examples/corelib/permissions/android/AndroidManifest.xml
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.qtproject.example"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
|
||||
android:versionName="-- %%INSERT_VERSION_NAME%% --">
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
||||
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
|
||||
<!-- %%INSERT_PERMISSIONS -->
|
||||
<!-- %%INSERT_FEATURES -->
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true" />
|
||||
<application
|
||||
android:name="org.qtproject.qt.android.bindings.QtApplication"
|
||||
android:hardwareAccelerated="true"
|
||||
android:label="-- %%INSERT_APP_NAME%% --"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:allowNativeHeapPointerTagging="false"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupOnly="false">
|
||||
<activity
|
||||
android:name="org.qtproject.qt.android.bindings.QtActivity"
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
|
||||
android:label="-- %%INSERT_APP_NAME%% --"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="unspecified"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.extract_android_style"
|
||||
android:value="minimal" />
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
87
examples/corelib/permissions/main.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtCore/qmetaobject.h>
|
||||
#include <QtWidgets/qapplication.h>
|
||||
#include <QtWidgets/qwidget.h>
|
||||
#include <QtWidgets/qpushbutton.h>
|
||||
#include <QtWidgets/qlayout.h>
|
||||
#include <QtWidgets/qmessagebox.h>
|
||||
|
||||
#if !QT_CONFIG(permissions)
|
||||
#error "This example requires the permissions feature, which is not enabled on this platform"
|
||||
#endif
|
||||
|
||||
#include <QtCore/qpermissions.h>
|
||||
|
||||
class PermissionWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PermissionWidget(QWidget *parent = nullptr) : QWidget(parent)
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
static const QPermission permissions[] = {
|
||||
QCameraPermission{},
|
||||
QMicrophonePermission{},
|
||||
QBluetoothPermission{},
|
||||
QContactsPermission{},
|
||||
QCalendarPermission{},
|
||||
QLocationPermission{}
|
||||
};
|
||||
|
||||
for (auto permission : permissions) {
|
||||
auto permissionName = QString::fromLatin1(permission.type().name());
|
||||
QPushButton *button = new QPushButton(permissionName.sliced(1, permissionName.length() - 11));
|
||||
connect(button, &QPushButton::clicked, this, &PermissionWidget::buttonClicked);
|
||||
button->setProperty("permission", QVariant::fromValue(permission));
|
||||
layout->addWidget(button);
|
||||
}
|
||||
|
||||
QPalette pal = palette();
|
||||
pal.setBrush(QPalette::Window, QGradient(QGradient::HappyAcid));
|
||||
setPalette(pal);
|
||||
}
|
||||
|
||||
private:
|
||||
void buttonClicked()
|
||||
{
|
||||
auto *button = static_cast<QPushButton*>(sender());
|
||||
|
||||
auto permission = button->property("permission").value<QPermission>();
|
||||
Q_ASSERT(permission.type().isValid());
|
||||
|
||||
switch (qApp->checkPermission(permission)) {
|
||||
case Qt::PermissionStatus::Undetermined:
|
||||
qApp->requestPermission(permission, this,
|
||||
[button](const QPermission &permission) {
|
||||
Q_UNUSED(permission);
|
||||
emit button->clicked(); // Try again
|
||||
}
|
||||
);
|
||||
return;
|
||||
case Qt::PermissionStatus::Denied:
|
||||
QMessageBox::warning(this, button->text(),
|
||||
tr("Permission is needed to use %1. Please grant permission "\
|
||||
"to this application in the system settings.").arg(button->text()));
|
||||
return;
|
||||
case Qt::PermissionStatus::Granted:
|
||||
break; // Proceed
|
||||
}
|
||||
|
||||
// All good, can use the feature
|
||||
QMessageBox::information(this, button->text(),
|
||||
tr("Accessing %1").arg(button->text()));
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
PermissionWidget widget;
|
||||
widget.show();
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
#include "main.moc"
|
6
examples/corelib/platform/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
if(ANDROID)
|
||||
add_subdirectory(androidnotifier)
|
||||
endif()
|
53
examples/corelib/platform/androidnotifier/CMakeLists.txt
Normal file
@ -0,0 +1,53 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(androidnotifier LANGUAGES CXX)
|
||||
|
||||
if(NOT ANDROID)
|
||||
message(FATAL_ERROR "Example only works on Android")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/platform/androidnotifier")
|
||||
|
||||
qt_add_executable(androidnotifier
|
||||
MANUAL_FINALIZATION
|
||||
main.cpp
|
||||
notificationclient.cpp
|
||||
notificationclient.h
|
||||
)
|
||||
|
||||
target_link_libraries(androidnotifier PRIVATE
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
set_property(TARGET androidnotifier APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||
|
||||
qt_finalize_executable(androidnotifier)
|
||||
|
||||
set(qml_resource_files
|
||||
"images/happy.png"
|
||||
"images/sad.png"
|
||||
)
|
||||
|
||||
qt_add_resources(androidnotifier "main"
|
||||
PREFIX
|
||||
"/"
|
||||
FILES
|
||||
${qml_resource_files}
|
||||
)
|
||||
|
||||
install(TARGETS androidnotifier
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.qtproject.example.androidnotifier"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
<!-- The comment below will be replaced with dependencies permissions upon deployment.
|
||||
Remove the comment if you do not require these default permissions. -->
|
||||
<!-- %%INSERT_PERMISSIONS -->
|
||||
|
||||
<!-- The comment below will be replaced with dependencies permissions upon deployment.
|
||||
Remove the comment if you do not require these default features. -->
|
||||
<!-- %%INSERT_FEATURES -->
|
||||
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true" />
|
||||
<application
|
||||
android:name="org.qtproject.qt.android.bindings.QtApplication"
|
||||
android:extractNativeLibs="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:label="Qt Notifier"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
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:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.background_running"
|
||||
android:value="false"/>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.extract_android_style"
|
||||
android:value="none" />
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 13 KiB |
@ -0,0 +1,46 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
package org.qtproject.example.androidnotifier;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.app.NotificationChannel;
|
||||
|
||||
public class NotificationClient
|
||||
{
|
||||
public static void notify(Context context, String message) {
|
||||
try {
|
||||
NotificationManager m_notificationManager = (NotificationManager)
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
Notification.Builder m_builder;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
int importance = NotificationManager.IMPORTANCE_DEFAULT;
|
||||
NotificationChannel notificationChannel;
|
||||
notificationChannel = new NotificationChannel("Qt", "Qt Notifier", importance);
|
||||
m_notificationManager.createNotificationChannel(notificationChannel);
|
||||
m_builder = new Notification.Builder(context, notificationChannel.getId());
|
||||
} else {
|
||||
m_builder = new Notification.Builder(context);
|
||||
}
|
||||
|
||||
Bitmap icon = BitmapFactory.decodeResource(context.getResources(), R.drawable.icon);
|
||||
m_builder.setSmallIcon(R.drawable.icon)
|
||||
.setLargeIcon(icon)
|
||||
.setContentTitle("A message from Qt!")
|
||||
.setContentText(message)
|
||||
.setDefaults(Notification.DEFAULT_SOUND)
|
||||
.setColor(Color.GREEN)
|
||||
.setAutoCancel(true);
|
||||
|
||||
m_notificationManager.notify(0, m_builder.build());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
QT += core gui
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
notificationclient.cpp
|
||||
|
||||
HEADERS += \
|
||||
notificationclient.h
|
||||
|
||||
RESOURCES += \
|
||||
main.qrc
|
||||
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/platform/androidnotifier
|
||||
INSTALLS += target
|
||||
|
||||
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
|
||||
OTHER_FILES += \
|
||||
android/src/org/qtproject/example/androidnotifier/NotificationClient.java \
|
||||
android/AndroidManifest.xml
|
After Width: | Height: | Size: 47 KiB |
@ -0,0 +1,60 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\title Qt Android Notifier
|
||||
\example platform/androidnotifier
|
||||
\brief Demonstrates calling Java code from Qt in an Android application.
|
||||
|
||||
\image androidnotifier.png
|
||||
|
||||
This example demonstrates how to add a custom Java class to an Android
|
||||
application, and how to call it using the JNI convenience APIs in Qt.
|
||||
|
||||
Click on one of the smiley faces to send a notification in the status bar
|
||||
of the Android screen.
|
||||
|
||||
\include examples-run.qdocinc
|
||||
|
||||
\section1 Calling Java Methods from C++ Code
|
||||
|
||||
We define a custom Java class called \c NotificationClient in the
|
||||
NotificationClient.java file:
|
||||
|
||||
\quotefromfile platform/androidnotifier/android/src/org/qtproject/example/androidnotifier/NotificationClient.java
|
||||
\skipto org.qtproject.example.androidnotifier
|
||||
\printuntil /^\}/
|
||||
|
||||
In the NotificationClient C++ class header file, \c notificationclient.h, we
|
||||
declare a simple C++ API to display notifications on an Android device. It
|
||||
consists of a single string property, \c notification, and a slot,
|
||||
\c updateAndroidNotification(), that calls the Java code:
|
||||
|
||||
\snippet platform/androidnotifier/notificationclient.h Qt Notification Class
|
||||
|
||||
We connect the \c notificationChanged() signal to the
|
||||
\c updateAndroidNotification() slot to update the notification text when the
|
||||
\c notification text changes:
|
||||
|
||||
\snippet platform/androidnotifier/notificationclient.cpp notification changed signal
|
||||
|
||||
The \c updateAndroidNotification() function calls the Java method responsible
|
||||
for sending the notification from the Android platform APIs. First, we construct
|
||||
a Java string \c jstring from the notification string, then pass the \c jstring
|
||||
object as a parameter to the \c notify() method in Java:
|
||||
|
||||
\snippet platform/androidnotifier/notificationclient.cpp Send notification message to Java
|
||||
|
||||
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
|
||||
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
|
||||
the following code to change the notification text if either of them are
|
||||
clicked:
|
||||
|
||||
\snippet platform/androidnotifier/main.cpp Connect button signals
|
||||
|
||||
\sa {Qt for Android}
|
||||
*/
|
BIN
examples/corelib/platform/androidnotifier/images/happy.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
examples/corelib/platform/androidnotifier/images/sad.png
Normal file
After Width: | Height: | Size: 40 KiB |
56
examples/corelib/platform/androidnotifier/main.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "notificationclient.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QWidget>
|
||||
#include <QPushButton>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QFont>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
|
||||
QWidget widget;
|
||||
QPushButton happyButton;
|
||||
happyButton.setIcon(QIcon(":/images/happy.png"));
|
||||
happyButton.setIconSize(QSize(happyButton.width(), 120));
|
||||
|
||||
QPushButton sadButton;
|
||||
sadButton.setIcon(QIcon(":/images/sad.png"));
|
||||
sadButton.setIconSize(QSize(sadButton.width(), 120));
|
||||
|
||||
QVBoxLayout mainLayout;
|
||||
QHBoxLayout labelLayout;
|
||||
QLabel label = QLabel("Click a smiley to notify your mood");
|
||||
QFont font = label.font();
|
||||
font.setPointSize(20);
|
||||
label.setFont(font);
|
||||
labelLayout.addWidget(&label);
|
||||
labelLayout.setAlignment(Qt::AlignHCenter);
|
||||
mainLayout.addLayout(&labelLayout);
|
||||
|
||||
QHBoxLayout smileysLayout;
|
||||
smileysLayout.addWidget(&sadButton);
|
||||
smileysLayout.addWidget(&happyButton);
|
||||
smileysLayout.setAlignment(Qt::AlignCenter);
|
||||
mainLayout.addLayout(&smileysLayout);
|
||||
widget.setLayout(&mainLayout);
|
||||
|
||||
//! [Connect button signals]
|
||||
QObject::connect(&happyButton, &QPushButton::clicked, []() {
|
||||
NotificationClient().setNotification("The user is happy!");
|
||||
});
|
||||
|
||||
QObject::connect(&sadButton, &QPushButton::clicked, []() {
|
||||
NotificationClient().setNotification("The user is sad!");
|
||||
});
|
||||
//! [Connect button signals]
|
||||
|
||||
widget.show();
|
||||
return a.exec();
|
||||
}
|
||||
|
6
examples/corelib/platform/androidnotifier/main.qrc
Normal file
@ -0,0 +1,6 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>images/happy.png</file>
|
||||
<file>images/sad.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
@ -0,0 +1,43 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "notificationclient.h"
|
||||
|
||||
#include <QtCore/qjniobject.h>
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
|
||||
NotificationClient::NotificationClient(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
connect(this, &NotificationClient::notificationChanged,
|
||||
this, &NotificationClient::updateAndroidNotification);
|
||||
}
|
||||
|
||||
void NotificationClient::setNotification(const QString ¬ification)
|
||||
{
|
||||
if (m_notification == notification)
|
||||
return;
|
||||
|
||||
//! [notification changed signal]
|
||||
m_notification = notification;
|
||||
emit notificationChanged();
|
||||
//! [notification changed signal]
|
||||
}
|
||||
|
||||
QString NotificationClient::notification() const
|
||||
{
|
||||
return m_notification;
|
||||
}
|
||||
|
||||
//! [Send notification message to Java]
|
||||
void NotificationClient::updateAndroidNotification()
|
||||
{
|
||||
QJniObject javaNotification = QJniObject::fromString(m_notification);
|
||||
QJniObject::callStaticMethod<void>(
|
||||
"org/qtproject/example/androidnotifier/NotificationClient",
|
||||
"notify",
|
||||
"(Landroid/content/Context;Ljava/lang/String;)V",
|
||||
QNativeInterface::QAndroidApplication::context(),
|
||||
javaNotification.object<jstring>());
|
||||
}
|
||||
//! [Send notification message to Java]
|
@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef NOTIFICATIONCLIENT_H
|
||||
#define NOTIFICATIONCLIENT_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
//! [Qt Notification Class]
|
||||
class NotificationClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit NotificationClient(QObject *parent = 0);
|
||||
|
||||
void setNotification(const QString ¬ification);
|
||||
QString notification() const;
|
||||
|
||||
signals:
|
||||
void notificationChanged();
|
||||
|
||||
private slots:
|
||||
void updateAndroidNotification();
|
||||
|
||||
private:
|
||||
QString m_notification;
|
||||
};
|
||||
//! [Qt Notification Class]
|
||||
#endif // NOTIFICATIONCLIENT_H
|
4
examples/corelib/platform/platform.pro
Normal file
@ -0,0 +1,4 @@
|
||||
TEMPLATE = subdirs
|
||||
CONFIG += no_docs_target
|
||||
|
||||
android: SUBDIRS += androidnotifier
|
12
examples/corelib/serialization/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
qt_internal_add_example(cbordump)
|
||||
qt_internal_add_example(convert)
|
||||
qt_internal_add_example(savegame)
|
||||
if(TARGET Qt6::Network AND TARGET Qt6::Widgets)
|
||||
qt_internal_add_example(rsslisting)
|
||||
endif()
|
||||
if(TARGET Qt6::Widgets)
|
||||
qt_internal_add_example(streambookmarks)
|
||||
endif()
|
29
examples/corelib/serialization/cbordump/CMakeLists.txt
Normal file
@ -0,0 +1,29 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(cbordump LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
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 PRIVATE
|
||||
Qt6::Core
|
||||
)
|
||||
|
||||
install(TARGETS cbordump
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
13
examples/corelib/serialization/cbordump/cbordump.pro
Normal file
@ -0,0 +1,13 @@
|
||||
QT += core
|
||||
QT -= gui
|
||||
|
||||
TARGET = cbordump
|
||||
CONFIG += cmdline
|
||||
|
||||
TEMPLATE = app
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/cbordump
|
||||
INSTALLS += target
|
||||
|
||||
SOURCES += main.cpp
|
BIN
examples/corelib/serialization/cbordump/doc/images/cbordump.png
Normal file
After Width: | Height: | Size: 47 KiB |
@ -0,0 +1,54 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example serialization/cbordump
|
||||
\examplecategory {Input/Output}
|
||||
\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}
|
||||
*/
|
859
examples/corelib/serialization/cbordump/main.cpp
Normal file
@ -0,0 +1,859 @@
|
||||
// Copyright (C) 2018 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QCborStreamReader>
|
||||
#include <QCommandLineParser>
|
||||
#include <QCommandLineOption>
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QLocale>
|
||||
#include <QStack>
|
||||
|
||||
#include <locale.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
* To regenerate:
|
||||
* curl -O https://www.iana.org/assignments/cbor-tags/cbor-tags.xml
|
||||
* xsltproc tag-transform.xslt cbor-tags.xml
|
||||
*
|
||||
* The XHTML URL mentioned in the comment below is a human-readable version of
|
||||
* the same resource.
|
||||
*/
|
||||
|
||||
/* TODO (if possible): fix XSLT to replace each newline and surrounding space in
|
||||
a semantics entry with a single space, instead of using a raw string to wrap
|
||||
each, propagating the spacing from the XML to the output of cbordump. Also
|
||||
auto-purge dangling spaces from the ends of generated lines.
|
||||
*/
|
||||
|
||||
// GENERATED CODE
|
||||
struct CborTagDescription
|
||||
{
|
||||
QCborTag tag;
|
||||
const char *description; // with space and parentheses
|
||||
};
|
||||
|
||||
// CBOR Tags
|
||||
static const CborTagDescription tagDescriptions[] = {
|
||||
// from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
|
||||
{ QCborTag(0),
|
||||
R"r( (Standard date/time string; see Section 3.4.1 [RFC8949]))r" },
|
||||
{ QCborTag(1),
|
||||
R"r( (Epoch-based date/time; see Section 3.4.2 [RFC8949]))r" },
|
||||
{ QCborTag(2),
|
||||
R"r( (Positive bignum; see Section 3.4.3 [RFC8949]))r" },
|
||||
{ QCborTag(3),
|
||||
R"r( (Negative bignum; see Section 3.4.3 [RFC8949]))r" },
|
||||
{ QCborTag(4),
|
||||
R"r( (Decimal fraction; see Section 3.4.4 [RFC8949]))r" },
|
||||
{ QCborTag(5),
|
||||
R"r( (Bigfloat; see Section 3.4.4 [RFC8949]))r" },
|
||||
{ QCborTag(16),
|
||||
R"r( (COSE Single Recipient Encrypted Data Object [RFC9052]))r" },
|
||||
{ QCborTag(17),
|
||||
R"r( (COSE Mac w/o Recipients Object [RFC9052]))r" },
|
||||
{ QCborTag(18),
|
||||
R"r( (COSE Single Signer Data Object [RFC9052]))r" },
|
||||
{ QCborTag(19),
|
||||
R"r( (COSE standalone V2 countersignature [RFC9338]))r" },
|
||||
{ QCborTag(21),
|
||||
R"r( (Expected conversion to base64url encoding; see Section 3.4.5.2 [RFC8949]))r" },
|
||||
{ QCborTag(22),
|
||||
R"r( (Expected conversion to base64 encoding; see Section 3.4.5.2 [RFC8949]))r" },
|
||||
{ QCborTag(23),
|
||||
R"r( (Expected conversion to base16 encoding; see Section 3.4.5.2 [RFC8949]))r" },
|
||||
{ QCborTag(24),
|
||||
R"r( (Encoded CBOR data item; see Section 3.4.5.1 [RFC8949]))r" },
|
||||
{ QCborTag(25),
|
||||
R"r( (reference the nth previously seen string))r" },
|
||||
{ QCborTag(26),
|
||||
R"r( (Serialised Perl object with classname and constructor arguments))r" },
|
||||
{ QCborTag(27),
|
||||
R"r( (Serialised language-independent object with type name and constructor arguments))r" },
|
||||
{ QCborTag(28),
|
||||
R"r( (mark value as (potentially) shared))r" },
|
||||
{ QCborTag(29),
|
||||
R"r( (reference nth marked value))r" },
|
||||
{ QCborTag(30),
|
||||
R"r( (Rational number))r" },
|
||||
{ QCborTag(31),
|
||||
R"r( (Absent value in a CBOR Array))r" },
|
||||
{ QCborTag(32),
|
||||
R"r( (URI; see Section 3.4.5.3 [RFC8949]))r" },
|
||||
{ QCborTag(33),
|
||||
R"r( (base64url; see Section 3.4.5.3 [RFC8949]))r" },
|
||||
{ QCborTag(34),
|
||||
R"r( (base64; see Section 3.4.5.3 [RFC8949]))r" },
|
||||
{ QCborTag(35),
|
||||
R"r( (Regular expression; see Section 2.4.4.3 [RFC7049]))r" },
|
||||
{ QCborTag(36),
|
||||
R"r( (MIME message; see Section 3.4.5.3 [RFC8949]))r" },
|
||||
{ QCborTag(37),
|
||||
R"r( (Binary UUID (RFC4122, Section 4.1.2)))r" },
|
||||
{ QCborTag(38),
|
||||
R"r( (Language-tagged string [RFC9290]))r" },
|
||||
{ QCborTag(39),
|
||||
R"r( (Identifier))r" },
|
||||
{ QCborTag(40),
|
||||
R"r( (Multi-dimensional Array, row-major order [RFC8746]))r" },
|
||||
{ QCborTag(41),
|
||||
R"r( (Homogeneous Array [RFC8746]))r" },
|
||||
{ QCborTag(42),
|
||||
R"r( (IPLD content identifier))r" },
|
||||
{ QCborTag(43),
|
||||
R"r( (YANG bits datatype; see Section 6.7. [RFC9254]))r" },
|
||||
{ QCborTag(44),
|
||||
R"r( (YANG enumeration datatype; see Section 6.6. [RFC9254]))r" },
|
||||
{ QCborTag(45),
|
||||
R"r( (YANG identityref datatype; see Section 6.10. [RFC9254]))r" },
|
||||
{ QCborTag(46),
|
||||
R"r( (YANG instance-identifier datatype; see Section 6.13. [RFC9254]))r" },
|
||||
{ QCborTag(47),
|
||||
R"r( (YANG Schema Item iDentifier (sid); see Section 3.2. [RFC9254]))r" },
|
||||
{ QCborTag(52),
|
||||
R"r( (IPv4, [prefixlen,IPv4], [IPv4,prefixpart] [RFC9164]))r" },
|
||||
{ QCborTag(54),
|
||||
R"r( (IPv6, [prefixlen,IPv6], [IPv6,prefixpart] [RFC9164]))r" },
|
||||
{ QCborTag(61),
|
||||
R"r( (CBOR Web Token (CWT) [RFC8392]))r" },
|
||||
{ QCborTag(63),
|
||||
R"r( (Encoded CBOR Sequence ))r" },
|
||||
{ QCborTag(64),
|
||||
R"r( (uint8 Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(65),
|
||||
R"r( (uint16, big endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(66),
|
||||
R"r( (uint32, big endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(67),
|
||||
R"r( (uint64, big endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(68),
|
||||
R"r( (uint8 Typed Array, clamped arithmetic [RFC8746]))r" },
|
||||
{ QCborTag(69),
|
||||
R"r( (uint16, little endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(70),
|
||||
R"r( (uint32, little endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(71),
|
||||
R"r( (uint64, little endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(72),
|
||||
R"r( (sint8 Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(73),
|
||||
R"r( (sint16, big endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(74),
|
||||
R"r( (sint32, big endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(75),
|
||||
R"r( (sint64, big endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(76),
|
||||
R"r( ((reserved) [RFC8746]))r" },
|
||||
{ QCborTag(77),
|
||||
R"r( (sint16, little endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(78),
|
||||
R"r( (sint32, little endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(79),
|
||||
R"r( (sint64, little endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(80),
|
||||
R"r( (IEEE 754 binary16, big endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(81),
|
||||
R"r( (IEEE 754 binary32, big endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(82),
|
||||
R"r( (IEEE 754 binary64, big endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(83),
|
||||
R"r( (IEEE 754 binary128, big endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(84),
|
||||
R"r( (IEEE 754 binary16, little endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(85),
|
||||
R"r( (IEEE 754 binary32, little endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(86),
|
||||
R"r( (IEEE 754 binary64, little endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(87),
|
||||
R"r( (IEEE 754 binary128, little endian, Typed Array [RFC8746]))r" },
|
||||
{ QCborTag(96),
|
||||
R"r( (COSE Encrypted Data Object [RFC9052]))r" },
|
||||
{ QCborTag(97),
|
||||
R"r( (COSE MACed Data Object [RFC9052]))r" },
|
||||
{ QCborTag(98),
|
||||
R"r( (COSE Signed Data Object [RFC9052]))r" },
|
||||
{ QCborTag(100),
|
||||
R"r( (Number of days since the epoch date 1970-01-01 [RFC8943]))r" },
|
||||
{ QCborTag(101),
|
||||
R"r( (alternatives as given by the uint + 128; see Section 9.1))r" },
|
||||
{ QCborTag(103),
|
||||
R"r( (Geographic Coordinates))r" },
|
||||
{ QCborTag(104),
|
||||
R"r( (Geographic Coordinate Reference System WKT or EPSG number))r" },
|
||||
{ QCborTag(110),
|
||||
R"r( (relative object identifier (BER encoding); SDNV sequence [RFC9090]))r" },
|
||||
{ QCborTag(111),
|
||||
R"r( (object identifier (BER encoding) [RFC9090]))r" },
|
||||
{ QCborTag(112),
|
||||
R"r( (object identifier (BER encoding), relative to 1.3.6.1.4.1 [RFC9090]))r" },
|
||||
{ QCborTag(120),
|
||||
R"r( (Internet of Things Data Point))r" },
|
||||
{ QCborTag(260),
|
||||
R"r( (Network Address (IPv4 or IPv6 or MAC Address) (DEPRECATED in favor of 52 and 54
|
||||
for IP addresses) [http://www.employees.oRg/~RaviR/CboR-netwoRk.txt]))r" },
|
||||
{ QCborTag(261),
|
||||
R"r( (Network Address Prefix (IPv4 or IPv6 Address + Mask Length) (DEPRECATED in favor of 52 and 54
|
||||
for IP addresses) [https://github.Com/toRaviR/CBOR-Tag-SpeCs/blob/masteR/netwoRkPReFix.md]))r" },
|
||||
{ QCborTag(271),
|
||||
R"r( (DDoS Open Threat Signaling (DOTS) signal channel object,
|
||||
as defined in [RFC9132]))r" },
|
||||
{ QCborTag(1004),
|
||||
R"r( ( full-date string [RFC8943]))r" },
|
||||
{ QCborTag(1040),
|
||||
R"r( (Multi-dimensional Array, column-major order [RFC8746]))r" },
|
||||
{ QCborTag(55799),
|
||||
R"r( (Self-described CBOR; see Section 3.4.6 [RFC8949]))r" },
|
||||
{ QCborTag(55800),
|
||||
R"r( (indicates that the file contains CBOR Sequences [RFC9277]))r" },
|
||||
{ QCborTag(55801),
|
||||
R"r( (indicates that the file starts with a CBOR-Labeled Non-CBOR Data label. [RFC9277]))r" },
|
||||
{ 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
|
||||
};
|
||||
|
||||
//! [0]
|
||||
struct CborDumper
|
||||
{
|
||||
enum DumpOption {
|
||||
ShowCompact = 0x01,
|
||||
ShowWidthIndicators = 0x02,
|
||||
ShowAnnotated = 0x04
|
||||
};
|
||||
Q_DECLARE_FLAGS(DumpOptions, DumpOption)
|
||||
|
||||
CborDumper(QFile *f, DumpOptions opts_);
|
||||
QCborError dump();
|
||||
|
||||
private:
|
||||
void dumpOne(int nestingLevel);
|
||||
void dumpOneDetailed(int nestingLevel);
|
||||
|
||||
void printByteArray(const QByteArray &ba);
|
||||
void printWidthIndicator(quint64 value, char space = '\0');
|
||||
void printStringWidthIndicator(quint64 value);
|
||||
|
||||
QCborStreamReader reader;
|
||||
QByteArray data;
|
||||
QStack<quint8> byteArrayEncoding;
|
||||
qint64 offset = 0;
|
||||
DumpOptions opts;
|
||||
};
|
||||
//! [0]
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(CborDumper::DumpOptions)
|
||||
|
||||
static int cborNumberSize(quint64 value)
|
||||
{
|
||||
int normalSize = 1;
|
||||
if (value > std::numeric_limits<quint32>::max())
|
||||
normalSize += 8;
|
||||
else if (value > std::numeric_limits<quint16>::max())
|
||||
normalSize += 4;
|
||||
else if (value > std::numeric_limits<quint8>::max())
|
||||
normalSize += 2;
|
||||
else if (value >= Value8Bit)
|
||||
normalSize += 1;
|
||||
return normalSize;
|
||||
}
|
||||
|
||||
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));
|
||||
if (ptr) {
|
||||
// worked
|
||||
data = QByteArray::fromRawData(ptr, f->size());
|
||||
reader.addData(data);
|
||||
} else if ((opts & ShowAnnotated) || f->isSequential()) {
|
||||
// details requires full contents, so allocate memory
|
||||
data = f->readAll();
|
||||
reader.addData(data);
|
||||
} else {
|
||||
// just use the QIODevice
|
||||
reader.setDevice(f);
|
||||
}
|
||||
}
|
||||
|
||||
QCborError CborDumper::dump()
|
||||
{
|
||||
byteArrayEncoding << quint8(QCborKnownTags::ExpectedBase16);
|
||||
if (!reader.lastError()) {
|
||||
if (opts & ShowAnnotated)
|
||||
dumpOneDetailed(0);
|
||||
else
|
||||
dumpOne(0);
|
||||
}
|
||||
|
||||
QCborError err = reader.lastError();
|
||||
offset = reader.currentOffset();
|
||||
if (err) {
|
||||
fflush(stdout);
|
||||
fprintf(stderr, "cbordump: decoding failed at %lld: %s\n",
|
||||
offset, qPrintable(err.toString()));
|
||||
if (!data.isEmpty())
|
||||
fprintf(stderr, " bytes at %lld: %s\n", offset,
|
||||
data.mid(offset, 9).toHex(' ').constData());
|
||||
} else {
|
||||
if (!opts.testFlag(ShowAnnotated))
|
||||
printf("\n");
|
||||
if (offset < data.size() || (reader.device() && reader.device()->bytesAvailable()))
|
||||
fprintf(stderr, "Warning: bytes remaining at the end of the CBOR stream\n");
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
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(TypeInfo::is_integer);
|
||||
|
||||
double supremum = ldexp(1, TypeInfo::digits);
|
||||
if (v >= supremum)
|
||||
return false;
|
||||
|
||||
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)
|
||||
{
|
||||
if (qIsInf(v))
|
||||
return v < 0 ? QStringLiteral("-inf") : QStringLiteral("inf");
|
||||
if (qIsNaN(v))
|
||||
return QStringLiteral("nan");
|
||||
if (canConvertTo<qint64>(v))
|
||||
return QString::number(qint64(v)) + ".0" + suffix;
|
||||
if (canConvertTo<quint64>(v))
|
||||
return QString::number(quint64(v)) + ".0" + suffix;
|
||||
|
||||
QString s = QString::number(v, 'g', QLocale::FloatingPointShortest);
|
||||
if (!s.contains('.') && !s.contains('e'))
|
||||
s += '.';
|
||||
s += suffix;
|
||||
return s;
|
||||
};
|
||||
|
||||
void CborDumper::dumpOne(int nestingLevel)
|
||||
{
|
||||
QString indent(1, QLatin1Char(' '));
|
||||
QString indented = indent;
|
||||
if (!opts.testFlag(ShowCompact)) {
|
||||
indent = QLatin1Char('\n') + QString(4 * nestingLevel, QLatin1Char(' '));
|
||||
indented = QLatin1Char('\n') + QString(4 + 4 * nestingLevel, QLatin1Char(' '));
|
||||
}
|
||||
|
||||
switch (reader.type()) {
|
||||
case QCborStreamReader::UnsignedInteger: {
|
||||
quint64 u = reader.toUnsignedInteger();
|
||||
printf("%llu", u);
|
||||
reader.next();
|
||||
printWidthIndicator(u);
|
||||
return;
|
||||
}
|
||||
|
||||
case QCborStreamReader::NegativeInteger: {
|
||||
quint64 n = quint64(reader.toNegativeInteger());
|
||||
if (n == 0) // -2^64 (wrapped around)
|
||||
printf("-18446744073709551616");
|
||||
else
|
||||
printf("-%llu", n);
|
||||
reader.next();
|
||||
printWidthIndicator(n);
|
||||
return;
|
||||
}
|
||||
|
||||
case QCborStreamReader::ByteArray:
|
||||
case QCborStreamReader::String: {
|
||||
bool isLengthKnown = reader.isLengthKnown();
|
||||
if (!isLengthKnown) {
|
||||
printf("(_ ");
|
||||
++offset;
|
||||
}
|
||||
|
||||
QString comma;
|
||||
if (reader.isByteArray()) {
|
||||
auto r = reader.readByteArray();
|
||||
while (r.status == QCborStreamReader::Ok) {
|
||||
printf("%s", qPrintable(comma));
|
||||
printByteArray(r.data);
|
||||
printStringWidthIndicator(r.data.size());
|
||||
|
||||
r = reader.readByteArray();
|
||||
comma = QLatin1Char(',') + indented;
|
||||
}
|
||||
} else {
|
||||
auto r = reader.readString();
|
||||
while (r.status == QCborStreamReader::Ok) {
|
||||
printf("%s\"%s\"", qPrintable(comma), qPrintable(r.data));
|
||||
printStringWidthIndicator(r.data.toUtf8().size());
|
||||
|
||||
r = reader.readString();
|
||||
comma = QLatin1Char(',') + indented;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isLengthKnown && !reader.lastError())
|
||||
printf(")");
|
||||
break;
|
||||
}
|
||||
|
||||
case QCborStreamReader::Array:
|
||||
case QCborStreamReader::Map: {
|
||||
const char *delimiters = (reader.isArray() ? "[]" : "{}");
|
||||
printf("%c", delimiters[0]);
|
||||
|
||||
if (reader.isLengthKnown()) {
|
||||
quint64 len = reader.length();
|
||||
reader.enterContainer();
|
||||
printWidthIndicator(len, ' ');
|
||||
} else {
|
||||
reader.enterContainer();
|
||||
offset = reader.currentOffset();
|
||||
printf("_ ");
|
||||
}
|
||||
|
||||
const char *comma = "";
|
||||
while (!reader.lastError() && reader.hasNext()) {
|
||||
printf("%s%s", comma, qPrintable(indented));
|
||||
comma = ",";
|
||||
dumpOne(nestingLevel + 1);
|
||||
|
||||
if (reader.parentContainerType() != QCborStreamReader::Map)
|
||||
continue;
|
||||
if (reader.lastError())
|
||||
break;
|
||||
printf(": ");
|
||||
dumpOne(nestingLevel + 1);
|
||||
}
|
||||
|
||||
if (!reader.lastError()) {
|
||||
reader.leaveContainer();
|
||||
printf("%s%c", qPrintable(indent), delimiters[1]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case QCborStreamReader::Tag: {
|
||||
QCborTag tag = reader.toTag();
|
||||
printf("%llu", quint64(tag));
|
||||
|
||||
if (tag == QCborKnownTags::ExpectedBase16 || tag == QCborKnownTags::ExpectedBase64
|
||||
|| tag == QCborKnownTags::ExpectedBase64url)
|
||||
byteArrayEncoding.push(quint8(tag));
|
||||
|
||||
if (reader.next()) {
|
||||
printWidthIndicator(quint64(tag));
|
||||
printf("(");
|
||||
dumpOne(nestingLevel); // same level!
|
||||
printf(")");
|
||||
}
|
||||
|
||||
if (tag == QCborKnownTags::ExpectedBase16 || tag == QCborKnownTags::ExpectedBase64
|
||||
|| tag == QCborKnownTags::ExpectedBase64url)
|
||||
byteArrayEncoding.pop();
|
||||
break;
|
||||
}
|
||||
|
||||
case QCborStreamReader::SimpleType:
|
||||
switch (reader.toSimpleType()) {
|
||||
case QCborSimpleType::False:
|
||||
printf("false");
|
||||
break;
|
||||
case QCborSimpleType::True:
|
||||
printf("true");
|
||||
break;
|
||||
case QCborSimpleType::Null:
|
||||
printf("null");
|
||||
break;
|
||||
case QCborSimpleType::Undefined:
|
||||
printf("undefined");
|
||||
break;
|
||||
default:
|
||||
printf("simple(%u)", quint8(reader.toSimpleType()));
|
||||
break;
|
||||
}
|
||||
reader.next();
|
||||
break;
|
||||
|
||||
case QCborStreamReader::Float16:
|
||||
printf("%s", qPrintable(fpToString(reader.toFloat16(), "f16")));
|
||||
reader.next();
|
||||
break;
|
||||
case QCborStreamReader::Float:
|
||||
printf("%s", qPrintable(fpToString(reader.toFloat(), "f")));
|
||||
reader.next();
|
||||
break;
|
||||
case QCborStreamReader::Double:
|
||||
printf("%s", qPrintable(fpToString(reader.toDouble(), "")));
|
||||
reader.next();
|
||||
break;
|
||||
case QCborStreamReader::Invalid:
|
||||
return;
|
||||
}
|
||||
|
||||
offset = reader.currentOffset();
|
||||
}
|
||||
|
||||
void CborDumper::dumpOneDetailed(int nestingLevel)
|
||||
{
|
||||
auto tagDescription = [](QCborTag tag) {
|
||||
for (auto entry : tagDescriptions) {
|
||||
if (entry.tag == tag)
|
||||
return entry.description;
|
||||
if (entry.tag > tag)
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
auto printOverlong = [](int actualSize, quint64 value) {
|
||||
if (cborNumberSize(value) != actualSize)
|
||||
printf(" (overlong)");
|
||||
};
|
||||
auto print = [=](const char *descr, const char *fmt, ...) {
|
||||
qint64 prevOffset = offset;
|
||||
offset = reader.currentOffset();
|
||||
if (prevOffset == offset)
|
||||
return;
|
||||
|
||||
QByteArray bytes = data.mid(prevOffset, offset - prevOffset);
|
||||
QByteArray indent(nestingLevel * 2, ' ');
|
||||
printf("%-50s # %s ", (indent + bytes.toHex(' ')).constData(), descr);
|
||||
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
vprintf(fmt, va);
|
||||
va_end(va);
|
||||
|
||||
if (strstr(fmt, "%ll")) {
|
||||
// Only works because all callers below that use %ll, use it as the
|
||||
// first arg
|
||||
va_start(va, fmt);
|
||||
quint64 value = va_arg(va, quint64);
|
||||
va_end(va);
|
||||
printOverlong(bytes.size(), value);
|
||||
}
|
||||
|
||||
puts("");
|
||||
};
|
||||
|
||||
auto printFp = [=](const char *descr, double 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) {
|
||||
constexpr qsizetype ChunkSizeLimit = std::numeric_limits<int>::max();
|
||||
QByteArray indent(nestingLevel * 2, ' ');
|
||||
const char *chunkStr = (reader.isLengthKnown() ? "" : "chunk ");
|
||||
int width = 48 - indent.size();
|
||||
int bytesPerLine = qMax(width / 3, 5);
|
||||
|
||||
qsizetype size = reader.currentStringChunkSize();
|
||||
if (size < 0)
|
||||
return; // error
|
||||
if (size >= ChunkSizeLimit) {
|
||||
fprintf(stderr, "String length too big, %lli\n", qint64(size));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// if asking for the current string chunk changes the offset, then it
|
||||
// was chunked
|
||||
print(descr, "(indeterminate length)");
|
||||
|
||||
QByteArray bytes(size, Qt::Uninitialized);
|
||||
auto r = reader.readStringChunk(bytes.data(), bytes.size());
|
||||
while (r.status == QCborStreamReader::Ok) {
|
||||
// We'll have to decode the length's width directly from CBOR...
|
||||
const char *lenstart = data.constData() + offset;
|
||||
const char *lenend = lenstart + 1;
|
||||
quint8 additionalInformation = (*lenstart & SmallValueMask);
|
||||
|
||||
// Decode this number directly from CBOR (see RFC 7049 section 2)
|
||||
if (additionalInformation >= Value8Bit) {
|
||||
if (additionalInformation == Value8Bit)
|
||||
lenend += 1;
|
||||
else if (additionalInformation == Value16Bit)
|
||||
lenend += 2;
|
||||
else if (additionalInformation == Value32Bit)
|
||||
lenend += 4;
|
||||
else
|
||||
lenend += 8;
|
||||
}
|
||||
|
||||
{
|
||||
QByteArray lenbytes = QByteArray::fromRawData(lenstart, lenend - lenstart);
|
||||
printf("%-50s # %s %slength %llu",
|
||||
(indent + lenbytes.toHex(' ')).constData(), descr, chunkStr, quint64(size));
|
||||
printOverlong(lenbytes.size(), size);
|
||||
puts("");
|
||||
}
|
||||
|
||||
offset = reader.currentOffset();
|
||||
|
||||
for (int i = 0; i < r.data; i += bytesPerLine) {
|
||||
QByteArray section = bytes.mid(i, bytesPerLine);
|
||||
printf(" %s%s", indent.constData(), section.toHex(' ').constData());
|
||||
|
||||
// print the decode
|
||||
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)
|
||||
printf("%c", ptr[j] >= 0x80 || ptr[j] < 0x20 ? '.' : ptr[j]);
|
||||
|
||||
puts("\"");
|
||||
}
|
||||
|
||||
// get the next chunk
|
||||
size = reader.currentStringChunkSize();
|
||||
if (size < 0)
|
||||
return; // error
|
||||
if (size >= ChunkSizeLimit) {
|
||||
fprintf(stderr, "String length too big, %lli\n", qint64(size));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
bytes.resize(size);
|
||||
r = reader.readStringChunk(bytes.data(), bytes.size());
|
||||
}
|
||||
};
|
||||
|
||||
if (reader.lastError())
|
||||
return;
|
||||
|
||||
switch (reader.type()) {
|
||||
case QCborStreamReader::UnsignedInteger: {
|
||||
quint64 u = reader.toUnsignedInteger();
|
||||
reader.next();
|
||||
if (u < 65536 || (u % 100000) == 0)
|
||||
print("Unsigned integer", "%llu", u);
|
||||
else
|
||||
print("Unsigned integer", "0x%llx", u);
|
||||
return;
|
||||
}
|
||||
|
||||
case QCborStreamReader::NegativeInteger: {
|
||||
quint64 n = quint64(reader.toNegativeInteger());
|
||||
reader.next();
|
||||
print("Negative integer", n == 0 ? "-18446744073709551616" : "-%llu", n);
|
||||
return;
|
||||
}
|
||||
|
||||
case QCborStreamReader::ByteArray:
|
||||
case QCborStreamReader::String: {
|
||||
bool isLengthKnown = reader.isLengthKnown();
|
||||
const char *descr = (reader.isString() ? "Text string" : "Byte string");
|
||||
if (!isLengthKnown)
|
||||
++nestingLevel;
|
||||
|
||||
printString(descr);
|
||||
if (reader.lastError())
|
||||
return;
|
||||
|
||||
if (!isLengthKnown) {
|
||||
--nestingLevel;
|
||||
print("Break", "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case QCborStreamReader::Array:
|
||||
case QCborStreamReader::Map: {
|
||||
const char *descr = (reader.isArray() ? "Array" : "Map");
|
||||
if (reader.isLengthKnown()) {
|
||||
quint64 len = reader.length();
|
||||
reader.enterContainer();
|
||||
print(descr, "length %llu", len);
|
||||
} else {
|
||||
reader.enterContainer();
|
||||
print(descr, "(indeterminate length)");
|
||||
}
|
||||
|
||||
while (!reader.lastError() && reader.hasNext())
|
||||
dumpOneDetailed(nestingLevel + 1);
|
||||
|
||||
if (!reader.lastError()) {
|
||||
reader.leaveContainer();
|
||||
print("Break", "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case QCborStreamReader::Tag: {
|
||||
QCborTag tag = reader.toTag();
|
||||
reader.next();
|
||||
print("Tag", "%llu%s", quint64(tag), tagDescription(tag));
|
||||
dumpOneDetailed(nestingLevel + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case QCborStreamReader::SimpleType: {
|
||||
QCborSimpleType st = reader.toSimpleType();
|
||||
reader.next();
|
||||
switch (st) {
|
||||
case QCborSimpleType::False:
|
||||
print("Simple Type", "false");
|
||||
break;
|
||||
case QCborSimpleType::True:
|
||||
print("Simple Type", "true");
|
||||
break;
|
||||
case QCborSimpleType::Null:
|
||||
print("Simple Type", "null");
|
||||
break;
|
||||
case QCborSimpleType::Undefined:
|
||||
print("Simple Type", "undefined");
|
||||
break;
|
||||
default:
|
||||
print("Simple Type", "%u", quint8(st));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case QCborStreamReader::Float16: {
|
||||
double d = reader.toFloat16();
|
||||
reader.next();
|
||||
printFp("Float16", d);
|
||||
break;
|
||||
}
|
||||
case QCborStreamReader::Float: {
|
||||
double d = reader.toFloat();
|
||||
reader.next();
|
||||
printFp("Float", d);
|
||||
break;
|
||||
}
|
||||
case QCborStreamReader::Double: {
|
||||
double d = reader.toDouble();
|
||||
reader.next();
|
||||
printFp("Double", d);
|
||||
break;
|
||||
}
|
||||
case QCborStreamReader::Invalid:
|
||||
return;
|
||||
}
|
||||
|
||||
offset = reader.currentOffset();
|
||||
}
|
||||
|
||||
void CborDumper::printByteArray(const QByteArray &ba)
|
||||
{
|
||||
switch (byteArrayEncoding.top()) {
|
||||
default:
|
||||
printf("h'%s'", ba.toHex(' ').constData());
|
||||
break;
|
||||
|
||||
case quint8(QCborKnownTags::ExpectedBase64):
|
||||
printf("b64'%s'", ba.toBase64().constData());
|
||||
break;
|
||||
|
||||
case quint8(QCborKnownTags::ExpectedBase64url):
|
||||
printf("b64'%s'", ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals).constData());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void printIndicator(quint64 value, qint64 previousOffset, qint64 offset, char space)
|
||||
{
|
||||
int normalSize = cborNumberSize(value);
|
||||
int actualSize = offset - previousOffset;
|
||||
|
||||
if (actualSize != normalSize) {
|
||||
Q_ASSERT(actualSize > 1);
|
||||
actualSize -= 2;
|
||||
printf("_%d", qPopulationCount(uint(actualSize)));
|
||||
if (space)
|
||||
printf("%c", space);
|
||||
}
|
||||
}
|
||||
|
||||
void CborDumper::printWidthIndicator(quint64 value, char space)
|
||||
{
|
||||
qint64 previousOffset = offset;
|
||||
offset = reader.currentOffset();
|
||||
if (opts & ShowWidthIndicators)
|
||||
printIndicator(value, previousOffset, offset, space);
|
||||
}
|
||||
|
||||
void CborDumper::printStringWidthIndicator(quint64 value)
|
||||
{
|
||||
qint64 previousOffset = offset;
|
||||
offset = reader.currentOffset();
|
||||
if (opts & ShowWidthIndicators)
|
||||
printIndicator(value, previousOffset, offset - uint(value), '\0');
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
setlocale(LC_ALL, "C");
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QStringLiteral("CBOR Dumper tool"));
|
||||
parser.addHelpOption();
|
||||
|
||||
QCommandLineOption compact({QStringLiteral("c"), QStringLiteral("compact")},
|
||||
QStringLiteral("Use compact form (no line breaks)"));
|
||||
parser.addOption(compact);
|
||||
|
||||
QCommandLineOption showIndicators({QStringLiteral("i"), QStringLiteral("indicators")},
|
||||
QStringLiteral("Show indicators for width of lengths and integrals"));
|
||||
parser.addOption(showIndicators);
|
||||
|
||||
QCommandLineOption verbose({QStringLiteral("a"), QStringLiteral("annotated")},
|
||||
QStringLiteral("Show bytes and annotated decoding"));
|
||||
parser.addOption(verbose);
|
||||
|
||||
parser.addPositionalArgument(QStringLiteral("[source]"),
|
||||
QStringLiteral("CBOR file to read from"));
|
||||
|
||||
parser.process(app);
|
||||
|
||||
CborDumper::DumpOptions opts;
|
||||
if (parser.isSet(compact))
|
||||
opts |= CborDumper::ShowCompact;
|
||||
if (parser.isSet(showIndicators))
|
||||
opts |= CborDumper::ShowWidthIndicators;
|
||||
if (parser.isSet(verbose))
|
||||
opts |= CborDumper::ShowAnnotated;
|
||||
|
||||
QStringList files = parser.positionalArguments();
|
||||
if (files.isEmpty())
|
||||
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)
|
||||
printf("/ From \"%s\" /\n", qPrintable(file));
|
||||
|
||||
CborDumper dumper(&f, opts);
|
||||
QCborError err = dumper.dump();
|
||||
if (err)
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
27
examples/corelib/serialization/cbordump/tag-transform.xslt
Normal file
@ -0,0 +1,27 @@
|
||||
<?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"/>),
|
||||
R"r( (<xsl:value-of select="a:semantics"/> <xsl:call-template name="xref"/>))r" },
|
||||
</xsl:template><!-- fn:replace(a:semantics, '\s+', ' ') -->
|
||||
<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>
|
36
examples/corelib/serialization/convert/CMakeLists.txt
Normal file
@ -0,0 +1,36 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(convert LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/convert")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(convert
|
||||
cborconverter.cpp cborconverter.h
|
||||
converter.h
|
||||
datastreamconverter.cpp datastreamconverter.h
|
||||
jsonconverter.cpp jsonconverter.h
|
||||
main.cpp
|
||||
nullconverter.cpp nullconverter.h
|
||||
textconverter.cpp textconverter.h
|
||||
xmlconverter.cpp xmlconverter.h
|
||||
)
|
||||
|
||||
target_link_libraries(convert PRIVATE
|
||||
Qt6::Core
|
||||
)
|
||||
|
||||
install(TARGETS convert
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
336
examples/corelib/serialization/convert/cborconverter.cpp
Normal file
@ -0,0 +1,336 @@
|
||||
// Copyright (C) 2018 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "cborconverter.h"
|
||||
|
||||
#include <QCborStreamReader>
|
||||
#include <QCborStreamWriter>
|
||||
#include <QCborMap>
|
||||
#include <QCborArray>
|
||||
#include <QCborValue>
|
||||
#include <QDataStream>
|
||||
#include <QFloat16>
|
||||
#include <QFile>
|
||||
#include <QMetaType>
|
||||
#include <QTextStream>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static CborConverter cborConverter;
|
||||
static CborDiagnosticDumper cborDiagnosticDumper;
|
||||
|
||||
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"
|
||||
" If 'always', won't check for loss of precision.\n"
|
||||
"float32=yes|always|no Write using single-precision floating point.\n"
|
||||
" If 'always', won't check for loss of precision.\n"
|
||||
"signature=yes|no Prepend the CBOR signature to the file output.\n"
|
||||
;
|
||||
|
||||
static const char diagnosticHelp[] =
|
||||
"extended=no|yes Use extended CBOR diagnostic format.\n"
|
||||
"line-wrap=yes|no Split output into multiple lines.\n"
|
||||
;
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QDataStream &operator<<(QDataStream &ds, QCborTag tag)
|
||||
{
|
||||
return ds << quint64(tag);
|
||||
}
|
||||
|
||||
QDataStream &operator>>(QDataStream &ds, QCborTag &tag)
|
||||
{
|
||||
quint64 v;
|
||||
ds >> v;
|
||||
tag = QCborTag(v);
|
||||
return ds;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
// We can't use QCborValue::toVariant directly because that would destroy
|
||||
// non-string keys in CBOR maps (QVariantMap can't handle those). Instead, we
|
||||
// have our own set of converter functions so we can keep the keys properly.
|
||||
|
||||
static QVariant convertCborValue(const QCborValue &value);
|
||||
|
||||
//! [0]
|
||||
static QVariant convertCborMap(const QCborMap &map)
|
||||
{
|
||||
VariantOrderedMap result;
|
||||
result.reserve(map.size());
|
||||
for (auto pair : map)
|
||||
result.append({ convertCborValue(pair.first), convertCborValue(pair.second) });
|
||||
return QVariant::fromValue(result);
|
||||
}
|
||||
|
||||
static QVariant convertCborArray(const QCborArray &array)
|
||||
{
|
||||
QVariantList result;
|
||||
result.reserve(array.size());
|
||||
for (auto value : array)
|
||||
result.append(convertCborValue(value));
|
||||
return result;
|
||||
}
|
||||
|
||||
static QVariant convertCborValue(const QCborValue &value)
|
||||
{
|
||||
if (value.isArray())
|
||||
return convertCborArray(value.toArray());
|
||||
if (value.isMap())
|
||||
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) {
|
||||
const QVariantList list = v.toList();
|
||||
QCborArray array;
|
||||
for (const QVariant &v : list)
|
||||
array.append(convertFromVariant(v, fpTrimming));
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
if (v.userType() == qMetaTypeId<VariantOrderedMap>()) {
|
||||
const auto m = qvariant_cast<VariantOrderedMap>(v);
|
||||
QCborMap map;
|
||||
for (const auto &pair : m)
|
||||
map.insert(convertFromVariant(pair.first, fpTrimming),
|
||||
convertFromVariant(pair.second, fpTrimming));
|
||||
return map;
|
||||
}
|
||||
|
||||
if (v.userType() == QMetaType::Double && fpTrimming != Double) {
|
||||
float f = float(v.toDouble());
|
||||
if (fpTrimming == Float16)
|
||||
return float(qfloat16(f));
|
||||
return f;
|
||||
}
|
||||
|
||||
return QCborValue::fromVariant(v);
|
||||
}
|
||||
//! [1]
|
||||
|
||||
QString CborDiagnosticDumper::name()
|
||||
{
|
||||
return QStringLiteral("cbor-dump");
|
||||
}
|
||||
|
||||
Converter::Direction CborDiagnosticDumper::directions()
|
||||
{
|
||||
return Out;
|
||||
}
|
||||
|
||||
Converter::Options CborDiagnosticDumper::outputOptions()
|
||||
{
|
||||
return SupportsArbitraryMapKeys;
|
||||
}
|
||||
|
||||
const char *CborDiagnosticDumper::optionsHelp()
|
||||
{
|
||||
return diagnosticHelp;
|
||||
}
|
||||
|
||||
bool CborDiagnosticDumper::probeFile(QIODevice *f)
|
||||
{
|
||||
Q_UNUSED(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant CborDiagnosticDumper::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
{
|
||||
Q_UNREACHABLE();
|
||||
Q_UNUSED(f);
|
||||
Q_UNUSED(outputConverter);
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
|
||||
{
|
||||
QCborValue::DiagnosticNotationOptions opts = QCborValue::LineWrapped;
|
||||
for (const QString &s : options) {
|
||||
QStringList pair = s.split('=');
|
||||
if (pair.size() == 2) {
|
||||
if (pair.first() == "line-wrap") {
|
||||
opts &= ~QCborValue::LineWrapped;
|
||||
if (pair.last() == "yes") {
|
||||
opts |= QCborValue::LineWrapped;
|
||||
continue;
|
||||
} else if (pair.last() == "no") {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (pair.first() == "extended") {
|
||||
opts &= ~QCborValue::ExtendedFormat;
|
||||
if (pair.last() == "yes")
|
||||
opts |= QCborValue::ExtendedFormat;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unknown CBOR diagnostic option '%s'. Available options are:\n%s",
|
||||
qPrintable(s), diagnosticHelp);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
QTextStream out(f);
|
||||
out << convertFromVariant(contents, Double).toDiagnosticNotation(opts)
|
||||
<< Qt::endl;
|
||||
}
|
||||
|
||||
CborConverter::CborConverter()
|
||||
{
|
||||
qRegisterMetaType<QCborTag>();
|
||||
}
|
||||
|
||||
QString CborConverter::name()
|
||||
{
|
||||
return "cbor";
|
||||
}
|
||||
|
||||
Converter::Direction CborConverter::directions()
|
||||
{
|
||||
return InOut;
|
||||
}
|
||||
|
||||
Converter::Options CborConverter::outputOptions()
|
||||
{
|
||||
return SupportsArbitraryMapKeys;
|
||||
}
|
||||
|
||||
const char *CborConverter::optionsHelp()
|
||||
{
|
||||
return cborOptionHelp;
|
||||
}
|
||||
|
||||
bool CborConverter::probeFile(QIODevice *f)
|
||||
{
|
||||
if (QFile *file = qobject_cast<QFile *>(f)) {
|
||||
if (file->fileName().endsWith(QLatin1String(".cbor")))
|
||||
return true;
|
||||
}
|
||||
return f->isReadable() && f->peek(3) == QByteArray("\xd9\xd9\xf7", 3);
|
||||
}
|
||||
|
||||
//! [2]
|
||||
QVariant CborConverter::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
{
|
||||
const char *ptr = nullptr;
|
||||
if (auto file = qobject_cast<QFile *>(f))
|
||||
ptr = reinterpret_cast<char *>(file->map(0, file->size()));
|
||||
|
||||
QByteArray mapped = QByteArray::fromRawData(ptr, ptr ? f->size() : 0);
|
||||
QCborStreamReader reader(mapped);
|
||||
if (!ptr)
|
||||
reader.setDevice(f);
|
||||
|
||||
if (reader.isTag() && reader.toTag() == QCborKnownTags::Signature)
|
||||
reader.next();
|
||||
|
||||
QCborValue contents = QCborValue::fromCbor(reader);
|
||||
qint64 offset = reader.currentOffset();
|
||||
if (reader.lastError()) {
|
||||
fprintf(stderr, "Error loading CBOR contents (byte %lld): %s\n", offset,
|
||||
qPrintable(reader.lastError().toString()));
|
||||
fprintf(stderr, " bytes: %s\n",
|
||||
(ptr ? mapped.mid(offset, 9) : f->read(9)).toHex(' ').constData());
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (offset < mapped.size() || (!ptr && f->bytesAvailable())) {
|
||||
fprintf(stderr, "Warning: bytes remaining at the end of the CBOR stream\n");
|
||||
}
|
||||
|
||||
if (outputConverter == nullptr)
|
||||
outputConverter = &cborDiagnosticDumper;
|
||||
else if (outputConverter == null)
|
||||
return QVariant();
|
||||
else if (!outputConverter->outputOptions().testFlag(SupportsArbitraryMapKeys))
|
||||
return contents.toVariant();
|
||||
return convertCborValue(contents);
|
||||
}
|
||||
//! [2]
|
||||
//! [3]
|
||||
void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
|
||||
{
|
||||
//! [3]
|
||||
bool useSignature = true;
|
||||
bool useIntegers = true;
|
||||
enum { Yes, No, Always } useFloat16 = Yes, useFloat = Yes;
|
||||
|
||||
for (const QString &s : options) {
|
||||
QStringList pair = s.split('=');
|
||||
if (pair.size() == 2) {
|
||||
if (pair.first() == "convert-float-to-int") {
|
||||
if (pair.last() == "yes") {
|
||||
useIntegers = true;
|
||||
continue;
|
||||
} else if (pair.last() == "no") {
|
||||
useIntegers = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (pair.first() == "float16") {
|
||||
if (pair.last() == "no") {
|
||||
useFloat16 = No;
|
||||
continue;
|
||||
} else if (pair.last() == "yes") {
|
||||
useFloat16 = Yes;
|
||||
continue;
|
||||
} else if (pair.last() == "always") {
|
||||
useFloat16 = Always;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (pair.first() == "float32") {
|
||||
if (pair.last() == "no") {
|
||||
useFloat = No;
|
||||
continue;
|
||||
} else if (pair.last() == "yes") {
|
||||
useFloat = Yes;
|
||||
continue;
|
||||
} else if (pair.last() == "always") {
|
||||
useFloat = Always;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (pair.first() == "signature") {
|
||||
if (pair.last() == "yes") {
|
||||
useSignature = true;
|
||||
continue;
|
||||
} else if (pair.last() == "no") {
|
||||
useSignature = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unknown CBOR format option '%s'. Valid options are:\n%s",
|
||||
qPrintable(s), cborOptionHelp);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
//! [4]
|
||||
QCborValue v = convertFromVariant(contents,
|
||||
useFloat16 == Always ? Float16 : useFloat == Always ? Float : Double);
|
||||
QCborStreamWriter writer(f);
|
||||
if (useSignature)
|
||||
writer.append(QCborKnownTags::Signature);
|
||||
|
||||
QCborValue::EncodingOptions opts;
|
||||
if (useIntegers)
|
||||
opts |= QCborValue::UseIntegers;
|
||||
if (useFloat != No)
|
||||
opts |= QCborValue::UseFloat;
|
||||
if (useFloat16 != No)
|
||||
opts |= QCborValue::UseFloat16;
|
||||
v.toCbor(writer, opts);
|
||||
}
|
||||
//! [4]
|
38
examples/corelib/serialization/convert/cborconverter.h
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright (C) 2018 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CBORCONVERTER_H
|
||||
#define CBORCONVERTER_H
|
||||
|
||||
#include "converter.h"
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
class CborConverter : public Converter
|
||||
{
|
||||
public:
|
||||
CborConverter();
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
#endif // CBORCONVERTER_H
|