diff options
Diffstat (limited to 'tests/manual/examples/widgets/tools')
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 |