GCC Code Coverage Report


Directory: ./
File: src/gui/dialog/pluginmanagerdialog.cc
Date: 2025-04-14 11:18:00
Exec Total Coverage
Lines: 0 240 0.0%
Branches: 0 510 0.0%

Line Branch Exec Source
1 // Copyright (c) 2015-2018, LAAS-CNRS
2 // Authors: Joseph Mirabel (joseph.mirabel@laas.fr)
3 //
4 // This file is part of gepetto-viewer.
5 // gepetto-viewer is free software: you can redistribute it
6 // and/or modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation, either version
8 // 3 of the License, or (at your option) any later version.
9 //
10 // gepetto-viewer is distributed in the hope that it will be
11 // useful, but WITHOUT ANY WARRANTY; without even the implied warranty
12 // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Lesser Public License for more details. You should have
14 // received a copy of the GNU Lesser General Public License along with
15 // gepetto-viewer. If not, see <http://www.gnu.org/licenses/>.
16
17 #include "gepetto/gui/dialog/pluginmanagerdialog.hh"
18
19 #include <QDebug>
20 #include <QMenu>
21
22 #include "gepetto/gui/mainwindow.hh"
23 #include "gepetto/gui/plugin-interface.hh"
24 #include "ui_pluginmanagerdialog.h"
25 #if GEPETTO_GUI_HAS_PYTHONQT
26 #include "gepetto/gui/pythonwidget.hh"
27 #endif
28
29 #include <iostream>
30
31 namespace gepetto {
32 namespace gui {
33 QList<QDir> PluginManager::pluginDirs_;
34
35 ✗ QIcon PluginManager::icon(const QPluginLoader* pl) {
36 ✗ if (pl->isLoaded()) {
37 ✗ const PluginInterface* pi = const_instance_cast<PluginInterface>(pl);
38 ✗ if (pi && pi->isInit()) {
39 ✗ return QApplication::style()->standardIcon(QStyle::SP_DialogOkButton);
40 }
41 ✗ return QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning);
42 }
43 ✗ return QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical);
44 }
45
46 ✗ QIcon pyicon(bool loaded) {
47 ✗ if (loaded)
48 ✗ return QApplication::style()->standardIcon(QStyle::SP_DialogOkButton);
49 ✗ return QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical);
50 }
51
52 ✗ QString PluginManager::status(const QPluginLoader* pl) {
53 ✗ if (pl->isLoaded()) {
54 ✗ const PluginInterface* pi = const_instance_cast<PluginInterface>(pl);
55 ✗ if (pi) {
56 ✗ if (pi->isInit())
57 ✗ return QString("Plugin loaded correctly");
58 else
59 ✗ return pi->errorMsg();
60 } else
61 ✗ return QString("Wrong interface");
62 } else
63 ✗ return pl->errorString();
64 }
65
66 ✗ void PluginManager::addPluginDir(const QString& path) {
67 ✗ QDir dir(QDir::cleanPath(path));
68 ✗ if (!dir.exists() || !dir.isReadable()) return;
69 ✗ QDir can(dir.canonicalPath());
70 ✗ if (can.exists() && can.isReadable() && !pluginDirs_.contains(can))
71 ✗ pluginDirs_.append(can);
72 }
73
74 ✗ void PluginManager::declareAllPlugins(QWidget* parent) {
75 ✗ foreach (const QDir& dir, pluginDirs_) {
76 ✗ qDebug() << "Looking for plugins into" << dir.absolutePath();
77 ✗ QStringList soFiles = dir.entryList(QStringList() << "*.so", QDir::Files);
78 ✗ foreach (const QString& soFile, soFiles) {
79 ✗ qDebug() << "Found" << soFile;
80 ✗ if (!plugins_.contains(soFile)) {
81 ✗ plugins_[soFile] =
82 ✗ new QPluginLoader(dir.absoluteFilePath(soFile), parent);
83 }
84 }
85 }
86 }
87
88 ✗ bool PluginManager::declarePlugin(const QString& name, QWidget* parent) {
89 ✗ if (!plugins_.contains(name)) {
90 ✗ QString filename = name;
91 ✗ if (!QDir::isAbsolutePath(name)) {
92 ✗ foreach (QDir dir, pluginDirs_) {
93 ✗ if (dir.exists(name)) {
94 ✗ filename = dir.absoluteFilePath(name);
95 ✗ break;
96 }
97 }
98 }
99 ✗ plugins_[name] = new QPluginLoader(filename, parent);
100 ✗ return true;
101 }
102 ✗ if (name != "omniorbserver.so")
103 ✗ qDebug() << "Plugin" << name << "already declared.";
104 ✗ return false;
105 }
106
107 ✗ bool PluginManager::loadPlugin(const QString& name) {
108 ✗ if (!plugins_.contains(name)) {
109 ✗ qDebug() << "Plugin" << name << "not declared.";
110 ✗ return false;
111 }
112 ✗ if (!plugins_[name]->load()) {
113 static bool hasShownErrorMessage = false;
114 ✗ qDebug() << name << ": " << plugins_[name]->errorString();
115 ✗ if (!hasShownErrorMessage) {
116 ✗ qDebug() << "The list of paths where plugins are searched for is:";
117 ✗ foreach (const QDir& dir, pluginDirs_) {
118 ✗ qDebug() << dir.absolutePath();
119 }
120 ✗ qDebug()
121 << "Use GEPETTO_GUI_PLUGIN_DIRS environment variable to add a path.";
122 ✗ hasShownErrorMessage = true;
123 }
124 ✗ return false;
125 }
126 ✗ return true;
127 }
128
129 ✗ bool PluginManager::initPlugin(const QString& name) {
130 ✗ if (!plugins_.contains(name)) {
131 ✗ qDebug() << "Plugin" << name << "not declared.";
132 ✗ return false;
133 }
134 ✗ QPluginLoader* p = plugins_[name];
135 ✗ if (!p->isLoaded()) {
136 ✗ qDebug() << "Plugin" << name << "not loaded:" << p->errorString();
137 ✗ return false;
138 }
139 ✗ PluginInterface* pi = qobject_cast<PluginInterface*>(p->instance());
140 ✗ if (!pi) {
141 ✗ qDebug() << name << ": Wrong interface.";
142 ✗ return false;
143 }
144 ✗ if (!pi->isInit()) pi->doInit();
145 ✗ return pi->isInit();
146 }
147
148 ✗ bool PluginManager::unloadPlugin(const QString& name) {
149 ✗ return plugins_[name]->unload();
150 }
151
152 ✗ void PluginManager::clearPlugins() {
153 ✗ foreach (QPluginLoader* p, plugins_) {
154 ✗ p->unload();
155 }
156 }
157
158 ✗ void PluginManager::declareAllPyPlugins() {
159 QSettings settings(QSettings::SystemScope,
160 ✗ QCoreApplication::organizationName(), "pyplugin");
161 ✗ QDir pypluginConf(settings.fileName() + ".d");
162
163 ✗ qDebug() << "Looking for declared pyplugins into"
164 << pypluginConf.absolutePath();
165 ✗ QStringList confFiles = pypluginConf.entryList(QDir::Files);
166 ✗ foreach (const QString& conf, confFiles) {
167 ✗ QFile file(pypluginConf.filePath(conf));
168 ✗ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
169 ✗ qDebug() << "Could not open" << file.fileName();
170 ✗ continue;
171 }
172
173 ✗ if (file.atEnd()) {
174 ✗ qDebug() << "Error: Empty file: " << file.fileName();
175 ✗ continue;
176 }
177 ✗ qDebug() << "Found" << file.readLine();
178 ✗ if (file.atEnd()) {
179 ✗ qDebug() << "Error: Does not contain a module: " << file.fileName();
180 ✗ continue;
181 }
182 ✗ QString name(file.readLine());
183 ✗ if (!file.atEnd()) {
184 ✗ qDebug() << "Error: Should contain only two lines: " << file.fileName();
185 ✗ continue;
186 }
187 ✗ if (!pyplugins_.contains(name)) {
188 ✗ declarePyPlugin(name);
189 }
190 }
191 }
192
193 ✗ bool PluginManager::declarePyPlugin(const QString& name) {
194 ✗ if (!pyplugins_.contains(name)) {
195 ✗ if (name.endsWith(".py")) {
196 ✗ QFileInfo fi(name);
197 ✗ QString moduleName = fi.baseName();
198 ✗ QString script;
199 ✗ if (fi.isAbsolute())
200 ✗ script = name;
201 else
202 ✗ script = QDir::currentPath() + QDir::separator() + name;
203 ✗ pyplugins_[name] = script;
204 ✗ } else
205 ✗ pyplugins_[name] = name;
206 ✗ return true;
207 }
208 ✗ qDebug() << "Python plugin" << name << "already declared.";
209 ✗ return false;
210 }
211
212 ✗ bool PluginManager::loadPyPlugin(const QString& name) {
213 ✗ if (!pyplugins_.contains(name)) {
214 ✗ qDebug() << "Python plugin" << name << "not declared.";
215 ✗ return false;
216 }
217 ✗ MainWindow* main = MainWindow::instance();
218 ✗ const QString& pyfile = pyplugins_[name];
219
220 #if GEPETTO_GUI_HAS_PYTHONQT
221 ✗ PythonWidget* pw = main->pythonWidget();
222 ✗ if (pyfile.endsWith(".py")) {
223 ✗ qDebug() << "Loading" << pyfile << "into module" << name;
224 ✗ pw->loadScriptPlugin(name, pyfile);
225 } else
226 ✗ pw->loadModulePlugin(name);
227 ✗ return true;
228 #else
229 main->logError(
230 "gepetto-viewer was compiled without GEPETTO_GUI_HAS_"
231 "PYTHONQT flag. Cannot not load Python plugin " +
232 name);
233 return false;
234 #endif
235 }
236
237 ✗ bool PluginManager::unloadPyPlugin(const QString& name) {
238 ✗ MainWindow* main = MainWindow::instance();
239 #if GEPETTO_GUI_HAS_PYTHONQT
240 ✗ PythonWidget* pw = main->pythonWidget();
241 ✗ pw->unloadModulePlugin(name);
242 ✗ return true;
243 #else
244 main->logError(
245 "gepetto-viewer was compiled without GEPETTO_GUI_HAS_"
246 "PYTHONQT flag. Cannot not unload Python plugin " +
247 name);
248 return false;
249 #endif
250 }
251
252 ✗ bool PluginManager::isPyPluginLoaded(const QString& name) {
253 #if GEPETTO_GUI_HAS_PYTHONQT
254 ✗ MainWindow* main = MainWindow::instance();
255 ✗ if (!main) return false;
256 ✗ PythonWidget* pw = main->pythonWidget();
257 ✗ return pw->hasPlugin(name);
258 #else
259 return false;
260 #endif
261 }
262
263 ✗ void PluginManager::clearPyPlugins() {
264 ✗ foreach (QString p, pyplugins_.keys()) {
265 ✗ unloadPyPlugin(p);
266 }
267 }
268
269 ✗ PluginManagerDialog::PluginManagerDialog(PluginManager* pm, QWidget* parent)
270 ✗ : QDialog(parent), ui_(new ::Ui::PluginManagerDialog), pm_(pm) {
271 ✗ ui_->setupUi(this);
272
273 ✗ updateList();
274
275 // C++ plugin list
276 ✗ ui_->pluginList->setColumnHidden(P_FILE, true);
277
278 ✗ connect(ui_->pluginList,
279 SIGNAL(currentItemChanged(QTableWidgetItem*, QTableWidgetItem*)),
280 SLOT(onItemChanged(QTableWidgetItem*, QTableWidgetItem*)));
281 ✗ connect(ui_->pluginList, SIGNAL(customContextMenuRequested(QPoint)),
282 SLOT(contextMenu(QPoint)));
283
284 // Python plugin list
285 // ui_->pypluginList->setColumnHidden(P_FILE, true);
286 ✗ ui_->pypluginList->setColumnHidden(P_FULLPATH, true);
287
288 ✗ connect(ui_->pypluginList, SIGNAL(customContextMenuRequested(QPoint)),
289 SLOT(pyContextMenu(QPoint)));
290
291 // Buttons
292 ✗ connect(ui_->declareAllPluginsButton, SIGNAL(clicked()), SLOT(declareAll()));
293 ✗ connect(ui_->saveButton, SIGNAL(clicked()), SLOT(save()));
294 }
295
296 ✗ PluginManagerDialog::~PluginManagerDialog() { delete ui_; }
297
298 ✗ void PluginManagerDialog::onItemChanged(QTableWidgetItem* current,
299 QTableWidgetItem* /*previous*/) {
300 ✗ if (!current) return;
301 ✗ QString key = ui_->pluginList->item(current->row(), P_FILE)->text();
302 ✗ const QPluginLoader* pl = pm_->plugins()[key];
303 ✗ ui_->pluginMessage->setText(pm_->status(pl));
304 }
305
306 ✗ void PluginManagerDialog::contextMenu(const QPoint& pos) {
307 ✗ int row = ui_->pluginList->rowAt(pos.y());
308 ✗ if (row == -1) return;
309 ✗ QString key = ui_->pluginList->item(row, P_FILE)->text();
310 ✗ QMenu contextMenu(tr("Plugin"), ui_->pluginList);
311 ✗ QSignalMapper sm;
312 ✗ if (pm_->plugins()[key]->isLoaded()) {
313 ✗ QAction* unload = contextMenu.addAction("&Unload", &sm, SLOT(map()));
314 ✗ sm.setMapping(unload, key);
315 ✗ connect(&sm, SIGNAL(mapped(QString)), this, SLOT(unload(QString)));
316 } else {
317 ✗ QAction* load = contextMenu.addAction("&Load", &sm, SLOT(map()));
318 ✗ sm.setMapping(load, key);
319 ✗ connect(&sm, SIGNAL(mapped(QString)), this, SLOT(load(QString)));
320 }
321 ✗ contextMenu.exec(ui_->pluginList->mapToGlobal(pos));
322 }
323
324 ✗ void PluginManagerDialog::load(const QString& name) {
325 ✗ pm_->loadPlugin(name);
326 ✗ pm_->initPlugin(name);
327 ✗ updateList();
328 }
329
330 ✗ void PluginManagerDialog::unload(const QString& name) {
331 ✗ pm_->unloadPlugin(name);
332 ✗ updateList();
333 }
334
335 ✗ void PluginManagerDialog::pyContextMenu(const QPoint& pos) {
336 ✗ int row = ui_->pypluginList->rowAt(pos.y());
337 ✗ if (row == -1) return;
338 ✗ QString key = ui_->pypluginList->item(row, P_NAME)->text();
339 ✗ QMenu contextMenu(tr("PythonPlugin"), ui_->pypluginList);
340 ✗ QSignalMapper sm;
341 ✗ if (pm_->isPyPluginLoaded(key)) {
342 ✗ QAction* unload = contextMenu.addAction("&Unload", &sm, SLOT(map()));
343 ✗ sm.setMapping(unload, key);
344 ✗ connect(&sm, SIGNAL(mapped(QString)), this, SLOT(pyUnload(QString)));
345 } else {
346 ✗ QAction* load = contextMenu.addAction("&Load", &sm, SLOT(map()));
347 ✗ sm.setMapping(load, key);
348 ✗ connect(&sm, SIGNAL(mapped(QString)), this, SLOT(pyLoad(QString)));
349 }
350 ✗ contextMenu.exec(ui_->pypluginList->mapToGlobal(pos));
351 }
352
353 ✗ void PluginManagerDialog::pyLoad(const QString& name) {
354 ✗ pm_->loadPyPlugin(name);
355 ✗ updateList();
356 }
357
358 ✗ void PluginManagerDialog::pyUnload(const QString& name) {
359 ✗ pm_->unloadPyPlugin(name);
360 ✗ updateList();
361 }
362
363 ✗ void PluginManagerDialog::declareAll() {
364 ✗ pm_->declareAllPlugins();
365 ✗ pm_->declareAllPyPlugins();
366 ✗ updateList();
367 }
368
369 ✗ void PluginManagerDialog::save() {
370 ✗ MainWindow::instance()->settings_->writeSettingFile();
371 }
372
373 const int PluginManagerDialog::P_NAME = 0;
374 const int PluginManagerDialog::P_FILE = 1;
375 const int PluginManagerDialog::P_VERSION = 2;
376 const int PluginManagerDialog::P_FULLPATH = 3;
377
378 ✗ void PluginManagerDialog::updateList() {
379 ✗ while (ui_->pluginList->rowCount() > 0) ui_->pluginList->removeRow(0);
380 ✗ while (ui_->pypluginList->rowCount() > 0) ui_->pypluginList->removeRow(0);
381 ✗ for (PluginManager::Map::const_iterator p = pm_->plugins().constBegin();
382 ✗ p != pm_->plugins().constEnd(); p++) {
383 ✗ QString name = p.key(), filename = p.key(),
384 ✗ fullpath = p.value()->fileName(), version = "";
385 ✗ if (p.value()->isLoaded()) {
386 PluginInterface* pi =
387 ✗ qobject_cast<PluginInterface*>(p.value()->instance());
388 ✗ if (pi) {
389 ✗ name = pi->name();
390 // version = pi->version();
391 }
392 }
393 ✗ QIcon icon = pm_->icon(p.value());
394
395 ✗ ui_->pluginList->insertRow(ui_->pluginList->rowCount());
396 ✗ ui_->pluginList->setItem(ui_->pluginList->rowCount() - 1, P_NAME,
397 ✗ new QTableWidgetItem(icon, name));
398 ✗ ui_->pluginList->setItem(ui_->pluginList->rowCount() - 1, P_FILE,
399 ✗ new QTableWidgetItem(filename));
400 ✗ ui_->pluginList->setItem(ui_->pluginList->rowCount() - 1, P_VERSION,
401 ✗ new QTableWidgetItem(version));
402 ✗ ui_->pluginList->setItem(ui_->pluginList->rowCount() - 1, P_FULLPATH,
403 ✗ new QTableWidgetItem(fullpath));
404 }
405 ✗ ui_->pluginList->resizeColumnsToContents();
406
407 ✗ for (PluginManager::PyMap::const_iterator p = pm_->pyplugins().constBegin();
408 ✗ p != pm_->pyplugins().constEnd(); p++) {
409 ✗ QString name = p.key(), filename = p.value(), version = "";
410 ✗ QIcon icon = pyicon(pm_->isPyPluginLoaded(p.key()));
411
412 ✗ ui_->pypluginList->insertRow(ui_->pypluginList->rowCount());
413 ✗ ui_->pypluginList->setItem(ui_->pypluginList->rowCount() - 1, P_NAME,
414 ✗ new QTableWidgetItem(icon, name));
415 ✗ ui_->pypluginList->setItem(ui_->pypluginList->rowCount() - 1, P_FILE,
416 ✗ new QTableWidgetItem(filename));
417 // ui_->pypluginList->setItem(ui_->pypluginList->rowCount() - 1, P_VERSION,
418 // new QTableWidgetItem (version));
419 // ui_->pypluginList->setItem(ui_->pypluginList->rowCount() - 1, P_FULLPATH,
420 // new QTableWidgetItem (fullpath));
421 }
422 ✗ ui_->pluginList->resizeColumnsToContents();
423 }
424 } // namespace gui
425 } // namespace gepetto
426