GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: src/gui/dialog/pluginmanagerdialog.cc Lines: 2 213 0.9 %
Date: 2020-05-14 11:23:33 Branches: 2 546 0.4 %

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
#include "ui_pluginmanagerdialog.h"
19
20
#include <QDebug>
21
#include <QMenu>
22
23
#include "gepetto/gui/plugin-interface.hh"
24
#include "gepetto/gui/mainwindow.hh"
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
1
    QList <QDir> PluginManager::pluginDirs_;
34
35
    QIcon PluginManager::icon(const QPluginLoader *pl)
36
    {
37
      if (pl->isLoaded()) {
38
        const PluginInterface* pi = const_instance_cast <PluginInterface> (pl);
39
        if (pi && pi->isInit ()) {
40
          return QApplication::style()->standardIcon(QStyle::SP_DialogOkButton);
41
        }
42
        return QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning);
43
      }
44
      return QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical);
45
    }
46
47
    QIcon pyicon(bool loaded)
48
    {
49
      if (loaded)
50
        return QApplication::style()->standardIcon(QStyle::SP_DialogOkButton);
51
      return QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical);
52
    }
53
54
    QString PluginManager::status(const QPluginLoader *pl)
55
    {
56
      if (pl->isLoaded()) {
57
        const PluginInterface* pi = const_instance_cast <PluginInterface> (pl);
58
        if (pi) {
59
          if (pi->isInit ())
60
            return QString ("Plugin loaded correctly");
61
          else
62
            return pi->errorMsg ();
63
        } else
64
          return QString ("Wrong interface");
65
      } else
66
        return pl->errorString();
67
    }
68
69
    void PluginManager::addPluginDir(const QString &path)
70
    {
71
      QDir dir (QDir::cleanPath(path));
72
      QDir can (dir.canonicalPath());
73
      if (can.exists() && can.isReadable())
74
	{
75
	  pluginDirs_.append (can);
76
	}
77
    }
78
79
    void PluginManager::declareAllPlugins (QWidget *parent)
80
    {
81
      foreach (const QDir& dir, pluginDirs_) {
82
        qDebug() << "Looking for plugins into" << dir.absolutePath();
83
        QStringList soFiles = dir.entryList(QStringList() << "*.so", QDir::Files);
84
        foreach (const QString& soFile, soFiles) {
85
          qDebug() << "Found" << soFile;
86
          if (!plugins_.contains(soFile)) {
87
            plugins_[soFile] = new QPluginLoader (dir.absoluteFilePath(soFile), parent);
88
          }
89
        }
90
      }
91
    }
92
93
    bool PluginManager::declarePlugin(const QString &name, QWidget *parent)
94
    {
95
      if (!plugins_.contains(name)) {
96
        QString filename = name;
97
        if (!QDir::isAbsolutePath(name)) {
98
            foreach (QDir dir, pluginDirs_) {
99
                if (dir.exists(name)) {
100
                    filename = dir.absoluteFilePath(name);
101
                    break;
102
                  }
103
              }
104
          }
105
        plugins_[name] = new QPluginLoader (filename, parent);
106
        return true;
107
      }
108
      qDebug () << "Plugin" << name << "already declared.";
109
      return false;
110
    }
111
112
    bool PluginManager::loadPlugin(const QString &name)
113
    {
114
      if (!plugins_.contains(name)) {
115
        qDebug () << "Plugin" << name << "not declared.";
116
        return false;
117
      }
118
      if (!plugins_[name]->load()) {
119
        qDebug() << name << ": " << plugins_[name]->errorString();
120
        return false;
121
      }
122
      return true;
123
    }
124
125
    bool PluginManager::initPlugin(const QString &name)
126
    {
127
      if (!plugins_.contains(name)) {
128
        qDebug () << "Plugin" << name << "not declared.";
129
        return false;
130
      }
131
      QPluginLoader* p = plugins_[name];
132
      if (!p->isLoaded()) {
133
        qDebug () << "Plugin" << name << "not loaded:" << p->errorString();
134
        return false;
135
      }
136
      PluginInterface* pi = qobject_cast <PluginInterface*> (p->instance());
137
      if (!pi) {
138
        qDebug() << name << ": Wrong interface.";
139
        return false;
140
      }
141
      if (!pi->isInit()) pi->doInit();
142
      return pi->isInit ();
143
    }
144
145
    bool PluginManager::unloadPlugin(const QString &name)
146
    {
147
      return plugins_[name]->unload();
148
    }
149
150
    void PluginManager::clearPlugins()
151
    {
152
      foreach (QPluginLoader* p, plugins_) {
153
        p->unload();
154
      }
155
    }
156
157
    void PluginManager::declareAllPyPlugins()
