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.
i18n
└─ SampleTools_fr.ts
res
└─ SampleTools_i18n.qrc.in
src
├─ sampletoolsplugin.cpp
├─ sampletoolsplugin.h
└─ sampletoolsplugin.json
CMakeLists.txt
Our plugin is part of the Sample category, which means that its code can be found under [OpenCOR]/src/plugins/sample/SampleTools/
.
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 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.
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).
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 static const Descriptions descriptions = {
48 { "en", QString::fromUtf8("a plugin that provides an addition tool.") },
49 { "fr", QString::fromUtf8("une extension qui fournit un outil d'addition.") }
50 };
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).
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.
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 = nullptr;
66
67 void runHelpCommand();
68 bool 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
232bool 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 false;
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 false;
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 false;
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 true;
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.