Viadeo Twitter Google Bookmarks ! Facebook Digg del.icio.us MySpace Yahoo MyWeb Blinklist Netvouz Reddit Simpy StumbleUpon Bookmarks Windows Live Favorites 
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Plug & Paint Example

Files:

The Plug & Paint example demonstrates how to write Qt applications that can be extended through plugins.

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. Afterward, you can read the Basic Tools and Extra Filters overviews, which show how to implement static and dynamic plugins, respectively.

Plug & Paint consists of the following classes:

  • MainWindow is a QMainWindow subclass that provides the menu system and that contains a PaintArea as the central widget.
  • PaintArea is a QWidget that allows the user to draw using a brush and to insert shapes.
  • PluginDialog is a dialog that shows information about the plugins detected by the application.
  • BrushInterface, ShapeInterface, and FilterInterface are abstract base classes that can be implemented by plugins to provide custom brushes, shapes, and image filters.

The Plugin Interfaces

We will start by reviewing the interfaces defined in interfaces.h. These interfaces are used by the Plug & Paint application to access extra functionality. They are implemented in the plugins.

 class BrushInterface
 {
 public:
     virtual ~BrushInterface() {}

     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;
 };

The BrushInterface class declares four pure virtual functions. The first pure virtual function, 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 brush parameter to identify which brush (among those returned by brushes()) is used.

mousePress(), mouseMove(), and mouseRelease() take a QPainter and one or two QPoints, 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 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.

 class ShapeInterface
 {
 public:
     virtual ~ShapeInterface() {}

     virtual QStringList shapes() const = 0;
     virtual QPainterPath generateShape(const QString &shape,
                                        QWidget *parent) = 0;
 };

The ShapeInterface class declares a shapes() function that works the same as BrushInterface's brushes() function, and a generateShape() function that has a shape parameter. Shapes are represented by a QPainterPath, a data type that can represent arbitrary 2D shapes or combinations of shapes. The parent parameter can be used by the plugin to pop up a dialog asking the user to specify more information.

 class FilterInterface
 {
 public:
     virtual ~FilterInterface() {}

     virtual QStringList filters() const = 0;
     virtual QImage filterImage(const QString &filter, const QImage &image,
                                QWidget *parent) = 0;
 };

The FilterInterface class declares a filters() function that returns a list of filter names, and a filterImage() function that applies a filter to an image.

 Q_DECLARE_INTERFACE(BrushInterface,
                     "com.trolltech.PlugAndPaint.BrushInterface/1.0")
 Q_DECLARE_INTERFACE(ShapeInterface,
                     "com.trolltech.PlugAndPaint.ShapeInterface/1.0")
 Q_DECLARE_INTERFACE(FilterInterface,
                     "com.trolltech.PlugAndPaint.FilterInterface/1.0")

To make it possible to query at run-time whether a plugin implements a given interface, we must use the 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 Basic Tools plugin and the Extra Filters plugin shows how to derive from BrushInterface, ShapeInterface, and FilterInterface.

A note on naming: It might have been tempting to give the brushes(), shapes(), and filters() functions a more generic name, such as keys() or 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.

The MainWindow Class

The MainWindow class is a standard QMainWindow subclass, as found in many of the other examples (e.g., Application). Here, we'll concentrate on the parts of the code that are related to plugins.

 void MainWindow::loadPlugins()
 {
     foreach (QObject *plugin, QPluginLoader::staticInstances())
         populateMenus(plugin);

The loadPlugins() function is called from the MainWindow constructor to detect plugins and update the Brush, Shapes, and 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.

     pluginsDir = QDir(qApp->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");

The next step is to load dynamic plugins. We initialize the pluginsDir member variable to refer to the 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 cd(). On Windows and Mac OS X, this file is usually located in a subdirectory, so we need to take this into account.

     foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
         QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
         QObject *plugin = loader.instance();
         if (plugin) {
             populateMenus(plugin);
             pluginFileNames += fileName;
         }
     }

We use QDir::entryList() to get a list of all files in that directory. Then we iterate over the result using foreach 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.

     brushMenu->setEnabled(!brushActionGroup->actions().isEmpty());
     shapesMenu->setEnabled(!shapesMenu->actions().isEmpty());
     filterMenu->setEnabled(!filterMenu->actions().isEmpty());
 }

At the end, we enable or disable the Brush, Shapes, and Filters menus based on whether they contain any items.

 void MainWindow::populateMenus(QObject *plugin)
 {
     BrushInterface *iBrush = qobject_cast<BrushInterface *>(plugin);
     if (iBrush)
         addToMenu(plugin, iBrush->brushes(), brushMenu, SLOT(changeBrush()),
                   brushActionGroup);

     ShapeInterface *iShape = qobject_cast<ShapeInterface *>(plugin);
     if (iShape)
         addToMenu(plugin, iShape->shapes(), shapesMenu, SLOT(insertShape()));

     FilterInterface *iFilter = qobject_cast<FilterInterface *>(plugin);
     if (iFilter)
         addToMenu(plugin, iFilter->filters(), filterMenu, SLOT(applyFilter()));
 }

