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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
project(SampleWindowPlugin)

# Add the plugin

add_plugin(SampleWindow
    SOURCES
        ../../i18ninterface.cpp
        ../../plugininfo.cpp
        ../../plugininterface.cpp
        ../../windowinterface.cpp

        src/samplewindowplugin.cpp
        src/samplewindowwindow.cpp
    HEADERS_MOC
        src/samplewindowplugin.h
        src/samplewindowwindow.h
    UIS
        src/samplewindowwindow.ui
    PLUGINS
        Core
        Sample
)

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 20 and 21) using the PLUGINS keyword (line 19). Our plugin comes with a dockable window, which is implemented using various files (lines 13, 16 and 18). One of those files is a .ui file, which is referenced using the UIS keyword (line 17).

Plugin information

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

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include "i18ninterface.h"
#include "plugininfo.h"
#include "plugininterface.h"
#include "windowinterface.h"

//==============================================================================

namespace OpenCOR {
namespace SampleWindow {

//==============================================================================

PLUGININFO_FUNC SampleWindowPluginInfo();

//==============================================================================

class SampleWindowWindow;

//==============================================================================

class SampleWindowPlugin : public QObject, public I18nInterface,
                           public PluginInterface, public WindowInterface
{
    Q_OBJECT

    Q_PLUGIN_METADATA(IID "OpenCOR.SampleWindowPlugin" FILE "samplewindowplugin.json")

    Q_INTERFACES(OpenCOR::I18nInterface)
    Q_INTERFACES(OpenCOR::PluginInterface)
    Q_INTERFACES(OpenCOR::WindowInterface)

public:
#include "i18ninterface.inl"
#include "plugininterface.inl"
#include "windowinterface.inl"

private:
    QAction *mSampleWindowAction;

    SampleWindowWindow *mSampleWindowWindow;
};

//==============================================================================

}   // namespace SampleWindow
}   // 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:

39
40
41
42
43
44
45
46
47
48
49
PLUGININFO_FUNC SampleWindowPluginInfo()
{
    Descriptions descriptions;

    descriptions.insert("en", QString::fromUtf8("a plugin that provides an addition window."));
    descriptions.insert("fr", QString::fromUtf8("une extension qui fournit une fenêtre d'addition."));

    return new PluginInfo(PluginInfo::Sample, true, false,
                          QStringList() << "Core" << "Sample",
                          descriptions);
}

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
53
54
55
56
57
58
59
60
61
62
63
64
//==============================================================================
// I18n interface
//==============================================================================

void SampleWindowPlugin::retranslateUi()
{
    // Retranslate our Sample window action

    retranslateAction(mSampleWindowAction,
                      tr("Sample"),
                      tr("Show/hide the Sample window"));
}

//==============================================================================

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
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//==============================================================================
// Plugin interface
//==============================================================================

bool SampleWindowPlugin::definesPluginInterfaces()
{
    // We don't handle this interface...

    return false;
}

//==============================================================================

bool SampleWindowPlugin::pluginInterfacesOk(const QString &pFileName,
                                            QObject *pInstance)
{
    Q_UNUSED(pFileName);
    Q_UNUSED(pInstance);

    // We don't handle this interface...

    return false;
}

//==============================================================================

void SampleWindowPlugin::initializePlugin()
{
    // Create an action to show/hide our Sample window

    mSampleWindowAction = Core::newAction(true, Core::mainWindow());

    // Create our Sample window

    mSampleWindowWindow = new SampleWindowWindow(Core::mainWindow());
}

//==============================================================================

void SampleWindowPlugin::finalizePlugin()
{
    // We don't handle this interface...
}

//==============================================================================

void SampleWindowPlugin::pluginsInitialized(const Plugins &pLoadedPlugins)
{
    Q_UNUSED(pLoadedPlugins);

    // We don't handle this interface...
}

//==============================================================================

void SampleWindowPlugin::loadSettings(QSettings *pSettings)
{
    Q_UNUSED(pSettings);

    // We don't handle this interface...
}

//==============================================================================

void SampleWindowPlugin::saveSettings(QSettings *pSettings) const
{
    Q_UNUSED(pSettings);

    // We don't handle this interface...
}

//==============================================================================

void SampleWindowPlugin::handleUrl(const QUrl &pUrl)
{
    Q_UNUSED(pUrl);

    // We don't handle this interface...
}

//==============================================================================

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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//==============================================================================
// Window interface
//==============================================================================

Qt::DockWidgetArea SampleWindowPlugin::windowDefaultDockArea() const
{
    // Return our default dock area

    return Qt::TopDockWidgetArea;
}

//==============================================================================

QAction * SampleWindowPlugin::windowAction() const
{
    // Return our window action

    return mSampleWindowAction;
}

//==============================================================================

QDockWidget * SampleWindowPlugin::windowWidget() const
{
    // Return our window widget

    return mSampleWindowWindow;
}

//==============================================================================

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include "windowwidget.h"

//==============================================================================

namespace Ui {
    class SampleWindowWindow;
}   // namespace Ui

//==============================================================================

namespace OpenCOR {
namespace SampleWindow {

//==============================================================================

class SampleWindowWindow : public Core::WindowWidget
{
    Q_OBJECT

public:
    explicit SampleWindowWindow(QWidget *pParent);
    ~SampleWindowWindow() override;

private:
    Ui::SampleWindowWindow *mGui;

private slots:
    void updateSum();
};

//==============================================================================

}   // namespace SampleWindow
}   // 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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include "sampleutilities.h"
#include "samplewindowwindow.h"

//==============================================================================

#include "ui_samplewindowwindow.h"

//==============================================================================

namespace OpenCOR {
namespace SampleWindow {

//==============================================================================

SampleWindowWindow::SampleWindowWindow(QWidget *pParent) :
    Core::WindowWidget(pParent),
    mGui(new Ui::SampleWindowWindow)
{
    // Set up the GUI

    mGui->setupUi(this);

    // A couple of connections to update our sum whenever one of the value of
    // one of our numbers is updated

    connect(mGui->nb1DoubleSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
            this, &SampleWindowWindow::updateSum);
    connect(mGui->nb2DoubleSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
            this, &SampleWindowWindow::updateSum);

    // Initialise our sum

    updateSum();
}

//==============================================================================

SampleWindowWindow::~SampleWindowWindow()
{
    // Delete the GUI

    delete mGui;
}

//==============================================================================

void SampleWindowWindow::updateSum()
{
    // Update our sum

    mGui->sumLabel->setText(QString::number(Sample::add(mGui->nb1DoubleSpinBox->value(), mGui->nb2DoubleSpinBox->value())));
}

//==============================================================================

}   // namespace SampleWindow
}   // 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).