158
    {
159
      QSettings settings (QSettings::SystemScope,
160
          QCoreApplication::organizationName (),
161
          "pyplugin");
162
      QDir pypluginConf (settings.fileName() + ".d");
163
164
      qDebug() << "Looking for declared pyplugins into" << 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
    {
195
      if (!pyplugins_.contains(name)) {
196
        if (name.endsWith (".py")) {
197
          QFileInfo fi (name);
198
          QString moduleName = fi.baseName();
199
          QString script;
200
          if (fi.isAbsolute()) script = name;
201
          else script = QDir::currentPath() + QDir::separator() + name;
202
          pyplugins_[name] = script;
203
        } else
204
          pyplugins_[name] = name;
205
        return true;
206
      }
207
      qDebug () << "Python plugin" << name << "already declared.";
208
      return false;
209
    }
210
211
    bool PluginManager::loadPyPlugin(const QString &name)
212
    {
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 ("gepetto-viewer was compiled without GEPETTO_GUI_HAS_"
230
          "PYTHONQT flag. Cannot not load Python plugin " + name);
231
      return false;
232
#endif
233
    }
234
235
    bool PluginManager::unloadPyPlugin(const QString &name)
236
    {
237
      MainWindow* main = MainWindow::instance();
238
#if GEPETTO_GUI_HAS_PYTHONQT
239
      PythonWidget* pw = main->pythonWidget();
240
      pw->unloadModulePlugin(name);
241
      return true;
242
#else
243
      main->logError ("gepetto-viewer was compiled without GEPETTO_GUI_HAS_"
244
          "PYTHONQT flag. Cannot not unload Python plugin " + name);
245
      return false;
246
#endif
247
    }
248
249
    bool PluginManager::isPyPluginLoaded (const QString& name)
250
    {
251
      MainWindow* main = MainWindow::instance();
252
#if GEPETTO_GUI_HAS_PYTHONQT
253
      PythonWidget* pw = main->pythonWidget();
254
      return pw->hasPlugin (name);
255
#else
256
      return false;
257
#endif
258
    }
259
260
    void PluginManager::clearPyPlugins()
261
    {
262
      foreach (QString p, pyplugins_.keys()) {
263
        unloadPyPlugin(p);
264
      }
265
    }
266
267
    PluginManagerDialog::PluginManagerDialog(PluginManager *pm, QWidget *parent) :
268
      QDialog(parent),
269
      ui_(new ::Ui::PluginManagerDialog),
270
      pm_ (pm)
271
    {
272
      ui_->setupUi(this);
273
274
      updateList ();
275
276
      // C++ plugin list
277
      ui_->pluginList->setColumnHidden(P_FILE, true);
278
279
      connect(ui_->pluginList, 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()),
293
          SLOT(declareAll()));
294
      connect(ui_->saveButton, SIGNAL(clicked()),
295
          SLOT(save()));
296
    }
297
298
    PluginManagerDialog::~PluginManagerDialog()
299
    {
300
      delete ui_;
301
    }
302
303
    void PluginManagerDialog::onItemChanged(QTableWidgetItem *current,
304
        QTableWidgetItem* /*previous*/)
305
    {
306
      if (!current) return;
307
      QString key = ui_->pluginList->item(current->row(), P_FILE)->text();
308
      const QPluginLoader* pl = pm_->plugins()[key];
309
      ui_->pluginMessage->setText(pm_->status (pl));
310
    }
311
312
    void PluginManagerDialog::contextMenu(const QPoint &pos)
313
    {
314
      int row = ui_->pluginList->rowAt(pos.y());
315
      if (row == -1) return;
316
      QString key = ui_->pluginList->item(row, P_FILE)->text();
317
      QMenu contextMenu (tr("Plugin"), ui_->pluginList);
318
      QSignalMapper sm;
319
      if (pm_->plugins()[key]->isLoaded()) {
320
        QAction* unload = contextMenu.addAction("&Unload", &sm, SLOT(map()));
321
        sm.setMapping (unload, key);
322
        connect(&sm, SIGNAL (mapped(QString)), this, SLOT(unload(QString)));
323
      } else {
324
        QAction* load = contextMenu.addAction("&Load", &sm, SLOT(map()));
325
        sm.setMapping (load, key);
326
        connect(&sm, SIGNAL (mapped(QString)), this, SLOT(load(QString)));
327
      }
328
      contextMenu.exec(ui_->pluginList->mapToGlobal(pos));
329
    }
330
331
    void PluginManagerDialog::load(const QString &name)
332
    {
333
      pm_->loadPlugin(name);
334
      pm_->initPlugin(name);
335
      updateList ();
336
    }
337
338
    void PluginManagerDialog::unload(const QString &name)
339
    {
340
      pm_->unloadPlugin (name);
341
      updateList ();
342
    }