For each plugin (static or dynamic), we check which interfaces it implements using qobject_cast(). First, we try to cast the plugin instance to a BrushInterface; if it works, we call the private function addToMenu() with the list of brushes returned by brushes(). Then we do the same with the ShapeInterface and the FilterInterface.

 void MainWindow::aboutPlugins()
 {
     PluginDialog dialog(pluginsDir.path(), pluginFileNames, this);
     dialog.exec();
 }

The aboutPlugins() slot is called on startup and can be invoked at any time through the About Plugins action. It pops up a PluginDialog, providing information about the loaded plugins.

Screenshot of the Plugin dialog

The addToMenu() function is called from loadPlugin() to create QActions 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.

 void MainWindow::changeBrush()
 {
     QAction *action = qobject_cast<QAction *>(sender());
     BrushInterface *iBrush = qobject_cast<BrushInterface *>(action->parent());
     const QString brush = action->text();

     paintArea->setBrush(iBrush, brush);
 }

The changeBrush() slot is invoked when the user chooses one of the brushes from the Brush menu. We start by finding out which action invoked the slot using QObject::sender(). Then we get the BrushInterface out of the plugin (which we conveniently passed as the QAction's parent) and we call PaintArea::setBrush() with the BrushInterface and the string identifying the brush. Next time the user draws on the paint area, PaintArea will use this brush.

 void MainWindow::insertShape()
 {
     QAction *action = qobject_cast<QAction *>(sender());
     ShapeInterface *iShape = qobject_cast<ShapeInterface *>(action->parent());

     const QPainterPath path = iShape->generateShape(action->text(), this);
     if (!path.isEmpty())
         paintArea->insertShape(path);
 }

The insertShape() is invoked when the use chooses one of the shapes from the Shapes menu. We retrieve the QAction that invoked the slot, then the ShapeInterface associated with that QAction, and finally we call ShapeInterface::generateShape() to obtain a QPainterPath.

 void MainWindow::applyFilter()
 {
     QAction *action = qobject_cast<QAction *>(sender());
     FilterInterface *iFilter =
             qobject_cast<FilterInterface *>(action->parent());

     const QImage image = iFilter->filterImage(action->text(), paintArea->image(),
                                               this);
     paintArea->setImage(image);
 }

The applyFilter() slot is similar: We retrieve the QAction that invoked the slot, then the FilterInterface associated to that QAction, and finally we call FilterInterface::filterImage() to apply the filter onto the current image.

The PaintArea Class

The PaintArea class contains some code that deals with BrushInterface, so we'll review it briefly.

 void PaintArea::setBrush(BrushInterface *brushInterface, const QString &brush)
 {
     this->brushInterface = brushInterface;
     this->brush = brush;
 }

In setBrush(), we simply store the BrushInterface and the brush that are given to us by MainWindow.

 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->pos());
             update(rect);
         }

         lastPos = event->pos();
     }
 }

In the mouse move event handler, we call the BrushInterface::mouseMove() function on the current BrushInterface, with the current brush. The mouse press and mouse release handlers are very similar.

The PluginDialog Class

The 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 findPlugins() to fill the QTreeWdiget with information about the plugins:

 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);

     foreach (QObject *plugin, QPluginLoader::staticInstances())
         populateTreeWidget(plugin, tr("%1 (Static Plugin)")
                                    .arg(plugin->metaObject()->className()));

     foreach (QString fileName, fileNames) {
         QPluginLoader loader(dir.absoluteFilePath(fileName));
         QObject *plugin = loader.instance();
         if (plugin)
             populateTreeWidget(plugin, fileName);
     }
 }

The findPlugins() is very similar to MainWindow::loadPlugins(). It uses QPluginLoader to access the static and dynamic plugins. Its helper function populateTreeWidget() uses qobject_cast() to find out which interfaces are implemented by the plugins:

 void PluginDialog::populateTreeWidget(QObject *plugin, const QString &text)
 {
     QTreeWidgetItem *pluginItem = new QTreeWidgetItem(treeWidget);
     pluginItem->setText(0, text);
     treeWidget->setItemExpanded(pluginItem, true);

     QFont boldFont = pluginItem->font(0);
     boldFont.setBold(true);
     pluginItem->setFont(0, boldFont);

     if (plugin) {
         BrushInterface *iBrush = qobject_cast<BrushInterface *>(plugin);
         if (iBrush)
             addItems(pluginItem, "BrushInterface", iBrush->brushes());

         ShapeInterface *iShape = qobject_cast<ShapeInterface *>(plugin);
         if (iShape)
             addItems(pluginItem, "ShapeInterface", iShape->shapes());

         FilterInterface *iFilter =
                 qobject_cast<FilterInterface *>(plugin);
         if (iFilter)
             addItems(pluginItem, "FilterInterface", iFilter->filters());
     }
 }

