This section will show you how to write a custom widget and how to embed the custom widget into a plugin. There are no restrictions or special considerations that must be taken into account when creating a widget that is destined to become a plugin. If you are an experienced Qt programmer you can safely skip the section on creating a custom widget and go directly to the section called Creating a Plugin "Creating a Plugin".
A custom widget is often a specialization (subclass) of another widget or a combination of widgets working together or a blend of both these approaches. If you simply want a collection of widgets in a particular configuration it is easiest to create them, select them as a group, and copy and paste them as required within Qt Designer. Custom widgets are generally created when you need to add new functionality to existing widgets or groups of widgets.
We have two recommendations that you should consider when creating a custom widget for a plugin:
Using Qt's property system will provide Qt Designer users with a direct means of configuring the widget through the property editor. (See the Qt Properties documentation.)
Consider making your widget's public 'set' functions into public slots so that you can perform signal-slot connections with the widget in Qt Designer.
In the course of this chapter we will create a simple but useful widget, 'FileChooser', which we'll later make available in Qt Designer as a plugin. In practice most custom widgets are created to add functionality rather than to compose widgets, so we will create our widget in code rather than using Qt Designer to reflect this approach. FileChooser consists of a QLineEdit and a QPushButton. The QLineEdit is used to hold a file or directory name, the QPushButton is used to launch a file dialog through which the user can choose a file or directory.
If you've followed the manual up to this point you may well be able to create this custom widget yourself. If you're confident that you can make your own version of the widget, or have another widget that you want to turn into a plugin, skip ahead to the section called Creating a Plugin "Creating a Plugin". If you prefer to read how we created the widget then read on.We will work step-by-step through the widget's header file, qt/tools/designer/examples/filechooser/widget/filechooser.h.
#include <qwidget.h> class QLineEdit; class QPushButton; |
class FileChooser : public QWidget { Q_OBJECT Q_ENUMS( Mode ) Q_PROPERTY( Mode mode READ mode WRITE setMode ) Q_PROPERTY( QString fileName READ fileName WRITE setFileName ) |
FileChooser( QWidget *parent = 0, const char *name = 0); QString fileName() const; Mode mode() const; |
public slots: void setFileName( const QString &fn ); void setMode( Mode m ); signals: void fileNameChanged( const QString & ); private slots: void chooseFile(); |
private: QLineEdit *lineEdit; QPushButton *button; Mode md; |
We will work step-by-step through the implementation which is in qt/tools/designer/examples/filechooser/widget/filechooser.cpp.
FileChooser::FileChooser( QWidget *parent, const char *name ) : QWidget( parent, name ), md( File ) |
QHBoxLayout *layout = new QHBoxLayout( this ); lineEdit = new QLineEdit( this, "filechooser_lineedit" ); layout->addWidget( lineEdit ); button = new QPushButton( "...", this, "filechooser_button" ); layout->addWidget( button ); |
connect( lineEdit, SIGNAL( textChanged( const QString & ) ), this, SIGNAL( fileNameChanged( const QString & ) ) ); connect( button, SIGNAL( clicked() ), this, SLOT( chooseFile() ) ); |
setFocusProxy( lineEdit ); |
void FileChooser::setFileName( const QString &fn ) { lineEdit->setText( fn ); } QString FileChooser::fileName() const { return lineEdit->text(); } |
void FileChooser::chooseFile() { QString fn; if ( mode() == File ) fn = QFileDialog::getOpenFileName( lineEdit->text(), QString::null, this ); else fn = QFileDialog::getExistingDirectory( lineEdit->text(),this ); if ( !fn.isEmpty() ) { lineEdit->setText( fn ); emit fileNameChanged( fn ); } } |
Although these two files complete the implementation of the FileChooser widget it is good practice to write a test harness to check that the widget behaves as expected before attempting to put it into a plugin.
We present a rudimentary test harness which will allow us to run our custom widget. The test harness requires two files, a main.cpp to contain the FileChooser, and a .pro file to create the Makefile from. Here is qt/tools/designer/examples/filechooser/widget/main.cpp:
#include <qapplication.h> #include "filechooser.h" int main( int argc, char ** argv ) { QApplication a( argc, argv ); FileChooser *fc = new FileChooser; fc->show(); return a.exec(); } |
TEMPLATE = app CONFIG+= qt warn_on release HEADERS = filechooser.h SOURCES = filechooser.cpp main.cpp INTERFACES = TARGET = filechooser |
A plugin is a software component. Plugins are not specific to Qt Designer; they can be used generally to wrap any number of classes which can then be supplied within a stand-alone component. Qt Designer can load any plugins that provide an interface which it understands: the WidgetInterface is such an interface and the one we'll use for our example. Qt Designer also provides an application interface through which plugins can access its own functionality. We will demonstrate how to wrap a custom widget, (the FileChooser widget we created in the previous section), inside an interface implementation and how to place the interface implementation inside a plugin. Although we will only include one widget in our interface implementation there is no limit to the number of widgets that could be included.
In our example we wish to make a custom widget available through a plugin so we must derive the interface implementation from the WidgetInterface class. We must implement all the WidgetInterface's pure virtual functions. In most cases the implementations are simple and can easily be adapted from the example we present.
Within the context of the WidgetInterface a feature is a widget class that a WidgetInterface implementation makes available to applications. Since a WidgetInterface implementation can include any number of features most of the interface functions take the name of the feature, (the particular widget class we wish to access through the interface), as their first argument to distinguish which feature we are referring to.
To make your own plugin it is probably easiest to start by copying the plugin.h and plugin.cpp files and changing 'CustomWidgetInterface' to the name you wish to use for your widget interface implementation class. Below we provide an introduction to the header file although it needs no changes beyond class renaming. The implementation file requires simple changes, mostly more class renaming; we will review each function in turn and explain what you need to do.
Note that if two different interface implementations, e.g. from two different suppliers, use the same class name there will be no conflict. This is because every interface (i.e. abstract base class) has a unique uuid (universal unique identifier). When the application wants the functionality of a particular interface it calls queryInterface() passing the uuid of the interface it is interested in, for example, IID_Widget. If the implementation provides the requested interface it returns a pointer to the interface's implementation. The application never uses (or knows) the name of the class that actually provides the implementation so no name conflict can occur.
We have called our header file plugin.h and we've called our interface class CustomWidgetInterface since we will be using our interface class to wrap our custom widgets. We present the entire header file to give you an impression of the scope of the implementation required. Most of the functions require just a few lines of code.
Example 5-1. From qt/tools/designer/examples/filechooser/plugin/plugin.h
#include <widgetinterface.h> #include <qobjectcleanuphandler.h> class CustomWidgetInterface : public WidgetInterface { public: CustomWidgetInterface(); virtual ~CustomWidgetInterface() {} // From QUnknownInterface QRESULT queryInterface( const QUuid&, QUnknownInterface **iface ); Q_REFCOUNT // From QFeatureListInterface QStringList featureList() const; // From WidgetInterface QWidget* create( const QString &classname, QWidget* parent = 0, const char* name = 0 ); QString group( const QString& ) const; QIconSet iconSet( const QString& ) const; QString includeFile( const QString& ) const; QString toolTip( const QString& ) const; QString whatsThis( const QString& ) const; bool isContainer( const QString& ) const; private: QObjectCleanupHandler objects; ulong ref; }; |
There are two items of private data, objects and ref. The ref item is used to store the widget interface implementation's reference count. The objects item is a guarded cleanup handler that ensures that the widgets that get instantiated by the interface implementation are deleted when the plugin is unloaded. (See the Qt Plugin documentation and the class documentation for QCleanupHandler for more information.)
Create your own plugin .cpp file by copying our plugin.cpp file and changing all occurrences of 'CustomWidgetInterface' to the name you wish to use for your widget interface implementation. Most of the other changes are simply replacing the name of our custom control, 'FileChooser', with the name of your custom control. You may need to add extra else if clauses if you have more than one custom control in your interface implementation.
We'll now look at the constructor.
CustomWidgetInterface::CustomWidgetInterface() : ref( 0 ) { } |
No destructor is necessary. We defined it in the header file as follows:
virtual ~CustomWidgetInterface() {} |
The queryInterface() is the most complicated function: but it can simply be copied into your own widget interface implementation. Change the class name to whatever class you've called your widget interface implementation. (It isn't implemented for us in the WidgetInterface class because some types of plugin need to specialise it.)
QRESULT CustomWidgetInterface::queryInterface( const QUuid& uuid, QUnknownInterface **iface ) { if ( uuid == IID_QUnknown ) *iface = (QUnknownInterface*)this; else if ( uuid == IID_QFeatureList ) *iface = (QFeatureListInterface*)this; else if ( uuid == IID_Widget ) *iface = (WidgetInterface*)this; if ( *iface ) (*iface)->addRef(); } |
The Q_EXPORT_INTERFACE Macro
Q_EXPORT_INTERFACE() { Q_CREATE_INSTANCE( CustomWidgetInterface ); } |
This macro must appear once in your plugin. It should be copied with the class name changed to the name of your interface's class. (See the Qt Plugin documentation for more information on the plugin entry point.)
Each widget you wrap in a widget interface implementation becomes a feature that the interface implementation offers. There is no limit to the number of features that you may include in an interface implementation.
QStringList CustomWidgetInterface::featureList() const { QStringList list; list << "FileChooser"; return list; } |
The create() function.
QWidget* CustomWidgetInterface::create( const QString &feature, QWidget* parent, const char* name ) { QWidget* w = 0; if ( feature == "FileChooser" ) w = new FileChooser( parent, name ); objects.add( w ); return w; } |
The includeFile() function.
QString CustomWidgetInterface::includeFile( const QString& feature ) const { if ( feature == "FileChooser" ) return "filechooser.h"; return QString::null; } |
The group(), iconSet(), iconset(), toolTip() and whatsThis() functions.
QString CustomWidgetInterface::group( const QString& feature ) const { if ( feature == "FileChooser" ) return "Input"; return QString::null; } |
The iconSet() function returns the pixmap to use in the toolbar to represent the custom widget. The toolTip() function returns the tooltip text and the whatsThis() function returns the Whats This text. Copy each of these functions changing the class name, feature name and the string you return to suit your own widget interface implementation.
The isContainer() function.
bool CustomWidgetInterface::isContainer( const QString& ) const { return FALSE; } |
The project file for a plugin is somewhat different from an application's project file but in most cases you can use our project file changing only the HEADERS and SOURCES lines.
Example 5-2. qt/tools/designer/examples/filechooser/plugin/plugin.pro
TEMPLATE = lib CONFIG+= qt warn_on release plugin HEADERS = plugin.h ../widget/filechooser.h SOURCES = plugin.cpp ../widget/filechooser.cpp INTERFACES = DESTDIR = ../../../../../../../plugins/designer INCLUDEPATH += $(QTDIR)/tools/designer/interfaces TARGET = filechooser target.path=$$plugins.path isEmpty(target.path):target.path=$$QT_PREFIX/plugins INSTALLS += target |