6.5.3 clean
@ -6,9 +6,6 @@ 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()
|
||||
|
@ -38,8 +38,7 @@ double BindableSubscription::calculateDiscount() const
|
||||
case Yearly:
|
||||
return 0.6;
|
||||
}
|
||||
Q_ASSERT(false);
|
||||
return -1;
|
||||
Q_UNREACHABLE_RETURN(-1);
|
||||
}
|
||||
|
||||
int BindableSubscription::basePrice() const
|
||||
|
@ -11,6 +11,10 @@
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QSpinBox>
|
||||
#include <QProperty>
|
||||
#include <QString>
|
||||
#include <QDateTimeEdit>
|
||||
#include <QBindable>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
@ -19,6 +23,8 @@ int main(int argc, char *argv[])
|
||||
BindableSubscription subscription(&user);
|
||||
|
||||
SubscriptionWindow w;
|
||||
// clazy:excludeall=lambda-in-connect
|
||||
// when subscription is out of scope so is window
|
||||
|
||||
// Initialize subscription data
|
||||
QRadioButton *monthly = w.findChild<QRadioButton *>("btnMonthly");
|
||||
@ -49,9 +55,8 @@ int main(int argc, char *argv[])
|
||||
});
|
||||
|
||||
QSpinBox *ageSpinBox = w.findChild<QSpinBox *>("ageSpinBox");
|
||||
QObject::connect(ageSpinBox, &QSpinBox::valueChanged, [&](int value) {
|
||||
user.setAge(value);
|
||||
});
|
||||
QBindable<int> ageBindable(ageSpinBox, "value");
|
||||
user.bindableAge().setBinding([ageBindable](){ return ageBindable.value();});
|
||||
|
||||
QLabel *priceDisplay = w.findChild<QLabel *>("priceDisplay");
|
||||
|
||||
|
@ -1,46 +0,0 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(permissions LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/permissions")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(permissions
|
||||
MANUAL_FINALIZATION
|
||||
main.cpp
|
||||
)
|
||||
|
||||
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)
|
@ -1,59 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
|
||||
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>Testing BluetoothAlways</string>
|
||||
<key>NSCalendarsUsageDescription</key>
|
||||
<string>Testing Calendars</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Testing Camera</string>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>Testing Contacts</string>
|
||||
<key>NSHealthShareUsageDescription</key>
|
||||
<string>Testing HealthShare</string>
|
||||
<key>NSHealthUpdateUsageDescription</key>
|
||||
<string>Testing HealthUpdate</string>
|
||||
<key>NSLocationUsageDescription</key>
|
||||
<string>Testing Location on macOS</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Testing Location when in use on iOS</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>Testing Location always and when in use on iOS</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Testing Microphone</string>
|
||||
|
||||
</dict>
|
||||
</plist>
|
@ -1,53 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.qtproject.example"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
|
||||
android:versionName="-- %%INSERT_VERSION_NAME%% --">
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
||||
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
|
||||
<!-- %%INSERT_PERMISSIONS -->
|
||||
<!-- %%INSERT_FEATURES -->
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true" />
|
||||
<application
|
||||
android:name="org.qtproject.qt.android.bindings.QtApplication"
|
||||
android:hardwareAccelerated="true"
|
||||
android:label="-- %%INSERT_APP_NAME%% --"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:allowNativeHeapPointerTagging="false"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupOnly="false">
|
||||
<activity
|
||||
android:name="org.qtproject.qt.android.bindings.QtActivity"
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
|
||||
android:label="-- %%INSERT_APP_NAME%% --"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="unspecified"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.lib_name"
|
||||
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.extract_android_style"
|
||||
android:value="minimal" />
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
@ -1,87 +0,0 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtCore/qmetaobject.h>
|
||||
#include <QtWidgets/qapplication.h>
|
||||
#include <QtWidgets/qwidget.h>
|
||||
#include <QtWidgets/qpushbutton.h>
|
||||
#include <QtWidgets/qlayout.h>
|
||||
#include <QtWidgets/qmessagebox.h>
|
||||
|
||||
#if !QT_CONFIG(permissions)
|
||||
#error "This example requires the permissions feature, which is not enabled on this platform"
|
||||
#endif
|
||||
|
||||
#include <QtCore/qpermissions.h>
|
||||
|
||||
class PermissionWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PermissionWidget(QWidget *parent = nullptr) : QWidget(parent)
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
static const QPermission permissions[] = {
|
||||
QCameraPermission{},
|
||||
QMicrophonePermission{},
|
||||
QBluetoothPermission{},
|
||||
QContactsPermission{},
|
||||
QCalendarPermission{},
|
||||
QLocationPermission{}
|
||||
};
|
||||
|
||||
for (auto permission : permissions) {
|
||||
auto permissionName = QString::fromLatin1(permission.type().name());
|
||||
QPushButton *button = new QPushButton(permissionName.sliced(1, permissionName.length() - 11));
|
||||
connect(button, &QPushButton::clicked, this, &PermissionWidget::buttonClicked);
|
||||
button->setProperty("permission", QVariant::fromValue(permission));
|
||||
layout->addWidget(button);
|
||||
}
|
||||
|
||||
QPalette pal = palette();
|
||||
pal.setBrush(QPalette::Window, QGradient(QGradient::HappyAcid));
|
||||
setPalette(pal);
|
||||
}
|
||||
|
||||
private:
|
||||
void buttonClicked()
|
||||
{
|
||||
auto *button = static_cast<QPushButton*>(sender());
|
||||
|
||||
auto permission = button->property("permission").value<QPermission>();
|
||||
Q_ASSERT(permission.type().isValid());
|
||||
|
||||
switch (qApp->checkPermission(permission)) {
|
||||
case Qt::PermissionStatus::Undetermined:
|
||||
qApp->requestPermission(permission, this,
|
||||
[button](const QPermission &permission) {
|
||||
Q_UNUSED(permission);
|
||||
emit button->clicked(); // Try again
|
||||
}
|
||||
);
|
||||
return;
|
||||
case Qt::PermissionStatus::Denied:
|
||||
QMessageBox::warning(this, button->text(),
|
||||
tr("Permission is needed to use %1. Please grant permission "\
|
||||
"to this application in the system settings.").arg(button->text()));
|
||||
return;
|
||||
case Qt::PermissionStatus::Granted:
|
||||
break; // Proceed
|
||||
}
|
||||
|
||||
// All good, can use the feature
|
||||
QMessageBox::information(this, button->text(),
|
||||
tr("Accessing %1").arg(button->text()));
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
PermissionWidget widget;
|
||||
widget.show();
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
#include "main.moc"
|
@ -4,6 +4,7 @@
|
||||
/*!
|
||||
\title Qt Android Notifier
|
||||
\example platform/androidnotifier
|
||||
\examplecategory {Mobile}
|
||||
\brief Demonstrates calling Java code from Qt in an Android application.
|
||||
|
||||
\image androidnotifier.png
|
||||
|
@ -1,11 +1,10 @@
|
||||
# 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)
|
||||
if(NOT ANDROID)
|
||||
qt_internal_add_example(cbordump)
|
||||
qt_internal_add_example(convert)
|
||||
qt_internal_add_example(savegame)
|
||||
endif()
|
||||
if(TARGET Qt6::Widgets)
|
||||
qt_internal_add_example(streambookmarks)
|
||||
|
@ -4,6 +4,10 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(cbordump LANGUAGES CXX)
|
||||
|
||||
if (ANDROID)
|
||||
message(FATAL_ERROR "This project cannot be built on Android.")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 19 KiB |
@ -3,7 +3,8 @@
|
||||
|
||||
/*!
|
||||
\example serialization/cbordump
|
||||
\examplecategory {Input/Output}
|
||||
\examplecategory {Data Processing & I/O}
|
||||
\meta tag {network}
|
||||
\title Parsing and displaying CBOR data
|
||||
|
||||
\brief A demonstration of how to parse files in CBOR format.
|
||||
|
@ -14,6 +14,8 @@
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
/*
|
||||
* To regenerate:
|
||||
* curl -O https://www.iana.org/assignments/cbor-tags/cbor-tags.xml
|
||||
@ -33,7 +35,7 @@
|
||||
struct CborTagDescription
|
||||
{
|
||||
QCborTag tag;
|
||||
const char *description; // with space and parentheses
|
||||
const char *description; // with space and parentheses
|
||||
};
|
||||
|
||||
// CBOR Tags
|
||||
@ -216,22 +218,18 @@ static const CborTagDescription tagDescriptions[] = {
|
||||
|
||||
enum {
|
||||
// See RFC 7049 section 2.
|
||||
SmallValueBitLength = 5,
|
||||
SmallValueMask = (1 << SmallValueBitLength) - 1, /* 0x1f */
|
||||
Value8Bit = 24,
|
||||
Value16Bit = 25,
|
||||
Value32Bit = 26,
|
||||
Value64Bit = 27
|
||||
SmallValueBitLength = 5,
|
||||
SmallValueMask = (1 << SmallValueBitLength) - 1, /* 0x1f */
|
||||
Value8Bit = 24,
|
||||
Value16Bit = 25,
|
||||
Value32Bit = 26,
|
||||
Value64Bit = 27
|
||||
};
|
||||
|
||||
//! [0]
|
||||
struct CborDumper
|
||||
{
|
||||
enum DumpOption {
|
||||
ShowCompact = 0x01,
|
||||
ShowWidthIndicators = 0x02,
|
||||
ShowAnnotated = 0x04
|
||||
};
|
||||
enum DumpOption { ShowCompact = 0x01, ShowWidthIndicators = 0x02, ShowAnnotated = 0x04 };
|
||||
Q_DECLARE_FLAGS(DumpOptions, DumpOption)
|
||||
|
||||
CborDumper(QFile *f, DumpOptions opts_);
|
||||
@ -268,8 +266,7 @@ static int cborNumberSize(quint64 value)
|
||||
return normalSize;
|
||||
}
|
||||
|
||||
CborDumper::CborDumper(QFile *f, DumpOptions opts_)
|
||||
: opts(opts_)
|
||||
CborDumper::CborDumper(QFile *f, DumpOptions opts_) : opts(opts_)
|
||||
{
|
||||
// try to mmap the file, this is faster
|
||||
char *ptr = reinterpret_cast<char *>(f->map(0, f->size(), QFile::MapPrivateOption));
|
||||
@ -316,7 +313,8 @@ QCborError CborDumper::dump()
|
||||
return err;
|
||||
}
|
||||
|
||||
template <typename T> static inline bool canConvertTo(double v)
|
||||
template<typename T>
|
||||
static inline bool canConvertTo(double v)
|
||||
{
|
||||
using TypeInfo = std::numeric_limits<T>;
|
||||
// The [conv.fpint] (7.10 Floating-integral conversions) section of the
|
||||
@ -337,31 +335,32 @@ template <typename T> static inline bool canConvertTo(double v)
|
||||
return v == floor(v);
|
||||
}
|
||||
|
||||
static QString fpToString(double v, const char *suffix)
|
||||
static QString fpToString(double v, QLatin1StringView suffix = ""_L1)
|
||||
{
|
||||
if (qIsInf(v))
|
||||
return v < 0 ? QStringLiteral("-inf") : QStringLiteral("inf");
|
||||
return v < 0 ? "-inf"_L1 : "inf"_L1;
|
||||
if (qIsNaN(v))
|
||||
return QStringLiteral("nan");
|
||||
return "nan"_L1;
|
||||
if (canConvertTo<qint64>(v))
|
||||
return QString::number(qint64(v)) + ".0" + suffix;
|
||||
return QString::number(qint64(v)) + ".0"_L1 + suffix;
|
||||
if (canConvertTo<quint64>(v))
|
||||
return QString::number(quint64(v)) + ".0" + suffix;
|
||||
return QString::number(quint64(v)) + ".0"_L1 + suffix;
|
||||
|
||||
QString s = QString::number(v, 'g', QLocale::FloatingPointShortest);
|
||||
if (!s.contains('.') && !s.contains('e'))
|
||||
s += '.';
|
||||
s += suffix;
|
||||
if (!s.contains(u'.') && !s.contains(u'e'))
|
||||
s += u'.';
|
||||
if (suffix.size())
|
||||
s += suffix;
|
||||
return s;
|
||||
};
|
||||
|
||||
void CborDumper::dumpOne(int nestingLevel)
|
||||
{
|
||||
QString indent(1, QLatin1Char(' '));
|
||||
QString indent(1, u' ');
|
||||
QString indented = indent;
|
||||
if (!opts.testFlag(ShowCompact)) {
|
||||
indent = QLatin1Char('\n') + QString(4 * nestingLevel, QLatin1Char(' '));
|
||||
indented = QLatin1Char('\n') + QString(4 + 4 * nestingLevel, QLatin1Char(' '));
|
||||
indent = u'\n' + QString(4 * nestingLevel, u' ');
|
||||
indented = u'\n' + QString(4 + 4 * nestingLevel, u' ');
|
||||
}
|
||||
|
||||
switch (reader.type()) {
|
||||
@ -401,7 +400,7 @@ void CborDumper::dumpOne(int nestingLevel)
|
||||
printStringWidthIndicator(r.data.size());
|
||||
|
||||
r = reader.readByteArray();
|
||||
comma = QLatin1Char(',') + indented;
|
||||
comma = u',' + indented;
|
||||
}
|
||||
} else {
|
||||
auto r = reader.readString();
|
||||
@ -410,7 +409,7 @@ void CborDumper::dumpOne(int nestingLevel)
|
||||
printStringWidthIndicator(r.data.toUtf8().size());
|
||||
|
||||
r = reader.readString();
|
||||
comma = QLatin1Char(',') + indented;
|
||||
comma = u',' + indented;
|
||||
}
|
||||
}
|
||||
|
||||
@ -466,7 +465,7 @@ void CborDumper::dumpOne(int nestingLevel)
|
||||
if (reader.next()) {
|
||||
printWidthIndicator(quint64(tag));
|
||||
printf("(");
|
||||
dumpOne(nestingLevel); // same level!
|
||||
dumpOne(nestingLevel); // same level!
|
||||
printf(")");
|
||||
}
|
||||
|
||||
@ -498,15 +497,15 @@ void CborDumper::dumpOne(int nestingLevel)
|
||||
break;
|
||||
|
||||
case QCborStreamReader::Float16:
|
||||
printf("%s", qPrintable(fpToString(reader.toFloat16(), "f16")));
|
||||
printf("%s", qPrintable(fpToString(reader.toFloat16(), "f16"_L1)));
|
||||
reader.next();
|
||||
break;
|
||||
case QCborStreamReader::Float:
|
||||
printf("%s", qPrintable(fpToString(reader.toFloat(), "f")));
|
||||
printf("%s", qPrintable(fpToString(reader.toFloat(), "f"_L1)));
|
||||
reader.next();
|
||||
break;
|
||||
case QCborStreamReader::Double:
|
||||
printf("%s", qPrintable(fpToString(reader.toDouble(), "")));
|
||||
printf("%s", qPrintable(fpToString(reader.toDouble())));
|
||||
reader.next();
|
||||
break;
|
||||
case QCborStreamReader::Invalid:
|
||||
@ -559,7 +558,7 @@ void CborDumper::dumpOneDetailed(int nestingLevel)
|
||||
};
|
||||
|
||||
auto printFp = [=](const char *descr, double d) {
|
||||
QString s = fpToString(d, "");
|
||||
QString s = fpToString(d);
|
||||
if (s.size() <= 6)
|
||||
return print(descr, "%s", qPrintable(s));
|
||||
return print(descr, "%a", d);
|
||||
@ -574,7 +573,7 @@ void CborDumper::dumpOneDetailed(int nestingLevel)
|
||||
|
||||
qsizetype size = reader.currentStringChunkSize();
|
||||
if (size < 0)
|
||||
return; // error
|
||||
return; // error
|
||||
if (size >= ChunkSizeLimit) {
|
||||
fprintf(stderr, "String length too big, %lli\n", qint64(size));
|
||||
exit(EXIT_FAILURE);
|
||||
@ -619,7 +618,7 @@ void CborDumper::dumpOneDetailed(int nestingLevel)
|
||||
printf(" %s%s", indent.constData(), section.toHex(' ').constData());
|
||||
|
||||
// print the decode
|
||||
QByteArray spaces(width > 0 ? width - section.size() * 3 + 1: 0, ' ');
|
||||
QByteArray spaces(width > 0 ? width - section.size() * 3 + 1 : 0, ' ');
|
||||
printf("%s # \"", spaces.constData());
|
||||
auto ptr = reinterpret_cast<const uchar *>(section.constData());
|
||||
for (int j = 0; j < section.size(); ++j)
|
||||
@ -631,7 +630,7 @@ void CborDumper::dumpOneDetailed(int nestingLevel)
|
||||
// get the next chunk
|
||||
size = reader.currentStringChunkSize();
|
||||
if (size < 0)
|
||||
return; // error
|
||||
return; // error
|
||||
if (size >= ChunkSizeLimit) {
|
||||
fprintf(stderr, "String length too big, %lli\n", qint64(size));
|
||||
exit(EXIT_FAILURE);
|
||||
@ -770,7 +769,9 @@ void CborDumper::printByteArray(const QByteArray &ba)
|
||||
break;
|
||||
|
||||
case quint8(QCborKnownTags::ExpectedBase64url):
|
||||
printf("b64'%s'", ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals).constData());
|
||||
printf("b64'%s'",
|
||||
ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)
|
||||
.constData());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -811,23 +812,20 @@ int main(int argc, char *argv[])
|
||||
setlocale(LC_ALL, "C");
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QStringLiteral("CBOR Dumper tool"));
|
||||
parser.setApplicationDescription("CBOR Dumper tool"_L1);
|
||||
parser.addHelpOption();
|
||||
|
||||
QCommandLineOption compact({QStringLiteral("c"), QStringLiteral("compact")},
|
||||
QStringLiteral("Use compact form (no line breaks)"));
|
||||
QCommandLineOption compact({"c"_L1, "compact"_L1}, "Use compact form (no line breaks)"_L1);
|
||||
parser.addOption(compact);
|
||||
|
||||
QCommandLineOption showIndicators({QStringLiteral("i"), QStringLiteral("indicators")},
|
||||
QStringLiteral("Show indicators for width of lengths and integrals"));
|
||||
QCommandLineOption showIndicators({ "i"_L1, "indicators"_L1 },
|
||||
"Show indicators for width of lengths and integrals"_L1);
|
||||
parser.addOption(showIndicators);
|
||||
|
||||
QCommandLineOption verbose({QStringLiteral("a"), QStringLiteral("annotated")},
|
||||
QStringLiteral("Show bytes and annotated decoding"));
|
||||
QCommandLineOption verbose({"a"_L1, "annotated"_L1}, "Show bytes and annotated decoding"_L1);
|
||||
parser.addOption(verbose);
|
||||
|
||||
parser.addPositionalArgument(QStringLiteral("[source]"),
|
||||
QStringLiteral("CBOR file to read from"));
|
||||
parser.addPositionalArgument("[source]"_L1, "CBOR file to read from"_L1);
|
||||
|
||||
parser.process(app);
|
||||
|
||||
|
@ -4,6 +4,10 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(convert LANGUAGES CXX)
|
||||
|
||||
if (ANDROID)
|
||||
message(FATAL_ERROR "This project cannot be built on Android.")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
@ -18,6 +22,7 @@ qt_add_executable(convert
|
||||
cborconverter.cpp cborconverter.h
|
||||
converter.h
|
||||
datastreamconverter.cpp datastreamconverter.h
|
||||
debugtextdumper.cpp debugtextdumper.h
|
||||
jsonconverter.cpp jsonconverter.h
|
||||
main.cpp
|
||||
nullconverter.cpp nullconverter.h
|
||||
|
@ -3,19 +3,21 @@
|
||||
|
||||
#include "cborconverter.h"
|
||||
|
||||
#include <QCborArray>
|
||||
#include <QCborMap>
|
||||
#include <QCborStreamReader>
|
||||
#include <QCborStreamWriter>
|
||||
#include <QCborMap>
|
||||
#include <QCborArray>
|
||||
#include <QCborValue>
|
||||
#include <QDataStream>
|
||||
#include <QFloat16>
|
||||
#include <QFile>
|
||||
#include <QFloat16>
|
||||
#include <QMetaType>
|
||||
#include <QTextStream>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
static CborConverter cborConverter;
|
||||
static CborDiagnosticDumper cborDiagnosticDumper;
|
||||
|
||||
@ -118,33 +120,33 @@ static QCborValue convertFromVariant(const QVariant &v, TrimFloatingPoint fpTrim
|
||||
}
|
||||
//! [1]
|
||||
|
||||
QString CborDiagnosticDumper::name()
|
||||
QString CborDiagnosticDumper::name() const
|
||||
{
|
||||
return QStringLiteral("cbor-dump");
|
||||
return "cbor-dump"_L1;
|
||||
}
|
||||
|
||||
Converter::Direction CborDiagnosticDumper::directions()
|
||||
Converter::Directions CborDiagnosticDumper::directions() const
|
||||
{
|
||||
return Out;
|
||||
return Direction::Out;
|
||||
}
|
||||
|
||||
Converter::Options CborDiagnosticDumper::outputOptions()
|
||||
Converter::Options CborDiagnosticDumper::outputOptions() const
|
||||
{
|
||||
return SupportsArbitraryMapKeys;
|
||||
}
|
||||
|
||||
const char *CborDiagnosticDumper::optionsHelp()
|
||||
const char *CborDiagnosticDumper::optionsHelp() const
|
||||
{
|
||||
return diagnosticHelp;
|
||||
}
|
||||
|
||||
bool CborDiagnosticDumper::probeFile(QIODevice *f)
|
||||
bool CborDiagnosticDumper::probeFile(QIODevice *f) const
|
||||
{
|
||||
Q_UNUSED(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant CborDiagnosticDumper::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
QVariant CborDiagnosticDumper::loadFile(QIODevice *f, const Converter *&outputConverter) const
|
||||
{
|
||||
Q_UNREACHABLE();
|
||||
Q_UNUSED(f);
|
||||
@ -152,7 +154,8 @@ QVariant CborDiagnosticDumper::loadFile(QIODevice *f, Converter *&outputConverte
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
|
||||
void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const
|
||||
{
|
||||
QCborValue::DiagnosticNotationOptions opts = QCborValue::LineWrapped;
|
||||
for (const QString &s : options) {
|
||||
@ -181,8 +184,7 @@ void CborDiagnosticDumper::saveFile(QIODevice *f, const QVariant &contents, cons
|
||||
}
|
||||
|
||||
QTextStream out(f);
|
||||
out << convertFromVariant(contents, Double).toDiagnosticNotation(opts)
|
||||
<< Qt::endl;
|
||||
out << convertFromVariant(contents, Double).toDiagnosticNotation(opts) << Qt::endl;
|
||||
}
|
||||
|
||||
CborConverter::CborConverter()
|
||||
@ -190,37 +192,37 @@ CborConverter::CborConverter()
|
||||
qRegisterMetaType<QCborTag>();
|
||||
}
|
||||
|
||||
QString CborConverter::name()
|
||||
QString CborConverter::name() const
|
||||
{
|
||||
return "cbor";
|
||||
}
|
||||
|
||||
Converter::Direction CborConverter::directions()
|
||||
Converter::Directions CborConverter::directions() const
|
||||
{
|
||||
return InOut;
|
||||
return Direction::InOut;
|
||||
}
|
||||
|
||||
Converter::Options CborConverter::outputOptions()
|
||||
Converter::Options CborConverter::outputOptions() const
|
||||
{
|
||||
return SupportsArbitraryMapKeys;
|
||||
}
|
||||
|
||||
const char *CborConverter::optionsHelp()
|
||||
const char *CborConverter::optionsHelp() const
|
||||
{
|
||||
return cborOptionHelp;
|
||||
}
|
||||
|
||||
bool CborConverter::probeFile(QIODevice *f)
|
||||
bool CborConverter::probeFile(QIODevice *f) const
|
||||
{
|
||||
if (QFile *file = qobject_cast<QFile *>(f)) {
|
||||
if (file->fileName().endsWith(QLatin1String(".cbor")))
|
||||
if (file->fileName().endsWith(".cbor"_L1))
|
||||
return true;
|
||||
}
|
||||
return f->isReadable() && f->peek(3) == QByteArray("\xd9\xd9\xf7", 3);
|
||||
}
|
||||
|
||||
//! [2]
|
||||
QVariant CborConverter::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
QVariant CborConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const
|
||||
{
|
||||
const char *ptr = nullptr;
|
||||
if (auto file = qobject_cast<QFile *>(f))
|
||||
@ -256,7 +258,7 @@ QVariant CborConverter::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
}
|
||||
//! [2]
|
||||
//! [3]
|
||||
void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
|
||||
void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) const
|
||||
{
|
||||
//! [3]
|
||||
bool useSignature = true;
|
||||
@ -318,8 +320,9 @@ void CborConverter::saveFile(QIODevice *f, const QVariant &contents, const QStri
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
//! [4]
|
||||
QCborValue v = convertFromVariant(contents,
|
||||
useFloat16 == Always ? Float16 : useFloat == Always ? Float : Double);
|
||||
QCborValue v =
|
||||
convertFromVariant(contents,
|
||||
useFloat16 == Always ? Float16 : useFloat == Always ? Float : Double);
|
||||
QCborStreamWriter writer(f);
|
||||
if (useSignature)
|
||||
writer.append(QCborKnownTags::Signature);
|
||||
|
@ -10,13 +10,14 @@ class CborDiagnosticDumper : public Converter
|
||||
{
|
||||
// Converter interface
|
||||
public:
|
||||
QString name() override;
|
||||
Direction directions() override;
|
||||
Options outputOptions() override;
|
||||
const char *optionsHelp() override;
|
||||
bool probeFile(QIODevice *f) override;
|
||||
QVariant loadFile(QIODevice *f, Converter *&outputConverter) override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override;
|
||||
QString name() const override;
|
||||
Directions directions() const override;
|
||||
Options outputOptions() const override;
|
||||
const char *optionsHelp() const override;
|
||||
bool probeFile(QIODevice *f) const override;
|
||||
QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const override;
|
||||
};
|
||||
|
||||
class CborConverter : public Converter
|
||||
@ -26,13 +27,14 @@ public:
|
||||
|
||||
// Converter interface
|
||||
public:
|
||||
QString name() override;
|
||||
Direction directions() override;
|
||||
Options outputOptions() override;
|
||||
const char *optionsHelp() override;
|
||||
bool probeFile(QIODevice *f) override;
|
||||
QVariant loadFile(QIODevice *f, Converter *&outputConverter) override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override;
|
||||
QString name() const override;
|
||||
Directions directions() const override;
|
||||
Options outputOptions() const override;
|
||||
const char *optionsHelp() const override;
|
||||
bool probeFile(QIODevice *f) const override;
|
||||
QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const override;
|
||||
};
|
||||
|
||||
#endif // CBORCONVERTER_H
|
||||
|
@ -12,17 +12,19 @@ INSTALLS += target
|
||||
|
||||
SOURCES += main.cpp \
|
||||
cborconverter.cpp \
|
||||
jsonconverter.cpp \
|
||||
datastreamconverter.cpp \
|
||||
debugtextdumper.cpp \
|
||||
jsonconverter.cpp \
|
||||
nullconverter.cpp \
|
||||
textconverter.cpp \
|
||||
xmlconverter.cpp \
|
||||
nullconverter.cpp
|
||||
xmlconverter.cpp
|
||||
|
||||
HEADERS += \
|
||||
converter.h \
|
||||
cborconverter.h \
|
||||
jsonconverter.h \
|
||||
datastreamconverter.h \
|
||||
debugtextdumper.h \
|
||||
jsonconverter.h \
|
||||
nullconverter.h \
|
||||
textconverter.h \
|
||||
xmlconverter.h \
|
||||
nullconverter.h
|
||||
xmlconverter.h
|
||||
|
@ -5,10 +5,10 @@
|
||||
#define CONVERTER_H
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QVariant>
|
||||
#include <QVariantMap>
|
||||
#include <QList>
|
||||
|
||||
class VariantOrderedMap : public QList<QPair<QVariant, QVariant>>
|
||||
{
|
||||
@ -32,26 +32,25 @@ protected:
|
||||
public:
|
||||
static Converter *null;
|
||||
|
||||
enum Direction {
|
||||
In = 1, Out = 2, InOut = 3
|
||||
};
|
||||
enum class Direction { In = 1, Out = 2, InOut = In | Out };
|
||||
Q_DECLARE_FLAGS(Directions, Direction)
|
||||
|
||||
enum Option {
|
||||
SupportsArbitraryMapKeys = 0x01
|
||||
};
|
||||
enum Option { SupportsArbitraryMapKeys = 0x01 };
|
||||
Q_DECLARE_FLAGS(Options, Option)
|
||||
|
||||
virtual ~Converter() = 0;
|
||||
|
||||
virtual QString name() = 0;
|
||||
virtual Direction directions() = 0;
|
||||
virtual Options outputOptions() = 0;
|
||||
virtual const char *optionsHelp() = 0;
|
||||
virtual bool probeFile(QIODevice *f) = 0;
|
||||
virtual QVariant loadFile(QIODevice *f, Converter *&outputConverter) = 0;
|
||||
virtual void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) = 0;
|
||||
virtual QString name() const = 0;
|
||||
virtual Directions directions() const = 0;
|
||||
virtual Options outputOptions() const = 0;
|
||||
virtual const char *optionsHelp() const = 0;
|
||||
virtual bool probeFile(QIODevice *f) const = 0;
|
||||
virtual QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const = 0;
|
||||
virtual void saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const = 0;
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Converter::Directions)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Converter::Options)
|
||||
|
||||
#endif // CONVERTER_H
|
||||
|
@ -2,20 +2,21 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "datastreamconverter.h"
|
||||
#include "debugtextdumper.h"
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
#include <QTextStream>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
static const char dataStreamOptionHelp[] =
|
||||
"byteorder=host|big|little Byte order to use.\n"
|
||||
"version=<n> QDataStream version (default: Qt 5.0).\n"
|
||||
"version=<n> QDataStream version (default: Qt 6.0).\n"
|
||||
;
|
||||
|
||||
static const char signature[] = "qds";
|
||||
|
||||
static DataStreamDumper dataStreamDumper;
|
||||
static DataStreamConverter DataStreamConverter;
|
||||
static DataStreamConverter dataStreamConverter;
|
||||
static DebugTextDumper debugTextDumper;
|
||||
|
||||
QDataStream &operator<<(QDataStream &ds, const VariantOrderedMap &map)
|
||||
{
|
||||
@ -42,123 +43,43 @@ QDataStream &operator>>(QDataStream &ds, VariantOrderedMap &map)
|
||||
return ds;
|
||||
}
|
||||
|
||||
|
||||
static QString dumpVariant(const QVariant &v, const QString &indent = QLatin1String("\n"))
|
||||
{
|
||||
QString result;
|
||||
QString indented = indent + QLatin1String(" ");
|
||||
|
||||
int type = v.userType();
|
||||
if (type == qMetaTypeId<VariantOrderedMap>() || type == QMetaType::QVariantMap) {
|
||||
const auto map = (type == QMetaType::QVariantMap) ?
|
||||
VariantOrderedMap(v.toMap()) : qvariant_cast<VariantOrderedMap>(v);
|
||||
|
||||
result = QLatin1String("Map {");
|
||||
for (const auto &pair : map) {
|
||||
result += indented + dumpVariant(pair.first, indented);
|
||||
result.chop(1); // remove comma
|
||||
result += QLatin1String(" => ") + dumpVariant(pair.second, indented);
|
||||
|
||||
}
|
||||
result.chop(1); // remove comma
|
||||
result += indent + QLatin1String("},");
|
||||
} else if (type == QMetaType::QVariantList) {
|
||||
const QVariantList list = v.toList();
|
||||
|
||||
result = QLatin1String("List [");
|
||||
for (const auto &item : list)
|
||||
result += indented + dumpVariant(item, indented);
|
||||
result.chop(1); // remove comma
|
||||
result += indent + QLatin1String("],");
|
||||
} else {
|
||||
QDebug debug(&result);
|
||||
debug.nospace() << v << ',';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString DataStreamDumper::name()
|
||||
{
|
||||
return QStringLiteral("datastream-dump");
|
||||
}
|
||||
|
||||
Converter::Direction DataStreamDumper::directions()
|
||||
{
|
||||
return Out;
|
||||
}
|
||||
|
||||
Converter::Options DataStreamDumper::outputOptions()
|
||||
{
|
||||
return SupportsArbitraryMapKeys;
|
||||
}
|
||||
|
||||
const char *DataStreamDumper::optionsHelp()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool DataStreamDumper::probeFile(QIODevice *f)
|
||||
{
|
||||
Q_UNUSED(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant DataStreamDumper::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
{
|
||||
Q_UNREACHABLE();
|
||||
Q_UNUSED(f);
|
||||
Q_UNUSED(outputConverter);
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void DataStreamDumper::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
|
||||
{
|
||||
Q_UNUSED(options);
|
||||
QString s = dumpVariant(contents);
|
||||
s[s.size() - 1] = QLatin1Char('\n'); // replace the comma with newline
|
||||
|
||||
QTextStream out(f);
|
||||
out << s;
|
||||
}
|
||||
|
||||
DataStreamConverter::DataStreamConverter()
|
||||
{
|
||||
qRegisterMetaType<VariantOrderedMap>();
|
||||
}
|
||||
|
||||
QString DataStreamConverter::name()
|
||||
QString DataStreamConverter::name() const
|
||||
{
|
||||
return QStringLiteral("datastream");
|
||||
return "datastream"_L1;
|
||||
}
|
||||
|
||||
Converter::Direction DataStreamConverter::directions()
|
||||
Converter::Directions DataStreamConverter::directions() const
|
||||
{
|
||||
return InOut;
|
||||
return Direction::InOut;
|
||||
}
|
||||
|
||||
Converter::Options DataStreamConverter::outputOptions()
|
||||
Converter::Options DataStreamConverter::outputOptions() const
|
||||
{
|
||||
return SupportsArbitraryMapKeys;
|
||||
}
|
||||
|
||||
const char *DataStreamConverter::optionsHelp()
|
||||
const char *DataStreamConverter::optionsHelp() const
|
||||
{
|
||||
return dataStreamOptionHelp;
|
||||
}
|
||||
|
||||
bool DataStreamConverter::probeFile(QIODevice *f)
|
||||
bool DataStreamConverter::probeFile(QIODevice *f) const
|
||||
{
|
||||
return f->isReadable() && f->peek(sizeof(signature) - 1) == signature;
|
||||
}
|
||||
|
||||
QVariant DataStreamConverter::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
QVariant DataStreamConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const
|
||||
{
|
||||
if (!outputConverter)
|
||||
outputConverter = &dataStreamDumper;
|
||||
outputConverter = &debugTextDumper;
|
||||
|
||||
char c;
|
||||
if (f->read(sizeof(signature) -1) != signature ||
|
||||
!f->getChar(&c) || (c != 'l' && c != 'B')) {
|
||||
if (f->read(sizeof(signature) - 1) != signature || !f->getChar(&c) || (c != 'l' && c != 'B')) {
|
||||
fprintf(stderr, "Could not load QDataStream file: invalid signature.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
@ -175,9 +96,10 @@ QVariant DataStreamConverter::loadFile(QIODevice *f, Converter *&outputConverter
|
||||
return result;
|
||||
}
|
||||
|
||||
void DataStreamConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
|
||||
void DataStreamConverter::saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const
|
||||
{
|
||||
QDataStream::Version version = QDataStream::Qt_5_0;
|
||||
QDataStream::Version version = QDataStream::Qt_6_0;
|
||||
auto order = QDataStream::ByteOrder(QSysInfo::ByteOrder);
|
||||
for (const QString &option : options) {
|
||||
const QStringList pair = option.split('=');
|
||||
@ -213,7 +135,7 @@ void DataStreamConverter::saveFile(QIODevice *f, const QVariant &contents, const
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
char c = order == QDataStream::LittleEndian ? 'l' : 'B';
|
||||
char c = order == QDataStream::LittleEndian ? 'l' : 'B';
|
||||
f->write(signature);
|
||||
f->write(&c, 1);
|
||||
|
||||
|
@ -6,19 +6,6 @@
|
||||
|
||||
#include "converter.h"
|
||||
|
||||
class DataStreamDumper : public Converter
|
||||
{
|
||||
// Converter interface
|
||||
public:
|
||||
QString name() override;
|
||||
Direction directions() override;
|
||||
Options outputOptions() override;
|
||||
const char *optionsHelp() override;
|
||||
bool probeFile(QIODevice *f) override;
|
||||
QVariant loadFile(QIODevice *f, Converter *&outputConverter) override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override;
|
||||
};
|
||||
|
||||
class DataStreamConverter : public Converter
|
||||
{
|
||||
public:
|
||||
@ -26,13 +13,14 @@ public:
|
||||
|
||||
// Converter interface
|
||||
public:
|
||||
QString name() override;
|
||||
Direction directions() override;
|
||||
Options outputOptions() override;
|
||||
const char *optionsHelp() override;
|
||||
bool probeFile(QIODevice *f) override;
|
||||
QVariant loadFile(QIODevice *f, Converter *&outputConverter) override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override;
|
||||
QString name() const override;
|
||||
Directions directions() const override;
|
||||
Options outputOptions() const override;
|
||||
const char *optionsHelp() const override;
|
||||
bool probeFile(QIODevice *f) const override;
|
||||
QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const override;
|
||||
};
|
||||
|
||||
#endif // DATASTREAMCONVERTER_H
|
||||
|
89
examples/corelib/serialization/convert/debugtextdumper.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright (C) 2018 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "debugtextdumper.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QTextStream>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
// Static instance is declared in datastreamconverter.cpp, since it uses it.
|
||||
|
||||
static QString dumpVariant(const QVariant &v, const QString &indent = "\n"_L1)
|
||||
{
|
||||
QString result;
|
||||
QString indented = indent + " "_L1;
|
||||
|
||||
int type = v.userType();
|
||||
if (type == qMetaTypeId<VariantOrderedMap>() || type == QMetaType::QVariantMap) {
|
||||
const auto map = (type == QMetaType::QVariantMap) ? VariantOrderedMap(v.toMap())
|
||||
: qvariant_cast<VariantOrderedMap>(v);
|
||||
|
||||
result = "Map {"_L1;
|
||||
for (const auto &pair : map) {
|
||||
result += indented + dumpVariant(pair.first, indented);
|
||||
result.chop(1); // remove comma
|
||||
result += " => "_L1 + dumpVariant(pair.second, indented);
|
||||
}
|
||||
result.chop(1); // remove comma
|
||||
result += indent + "},"_L1;
|
||||
} else if (type == QMetaType::QVariantList) {
|
||||
const QVariantList list = v.toList();
|
||||
|
||||
result = "List ["_L1;
|
||||
for (const auto &item : list)
|
||||
result += indented + dumpVariant(item, indented);
|
||||
result.chop(1); // remove comma
|
||||
result += indent + "],"_L1;
|
||||
} else {
|
||||
QDebug debug(&result);
|
||||
debug.nospace() << v << ',';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString DebugTextDumper::name() const
|
||||
{
|
||||
return "debugtext-dump"_L1;
|
||||
}
|
||||
|
||||
Converter::Directions DebugTextDumper::directions() const
|
||||
{
|
||||
return Direction::Out;
|
||||
}
|
||||
|
||||
Converter::Options DebugTextDumper::outputOptions() const
|
||||
{
|
||||
return SupportsArbitraryMapKeys;
|
||||
}
|
||||
|
||||
const char *DebugTextDumper::optionsHelp() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool DebugTextDumper::probeFile(QIODevice *f) const
|
||||
{
|
||||
Q_UNUSED(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant DebugTextDumper::loadFile(QIODevice *f, const Converter *&outputConverter) const
|
||||
{
|
||||
Q_UNREACHABLE();
|
||||
Q_UNUSED(f);
|
||||
Q_UNUSED(outputConverter);
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void DebugTextDumper::saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const
|
||||
{
|
||||
Q_UNUSED(options);
|
||||
QString s = dumpVariant(contents);
|
||||
s[s.size() - 1] = u'\n'; // replace the comma with newline
|
||||
|
||||
QTextStream out(f);
|
||||
out << s;
|
||||
}
|
23
examples/corelib/serialization/convert/debugtextdumper.h
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (C) 2018 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef DEBUGTEXTDUMPER_H
|
||||
#define DEBUGTEXTDUMPER_H
|
||||
|
||||
#include "converter.h"
|
||||
|
||||
class DebugTextDumper : public Converter
|
||||
{
|
||||
// Converter interface
|
||||
public:
|
||||
QString name() const override;
|
||||
Directions directions() const override;
|
||||
Options outputOptions() const override;
|
||||
const char *optionsHelp() const override;
|
||||
bool probeFile(QIODevice *f) const override;
|
||||
QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const override;
|
||||
};
|
||||
|
||||
#endif // DEBUGTEXTDUMPER_H
|
@ -3,7 +3,8 @@
|
||||
|
||||
/*!
|
||||
\example serialization/convert
|
||||
\examplecategory {Input/Output}
|
||||
\examplecategory {Data Processing & I/O}
|
||||
\meta tag {network}
|
||||
\title Convert Example
|
||||
|
||||
\brief The Convert example demonstrates how to convert between different
|
||||
@ -59,7 +60,7 @@
|
||||
\section1 The DataStreamConverter Class
|
||||
|
||||
The DataStreamConverter class is used to serialize to and from the
|
||||
QDataStream format. There is also the DataStreamDumper class for outputting
|
||||
QDataStream format. There is also the DebugTextDumper class for outputting
|
||||
the data lossless in a non-standardized human readable format.
|
||||
|
||||
\section1 The JsonConverter Class
|
||||
|
@ -9,10 +9,11 @@
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
static JsonConverter jsonConverter;
|
||||
|
||||
static const char jsonOptionHelp[] =
|
||||
"compact=no|yes Use compact JSON form.\n";
|
||||
static const char jsonOptionHelp[] = "compact=no|yes Use compact JSON form.\n";
|
||||
|
||||
static QJsonDocument convertFromVariant(const QVariant &v)
|
||||
{
|
||||
@ -24,34 +25,30 @@ static QJsonDocument convertFromVariant(const QVariant &v)
|
||||
return doc;
|
||||
}
|
||||
|
||||
JsonConverter::JsonConverter()
|
||||
QString JsonConverter::name() const
|
||||
{
|
||||
return "json"_L1;
|
||||
}
|
||||
|
||||
QString JsonConverter::name()
|
||||
Converter::Directions JsonConverter::directions() const
|
||||
{
|
||||
return "json";
|
||||
return Direction::InOut;
|
||||
}
|
||||
|
||||
Converter::Direction JsonConverter::directions()
|
||||
{
|
||||
return InOut;
|
||||
}
|
||||
|
||||
Converter::Options JsonConverter::outputOptions()
|
||||
Converter::Options JsonConverter::outputOptions() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const char *JsonConverter::optionsHelp()
|
||||
const char *JsonConverter::optionsHelp() const
|
||||
{
|
||||
return jsonOptionHelp;
|
||||
}
|
||||
|
||||
bool JsonConverter::probeFile(QIODevice *f)
|
||||
bool JsonConverter::probeFile(QIODevice *f) const
|
||||
{
|
||||
if (QFile *file = qobject_cast<QFile *>(f)) {
|
||||
if (file->fileName().endsWith(QLatin1String(".json")))
|
||||
if (file->fileName().endsWith(".json"_L1))
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -62,7 +59,7 @@ bool JsonConverter::probeFile(QIODevice *f)
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant JsonConverter::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
QVariant JsonConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const
|
||||
{
|
||||
if (!outputConverter)
|
||||
outputConverter = this;
|
||||
@ -87,13 +84,14 @@ QVariant JsonConverter::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
return doc.toVariant();
|
||||
}
|
||||
|
||||
void JsonConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
|
||||
void JsonConverter::saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const
|
||||
{
|
||||
QJsonDocument::JsonFormat format = QJsonDocument::Indented;
|
||||
for (const QString &s : options) {
|
||||
if (s == QLatin1String("compact=no")) {
|
||||
if (s == "compact=no"_L1) {
|
||||
format = QJsonDocument::Indented;
|
||||
} else if (s == QLatin1String("compact=yes")) {
|
||||
} else if (s == "compact=yes"_L1) {
|
||||
format = QJsonDocument::Compact;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown option '%s' to JSON output. Valid options are:\n%s",
|
||||
|
@ -8,18 +8,16 @@
|
||||
|
||||
class JsonConverter : public Converter
|
||||
{
|
||||
public:
|
||||
JsonConverter();
|
||||
|
||||
// Converter interface
|
||||
public:
|
||||
QString name() override;
|
||||
Direction directions() override;
|
||||
Options outputOptions() override;
|
||||
const char *optionsHelp() override;
|
||||
bool probeFile(QIODevice *f) override;
|
||||
QVariant loadFile(QIODevice *f, Converter *&outputConverter) override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override;
|
||||
QString name() const override;
|
||||
Directions directions() const override;
|
||||
Options outputOptions() const override;
|
||||
const char *optionsHelp() const override;
|
||||
bool probeFile(QIODevice *f) const override;
|
||||
QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const override;
|
||||
};
|
||||
|
||||
#endif // JSONCONVERTER_H
|
||||
|
@ -11,12 +11,14 @@
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static QList<Converter *> *availableConverters;
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
static QList<const Converter *> *availableConverters;
|
||||
|
||||
Converter::Converter()
|
||||
{
|
||||
if (!availableConverters)
|
||||
availableConverters = new QList<Converter *>;
|
||||
availableConverters = new QList<const Converter *>;
|
||||
availableConverters->append(this);
|
||||
}
|
||||
|
||||
@ -31,64 +33,68 @@ int main(int argc, char *argv[])
|
||||
|
||||
QStringList inputFormats;
|
||||
QStringList outputFormats;
|
||||
for (Converter *conv : std::as_const(*availableConverters)) {
|
||||
for (const Converter *conv : std::as_const(*availableConverters)) {
|
||||
auto direction = conv->directions();
|
||||
QString name = conv->name();
|
||||
if (direction & Converter::In)
|
||||
if (direction.testFlag(Converter::Direction::In))
|
||||
inputFormats << name;
|
||||
if (direction & Converter::Out)
|
||||
if (direction.testFlag(Converter::Direction::Out))
|
||||
outputFormats << name;
|
||||
}
|
||||
inputFormats.sort();
|
||||
outputFormats.sort();
|
||||
inputFormats.prepend("auto");
|
||||
outputFormats.prepend("auto");
|
||||
inputFormats.prepend("auto"_L1);
|
||||
outputFormats.prepend("auto"_L1);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(QStringLiteral("Qt file format conversion tool"));
|
||||
parser.setApplicationDescription("Qt file format conversion tool"_L1);
|
||||
parser.addHelpOption();
|
||||
|
||||
QCommandLineOption inputFormatOption(QStringList{"I", "input-format"});
|
||||
inputFormatOption.setDescription(QLatin1String("Select the input format for the input file. Available formats: ") +
|
||||
inputFormats.join(", "));
|
||||
inputFormatOption.setValueName("format");
|
||||
QCommandLineOption inputFormatOption(QStringList{ "I"_L1, "input-format"_L1 });
|
||||
inputFormatOption.setDescription(
|
||||
"Select the input format for the input file. Available formats: "_L1
|
||||
+ inputFormats.join(", "_L1));
|
||||
inputFormatOption.setValueName("format"_L1);
|
||||
inputFormatOption.setDefaultValue(inputFormats.constFirst());
|
||||
parser.addOption(inputFormatOption);
|
||||
|
||||
QCommandLineOption outputFormatOption(QStringList{"O", "output-format"});
|
||||
outputFormatOption.setDescription(QLatin1String("Select the output format for the output file. Available formats: ") +
|
||||
outputFormats.join(", "));
|
||||
outputFormatOption.setValueName("format");
|
||||
QCommandLineOption outputFormatOption(QStringList{ "O"_L1, "output-format"_L1 });
|
||||
outputFormatOption.setDescription(
|
||||
"Select the output format for the output file. Available formats: "_L1
|
||||
+ outputFormats.join(", "_L1));
|
||||
outputFormatOption.setValueName("format"_L1);
|
||||
outputFormatOption.setDefaultValue(outputFormats.constFirst());
|
||||
parser.addOption(outputFormatOption);
|
||||
|
||||
QCommandLineOption optionOption(QStringList{"o", "option"});
|
||||
optionOption.setDescription(QStringLiteral("Format-specific options. Use --format-options to find out what options are available."));
|
||||
optionOption.setValueName("options...");
|
||||
QCommandLineOption optionOption(QStringList{ "o"_L1, "option"_L1 });
|
||||
optionOption.setDescription(
|
||||
"Format-specific options. Use --format-options to find out what options are available."_L1);
|
||||
optionOption.setValueName("options..."_L1);
|
||||
optionOption.setDefaultValues({});
|
||||
parser.addOption(optionOption);
|
||||
|
||||
QCommandLineOption formatOptionsOption("format-options");
|
||||
formatOptionsOption.setDescription(QStringLiteral("Prints the list of valid options for --option for the converter format <format>."));
|
||||
formatOptionsOption.setValueName("format");
|
||||
QCommandLineOption formatOptionsOption("format-options"_L1);
|
||||
formatOptionsOption.setDescription(
|
||||
"Prints the list of valid options for --option for the converter format <format>."_L1);
|
||||
formatOptionsOption.setValueName("format"_L1);
|
||||
parser.addOption(formatOptionsOption);
|
||||
|
||||
parser.addPositionalArgument(QStringLiteral("[source]"),
|
||||
QStringLiteral("File to read from (stdin if none)"));
|
||||
parser.addPositionalArgument(QStringLiteral("[destination]"),
|
||||
QStringLiteral("File to write to (stdout if none)"));
|
||||
parser.addPositionalArgument("[source]"_L1, "File to read from (stdin if none)"_L1);
|
||||
parser.addPositionalArgument("[destination]"_L1, "File to write to (stdout if none)"_L1);
|
||||
|
||||
parser.process(app);
|
||||
|
||||
if (parser.isSet(formatOptionsOption)) {
|
||||
QString format = parser.value(formatOptionsOption);
|
||||
for (Converter *conv : std::as_const(*availableConverters)) {
|
||||
for (const Converter *conv : std::as_const(*availableConverters)) {
|
||||
if (conv->name() == format) {
|
||||
const char *help = conv->optionsHelp();
|
||||
if (help)
|
||||
printf("The following options are available for format '%s':\n\n%s", qPrintable(format), help);
|
||||
else
|
||||
if (help) {
|
||||
printf("The following options are available for format '%s':\n\n%s",
|
||||
qPrintable(format), help);
|
||||
} else {
|
||||
printf("Format '%s' supports no options.\n", qPrintable(format));
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
@ -97,10 +103,10 @@ int main(int argc, char *argv[])
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Converter *inconv = nullptr;
|
||||
const Converter *inconv = nullptr;
|
||||
QString format = parser.value(inputFormatOption);
|
||||
if (format != "auto") {
|
||||
for (Converter *conv : std::as_const(*availableConverters)) {
|
||||
if (format != "auto"_L1) {
|
||||
for (const Converter *conv : std::as_const(*availableConverters)) {
|
||||
if (conv->name() == format) {
|
||||
inconv = conv;
|
||||
break;
|
||||
@ -113,10 +119,10 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
|
||||
Converter *outconv = nullptr;
|
||||
const Converter *outconv = nullptr;
|
||||
format = parser.value(outputFormatOption);
|
||||
if (format != "auto") {
|
||||
for (Converter *conv : std::as_const(*availableConverters)) {
|
||||
if (format != "auto"_L1) {
|
||||
for (const Converter *conv : std::as_const(*availableConverters)) {
|
||||
if (conv->name() == format) {
|
||||
outconv = conv;
|
||||
break;
|
||||
@ -155,8 +161,9 @@ int main(int argc, char *argv[])
|
||||
|
||||
if (!inconv) {
|
||||
// probe the input to find a file format
|
||||
for (Converter *conv : std::as_const(*availableConverters)) {
|
||||
if (conv->directions() & Converter::In && conv->probeFile(&input)) {
|
||||
for (const Converter *conv : std::as_const(*availableConverters)) {
|
||||
if (conv->directions().testFlag(Converter::Direction::In)
|
||||
&& conv->probeFile(&input)) {
|
||||
inconv = conv;
|
||||
break;
|
||||
}
|
||||
@ -170,8 +177,9 @@ int main(int argc, char *argv[])
|
||||
|
||||
if (!outconv) {
|
||||
// probe the output to find a file format
|
||||
for (Converter *conv : std::as_const(*availableConverters)) {
|
||||
if (conv->directions() & Converter::Out && conv->probeFile(&output)) {
|
||||
for (const Converter *conv : std::as_const(*availableConverters)) {
|
||||
if (conv->directions().testFlag(Converter::Direction::Out)
|
||||
&& conv->probeFile(&output)) {
|
||||
outconv = conv;
|
||||
break;
|
||||
}
|
||||
|
@ -3,36 +3,38 @@
|
||||
|
||||
#include "nullconverter.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
static NullConverter nullConverter;
|
||||
Converter* Converter::null = &nullConverter;
|
||||
Converter *Converter::null = &nullConverter;
|
||||
|
||||
QString NullConverter::name()
|
||||
QString NullConverter::name() const
|
||||
{
|
||||
return QLatin1String("null");
|
||||
return "null"_L1;
|
||||
}
|
||||
|
||||
Converter::Direction NullConverter::directions()
|
||||
Converter::Directions NullConverter::directions() const
|
||||
{
|
||||
return Out;
|
||||
return Direction::Out;
|
||||
}
|
||||
|
||||
Converter::Options NullConverter::outputOptions()
|
||||
Converter::Options NullConverter::outputOptions() const
|
||||
{
|
||||
return SupportsArbitraryMapKeys;
|
||||
}
|
||||
|
||||
const char *NullConverter::optionsHelp()
|
||||
const char *NullConverter::optionsHelp() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool NullConverter::probeFile(QIODevice *f)
|
||||
bool NullConverter::probeFile(QIODevice *f) const
|
||||
{
|
||||
Q_UNUSED(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant NullConverter::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
QVariant NullConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const
|
||||
{
|
||||
Q_UNUSED(f);
|
||||
Q_UNUSED(outputConverter);
|
||||
@ -40,10 +42,12 @@ QVariant NullConverter::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void NullConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
|
||||
void NullConverter::saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const
|
||||
{
|
||||
if (!options.isEmpty()) {
|
||||
fprintf(stderr, "Unknown option '%s' to null output. This format has no options.\n", qPrintable(options.first()));
|
||||
fprintf(stderr, "Unknown option '%s' to null output. This format has no options.\n",
|
||||
qPrintable(options.first()));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
|
@ -10,13 +10,14 @@ class NullConverter : public Converter
|
||||
{
|
||||
// Converter interface
|
||||
public:
|
||||
QString name() override;
|
||||
Direction directions() override;
|
||||
Options outputOptions() override;
|
||||
const char *optionsHelp() override;
|
||||
bool probeFile(QIODevice *f) override;
|
||||
QVariant loadFile(QIODevice *f, Converter *&outputConverter) override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override;
|
||||
QString name() const override;
|
||||
Directions directions() const override;
|
||||
Options outputOptions() const override;
|
||||
const char *optionsHelp() const override;
|
||||
bool probeFile(QIODevice *f) const override;
|
||||
QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const override;
|
||||
};
|
||||
|
||||
#endif // NULLCONVERTER_H
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
static void dumpVariant(QTextStream &out, const QVariant &v)
|
||||
{
|
||||
switch (v.userType()) {
|
||||
@ -42,67 +44,62 @@ static void dumpVariant(QTextStream &out, const QVariant &v)
|
||||
}
|
||||
}
|
||||
|
||||
QString TextConverter::name()
|
||||
QString TextConverter::name() const
|
||||
{
|
||||
return QStringLiteral("text");
|
||||
return "text"_L1;
|
||||
}
|
||||
|
||||
Converter::Direction TextConverter::directions()
|
||||
Converter::Directions TextConverter::directions() const
|
||||
{
|
||||
return InOut;
|
||||
return Direction::InOut;
|
||||
}
|
||||
|
||||
Converter::Options TextConverter::outputOptions()
|
||||
Converter::Options TextConverter::outputOptions() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const char *TextConverter::optionsHelp()
|
||||
const char *TextConverter::optionsHelp() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool TextConverter::probeFile(QIODevice *f)
|
||||
bool TextConverter::probeFile(QIODevice *f) const
|
||||
{
|
||||
if (QFile *file = qobject_cast<QFile *>(f))
|
||||
return file->fileName().endsWith(QLatin1String(".txt"));
|
||||
return file->fileName().endsWith(".txt"_L1);
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant TextConverter::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
QVariant TextConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const
|
||||
{
|
||||
if (!outputConverter)
|
||||
outputConverter = this;
|
||||
|
||||
QVariantList list;
|
||||
QTextStream in(f);
|
||||
QString line ;
|
||||
QString line;
|
||||
while (!in.atEnd()) {
|
||||
in.readLineInto(&line);
|
||||
|
||||
bool ok;
|
||||
qint64 v = line.toLongLong(&ok);
|
||||
if (ok) {
|
||||
|
||||
if (qint64 v = line.toLongLong(&ok); ok)
|
||||
list.append(v);
|
||||
continue;
|
||||
}
|
||||
|
||||
double d = line.toDouble(&ok);
|
||||
if (ok) {
|
||||
else if (double d = line.toDouble(&ok); ok)
|
||||
list.append(d);
|
||||
continue;
|
||||
}
|
||||
|
||||
list.append(line);
|
||||
else
|
||||
list.append(line);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void TextConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
|
||||
void TextConverter::saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const
|
||||
{
|
||||
if (!options.isEmpty()) {
|
||||
fprintf(stderr, "Unknown option '%s' to text output. This format has no options.\n", qPrintable(options.first()));
|
||||
fprintf(stderr, "Unknown option '%s' to text output. This format has no options.\n",
|
||||
qPrintable(options.first()));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
|
@ -8,16 +8,16 @@
|
||||
|
||||
class TextConverter : public Converter
|
||||
{
|
||||
|
||||
// Converter interface
|
||||
public:
|
||||
QString name() override;
|
||||
Direction directions() override;
|
||||
Options outputOptions() override;
|
||||
const char *optionsHelp() override;
|
||||
bool probeFile(QIODevice *f) override;
|
||||
QVariant loadFile(QIODevice *f, Converter *&outputConverter) override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override;
|
||||
QString name() const override;
|
||||
Directions directions() const override;
|
||||
Options outputOptions() const override;
|
||||
const char *optionsHelp() const override;
|
||||
bool probeFile(QIODevice *f) const override;
|
||||
QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const override;
|
||||
};
|
||||
|
||||
#endif // TEXTCONVERTER_H
|
||||
|
@ -13,8 +13,9 @@
|
||||
#include <QXmlStreamReader>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
static const char xmlOptionHelp[] =
|
||||
"compact=no|yes Use compact XML form.\n";
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
static const char xmlOptionHelp[] = "compact=no|yes Use compact XML form.\n";
|
||||
|
||||
static XmlConverter xmlConverter;
|
||||
|
||||
@ -23,7 +24,7 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options
|
||||
static QVariantList listFromXml(QXmlStreamReader &xml, Converter::Options options)
|
||||
{
|
||||
QVariantList list;
|
||||
while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == QLatin1String("list"))) {
|
||||
while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == "list"_L1)) {
|
||||
xml.readNext();
|
||||
switch (xml.tokenType()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
@ -47,8 +48,7 @@ static QVariantList listFromXml(QXmlStreamReader &xml, Converter::Options option
|
||||
break;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n",
|
||||
xml.lineNumber(), xml.columnNumber(),
|
||||
fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", xml.lineNumber(), xml.columnNumber(),
|
||||
qPrintable(xml.tokenString()), qPrintable(xml.name().toString()));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
@ -57,10 +57,11 @@ static QVariantList listFromXml(QXmlStreamReader &xml, Converter::Options option
|
||||
return list;
|
||||
}
|
||||
|
||||
static VariantOrderedMap::value_type mapEntryFromXml(QXmlStreamReader &xml, Converter::Options options)
|
||||
static VariantOrderedMap::value_type mapEntryFromXml(QXmlStreamReader &xml,
|
||||
Converter::Options options)
|
||||
{
|
||||
QVariant key, value;
|
||||
while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == QLatin1String("entry"))) {
|
||||
while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == "entry"_L1)) {
|
||||
xml.readNext();
|
||||
switch (xml.tokenType()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
@ -89,8 +90,7 @@ static VariantOrderedMap::value_type mapEntryFromXml(QXmlStreamReader &xml, Conv
|
||||
break;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n",
|
||||
xml.lineNumber(), xml.columnNumber(),
|
||||
fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", xml.lineNumber(), xml.columnNumber(),
|
||||
qPrintable(xml.tokenString()), qPrintable(xml.name().toString()));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
@ -103,11 +103,11 @@ static QVariant mapFromXml(QXmlStreamReader &xml, Converter::Options options)
|
||||
QVariantMap map1;
|
||||
VariantOrderedMap map2;
|
||||
|
||||
while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == QLatin1String("map"))) {
|
||||
while (!xml.atEnd() && !(xml.isEndElement() && xml.name() == "map"_L1)) {
|
||||
xml.readNext();
|
||||
switch (xml.tokenType()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
if (xml.name() == QLatin1String("entry")) {
|
||||
if (xml.name() == "entry"_L1) {
|
||||
auto pair = mapEntryFromXml(xml, options);
|
||||
if (options & Converter::SupportsArbitraryMapKeys)
|
||||
map2.append(pair);
|
||||
@ -134,8 +134,7 @@ static QVariant mapFromXml(QXmlStreamReader &xml, Converter::Options options)
|
||||
break;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n",
|
||||
xml.lineNumber(), xml.columnNumber(),
|
||||
fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", xml.lineNumber(), xml.columnNumber(),
|
||||
qPrintable(xml.tokenString()), qPrintable(xml.name().toString()));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
@ -149,18 +148,18 @@ static QVariant mapFromXml(QXmlStreamReader &xml, Converter::Options options)
|
||||
static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options)
|
||||
{
|
||||
QStringView name = xml.name();
|
||||
if (name == QLatin1String("list"))
|
||||
if (name == "list"_L1)
|
||||
return listFromXml(xml, options);
|
||||
if (name == QLatin1String("map"))
|
||||
if (name == "map"_L1)
|
||||
return mapFromXml(xml, options);
|
||||
if (name != QLatin1String("value")) {
|
||||
if (name != "value"_L1) {
|
||||
fprintf(stderr, "%lld:%lld: Invalid XML key '%s'.\n",
|
||||
xml.lineNumber(), xml.columnNumber(), qPrintable(name.toString()));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
QXmlStreamAttributes attrs = xml.attributes();
|
||||
QStringView type = attrs.value(QLatin1String("type"));
|
||||
QStringView type = attrs.value("type"_L1);
|
||||
|
||||
forever {
|
||||
xml.readNext();
|
||||
@ -169,8 +168,7 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options
|
||||
if (xml.isCDATA() || xml.isCharacters() || xml.isEndElement())
|
||||
break;
|
||||
|
||||
fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n",
|
||||
xml.lineNumber(), xml.columnNumber(),
|
||||
fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", xml.lineNumber(), xml.columnNumber(),
|
||||
qPrintable(xml.tokenString()), qPrintable(name.toString()));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
@ -180,45 +178,45 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options
|
||||
text = text.trimmed();
|
||||
|
||||
QVariant result;
|
||||
bool ok;
|
||||
if (type.isEmpty()) {
|
||||
// ok
|
||||
} else if (type == QLatin1String("number")) {
|
||||
} else if (type == "number"_L1) {
|
||||
// try integer first
|
||||
bool ok;
|
||||
qint64 v = text.toLongLong(&ok);
|
||||
if (ok) {
|
||||
result = v;
|
||||
} else {
|
||||
// let's see floating point
|
||||
double d = text.toDouble(&ok);
|
||||
result = d;
|
||||
if (!ok) {
|
||||
fprintf(stderr, "%lld:%lld: Invalid XML: could not interpret '%s' as a number.\n",
|
||||
xml.lineNumber(), xml.columnNumber(), qPrintable(text.toString()));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
result = d;
|
||||
}
|
||||
} else if (type == QLatin1String("bytes")) {
|
||||
} else if (type == "bytes"_L1) {
|
||||
QByteArray data = text.toLatin1();
|
||||
QStringView encoding = attrs.value("encoding");
|
||||
if (encoding == QLatin1String("base64url")) {
|
||||
if (encoding == "base64url"_L1) {
|
||||
result = QByteArray::fromBase64(data, QByteArray::Base64UrlEncoding);
|
||||
} else if (encoding == QLatin1String("hex")) {
|
||||
} else if (encoding == "hex"_L1) {
|
||||
result = QByteArray::fromHex(data);
|
||||
} else if (encoding.isEmpty() || encoding == QLatin1String("base64")) {
|
||||
} else if (encoding.isEmpty() || encoding == "base64"_L1) {
|
||||
result = QByteArray::fromBase64(data);
|
||||
} else {
|
||||
fprintf(stderr, "%lld:%lld: Invalid XML: unknown encoding '%s' for bytes.\n",
|
||||
xml.lineNumber(), xml.columnNumber(), qPrintable(encoding.toString()));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (type == QLatin1String("string")) {
|
||||
} else if (type == "string"_L1) {
|
||||
result = text.toString();
|
||||
} else if (type == QLatin1String("null")) {
|
||||
} else if (type == "null"_L1) {
|
||||
result = QVariant::fromValue(nullptr);
|
||||
} else if (type == QLatin1String("CBOR simple type")) {
|
||||
} else if (type == "CBOR simple type"_L1) {
|
||||
result = QVariant::fromValue(QCborSimpleType(text.toShort()));
|
||||
} else if (type == QLatin1String("bits")) {
|
||||
} else if (type == "bits"_L1) {
|
||||
QBitArray ba;
|
||||
ba.resize(text.size());
|
||||
qsizetype n = 0;
|
||||
@ -238,13 +236,13 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options
|
||||
result = ba;
|
||||
} else {
|
||||
int id = QMetaType::UnknownType;
|
||||
if (type == QLatin1String("datetime"))
|
||||
if (type == "datetime"_L1)
|
||||
id = QMetaType::QDateTime;
|
||||
else if (type == QLatin1String("url"))
|
||||
else if (type == "url"_L1)
|
||||
id = QMetaType::QUrl;
|
||||
else if (type == QLatin1String("uuid"))
|
||||
else if (type == "uuid"_L1)
|
||||
id = QMetaType::QUuid;
|
||||
else if (type == QLatin1String("regex"))
|
||||
else if (type == "regex"_L1)
|
||||
id = QMetaType::QRegularExpression;
|
||||
else
|
||||
id = QMetaType::fromName(type.toLatin1()).id();
|
||||
@ -267,8 +265,7 @@ static QVariant variantFromXml(QXmlStreamReader &xml, Converter::Options options
|
||||
} while (xml.isComment() || xml.isWhitespace());
|
||||
|
||||
if (!xml.isEndElement()) {
|
||||
fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n",
|
||||
xml.lineNumber(), xml.columnNumber(),
|
||||
fprintf(stderr, "%lld:%lld: Invalid XML %s '%s'.\n", xml.lineNumber(), xml.columnNumber(),
|
||||
qPrintable(xml.tokenString()), qPrintable(name.toString()));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
@ -287,9 +284,9 @@ static void variantToXml(QXmlStreamWriter &xml, const QVariant &v)
|
||||
variantToXml(xml, v);
|
||||
xml.writeEndElement();
|
||||
} else if (type == QMetaType::QVariantMap || type == qMetaTypeId<VariantOrderedMap>()) {
|
||||
const VariantOrderedMap map = (type == QMetaType::QVariantMap) ?
|
||||
VariantOrderedMap(v.toMap()) :
|
||||
qvariant_cast<VariantOrderedMap>(v);
|
||||
const VariantOrderedMap map = (type == QMetaType::QVariantMap)
|
||||
? VariantOrderedMap(v.toMap())
|
||||
: qvariant_cast<VariantOrderedMap>(v);
|
||||
|
||||
xml.writeStartElement("map");
|
||||
for (const auto &pair : map) {
|
||||
@ -301,7 +298,7 @@ static void variantToXml(QXmlStreamWriter &xml, const QVariant &v)
|
||||
xml.writeEndElement();
|
||||
} else {
|
||||
xml.writeStartElement("value");
|
||||
QString typeString = QStringLiteral("type");
|
||||
QString typeString = "type"_L1;
|
||||
switch (type) {
|
||||
case QMetaType::Short:
|
||||
case QMetaType::UShort:
|
||||
@ -399,37 +396,37 @@ static void variantToXml(QXmlStreamWriter &xml, const QVariant &v)
|
||||
}
|
||||
}
|
||||
|
||||
QString XmlConverter::name()
|
||||
QString XmlConverter::name() const
|
||||
{
|
||||
return QStringLiteral("xml");
|
||||
return "xml"_L1;
|
||||
}
|
||||
|
||||
Converter::Direction XmlConverter::directions()
|
||||
Converter::Directions XmlConverter::directions() const
|
||||
{
|
||||
return InOut;
|
||||
return Direction::InOut;
|
||||
}
|
||||
|
||||
Converter::Options XmlConverter::outputOptions()
|
||||
Converter::Options XmlConverter::outputOptions() const
|
||||
{
|
||||
return SupportsArbitraryMapKeys;
|
||||
}
|
||||
|
||||
const char *XmlConverter::optionsHelp()
|
||||
const char *XmlConverter::optionsHelp() const
|
||||
{
|
||||
return xmlOptionHelp;
|
||||
}
|
||||
|
||||
bool XmlConverter::probeFile(QIODevice *f)
|
||||
bool XmlConverter::probeFile(QIODevice *f) const
|
||||
{
|
||||
if (QFile *file = qobject_cast<QFile *>(f)) {
|
||||
if (file->fileName().endsWith(QLatin1String(".xml")))
|
||||
if (file->fileName().endsWith(".xml"_L1))
|
||||
return true;
|
||||
}
|
||||
|
||||
return f->isReadable() && f->peek(5) == "<?xml";
|
||||
}
|
||||
|
||||
QVariant XmlConverter::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
QVariant XmlConverter::loadFile(QIODevice *f, const Converter *&outputConverter) const
|
||||
{
|
||||
if (!outputConverter)
|
||||
outputConverter = this;
|
||||
@ -445,13 +442,14 @@ QVariant XmlConverter::loadFile(QIODevice *f, Converter *&outputConverter)
|
||||
return v;
|
||||
}
|
||||
|
||||
void XmlConverter::saveFile(QIODevice *f, const QVariant &contents, const QStringList &options)
|
||||
void XmlConverter::saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const
|
||||
{
|
||||
bool compact = false;
|
||||
for (const QString &s : options) {
|
||||
if (s == QLatin1String("compact=no")) {
|
||||
if (s == "compact=no"_L1) {
|
||||
compact = false;
|
||||
} else if (s == QLatin1String("compact=yes")) {
|
||||
} else if (s == "compact=yes"_L1) {
|
||||
compact = true;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown option '%s' to XML output. Valid options are:\n%s",
|
||||
|
@ -10,13 +10,14 @@ class XmlConverter : public Converter
|
||||
{
|
||||
// Converter interface
|
||||
public:
|
||||
QString name() override;
|
||||
Direction directions() override;
|
||||
Options outputOptions() override;
|
||||
const char *optionsHelp() override;
|
||||
bool probeFile(QIODevice *f) override;
|
||||
QVariant loadFile(QIODevice *f, Converter *&outputConverter) override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents, const QStringList &options) override;
|
||||
QString name() const override;
|
||||
Directions directions() const override;
|
||||
Options outputOptions() const override;
|
||||
const char *optionsHelp() const override;
|
||||
bool probeFile(QIODevice *f) const override;
|
||||
QVariant loadFile(QIODevice *f, const Converter *&outputConverter) const override;
|
||||
void saveFile(QIODevice *f, const QVariant &contents,
|
||||
const QStringList &options) const override;
|
||||
};
|
||||
|
||||
#endif // XMLCONVERTER_H
|
||||
|
@ -1,38 +0,0 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(rsslisting LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/serialization/rsslisting")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Network Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(rsslisting
|
||||
main.cpp
|
||||
rsslisting.cpp rsslisting.h
|
||||
)
|
||||
|
||||
set_target_properties(rsslisting PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(rsslisting PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Network
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS rsslisting
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
@ -1,26 +0,0 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
/*
|
||||
main.cpp
|
||||
|
||||
Provides the main function for the RSS news reader example.
|
||||
*/
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "rsslisting.h"
|
||||
|
||||
/*!
|
||||
Create an application and a main widget. Open the main widget for
|
||||
user input, and exit with an appropriate return value when it is
|
||||
closed.
|
||||
*/
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
RSSListing *rsslisting = new RSSListing;
|
||||
rsslisting->show();
|
||||
return app.exec();
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
/*
|
||||
rsslisting.cpp
|
||||
|
||||
Provides a widget for displaying news items from RDF news sources.
|
||||
RDF is an XML-based format for storing items of information (see
|
||||
http://www.w3.org/RDF/ for details).
|
||||
|
||||
The widget itself provides a simple user interface for specifying
|
||||
the URL of a news source, and controlling the downloading of news.
|
||||
|
||||
The widget downloads and parses the XML asynchronously, feeding the
|
||||
data to an XML reader in pieces. This allows the user to interrupt
|
||||
its operation, and also allows very large data sources to be read.
|
||||
*/
|
||||
|
||||
|
||||
#include <QtCore>
|
||||
#include <QtWidgets>
|
||||
#include <QtNetwork>
|
||||
|
||||
#include "rsslisting.h"
|
||||
|
||||
|
||||
/*
|
||||
Constructs an RSSListing widget with a simple user interface, and sets
|
||||
up the XML reader to use a custom handler class.
|
||||
|
||||
The user interface consists of a line edit, a push button, and a
|
||||
list view widget. The line edit is used for entering the URLs of news
|
||||
sources; the push button starts the process of reading the
|
||||
news.
|
||||
*/
|
||||
|
||||
RSSListing::RSSListing(QWidget *parent)
|
||||
: QWidget(parent), currentReply(0)
|
||||
{
|
||||
|
||||
lineEdit = new QLineEdit(this);
|
||||
lineEdit->setText("http://blog.qt.io/feed/");
|
||||
|
||||
fetchButton = new QPushButton(tr("Fetch"), this);
|
||||
|
||||
treeWidget = new QTreeWidget(this);
|
||||
connect(treeWidget, &QTreeWidget::itemActivated,
|
||||
this, &RSSListing::itemActivated);
|
||||
QStringList headerLabels;
|
||||
headerLabels << tr("Title") << tr("Link");
|
||||
treeWidget->setHeaderLabels(headerLabels);
|
||||
treeWidget->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
|
||||
connect(&manager, &QNetworkAccessManager::finished,
|
||||
this, &RSSListing::finished);
|
||||
|
||||
connect(lineEdit, &QLineEdit::returnPressed, this, &RSSListing::fetch);
|
||||
connect(fetchButton, &QPushButton::clicked, this, &RSSListing::fetch);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
QHBoxLayout *hboxLayout = new QHBoxLayout;
|
||||
|
||||
hboxLayout->addWidget(lineEdit);
|
||||
hboxLayout->addWidget(fetchButton);
|
||||
|
||||
layout->addLayout(hboxLayout);
|
||||
layout->addWidget(treeWidget);
|
||||
|
||||
setWindowTitle(tr("RSS listing example"));
|
||||
resize(640,480);
|
||||
}
|
||||
|
||||
/*
|
||||
Starts the network request and connects the needed signals
|
||||
*/
|
||||
void RSSListing::get(const QUrl &url)
|
||||
{
|
||||
QNetworkRequest request(url);
|
||||
if (currentReply) {
|
||||
currentReply->disconnect(this);
|
||||
currentReply->deleteLater();
|
||||
}
|
||||
currentReply = manager.get(request);
|
||||
connect(currentReply, &QNetworkReply::readyRead, this, &RSSListing::readyRead);
|
||||
connect(currentReply, &QNetworkReply::metaDataChanged, this, &RSSListing::metaDataChanged);
|
||||
connect(currentReply, &QNetworkReply::errorOccurred, this, &RSSListing::error);
|
||||
}
|
||||
|
||||
/*
|
||||
Starts fetching data from a news source specified in the line
|
||||
edit widget.
|
||||
|
||||
The line edit is made read only to prevent the user from modifying its
|
||||
contents during the fetch; this is only for cosmetic purposes.
|
||||
The fetch button is disabled, the list view is cleared, and we
|
||||
define the last list view item to be 0, meaning that there are no
|
||||
existing items in the list.
|
||||
|
||||
A URL is created with the raw contents of the line edit and
|
||||
a get is initiated.
|
||||
*/
|
||||
|
||||
void RSSListing::fetch()
|
||||
{
|
||||
lineEdit->setReadOnly(true);
|
||||
fetchButton->setEnabled(false);
|
||||
treeWidget->clear();
|
||||
|
||||
xml.clear();
|
||||
|
||||
QUrl url(lineEdit->text());
|
||||
get(url);
|
||||
}
|
||||
|
||||
void RSSListing::metaDataChanged()
|
||||
{
|
||||
QUrl redirectionTarget = currentReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
|
||||
if (redirectionTarget.isValid()) {
|
||||
get(redirectionTarget);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Reads data received from the RDF source.
|
||||
|
||||
We read all the available data, and pass it to the XML
|
||||
stream reader. Then we call the XML parsing function.
|
||||
*/
|
||||
|
||||
void RSSListing::readyRead()
|
||||
{
|
||||
int statusCode = currentReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (statusCode >= 200 && statusCode < 300) {
|
||||
QByteArray data = currentReply->readAll();
|
||||
xml.addData(data);
|
||||
parseXml();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Finishes processing an HTTP request.
|
||||
|
||||
The default behavior is to keep the text edit read only.
|
||||
|
||||
If an error has occurred, the user interface is made available
|
||||
to the user for further input, allowing a new fetch to be
|
||||
started.
|
||||
|
||||
If the HTTP get request has finished, we make the
|
||||
user interface available to the user for further input.
|
||||
*/
|
||||
|
||||
void RSSListing::finished(QNetworkReply *reply)
|
||||
{
|
||||
Q_UNUSED(reply);
|
||||
lineEdit->setReadOnly(false);
|
||||
fetchButton->setEnabled(true);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Parses the XML data and creates treeWidget items accordingly.
|
||||
*/
|
||||
void RSSListing::parseXml()
|
||||
{
|
||||
while (!xml.atEnd()) {
|
||||
xml.readNext();
|
||||
if (xml.isStartElement()) {
|
||||
if (xml.name() == u"item")
|
||||
linkString = xml.attributes().value("rss:about").toString();
|
||||
currentTag = xml.name().toString();
|
||||
} else if (xml.isEndElement()) {
|
||||
if (xml.name() == u"item") {
|
||||
|
||||
QTreeWidgetItem *item = new QTreeWidgetItem;
|
||||
item->setText(0, titleString);
|
||||
item->setText(1, linkString);
|
||||
treeWidget->addTopLevelItem(item);
|
||||
|
||||
titleString.clear();
|
||||
linkString.clear();
|
||||
}
|
||||
|
||||
} else if (xml.isCharacters() && !xml.isWhitespace()) {
|
||||
if (currentTag == "title")
|
||||
titleString += xml.text();
|
||||
else if (currentTag == "link")
|
||||
linkString += xml.text();
|
||||
}
|
||||
}
|
||||
if (xml.error() && xml.error() != QXmlStreamReader::PrematureEndOfDocumentError) {
|
||||
qWarning() << "XML ERROR:" << xml.lineNumber() << ": " << xml.errorString();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Open the link in the browser
|
||||
*/
|
||||
void RSSListing::itemActivated(QTreeWidgetItem * item)
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl(item->text(1)));
|
||||
}
|
||||
|
||||
void RSSListing::error(QNetworkReply::NetworkError)
|
||||
{
|
||||
qWarning("error retrieving RSS feed");
|
||||
currentReply->disconnect(this);
|
||||
currentReply->deleteLater();
|
||||
currentReply = 0;
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef RSSLISTING_H
|
||||
#define RSSLISTING_H
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QWidget>
|
||||
#include <QBuffer>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QUrl>
|
||||
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLineEdit;
|
||||
class QTreeWidget;
|
||||
class QTreeWidgetItem;
|
||||
class QPushButton;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class RSSListing : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RSSListing(QWidget *widget = nullptr);
|
||||
|
||||
public slots:
|
||||
void fetch();
|
||||
void finished(QNetworkReply *reply);
|
||||
void readyRead();
|
||||
void metaDataChanged();
|
||||
void itemActivated(QTreeWidgetItem * item);
|
||||
void error(QNetworkReply::NetworkError);
|
||||
|
||||
private:
|
||||
void parseXml();
|
||||
void get(const QUrl &url);
|
||||
|
||||
QXmlStreamReader xml;
|
||||
QString currentTag;
|
||||
QString linkString;
|
||||
QString titleString;
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
QNetworkReply *currentReply;
|
||||
|
||||
QLineEdit *lineEdit;
|
||||
QTreeWidget *treeWidget;
|
||||
QPushButton *fetchButton;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -1,8 +0,0 @@
|
||||
HEADERS += rsslisting.h
|
||||
SOURCES += main.cpp rsslisting.cpp
|
||||
QT += network widgets
|
||||
requires(qtConfig(treewidget))
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/rsslisting
|
||||
INSTALLS += target
|
@ -4,6 +4,10 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(savegame LANGUAGES CXX)
|
||||
|
||||
if (ANDROID)
|
||||
message(FATAL_ERROR "This project cannot be built on Android.")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
@ -6,15 +6,10 @@
|
||||
#include <QMetaEnum>
|
||||
#include <QTextStream>
|
||||
|
||||
Character::Character()
|
||||
= default;
|
||||
Character::Character() = default;
|
||||
|
||||
Character::Character(const QString &name,
|
||||
int level,
|
||||
Character::ClassType classType) :
|
||||
mName(name),
|
||||
mLevel(level),
|
||||
mClassType(classType)
|
||||
Character::Character(const QString &name, int level, Character::ClassType classType)
|
||||
: mName(name), mLevel(level), mClassType(classType)
|
||||
{
|
||||
}
|
||||
|
||||
@ -48,35 +43,41 @@ void Character::setClassType(Character::ClassType classType)
|
||||
mClassType = classType;
|
||||
}
|
||||
|
||||
//! [0]
|
||||
void Character::read(const QJsonObject &json)
|
||||
//! [fromJson]
|
||||
Character Character::fromJson(const QJsonObject &json)
|
||||
{
|
||||
if (json.contains("name") && json["name"].isString())
|
||||
mName = json["name"].toString();
|
||||
Character result;
|
||||
|
||||
if (json.contains("level") && json["level"].isDouble())
|
||||
mLevel = json["level"].toInt();
|
||||
if (const QJsonValue v = json["name"]; v.isString())
|
||||
result.mName = v.toString();
|
||||
|
||||
if (json.contains("classType") && json["classType"].isDouble())
|
||||
mClassType = ClassType(json["classType"].toInt());
|
||||
if (const QJsonValue v = json["level"]; v.isDouble())
|
||||
result.mLevel = v.toInt();
|
||||
|
||||
if (const QJsonValue v = json["classType"]; v.isDouble())
|
||||
result.mClassType = ClassType(v.toInt());
|
||||
|
||||
return result;
|
||||
}
|
||||
//! [0]
|
||||
//! [fromJson]
|
||||
|
||||
//! [1]
|
||||
void Character::write(QJsonObject &json) const
|
||||
//! [toJson]
|
||||
QJsonObject Character::toJson() const
|
||||
{
|
||||
QJsonObject json;
|
||||
json["name"] = mName;
|
||||
json["level"] = mLevel;
|
||||
json["classType"] = mClassType;
|
||||
return json;
|
||||
}
|
||||
//! [1]
|
||||
//! [toJson]
|
||||
|
||||
void Character::print(int indentation) const
|
||||
void Character::print(QTextStream &s, int indentation) const
|
||||
{
|
||||
const QString indent(indentation * 2, ' ');
|
||||
QTextStream(stdout) << indent << "Name:\t" << mName << "\n";
|
||||
QTextStream(stdout) << indent << "Level:\t" << mLevel << "\n";
|
||||
const QString className = QMetaEnum::fromType<ClassType>().valueToKey(mClassType);
|
||||
|
||||
QString className = QMetaEnum::fromType<ClassType>().valueToKey(mClassType);
|
||||
QTextStream(stdout) << indent << "Class:\t" << className << "\n";
|
||||
s << indent << "Name:\t" << mName << "\n"
|
||||
<< indent << "Level:\t" << mLevel << "\n"
|
||||
<< indent << "Class:\t" << className << "\n";
|
||||
}
|
||||
|
@ -8,15 +8,15 @@
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QTextStream)
|
||||
|
||||
//! [0]
|
||||
class Character
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
enum ClassType {
|
||||
Warrior, Mage, Archer
|
||||
};
|
||||
enum ClassType { Warrior, Mage, Archer };
|
||||
Q_ENUM(ClassType)
|
||||
|
||||
Character();
|
||||
@ -31,10 +31,11 @@ public:
|
||||
ClassType classType() const;
|
||||
void setClassType(ClassType classType);
|
||||
|
||||
void read(const QJsonObject &json);
|
||||
void write(QJsonObject &json) const;
|
||||
static Character fromJson(const QJsonObject &json);
|
||||
QJsonObject toJson() const;
|
||||
|
||||
void print(QTextStream &s, int indentation = 0) const;
|
||||
|
||||
void print(int indentation = 0) const;
|
||||
private:
|
||||
QString mName;
|
||||
int mLevel = 0;
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
/*!
|
||||
\example serialization/savegame
|
||||
\examplecategory {Input/Output}
|
||||
\examplecategory {Data Processing & I/O}
|
||||
\title JSON Save Game Example
|
||||
|
||||
\brief The JSON Save Game example demonstrates how to save and load a
|
||||
@ -11,11 +11,12 @@
|
||||
|
||||
Many games provide save functionality, so that the player's progress through
|
||||
the game can be saved and loaded at a later time. The process of saving a
|
||||
game generally involves serializing each game object's member variables
|
||||
to a file. Many formats can be used for this purpose, one of which is JSON.
|
||||
With QJsonDocument, you also have the ability to serialize a document in a
|
||||
\l {RFC 7049} {CBOR} format, which is great if you
|
||||
don't want the save file to be readable, or if you need to keep the file size down.
|
||||
game generally involves serializing each game object's member variables to a
|
||||
file. Many formats can be used for this purpose, one of which is JSON. With
|
||||
QJsonDocument, you also have the ability to serialize a document in a \l
|
||||
{RFC 7049} {CBOR} format, which is great if you don't want the save file to
|
||||
be easy to read (but see \l {Parsing and displaying CBOR data} for how it \e
|
||||
can be read), or if you need to keep the file size down.
|
||||
|
||||
In this example, we'll demonstrate how to save and load a simple game to
|
||||
and from JSON and binary formats.
|
||||
@ -25,45 +26,83 @@
|
||||
The Character class represents a non-player character (NPC) in our game, and
|
||||
stores the player's name, level, and class type.
|
||||
|
||||
It provides read() and write() functions to serialise its member variables.
|
||||
It provides static fromJson() and non-static toJson() functions to
|
||||
serialise itself.
|
||||
|
||||
\note This pattern (fromJson()/toJson()) works because QJsonObjects can be
|
||||
constructed independent of an owning QJsonDocument, and because the data
|
||||
types being (de)serialized here are value types, so can be copied. When
|
||||
serializing to another format — for example XML or QDataStream, which require passing
|
||||
a document-like object — or when the object identity is important (QObject
|
||||
subclasses, for example), other patterns may be more suitable. See the
|
||||
\l{xml/dombookmarks} and \l{xml/streambookmarks} examples for XML, and the
|
||||
implementation of \l QListWidgetItem::read() and \l QListWidgetItem::write()
|
||||
for idiomatic QDataStream serialization. The \c{print()} functions in this example
|
||||
are good examples of QTextStream serialization, even though they, of course, lack
|
||||
the deserialization side.
|
||||
|
||||
\snippet serialization/savegame/character.h 0
|
||||
|
||||
Of particular interest to us are the read and write function
|
||||
Of particular interest to us are the fromJson() and toJson() function
|
||||
implementations:
|
||||
|
||||
\snippet serialization/savegame/character.cpp 0
|
||||
\snippet serialization/savegame/character.cpp fromJson
|
||||
|
||||
In the read() function, we assign Character's members values from the
|
||||
QJsonObject argument. You can use either \l QJsonObject::operator[]() or
|
||||
QJsonObject::value() to access values within the JSON object; both are
|
||||
const functions and return QJsonValue::Undefined if the key is invalid. We
|
||||
check if the keys are valid before attempting to read them with
|
||||
QJsonObject::contains().
|
||||
In the fromJson() function, we construct a local \c result Character object
|
||||
and assign \c{result}'s members values from the QJsonObject argument. You
|
||||
can use either \l QJsonObject::operator[]() or QJsonObject::value() to
|
||||
access values within the JSON object; both are const functions and return
|
||||
QJsonValue::Undefined if the key is invalid. In particular, the \c{is...}
|
||||
functions (for example \l QJsonValue::isString(), \l
|
||||
QJsonValue::isDouble()) return \c false for QJsonValue::Undefined, so we
|
||||
can check for existence as well as the correct type in a single lookup.
|
||||
|
||||
\snippet serialization/savegame/character.cpp 1
|
||||
If a value does not exist in the JSON object, or has the wrong type, we
|
||||
don't write to the corresponding \c result member, either, thereby
|
||||
preserving any values the default constructor may have set. This means
|
||||
default values are centrally defined in one location (the default
|
||||
constructor) and need not be repeated in serialisation code
|
||||
(\l{https://en.wikipedia.org/wiki/Don%27t_repeat_yourself}{DRY}).
|
||||
|
||||
In the write() function, we do the reverse of the read() function; assign
|
||||
values from the Character object to the JSON object. As with accessing
|
||||
values, there are two ways to set values on a QJsonObject:
|
||||
\l QJsonObject::operator[]() and QJsonObject::insert(). Both will override
|
||||
any existing value at the given key.
|
||||
Observe the use of
|
||||
\l{https://en.cppreference.com/w/cpp/language/if#If_statements_with_initializer}
|
||||
{C++17 if-with-initializer} to separate scoping and checking of the variable \c v.
|
||||
This means we can keep the variable name short, because its scope is limited.
|
||||
|
||||
Next up is the Level class:
|
||||
Compare that to the naïve approach using \c QJsonObject::contains():
|
||||
|
||||
\badcode
|
||||
if (json.contains("name") && json["name"].isString())
|
||||
result.mName = json["name"].toString();
|
||||
\endcode
|
||||
|
||||
which, beside being less readable, requires a total of three lookups (no,
|
||||
the compiler will \e not optimize these into one), so is three times
|
||||
slower and repeats \c{"name"} three times (violating the DRY principle).
|
||||
|
||||
\snippet serialization/savegame/character.cpp toJson
|
||||
|
||||
In the toJson() function, we do the reverse of the fromJson() function;
|
||||
assign values from the Character object to a new JSON object we then
|
||||
return. As with accessing values, there are two ways to set values on a
|
||||
QJsonObject: \l QJsonObject::operator[]() and \l QJsonObject::insert().
|
||||
Both will override any existing value at the given key.
|
||||
|
||||
\section1 The Level Class
|
||||
|
||||
\snippet serialization/savegame/level.h 0
|
||||
|
||||
We want to have several levels in our game, each with several NPCs, so we
|
||||
keep a QList of Character objects. We also provide the familiar read() and
|
||||
write() functions.
|
||||
We want the levels in our game to each each have several NPCs, so we keep a QList
|
||||
of Character objects. We also provide the familiar fromJson() and toJson()
|
||||
functions.
|
||||
|
||||
\snippet serialization/savegame/level.cpp 0
|
||||
\snippet serialization/savegame/level.cpp fromJson
|
||||
|
||||
Containers can be written and read to and from JSON using QJsonArray. In our
|
||||
Containers can be written to and read from JSON using QJsonArray. In our
|
||||
case, we construct a QJsonArray from the value associated with the key
|
||||
\c "npcs". Then, for each QJsonValue element in the array, we call
|
||||
toObject() to get the Character's JSON object. The Character object can then
|
||||
read their JSON and be appended to our NPC array.
|
||||
toObject() to get the Character's JSON object. Character::fromJson() can
|
||||
then turn that QJSonObject into a Character object to append to our NPC array.
|
||||
|
||||
\note \l{Container Classes}{Associate containers} can be written by storing
|
||||
the key in each value object (if it's not already). With this approach, the
|
||||
@ -71,11 +110,13 @@
|
||||
element is used as the key to construct the container when reading it back
|
||||
in.
|
||||
|
||||
\snippet serialization/savegame/level.cpp 1
|
||||
\snippet serialization/savegame/level.cpp toJson
|
||||
|
||||
Again, the write() function is similar to the read() function, except
|
||||
Again, the toJson() function is similar to the fromJson() function, except
|
||||
reversed.
|
||||
|
||||
\section1 The Game Class
|
||||
|
||||
Having established the Character and Level classes, we can move on to
|
||||
the Game class:
|
||||
|
||||
@ -87,26 +128,43 @@
|
||||
Next, we provide accessors for the player and levels. We then expose three
|
||||
functions: newGame(), saveGame() and loadGame().
|
||||
|
||||
The read() and write() functions are used by saveGame() and loadGame().
|
||||
The read() and toJson() functions are used by saveGame() and loadGame().
|
||||
|
||||
\snippet serialization/savegame/game.cpp 0
|
||||
\div{class="admonition note"}\b{Note:}
|
||||
Despite \c Game being a value class, we assume that the author wants a game to have
|
||||
identity, much like your main window would have. We therefore don't use a
|
||||
static fromJson() function, which would create a new object, but a read()
|
||||
function we can call on existing objects. There's a 1:1 correspondence
|
||||
between read() and fromJson(), in that one can be implemented in terms of
|
||||
the other:
|
||||
|
||||
\code
|
||||
void read(const QJsonObject &json) { *this = fromJson(json); }
|
||||
static Game fromObject(const QJsonObject &json) { Game g; g.read(json); return g; }
|
||||
\endcode
|
||||
|
||||
We just use what's more convenient for callers of the functions.
|
||||
\enddiv
|
||||
|
||||
\snippet serialization/savegame/game.cpp newGame
|
||||
|
||||
To setup a new game, we create the player and populate the levels and their
|
||||
NPCs.
|
||||
|
||||
\snippet serialization/savegame/game.cpp 1
|
||||
\snippet serialization/savegame/game.cpp read
|
||||
|
||||
The first thing we do in the read() function is tell the player to read
|
||||
itself. We then clear the level array so that calling loadGame() on the
|
||||
same Game object twice doesn't result in old levels hanging around.
|
||||
The read() function starts by replacing the player with the
|
||||
one read from JSON. We then clear() the level array so that calling
|
||||
loadGame() on the same Game object twice doesn't result in old levels
|
||||
hanging around.
|
||||
|
||||
We then populate the level array by reading each Level from a QJsonArray.
|
||||
|
||||
\snippet serialization/savegame/game.cpp 2
|
||||
\snippet serialization/savegame/game.cpp toJson
|
||||
|
||||
We write the game to JSON similarly to how we write Level.
|
||||
Writing the game to JSON is similar to writing a level.
|
||||
|
||||
\snippet serialization/savegame/game.cpp 3
|
||||
\snippet serialization/savegame/game.cpp loadGame
|
||||
|
||||
When loading a saved game in loadGame(), the first thing we do is open the
|
||||
save file based on which format it was saved to; \c "save.json" for JSON,
|
||||
@ -120,14 +178,16 @@
|
||||
After constructing the QJsonDocument, we instruct the Game object to read
|
||||
itself and then return \c true to indicate success.
|
||||
|
||||
\snippet serialization/savegame/game.cpp 4
|
||||
\snippet serialization/savegame/game.cpp saveGame
|
||||
|
||||
Not surprisingly, saveGame() looks very much like loadGame(). We determine
|
||||
the file extension based on the format, print a warning and return \c false
|
||||
if the opening of the file fails. We then write the Game object to a
|
||||
QJsonDocument, and call either QJsonDocument::toJson() or to
|
||||
QJsonDocument::toBinaryData() to save the game, depending on which format
|
||||
was specified.
|
||||
QJsonObject. To save the game in the format that was specified, we
|
||||
convert the JSON object into either a QJsonDocument for a subsequent
|
||||
QJsonDocument::toJson() call, or a QCborValue for QCborValue::toCbor().
|
||||
|
||||
\section1 Tying It All Together
|
||||
|
||||
We are now ready to enter main():
|
||||
|
||||
|
@ -11,6 +11,8 @@
|
||||
#include <QRandomGenerator>
|
||||
#include <QTextStream>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
Character Game::player() const
|
||||
{
|
||||
return mPlayer;
|
||||
@ -21,52 +23,45 @@ QList<Level> Game::levels() const
|
||||
return mLevels;
|
||||
}
|
||||
|
||||
//! [0]
|
||||
//! [newGame]
|
||||
void Game::newGame()
|
||||
{
|
||||
mPlayer = Character();
|
||||
mPlayer.setName(QStringLiteral("Hero"));
|
||||
mPlayer.setName("Hero"_L1);
|
||||
mPlayer.setClassType(Character::Archer);
|
||||
mPlayer.setLevel(QRandomGenerator::global()->bounded(15, 21));
|
||||
|
||||
mLevels.clear();
|
||||
mLevels.reserve(2);
|
||||
|
||||
Level village(QStringLiteral("Village"));
|
||||
Level village("Village"_L1);
|
||||
QList<Character> villageNpcs;
|
||||
villageNpcs.reserve(2);
|
||||
villageNpcs.append(Character(QStringLiteral("Barry the Blacksmith"),
|
||||
QRandomGenerator::global()->bounded(8, 11),
|
||||
Character::Warrior));
|
||||
villageNpcs.append(Character(QStringLiteral("Terry the Trader"),
|
||||
QRandomGenerator::global()->bounded(6, 8),
|
||||
Character::Warrior));
|
||||
villageNpcs.append(Character("Barry the Blacksmith"_L1,
|
||||
QRandomGenerator::global()->bounded(8, 11), Character::Warrior));
|
||||
villageNpcs.append(Character("Terry the Trader"_L1,
|
||||
QRandomGenerator::global()->bounded(6, 8), Character::Warrior));
|
||||
village.setNpcs(villageNpcs);
|
||||
mLevels.append(village);
|
||||
|
||||
Level dungeon(QStringLiteral("Dungeon"));
|
||||
Level dungeon("Dungeon"_L1);
|
||||
QList<Character> dungeonNpcs;
|
||||
dungeonNpcs.reserve(3);
|
||||
dungeonNpcs.append(Character(QStringLiteral("Eric the Evil"),
|
||||
QRandomGenerator::global()->bounded(18, 26),
|
||||
Character::Mage));
|
||||
dungeonNpcs.append(Character(QStringLiteral("Eric's Left Minion"),
|
||||
QRandomGenerator::global()->bounded(5, 7),
|
||||
Character::Warrior));
|
||||
dungeonNpcs.append(Character(QStringLiteral("Eric's Right Minion"),
|
||||
QRandomGenerator::global()->bounded(4, 9),
|
||||
Character::Warrior));
|
||||
dungeonNpcs.append(Character("Eric the Evil"_L1,
|
||||
QRandomGenerator::global()->bounded(18, 26), Character::Mage));
|
||||
dungeonNpcs.append(Character("Eric's Left Minion"_L1,
|
||||
QRandomGenerator::global()->bounded(5, 7), Character::Warrior));
|
||||
dungeonNpcs.append(Character("Eric's Right Minion"_L1,
|
||||
QRandomGenerator::global()->bounded(4, 9), Character::Warrior));
|
||||
dungeon.setNpcs(dungeonNpcs);
|
||||
mLevels.append(dungeon);
|
||||
}
|
||||
//! [0]
|
||||
//! [newGame]
|
||||
|
||||
//! [3]
|
||||
//! [loadGame]
|
||||
bool Game::loadGame(Game::SaveFormat saveFormat)
|
||||
{
|
||||
QFile loadFile(saveFormat == Json
|
||||
? QStringLiteral("save.json")
|
||||
: QStringLiteral("save.dat"));
|
||||
QFile loadFile(saveFormat == Json ? "save.json"_L1 : "save.dat"_L1);
|
||||
|
||||
if (!loadFile.open(QIODevice::ReadOnly)) {
|
||||
qWarning("Couldn't open save file.");
|
||||
@ -76,85 +71,72 @@ bool Game::loadGame(Game::SaveFormat saveFormat)
|
||||
QByteArray saveData = loadFile.readAll();
|
||||
|
||||
QJsonDocument loadDoc(saveFormat == Json
|
||||
? QJsonDocument::fromJson(saveData)
|
||||
: QJsonDocument(QCborValue::fromCbor(saveData).toMap().toJsonObject()));
|
||||
? QJsonDocument::fromJson(saveData)
|
||||
: QJsonDocument(QCborValue::fromCbor(saveData).toMap().toJsonObject()));
|
||||
|
||||
read(loadDoc.object());
|
||||
|
||||
QTextStream(stdout) << "Loaded save for "
|
||||
<< loadDoc["player"]["name"].toString()
|
||||
<< " using "
|
||||
<< (saveFormat != Json ? "CBOR" : "JSON") << "...\n";
|
||||
QTextStream(stdout) << "Loaded save for " << loadDoc["player"]["name"].toString()
|
||||
<< " using " << (saveFormat != Json ? "CBOR" : "JSON") << "...\n";
|
||||
return true;
|
||||
}
|
||||
//! [3]
|
||||
//! [loadGame]
|
||||
|
||||
//! [4]
|
||||
//! [saveGame]
|
||||
bool Game::saveGame(Game::SaveFormat saveFormat) const
|
||||
{
|
||||
QFile saveFile(saveFormat == Json
|
||||
? QStringLiteral("save.json")
|
||||
: QStringLiteral("save.dat"));
|
||||
QFile saveFile(saveFormat == Json ? "save.json"_L1 : "save.dat"_L1);
|
||||
|
||||
if (!saveFile.open(QIODevice::WriteOnly)) {
|
||||
qWarning("Couldn't open save file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject gameObject;
|
||||
write(gameObject);
|
||||
saveFile.write(saveFormat == Json
|
||||
? QJsonDocument(gameObject).toJson()
|
||||
: QCborValue::fromJsonValue(gameObject).toCbor());
|
||||
QJsonObject gameObject = toJson();
|
||||
saveFile.write(saveFormat == Json ? QJsonDocument(gameObject).toJson()
|
||||
: QCborValue::fromJsonValue(gameObject).toCbor());
|
||||
|
||||
return true;
|
||||
}
|
||||
//! [4]
|
||||
//! [saveGame]
|
||||
|
||||
//! [1]
|
||||
//! [read]
|
||||
void Game::read(const QJsonObject &json)
|
||||
{
|
||||
if (json.contains("player") && json["player"].isObject())
|
||||
mPlayer.read(json["player"].toObject());
|
||||
if (const QJsonValue v = json["player"]; v.isObject())
|
||||
mPlayer = Character::fromJson(v.toObject());
|
||||
|
||||
if (json.contains("levels") && json["levels"].isArray()) {
|
||||
QJsonArray levelArray = json["levels"].toArray();
|
||||
if (const QJsonValue v = json["levels"]; v.isArray()) {
|
||||
const QJsonArray levels = v.toArray();
|
||||
mLevels.clear();
|
||||
mLevels.reserve(levelArray.size());
|
||||
for (const QJsonValue &v : levelArray) {
|
||||
QJsonObject levelObject = v.toObject();
|
||||
Level level;
|
||||
level.read(levelObject);
|
||||
mLevels.append(level);
|
||||
}
|
||||
mLevels.reserve(levels.size());
|
||||
for (const QJsonValue &level : levels)
|
||||
mLevels.append(Level::fromJson(level.toObject()));
|
||||
}
|
||||
}
|
||||
//! [1]
|
||||
//! [read]
|
||||
|
||||
//! [2]
|
||||
void Game::write(QJsonObject &json) const
|
||||
//! [toJson]
|
||||
QJsonObject Game::toJson() const
|
||||
{
|
||||
QJsonObject playerObject;
|
||||
mPlayer.write(playerObject);
|
||||
json["player"] = playerObject;
|
||||
QJsonObject json;
|
||||
json["player"] = mPlayer.toJson();
|
||||
|
||||
QJsonArray levelArray;
|
||||
for (const Level &level : mLevels) {
|
||||
QJsonObject levelObject;
|
||||
level.write(levelObject);
|
||||
levelArray.append(levelObject);
|
||||
}
|
||||
json["levels"] = levelArray;
|
||||
QJsonArray levels;
|
||||
for (const Level &level : mLevels)
|
||||
levels.append(level.toJson());
|
||||
json["levels"] = levels;
|
||||
return json;
|
||||
}
|
||||
//! [2]
|
||||
//! [toJson]
|
||||
|
||||
void Game::print(int indentation) const
|
||||
void Game::print(QTextStream &s, int indentation) const
|
||||
{
|
||||
const QString indent(indentation * 2, ' ');
|
||||
QTextStream(stdout) << indent << "Player\n";
|
||||
mPlayer.print(indentation + 1);
|
||||
s << indent << "Player\n";
|
||||
mPlayer.print(s, indentation + 1);
|
||||
|
||||
QTextStream(stdout) << indent << "Levels\n";
|
||||
s << indent << "Levels\n";
|
||||
for (const Level &level : mLevels)
|
||||
level.print(indentation + 1);
|
||||
level.print(s, indentation + 1);
|
||||
}
|
||||
|
@ -10,13 +10,13 @@
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QTextStream)
|
||||
|
||||
//! [0]
|
||||
class Game
|
||||
{
|
||||
public:
|
||||
enum SaveFormat {
|
||||
Json, Binary
|
||||
};
|
||||
enum SaveFormat { Json, Binary };
|
||||
|
||||
Character player() const;
|
||||
QList<Level> levels() const;
|
||||
@ -26,9 +26,10 @@ public:
|
||||
bool saveGame(SaveFormat saveFormat) const;
|
||||
|
||||
void read(const QJsonObject &json);
|
||||
void write(QJsonObject &json) const;
|
||||
QJsonObject toJson() const;
|
||||
|
||||
void print(QTextStream &s, int indentation = 0) const;
|
||||
|
||||
void print(int indentation = 0) const;
|
||||
private:
|
||||
Character mPlayer;
|
||||
QList<Level> mLevels;
|
||||
|
@ -6,9 +6,7 @@
|
||||
#include <QJsonArray>
|
||||
#include <QTextStream>
|
||||
|
||||
Level::Level(const QString &name) : mName(name)
|
||||
{
|
||||
}
|
||||
Level::Level(const QString &name) : mName(name) { }
|
||||
|
||||
QString Level::name() const
|
||||
{
|
||||
@ -25,46 +23,43 @@ void Level::setNpcs(const QList<Character> &npcs)
|
||||
mNpcs = npcs;
|
||||
}
|
||||
|
||||
//! [0]
|
||||
void Level::read(const QJsonObject &json)
|
||||
//! [fromJson]
|
||||
Level Level::fromJson(const QJsonObject &json)
|
||||
{
|
||||
if (json.contains("name") && json["name"].isString())
|
||||
mName = json["name"].toString();
|
||||
Level result;
|
||||
|
||||
if (json.contains("npcs") && json["npcs"].isArray()) {
|
||||
QJsonArray npcArray = json["npcs"].toArray();
|
||||
mNpcs.clear();
|
||||
mNpcs.reserve(npcArray.size());
|
||||
for (const QJsonValue &v : npcArray) {
|
||||
QJsonObject npcObject = v.toObject();
|
||||
Character npc;
|
||||
npc.read(npcObject);
|
||||
mNpcs.append(npc);
|
||||
}
|
||||
if (const QJsonValue v = json["name"]; v.isString())
|
||||
result.mName = v.toString();
|
||||
|
||||
if (const QJsonValue v = json["npcs"]; v.isArray()) {
|
||||
const QJsonArray npcs = v.toArray();
|
||||
result.mNpcs.reserve(npcs.size());
|
||||
for (const QJsonValue &npc : npcs)
|
||||
result.mNpcs.append(Character::fromJson(npc.toObject()));
|
||||
}
|
||||
}
|
||||
//! [0]
|
||||
|
||||
//! [1]
|
||||
void Level::write(QJsonObject &json) const
|
||||
return result;
|
||||
}
|
||||
//! [fromJson]
|
||||
|
||||
//! [toJson]
|
||||
QJsonObject Level::toJson() const
|
||||
{
|
||||
QJsonObject json;
|
||||
json["name"] = mName;
|
||||
QJsonArray npcArray;
|
||||
for (const Character &npc : mNpcs) {
|
||||
QJsonObject npcObject;
|
||||
npc.write(npcObject);
|
||||
npcArray.append(npcObject);
|
||||
}
|
||||
for (const Character &npc : mNpcs)
|
||||
npcArray.append(npc.toJson());
|
||||
json["npcs"] = npcArray;
|
||||
return json;
|
||||
}
|
||||
//! [1]
|
||||
//! [toJson]
|
||||
|
||||
void Level::print(int indentation) const
|
||||
void Level::print(QTextStream &s, int indentation) const
|
||||
{
|
||||
const QString indent(indentation * 2, ' ');
|
||||
QTextStream(stdout) << indent << "Name:\t" << mName << "\n";
|
||||
|
||||
QTextStream(stdout) << indent << "NPCs:\n";
|
||||
s << indent << "Name:\t" << mName << "\n" << indent << "NPCs:\n";
|
||||
for (const Character &character : mNpcs)
|
||||
character.print(2);
|
||||
character.print(s, indentation + 1);
|
||||
}
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QTextStream)
|
||||
|
||||
//! [0]
|
||||
class Level
|
||||
{
|
||||
@ -21,10 +23,11 @@ public:
|
||||
QList<Character> npcs() const;
|
||||
void setNpcs(const QList<Character> &npcs);
|
||||
|
||||
void read(const QJsonObject &json);
|
||||
void write(QJsonObject &json) const;
|
||||
static Level fromJson(const QJsonObject &json);
|
||||
QJsonObject toJson() const;
|
||||
|
||||
void print(QTextStream &s, int indentation = 0) const;
|
||||
|
||||
void print(int indentation = 0) const;
|
||||
private:
|
||||
QString mName;
|
||||
QList<Character> mNpcs;
|
||||
|
@ -17,9 +17,9 @@ int main(int argc, char *argv[])
|
||||
|
||||
const QStringList args = QCoreApplication::arguments();
|
||||
const bool newGame
|
||||
= args.size() <= 1 || QString::compare(args[1], "load"_L1, Qt::CaseInsensitive) == 0;
|
||||
= args.size() <= 1 || QString::compare(args[1], "load"_L1, Qt::CaseInsensitive) != 0;
|
||||
const bool json
|
||||
= args.size() <= 2 || QString::compare(args[2], "binary"_L1, Qt::CaseInsensitive) == 0;
|
||||
= args.size() <= 2 || QString::compare(args[2], "binary"_L1, Qt::CaseInsensitive) != 0;
|
||||
|
||||
Game game;
|
||||
if (newGame)
|
||||
@ -29,8 +29,9 @@ int main(int argc, char *argv[])
|
||||
// Game is played; changes are made...
|
||||
//! [0]
|
||||
//! [1]
|
||||
QTextStream(stdout) << "Game ended in the following state:\n";
|
||||
game.print();
|
||||
QTextStream s(stdout);
|
||||
s << "Game ended in the following state:\n";
|
||||
game.print(s);
|
||||
if (!game.saveGame(json ? Game::Json : Game::Binary))
|
||||
return 1;
|
||||
|
||||
|
@ -6,6 +6,4 @@ SUBDIRS = \
|
||||
|
||||
qtHaveModule(widgets) {
|
||||
SUBDIRS += streambookmarks
|
||||
qtHaveModule(network): SUBDIRS += \
|
||||
rsslisting
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 22 KiB |
@ -3,169 +3,221 @@
|
||||
|
||||
/*!
|
||||
\example serialization/streambookmarks
|
||||
\examplecategory {Data Processing & I/O}
|
||||
\meta tag {network}
|
||||
\title QXmlStream Bookmarks Example
|
||||
\examplecategory {Input/Output}
|
||||
\brief Demonstrates how to read and write to XBEL files.
|
||||
\brief Demonstrates how to read and write XBEL files.
|
||||
\ingroup xml-examples
|
||||
|
||||
The QXmlStream Bookmarks example provides a reader for XML Bookmark
|
||||
Exchange Language (XBEL) files using Qt's QXmlStreamReader class
|
||||
for reading, and QXmlStreamWriter class for writing the files.
|
||||
The QXmlStream Bookmarks example provides a viewer for XML Bookmark Exchange
|
||||
Language (XBEL) files. It can read bookmarks using Qt's QXmlStreamReader and
|
||||
write them back out again using QXmlStreamWriter. As this example aims to
|
||||
show how to use these reader and writer types, it provides no means to open
|
||||
a bookmark, add a new one, or merge two bookmark files, and only minimal
|
||||
scope for editing bookmarks. None the less, it could surely be extended with
|
||||
such features, if desired.
|
||||
|
||||
\image xmlstreamexample-screenshot.png
|
||||
\image screenshot.png
|
||||
|
||||
\section1 XbelWriter Class Definition
|
||||
|
||||
The \c XbelWriter class contains a private instance of QXmlStreamWriter,
|
||||
which provides an XML writer with a streaming API. \c XbelWriter also
|
||||
has a reference to the QTreeWidget instance where the bookmark hierarchy
|
||||
is stored.
|
||||
The \c XbelWriter class takes a \l{QTreeWidget}{tree widget} describing a
|
||||
hierarchy of folders containing bookmarks. Its \c writeFile() provides the
|
||||
means to write out this hierarchy, in XBEL format, to a given output device.
|
||||
|
||||
Internally, it records the tree widget it was given and packages a private
|
||||
instance of QXmlStreamWriter, which provides it with the means to stream
|
||||
XML. It has an internal \c writeItem() to write each item in its tree.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelwriter.h 0
|
||||
|
||||
\section1 XbelWriter Class Implementation
|
||||
|
||||
The \c XbelWriter constructor accepts a \a treeWidget to initialize within
|
||||
its definition. We enable \l{QXmlStreamWriter}'s auto-formatting property
|
||||
to ensure line-breaks and indentations are added automatically to empty
|
||||
sections between elements, increasing readability as the data is split into
|
||||
several lines.
|
||||
The \c XbelWriter constructor accepts the \a treeWidget it will describe. It
|
||||
stores that and enables \l{QXmlStreamWriter}'s auto-formatting property.
|
||||
This last splits the data into several lines, with indentation to indicate
|
||||
the structure of the tree, which makes the XML output easier to read.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelwriter.cpp 0
|
||||
|
||||
The \c writeFile() function accepts a QIODevice object and sets it using
|
||||
\c setDevice(). This function then writes the document type
|
||||
definition(DTD), the start element, the version, and \c{treeWidget}'s
|
||||
top-level items.
|
||||
The \c writeFile() function accepts a QIODevice object and directs its
|
||||
QXmlStreamWriter member to write to this device, using \c setDevice(). This
|
||||
function then writes the document type definition(DTD), the start element,
|
||||
the version, and delegates writing of each of the \c{treeWidget}'s top-level
|
||||
items to \c writeItem(). Finally, it closes the document and returns.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelwriter.cpp 1
|
||||
|
||||
The \c writeItem() function accepts a QTreeWidgetItem object and writes it
|
||||
to the stream, depending on its \c tagName, which can either be a "folder",
|
||||
"bookmark", or "separator".
|
||||
The \c writeItem() function accepts a QTreeWidgetItem object and writes to
|
||||
its XML stream a representation of the object, which depends on its \c
|
||||
UserRole, which can be one of a \c{"folder"}, \c{"bookmark"},
|
||||
or \c{"separator"}. Within each folder, it calls itself recursively on each
|
||||
child item, to recursively include a representation of each child within the
|
||||
folder's XML element.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelwriter.cpp 2
|
||||
|
||||
\section1 XbelReader Class Definition
|
||||
|
||||
The \c XbelReader contains a private instance of QXmlStreamReader, the
|
||||
companion class to QXmlStreamWriter. \c XbelReader also contains a
|
||||
reference to the QTreeWidget that is used to group the bookmarks according
|
||||
to their hierarchy.
|
||||
The \c XbelReader takes a \l{QTreeWidget}{tree widget} to populate with
|
||||
items describing a bookmark hierarchy. It supports reading XBEL data from a
|
||||
QIODevice as a source of these items. If parsing of the XBEL data fails, it
|
||||
can report what went wrong.
|
||||
|
||||
Internally, it records the QTreeWidget that it will populate and packages an
|
||||
instance of QXmlStreamReader, the companion class to QXmlStreamWriter, which
|
||||
it will use to read XBEL data.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelreader.h 0
|
||||
|
||||
\section1 XbelReader Class Implementation
|
||||
|
||||
The \c XbelReader constructor accepts a QTreeWidget to initialize the
|
||||
\c treeWidget within its definition. A QStyle object is used to set
|
||||
\c{treeWidget}'s style property. The \c folderIcon is set to QIcon::Normal
|
||||
mode where the pixmap is only displayed when the user is not interacting
|
||||
with the icon. The QStyle::SP_DirClosedIcon, QStyle::SP_DirOpenIcon, and
|
||||
QStyle::SP_FileIcon correspond to standard pixmaps that follow the style
|
||||
of your GUI.
|
||||
Since the XBEL reader is only concerned with reading XML elements, it makes
|
||||
extensive use of the \l{QXmlStreamReader::}{readNextStartElement()}
|
||||
convenience function.
|
||||
|
||||
The \c XbelReader constructor requires a QTreeWidget that it will populate.
|
||||
It populates the tree widget's style with suitable icons: a folder icon that
|
||||
changes form to indicate whether each folder as open or closed; and a
|
||||
standard file icon for the individual bookmarks within those folders.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelreader.cpp 0
|
||||
|
||||
The \c read() function accepts a QIODevice and sets it using
|
||||
\l{QXmlStreamReader::}{setDevice()}. The actual process of reading only
|
||||
takes place if the file is a valid XBEL 1.0 file. Note that the XML input
|
||||
needs to be well-formed to be accepted by QXmlStreamReader. Otherwise, the
|
||||
\l{QXmlStreamReader::}{raiseError()} function is used to display an error
|
||||
message. Since the XBEL reader is only concerned with reading XML elements,
|
||||
it makes extensive use of the \l{QXmlStreamReader::}{readNextStartElement()}
|
||||
convenience function.
|
||||
The \c read() function accepts a QIODevice. It directs its QXmlStreamReader
|
||||
member to read content from that device. Note that the XML input must be
|
||||
well-formed to be accepted by QXmlStreamReader. First it reads the outer
|
||||
structure and verifies the content is an XBEL 1.0 file; if it is, \c read()
|
||||
delegates the actual reading of content to the internal \c readXBEL().
|
||||
|
||||
Otherwise, the \l{QXmlStreamReader::}{raiseError()} function is used to
|
||||
record an error message. The reader itself may also do the same if it
|
||||
encounters errors in the input. When \c read() has finished, it returns
|
||||
true if there were no errors.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelreader.cpp 1
|
||||
|
||||
The \c errorString() function is used if an error occurred, in order to
|
||||
obtain a description of the error complete with line and column number
|
||||
information.
|
||||
If \c read() returns false, its caller can obtain a description of the
|
||||
error, complete with line and column number within the stream, by calling
|
||||
the \c errorString() function.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelreader.cpp 2
|
||||
|
||||
The \c readXBEL() function reads the name of a startElement and calls
|
||||
the appropriate function to read it, depending on whether if its a
|
||||
"folder", "bookmark" or "separator". Otherwise, it calls
|
||||
\l{QXmlStreamReader::}{skipCurrentElement()}. The Q_ASSERT() macro is used
|
||||
to provide a pre-condition for the function.
|
||||
The \c readXBEL() function reads the name of a startElement and calls the
|
||||
appropriate function to read it, depending on whether if its tag name
|
||||
is \c{"folder"}, \c{"bookmark"} or \c{"separator"}. Any other elements
|
||||
encountered are skipped. The function starts with a precondition, verifying
|
||||
that the XML reader has just opened an \c{"xbel"} element.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelreader.cpp 3
|
||||
|
||||
The \c readTitle() function reads the bookmark's title.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelreader.cpp 4
|
||||
|
||||
The \c readSeparator() function creates a separator and sets its flags.
|
||||
The text is set to 30 "0xB7", the HEX equivalent for period. The element
|
||||
is then skipped using \l{QXmlStreamReader::}{skipCurrentElement()}.
|
||||
The \c readBookmark() function creates a new editable item representing a
|
||||
single bookmark. It records the XML \c{"href"} attribute of the current
|
||||
element as second column text of the item and provisionally sets its first
|
||||
column text to \c{"Unknown title"} before scanning the rest of the element
|
||||
for a title element to over-ride that, skipping any unrecognized child
|
||||
elements.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelreader.cpp 5
|
||||
|
||||
The \c readTitle() function reads a bookmark's title and records it as the
|
||||
title (first column text) of the item for which it was called.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelreader.cpp 6
|
||||
|
||||
The \c readSeparator() function creates a separator and sets its flags. The
|
||||
separator item's text is set to 30 centered dots. The rest of the element is
|
||||
then skipped using \l{QXmlStreamReader::}{skipCurrentElement()}.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelreader.cpp 6
|
||||
|
||||
The \c readFolder() function creates an item and iterates the content of the
|
||||
folder element, adding children to this item to represent the contents of
|
||||
the folder element. The loop over folder content is similar in form to the
|
||||
one in \c readXBEL(), save that it now accepts a title element to set the
|
||||
title of the folder.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelreader.cpp 7
|
||||
|
||||
The \c createChildItem() helper function creates a new tree widget item
|
||||
that's either a child of the given item or, if no parent item is given, a
|
||||
direct child of the tree widget. It sets the new item's \c UserRole to the
|
||||
tag name of the current XML element, matching how XbelWriter::writeFile()
|
||||
uses that \c UserRole.
|
||||
|
||||
\snippet serialization/streambookmarks/xbelreader.cpp 8
|
||||
|
||||
\section1 MainWindow Class Definition
|
||||
|
||||
The \c MainWindow class is a subclass of QMainWindow, with a
|
||||
\c File menu and a \c Help menu.
|
||||
The \c MainWindow class is a subclass of QMainWindow, with a \c File menu
|
||||
and a \c Help menu.
|
||||
|
||||
\snippet serialization/streambookmarks/mainwindow.h 0
|
||||
|
||||
\section1 MainWindow Class Implementation
|
||||
|
||||
The \c MainWindow constructor instantiates the QTreeWidget object, \c
|
||||
treeWidget and sets its header with a QStringList object, \c labels.
|
||||
The constructor also invokes \c createActions() and \c createMenus()
|
||||
to set up the menus and their corresponding actions. The \c statusBar()
|
||||
is used to display the message "Ready" and the window's size is fixed
|
||||
to 480x320 pixels.
|
||||
The \c MainWindow constructor sets up its QTreeWidget object, \c treeWidget,
|
||||
as its own central widget, with column headings for the title and location
|
||||
of each book-mark. It configures a custom menu that enables the user to
|
||||
perform actions on individual bookmarks within the tree widget.
|
||||
|
||||
It invokes \c createMenus() to set up its own menus and their corresponding
|
||||
actions. It sets its title, announces itself as ready and sets its size to a
|
||||
reasonable proportion of the available screen space.
|
||||
|
||||
\snippet serialization/streambookmarks/mainwindow.cpp 0
|
||||
|
||||
The \c open() function enables the user to open an XBEL file using
|
||||
QFileDialog::getOpenFileName(). A warning message is displayed along
|
||||
with the \c fileName and \c errorString if the file cannot be read or
|
||||
if there is a parse error.
|
||||
A custom menu, triggered when the user right-clicks on a bookmark, provides
|
||||
for copying the bookmark as a link or directing a desktop browser to open
|
||||
the URL it references. This menu is implemented (when relevant features are
|
||||
enabled) by \c onCustomContextMenuRequested().
|
||||
|
||||
\snippet serialization/streambookmarks/mainwindow.cpp 1
|
||||
|
||||
The \c saveAs() function displays a QFileDialog, prompting the user for
|
||||
a \c fileName using QFileDialog::getSaveFileName(). Similar to the
|
||||
\c open() function, this function also displays a warning message if
|
||||
the file cannot be written to.
|
||||
The \c createMenus() function creates the \c fileMenu and \c helpMenu and
|
||||
adds QAction objects to them, bound variously to the \c open(), \c saveAs()
|
||||
and \c about() functions, along with QWidget::close() and
|
||||
QApplication::aboutQt(). The connections are as shown below:
|
||||
|
||||
\snippet serialization/streambookmarks/mainwindow.cpp 2
|
||||
|
||||
The \c about() function displays a QMessageBox with a brief description
|
||||
of the example.
|
||||
|
||||
\snippet serialization/streambookmarks/mainwindow.cpp 3
|
||||
|
||||
In order to implement the \c open(), \c saveAs(), \c exit(), \c about()
|
||||
and \c aboutQt() functions, we connect them to QAction objects and
|
||||
add them to the \c fileMenu and \c helpMenu. The connections are as shown
|
||||
below:
|
||||
|
||||
\snippet serialization/streambookmarks/mainwindow.cpp 5
|
||||
|
||||
The \c createMenus() function creates the \c fileMenu and \c helpMenu
|
||||
and adds the QAction objects to them in order to create the menu shown
|
||||
in the screenshot below:
|
||||
This creates the menu shown in the screenshots below:
|
||||
|
||||
\table
|
||||
\row
|
||||
\li \inlineimage xmlstreamexample-filemenu.png
|
||||
\li \inlineimage xmlstreamexample-helpmenu.png
|
||||
\li \inlineimage filemenu.png
|
||||
\li \inlineimage helpmenu.png
|
||||
\endtable
|
||||
|
||||
The \c open() function, when triggered, offers the user a file dialog to use
|
||||
to select a bookmarks file. If a file is selected, it is parsed using an \c
|
||||
XBelReader to populate the \c treeWidget with bookmarks. If problems arise
|
||||
with opening or parsing the file, a suitable warning message is displayed to
|
||||
the user, including file name and error message. Otherwise, the bookmarks
|
||||
read from the file are displayed and the window's status bar briefly reports
|
||||
that the file has been loaded.
|
||||
|
||||
\snippet serialization/streambookmarks/mainwindow.cpp 3
|
||||
|
||||
The \c saveAs() function displays a QFileDialog, prompting the user for a \c
|
||||
fileName, to which to save a copy of the bookmarks data. Similar to the \c
|
||||
open() function, this function also displays a warning message if the file
|
||||
cannot be written to.
|
||||
|
||||
\snippet serialization/streambookmarks/mainwindow.cpp 4
|
||||
|
||||
The \c about() function displays a QMessageBox with a brief description of
|
||||
the example, or general information about Qt and the version of it in use.
|
||||
|
||||
\snippet serialization/streambookmarks/mainwindow.cpp 5
|
||||
|
||||
\section1 \c{main()} Function
|
||||
|
||||
The \c main() function instantiates \c MainWindow and invokes the \c show()
|
||||
function.
|
||||
function to display it, then its \c open(), as this is most likely what the
|
||||
user shall want to do first.
|
||||
|
||||
\snippet serialization/streambookmarks/main.cpp 0
|
||||
|
||||
See the \l{http://pyxml.sourceforge.net/topics/xbel/}
|
||||
{XML Bookmark Exchange Language Resource Page} for more information
|
||||
about XBEL files.
|
||||
See the \l{https://pyxml.sourceforge.net/topics/xbel/} {XML Bookmark
|
||||
Exchange Language Resource Page} for more information about XBEL files.
|
||||
*/
|
||||
|
@ -3,66 +3,66 @@
|
||||
<xbel version="1.0">
|
||||
<folder folded="no">
|
||||
<title>Qt Resources</title>
|
||||
<bookmark href="http://qt.io/">
|
||||
<bookmark href="https://www.qt.io/">
|
||||
<title>Qt home page</title>
|
||||
</bookmark>
|
||||
<bookmark href="https://www.qt.io/partners/">
|
||||
<bookmark href="https://www.qt.io/contact-us/partners">
|
||||
<title>Qt Partners</title>
|
||||
</bookmark>
|
||||
<bookmark href="https://www.qt.io/qt-training/">
|
||||
<title>Training</title>
|
||||
<bookmark href="https://www.qt.io/qt-professional-services">
|
||||
<title>Professional Services</title>
|
||||
</bookmark>
|
||||
<bookmark href="http://doc.qt.io/">
|
||||
<title>Qt 5 documentation</title>
|
||||
</bookmark>
|
||||
<bookmark href="http://qt-project.org/faq/">
|
||||
<title>Frequently Asked Questions</title>
|
||||
<bookmark href="https://doc.qt.io/">
|
||||
<title>Qt Documentation</title>
|
||||
</bookmark>
|
||||
<folder folded="yes">
|
||||
<title>Community Resources</title>
|
||||
<bookmark href="http://www.qtcentre.org/content/">
|
||||
<bookmark href="https://contribute.qt-project.org">
|
||||
<title>The Qt Project</title>
|
||||
</bookmark>
|
||||
<bookmark href="https://www.qtcentre.org/content/">
|
||||
<title>Qt Centre</title>
|
||||
</bookmark>
|
||||
<bookmark href="http://www.qtforum.org/">
|
||||
<title>QtForum.org</title>
|
||||
<bookmark href="https://forum.qt.io/">
|
||||
<title>Forum.Qt.org</title>
|
||||
</bookmark>
|
||||
<bookmark href="http://digitalfanatics.org/projects/qt_tutorial/">
|
||||
<bookmark href="https://digitalfanatics.org/projects/qt_tutorial/">
|
||||
<title>The Independent Qt Tutorial</title>
|
||||
</bookmark>
|
||||
<bookmark href="http://www.qtforum.de/">
|
||||
<bookmark href="https://www.qtforum.de/">
|
||||
<title>German Qt Forum</title>
|
||||
</bookmark>
|
||||
<bookmark href="http://www.korone.net/">
|
||||
<bookmark href="https://www.qt-dev.com/">
|
||||
<title>Korean Qt Community Site</title>
|
||||
</bookmark>
|
||||
<bookmark href="http://prog.org.ru/">
|
||||
<bookmark href="http://www.prog.org.ru/">
|
||||
<title>Russian Qt Forum</title>
|
||||
</bookmark>
|
||||
</folder>
|
||||
</folder>
|
||||
<folder folded="no">
|
||||
<title>Online Dictionaries</title>
|
||||
<bookmark href="http://www.dictionary.com/">
|
||||
<bookmark href="https://www.dictionary.com/">
|
||||
<title>Dictionary.com</title>
|
||||
</bookmark>
|
||||
<bookmark href="http://www.m-w.com/">
|
||||
<bookmark href="https://www.merriam-webster.com/">
|
||||
<title>Merriam-Webster Online</title>
|
||||
</bookmark>
|
||||
<bookmark href="http://dictionary.cambridge.org/">
|
||||
<bookmark href="https://dictionary.cambridge.org/">
|
||||
<title>Cambridge Dictionaries Online</title>
|
||||
</bookmark>
|
||||
<bookmark href="http://www.onelook.com/">
|
||||
<bookmark href="https://www.onelook.com/">
|
||||
<title>OneLook Dictionary Search</title>
|
||||
</bookmark>
|
||||
<separator/>
|
||||
<bookmark href="http://dict.tu-chemnitz.de/">
|
||||
<title>TU Chemnitz German-English Dictionary</title>
|
||||
<bookmark href="https://dict.tu-chemnitz.de/">
|
||||
<title>BEOLINGUS, a service of TU Chemnitz</title>
|
||||
</bookmark>
|
||||
<separator/>
|
||||
<bookmark href="http://atilf.atilf.fr/tlf.htm">
|
||||
<title>Trésor de la Langue Française informatisé</title>
|
||||
</bookmark>
|
||||
<bookmark href="http://dictionnaires.atilf.fr/dictionnaires/ACADEMIE/">
|
||||
<bookmark href="https://www.dictionnaire-academie.fr/">
|
||||
<title>Dictionnaire de l'Académie Française</title>
|
||||
</bookmark>
|
||||
</folder>
|
||||
|
@ -1,10 +1,10 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
//! [0]
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
@ -1,22 +1,33 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include "xbelreader.h"
|
||||
#include "xbelwriter.h"
|
||||
|
||||
//! [0]
|
||||
MainWindow::MainWindow()
|
||||
{
|
||||
QStringList labels;
|
||||
labels << tr("Title") << tr("Location");
|
||||
#include <QFileDialog>
|
||||
#include <QHeaderView>
|
||||
#include <QMenuBar>
|
||||
#include <QMessageBox>
|
||||
#include <QStatusBar>
|
||||
#include <QTreeWidget>
|
||||
|
||||
treeWidget = new QTreeWidget;
|
||||
#include <QAction>
|
||||
#if QT_CONFIG(clipboard)
|
||||
# include <QClipboard>
|
||||
#endif
|
||||
#include <QDesktopServices>
|
||||
#include <QApplication>
|
||||
#include <QScreen>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
//! [0]
|
||||
MainWindow::MainWindow() : treeWidget(new QTreeWidget)
|
||||
{
|
||||
treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
treeWidget->setHeaderLabels(labels);
|
||||
#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD)
|
||||
treeWidget->setHeaderLabels(QStringList{tr("Title"), tr("Location")});
|
||||
#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu)
|
||||
treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(treeWidget, &QWidget::customContextMenuRequested,
|
||||
this, &MainWindow::onCustomContextMenuRequested);
|
||||
@ -33,7 +44,8 @@ MainWindow::MainWindow()
|
||||
}
|
||||
//! [0]
|
||||
|
||||
#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD)
|
||||
//! [1]
|
||||
#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu)
|
||||
void MainWindow::onCustomContextMenuRequested(const QPoint &pos)
|
||||
{
|
||||
const QTreeWidgetItem *item = treeWidget->itemAt(pos);
|
||||
@ -49,78 +61,10 @@ void MainWindow::onCustomContextMenuRequested(const QPoint &pos)
|
||||
else if (action == openAction)
|
||||
QDesktopServices::openUrl(QUrl(url));
|
||||
}
|
||||
#endif // !QT_NO_CONTEXTMENU && !QT_NO_CLIPBOARD
|
||||
|
||||
//! [1]
|
||||
void MainWindow::open()
|
||||
{
|
||||
QString fileName =
|
||||
QFileDialog::getOpenFileName(this, tr("Open Bookmark File"),
|
||||
QDir::currentPath(),
|
||||
tr("XBEL Files (*.xbel *.xml)"));
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
treeWidget->clear();
|
||||
|
||||
|
||||
QFile file(fileName);
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QMessageBox::warning(this, tr("QXmlStream Bookmarks"),
|
||||
tr("Cannot read file %1:\n%2.")
|
||||
.arg(QDir::toNativeSeparators(fileName),
|
||||
file.errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
XbelReader reader(treeWidget);
|
||||
if (!reader.read(&file)) {
|
||||
QMessageBox::warning(this, tr("QXmlStream Bookmarks"),
|
||||
tr("Parse error in file %1:\n\n%2")
|
||||
.arg(QDir::toNativeSeparators(fileName),
|
||||
reader.errorString()));
|
||||
} else {
|
||||
statusBar()->showMessage(tr("File loaded"), 2000);
|
||||
}
|
||||
|
||||
}
|
||||
#endif // QT_CONFIG(clipboard) && QT_CONFIG(contextmenu)
|
||||
//! [1]
|
||||
|
||||
//! [2]
|
||||
void MainWindow::saveAs()
|
||||
{
|
||||
QString fileName =
|
||||
QFileDialog::getSaveFileName(this, tr("Save Bookmark File"),
|
||||
QDir::currentPath(),
|
||||
tr("XBEL Files (*.xbel *.xml)"));
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
QFile file(fileName);
|
||||
if (!file.open(QFile::WriteOnly | QFile::Text)) {
|
||||
QMessageBox::warning(this, tr("QXmlStream Bookmarks"),
|
||||
tr("Cannot write file %1:\n%2.")
|
||||
.arg(QDir::toNativeSeparators(fileName),
|
||||
file.errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
XbelWriter writer(treeWidget);
|
||||
if (writer.writeFile(&file))
|
||||
statusBar()->showMessage(tr("File saved"), 2000);
|
||||
}
|
||||
//! [2]
|
||||
|
||||
//! [3]
|
||||
void MainWindow::about()
|
||||
{
|
||||
QMessageBox::about(this, tr("About QXmlStream Bookmarks"),
|
||||
tr("The <b>QXmlStream Bookmarks</b> example demonstrates how to use Qt's "
|
||||
"QXmlStream classes to read and write XML documents."));
|
||||
}
|
||||
//! [3]
|
||||
|
||||
//! [5]
|
||||
void MainWindow::createMenus()
|
||||
{
|
||||
QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
|
||||
@ -137,6 +81,71 @@ void MainWindow::createMenus()
|
||||
|
||||
QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
|
||||
helpMenu->addAction(tr("&About"), this, &MainWindow::about);
|
||||
helpMenu->addAction(tr("About &Qt"), qApp, &QCoreApplication::quit);
|
||||
helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
|
||||
}
|
||||
//! [2]
|
||||
|
||||
//! [3]
|
||||
void MainWindow::open()
|
||||
{
|
||||
QFileDialog fileDialog(this, tr("Open Bookmark File"), QDir::currentPath());
|
||||
fileDialog.setMimeTypeFilters({"application/x-xbel"_L1});
|
||||
if (fileDialog.exec() != QDialog::Accepted)
|
||||
return;
|
||||
|
||||
treeWidget->clear();
|
||||
|
||||
const QString fileName = fileDialog.selectedFiles().constFirst();
|
||||
QFile file(fileName);
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QMessageBox::warning(this, tr("QXmlStream Bookmarks"),
|
||||
tr("Cannot read file %1:\n%2.")
|
||||
.arg(QDir::toNativeSeparators(fileName), file.errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
XbelReader reader(treeWidget);
|
||||
if (!reader.read(&file)) {
|
||||
QMessageBox::warning(
|
||||
this, tr("QXmlStream Bookmarks"),
|
||||
tr("Parse error in file %1:\n\n%2")
|
||||
.arg(QDir::toNativeSeparators(fileName), reader.errorString()));
|
||||
} else {
|
||||
statusBar()->showMessage(tr("File loaded"), 2000);
|
||||
}
|
||||
}
|
||||
//! [3]
|
||||
|
||||
//! [4]
|
||||
void MainWindow::saveAs()
|
||||
{
|
||||
QFileDialog fileDialog(this, tr("Save Bookmark File"), QDir::currentPath());
|
||||
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
fileDialog.setDefaultSuffix("xbel"_L1);
|
||||
fileDialog.setMimeTypeFilters({"application/x-xbel"_L1});
|
||||
if (fileDialog.exec() != QDialog::Accepted)
|
||||
return;
|
||||
|
||||
const QString fileName = fileDialog.selectedFiles().constFirst();
|
||||
QFile file(fileName);
|
||||
if (!file.open(QFile::WriteOnly | QFile::Text)) {
|
||||
QMessageBox::warning(this, tr("QXmlStream Bookmarks"),
|
||||
tr("Cannot write file %1:\n%2.")
|
||||
.arg(QDir::toNativeSeparators(fileName), file.errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
XbelWriter writer(treeWidget);
|
||||
if (writer.writeFile(&file))
|
||||
statusBar()->showMessage(tr("File saved"), 2000);
|
||||
}
|
||||
//! [4]
|
||||
|
||||
//! [5]
|
||||
void MainWindow::about()
|
||||
{
|
||||
QMessageBox::about(this, tr("About QXmlStream Bookmarks"),
|
||||
tr("The <b>QXmlStream Bookmarks</b> example demonstrates how to use Qt's "
|
||||
"QXmlStream classes to read and write XML documents."));
|
||||
}
|
||||
//! [5]
|
||||
|
@ -22,13 +22,13 @@ public slots:
|
||||
void open();
|
||||
void saveAs();
|
||||
void about();
|
||||
#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD)
|
||||
#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu)
|
||||
void onCustomContextMenuRequested(const QPoint &pos);
|
||||
#endif
|
||||
private:
|
||||
void createMenus();
|
||||
|
||||
QTreeWidget *treeWidget;
|
||||
QTreeWidget *const treeWidget;
|
||||
};
|
||||
//! [0]
|
||||
|
||||
|
@ -8,7 +8,7 @@ SOURCES = main.cpp \
|
||||
QT += widgets
|
||||
requires(qtConfig(filedialog))
|
||||
|
||||
EXAMPLE_FILES = frank.xbel jennifer.xbel
|
||||
EXAMPLE_FILES = jennifer.xbel
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/corelib/serialization/streambookmarks
|
||||
|
@ -1,20 +1,21 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "xbelreader.h"
|
||||
|
||||
#include <QStyle>
|
||||
#include <QTreeWidget>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
//! [0]
|
||||
XbelReader::XbelReader(QTreeWidget *treeWidget)
|
||||
: treeWidget(treeWidget)
|
||||
XbelReader::XbelReader(QTreeWidget *treeWidget) : treeWidget(treeWidget)
|
||||
{
|
||||
QStyle *style = treeWidget->style();
|
||||
|
||||
folderIcon.addPixmap(style->standardPixmap(QStyle::SP_DirClosedIcon),
|
||||
QIcon::Normal, QIcon::Off);
|
||||
folderIcon.addPixmap(style->standardPixmap(QStyle::SP_DirOpenIcon),
|
||||
QIcon::Normal, QIcon::On);
|
||||
folderIcon.addPixmap(style->standardPixmap(QStyle::SP_DirClosedIcon), QIcon::Normal,
|
||||
QIcon::Off);
|
||||
folderIcon.addPixmap(style->standardPixmap(QStyle::SP_DirOpenIcon), QIcon::Normal, QIcon::On);
|
||||
bookmarkIcon.addPixmap(style->standardPixmap(QStyle::SP_FileIcon));
|
||||
}
|
||||
//! [0]
|
||||
@ -25,12 +26,10 @@ bool XbelReader::read(QIODevice *device)
|
||||
xml.setDevice(device);
|
||||
|
||||
if (xml.readNextStartElement()) {
|
||||
if (xml.name() == QLatin1String("xbel")
|
||||
&& xml.attributes().value(versionAttribute()) == QLatin1String("1.0")) {
|
||||
if (xml.name() == "xbel"_L1 && xml.attributes().value("version"_L1) == "1.0"_L1)
|
||||
readXBEL();
|
||||
} else {
|
||||
else
|
||||
xml.raiseError(QObject::tr("The file is not an XBEL version 1.0 file."));
|
||||
}
|
||||
}
|
||||
|
||||
return !xml.error();
|
||||
@ -50,15 +49,15 @@ QString XbelReader::errorString() const
|
||||
//! [3]
|
||||
void XbelReader::readXBEL()
|
||||
{
|
||||
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("xbel"));
|
||||
Q_ASSERT(xml.isStartElement() && xml.name() == "xbel"_L1);
|
||||
|
||||
while (xml.readNextStartElement()) {
|
||||
if (xml.name() == QLatin1String("folder"))
|
||||
readFolder(0);
|
||||
else if (xml.name() == QLatin1String("bookmark"))
|
||||
readBookmark(0);
|
||||
else if (xml.name() == QLatin1String("separator"))
|
||||
readSeparator(0);
|
||||
if (xml.name() == "folder"_L1)
|
||||
readFolder(nullptr);
|
||||
else if (xml.name() == "bookmark"_L1)
|
||||
readBookmark(nullptr);
|
||||
else if (xml.name() == "separator"_L1)
|
||||
readSeparator(nullptr);
|
||||
else
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
@ -66,75 +65,76 @@ void XbelReader::readXBEL()
|
||||
//! [3]
|
||||
|
||||
//! [4]
|
||||
void XbelReader::readTitle(QTreeWidgetItem *item)
|
||||
{
|
||||
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("title"));
|
||||
|
||||
QString title = xml.readElementText();
|
||||
item->setText(0, title);
|
||||
}
|
||||
//! [4]
|
||||
|
||||
//! [5]
|
||||
void XbelReader::readSeparator(QTreeWidgetItem *item)
|
||||
{
|
||||
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("separator"));
|
||||
|
||||
QTreeWidgetItem *separator = createChildItem(item);
|
||||
separator->setFlags(item->flags() & ~Qt::ItemIsSelectable);
|
||||
separator->setText(0, QString(30, u'\xB7'));
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
//! [5]
|
||||
|
||||
void XbelReader::readFolder(QTreeWidgetItem *item)
|
||||
{
|
||||
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("folder"));
|
||||
|
||||
QTreeWidgetItem *folder = createChildItem(item);
|
||||
bool folded = (xml.attributes().value(foldedAttribute()) != QLatin1String("no"));
|
||||
folder->setExpanded(!folded);
|
||||
|
||||
while (xml.readNextStartElement()) {
|
||||
if (xml.name() == QLatin1String("title"))
|
||||
readTitle(folder);
|
||||
else if (xml.name() == QLatin1String("folder"))
|
||||
readFolder(folder);
|
||||
else if (xml.name() == QLatin1String("bookmark"))
|
||||
readBookmark(folder);
|
||||
else if (xml.name() == QLatin1String("separator"))
|
||||
readSeparator(folder);
|
||||
else
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
void XbelReader::readBookmark(QTreeWidgetItem *item)
|
||||
{
|
||||
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("bookmark"));
|
||||
Q_ASSERT(xml.isStartElement() && xml.name() == "bookmark"_L1);
|
||||
|
||||
QTreeWidgetItem *bookmark = createChildItem(item);
|
||||
bookmark->setFlags(bookmark->flags() | Qt::ItemIsEditable);
|
||||
bookmark->setIcon(0, bookmarkIcon);
|
||||
bookmark->setText(0, QObject::tr("Unknown title"));
|
||||
bookmark->setText(1, xml.attributes().value(hrefAttribute()).toString());
|
||||
bookmark->setText(1, xml.attributes().value("href"_L1).toString());
|
||||
|
||||
while (xml.readNextStartElement()) {
|
||||
if (xml.name() == QLatin1String("title"))
|
||||
if (xml.name() == "title"_L1)
|
||||
readTitle(bookmark);
|
||||
else
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
//! [4]
|
||||
|
||||
//! [5]
|
||||
void XbelReader::readTitle(QTreeWidgetItem *item)
|
||||
{
|
||||
Q_ASSERT(xml.isStartElement() && xml.name() == "title"_L1);
|
||||
item->setText(0, xml.readElementText());
|
||||
}
|
||||
//! [5]
|
||||
|
||||
//! [6]
|
||||
void XbelReader::readSeparator(QTreeWidgetItem *item)
|
||||
{
|
||||
Q_ASSERT(xml.isStartElement() && xml.name() == "separator"_L1);
|
||||
constexpr char16_t midDot = u'\xB7';
|
||||
static const QString dots(30, midDot);
|
||||
|
||||
QTreeWidgetItem *separator = createChildItem(item);
|
||||
separator->setFlags(item ? item->flags() & ~Qt::ItemIsSelectable : Qt::ItemFlags{});
|
||||
separator->setText(0, dots);
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
//! [6]
|
||||
|
||||
//! [7]
|
||||
void XbelReader::readFolder(QTreeWidgetItem *item)
|
||||
{
|
||||
Q_ASSERT(xml.isStartElement() && xml.name() == "folder"_L1);
|
||||
|
||||
QTreeWidgetItem *folder = createChildItem(item);
|
||||
bool folded = xml.attributes().value("folded"_L1) != "no"_L1;
|
||||
folder->setExpanded(!folded);
|
||||
|
||||
while (xml.readNextStartElement()) {
|
||||
if (xml.name() == "title"_L1)
|
||||
readTitle(folder);
|
||||
else if (xml.name() == "folder"_L1)
|
||||
readFolder(folder);
|
||||
else if (xml.name() == "bookmark"_L1)
|
||||
readBookmark(folder);
|
||||
else if (xml.name() == "separator"_L1)
|
||||
readSeparator(folder);
|
||||
else
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
//! [7]
|
||||
|
||||
//! [8]
|
||||
QTreeWidgetItem *XbelReader::createChildItem(QTreeWidgetItem *item)
|
||||
{
|
||||
QTreeWidgetItem *childItem;
|
||||
if (item) {
|
||||
childItem = new QTreeWidgetItem(item);
|
||||
} else {
|
||||
childItem = new QTreeWidgetItem(treeWidget);
|
||||
}
|
||||
QTreeWidgetItem *childItem = item ? new QTreeWidgetItem(item) : new QTreeWidgetItem(treeWidget);
|
||||
childItem->setData(0, Qt::UserRole, xml.name().toString());
|
||||
return childItem;
|
||||
}
|
||||
//! [8]
|
||||
|
@ -21,13 +21,8 @@ public:
|
||||
//! [1]
|
||||
|
||||
bool read(QIODevice *device);
|
||||
|
||||
QString errorString() const;
|
||||
|
||||
static inline QString versionAttribute() { return QStringLiteral("version"); }
|
||||
static inline QString hrefAttribute() { return QStringLiteral("href"); }
|
||||
static inline QString foldedAttribute() { return QStringLiteral("folded"); }
|
||||
|
||||
private:
|
||||
//! [2]
|
||||
void readXBEL();
|
||||
|
@ -1,18 +1,15 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "xbelwriter.h"
|
||||
#include "xbelreader.h"
|
||||
|
||||
static inline QString yesValue() { return QStringLiteral("yes"); }
|
||||
static inline QString noValue() { return QStringLiteral("no"); }
|
||||
static inline QString titleElement() { return QStringLiteral("title"); }
|
||||
#include <QTreeWidget>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
//! [0]
|
||||
XbelWriter::XbelWriter(const QTreeWidget *treeWidget)
|
||||
: treeWidget(treeWidget)
|
||||
XbelWriter::XbelWriter(const QTreeWidget *treeWidget) : treeWidget(treeWidget)
|
||||
{
|
||||
xml.setAutoFormatting(true);
|
||||
}
|
||||
@ -24,9 +21,9 @@ bool XbelWriter::writeFile(QIODevice *device)
|
||||
xml.setDevice(device);
|
||||
|
||||
xml.writeStartDocument();
|
||||
xml.writeDTD(QStringLiteral("<!DOCTYPE xbel>"));
|
||||
xml.writeStartElement(QStringLiteral("xbel"));
|
||||
xml.writeAttribute(XbelReader::versionAttribute(), QStringLiteral("1.0"));
|
||||
xml.writeDTD("<!DOCTYPE xbel>"_L1);
|
||||
xml.writeStartElement("xbel"_L1);
|
||||
xml.writeAttribute("version"_L1, "1.0"_L1);
|
||||
for (int i = 0; i < treeWidget->topLevelItemCount(); ++i)
|
||||
writeItem(treeWidget->topLevelItem(i));
|
||||
|
||||
@ -39,21 +36,21 @@ bool XbelWriter::writeFile(QIODevice *device)
|
||||
void XbelWriter::writeItem(const QTreeWidgetItem *item)
|
||||
{
|
||||
QString tagName = item->data(0, Qt::UserRole).toString();
|
||||
if (tagName == QLatin1String("folder")) {
|
||||
if (tagName == "folder"_L1) {
|
||||
bool folded = !item->isExpanded();
|
||||
xml.writeStartElement(tagName);
|
||||
xml.writeAttribute(XbelReader::foldedAttribute(), folded ? yesValue() : noValue());
|
||||
xml.writeTextElement(titleElement(), item->text(0));
|
||||
xml.writeAttribute("folded"_L1, folded ? "yes"_L1 : "no"_L1);
|
||||
xml.writeTextElement("title"_L1, item->text(0));
|
||||
for (int i = 0; i < item->childCount(); ++i)
|
||||
writeItem(item->child(i));
|
||||
xml.writeEndElement();
|
||||
} else if (tagName == QLatin1String("bookmark")) {
|
||||
} else if (tagName == "bookmark"_L1) {
|
||||
xml.writeStartElement(tagName);
|
||||
if (!item->text(1).isEmpty())
|
||||
xml.writeAttribute(XbelReader::hrefAttribute(), item->text(1));
|
||||
xml.writeTextElement(titleElement(), item->text(0));
|
||||
xml.writeAttribute("href"_L1, item->text(1));
|
||||
xml.writeTextElement("title"_L1, item->text(0));
|
||||
xml.writeEndElement();
|
||||
} else if (tagName == QLatin1String("separator")) {
|
||||
} else if (tagName == "separator"_L1) {
|
||||
xml.writeEmptyElement(tagName);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
qt_internal_add_example(semaphores)
|
||||
qt_internal_add_example(waitconditions)
|
||||
if(NOT ANDROID)
|
||||
qt_internal_add_example(semaphores)
|
||||
qt_internal_add_example(waitconditions)
|
||||
endif()
|
||||
if(TARGET Qt6::Widgets)
|
||||
qt_internal_add_example(mandelbrot)
|
||||
qt_internal_add_example(queuedcustomtype)
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
/*!
|
||||
\example threads/mandelbrot
|
||||
\title Mandelbrot Example
|
||||
\title Mandelbrot
|
||||
\ingroup qtconcurrent-mtexamples
|
||||
|
||||
\brief The Mandelbrot example demonstrates multi-thread programming
|
||||
@ -151,7 +151,7 @@
|
||||
it needs to access \c{RenderThread}'s member variables (e.g., in
|
||||
\c render()).
|
||||
|
||||
The \c forever keyword is, like \c foreach, a Qt pseudo-keyword.
|
||||
The \c forever keyword is a Qt pseudo-keyword.
|
||||
|
||||
\snippet threads/mandelbrot/renderthread.cpp 4
|
||||
\snippet threads/mandelbrot/renderthread.cpp 5
|
||||
|
@ -2,15 +2,14 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "mandelbrotwidget.h"
|
||||
#include "renderthread.h"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include <QScreen>
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QCommandLineOption>
|
||||
#include <QDebug>
|
||||
#include <QRect>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
//! [0]
|
||||
int main(int argc, char *argv[])
|
||||
@ -18,10 +17,10 @@ int main(int argc, char *argv[])
|
||||
QApplication app(argc, argv);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("Qt Mandelbrot Example");
|
||||
parser.setApplicationDescription(u"Qt Mandelbrot Example"_s);
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
QCommandLineOption passesOption("passes", "Number of passes (1-8)", "passes");
|
||||
QCommandLineOption passesOption(u"passes"_s, u"Number of passes (1-8)"_s, u"passes"_s);
|
||||
parser.addOption(passesOption);
|
||||
parser.process(app);
|
||||
|
||||
|
@ -4,19 +4,20 @@
|
||||
#include "mandelbrotwidget.h"
|
||||
|
||||
#include <QGesture>
|
||||
#include <QGestureEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QPainter>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
//! [0]
|
||||
const double DefaultCenterX = -0.637011;
|
||||
const double DefaultCenterY = -0.0395159;
|
||||
const double DefaultScale = 0.00403897;
|
||||
constexpr double DefaultCenterX = -0.637011;
|
||||
constexpr double DefaultCenterY = -0.0395159;
|
||||
constexpr double DefaultScale = 0.00403897;
|
||||
|
||||
const double ZoomInFactor = 0.8;
|
||||
const double ZoomOutFactor = 1 / ZoomInFactor;
|
||||
const int ScrollStep = 20;
|
||||
constexpr double ZoomInFactor = 0.8;
|
||||
constexpr double ZoomOutFactor = 1 / ZoomInFactor;
|
||||
constexpr int ScrollStep = 20;
|
||||
//! [0]
|
||||
|
||||
//! [1]
|
||||
@ -46,7 +47,8 @@ void MandelbrotWidget::paintEvent(QPaintEvent * /* event */)
|
||||
|
||||
if (pixmap.isNull()) {
|
||||
painter.setPen(Qt::white);
|
||||
painter.drawText(rect(), Qt::AlignCenter|Qt::TextWordWrap, tr("Rendering initial image, please wait..."));
|
||||
painter.drawText(rect(), Qt::AlignCenter|Qt::TextWordWrap,
|
||||
tr("Rendering initial image, please wait..."));
|
||||
//! [2] //! [3]
|
||||
return;
|
||||
//! [3] //! [4]
|
||||
@ -60,47 +62,47 @@ void MandelbrotWidget::paintEvent(QPaintEvent * /* event */)
|
||||
//! [6] //! [7]
|
||||
} else {
|
||||
//! [7] //! [8]
|
||||
auto previewPixmap = qFuzzyCompare(pixmap.devicePixelRatio(), qreal(1))
|
||||
const auto previewPixmap = qFuzzyCompare(pixmap.devicePixelRatio(), qreal(1))
|
||||
? pixmap
|
||||
: pixmap.scaled(pixmap.deviceIndependentSize().toSize(), Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
double scaleFactor = pixmapScale / curScale;
|
||||
int newWidth = int(previewPixmap.width() * scaleFactor);
|
||||
int newHeight = int(previewPixmap.height() * scaleFactor);
|
||||
int newX = pixmapOffset.x() + (previewPixmap.width() - newWidth) / 2;
|
||||
int newY = pixmapOffset.y() + (previewPixmap.height() - newHeight) / 2;
|
||||
const double scaleFactor = pixmapScale / curScale;
|
||||
const int newWidth = int(previewPixmap.width() * scaleFactor);
|
||||
const int newHeight = int(previewPixmap.height() * scaleFactor);
|
||||
const int newX = pixmapOffset.x() + (previewPixmap.width() - newWidth) / 2;
|
||||
const int newY = pixmapOffset.y() + (previewPixmap.height() - newHeight) / 2;
|
||||
|
||||
painter.save();
|
||||
painter.translate(newX, newY);
|
||||
painter.scale(scaleFactor, scaleFactor);
|
||||
|
||||
QRectF exposed = painter.transform().inverted().mapRect(rect()).adjusted(-1, -1, 1, 1);
|
||||
const QRectF exposed = painter.transform().inverted().mapRect(rect())
|
||||
.adjusted(-1, -1, 1, 1);
|
||||
painter.drawPixmap(exposed, previewPixmap, exposed);
|
||||
painter.restore();
|
||||
}
|
||||
//! [8] //! [9]
|
||||
|
||||
QFontMetrics metrics = painter.fontMetrics();
|
||||
const QFontMetrics metrics = painter.fontMetrics();
|
||||
if (!info.isEmpty()){
|
||||
int infoWidth = metrics.horizontalAdvance(info);
|
||||
int infoHeight = metrics.height();
|
||||
const int infoWidth = metrics.horizontalAdvance(info);
|
||||
const int infoHeight = (infoWidth/width() + 1) * (metrics.height() + 5);
|
||||
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(QColor(0, 0, 0, 127));
|
||||
infoHeight = (infoWidth/width()+1) * (infoHeight + 5);
|
||||
painter.drawRect((width() - infoWidth) / 2 - 5, 0, infoWidth + 10, infoHeight);
|
||||
|
||||
painter.setPen(Qt::white);
|
||||
painter.drawText(rect(), Qt::AlignHCenter|Qt::AlignTop|Qt::TextWordWrap, info);
|
||||
}
|
||||
|
||||
int helpWidth = metrics.horizontalAdvance(help);
|
||||
int helpHeight = metrics.height();
|
||||
const int helpWidth = metrics.horizontalAdvance(help);
|
||||
const int helpHeight = (helpWidth/width() + 1) * (metrics.height() + 5);
|
||||
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(QColor(0, 0, 0, 127));
|
||||
helpHeight = (helpWidth/width()+1) * (helpHeight + 5);
|
||||
painter.drawRect((width() - helpWidth) / 2 - 5, height()-helpHeight, helpWidth + 10, helpHeight);
|
||||
painter.drawRect((width() - helpWidth) / 2 - 5, height()-helpHeight, helpWidth + 10,
|
||||
helpHeight);
|
||||
|
||||
painter.setPen(Qt::white);
|
||||
painter.drawText(rect(), Qt::AlignHCenter|Qt::AlignBottom|Qt::TextWordWrap, help);
|
||||
@ -184,8 +186,8 @@ void MandelbrotWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
lastDragPos = QPoint();
|
||||
|
||||
const auto pixmapSize = pixmap.deviceIndependentSize().toSize();
|
||||
int deltaX = (width() - pixmapSize.width()) / 2 - pixmapOffset.x();
|
||||
int deltaY = (height() - pixmapSize.height()) / 2 - pixmapOffset.y();
|
||||
const int deltaX = (width() - pixmapSize.width()) / 2 - pixmapOffset.x();
|
||||
const int deltaY = (height() - pixmapSize.height()) / 2 - pixmapOffset.y();
|
||||
scroll(deltaX, deltaY);
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,14 @@
|
||||
#ifndef MANDELBROTWIDGET_H
|
||||
#define MANDELBROTWIDGET_H
|
||||
|
||||
#include <QGestureEvent>
|
||||
#include <QPixmap>
|
||||
#include <QWidget>
|
||||
#include "renderthread.h"
|
||||
|
||||
#include <QPixmap>
|
||||
#include <QWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QGestureEvent;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
//! [0]
|
||||
class MandelbrotWidget : public QWidget
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include "renderthread.h"
|
||||
|
||||
#include <QImage>
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QTextStream>
|
||||
|
||||
@ -70,16 +69,16 @@ void RenderThread::run()
|
||||
//! [3]
|
||||
|
||||
//! [4]
|
||||
int halfWidth = resultSize.width() / 2;
|
||||
const int halfWidth = resultSize.width() / 2;
|
||||
//! [4] //! [5]
|
||||
int halfHeight = resultSize.height() / 2;
|
||||
const int halfHeight = resultSize.height() / 2;
|
||||
QImage image(resultSize, QImage::Format_RGB32);
|
||||
image.setDevicePixelRatio(devicePixelRatio);
|
||||
|
||||
int pass = 0;
|
||||
while (pass < numPasses) {
|
||||
const int MaxIterations = (1 << (2 * pass + 6)) + 32;
|
||||
const int Limit = 4;
|
||||
constexpr int Limit = 4;
|
||||
bool allBlack = true;
|
||||
|
||||
timer.restart();
|
||||
|
@ -4,6 +4,10 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(semaphores LANGUAGES CXX)
|
||||
|
||||
if (ANDROID)
|
||||
message(FATAL_ERROR "This project cannot be built on Android.")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
@ -7,9 +7,9 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
//! [0]
|
||||
const int DataSize = 100000;
|
||||
constexpr int DataSize = 100000;
|
||||
|
||||
const int BufferSize = 8192;
|
||||
constexpr int BufferSize = 8192;
|
||||
char buffer[BufferSize];
|
||||
|
||||
QSemaphore freeBytes(BufferSize);
|
||||
|
@ -4,6 +4,10 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(waitconditions LANGUAGES CXX)
|
||||
|
||||
if (ANDROID)
|
||||
message(FATAL_ERROR "This project cannot be built on Android.")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|