Sample tools

The purpose of this plugin is to extend our Sample plugin by making it possible for the user to add two numbers, be it from the command line or through OpenCOR’s Tools menu.

File structure

i18n
 └─ SampleTools_fr.ts
res
 └─ SampleTools_i18n.qrc.in
src
 ├─ sampletoolsplugin.cpp
 ├─ sampletoolsplugin.h
 └─ sampletoolsplugin.json
CMakeLists.txt

Category

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

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 work from the command line, so we need to implement the CLI interface. We also want our plugin to work through OpenCOR’s Tools menu, which involves creating a menu item and making it available to OpenCOR, so that it can add it for us to its Tools menu. To do these, we need to implement both the GUI and Plugin 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(SampleToolsPlugin)
 2
 3# Add the plugin
 4
 5add_plugin(SampleTools
 6    SOURCES
 7        ../../cliinterface.cpp
 8        ../../guiinterface.cpp
 9        ../../i18ninterface.cpp
10        ../../plugininfo.cpp
11        ../../plugininterface.cpp
12
13        src/sampletoolsplugin.cpp
14    PLUGINS
15        Core
16        Sample
17)

The interfaces our plugin implements come with a .cpp file, so we reference them (lines 7-9 and 11). Then, our plugin needs the Core and Sample plugins (the latter, to be able to use its add() function), so they are referenced (lines 15 and 16) using the PLUGINS keyword (line 14).

Plugin information

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

28#include "cliinterface.h"
29#include "guiinterface.h"
30#include "i18ninterface.h"
31#include "plugininfo.h"
32#include "plugininterface.h"
33
34//==============================================================================
35
36namespace OpenCOR {
37namespace SampleTools {
38
39//==============================================================================
40
41PLUGININFO_FUNC SampleToolsPluginInfo();
42
43//==============================================================================
44
45class SampleToolsPlugin : public QObject, public CliInterface,
46                          public GuiInterface, public I18nInterface,
47                          public PluginInterface
48{
49    Q_OBJECT
50
51    Q_PLUGIN_METADATA(IID "OpenCOR.SampleToolsPlugin" FILE "sampletoolsplugin.json")
52
53    Q_INTERFACES(OpenCOR::CliInterface)
54    Q_INTERFACES(OpenCOR::GuiInterface)
55    Q_INTERFACES(OpenCOR::I18nInterface)
56    Q_INTERFACES(OpenCOR::PluginInterface)
57
58public:
59#include "cliinterface.inl"
60#include "guiinterface.inl"
61#include "i18ninterface.inl"
62#include "plugininterface.inl"
63
64private:
65    QAction *mAddTwoNumbersAction = nullptr;
66
67    void runHelpCommand();
68    bool runAddCommand(const QStringList &pArguments);
69
70private slots:
71    void addTwoNumbers();
72};
73
74//==============================================================================
75
76} // namespace SampleTools
77} // namespace OpenCOR

