summaryrefslogtreecommitdiffstats
path: root/tests/manual/examples/widgets/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tests/manual/examples/widgets/tools')
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/CMakeLists.txt18
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/app/CMakeLists.txt36
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/app/app.pro37
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/app/interfaces.h76
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/app/main.cpp19
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/app/mainwindow.cpp282
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/app/mainwindow.h68
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/app/paintarea.cpp152
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/app/paintarea.h54
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/app/plugindialog.cpp118
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/app/plugindialog.h38
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/plugandpaint.pro7
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/plugins/CMakeLists.txt4
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/CMakeLists.txt18
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictools.json1
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictools.pro17
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictoolsplugin.cpp150
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictoolsplugin.h54
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/CMakeLists.txt26
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafilters.json1
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafilters.pro17
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafiltersplugin.cpp82
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafiltersplugin.h28
-rw-r--r--tests/manual/examples/widgets/tools/plugandpaint/plugins/plugins.pro3
-rw-r--r--tests/manual/examples/widgets/tools/settingseditor/CMakeLists.txt20
-rw-r--r--tests/manual/examples/widgets/tools/settingseditor/inifiles/licensepage.ini46
-rw-r--r--tests/manual/examples/widgets/tools/settingseditor/inifiles/qsa.ini26
-rw-r--r--tests/manual/examples/widgets/tools/settingseditor/locationdialog.cpp192
-rw-r--r--tests/manual/examples/widgets/tools/settingseditor/locationdialog.h49
-rw-r--r--tests/manual/examples/widgets/tools/settingseditor/main.cpp17
-rw-r--r--tests/manual/examples/widgets/tools/settingseditor/mainwindow.cpp175
-rw-r--r--tests/manual/examples/widgets/tools/settingseditor/mainwindow.h44
-rw-r--r--tests/manual/examples/widgets/tools/settingseditor/settingseditor.pro18
-rw-r--r--tests/manual/examples/widgets/tools/settingseditor/settingstree.cpp231
-rw-r--r--tests/manual/examples/widgets/tools/settingseditor/settingstree.h61
-rw-r--r--tests/manual/examples/widgets/tools/settingseditor/variantdelegate.cpp377
-rw-r--r--tests/manual/examples/widgets/tools/settingseditor/variantdelegate.h53
37 files changed, 2615 insertions, 0 deletions
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/CMakeLists.txt b/tests/manual/examples/widgets/tools/plugandpaint/CMakeLists.txt
new file mode 100644
index 0000000000..16b261f2f1
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/CMakeLists.txt
@@ -0,0 +1,18 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(plugandpaint LANGUAGES CXX)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/widgets/tools/plugandpaint")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
+
+qt_standard_project_setup()
+
+add_subdirectory(plugins)
+add_subdirectory(app)
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/app/CMakeLists.txt b/tests/manual/examples/widgets/tools/plugandpaint/app/CMakeLists.txt
new file mode 100644
index 0000000000..b4b6277e2a
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/app/CMakeLists.txt
@@ -0,0 +1,36 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+qt_add_executable(plugandpaint
+ interfaces.h
+ main.cpp
+ mainwindow.cpp mainwindow.h
+ paintarea.cpp paintarea.h
+ plugindialog.cpp plugindialog.h
+)
+
+set_target_properties(plugandpaint PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(plugandpaint PRIVATE
+ Qt6::Widgets
+ pnp_basictools
+)
+
+if(QT6_IS_SHARED_LIBS_BUILD)
+ # Build the shared plugin too when building this example target.
+ add_dependencies(plugandpaint pnp_extrafilters)
+else()
+ # Link the extrafilters plugin if Qt is built statically.
+ target_link_libraries(plugandpaint PRIVATE
+ pnp_extrafilters
+ )
+endif()
+
+install(TARGETS plugandpaint
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/app/app.pro b/tests/manual/examples/widgets/tools/plugandpaint/app/app.pro
new file mode 100644
index 0000000000..e5ff02ecf2
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/app/app.pro
@@ -0,0 +1,37 @@
+#! [0]
+TARGET = plugandpaint
+DESTDIR = ..
+
+QT += widgets
+
+HEADERS = interfaces.h \
+ mainwindow.h \
+ paintarea.h \
+ plugindialog.h
+SOURCES = main.cpp \
+ mainwindow.cpp \
+ paintarea.cpp \
+ plugindialog.cpp
+
+LIBS = -L../plugins
+
+macx-xcode {
+ LIBS += -lpnp_basictools$($${QMAKE_XCODE_LIBRARY_SUFFIX_SETTING})
+} else {
+ android {
+ LIBS += -lpnp_basictools_$${QT_ARCH}
+ } else {
+ LIBS += -lpnp_basictools
+ }
+ if(!debug_and_release|build_pass):CONFIG(debug, debug|release) {
+ mac:LIBS = $$member(LIBS, 0) $$member(LIBS, 1)_debug
+ win32:LIBS = $$member(LIBS, 0) $$member(LIBS, 1)d
+ }
+}
+#! [0]
+
+# install
+target.path = $$[QT_INSTALL_EXAMPLES]/widgets/tools/plugandpaint
+INSTALLS += target
+
+CONFIG += install_ok # Do not cargo-cult this!
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/app/interfaces.h b/tests/manual/examples/widgets/tools/plugandpaint/app/interfaces.h
new file mode 100644
index 0000000000..9cd0e34fda
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/app/interfaces.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef INTERFACES_H
+#define INTERFACES_H
+
+#include <QtPlugin>
+
+QT_BEGIN_NAMESPACE
+class QImage;
+class QPainter;
+class QWidget;
+class QPainterPath;
+class QPoint;
+class QRect;
+class QString;
+QT_END_NAMESPACE
+
+//! [0]
+class BrushInterface
+{
+public:
+ virtual ~BrushInterface() = default;
+
+ virtual QStringList brushes() const = 0;
+ virtual QRect mousePress(const QString &brush, QPainter &painter,
+ const QPoint &pos) = 0;
+ virtual QRect mouseMove(const QString &brush, QPainter &painter,
+ const QPoint &oldPos, const QPoint &newPos) = 0;
+ virtual QRect mouseRelease(const QString &brush, QPainter &painter,
+ const QPoint &pos) = 0;
+};
+//! [0]
+
+//! [1]
+class ShapeInterface
+{
+public:
+ virtual ~ShapeInterface() = default;
+
+ virtual QStringList shapes() const = 0;
+ virtual QPainterPath generateShape(const QString &shape,
+ QWidget *parent) = 0;
+};
+//! [1]
+
+//! [2]
+class FilterInterface
+{
+public:
+ virtual ~FilterInterface() = default;
+
+ virtual QStringList filters() const = 0;
+ virtual QImage filterImage(const QString &filter, const QImage &image,
+ QWidget *parent) = 0;
+};
+//! [2]
+
+QT_BEGIN_NAMESPACE
+//! [3] //! [4]
+#define BrushInterface_iid "org.qt-project.Qt.Examples.PlugAndPaint.BrushInterface/1.0"
+
+Q_DECLARE_INTERFACE(BrushInterface, BrushInterface_iid)
+//! [3]
+
+#define ShapeInterface_iid "org.qt-project.Qt.Examples.PlugAndPaint.ShapeInterface/1.0"
+
+Q_DECLARE_INTERFACE(ShapeInterface, ShapeInterface_iid)
+//! [5]
+#define FilterInterface_iid "org.qt-project.Qt.Examples.PlugAndPaint.FilterInterface/1.0"
+
+Q_DECLARE_INTERFACE(FilterInterface, FilterInterface_iid)
+//! [4] //! [5]
+QT_END_NAMESPACE
+
+#endif
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/app/main.cpp b/tests/manual/examples/widgets/tools/plugandpaint/app/main.cpp
new file mode 100644
index 0000000000..3fc647dcca
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/app/main.cpp
@@ -0,0 +1,19 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+//! [0]
+#include "mainwindow.h"
+
+#include <QApplication>
+#include <QtPlugin>
+
+Q_IMPORT_PLUGIN(BasicToolsPlugin)
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+ MainWindow window;
+ window.show();
+ return app.exec();
+}
+//! [0]
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/app/mainwindow.cpp b/tests/manual/examples/widgets/tools/plugandpaint/app/mainwindow.cpp
new file mode 100644
index 0000000000..8e58ae4ba8
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/app/mainwindow.cpp
@@ -0,0 +1,282 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+
+#include "mainwindow.h"
+#include "interfaces.h"
+#include "paintarea.h"
+#include "plugindialog.h"
+
+#include <QAction>
+#include <QActionGroup>
+#include <QApplication>
+#include <QColorDialog>
+#include <QFileDialog>
+#include <QInputDialog>
+#include <QMenu>
+#include <QMenuBar>
+#include <QMessageBox>
+#include <QPluginLoader>
+#include <QScrollArea>
+#include <QTimer>
+
+MainWindow::MainWindow() : paintArea(new PaintArea)
+ , scrollArea(new QScrollArea)
+{
+ scrollArea->setBackgroundRole(QPalette::Dark);
+ scrollArea->setWidget(paintArea);
+ setCentralWidget(scrollArea);
+
+ createActions();
+ createMenus();
+ loadPlugins();
+
+ setWindowTitle(tr("Plug & Paint"));
+
+ if (!brushActionGroup->actions().isEmpty())
+ brushActionGroup->actions().first()->trigger();
+
+ QTimer::singleShot(500, this, &MainWindow::aboutPlugins);
+}
+
+void MainWindow::open()
+{
+ const QString fileName = QFileDialog::getOpenFileName(this,
+ tr("Open File"),
+ QDir::currentPath());
+ if (!fileName.isEmpty()) {
+ if (!paintArea->openImage(fileName)) {
+ QMessageBox::information(this, tr("Plug & Paint"),
+ tr("Cannot load %1.").arg(fileName));
+ return;
+ }
+ paintArea->adjustSize();
+ }
+}
+
+bool MainWindow::saveAs()
+{
+ const QString initialPath = QDir::currentPath() + "/untitled.png";
+
+ const QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"),
+ initialPath);
+ if (fileName.isEmpty())
+ return false;
+
+ return paintArea->saveImage(fileName, "png");
+}
+
+void MainWindow::brushColor()
+{
+ const QColor newColor = QColorDialog::getColor(paintArea->brushColor());
+ if (newColor.isValid())
+ paintArea->setBrushColor(newColor);
+}
+
+void MainWindow::brushWidth()
+{
+ bool ok;
+ const int newWidth = QInputDialog::getInt(this, tr("Plug & Paint"),
+ tr("Select brush width:"),
+ paintArea->brushWidth(),
+ 1, 50, 1, &ok);
+ if (ok)
+ paintArea->setBrushWidth(newWidth);
+}
+
+//! [0]
+void MainWindow::changeBrush()
+{
+ auto action = qobject_cast<QAction *>(sender());
+ if (!action)
+ return;
+ auto iBrush = qobject_cast<BrushInterface *>(action->parent());
+ if (!iBrush)
+ return;
+ const QString brush = action->text();
+
+ paintArea->setBrush(iBrush, brush);
+}
+//! [0]
+
+//! [1]
+void MainWindow::insertShape()
+{
+ auto action = qobject_cast<QAction *>(sender());
+ if (!action)
+ return;
+ auto iShape = qobject_cast<ShapeInterface *>(action->parent());
+ if (!iShape)
+ return;
+
+ const QPainterPath path = iShape->generateShape(action->text(), this);
+ if (!path.isEmpty())
+ paintArea->insertShape(path);
+}
+//! [1]
+
+//! [2]
+void MainWindow::applyFilter()
+{
+ auto action = qobject_cast<QAction *>(sender());
+ if (!action)
+ return;
+ auto iFilter = qobject_cast<FilterInterface *>(action->parent());
+ if (!iFilter)
+ return;
+
+ const QImage image = iFilter->filterImage(action->text(), paintArea->image(),
+ this);
+ paintArea->setImage(image);
+}
+//! [2]
+
+void MainWindow::about()
+{
+ QMessageBox::about(this, tr("About Plug & Paint"),
+ tr("The <b>Plug & Paint</b> example demonstrates how to write Qt "
+ "applications that can be extended through plugins."));
+}
+
+//! [3]
+void MainWindow::aboutPlugins()
+{
+ PluginDialog dialog(pluginsDir.path(), pluginFileNames, this);
+ dialog.exec();
+}
+//! [3]
+
+void MainWindow::createActions()
+{
+ openAct = new QAction(tr("&Open..."), this);
+ openAct->setShortcuts(QKeySequence::Open);
+ connect(openAct, &QAction::triggered, this, &MainWindow::open);
+
+ saveAsAct = new QAction(tr("&Save As..."), this);
+ saveAsAct->setShortcuts(QKeySequence::SaveAs);
+ connect(saveAsAct, &QAction::triggered, this, &MainWindow::saveAs);
+
+ exitAct = new QAction(tr("E&xit"), this);
+ exitAct->setShortcuts(QKeySequence::Quit);
+ connect(exitAct, &QAction::triggered, this, &MainWindow::close);
+
+ brushColorAct = new QAction(tr("&Brush Color..."), this);
+ connect(brushColorAct, &QAction::triggered, this, &MainWindow::brushColor);
+
+ brushWidthAct = new QAction(tr("&Brush Width..."), this);
+ connect(brushWidthAct, &QAction::triggered, this, &MainWindow::brushWidth);
+
+ brushActionGroup = new QActionGroup(this);
+
+ aboutAct = new QAction(tr("&About"), this);
+ connect(aboutAct, &QAction::triggered, this, &MainWindow::about);
+
+ aboutQtAct = new QAction(tr("About &Qt"), this);
+ connect(aboutQtAct, &QAction::triggered, qApp, &QApplication::aboutQt);
+
+ aboutPluginsAct = new QAction(tr("About &Plugins"), this);
+ connect(aboutPluginsAct, &QAction::triggered, this, &MainWindow::aboutPlugins);
+}
+
+void MainWindow::createMenus()
+{
+ fileMenu = menuBar()->addMenu(tr("&File"));
+ fileMenu->addAction(openAct);
+ fileMenu->addAction(saveAsAct);
+ fileMenu->addSeparator();
+ fileMenu->addAction(exitAct);
+
+ brushMenu = menuBar()->addMenu(tr("&Brush"));
+ brushMenu->addAction(brushColorAct);
+ brushMenu->addAction(brushWidthAct);
+ brushMenu->addSeparator();
+
+ shapesMenu = menuBar()->addMenu(tr("&Shapes"));
+
+ filterMenu = menuBar()->addMenu(tr("&Filter"));
+
+ menuBar()->addSeparator();
+
+ helpMenu = menuBar()->addMenu(tr("&Help"));
+ helpMenu->addAction(aboutAct);
+ helpMenu->addAction(aboutQtAct);
+ helpMenu->addAction(aboutPluginsAct);
+}
+
+//! [4]
+void MainWindow::loadPlugins()
+{
+ const auto staticInstances = QPluginLoader::staticInstances();
+ for (QObject *plugin : staticInstances)
+ populateMenus(plugin);
+//! [4] //! [5]
+
+ pluginsDir = QDir(QCoreApplication::applicationDirPath());
+
+#if defined(Q_OS_WIN)
+ if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release")
+ pluginsDir.cdUp();
+#elif defined(Q_OS_MAC)
+ if (pluginsDir.dirName() == "MacOS") {
+ pluginsDir.cdUp();
+ pluginsDir.cdUp();
+ pluginsDir.cdUp();
+ }
+#endif
+ pluginsDir.cd("plugins");
+//! [5]
+
+//! [6]
+ const auto entryList = pluginsDir.entryList(QDir::Files);
+ for (const QString &fileName : entryList) {
+ QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
+ QObject *plugin = loader.instance();
+ if (plugin) {
+ populateMenus(plugin);
+ pluginFileNames += fileName;
+//! [6] //! [7]
+ }
+//! [7] //! [8]
+ }
+//! [8]
+
+//! [9]
+ brushMenu->setEnabled(!brushActionGroup->actions().isEmpty());
+ shapesMenu->setEnabled(!shapesMenu->actions().isEmpty());
+ filterMenu->setEnabled(!filterMenu->actions().isEmpty());
+}
+//! [9]
+
+//! [10]
+void MainWindow::populateMenus(QObject *plugin)
+{
+ auto iBrush = qobject_cast<BrushInterface *>(plugin);
+ if (iBrush)
+ addToMenu(plugin, iBrush->brushes(), brushMenu, &MainWindow::changeBrush,
+ brushActionGroup);
+
+ auto iShape = qobject_cast<ShapeInterface *>(plugin);
+ if (iShape)
+ addToMenu(plugin, iShape->shapes(), shapesMenu, &MainWindow::insertShape);
+
+ auto iFilter = qobject_cast<FilterInterface *>(plugin);
+ if (iFilter)
+ addToMenu(plugin, iFilter->filters(), filterMenu, &MainWindow::applyFilter);
+}
+//! [10]
+
+void MainWindow::addToMenu(QObject *plugin, const QStringList &texts,
+ QMenu *menu, Member member,
+ QActionGroup *actionGroup)
+{
+ for (const QString &text : texts) {
+ auto action = new QAction(text, plugin);
+ connect(action, &QAction::triggered, this, member);
+ menu->addAction(action);
+
+ if (actionGroup) {
+ action->setCheckable(true);
+ actionGroup->addAction(action);
+ }
+ }
+}
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/app/mainwindow.h b/tests/manual/examples/widgets/tools/plugandpaint/app/mainwindow.h
new file mode 100644
index 0000000000..bc09471ba5
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/app/mainwindow.h
@@ -0,0 +1,68 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QDir>
+#include <QMainWindow>
+#include <QStringList>
+
+QT_BEGIN_NAMESPACE
+class QAction;
+class QActionGroup;
+class QMenu;
+class QScrollArea;
+QT_END_NAMESPACE
+class PaintArea;
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ MainWindow();
+
+private slots:
+ void open();
+ bool saveAs();
+ void brushColor();
+ void brushWidth();
+ void changeBrush();
+ void insertShape();
+ void applyFilter();
+ void about();
+ void aboutPlugins();
+
+private:
+ typedef void (MainWindow::*Member)();
+
+ void createActions();
+ void createMenus();
+ void loadPlugins();
+ void populateMenus(QObject *plugin);
+ void addToMenu(QObject *plugin, const QStringList &texts, QMenu *menu,
+ Member member, QActionGroup *actionGroup = nullptr);
+
+ PaintArea *paintArea = nullptr;
+ QScrollArea *scrollArea = nullptr;
+ QDir pluginsDir;
+ QStringList pluginFileNames;
+
+ QMenu *fileMenu = nullptr;
+ QMenu *brushMenu = nullptr;
+ QMenu *shapesMenu = nullptr;
+ QMenu *filterMenu = nullptr;
+ QMenu *helpMenu = nullptr;
+ QActionGroup *brushActionGroup = nullptr;
+ QAction *openAct = nullptr;
+ QAction *saveAsAct = nullptr;
+ QAction *exitAct = nullptr;
+ QAction *brushWidthAct = nullptr;
+ QAction *brushColorAct = nullptr;
+ QAction *aboutAct = nullptr;
+ QAction *aboutQtAct = nullptr;
+ QAction *aboutPluginsAct = nullptr;
+};
+
+#endif
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/app/paintarea.cpp b/tests/manual/examples/widgets/tools/plugandpaint/app/paintarea.cpp
new file mode 100644
index 0000000000..3596f7979c
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/app/paintarea.cpp
@@ -0,0 +1,152 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+
+#include "paintarea.h"
+#include "interfaces.h"
+
+#include <QMouseEvent>
+#include <QPainter>
+
+PaintArea::PaintArea(QWidget *parent) : QWidget(parent)
+{
+ setAttribute(Qt::WA_StaticContents);
+ setAttribute(Qt::WA_OpaquePaintEvent);
+
+ theImage.fill(qRgb(255, 255, 255));
+}
+
+bool PaintArea::openImage(const QString &fileName)
+{
+ QImage image;
+ if (!image.load(fileName))
+ return false;
+
+ setImage(image);
+ return true;
+}
+
+bool PaintArea::saveImage(const QString &fileName, const char *fileFormat)
+{
+ return theImage.save(fileName, fileFormat);
+}
+
+void PaintArea::setImage(const QImage &image)
+{
+ theImage = image.convertToFormat(QImage::Format_RGB32);
+ update();
+ updateGeometry();
+}
+
+void PaintArea::insertShape(const QPainterPath &path)
+{
+ pendingPath = path;
+#ifndef QT_NO_CURSOR
+ setCursor(Qt::CrossCursor);
+#endif
+}
+
+void PaintArea::setBrushColor(const QColor &color)
+{
+ this->color = color;
+}
+
+void PaintArea::setBrushWidth(int width)
+{
+ thickness = width;
+}
+
+//! [0]
+void PaintArea::setBrush(BrushInterface *brushInterface, const QString &brush)
+{
+ this->brushInterface = brushInterface;
+ this->brush = brush;
+}
+//! [0]
+
+QSize PaintArea::sizeHint() const
+{
+ return theImage.size();
+}
+
+void PaintArea::paintEvent(QPaintEvent * /* event */)
+{
+ QPainter painter(this);
+ painter.drawImage(QPoint(0, 0), theImage);
+}
+
+void PaintArea::mousePressEvent(QMouseEvent *event)
+{
+ if (event->button() == Qt::LeftButton) {
+ if (!pendingPath.isEmpty()) {
+ QPainter painter(&theImage);
+ setupPainter(painter);
+
+ const QRectF boundingRect = pendingPath.boundingRect();
+ QLinearGradient gradient(boundingRect.topRight(),
+ boundingRect.bottomLeft());
+ gradient.setColorAt(0.0, QColor(color.red(), color.green(),
+ color.blue(), 63));
+ gradient.setColorAt(1.0, QColor(color.red(), color.green(),
+ color.blue(), 191));
+ painter.setBrush(gradient);
+ painter.translate(event->position().toPoint() - boundingRect.center());
+ painter.drawPath(pendingPath);
+
+ pendingPath = QPainterPath();
+#ifndef QT_NO_CURSOR
+ unsetCursor();
+#endif
+ update();
+ } else {
+ if (brushInterface) {
+ QPainter painter(&theImage);
+ setupPainter(painter);
+ const QRect rect = brushInterface->mousePress(brush, painter,
+ event->position().toPoint());
+ update(rect);
+ }
+
+ lastPos = event->position().toPoint();
+ }
+ }
+}
+
+//! [1]
+void PaintArea::mouseMoveEvent(QMouseEvent *event)
+{
+ if ((event->buttons() & Qt::LeftButton) && lastPos != QPoint(-1, -1)) {
+ if (brushInterface) {
+ QPainter painter(&theImage);
+ setupPainter(painter);
+ const QRect rect = brushInterface->mouseMove(brush, painter, lastPos,
+ event->position().toPoint());
+ update(rect);
+ }
+
+ lastPos = event->position().toPoint();
+ }
+}
+//! [1]
+
+void PaintArea::mouseReleaseEvent(QMouseEvent *event)
+{
+ if (event->button() == Qt::LeftButton && lastPos != QPoint(-1, -1)) {
+ if (brushInterface) {
+ QPainter painter(&theImage);
+ setupPainter(painter);
+ QRect rect = brushInterface->mouseRelease(brush, painter,
+ event->position().toPoint());
+ update(rect);
+ }
+
+ lastPos = QPoint(-1, -1);
+ }
+}
+
+void PaintArea::setupPainter(QPainter &painter)
+{
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ painter.setPen(QPen(color, thickness, Qt::SolidLine, Qt::RoundCap,
+ Qt::RoundJoin));
+}
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/app/paintarea.h b/tests/manual/examples/widgets/tools/plugandpaint/app/paintarea.h
new file mode 100644
index 0000000000..f24db0ba89
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/app/paintarea.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef PAINTAREA_H
+#define PAINTAREA_H
+
+#include <QColor>
+#include <QImage>
+#include <QPainterPath>
+#include <QWidget>
+
+class BrushInterface;
+
+class PaintArea : public QWidget
+{
+ Q_OBJECT
+
+public:
+ PaintArea(QWidget *parent = nullptr);
+
+ bool openImage(const QString &fileName);
+ bool saveImage(const QString &fileName, const char *fileFormat);
+ void setImage(const QImage &image);
+ void insertShape(const QPainterPath &path);
+ void setBrushColor(const QColor &color);
+ void setBrushWidth(int width);
+ void setBrush(BrushInterface *brushInterface, const QString &brush);
+
+ QImage image() const { return theImage; }
+ QColor brushColor() const { return color; }
+ int brushWidth() const { return thickness; }
+ QSize sizeHint() const override;
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+ void mousePressEvent(QMouseEvent *event) override;
+ void mouseMoveEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+
+private:
+ void setupPainter(QPainter &painter);
+
+ QImage theImage = {500, 400, QImage::Format_RGB32};
+ QColor color = Qt::blue;
+ int thickness = 3;
+
+ BrushInterface *brushInterface = nullptr;
+ QString brush;
+ QPoint lastPos = {-1, -1};
+
+ QPainterPath pendingPath;
+};
+
+#endif
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/app/plugindialog.cpp b/tests/manual/examples/widgets/tools/plugandpaint/app/plugindialog.cpp
new file mode 100644
index 0000000000..2ff5c4b1e3
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/app/plugindialog.cpp
@@ -0,0 +1,118 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+
+#include "plugindialog.h"
+#include "interfaces.h"
+
+#include <QDir>
+#include <QGridLayout>
+#include <QHeaderView>
+#include <QLabel>
+#include <QPluginLoader>
+#include <QPushButton>
+#include <QStringList>
+#include <QTreeWidget>
+#include <QTreeWidgetItem>
+
+PluginDialog::PluginDialog(const QString &path, const QStringList &fileNames,
+ QWidget *parent) :
+ QDialog(parent),
+ label(new QLabel),
+ treeWidget(new QTreeWidget),
+ okButton(new QPushButton(tr("OK")))
+{
+ treeWidget->setAlternatingRowColors(false);
+ treeWidget->setSelectionMode(QAbstractItemView::NoSelection);
+ treeWidget->setColumnCount(1);
+ treeWidget->header()->hide();
+
+ okButton->setDefault(true);
+
+ connect(okButton, &QAbstractButton::clicked, this, &QWidget::close);
+
+ QGridLayout *mainLayout = new QGridLayout;
+ mainLayout->setColumnStretch(0, 1);
+ mainLayout->setColumnStretch(2, 1);
+ mainLayout->addWidget(label, 0, 0, 1, 3);
+ mainLayout->addWidget(treeWidget, 1, 0, 1, 3);
+ mainLayout->addWidget(okButton, 2, 1);
+ setLayout(mainLayout);
+
+ interfaceIcon.addPixmap(style()->standardPixmap(QStyle::SP_DirOpenIcon),
+ QIcon::Normal, QIcon::On);
+ interfaceIcon.addPixmap(style()->standardPixmap(QStyle::SP_DirClosedIcon),
+ QIcon::Normal, QIcon::Off);
+ featureIcon.addPixmap(style()->standardPixmap(QStyle::SP_FileIcon));
+
+ setWindowTitle(tr("Plugin Information"));
+ findPlugins(path, fileNames);
+}
+
+//! [0]
+void PluginDialog::findPlugins(const QString &path,
+ const QStringList &fileNames)
+{
+ label->setText(tr("Plug & Paint found the following plugins\n"
+ "(looked in %1):")
+ .arg(QDir::toNativeSeparators(path)));
+
+ const QDir dir(path);
+
+ const auto staticInstances = QPluginLoader::staticInstances();
+ for (QObject *plugin : staticInstances)
+ populateTreeWidget(plugin, tr("%1 (Static Plugin)")
+ .arg(plugin->metaObject()->className()));
+
+ for (const QString &fileName : fileNames) {
+ QPluginLoader loader(dir.absoluteFilePath(fileName));
+ QObject *plugin = loader.instance();
+ if (plugin)
+ populateTreeWidget(plugin, fileName);
+ }
+}
+//! [0]
+
+//! [1]
+void PluginDialog::populateTreeWidget(QObject *plugin, const QString &text)
+{
+ auto pluginItem = new QTreeWidgetItem(treeWidget);
+ pluginItem->setText(0, text);
+ pluginItem->setExpanded(true);
+
+ QFont boldFont = pluginItem->font(0);
+ boldFont.setBold(true);
+ pluginItem->setFont(0, boldFont);
+
+ if (plugin) {
+ auto iBrush = qobject_cast<BrushInterface *>(plugin);
+ if (iBrush)
+ addItems(pluginItem, "BrushInterface", iBrush->brushes());
+
+ auto iShape = qobject_cast<ShapeInterface *>(plugin);
+ if (iShape)
+ addItems(pluginItem, "ShapeInterface", iShape->shapes());
+
+ auto iFilter = qobject_cast<FilterInterface *>(plugin);
+ if (iFilter)
+ addItems(pluginItem, "FilterInterface", iFilter->filters());
+ }
+}
+//! [1]
+
+void PluginDialog::addItems(QTreeWidgetItem *pluginItem,
+ const char *interfaceName,
+ const QStringList &features)
+{
+ auto interfaceItem = new QTreeWidgetItem(pluginItem);
+ interfaceItem->setText(0, interfaceName);
+ interfaceItem->setIcon(0, interfaceIcon);
+
+ for (QString feature : features) {
+ if (feature.endsWith("..."))
+ feature.chop(3);
+ auto featureItem = new QTreeWidgetItem(interfaceItem);
+ featureItem->setText(0, feature);
+ featureItem->setIcon(0, featureIcon);
+ }
+}
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/app/plugindialog.h b/tests/manual/examples/widgets/tools/plugandpaint/app/plugindialog.h
new file mode 100644
index 0000000000..32b8aa6fe0
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/app/plugindialog.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef PLUGINDIALOG_H
+#define PLUGINDIALOG_H
+
+#include <QDialog>
+#include <QIcon>
+
+QT_BEGIN_NAMESPACE
+class QLabel;
+class QPushButton;
+class QTreeWidget;
+class QTreeWidgetItem;
+QT_END_NAMESPACE
+
+class PluginDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ PluginDialog(const QString &path, const QStringList &fileNames,
+ QWidget *parent = nullptr);
+
+private:
+ void findPlugins(const QString &path, const QStringList &fileNames);
+ void populateTreeWidget(QObject *plugin, const QString &text);
+ void addItems(QTreeWidgetItem *pluginItem, const char *interfaceName,
+ const QStringList &features);
+
+ QLabel *label = nullptr;
+ QTreeWidget *treeWidget = nullptr;
+ QPushButton *okButton = nullptr;
+ QIcon interfaceIcon;
+ QIcon featureIcon;
+};
+
+#endif
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/plugandpaint.pro b/tests/manual/examples/widgets/tools/plugandpaint/plugandpaint.pro
new file mode 100644
index 0000000000..58c4dbbb6e
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/plugandpaint.pro
@@ -0,0 +1,7 @@
+QT_FOR_CONFIG += widgets
+requires(qtConfig(inputdialog))
+
+TEMPLATE = subdirs
+SUBDIRS = plugins app
+
+app.depends = plugins
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/plugins/CMakeLists.txt b/tests/manual/examples/widgets/tools/plugandpaint/plugins/CMakeLists.txt
new file mode 100644
index 0000000000..c468cdc191
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/plugins/CMakeLists.txt
@@ -0,0 +1,4 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+add_subdirectory(basictools)
+add_subdirectory(extrafilters)
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/CMakeLists.txt b/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/CMakeLists.txt
new file mode 100644
index 0000000000..8b6436173b
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/CMakeLists.txt
@@ -0,0 +1,18 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+qt_add_plugin(pnp_basictools
+ STATIC
+ CLASS_NAME BasicToolsPlugin
+ basictoolsplugin.cpp basictoolsplugin.h
+)
+
+target_include_directories(pnp_basictools PRIVATE
+ ../../app
+)
+
+target_link_libraries(pnp_basictools PRIVATE
+ Qt6::Core
+ Qt6::Gui
+ Qt6::Widgets
+)
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictools.json b/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictools.json
new file mode 100644
index 0000000000..0967ef424b
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictools.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictools.pro b/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictools.pro
new file mode 100644
index 0000000000..f5ba95252c
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictools.pro
@@ -0,0 +1,17 @@
+#! [0]
+TEMPLATE = lib
+CONFIG += plugin static
+QT += widgets
+INCLUDEPATH += ../../app
+HEADERS = basictoolsplugin.h
+SOURCES = basictoolsplugin.cpp
+TARGET = $$qtLibraryTarget(pnp_basictools)
+DESTDIR = ../../plugins
+#! [0]
+
+# install
+target.path = $$[QT_INSTALL_EXAMPLES]/widgets/tools/plugandpaint/plugins
+INSTALLS += target
+
+CONFIG += install_ok # Do not cargo-cult this!
+uikit: CONFIG += debug_and_release
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictoolsplugin.cpp b/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictoolsplugin.cpp
new file mode 100644
index 0000000000..6a350f38a4
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictoolsplugin.cpp
@@ -0,0 +1,150 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "basictoolsplugin.h"
+
+#include <QInputDialog>
+#include <QPainter>
+#include <QRandomGenerator>
+#include <QtMath>
+
+//! [0]
+QStringList BasicToolsPlugin::brushes() const
+{
+ return {tr("Pencil"), tr("Air Brush"), tr("Random Letters")};
+}
+//! [0]
+
+//! [1]
+QRect BasicToolsPlugin::mousePress(const QString &brush, QPainter &painter,
+ const QPoint &pos)
+{
+ return mouseMove(brush, painter, pos, pos);
+}
+//! [1]
+
+//! [2]
+QRect BasicToolsPlugin::mouseMove(const QString &brush, QPainter &painter,
+ const QPoint &oldPos, const QPoint &newPos)
+{
+ painter.save();
+
+ int rad = painter.pen().width() / 2;
+ QRect boundingRect = QRect(oldPos, newPos).normalized()
+ .adjusted(-rad, -rad, +rad, +rad);
+ QColor color = painter.pen().color();
+ int thickness = painter.pen().width();
+ QColor transparentColor(color.red(), color.green(), color.blue(), 0);
+//! [2] //! [3]
+
+ if (brush == tr("Pencil")) {
+ painter.drawLine(oldPos, newPos);
+ } else if (brush == tr("Air Brush")) {
+ int numSteps = 2 + (newPos - oldPos).manhattanLength() / 2;
+
+ painter.setBrush(QBrush(color, Qt::Dense6Pattern));
+ painter.setPen(Qt::NoPen);
+
+ for (int i = 0; i < numSteps; ++i) {
+ int x = oldPos.x() + i * (newPos.x() - oldPos.x()) / (numSteps - 1);
+ int y = oldPos.y() + i * (newPos.y() - oldPos.y()) / (numSteps - 1);
+
+ painter.drawEllipse(x - (thickness / 2), y - (thickness / 2),
+ thickness, thickness);
+ }
+ } else if (brush == tr("Random Letters")) {
+ QChar ch(QRandomGenerator::global()->bounded('A', 'Z' + 1));
+
+ QFont biggerFont = painter.font();
+ biggerFont.setBold(true);
+ biggerFont.setPointSize(biggerFont.pointSize() + thickness);
+ painter.setFont(biggerFont);
+
+ painter.drawText(newPos, QString(ch));
+
+ QFontMetrics metrics(painter.font());
+ boundingRect = metrics.boundingRect(ch);
+ boundingRect.translate(newPos);
+ boundingRect.adjust(-10, -10, +10, +10);
+ }
+ painter.restore();
+ return boundingRect;
+}
+//! [3]
+
+//! [4]
+QRect BasicToolsPlugin::mouseRelease(const QString & /* brush */,
+ QPainter & /* painter */,
+ const QPoint & /* pos */)
+{
+ return QRect(0, 0, 0, 0);
+}
+//! [4]
+
+//! [5]
+QStringList BasicToolsPlugin::shapes() const
+{
+ return {tr("Circle"), tr("Star"), tr("Text...")};
+}
+//! [5]
+
+//! [6]
+QPainterPath BasicToolsPlugin::generateShape(const QString &shape,
+ QWidget *parent)
+{
+ QPainterPath path;
+
+ if (shape == tr("Circle")) {
+ path.addEllipse(0, 0, 50, 50);
+ } else if (shape == tr("Star")) {
+ path.moveTo(90, 50);
+ for (int i = 1; i < 5; ++i) {
+ path.lineTo(50 + 40 * std::cos(0.8 * i * M_PI),
+ 50 + 40 * std::sin(0.8 * i * M_PI));
+ }
+ path.closeSubpath();
+ } else if (shape == tr("Text...")) {
+ QString text = QInputDialog::getText(parent, tr("Text Shape"),
+ tr("Enter text:"),
+ QLineEdit::Normal, tr("Qt"));
+ if (!text.isEmpty()) {
+ QFont timesFont("Times", 50);
+ timesFont.setStyleStrategy(QFont::ForceOutline);
+ path.addText(0, 0, timesFont, text);
+ }
+ }
+
+ return path;
+}
+//! [6]
+
+//! [7]
+QStringList BasicToolsPlugin::filters() const
+{
+ return {tr("Invert Pixels"), tr("Swap RGB"), tr("Grayscale")};
+}
+//! [7]
+
+//! [8]
+QImage BasicToolsPlugin::filterImage(const QString &filter, const QImage &image,
+ QWidget * /* parent */)
+{
+ QImage result = image.convertToFormat(QImage::Format_RGB32);
+
+ if (filter == tr("Invert Pixels")) {
+ result.invertPixels();
+ } else if (filter == tr("Swap RGB")) {
+ result = result.rgbSwapped();
+ } else if (filter == tr("Grayscale")) {
+ for (int y = 0; y < result.height(); ++y) {
+ for (int x = 0; x < result.width(); ++x) {
+ QRgb pixel = result.pixel(x, y);
+ int gray = qGray(pixel);
+ int alpha = qAlpha(pixel);
+ result.setPixel(x, y, qRgba(gray, gray, gray, alpha));
+ }
+ }
+ }
+ return result;
+}
+//! [8]
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictoolsplugin.h b/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictoolsplugin.h
new file mode 100644
index 0000000000..9fb3295bf5
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/plugins/basictools/basictoolsplugin.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef BASICTOOLSPLUGIN_H
+#define BASICTOOLSPLUGIN_H
+
+//! [0]
+#include <interfaces.h>
+
+#include <QImage>
+#include <QObject>
+#include <QPainterPath>
+#include <QRect>
+#include <QStringList>
+#include <QtPlugin>
+
+//! [1]
+class BasicToolsPlugin : public QObject,
+ public BrushInterface,
+ public ShapeInterface,
+ public FilterInterface
+{
+ Q_OBJECT
+//! [4]
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.PlugAndPaint.BrushInterface" FILE "basictools.json")
+//! [4]
+ Q_INTERFACES(BrushInterface ShapeInterface FilterInterface)
+//! [0]
+
+//! [2]
+public:
+//! [1]
+ // BrushInterface
+ QStringList brushes() const override;
+ QRect mousePress(const QString &brush, QPainter &painter,
+ const QPoint &pos) override;
+ QRect mouseMove(const QString &brush, QPainter &painter,
+ const QPoint &oldPos, const QPoint &newPos) override;
+ QRect mouseRelease(const QString &brush, QPainter &painter,
+ const QPoint &pos) override;
+
+ // ShapeInterface
+ QStringList shapes() const override;
+ QPainterPath generateShape(const QString &shape, QWidget *parent) override;
+
+ // FilterInterface
+ QStringList filters() const override;
+ QImage filterImage(const QString &filter, const QImage &image,
+ QWidget *parent) override;
+//! [3]
+};
+//! [2] //! [3]
+
+#endif
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/CMakeLists.txt b/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/CMakeLists.txt
new file mode 100644
index 0000000000..ead67decd5
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+qt_add_plugin(pnp_extrafilters
+ CLASS_NAME ExtraFiltersPlugin
+ extrafiltersplugin.cpp extrafiltersplugin.h
+)
+
+set_target_properties(pnp_extrafilters PROPERTIES
+ LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/app"
+)
+
+target_include_directories(pnp_extrafilters PRIVATE
+ ../../app
+)
+
+target_link_libraries(pnp_extrafilters PRIVATE
+ Qt6::Core
+ Qt6::Gui
+ Qt6::Widgets
+)
+
+install(TARGETS pnp_extrafilters
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}/plugins"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}/plugins"
+)
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafilters.json b/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafilters.json
new file mode 100644
index 0000000000..0967ef424b
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafilters.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafilters.pro b/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafilters.pro
new file mode 100644
index 0000000000..e137b04823
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafilters.pro
@@ -0,0 +1,17 @@
+#! [0]
+TEMPLATE = lib
+CONFIG += plugin
+QT += widgets
+INCLUDEPATH += ../../app
+HEADERS = extrafiltersplugin.h
+SOURCES = extrafiltersplugin.cpp
+TARGET = $$qtLibraryTarget(pnp_extrafilters)
+DESTDIR = ../../plugins
+
+#! [0]
+# install
+target.path = $$[QT_INSTALL_EXAMPLES]/widgets/tools/plugandpaint/plugins
+INSTALLS += target
+
+CONFIG += install_ok # Do not cargo-cult this!
+uikit: CONFIG += debug_and_release
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafiltersplugin.cpp b/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafiltersplugin.cpp
new file mode 100644
index 0000000000..5356efc328
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafiltersplugin.cpp
@@ -0,0 +1,82 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "extrafiltersplugin.h"
+
+#include <QInputDialog>
+
+QStringList ExtraFiltersPlugin::filters() const
+{
+ return {tr("Flip Horizontally"), tr("Flip Vertically"),
+ tr("Smudge..."), tr("Threshold...")};
+}
+
+QImage ExtraFiltersPlugin::filterImage(const QString &filter,
+ const QImage &image, QWidget *parent)
+{
+ QImage original = image.convertToFormat(QImage::Format_RGB32);
+ QImage result = original;
+
+ if (filter == tr("Flip Horizontally")) {
+ for (int y = 0; y < original.height(); ++y) {
+ for (int x = 0; x < original.width(); ++x) {
+ QRgb pixel = original.pixel(original.width() - x - 1, y);
+ result.setPixel(x, y, pixel);
+ }
+ }
+ } else if (filter == tr("Flip Vertically")) {
+ for (int y = 0; y < original.height(); ++y) {
+ for (int x = 0; x < original.width(); ++x) {
+ QRgb pixel = original.pixel(x, original.height() - y - 1);
+ result.setPixel(x, y, pixel);
+ }
+ }
+ } else if (filter == tr("Smudge...")) {
+ bool ok;
+ int numIters = QInputDialog::getInt(parent, tr("Smudge Filter"),
+ tr("Enter number of iterations:"),
+ 5, 1, 20, 1, &ok);
+ if (ok) {
+ for (int i = 0; i < numIters; ++i) {
+ for (int y = 1; y < original.height() - 1; ++y) {
+ for (int x = 1; x < original.width() - 1; ++x) {
+ QRgb p1 = original.pixel(x, y);
+ QRgb p2 = original.pixel(x, y + 1);
+ QRgb p3 = original.pixel(x, y - 1);
+ QRgb p4 = original.pixel(x + 1, y);
+ QRgb p5 = original.pixel(x - 1, y);
+
+ int red = (qRed(p1) + qRed(p2) + qRed(p3) + qRed(p4)
+ + qRed(p5)) / 5;
+ int green = (qGreen(p1) + qGreen(p2) + qGreen(p3)
+ + qGreen(p4) + qGreen(p5)) / 5;
+ int blue = (qBlue(p1) + qBlue(p2) + qBlue(p3)
+ + qBlue(p4) + qBlue(p5)) / 5;
+ int alpha = (qAlpha(p1) + qAlpha(p2) + qAlpha(p3)
+ + qAlpha(p4) + qAlpha(p5)) / 5;
+
+ result.setPixel(x, y, qRgba(red, green, blue, alpha));
+ }
+ }
+ }
+ }
+ } else if (filter == tr("Threshold...")) {
+ bool ok;
+ int threshold = QInputDialog::getInt(parent, tr("Threshold Filter"),
+ tr("Enter threshold:"),
+ 10, 1, 256, 1, &ok);
+ if (ok) {
+ int factor = 256 / threshold;
+ for (int y = 0; y < original.height(); ++y) {
+ for (int x = 0; x < original.width(); ++x) {
+ QRgb pixel = original.pixel(x, y);
+ result.setPixel(x, y, qRgba(qRed(pixel) / factor * factor,
+ qGreen(pixel) / factor * factor,
+ qBlue(pixel) / factor * factor,
+ qAlpha(pixel)));
+ }
+ }
+ }
+ }
+ return result;
+}
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafiltersplugin.h b/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafiltersplugin.h
new file mode 100644
index 0000000000..0fc50d2fd0
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/plugins/extrafilters/extrafiltersplugin.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef EXTRAFILTERSPLUGIN_H
+#define EXTRAFILTERSPLUGIN_H
+
+//! [0]
+#include <interfaces.h>
+
+#include <QObject>
+#include <QtPlugin>
+#include <QStringList>
+#include <QImage>
+
+class ExtraFiltersPlugin : public QObject, public FilterInterface
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.PlugAndPaint.FilterInterface" FILE "extrafilters.json")
+ Q_INTERFACES(FilterInterface)
+
+public:
+ QStringList filters() const override;
+ QImage filterImage(const QString &filter, const QImage &image,
+ QWidget *parent) override;
+};
+//! [0]
+
+#endif
diff --git a/tests/manual/examples/widgets/tools/plugandpaint/plugins/plugins.pro b/tests/manual/examples/widgets/tools/plugandpaint/plugins/plugins.pro
new file mode 100644
index 0000000000..e15220c621
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/plugandpaint/plugins/plugins.pro
@@ -0,0 +1,3 @@
+TEMPLATE = subdirs
+SUBDIRS = basictools \
+ extrafilters
diff --git a/tests/manual/examples/widgets/tools/settingseditor/CMakeLists.txt b/tests/manual/examples/widgets/tools/settingseditor/CMakeLists.txt
new file mode 100644
index 0000000000..3b934a9ae9
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/settingseditor/CMakeLists.txt
@@ -0,0 +1,20 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(settingseditor LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
+qt_internal_add_manual_test(settingseditor
+ SOURCES
+ locationdialog.cpp locationdialog.h
+ main.cpp
+ mainwindow.cpp mainwindow.h
+ settingstree.cpp settingstree.h
+ variantdelegate.cpp variantdelegate.h
+ LIBRARIES
+ Qt::Widgets
+)
diff --git a/tests/manual/examples/widgets/tools/settingseditor/inifiles/licensepage.ini b/tests/manual/examples/widgets/tools/settingseditor/inifiles/licensepage.ini
new file mode 100644
index 0000000000..608d1b7885
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/settingseditor/inifiles/licensepage.ini
@@ -0,0 +1,46 @@
+[Field%201]
+Bottom=89
+Flags=MULTILINE|VSCROLL|READONLY
+Left=4
+Right=296
+State=No license agreement file found. Please contact support.
+Top=14
+Type=Text
+
+[Field%202]
+Bottom=8
+Left=4
+Right=294
+Text=Press Page Down to see the rest of the agreement.
+Top=0
+Type=Label
+
+[Field%203]
+Bottom=111
+Left=4
+Right=297
+Text=If you accept the terms of the agreement, select the first option below. You must accept the agreement to install this software. Click Next to continue.
+Top=92
+Type=Label
+
+[Field%204]
+Bottom=129
+Flags=GROUP|NOTIFY
+Left=4
+Right=299
+Text=I &accept the terms in the License Agreement
+Top=120
+Type=RadioButton
+
+[Field%205]
+Bottom=140
+Flags=NOTIFY
+Left=4
+Right=300
+State=1
+Text=I &do not accept the terms in the License Agreement
+Top=129
+Type=RadioButton
+
+[Settings]
+NumFields=5
diff --git a/tests/manual/examples/widgets/tools/settingseditor/inifiles/qsa.ini b/tests/manual/examples/widgets/tools/settingseditor/inifiles/qsa.ini
new file mode 100644
index 0000000000..56a2964ee5
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/settingseditor/inifiles/qsa.ini
@@ -0,0 +1,26 @@
+[Field%201]
+Bottom=65
+Left=0
+Right=299
+Text=QSA Build Options
+Top=9
+Type=Groupbox
+
+[Field%202]
+Bottom=37
+Left=20
+Right=284
+Text=Don't compile QSA Workbench into QSA.
+Top=27
+Type=Checkbox
+
+[Field%203]
+Bottom=56
+Left=20
+Right=247
+Text=Don't compile QSA Workbench nor QSA Editor into QSA.
+Top=45
+Type=Checkbox
+
+[Settings]
+NumFields=3
diff --git a/tests/manual/examples/widgets/tools/settingseditor/locationdialog.cpp b/tests/manual/examples/widgets/tools/settingseditor/locationdialog.cpp
new file mode 100644
index 0000000000..1c41d45009
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/settingseditor/locationdialog.cpp
@@ -0,0 +1,192 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "locationdialog.h"
+
+#include <QBoxLayout>
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QDir>
+#include <QPushButton>
+#include <QGroupBox>
+#include <QHeaderView>
+#include <QLabel>
+#include <QLineEdit>
+#include <QTableWidget>
+#include <QTableWidgetItem>
+
+LocationDialog::LocationDialog(QWidget *parent)
+ : QDialog(parent)
+{
+ formatComboBox = new QComboBox;
+ formatComboBox->addItem(tr("Native"));
+ formatComboBox->addItem(tr("INI"));
+
+ scopeComboBox = new QComboBox;
+ scopeComboBox->addItem(tr("User"));
+ scopeComboBox->addItem(tr("System"));
+
+ organizationComboBox = new QComboBox;
+ organizationComboBox->addItem(tr("QtProject"));
+ organizationComboBox->setEditable(true);
+
+ applicationComboBox = new QComboBox;
+ applicationComboBox->addItem(tr("Any"));
+ applicationComboBox->addItem(tr("Qt Creator"));
+ applicationComboBox->addItem(tr("Assistant"));
+ applicationComboBox->addItem(tr("Designer"));
+ applicationComboBox->addItem(tr("Linguist"));
+ applicationComboBox->setEditable(true);
+ applicationComboBox->setCurrentIndex(1);
+
+ formatLabel = new QLabel(tr("&Format:"));
+ formatLabel->setBuddy(formatComboBox);
+
+ scopeLabel = new QLabel(tr("&Scope:"));
+ scopeLabel->setBuddy(scopeComboBox);
+
+ organizationLabel = new QLabel(tr("&Organization:"));
+ organizationLabel->setBuddy(organizationComboBox);
+
+ applicationLabel = new QLabel(tr("&Application:"));
+ applicationLabel->setBuddy(applicationComboBox);
+
+ locationsGroupBox = new QGroupBox(tr("Setting Locations"));
+
+ const QStringList labels{tr("Location"), tr("Access")};
+
+ locationsTable = new QTableWidget;
+ locationsTable->setSelectionMode(QAbstractItemView::SingleSelection);
+ locationsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
+ locationsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ locationsTable->setColumnCount(2);
+ locationsTable->setHorizontalHeaderLabels(labels);
+ locationsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+ locationsTable->horizontalHeader()->resizeSection(1, 180);
+ connect(locationsTable, &QTableWidget::itemActivated, this, &LocationDialog::itemActivated);
+
+ buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+
+ connect(formatComboBox, &QComboBox::activated,
+ this, &LocationDialog::updateLocationsTable);
+ connect(scopeComboBox, &QComboBox::activated,
+ this, &LocationDialog::updateLocationsTable);
+ connect(organizationComboBox->lineEdit(),
+ &QLineEdit::editingFinished,
+ this, &LocationDialog::updateLocationsTable);
+ connect(applicationComboBox->lineEdit(),
+ &QLineEdit::editingFinished,
+ this, &LocationDialog::updateLocationsTable);
+ connect(applicationComboBox, &QComboBox::activated,
+ this, &LocationDialog::updateLocationsTable);
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+
+ QVBoxLayout *locationsLayout = new QVBoxLayout(locationsGroupBox);
+ locationsLayout->addWidget(locationsTable);
+
+ QGridLayout *mainLayout = new QGridLayout(this);
+ mainLayout->addWidget(formatLabel, 0, 0);
+ mainLayout->addWidget(formatComboBox, 0, 1);
+ mainLayout->addWidget(scopeLabel, 1, 0);
+ mainLayout->addWidget(scopeComboBox, 1, 1);
+ mainLayout->addWidget(organizationLabel, 2, 0);
+ mainLayout->addWidget(organizationComboBox, 2, 1);
+ mainLayout->addWidget(applicationLabel, 3, 0);
+ mainLayout->addWidget(applicationComboBox, 3, 1);
+ mainLayout->addWidget(locationsGroupBox, 4, 0, 1, 2);
+ mainLayout->addWidget(buttonBox, 5, 0, 1, 2);
+
+ updateLocationsTable();
+
+ setWindowTitle(tr("Open Application Settings"));
+ resize(650, 400);
+}
+
+QSettings::Format LocationDialog::format() const
+{
+ if (formatComboBox->currentIndex() == 0)
+ return QSettings::NativeFormat;
+ else
+ return QSettings::IniFormat;
+}
+
+QSettings::Scope LocationDialog::scope() const
+{
+ if (scopeComboBox->currentIndex() == 0)
+ return QSettings::UserScope;
+ else
+ return QSettings::SystemScope;
+}
+
+QString LocationDialog::organization() const
+{
+ return organizationComboBox->currentText();
+}
+
+QString LocationDialog::application() const
+{
+ if (applicationComboBox->currentText() == tr("Any"))
+ return QString();
+ else
+ return applicationComboBox->currentText();
+}
+
+void LocationDialog::itemActivated(QTableWidgetItem *)
+{
+ buttonBox->button(QDialogButtonBox::Ok)->animateClick();
+}
+
+void LocationDialog::updateLocationsTable()
+{
+ locationsTable->setUpdatesEnabled(false);
+ locationsTable->setRowCount(0);
+
+ for (int i = 0; i < 2; ++i) {
+ if (i == 0 && scope() == QSettings::SystemScope)
+ continue;
+
+ QSettings::Scope actualScope = (i == 0) ? QSettings::UserScope
+ : QSettings::SystemScope;
+ for (int j = 0; j < 2; ++j) {
+ if (j == 0 && application().isEmpty())
+ continue;
+
+ QString actualApplication;
+ if (j == 0)
+ actualApplication = application();
+ QSettings settings(format(), actualScope, organization(),
+ actualApplication);
+
+ int row = locationsTable->rowCount();
+ locationsTable->setRowCount(row + 1);
+
+ QTableWidgetItem *item0 = new QTableWidgetItem(QDir::toNativeSeparators(settings.fileName()));
+
+ QTableWidgetItem *item1 = new QTableWidgetItem;
+ bool disable = (settings.childKeys().isEmpty()
+ && settings.childGroups().isEmpty());
+
+ if (row == 0) {
+ if (settings.isWritable()) {
+ item1->setText(tr("Read-write"));
+ disable = false;
+ } else {
+ item1->setText(tr("Read-only"));
+ }
+ buttonBox->button(QDialogButtonBox::Ok)->setDisabled(disable);
+ } else {
+ item1->setText(tr("Read-only fallback"));
+ }
+
+ if (disable) {
+ item0->setFlags(item0->flags() & ~Qt::ItemIsEnabled);
+ item1->setFlags(item1->flags() & ~Qt::ItemIsEnabled);
+ }
+
+ locationsTable->setItem(row, 0, item0);
+ locationsTable->setItem(row, 1, item1);
+ }
+ }
+ locationsTable->setUpdatesEnabled(true);
+}
diff --git a/tests/manual/examples/widgets/tools/settingseditor/locationdialog.h b/tests/manual/examples/widgets/tools/settingseditor/locationdialog.h
new file mode 100644
index 0000000000..4bcef76ce7
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/settingseditor/locationdialog.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef LOCATIONDIALOG_H
+#define LOCATIONDIALOG_H
+
+#include <QDialog>
+#include <QSettings>
+
+QT_BEGIN_NAMESPACE
+class QComboBox;
+class QDialogButtonBox;
+class QGroupBox;
+class QLabel;
+class QTableWidget;
+class QTableWidgetItem;
+QT_END_NAMESPACE
+
+class LocationDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ LocationDialog(QWidget *parent = nullptr);
+
+ QSettings::Format format() const;
+ QSettings::Scope scope() const;
+ QString organization() const;
+ QString application() const;
+
+private slots:
+ void updateLocationsTable();
+ void itemActivated(QTableWidgetItem *);
+
+private:
+ QLabel *formatLabel;
+ QLabel *scopeLabel;
+ QLabel *organizationLabel;
+ QLabel *applicationLabel;
+ QComboBox *formatComboBox;
+ QComboBox *scopeComboBox;
+ QComboBox *organizationComboBox;
+ QComboBox *applicationComboBox;
+ QGroupBox *locationsGroupBox;
+ QTableWidget *locationsTable;
+ QDialogButtonBox *buttonBox;
+};
+
+#endif
diff --git a/tests/manual/examples/widgets/tools/settingseditor/main.cpp b/tests/manual/examples/widgets/tools/settingseditor/main.cpp
new file mode 100644
index 0000000000..f49701be5c
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/settingseditor/main.cpp
@@ -0,0 +1,17 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QApplication>
+
+#include "mainwindow.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+ QCoreApplication::setApplicationName("Settings Editor");
+ QCoreApplication::setApplicationVersion(QT_VERSION_STR);
+
+ MainWindow mainWin;
+ mainWin.show();
+ return app.exec();
+}
diff --git a/tests/manual/examples/widgets/tools/settingseditor/mainwindow.cpp b/tests/manual/examples/widgets/tools/settingseditor/mainwindow.cpp
new file mode 100644
index 0000000000..be9f19e8cc
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/settingseditor/mainwindow.cpp
@@ -0,0 +1,175 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "locationdialog.h"
+#include "mainwindow.h"
+#include "settingstree.h"
+
+#include <QAction>
+#include <QApplication>
+#include <QFileDialog>
+#include <QInputDialog>
+#include <QLineEdit>
+#include <QMenuBar>
+#include <QMessageBox>
+#include <QScreen>
+#include <QStandardPaths>
+#include <QStatusBar>
+
+MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
+ , settingsTree(new SettingsTree)
+{
+ setCentralWidget(settingsTree);
+
+ createActions();
+
+ autoRefreshAct->setChecked(true);
+ fallbacksAct->setChecked(true);
+
+ setWindowTitle(QCoreApplication::applicationName());
+ const QRect availableGeometry = screen()->availableGeometry();
+ adjustSize();
+ move((availableGeometry.width() - width()) / 2, (availableGeometry.height() - height()) / 2);
+}
+
+void MainWindow::openSettings()
+{
+ if (!locationDialog)
+ locationDialog = new LocationDialog(this);
+
+ if (locationDialog->exec() != QDialog::Accepted)
+ return;
+
+ SettingsPtr settings(new QSettings(locationDialog->format(),
+ locationDialog->scope(),
+ locationDialog->organization(),
+ locationDialog->application()));
+
+ setSettingsObject(settings);
+ fallbacksAct->setEnabled(true);
+}
+
+void MainWindow::openIniFile()
+{
+ const QString directory = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
+ const QString fileName =
+ QFileDialog::getOpenFileName(this, tr("Open INI File"),
+ directory, tr("INI Files (*.ini *.conf)"));
+ if (fileName.isEmpty())
+ return;
+
+ SettingsPtr settings(new QSettings(fileName, QSettings::IniFormat));
+
+ setSettingsObject(settings);
+ fallbacksAct->setEnabled(false);
+}
+
+void MainWindow::openPropertyList()
+{
+ const QString directory = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
+ const QString fileName =
+ QFileDialog::getOpenFileName(this, tr("Open Property List"),
+ directory, tr("Property List Files (*.plist)"));
+ if (fileName.isEmpty())
+ return;
+
+ SettingsPtr settings(new QSettings(fileName, QSettings::NativeFormat));
+ setSettingsObject(settings);
+ fallbacksAct->setEnabled(false);
+}
+
+void MainWindow::openRegistryPath()
+{
+ const QString path =
+ QInputDialog::getText(this, tr("Open Registry Path"),
+ tr("Enter the path in the Windows registry:"),
+ QLineEdit::Normal, "HKEY_CURRENT_USER\\");
+ if (path.isEmpty())
+ return;
+
+ SettingsPtr settings(new QSettings(path, QSettings::NativeFormat));
+
+ setSettingsObject(settings);
+ fallbacksAct->setEnabled(false);
+}
+
+void MainWindow::about()
+{
+ QMessageBox::about(this, tr("About Settings Editor"),
+ tr("The <b>Settings Editor</b> example shows how to access "
+ "application settings using Qt."));
+}
+
+void MainWindow::createActions()
+{
+ QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
+
+ QAction *openSettingsAct = fileMenu->addAction(tr("&Open Application Settings..."), this, &MainWindow::openSettings);
+ openSettingsAct->setShortcuts(QKeySequence::Open);
+
+ QAction *openIniFileAct = fileMenu->addAction(tr("Open I&NI File..."), this, &MainWindow::openIniFile);
+ openIniFileAct->setShortcut(tr("Ctrl+N"));
+
+#ifdef Q_OS_MACOS
+ QAction *openPropertyListAct = fileMenu->addAction(tr("Open Apple &Property List..."), this, &MainWindow::openPropertyList);
+ openPropertyListAct->setShortcut(tr("Ctrl+P"));
+#endif // Q_OS_MACOS
+
+#ifdef Q_OS_WIN
+ QAction *openRegistryPathAct = fileMenu->addAction(tr("Open Windows &Registry Path..."), this, &MainWindow::openRegistryPath);
+ openRegistryPathAct->setShortcut(tr("Ctrl+G"));
+#endif // Q_OS_WIN
+
+ fileMenu->addSeparator();
+
+ refreshAct = fileMenu->addAction(tr("&Refresh"), settingsTree, &SettingsTree::refresh);
+ refreshAct->setShortcut(tr("Ctrl+R"));
+ refreshAct->setEnabled(false);
+
+ fileMenu->addSeparator();
+
+ QAction *exitAct = fileMenu->addAction(tr("E&xit"), this, &QWidget::close);
+ exitAct->setShortcuts(QKeySequence::Quit);
+
+ QMenu *optionsMenu = menuBar()->addMenu(tr("&Options"));
+
+ autoRefreshAct = optionsMenu->addAction(tr("&Auto-Refresh"));
+ autoRefreshAct->setShortcut(tr("Ctrl+A"));
+ autoRefreshAct->setCheckable(true);
+ autoRefreshAct->setEnabled(false);
+ connect(autoRefreshAct, &QAction::triggered,
+ settingsTree, &SettingsTree::setAutoRefresh);
+ connect(autoRefreshAct, &QAction::triggered,
+ refreshAct, &QAction::setDisabled);
+
+ fallbacksAct = optionsMenu->addAction(tr("&Fallbacks"));
+ fallbacksAct->setShortcut(tr("Ctrl+F"));
+ fallbacksAct->setCheckable(true);
+ fallbacksAct->setEnabled(false);
+ connect(fallbacksAct, &QAction::triggered,
+ settingsTree, &SettingsTree::setFallbacksEnabled);
+
+ QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
+ helpMenu->addAction(tr("&About"), this, &MainWindow::about);
+ helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
+}
+
+void MainWindow::setSettingsObject(const SettingsPtr &settings)
+{
+ settings->setFallbacksEnabled(fallbacksAct->isChecked());
+ settingsTree->setSettingsObject(settings);
+
+ refreshAct->setEnabled(true);
+ autoRefreshAct->setEnabled(true);
+
+ QString niceName = QDir::cleanPath(settings->fileName());
+ int pos = niceName.lastIndexOf(QLatin1Char('/'));
+ if (pos != -1)
+ niceName.remove(0, pos + 1);
+
+ if (!settings->isWritable())
+ niceName = tr("%1 (read only)").arg(niceName);
+
+ setWindowTitle(tr("%1 - %2").arg(niceName, QCoreApplication::applicationName()));
+ statusBar()->showMessage(tr("Opened \"%1\"").arg(QDir::toNativeSeparators(settings->fileName())));
+}
diff --git a/tests/manual/examples/widgets/tools/settingseditor/mainwindow.h b/tests/manual/examples/widgets/tools/settingseditor/mainwindow.h
new file mode 100644
index 0000000000..84bdaef966
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/settingseditor/mainwindow.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+#include <QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+class QAction;
+class QSettings;
+QT_END_NAMESPACE
+class LocationDialog;
+class SettingsTree;
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ typedef QSharedPointer<QSettings> SettingsPtr;
+
+ MainWindow(QWidget *parent = nullptr);
+
+private slots:
+ void openSettings();
+ void openIniFile();
+ void openPropertyList();
+ void openRegistryPath();
+ void about();
+
+private:
+ void createActions();
+ void setSettingsObject(const SettingsPtr &settings);
+
+ SettingsTree *settingsTree = nullptr;
+ LocationDialog *locationDialog = nullptr;
+ QAction *refreshAct = nullptr;
+ QAction *autoRefreshAct = nullptr;
+ QAction *fallbacksAct = nullptr;
+};
+
+#endif
diff --git a/tests/manual/examples/widgets/tools/settingseditor/settingseditor.pro b/tests/manual/examples/widgets/tools/settingseditor/settingseditor.pro
new file mode 100644
index 0000000000..4880b7e582
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/settingseditor/settingseditor.pro
@@ -0,0 +1,18 @@
+QT += widgets
+requires(qtConfig(tablewidget))
+
+HEADERS = locationdialog.h \
+ mainwindow.h \
+ settingstree.h \
+ variantdelegate.h
+SOURCES = locationdialog.cpp \
+ main.cpp \
+ mainwindow.cpp \
+ settingstree.cpp \
+ variantdelegate.cpp
+
+EXAMPLE_FILES = inifiles
+
+# install
+target.path = $$[QT_INSTALL_EXAMPLES]/widgets/tools/settingseditor
+INSTALLS += target
diff --git a/tests/manual/examples/widgets/tools/settingseditor/settingstree.cpp b/tests/manual/examples/widgets/tools/settingseditor/settingstree.cpp
new file mode 100644
index 0000000000..5de2a8cff1
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/settingseditor/settingstree.cpp
@@ -0,0 +1,231 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "settingstree.h"
+#include "variantdelegate.h"
+
+#include <QApplication>
+#include <QHeaderView>
+#include <QScreen>
+#include <QSettings>
+
+SettingsTree::SettingsTree(QWidget *parent)
+ : QTreeWidget(parent),
+ m_typeChecker(new TypeChecker)
+{
+ setItemDelegate(new VariantDelegate(m_typeChecker, this));
+
+ setHeaderLabels({tr("Setting"), tr("Type"), tr("Value")});
+ header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
+ header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
+ header()->setSectionResizeMode(2, QHeaderView::Stretch);
+
+ refreshTimer.setInterval(2000);
+
+ groupIcon.addPixmap(style()->standardPixmap(QStyle::SP_DirClosedIcon),
+ QIcon::Normal, QIcon::Off);
+ groupIcon.addPixmap(style()->standardPixmap(QStyle::SP_DirOpenIcon),
+ QIcon::Normal, QIcon::On);
+ keyIcon.addPixmap(style()->standardPixmap(QStyle::SP_FileIcon));
+
+ connect(&refreshTimer, &QTimer::timeout, this, &SettingsTree::maybeRefresh);
+}
+
+SettingsTree::~SettingsTree() = default;
+
+void SettingsTree::setSettingsObject(const SettingsPtr &newSettings)
+{
+ settings = newSettings;
+ clear();
+
+ if (settings.isNull()) {
+ refreshTimer.stop();
+ } else {
+ refresh();
+ if (autoRefresh)
+ refreshTimer.start();
+ }
+}
+
+QSize SettingsTree::sizeHint() const
+{
+ const QRect availableGeometry = screen()->availableGeometry();
+ return QSize(availableGeometry.width() * 2 / 3, availableGeometry.height() * 2 / 3);
+}
+
+void SettingsTree::setAutoRefresh(bool autoRefresh)
+{
+ this->autoRefresh = autoRefresh;
+ if (!settings.isNull()) {
+ if (autoRefresh) {
+ maybeRefresh();
+ refreshTimer.start();
+ } else {
+ refreshTimer.stop();
+ }
+ }
+}
+
+void SettingsTree::setFallbacksEnabled(bool enabled)
+{
+ if (!settings.isNull()) {
+ settings->setFallbacksEnabled(enabled);
+ refresh();
+ }
+}
+
+void SettingsTree::maybeRefresh()
+{
+ if (state() != EditingState)
+ refresh();
+}
+
+void SettingsTree::refresh()
+{
+ if (settings.isNull())
+ return;
+
+ disconnect(this, &QTreeWidget::itemChanged,
+ this, &SettingsTree::updateSetting);
+
+ settings->sync();
+ updateChildItems(nullptr);
+
+ connect(this, &QTreeWidget::itemChanged,
+ this, &SettingsTree::updateSetting);
+}
+
+bool SettingsTree::event(QEvent *event)
+{
+ if (event->type() == QEvent::WindowActivate) {
+ if (isActiveWindow() && autoRefresh)
+ maybeRefresh();
+ }
+ return QTreeWidget::event(event);
+}
+
+void SettingsTree::updateSetting(QTreeWidgetItem *item)
+{
+ QString key = item->text(0);
+ QTreeWidgetItem *ancestor = item->parent();
+ while (ancestor) {
+ key.prepend(ancestor->text(0) + QLatin1Char('/'));
+ ancestor = ancestor->parent();
+ }
+
+ settings->setValue(key, item->data(2, Qt::UserRole));
+ if (autoRefresh)
+ refresh();
+}
+
+void SettingsTree::updateChildItems(QTreeWidgetItem *parent)
+{
+ int dividerIndex = 0;
+
+ const QStringList childGroups = settings->childGroups();
+ for (const QString &group : childGroups) {
+ QTreeWidgetItem *child;
+ int childIndex = findChild(parent, group, dividerIndex);
+ if (childIndex != -1) {
+ child = childAt(parent, childIndex);
+ child->setText(1, QString());
+ child->setText(2, QString());
+ child->setData(2, Qt::UserRole, QVariant());
+ moveItemForward(parent, childIndex, dividerIndex);
+ } else {
+ child = createItem(group, parent, dividerIndex);
+ }
+ child->setIcon(0, groupIcon);
+ ++dividerIndex;
+
+ settings->beginGroup(group);
+ updateChildItems(child);
+ settings->endGroup();
+ }
+
+ const QStringList childKeys = settings->childKeys();
+ for (const QString &key : childKeys) {
+ QTreeWidgetItem *child;
+ int childIndex = findChild(parent, key, 0);
+
+ if (childIndex == -1 || childIndex >= dividerIndex) {
+ if (childIndex != -1) {
+ child = childAt(parent, childIndex);
+ for (int i = 0; i < child->childCount(); ++i)
+ delete childAt(child, i);
+ moveItemForward(parent, childIndex, dividerIndex);
+ } else {
+ child = createItem(key, parent, dividerIndex);
+ }
+ child->setIcon(0, keyIcon);
+ ++dividerIndex;
+ } else {
+ child = childAt(parent, childIndex);
+ }
+
+ QVariant value = settings->value(key);
+ if (value.userType() == QMetaType::UnknownType) {
+ child->setText(1, "Invalid");
+ } else {
+ if (value.typeId() == QMetaType::QString) {
+ const QString stringValue = value.toString();
+ if (m_typeChecker->boolExp.match(stringValue).hasMatch()) {
+ value.setValue(stringValue.compare("true", Qt::CaseInsensitive) == 0);
+ } else if (m_typeChecker->signedIntegerExp.match(stringValue).hasMatch())
+ value.setValue(stringValue.toInt());
+ }
+
+ child->setText(1, value.typeName());
+ }
+ child->setText(2, VariantDelegate::displayText(value));
+ child->setData(2, Qt::UserRole, value);
+ }
+
+ while (dividerIndex < childCount(parent))
+ delete childAt(parent, dividerIndex);
+}
+
+QTreeWidgetItem *SettingsTree::createItem(const QString &text,
+ QTreeWidgetItem *parent, int index)
+{
+ QTreeWidgetItem *after = nullptr;
+ if (index != 0)
+ after = childAt(parent, index - 1);
+
+ QTreeWidgetItem *item;
+ if (parent)
+ item = new QTreeWidgetItem(parent, after);
+ else
+ item = new QTreeWidgetItem(this, after);
+
+ item->setText(0, text);
+ item->setFlags(item->flags() | Qt::ItemIsEditable);
+ return item;
+}
+
+QTreeWidgetItem *SettingsTree::childAt(QTreeWidgetItem *parent, int index) const
+{
+ return (parent ? parent->child(index) : topLevelItem(index));
+}
+
+int SettingsTree::childCount(QTreeWidgetItem *parent) const
+{
+ return (parent ? parent->childCount() : topLevelItemCount());
+}
+
+int SettingsTree::findChild(QTreeWidgetItem *parent, const QString &text,
+ int startIndex) const
+{
+ for (int i = startIndex; i < childCount(parent); ++i) {
+ if (childAt(parent, i)->text(0) == text)
+ return i;
+ }
+ return -1;
+}
+
+void SettingsTree::moveItemForward(QTreeWidgetItem *parent, int oldIndex,
+ int newIndex)
+{
+ for (int i = 0; i < oldIndex - newIndex; ++i)
+ delete childAt(parent, newIndex);
+}
diff --git a/tests/manual/examples/widgets/tools/settingseditor/settingstree.h b/tests/manual/examples/widgets/tools/settingseditor/settingstree.h
new file mode 100644
index 0000000000..8dfa52113f
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/settingseditor/settingstree.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef SETTINGSTREE_H
+#define SETTINGSTREE_H
+
+#include <QIcon>
+#include <QTimer>
+#include <QTreeWidget>
+#include <QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+class QSettings;
+QT_END_NAMESPACE
+
+struct TypeChecker;
+
+class SettingsTree : public QTreeWidget
+{
+ Q_OBJECT
+
+public:
+ using SettingsPtr = QSharedPointer<QSettings>;
+ using TypeCheckerPtr = QSharedPointer<TypeChecker>;
+
+ SettingsTree(QWidget *parent = nullptr);
+ ~SettingsTree();
+
+ void setSettingsObject(const SettingsPtr &settings);
+ QSize sizeHint() const override;
+
+public slots:
+ void setAutoRefresh(bool autoRefresh);
+ void setFallbacksEnabled(bool enabled);
+ void maybeRefresh();
+ void refresh();
+
+protected:
+ bool event(QEvent *event) override;
+
+private slots:
+ void updateSetting(QTreeWidgetItem *item);
+
+private:
+ void updateChildItems(QTreeWidgetItem *parent);
+ QTreeWidgetItem *createItem(const QString &text, QTreeWidgetItem *parent,
+ int index);
+ QTreeWidgetItem *childAt(QTreeWidgetItem *parent, int index) const;
+ int childCount(QTreeWidgetItem *parent) const;
+ int findChild(QTreeWidgetItem *parent, const QString &text, int startIndex) const;
+ void moveItemForward(QTreeWidgetItem *parent, int oldIndex, int newIndex);
+
+ SettingsPtr settings;
+ TypeCheckerPtr m_typeChecker;
+ QTimer refreshTimer;
+ QIcon groupIcon;
+ QIcon keyIcon;
+ bool autoRefresh = false;
+};
+
+#endif
diff --git a/tests/manual/examples/widgets/tools/settingseditor/variantdelegate.cpp b/tests/manual/examples/widgets/tools/settingseditor/variantdelegate.cpp
new file mode 100644
index 0000000000..ed51a1645b
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/settingseditor/variantdelegate.cpp
@@ -0,0 +1,377 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "variantdelegate.h"
+
+#include <QCheckBox>
+#include <QDateTime>
+#include <QLineEdit>
+#include <QSpinBox>
+#include <QRegularExpressionValidator>
+#include <QTextStream>
+
+#include <algorithm>
+
+static bool isPrintableChar(char c)
+{
+ return uchar(c) >= 32 && uchar(c) < 128;
+}
+
+static bool isPrintable(const QByteArray &ba)
+{
+ return std::all_of(ba.cbegin(), ba.cend(), isPrintableChar);
+}
+
+static QString byteArrayToString(const QByteArray &ba)
+{
+ if (isPrintable(ba))
+ return QString::fromLatin1(ba);
+ QString result;
+ for (char c : ba) {
+ if (isPrintableChar(c)) {
+ if (c == '\\')
+ result += QLatin1Char(c);
+ result += QLatin1Char(c);
+ } else {
+ const uint uc = uchar(c);
+ result += "\\x";
+ if (uc < 16)
+ result += '0';
+ result += QString::number(uc, 16);
+ }
+ }
+ return result;
+}
+
+TypeChecker::TypeChecker()
+{
+ boolExp.setPattern("^(true)|(false)$");
+ boolExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
+ Q_ASSERT(boolExp.isValid());
+
+ byteArrayExp.setPattern(R"RX(^[\x00-\xff]*$)RX");
+ charExp.setPattern("^.$");
+ Q_ASSERT(charExp.isValid());
+ colorExp.setPattern(R"RX(^\(([0-9]*),([0-9]*),([0-9]*),([0-9]*)\)$)RX");
+ Q_ASSERT(colorExp.isValid());
+ doubleExp.setPattern("");
+ pointExp.setPattern(R"RX(^\((-?[0-9]*),(-?[0-9]*)\)$)RX");
+ Q_ASSERT(pointExp.isValid());
+ rectExp.setPattern(R"RX(^\((-?[0-9]*),(-?[0-9]*),(-?[0-9]*),(-?[0-9]*)\)$)RX");
+ Q_ASSERT(rectExp.isValid());
+ signedIntegerExp.setPattern("^-?[0-9]*$");
+ Q_ASSERT(signedIntegerExp.isValid());
+ sizeExp = pointExp;
+ unsignedIntegerExp.setPattern("^[0-9]+$");
+ Q_ASSERT(unsignedIntegerExp.isValid());
+
+ const QString datePattern = "([0-9]{,4})-([0-9]{,2})-([0-9]{,2})";
+ dateExp.setPattern('^' + datePattern + '$');
+ Q_ASSERT(dateExp.isValid());
+ const QString timePattern = "([0-9]{,2}):([0-9]{,2}):([0-9]{,2})";
+ timeExp.setPattern('^' + timePattern + '$');
+ Q_ASSERT(timeExp.isValid());
+ dateTimeExp.setPattern('^' + datePattern + 'T' + timePattern + '$');
+ Q_ASSERT(dateTimeExp.isValid());
+}
+
+VariantDelegate::VariantDelegate(const QSharedPointer<TypeChecker> &typeChecker,
+ QObject *parent)
+ : QStyledItemDelegate(parent),
+ m_typeChecker(typeChecker)
+{
+}
+
+void VariantDelegate::paint(QPainter *painter,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ if (index.column() == 2) {
+ QVariant value = index.model()->data(index, Qt::UserRole);
+ if (!isSupportedType(value.userType())) {
+ QStyleOptionViewItem myOption = option;
+ myOption.state &= ~QStyle::State_Enabled;
+ QStyledItemDelegate::paint(painter, myOption, index);
+ return;
+ }
+ }
+
+ QStyledItemDelegate::paint(painter, option, index);
+}
+
+QWidget *VariantDelegate::createEditor(QWidget *parent,
+ const QStyleOptionViewItem & /* option */,
+ const QModelIndex &index) const
+{
+ if (index.column() != 2)
+ return nullptr;
+
+ QVariant originalValue = index.model()->data(index, Qt::UserRole);
+ if (!isSupportedType(originalValue.userType()))
+ return nullptr;
+
+ switch (originalValue.userType()) {
+ case QMetaType::Bool:
+ return new QCheckBox(parent);
+ break;
+ case QMetaType::Int:
+ case QMetaType::LongLong: {
+ auto spinBox = new QSpinBox(parent);
+ spinBox->setRange(-32767, 32767);
+ return spinBox;
+ }
+ case QMetaType::UInt:
+ case QMetaType::ULongLong: {
+ auto spinBox = new QSpinBox(parent);
+ spinBox->setRange(0, 63335);
+ return spinBox;
+ }
+ default:
+ break;
+ }
+
+ QLineEdit *lineEdit = new QLineEdit(parent);
+ lineEdit->setFrame(false);
+
+ QRegularExpression regExp;
+
+ switch (originalValue.userType()) {
+ case QMetaType::Bool:
+ regExp = m_typeChecker->boolExp;
+ break;
+ case QMetaType::QByteArray:
+ regExp = m_typeChecker->byteArrayExp;
+ break;
+ case QMetaType::QChar:
+ regExp = m_typeChecker->charExp;
+ break;
+ case QMetaType::QColor:
+ regExp = m_typeChecker->colorExp;
+ break;
+ case QMetaType::QDate:
+ regExp = m_typeChecker->dateExp;
+ break;
+ case QMetaType::QDateTime:
+ regExp = m_typeChecker->dateTimeExp;
+ break;
+ case QMetaType::Double:
+ regExp = m_typeChecker->doubleExp;
+ break;
+ case QMetaType::Int:
+ case QMetaType::LongLong:
+ regExp = m_typeChecker->signedIntegerExp;
+ break;
+ case QMetaType::QPoint:
+ regExp = m_typeChecker->pointExp;
+ break;
+ case QMetaType::QRect:
+ regExp = m_typeChecker->rectExp;
+ break;
+ case QMetaType::QSize:
+ regExp = m_typeChecker->sizeExp;
+ break;
+ case QMetaType::QTime:
+ regExp = m_typeChecker->timeExp;
+ break;
+ case QMetaType::UInt:
+ case QMetaType::ULongLong:
+ regExp = m_typeChecker->unsignedIntegerExp;
+ break;
+ default:
+ break;
+ }
+
+ if (regExp.isValid()) {
+ QValidator *validator = new QRegularExpressionValidator(regExp, lineEdit);
+ lineEdit->setValidator(validator);
+ }
+
+ return lineEdit;
+}
+
+void VariantDelegate::setEditorData(QWidget *editor,
+ const QModelIndex &index) const
+{
+ QVariant value = index.model()->data(index, Qt::UserRole);
+ if (auto spinBox = qobject_cast<QSpinBox *>(editor)) {
+ const auto userType = value.userType();
+ if (userType == QMetaType::UInt || userType == QMetaType::ULongLong)
+ spinBox->setValue(value.toUInt());
+ else
+ spinBox->setValue(value.toInt());
+ } else if (auto checkBox = qobject_cast<QCheckBox *>(editor)) {
+ checkBox->setChecked(value.toBool());
+ } else if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor)) {
+ if (value.userType() == QMetaType::QByteArray
+ && !isPrintable(value.toByteArray())) {
+ lineEdit->setReadOnly(true);
+ }
+ lineEdit->setText(displayText(value));
+ }
+}
+
+void VariantDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const
+{
+ const QVariant originalValue = index.model()->data(index, Qt::UserRole);
+ QVariant value;
+
+ if (auto spinBox = qobject_cast<QSpinBox *>(editor)) {
+ value.setValue(spinBox->value());
+ } else if (auto checkBox = qobject_cast<QCheckBox *>(editor)) {
+ value.setValue(checkBox->isChecked());
+ } else if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor)) {
+ if (!lineEdit->isModified())
+ return;
+
+ QString text = lineEdit->text();
+ const QValidator *validator = lineEdit->validator();
+ if (validator) {
+ int pos;
+ if (validator->validate(text, pos) != QValidator::Acceptable)
+ return;
+ }
+
+ QRegularExpressionMatch match;
+
+ switch (originalValue.userType()) {
+ case QMetaType::QChar:
+ value = text.at(0);
+ break;
+ case QMetaType::QColor:
+ match = m_typeChecker->colorExp.match(text);
+ value = QColor(qMin(match.captured(1).toInt(), 255),
+ qMin(match.captured(2).toInt(), 255),
+ qMin(match.captured(3).toInt(), 255),
+ qMin(match.captured(4).toInt(), 255));
+ break;
+ case QMetaType::QDate:
+ {
+ QDate date = QDate::fromString(text, Qt::ISODate);
+ if (!date.isValid())
+ return;
+ value = date;
+ }
+ break;
+ case QMetaType::QDateTime:
+ {
+ QDateTime dateTime = QDateTime::fromString(text, Qt::ISODate);
+ if (!dateTime.isValid())
+ return;
+ value = dateTime;
+ }
+ break;
+ case QMetaType::QPoint:
+ match = m_typeChecker->pointExp.match(text);
+ value = QPoint(match.captured(1).toInt(), match.captured(2).toInt());
+ break;
+ case QMetaType::QRect:
+ match = m_typeChecker->rectExp.match(text);
+ value = QRect(match.captured(1).toInt(), match.captured(2).toInt(),
+ match.captured(3).toInt(), match.captured(4).toInt());
+ break;
+ case QMetaType::QSize:
+ match = m_typeChecker->sizeExp.match(text);
+ value = QSize(match.captured(1).toInt(), match.captured(2).toInt());
+ break;
+ case QMetaType::QStringList:
+ value = text.split(',');
+ break;
+ case QMetaType::QTime:
+ {
+ QTime time = QTime::fromString(text, Qt::ISODate);
+ if (!time.isValid())
+ return;
+ value = time;
+ }
+ break;
+ default:
+ value = text;
+ value.convert(originalValue.metaType());
+ }
+ }
+
+ model->setData(index, displayText(value), Qt::DisplayRole);
+ model->setData(index, value, Qt::UserRole);
+}
+
+bool VariantDelegate::isSupportedType(int type)
+{
+ switch (type) {
+ case QMetaType::Bool:
+ case QMetaType::QByteArray:
+ case QMetaType::QChar:
+ case QMetaType::QColor:
+ case QMetaType::QDate:
+ case QMetaType::QDateTime:
+ case QMetaType::Double:
+ case QMetaType::Int:
+ case QMetaType::LongLong:
+ case QMetaType::QPoint:
+ case QMetaType::QRect:
+ case QMetaType::QSize:
+ case QMetaType::QString:
+ case QMetaType::QStringList:
+ case QMetaType::QTime:
+ case QMetaType::UInt:
+ case QMetaType::ULongLong:
+ return true;
+ default:
+ return false;
+ }
+}
+
+QString VariantDelegate::displayText(const QVariant &value)
+{
+ switch (value.userType()) {
+ case QMetaType::Bool:
+ return value.toBool() ? "✓" : "☐";
+ case QMetaType::QByteArray:
+ return byteArrayToString(value.toByteArray());
+ case QMetaType::QChar:
+ case QMetaType::Double:
+ case QMetaType::Int:
+ case QMetaType::LongLong:
+ case QMetaType::QString:
+ case QMetaType::UInt:
+ case QMetaType::ULongLong:
+ return value.toString();
+ case QMetaType::QColor:
+ {
+ QColor color = qvariant_cast<QColor>(value);
+ return QString("(%1,%2,%3,%4)")
+ .arg(color.red()).arg(color.green())
+ .arg(color.blue()).arg(color.alpha());
+ }
+ case QMetaType::QDate:
+ return value.toDate().toString(Qt::ISODate);
+ case QMetaType::QDateTime:
+ return value.toDateTime().toString(Qt::ISODate);
+ case QMetaType::UnknownType:
+ return "<Invalid>";
+ case QMetaType::QPoint:
+ {
+ QPoint point = value.toPoint();
+ return QString("(%1,%2)").arg(point.x()).arg(point.y());
+ }
+ case QMetaType::QRect:
+ {
+ QRect rect = value.toRect();
+ return QString("(%1,%2,%3,%4)")
+ .arg(rect.x()).arg(rect.y())
+ .arg(rect.width()).arg(rect.height());
+ }
+ case QMetaType::QSize:
+ {
+ QSize size = value.toSize();
+ return QString("(%1,%2)").arg(size.width()).arg(size.height());
+ }
+ case QMetaType::QStringList:
+ return value.toStringList().join(',');
+ case QMetaType::QTime:
+ return value.toTime().toString(Qt::ISODate);
+ default:
+ break;
+ }
+ return QString("<%1>").arg(value.typeName());
+}
diff --git a/tests/manual/examples/widgets/tools/settingseditor/variantdelegate.h b/tests/manual/examples/widgets/tools/settingseditor/variantdelegate.h
new file mode 100644
index 0000000000..dc06d51bbc
--- /dev/null
+++ b/tests/manual/examples/widgets/tools/settingseditor/variantdelegate.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef VARIANTDELEGATE_H
+#define VARIANTDELEGATE_H
+
+#include <QStyledItemDelegate>
+#include <QRegularExpression>
+#include <QSharedPointer>
+
+struct TypeChecker
+{
+ TypeChecker();
+
+ QRegularExpression boolExp;
+ QRegularExpression byteArrayExp;
+ QRegularExpression charExp;
+ QRegularExpression colorExp;
+ QRegularExpression dateExp;
+ QRegularExpression dateTimeExp;
+ QRegularExpression doubleExp;
+ QRegularExpression pointExp;
+ QRegularExpression rectExp;
+ QRegularExpression signedIntegerExp;
+ QRegularExpression sizeExp;
+ QRegularExpression timeExp;
+ QRegularExpression unsignedIntegerExp;
+};
+
+class VariantDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+public:
+ explicit VariantDelegate(const QSharedPointer<TypeChecker> &typeChecker,
+ QObject *parent = nullptr);
+
+ void paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+ QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+ void setEditorData(QWidget *editor, const QModelIndex &index) const override;
+ void setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const override;
+
+ static bool isSupportedType(int type);
+ static QString displayText(const QVariant &value);
+
+private:
+ QSharedPointer<TypeChecker> m_typeChecker;
+};
+
+#endif