Sample window

The purpose of this plugin is to extend our Sample plugin by making it possible for the user to add two numbers through a dockable window.

File structure

i18n
 └─ SampleWindow_fr.ts
res
 └─ SampleWindow_i18n.qrc.in
src
 ├─ samplewindowplugin.cpp
 ├─ samplewindowplugin.h
 ├─ samplewindowplugin.json
 ├─ samplewindowwindow.cpp
 ├─ samplewindowwindow.h
 └─ samplewindowwindow.ui
CMakeLists.txt

Category

Our plugin is part of the Sample category, which means that its code can be found under [OpenCOR]/src/plugins/sample/SampleWindow/.

Interfaces

Unlike for the Sample plugin, we want our plugin to interact with OpenCOR. This means that it needs to implement some interfaces.

More specifically, we want our plugin to be a dockable window, so we need to implement both the Plugin and Window interfaces. While we are at it, we might also internationalise our plugin, which means also implementing the Internationalisation interface.

CMake project

As for the Sample plugin, our plugin has a CMakeLists.txt file, which contents is:

 1project(SampleWindowPlugin)
 2
 3# Add the plugin
 4
 5add_plugin(SampleWindow
 6    SOURCES
 7        ../../i18ninterface.cpp
 8        ../../plugininfo.cpp
 9        ../../plugininterface.cpp
10        ../../windowinterface.cpp
11
12        src/samplewindowplugin.cpp
13        src/samplewindowwindow.cpp
14    UIS
15        src/samplewindowwindow.ui
16    PLUGINS
17        Core
18        Sample
19)

The interfaces our plugin implements come with a .cpp file, so we reference them (lines 7, 9 and 10). Then, our plugin needs both the Core and Sample plugins (the latter, to be able to use its add() function), so they are referenced (lines 17 and 18) using the PLUGINS keyword (line 16). Our plugin comes with a dockable window, which is implemented using various files (lines 13 and 15). One of those files is a .ui file, which is referenced using the UIS keyword (line 14).

Plugin information

Our plugin information can be found in samplewindowplugin.cpp, samplewindowplugin.h and samplewindowplugin.json. Starting with samplewindowplugin.h, its contents is:

28#include "i18ninterface.h"
29#include "plugininfo.h"
30#include "plugininterface.h"
31#include "windowinterface.h"
32
33//==============================================================================
34
35namespace OpenCOR {
36namespace SampleWindow {
37
38//==============================================================================
39
40PLUGININFO_FUNC SampleWindowPluginInfo();
41
42//==============================================================================
43
44class SampleWindowWindow;
45
46//==============================================================================
47
48class SampleWindowPlugin : public QObject, public I18nInterface,
49                           public PluginInterface, public WindowInterface
50{
51    Q_OBJECT
52
53    Q_PLUGIN_METADATA(IID "OpenCOR.SampleWindowPlugin" FILE "samplewindowplugin.json")
54
55    Q_INTERFACES(OpenCOR::I18nInterface)
56    Q_INTERFACES(OpenCOR::PluginInterface)
57    Q_INTERFACES(OpenCOR::WindowInterface)
58
59public:
60#include "i18ninterface.inl"
61#include "plugininterface.inl"
62#include "windowinterface.inl"
63
64private:
65    QAction *mSampleWindowAction = nullptr;
66
67    SampleWindowWindow *mSampleWindowWindow = nullptr;
68};
69
70//==============================================================================
71
72} // namespace SampleWindow
73} // namespace OpenCOR

As mentioned above, our plugin implements some interfaces, which means that their header file is included (lines 28, 30 and 31). It also means that our plugin class inherits from those interfaces (lines 48 and 49), as well as makes calls to the Q_INTERFACES() macro to let Qt know which interfaces it implements (lines 55-57). Finally, we include the inline files (lines 60-62) that declare various methods that must be implemented by our plugin (see below). (The rest of the class definition is specific to our plugin and is discussed below.)

The C function that is used by OpenCOR to retrieve some basic information about our plugin can be found in samplewindowplugin.cpp:

39PLUGININFO_FUNC SampleWindowPluginInfo()
40{
41    Descriptions descriptions;
42
43    descriptions.insert("en", QString::fromUtf8("a plugin that provides an addition window."));
44    descriptions.insert("fr", QString::fromUtf8("une extension qui fournit une fenêtre d'addition."));
45
46    return new PluginInfo(PluginInfo::Category::Sample, true, false,
47                          { "Core", "Sample" },
48                          descriptions);
49}

As can be seen, our plugin is selectable by the user, but it does not offer CLI support (line 46). It also has a direct dependency on both the Core and Sample plugins (line 47).

Interfaces implementation

The implementation of the interfaces’ various methods can be found in samplewindowplugin.cpp. The methods are grouped by interface and are ordered alphabetically.

We start with the Internationalisation interface:

51//==============================================================================
52// I18n interface
53//==============================================================================
54
55void SampleWindowPlugin::retranslateUi()
56{
57    // Retranslate our Sample window action
58
59    retranslateAction(mSampleWindowAction,
60                      tr("Sample"),
61                      tr("Show/hide the Sample window"));
62}
63
64//==============================================================================

All that we need to do is (re)translate mSampleWindowAction with the actual (French) translations in SampleWindow_fr.ts (together with some other translations needed below).

