GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: src/gui/dialog/pluginmanagerdialog.cc Lines: 0 219 0.0 %
Date: 2023-03-14 11:04:37 Branches: 0 542 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