343
344
    void PluginManagerDialog::pyContextMenu(const QPoint &pos)
345
    {
346
      int row = ui_->pypluginList->rowAt(pos.y());
347
      if (row == -1) return;
348
      QString key = ui_->pypluginList->item(row, P_NAME)->text();
349
      QMenu contextMenu (tr("PythonPlugin"), ui_->pypluginList);
350
      QSignalMapper sm;
351
      if (pm_->isPyPluginLoaded (key)) {
352
        QAction* unload = contextMenu.addAction("&Unload", &sm, SLOT(map()));
353
        sm.setMapping (unload, key);
354
        connect(&sm, SIGNAL (mapped(QString)), this, SLOT(pyUnload(QString)));
355
      } else {
356
        QAction* load = contextMenu.addAction("&Load", &sm, SLOT(map()));
357
        sm.setMapping (load, key);
358
        connect(&sm, SIGNAL (mapped(QString)), this, SLOT(pyLoad(QString)));
359
      }
360
      contextMenu.exec(ui_->pypluginList->mapToGlobal(pos));
361
    }
362
363
    void PluginManagerDialog::pyLoad(const QString &name)
364
    {
365
      pm_->loadPyPlugin(name);
366
      updateList ();
367
    }
368
369
    void PluginManagerDialog::pyUnload(const QString &name)
370
    {
371
      pm_->unloadPyPlugin (name);
372
      updateList ();
373
    }
374
375
    void PluginManagerDialog::declareAll()
376
    {
377
      pm_->declareAllPlugins();
378
      pm_->declareAllPyPlugins();
379
      updateList();
380
    }
381
382
    void PluginManagerDialog::save ()
383
    {
384
      MainWindow::instance()->settings_->writeSettingFile();
385
    }
386
387
    const int PluginManagerDialog::P_NAME = 0;
388
    const int PluginManagerDialog::P_FILE = 1;
389
    const int PluginManagerDialog::P_VERSION = 2;
390
    const int PluginManagerDialog::P_FULLPATH = 3;
391
392
    void PluginManagerDialog::updateList()
393
    {
394
      while (ui_->pluginList->rowCount() > 0)
395
        ui_->pluginList->removeRow(0);
396
      while (ui_->pypluginList->rowCount() > 0)
397
        ui_->pypluginList->removeRow(0);
398
      for (PluginManager::Map::const_iterator p = pm_->plugins ().constBegin();
399
          p != pm_->plugins().constEnd(); p++) {
400
        QString name = p.key(),
401
                filename = p.key(),
402
                fullpath = p.value()->fileName(),
403
                version = "";
404
        if (p.value ()->isLoaded ()) {
405
          PluginInterface* pi = qobject_cast <PluginInterface*> (p.value()->instance());
406
          if (pi) {
407
            name = pi->name();
408
            // version = pi->version();
409
          }
410
        }
411
        QIcon icon = pm_->icon (p.value());
412
413
        ui_->pluginList->insertRow(ui_->pluginList->rowCount());
414
        ui_->pluginList->setItem(ui_->pluginList->rowCount() - 1, P_NAME, new QTableWidgetItem (icon, name));
415
        ui_->pluginList->setItem(ui_->pluginList->rowCount() - 1, P_FILE, new QTableWidgetItem (filename));
416
        ui_->pluginList->setItem(ui_->pluginList->rowCount() - 1, P_VERSION, new QTableWidgetItem (version));
417
        ui_->pluginList->setItem(ui_->pluginList->rowCount() - 1, P_FULLPATH, new QTableWidgetItem (fullpath));
418
      }
419
      ui_->pluginList->resizeColumnsToContents();
420
421
      for (PluginManager::PyMap::const_iterator p = pm_->pyplugins ().constBegin();
422
          p != pm_->pyplugins().constEnd(); p++) {
423
        QString name = p.key(),
424
                filename = p.value(),
425
                version = "";
426
        QIcon icon = pyicon (pm_->isPyPluginLoaded (p.key()));
427
428
        ui_->pypluginList->insertRow(ui_->pypluginList->rowCount());
429
        ui_->pypluginList->setItem(ui_->pypluginList->rowCount() - 1, P_NAME, new QTableWidgetItem (icon, name));
430
        ui_->pypluginList->setItem(ui_->pypluginList->rowCount() - 1, P_FILE, new QTableWidgetItem (filename));
431
        //ui_->pypluginList->setItem(ui_->pypluginList->rowCount() - 1, P_VERSION, new QTableWidgetItem (version));
432
        //ui_->pypluginList->setItem(ui_->pypluginList->rowCount() - 1, P_FULLPATH, new QTableWidgetItem (fullpath));
433
      }
434
      ui_->pluginList->resizeColumnsToContents();
435
    }
436
  } // namespace gui
437

3
} // namespace gepetto