Importing Static Plugins

The 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 .cpp file) and specifying the plugin in the .pro file.

For Plug & Paint, we have chosen to put Q_IMPORT_PLUGIN() in main.cpp:

 #include "mainwindow.h"
 #include <QtPlugin>
 #include <QApplication>

 Q_IMPORT_PLUGIN(pnp_basictools)

 int main(int argc, char *argv[])
 {
     QApplication app(argc, argv);
     MainWindow window;
     window.show();
     return app.exec();
 }

The argument to Q_IMPORT_PLUGIN() is the plugin's name, as specified with Q_EXPORT_PLUGIN2() in the plugin.

In the .pro file, we need to specify the static library. Here's the project file for building Plug & Paint:

 HEADERS        = interfaces.h \
                  mainwindow.h \
                  paintarea.h \
                  plugindialog.h
 SOURCES        = main.cpp \
                  mainwindow.cpp \
                  paintarea.cpp \
                  plugindialog.cpp
 symbian {
     LIBS           = -lpnp_basictools.lib
 } else {
     LIBS           = -L$${QT_BUILD_TREE}/examples/tools/plugandpaint/plugins -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
 }

The LIBS line variable specifies the library pnp_basictools located in the ../plugandpaintplugins/basictools directory. (Although the LIBS syntax has a distinct Unix flavor, qmake supports it on all platforms.)

The 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 Basic Tools example plugin.

Publicité

Best Of

Actualités les plus lues

Semaine
Mois
Année
  1. « Quelque chose ne va vraiment pas avec les développeurs "modernes" », un développeur à "l'ancienne" critique la multiplication des bibliothèques 102
  2. Pourquoi les programmeurs sont-ils moins payés que les gestionnaires de programmes ? Manquent-ils de pouvoir de négociation ? 53
  3. «Le projet de loi des droits du développeur» : quelles conditions doivent remplir les entreprises pour que le développeur puisse réussir ? 73
  4. Les développeurs détestent-ils les antivirus ? Un programmeur manifeste sa haine envers ces solutions de sécurité 27
  5. Qt Commercial : Digia organise un webinar gratuit le 27 mars sur la conception d'interfaces utilisateur et d'applications avec le framework 0
  6. Quelles nouveautés de C++11 Visual C++ doit-il rapidement intégrer ? Donnez-nous votre avis 10
  7. 2017 : un quinquennat pour une nouvelle version du C++ ? Possible, selon Herb Sutter 11
Page suivante
  1. Linus Torvalds : le "C++ est un langage horrible", en justifiant le choix du C pour le système de gestion de version Git 100
  2. Comment prendre en compte l'utilisateur dans vos applications ? Pour un développeur, « 90 % des utilisateurs sont des idiots » 229
  3. Quel est LE livre que tout développeur doit lire absolument ? Celui qui vous a le plus marqué et inspiré 96
  4. Apple cède et s'engage à payer des droits à Nokia, le conflit des brevets entre les deux firmes s'achève 158
  5. Nokia porte à nouveau plainte contre Apple pour violation de sept nouveaux brevets 158
  6. Quel est le code dont vous êtes le plus fier ? Pourquoi l'avez-vous écrit ? Et pourquoi vous a-t-il donné autant de satisfaction ? 83
  7. « Quelque chose ne va vraiment pas avec les développeurs "modernes" », un développeur à "l'ancienne" critique la multiplication des bibliothèques 101
Page suivante

Le blog Digia au hasard

Logo

Créer des applications avec un style Metro avec Qt, exemples en QML et C++, un article de Digia Qt traduit par Thibaut Cuvelier

Le blog Digia est l'endroit privilégié pour la communication sur l'édition commerciale de Qt, où des réponses publiques sont apportées aux questions les plus posées au support. Lire l'article.

Communauté

Ressources

Liens utiles

Contact

  • Vous souhaitez rejoindre la rédaction ou proposer un tutoriel, une traduction, une question... ? Postez dans le forum Contribuez ou contactez-nous par MP ou par email (voir en bas de page).

Qt dans le magazine

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.6-snapshot
Copyright © 2012 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur ? Un bug ? Une redirection cassée ? Ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
 
 
 
 
Partenaires

Hébergement Web