Next, we have the Plugin interface:

 64//==============================================================================
 65// Plugin interface
 66//==============================================================================
 67
 68bool SampleWindowPlugin::definesPluginInterfaces()
 69{
 70    // We don't handle this interface...
 71
 72    return false;
 73}
 74
 75//==============================================================================
 76
 77bool SampleWindowPlugin::pluginInterfacesOk(const QString &pFileName,
 78                                            QObject *pInstance)
 79{
 80    Q_UNUSED(pFileName)
 81    Q_UNUSED(pInstance)
 82
 83    // We don't handle this interface...
 84
 85    return false;
 86}
 87
 88//==============================================================================
 89
 90void SampleWindowPlugin::initializePlugin()
 91{
 92    // Create an action to show/hide our Sample window
 93
 94    mSampleWindowAction = Core::newAction(true, Core::mainWindow());
 95
 96    // Create our Sample window
 97
 98    mSampleWindowWindow = new SampleWindowWindow(Core::mainWindow());
 99}
100
101//==============================================================================
102
103void SampleWindowPlugin::finalizePlugin()
104{
105    // We don't handle this interface...
106}
107
108//==============================================================================
109
110void SampleWindowPlugin::pluginsInitialized(const Plugins &pLoadedPlugins)
111{
112    Q_UNUSED(pLoadedPlugins)
113
114    // We don't handle this interface...
115}
116
117//==============================================================================
118
119void SampleWindowPlugin::loadSettings(QSettings &pSettings)
120{
121    Q_UNUSED(pSettings)
122
123    // We don't handle this interface...
124}
125
126//==============================================================================
127
128void SampleWindowPlugin::saveSettings(QSettings &pSettings) const
129{
130    Q_UNUSED(pSettings)
131
132    // We don't handle this interface...
133}
134
135//==============================================================================
136
137void SampleWindowPlugin::handleUrl(const QUrl &pUrl)
138{
139    Q_UNUSED(pUrl)
140
141    // We don't handle this interface...
142}
143
144//==============================================================================

The only method of interest to our plugin is initializePlugin() (lines 90-99), which is where we initialise both mSampleWindowAction and mSampleWindowWindow. All the other methods (definesPluginInterfaces(), pluginInterfacesOk(), finalizePlugin(), pluginsInitialized(), loadSettings(), saveSettings() and handleUrl()) are left empty.

Finally, we have the Window interface:

144//==============================================================================
145// Window interface
146//==============================================================================
147
148Qt::DockWidgetArea SampleWindowPlugin::windowDefaultDockArea() const
149{
150    // Return our default dock area
151
152    return Qt::TopDockWidgetArea;
153}
154
155//==============================================================================
156
157QAction * SampleWindowPlugin::windowAction() const
158{
159    // Return our window action
160
161    return mSampleWindowAction;
162}
163
164//==============================================================================
165
166QDockWidget * SampleWindowPlugin::windowWidget() const
167{
168    // Return our window widget
169
170    return mSampleWindowWindow;
171}
172
173//==============================================================================

All three methods are implemented since they tell OpenCOR the default dock area for our plugin window (see windowDefaultDockArea(); lines 148-153), as well as provide the pointer to our action (see windowAction(); lines 157-162) and window (see windowWidget(); lines 166-171).

Plugin specific

Some extra work is needed to get our plugin to do what we want, and this is done via the SampleWindowWindow class in samplewindowwindow.h:

28#include "windowwidget.h"
29
30//==============================================================================
31
32namespace Ui {
33    class SampleWindowWindow;
34} // namespace Ui
35
36//==============================================================================
37
38namespace OpenCOR {
39namespace SampleWindow {
40
41//==============================================================================
42
43class SampleWindowWindow : public Core::WindowWidget
44{
45    Q_OBJECT
46
47public:
48    explicit SampleWindowWindow(QWidget *pParent);
49    ~SampleWindowWindow() override;
50
51private:
52    Ui::SampleWindowWindow *mGui;
53
54private slots:
55    void updateSum();
56};
57
58//==============================================================================
59
60} // namespace SampleWindow
61} // namespace OpenCOR

SampleWindowWindow inherits from Core::WindowWidget, which is defined in the Core plugin and is an extended version of Qt’s QDockWidget class (line 43). It also comes with a GUI file, which describes the layout of our plugin window (samplewindowwindow.ui).

The implementation of SampleWindowWindow can be found in samplewindowwindow.cpp:

24#include "sampleutilities.h"
25#include "samplewindowwindow.h"
26
27//==============================================================================
28
29#include "ui_samplewindowwindow.h"
30
31//==============================================================================
32
33namespace OpenCOR {
34namespace SampleWindow {
35
36//==============================================================================
37
38SampleWindowWindow::SampleWindowWindow(QWidget *pParent) :
39    Core::WindowWidget(pParent),
40    mGui(new Ui::SampleWindowWindow)
41{
42    // Set up the GUI
43
44    mGui->setupUi(this);
45
46    // A couple of connections to update our sum whenever one of the value of
47    // one of our numbers is updated
48
49    connect(mGui->nb1DoubleSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
50            this, &SampleWindowWindow::updateSum);
51    connect(mGui->nb2DoubleSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
52            this, &SampleWindowWindow::updateSum);
53
54    // Initialise our sum
55
56    updateSum();
57}
58
59//==============================================================================
60
61SampleWindowWindow::~SampleWindowWindow()
62{
63    // Delete the GUI
64
65    delete mGui;
66}
67
68//==============================================================================
69
70void SampleWindowWindow::updateSum()
71{
72    // Update our sum
73
74    mGui->sumLabel->setText(QString::number(Sample::add(mGui->nb1DoubleSpinBox->value(), mGui->nb2DoubleSpinBox->value())));
75}
76
77//==============================================================================
78
79} // namespace SampleWindow
80} // namespace OpenCOR

SampleWindowWindow() (lines 38-57) initialises the SampleWindowWindow object, as well as creates a couple of connections (lines 49-52) and initialises our sum by calling updateSum() (line 56). As can be seen, updateSum() calls the add() method from the Sample plugin (lines 70-75).