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.

Prerequisites
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 (click here for some information on 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 as well internationalise our plugin, in which case it means that we also need to implement the Internationalisation interface.

CMake project

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

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
    INCLUDE_DIRS
        src
    PLUGINS
        Core
        Sample
)

One of the interfaces our plugin implements comes with a .cpp file, so we reference it (lines 7 and 9-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 22 and 23) using the PLUGINS keyword (line 21). 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:

#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 27, 29 and 30). It also means that our plugin class inherits from those interfaces (lines 47-48), as well as make calls to the Q_INTERFACES() macro to let Qt know which interfaces it implements (lines 54-56). Finally, we include the inline files (lines 59-61) that declare various methods that must be implemented by our plugin (see the next section). (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:

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 direct CLI support (line 45). It also has a direct dependency on both the Core and Sample plugins (line 46).

Interfaces implementation

The implementation of the interfaces' various methods can also be found in samplewindowplugin.cpp. The methods are grouped by interface and are ordered alphabetically. The interfaces are also ordered alphabetically, making it easier to read and maintain the code.

We start with the Internationalisation interface:

//==============================================================================
// 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 here is to (re)translate mSampleWindowAction with the actual (French) translations in SampleWindow_fr.ts (together with some other translations needed in the next section). Note that mSampleWindowAction is initialised in our implementation of the Plugin interface (see below).

Next, we have the Plugin interface:

//==============================================================================
// 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 89-98), which is where we initialise both mSampleWindowAction and mSampleWindowWindow . All the other methods (finalizePlugin(), pluginsInitialized(), loadSettings(), saveSettings() and handleUrl()) are left empty.

Finally, we have the Window interface:

//==============================================================================
// 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 147-152), as well as provide the pointer to our action (see windowAction(); lines 156-161) and window (see windowWidget(); lines 165-170).

Plugin specific

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

#include "windowwidget.h"

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

namespace Ui {
    class SampleWindowWindow;
}

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

namespace OpenCOR {
namespace SampleWindow {

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

class SampleWindowWindow : public Core::WindowWidget
{
    Q_OBJECT

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

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 (line 42). 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:

#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, SIGNAL(valueChanged(double)),
            this, SLOT(updateSum()));
    connect(mGui->nb2DoubleSpinBox, SIGNAL(valueChanged(double)),
            this, SLOT(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 37-56) initialises the SampleWindowWindow object, as well as creates a couple of connections (lines 48-51) and initialises our sum by calling updateSum() (line 55). As can be seen, updateSum() calls the add() method from the Sample plugin (lines 69-74).