GCC Code Coverage Report


Directory: ./
File: src/gui/dialog/pluginmanagerdialog.cc
Date: 2024-12-20 15:53:58
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