As mentioned above, our plugin implements some interfaces, which means that their header file is included (lines 28-30 and 32). It also means that our plugin class inherits from those interfaces (lines 45-47), as well as makes calls to the Q_INTERFACES() macro to let Qt know which interfaces it implements (lines 53-56). Finally, we include the inline files (lines 59-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 sampletoolsplugin.cpp:

45PLUGININFO_FUNC SampleToolsPluginInfo()
46{
47    Descriptions descriptions;
48
49    descriptions.insert("en", QString::fromUtf8("a plugin that provides an addition tool."));
50    descriptions.insert("fr", QString::fromUtf8("une extension qui fournit un outil d'addition."));
51
52    return new PluginInfo(PluginInfo::Category::Sample, true, true,
53                          { "Core", "Sample" },
54                          descriptions);
55}

As can be seen, our plugin is selectable by the user and it offers direct CLI support (line 52). It also has a direct dependency on the Core and Sample plugins (line 53).

Interfaces implementation

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

We start with the CLI interface:

57//==============================================================================
58// CLI interface
59//==============================================================================
60
61bool SampleToolsPlugin::executeCommand(const QString &pCommand,
62                                       const QStringList &pArguments, int &pRes)
63{
64    Q_UNUSED(pRes)
65
66    // Run the given CLI command
67
68    static const QString Help = "help";
69    static const QString Add  = "add";
70
71    if (pCommand == Help) {
72        // Display the commands that we support
73
74        runHelpCommand();
75
76        return true;
77    }
78
79    if (pCommand == Add) {
80        // Add some numbers
81
82        return runAddCommand(pArguments);
83    }
84
85    // Not a CLI command that we support, so show our help and leave
86
87    runHelpCommand();
88
89    return false;
90}
91
92//==============================================================================

As can be seen, our plugin handles both the help and add commands (lines 71-77 and 79-83, respectively).

Next, we have the GUI interface:

 92//==============================================================================
 93// GUI interface
 94//==============================================================================
 95
 96void SampleToolsPlugin::updateGui(Plugin *pViewPlugin, const QString &pFileName)
 97{
 98    Q_UNUSED(pViewPlugin)
 99    Q_UNUSED(pFileName)
100
101    // We don't handle this interface...
102}
103
104//==============================================================================
105
106Gui::Menus SampleToolsPlugin::guiMenus() const
107{
108    // We don't handle this interface...
109
110    return {};
111}
112
113//==============================================================================
114
115Gui::MenuActions SampleToolsPlugin::guiMenuActions() const
116{
117    // Return our menu actions
118
119    return Gui::MenuActions() << Gui::MenuAction(Gui::MenuAction::Type::Tools, mAddTwoNumbersAction)
120                              << Gui::MenuAction(Gui::MenuAction::Type::Tools, Core::newSeparator(Core::mainWindow()));
121}
122
123//==============================================================================

Our plugin does not need to do anything whenever OpenCOR needs to update the GUI, so we do nothing in updateGui() (lines 96-102). Similarly, we do not need to add menus to OpenCOR, so all guiMenus() does is return Gui::Menus() (lines 106-111). However, we do want to add a menu action (and a menu separator) to OpenCOR’s Tools menu, which we do via guiMenuActions() (lines 115-121). Note that mAddTwoNumbersAction is initialised in our implementation of the Plugin interface (see below).

After the GUI interface, we have the Internationalisation interface:

123//==============================================================================
124// I18n interface
125//==============================================================================
126
127void SampleToolsPlugin::retranslateUi()
128{
129    // Retranslate our different Tools actions
130
131    retranslateAction(mAddTwoNumbersAction, tr("Add Two Numbers..."), tr("Add two numbers together"));
132}
133
134//==============================================================================

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

Finally, we have the Plugin interface:

134//==============================================================================
135// Plugin interface
136//==============================================================================
137
138bool SampleToolsPlugin::definesPluginInterfaces()
139{
140    // We don't handle this interface...
141
142    return false;
143}
144
145//==============================================================================
146
147bool SampleToolsPlugin::pluginInterfacesOk(const QString &pFileName,
148                                           QObject *pInstance)
149{
150    Q_UNUSED(pFileName)
151    Q_UNUSED(pInstance)
152
153    // We don't handle this interface...
154
155    return false;
156}
157
158//==============================================================================
159
160void SampleToolsPlugin::initializePlugin()
161{
162    // Create our Add Two Numbers action
163
164    mAddTwoNumbersAction = new QAction(Core::mainWindow());
165
166    // A connection to handle our Add Two Numbers action
167
168    connect(mAddTwoNumbersAction, &QAction::triggered,
169            this, &SampleToolsPlugin::addTwoNumbers);
170}
171
172//==============================================================================
173
174void SampleToolsPlugin::finalizePlugin()
175{
176    // We don't handle this interface...
177}
178
179//==============================================================================
180
181void SampleToolsPlugin::pluginsInitialized(const Plugins &pLoadedPlugins)
182{
183    Q_UNUSED(pLoadedPlugins)
184
185    // We don't handle this interface...
186}
187
188//==============================================================================
189
190void SampleToolsPlugin::loadSettings(QSettings &pSettings)
191{
192    Q_UNUSED(pSettings)
193
194    // We don't handle this interface...
195}
196
197//==============================================================================
198
199void SampleToolsPlugin::saveSettings(QSettings &pSettings) const
200{
201    Q_UNUSED(pSettings)
202
203    // We don't handle this interface...
204}
205
206//==============================================================================
207
208void SampleToolsPlugin::handleUrl(const QUrl &pUrl)
209{
210    Q_UNUSED(pUrl)
211
212    // We don't handle this interface...
213}
214
215//==============================================================================

The only method of interest to our plugin is initializePlugin() (lines 160-170), which is where we initialise mAddTwoNumbersAction, among other things. All the other methods (definesPluginInterfaces(), pluginInterfacesOk(), finalizePlugin(), pluginsInitialized(), loadSettings(), saveSettings() and handleUrl()) are left empty.

Plugin specific

Some extra methods are needed to get our plugin to do what it is supposed to be doing. They are declared in the SampleToolsPlugin class in sampletoolsplugin.h:

64private:
65    QAction *mAddTwoNumbersAction;
66
67    void runHelpCommand();
68    int runAddCommand(const QStringList &pArguments);
69
70private slots:
71    void addTwoNumbers();

Their implementation can be found in sampletoolsplugin.cpp:

215//==============================================================================
216// Plugin specific
217//==============================================================================
218
219void SampleToolsPlugin::runHelpCommand()
220{
221    // Output the commands we support
222
223    std::cout << "Commands supported by the SampleTools plugin:" << std::endl;
224    std::cout << " * Display the commands supported by the SampleTools plugin:" << std::endl;
225    std::cout << "      help" << std::endl;
226    std::cout << " * Add two numbers:" << std::endl;
227    std::cout << "      add <nb1> <nb2>" << std::endl;
228}
229
230//==============================================================================
231
232int SampleToolsPlugin::runAddCommand(const QStringList &pArguments)
233{
234    // Make sure that we have the correct number of arguments
235
236    if (pArguments.count() != 2) {
237        runHelpCommand();
238
239        return -1;
240    }
241
242    // Make sure that the two arguments are valid numbers
243
244    bool ok;
245
246    double nb1 = pArguments.first().toDouble(&ok);
247
248    if (!ok) {
249        std::cout << "Sorry, but " << qPrintable(pArguments.first()) << " is not a valid number." << std::endl;
250
251        return -1;
252    }
253
254    double nb2 = pArguments.last().toDouble(&ok);
255
256    if (!ok) {
257        std::cout << "Sorry, but " << qPrintable(pArguments.last()) << " is not a valid number." << std::endl;
258
259        return -1;
260    }
261
262    // Add the two numbers and output the result
263
264    std::cout << qPrintable(pArguments.first()) << " + " << qPrintable(pArguments.last()) << " = " << Sample::add(nb1, nb2) << std::endl;
265
266    return 0;
267}
268
269//==============================================================================
270
271void SampleToolsPlugin::addTwoNumbers()
272{
273    bool ok;
274    double nb1 = QInputDialog::getDouble(Core::mainWindow(), tr("Add Two Numbers"), tr("First number:"),
275                                         0, -2147483647, 2147483647, 1, &ok);
276    double nb2;
277
278    if (ok) {
279        nb2 = QInputDialog::getDouble(Core::mainWindow(), tr("Add Two Numbers"), tr("Second number:"),
280                                      0, -2147483647, 2147483647, 1, &ok);
281
282        if (ok) {
283            Core::informationMessageBox(tr("Add Two Numbers"),
284                                        QString::number(nb1)+" + "+QString::number(nb2)+" = "+QString::number(Sample::add(nb1, nb2)));
285        }
286    }
287}
288
289//==============================================================================

runHelpCommand() (lines 219-228) is the method that is executed whenever our plugin is asked to handle the help command. It provides the user with some information about the commands it supports. In a similar way, runAddCommand() (lines 232-267) is executed whenever our plugin is asked to handle the add command. It checks that two numbers have been passed and, if so, returns their sum to the user.

addTwoNumbers() (lines 271-287) is a Qt slot that is executed whenever the user selects our menu item (see mAddTwoNumbersAction). Using a GUI approach, it asks the user to provide two numbers and returns their sum, unless the user decides to cancel the action.