Move plug and paint example to manual test

Pick-to: 6.5 6.6
Change-Id: Ibfd870f2f879d6ae68cd6806b0c1ab02da0a3441
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Tor Arne Vestbø 2023-06-26 15:06:29 +02:00
parent 921337f98c
commit 9c71b92430
33 changed files with 5 additions and 557 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -175,7 +175,5 @@
create plugins.
We give an example of a plugin that extends Qt in the \l{Style
Plugin Example}{style plugin} example. The \l{Plug & Paint
Example}{plug and paint} example shows how to create static
plugins.
Plugin Example}{style plugin} example.
*/

View File

@ -1,526 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example tools/plugandpaint/app
\title Plug & Paint Example
\ingroup examples-widgets-tools
\brief Demonstrates how to extend Qt applications using plugins.
\image plugandpaint.png Screenshot of the Plug & Paint example
A plugin is a dynamic library that can be loaded at run-time to
extend an application. Qt makes it possible to create custom
plugins and to load them using QPluginLoader. To ensure that
plugins don't get lost, it is also possible to link them
statically to the executable. The Plug & Paint example uses
plugins to support custom brushes, shapes, and image filters. A
single plugin can provide multiple brushes, shapes, and/or
filters.
If you want to learn how to make your own application extensible
through plugins, we recommend that you start by reading this
overview, which explains how to make an application use plugins.
Afterwards, you can read the
\l{tools/plugandpaint/plugins/basictools}{Basic Tools} and
\l{tools/plugandpaint/plugins/extrafilters}{Extra Filters}
overviews, which show how to implement static and dynamic
plugins, respectively.
Plug & Paint consists of the following classes:
\list
\li \c MainWindow is a QMainWindow subclass that provides the menu
system and that contains a \c PaintArea as the central widget.
\li \c PaintArea is a QWidget that allows the user to draw using a
brush and to insert shapes.
\li \c PluginDialog is a dialog that shows information about the
plugins detected by the application.
\li \c BrushInterface, \c ShapeInterface, and \c FilterInterface are
abstract base classes that can be implemented by plugins to
provide custom brushes, shapes, and image filters.
\endlist
\section1 The Plugin Interfaces
We will start by reviewing the interfaces defined in \c
interfaces.h. These interfaces are used by the Plug & Paint
application to access extra functionality. They are implemented
in the plugins.
\snippet tools/plugandpaint/app/interfaces.h 0
The \c BrushInterface class declares four pure virtual functions.
The first pure virtual function, \c brushes(), returns a list of
strings that identify the brushes provided by the plugin. By
returning a QStringList instead of a QString, we make it possible
for a single plugin to provide multiple brushes. The other
functions have a \c brush parameter to identify which brush
(among those returned by \c brushes()) is used.
\c mousePress(), \c mouseMove(), and \c mouseRelease() take a
QPainter and one or two \l{QPoint}s, and return a QRect
identifying which portion of the image was altered by the brush.
The class also has a virtual destructor. Interface classes
usually don't need such a destructor (because it would make
little sense to \c delete the object that implements the
interface through a pointer to the interface), but some compilers
emit a warning for classes that declare virtual functions but no
virtual destructor. We provide the destructor to keep these
compilers happy.
\snippet tools/plugandpaint/app/interfaces.h 1
The \c ShapeInterface class declares a \c shapes() function that
works the same as \c{BrushInterface}'s \c brushes() function, and
a \c generateShape() function that has a \c shape parameter.
Shapes are represented by a QPainterPath, a data type that can
represent arbitrary 2D shapes or combinations of shapes. The \c
parent parameter can be used by the plugin to pop up a dialog
asking the user to specify more information.
\snippet tools/plugandpaint/app/interfaces.h 2
The \c FilterInterface class declares a \c filters() function
that returns a list of filter names, and a \c filterImage()
function that applies a filter to an image.
\snippet tools/plugandpaint/app/interfaces.h 4
To make it possible to query at run-time whether a plugin
implements a given interface, we must use the \c
Q_DECLARE_INTERFACE() macro. The first argument is the name of
the interface. The second argument is a string identifying the
interface in a unique way. By convention, we use a "Java package
name" syntax to identify interfaces. If we later change the
interfaces, we must use a different string to identify the new
interface; otherwise, the application might crash. It is therefore
a good idea to include a version number in the string, as we did
above.
The \l{tools/plugandpaint/plugins/basictools}{Basic Tools} plugin
and the \l{tools/plugandpaint/plugins/extrafilters}{Extra Filters}
plugin shows how to derive from \c BrushInterface, \c
ShapeInterface, and \c FilterInterface.
A note on naming: It might have been tempting to give the \c
brushes(), \c shapes(), and \c filters() functions a more generic
name, such as \c keys() or \c features(). However, that would
have made multiple inheritance impractical. When creating
interfaces, we should always try to give unique names to the pure
virtual functions.
\section1 The MainWindow Class
The \c MainWindow class is a standard QMainWindow subclass, as
found in many of the other examples. Here, we'll
concentrate on the parts of the code that are related to plugins.
\snippet tools/plugandpaint/app/mainwindow.cpp 4
The \c loadPlugins() function is called from the \c MainWindow
constructor to detect plugins and update the \uicontrol{Brush},
\uicontrol{Shapes}, and \uicontrol{Filters} menus. We start by handling static
plugins (available through QPluginLoader::staticInstances())
To the application that uses the plugin, a Qt plugin is simply a
QObject. That QObject implements plugin interfaces using multiple
inheritance.
\snippet tools/plugandpaint/app/mainwindow.cpp 5
The next step is to load dynamic plugins. We initialize the \c
pluginsDir member variable to refer to the \c plugins
subdirectory of the Plug & Paint example. On Unix, this is just a
matter of initializing the QDir variable with
QApplication::applicationDirPath(), the path of the executable
file, and to do a \l{QDir::cd()}{cd()}. On Windows and \macos,
this file is usually located in a subdirectory, so we need to
take this into account.
\snippet tools/plugandpaint/app/mainwindow.cpp 6
\snippet tools/plugandpaint/app/mainwindow.cpp 7
\snippet tools/plugandpaint/app/mainwindow.cpp 8
We use QDir::entryList() to get a list of all files in that
directory. Then we iterate over the result using a range-based for loop
and try to load the plugin using QPluginLoader.
The QObject provided by the plugin is accessible through
QPluginLoader::instance(). If the dynamic library isn't a Qt
plugin, or if it was compiled against an incompatible version of
the Qt library, QPluginLoader::instance() returns a null pointer.
If QPluginLoader::instance() is non-null, we add it to the menus.
\snippet tools/plugandpaint/app/mainwindow.cpp 9
At the end, we enable or disable the \uicontrol{Brush}, \uicontrol{Shapes},
and \uicontrol{Filters} menus based on whether they contain any items.
\snippet tools/plugandpaint/app/mainwindow.cpp 10
For each plugin (static or dynamic), we check which interfaces it
implements using \l qobject_cast(). First, we try to cast the
plugin instance to a \c BrushInterface; if it works, we call the
private function \c addToMenu() with the list of brushes returned
by \c brushes(). Then we do the same with the \c ShapeInterface
and the \c FilterInterface.
\snippet tools/plugandpaint/app/mainwindow.cpp 3
The \c aboutPlugins() slot is called on startup and can be
invoked at any time through the \uicontrol{About Plugins} action. It
pops up a \c PluginDialog, providing information about the loaded
plugins.
\image plugandpaint-plugindialog.png Screenshot of the Plugin dialog
The \c addToMenu() function is called from \c loadPlugin() to
create \l{QAction}s for custom brushes, shapes, or filters and
add them to the relevant menu. The QAction is created with the
plugin from which it comes from as the parent; this makes it
convenient to get access to the plugin later.
\snippet tools/plugandpaint/app/mainwindow.cpp 0
The \c changeBrush() slot is invoked when the user chooses one of
the brushes from the \uicontrol{Brush} menu. We start by finding out
which action invoked the slot using QObject::sender(). Then we
get the \c BrushInterface out of the plugin (which we
conveniently passed as the QAction's parent) and we call \c
PaintArea::setBrush() with the \c BrushInterface and the string
identifying the brush. Next time the user draws on the paint
area, \c PaintArea will use this brush.
\snippet tools/plugandpaint/app/mainwindow.cpp 1
The \c insertShape() is invoked when the use chooses one of the
shapes from the \uicontrol{Shapes} menu. We retrieve the QAction that
invoked the slot, then the \c ShapeInterface associated with that
QAction, and finally we call \c ShapeInterface::generateShape()
to obtain a QPainterPath.
\snippet tools/plugandpaint/app/mainwindow.cpp 2
The \c applyFilter() slot is similar: We retrieve the QAction
that invoked the slot, then the \c FilterInterface associated to
that QAction, and finally we call \c
FilterInterface::filterImage() to apply the filter onto the
current image.
\section1 The PaintArea Class
The \c PaintArea class contains some code that deals with \c
BrushInterface, so we'll review it briefly.
\snippet tools/plugandpaint/app/paintarea.cpp 0
In \c setBrush(), we simply store the \c BrushInterface and the
brush that are given to us by \c MainWindow.
\snippet tools/plugandpaint/app/paintarea.cpp 1
In the \l{QWidget::mouseMoveEvent()}{mouse move event handler},
we call the \c BrushInterface::mouseMove() function on the
current \c BrushInterface, with the current brush. The mouse
press and mouse release handlers are very similar.
\section1 The PluginDialog Class
The \c PluginDialog class provides information about the loaded
plugins to the user. Its constructor takes a path to the plugins
and a list of plugin file names. It calls \c findPlugins()
to fill the QTreeWdiget with information about the plugins:
\snippet tools/plugandpaint/app/plugindialog.cpp 0
The \c findPlugins() is very similar to \c
MainWindow::loadPlugins(). It uses QPluginLoader to access the
static and dynamic plugins. Its helper function \c
populateTreeWidget() uses \l qobject_cast() to find out which
interfaces are implemented by the plugins:
\snippet tools/plugandpaint/app/plugindialog.cpp 1
\section1 Importing Static Plugins
The \l{tools/plugandpaint/plugins/basictools}{Basic Tools} plugin
is built as a static plugin, to ensure that it is always
available to the application. This requires using the
Q_IMPORT_PLUGIN() macro somewhere in the application (in a \c
.cpp file) and specifying the plugin in the \c .pro file.
For Plug & Paint, we have chosen to put Q_IMPORT_PLUGIN() in \c
main.cpp:
\snippet tools/plugandpaint/app/main.cpp 0
The argument to Q_IMPORT_PLUGIN() is the plugin name, which corresponds
with the name of the class that declares metadata for the plugin with
Q_PLUGIN_METADATA().
In the \c .pro file, we need to specify the static library.
Here's the project file for building Plug & Paint:
\snippet tools/plugandpaint/app/app.pro 0
The \c LIBS line variable specifies the library \c pnp_basictools
located in the \c ../plugandpaint/plugins/basictools directory.
(Although the \c LIBS syntax has a distinct Unix flavor, \c qmake
supports it on all platforms.)
The \c CONFIG() code at the end is necessary for this example
because the example is part of the Qt distribution and Qt can be
configured to be built simultaneously in debug and in release
modes. You don't need to for your own plugin applications.
This completes our review of the Plug & Paint application. At
this point, you might want to take a look at the
\l{tools/plugandpaint/plugins/basictools}{Basic Tools} example
plugin.
*/
/*!
\example tools/plugandpaint/plugins/basictools
\title Plug & Paint Basic Tools Example
\brief A plugin providing the basic tools for painting functionality.
\image plugandpaint.png Screenshot of the Plug & Paint example
The Basic Tools example is a static plugin for the
\l{tools/plugandpaint/app}{Plug & Paint} example. It provides a set
of basic brushes, shapes, and filters. Through the Basic Tools
example, we will review the four steps involved in writing a Qt
plugin:
\list 1
\li Declare a plugin class.
\li Implement the interfaces provided by the plugin.
\li Export the plugin using the Q_PLUGIN_METADATA() macro.
\li Build the plugin using an adequate \c .pro file.
\endlist
\section1 Declaration of the Plugin Class
\snippet tools/plugandpaint/plugins/basictools/basictoolsplugin.h 0
We start by including \c interfaces.h, which defines the plugin
interfaces for the \l{tools/plugandpaint/app}{Plug & Paint}
application. For the \c #include to work, we need to add an \c
INCLUDEPATH entry to the \c .pro file with the path to the
header file.
The \c BasicToolsPlugin class is a QObject subclass that
implements the \c BrushInterface, the \c ShapeInterface, and the
\c FilterInterface. This is done through multiple inheritance.
The \c Q_INTERFACES() macro is necessary to tell \l{moc}, Qt's
meta-object compiler, that the base classes are plugin
interfaces. Without the \c Q_INTERFACES() macro, we couldn't use
\l qobject_cast() in the \l{tools/plugandpaint/app}{Plug & Paint}
application to detect interfaces.
For an explanation for the \c Q_PLUGIN_METADATA() macro see
\l {Exporting the Plugin}.
\snippet tools/plugandpaint/plugins/basictools/basictoolsplugin.h 2
In the \c public section of the class, we declare all the
functions from the three interfaces.
\section1 Implementation of the Brush Interface
Let's now review the implementation of the \c BasicToolsPlugin
member functions inherited from \c BrushInterface.
\snippet tools/plugandpaint/plugins/basictools/basictoolsplugin.cpp 0
The \c brushes() function returns a list of brushes provided by
this plugin. We provide three brushes: \uicontrol{Pencil}, \uicontrol{Air
Brush}, and \uicontrol{Random Letters}.
\snippet tools/plugandpaint/plugins/basictools/basictoolsplugin.cpp 1
On a mouse press event, we just call \c mouseMove() to draw the
spot where the event occurred.
\snippet tools/plugandpaint/plugins/basictools/basictoolsplugin.cpp 2
In \c mouseMove(), we start by saving the state of the QPainter
and we compute a few variables that we'll need later.
\snippet tools/plugandpaint/plugins/basictools/basictoolsplugin.cpp 3
Then comes the brush-dependent part of the code:
\list
\li If the brush is \uicontrol{Pencil}, we just call
QPainter::drawLine() with the current QPen.
\li If the brush is \uicontrol{Air Brush}, we start by setting the
painter's QBrush to Qt::Dense6Pattern to obtain a dotted
pattern. Then we draw a circle filled with that QBrush several
times, resulting in a thick line.
\li If the brush is \uicontrol{Random Letters}, we draw a random letter
at the new cursor position. Most of the code is for setting
the font to be bold and larger than the default font and for
computing an appropriate bounding rect.
\endlist
At the end, we restore the painter state to what it was upon
entering the function and we return the bounding rectangle.
\snippet tools/plugandpaint/plugins/basictools/basictoolsplugin.cpp 4
When the user releases the mouse, we do nothing and return an
empty QRect.
\section1 Implementation of the Shape Interface
\snippet tools/plugandpaint/plugins/basictools/basictoolsplugin.cpp 5
The plugin provides three shapes: \uicontrol{Circle}, \uicontrol{Star}, and
\uicontrol{Text...}. The three dots after \uicontrol{Text} are there because
the shape pops up a dialog asking for more information. We know
that the shape names will end up in a menu, so we include the
three dots in the shape name.
A cleaner but more complicated design would have been to
distinguish between the internal shape name and the name used in
the user interface.
\snippet tools/plugandpaint/plugins/basictools/basictoolsplugin.cpp 6
The \c generateShape() creates a QPainterPath for the specified
shape. If the shape is \uicontrol{Text}, we pop up a QInputDialog to
let the user enter some text.
\section1 Implementation of the Filter Interface
\snippet tools/plugandpaint/plugins/basictools/basictoolsplugin.cpp 7
The plugin provides three filters: \uicontrol{Invert Pixels}, \uicontrol{Swap
RGB}, and \uicontrol{Grayscale}.
\snippet tools/plugandpaint/plugins/basictools/basictoolsplugin.cpp 8
The \c filterImage() function takes a filter name and a QImage as
parameters and returns an altered QImage. The first thing we do
is to convert the image to a 32-bit RGB format, to ensure that
the algorithms will work as expected. For example,
QImage::invertPixels(), which is used to implement the
\uicontrol{Invert Pixels} filter, gives counterintuitive results for
8-bit images, because they invert the indices into the color
table instead of inverting the color table's entries.
\section1 Exporting the Plugin
To finally export your plugin you just have to add the
\c Q_PLUGIN_METADATA() macro right next to the \c Q_OBJECT() macro
into the header file of the plugin.
It must contain the plugins IID and optionally a filename pointing
to a json file containing the metadata for the plugin.
\snippet tools/plugandpaint/plugins/basictools/basictoolsplugin.h 4
Within this example the json file does not need to export any metadata,
so it just contains an empty json object.
\code
{}
\endcode
\section1 The .pro File
Here's the project file for building the Basic Tools plugin:
\snippet tools/plugandpaint/plugins/basictools/basictools.pro 0
The \c .pro file differs from typical \c .pro files in many
respects. First, it starts with a \c TEMPLATE entry specifying \c
lib. (The default template is \c app.) It also adds \c plugin to
the \c CONFIG variable. This is necessary on some platforms to
avoid generating symbolic links with version numbers in the file
name, which is appropriate for most dynamic libraries but not for
plugins.
To make the plugin a static plugin, all that is required is to
specify \c static in addition to \c plugin. The
\l{tools/plugandpaint/plugins/extrafilters}{Extra Filters} plugin,
which is compiled as a dynamic plugin, doesn't specify \c static
in its \c .pro file.
The \c INCLUDEPATH variable sets the search paths for global
headers (i.e., header files included using \c{#include <...>}).
We add \c ../../app to the list, so that we can include
\c <interfaces.h>.
The \c TARGET variable specifies which name we want to give the
target library. We use \c pnp_ as the prefix to show that the
plugin is designed to work with Plug & Paint. On Unix, \c lib is
also prepended to that name. On all platforms, a
platform-specific suffix is appended (e.g., \c .dll on Windows,
\c .a on Linux).
The \c CONFIG() code at the end is necessary for this example
because the example is part of the Qt distribution and Qt can be
configured to be built simultaneously in debug and in release
modes. You don't need to for your own plugins.
*/
/*!
\example tools/plugandpaint/plugins/extrafilters
\title Plug & Paint Extra Filters Example
\brief A plugin providing the extra filters.
\image plugandpaint.png Screenshot of the Plug & Paint example
The Extra Filters example is a plugin for the
\l{tools/plugandpaint/app}{Plug & Paint} example. It provides a set
of filters in addition to those provided by the
\l{tools/plugandpaint/plugins/basictools}{Basic Tools} plugin.
Since the approach is identical to
\l{tools/plugandpaint/plugins/basictools}{Basic Tools}, we won't
review the code here. The only part of interest is the
\c .pro file, since Extra Filters is a dynamic plugin
(\l{tools/plugandpaint/plugins/basictools}{Basic Tools} is
linked statically into the Plug & Paint executable).
Here's the project file for building the Extra Filters plugin:
\snippet tools/plugandpaint/plugins/extrafilters/extrafilters.pro 0
The \c .pro file differs from typical \c .pro files in many
respects. First, it starts with a \c TEMPLATE entry specifying \c
lib. (The default template is \c app.) It also adds \c plugin to
the \c CONFIG variable. This is necessary on some platforms to
avoid generating symbolic links with version numbers in the file
name, which is appropriate for most dynamic libraries but not for
plugins.
The \c INCLUDEPATH variable sets the search paths for global
headers (i.e., header files included using \c{#include <...>}).
We add \c ../../app to the list, so that we can include
\c <interfaces.h>.
The \c TARGET variable specifies which name we want to give the
target library. We use \c pnp_ as the prefix to show that the
plugin is designed to work with Plug & Paint. On Unix, \c lib is
also prepended to that name. On all platforms, a
platform-specific suffix is appended (e.g., \c .dll on Windows,
\c .so on Linux).
The \c DESTDIR variable specifies where we want to install the
plugin. We put it in Plug & Paint's \c plugins subdirectory,
since that's where the application looks for dynamic plugins.
The \c CONFIG() code at the end is necessary for this example
because the example is part of the Qt distribution and Qt can be
configured to be built simultaneously in debug and in release
modes. You don't need to for your own plugins.
*/

View File

@ -133,7 +133,4 @@
In the \l{Echo Plugin Example}{echo plugin example} we show how to
implement plugins that extends Qt applications rather than Qt
itself, which is the case with the style plugin of this example.
The \l{Plug & Paint Example}{plug & paint} example shows how to
implement a static plugin as well as being a more involved example
on plugins that extend applications.
*/

View File

@ -11,8 +11,4 @@ qt_internal_add_example(undoframework)
if(QT_FEATURE_library)
qt_internal_add_example(echoplugin)
if(QT_FEATURE_inputdialog)
qt_internal_add_example(plugandpaint)
endif()
endif()

View File

@ -3,7 +3,6 @@ SUBDIRS = \
completer \
customcompleter \
echoplugin \
plugandpaint \
regularexpression \
settingseditor \
styleplugin \
@ -12,6 +11,5 @@ SUBDIRS = \
!qtConfig(library) {
SUBDIRS -= \
echoplugin \
plugandpaint
echoplugin
}

View File

@ -1203,8 +1203,7 @@ inline QObjectPrivate::Connection::~Connection()
\c dynamic_cast(), with the advantages that it doesn't require
RTTI support and it works across dynamic library boundaries.
qobject_cast() can also be used in conjunction with interfaces;
see the \l{tools/plugandpaint/app}{Plug & Paint} example for details.
qobject_cast() can also be used in conjunction with interfaces.
\warning If T isn't declared with the Q_OBJECT macro, this
function's return value is undefined.
@ -4374,15 +4373,6 @@ QDebug operator<<(QDebug dbg, const QObject *o)
This macro tells Qt which interfaces the class implements. This
is used when implementing plugins.
Example:
\snippet ../widgets/tools/plugandpaint/plugins/basictools/basictoolsplugin.h 1
\dots
\snippet ../widgets/tools/plugandpaint/plugins/basictools/basictoolsplugin.h 3
See the \l{tools/plugandpaint/plugins/basictools}{Plug & Paint
Basic Tools} example for details.
\sa Q_DECLARE_INTERFACE(), Q_PLUGIN_METADATA(), {How to Create Qt Plugins}
*/

View File

@ -19,13 +19,10 @@
This macro associates the given \a Identifier (a string literal)
to the interface class called \a ClassName. The \a Identifier must
be unique. For example:
\snippet plugandpaint/app/interfaces.h 3
be unique.
This macro is normally used right after the class definition for
\a ClassName, in a header file. See the
\l{tools/plugandpaint/app}{Plug & Paint} example for details.
\a ClassName, in a header file.
If you want to use Q_DECLARE_INTERFACE with interface classes
declared in a namespace then you have to make sure the Q_DECLARE_INTERFACE
@ -53,8 +50,6 @@
\snippet code/doc_src_qplugin.cpp 1
See the \l{tools/plugandpaint/app}{Plug & Paint} example for details.
Note that the class this macro appears on must be default-constructible.
FILE is optional and points to a json file.