1 /***************************************************************************
2        PluginManager.cpp -  manager class for Kwave's plugins
3 			     -------------------
4     begin                : Sun Aug 27 2000
5     copyright            : (C) 2000 by Thomas Eschenbacher
6     email                : Thomas.Eschenbacher@gmx.de
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "config.h"
19 
20 #include <errno.h>
21 #include <unistd.h>
22 #include <new>
23 
24 #include <QApplication>
25 #include <QDir>
26 #include <QLatin1Char>
27 #include <QLibrary>
28 #include <QLibraryInfo>
29 #include <QMutableListIterator>
30 #include <QtGlobal>
31 #include <QVariantList>
32 
33 #include <KAboutData>
34 #include <KConfig>
35 #include <KConfigGroup>
36 #include <KLocalizedString>
37 #include <KMainWindow>
38 #include <KPluginInfo>
39 #include <KPluginFactory>
40 #include <KPluginMetaData>
41 #include <KSharedConfig>
42 
43 #include "libkwave/MessageBox.h"
44 #include "libkwave/MultiPlaybackSink.h"
45 #include "libkwave/PlayBackDevice.h"
46 #include "libkwave/PlaybackDeviceFactory.h"
47 #include "libkwave/Plugin.h"
48 #include "libkwave/PluginManager.h"
49 #include "libkwave/SignalManager.h"
50 #include "libkwave/Utils.h"
51 #include "libkwave/Writer.h"
52 #include "libkwave/undo/UndoAction.h"
53 #include "libkwave/undo/UndoModifyAction.h"
54 #include "libkwave/undo/UndoTransactionGuard.h"
55 
56 //***************************************************************************
57 // static initializers
58 
59 QMap<QString, Kwave::PluginManager::PluginModule>
60     Kwave::PluginManager::m_plugin_modules;
61 
62 Kwave::PluginManager *Kwave::PluginManager::m_active_instance = Q_NULLPTR;
63 
64 //***************************************************************************
PluginManager(QWidget * parent,Kwave::SignalManager & signal_manager)65 Kwave::PluginManager::PluginManager(QWidget *parent,
66                                     Kwave::SignalManager &signal_manager)
67     :m_plugin_instances(),
68      m_running_plugins(),
69      m_parent_widget(parent),
70      m_signal_manager(signal_manager),
71      m_view_manager(Q_NULLPTR)
72 {
73 }
74 
75 //***************************************************************************
~PluginManager()76 Kwave::PluginManager::~PluginManager()
77 {
78     // inform all plugins and client windows that we close now
79     emit sigClosed();
80 
81     // wait until all plugins are really closed
82     this->sync();
83 
84     // give all plugins that still are loaded the chance to do some cleanups
85     // or unregistration tasks. Ideally this should also trigger a "release"
86     // of these remaining plugins, so that afterwards we have no more
87     // plugin instances left.
88     while (!m_plugin_instances.isEmpty()) {
89 	KwavePluginPointer p = m_plugin_instances.takeLast();
90 	Q_ASSERT(p);
91 	if (p) p->unload();
92     }
93 
94     // this should make the cleanup handlers run (deferred delete)
95     QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
96 
97     // release all loaded modules
98     for (QMap<QString, PluginModule>::iterator it(m_plugin_modules.begin());
99          it != m_plugin_modules.end(); )
100     {
101 // 	const QString &name = it.key();
102 	PluginModule  &p    = it.value();
103 	p.m_use_count--;
104 
105 // 	qDebug("PluginManager: releasing module '%s' [refcnt=%d]",
106 // 	       DBG(name), p.m_use_count);
107 	if (p.m_use_count == 0) {
108 	    // take out the pointer to the loadable module
109 	    KPluginFactory *factory = p.m_factory;
110             p.m_factory = Q_NULLPTR;
111 
112 	    // remove the module from the list
113 	    it = m_plugin_modules.erase(it);
114 
115 	    // now the handle of the shared object can be released too
116 	    if (factory) delete factory;
117 	} else {
118 	    // still in use
119 	    ++it;
120 	}
121     }
122 
123     // we are no longer the active instance
124     if (m_active_instance == this)
125         m_active_instance = Q_NULLPTR;
126 }
127 
128 //***************************************************************************
loadAllPlugins()129 bool Kwave::PluginManager::loadAllPlugins()
130 {
131     // Try to load all plugins. This has to be called only once per
132     // instance of the main window!
133     // NOTE: this also gives each plugin the chance to stay in memory
134     //       if necessary (e.g. for codecs)
135     for (QMap<QString, PluginModule>::iterator it(m_plugin_modules.begin());
136 	 it != m_plugin_modules.end(); ++it)
137     {
138 	const QString      &name   = it.key();
139 	KwavePluginPointer  plugin = createPluginInstance(name);
140 	if (plugin) {
141 // 	    qDebug("PluginManager::loadAllPlugins(): plugin '%s'",
142 // 		   DBG(plugin->name()));
143 
144 	    // get the last settings and call the "load" function
145 	    // now the plugin is present and loaded
146 	    QStringList last_params = defaultParams(name);
147 	    plugin->load(last_params);
148 
149 	    // reduce use count again, we loaded the plugin only to give
150 	    // it a chance to register some service if necessary (e.g. a
151 	    // codec)
152 	    // Most plugins fall back to use count zero and will be
153 	    // deleted again.
154 	    plugin->release();
155 	} else {
156 	    // loading failed => remove it from the list
157 	    qWarning("PluginManager::loadAllPlugins(): removing '%s' "
158 	            "from list", DBG(name));
159 	    m_plugin_modules.remove(name);
160 	}
161     }
162 
163     return !m_plugin_modules.isEmpty();
164 }
165 
166 //***************************************************************************
stopAllPlugins()167 void Kwave::PluginManager::stopAllPlugins()
168 {
169     // check: this must be called from the GUI thread only!
170     Q_ASSERT(this->thread() == QThread::currentThread());
171     Q_ASSERT(this->thread() == qApp->thread());
172 
173     if (!m_plugin_instances.isEmpty())
174 	foreach (const KwavePluginPointer &plugin, m_plugin_instances)
175 	    if (plugin && plugin->isRunning())
176 		plugin->stop() ;
177 
178     sync();
179 }
180 
181 //***************************************************************************
createPluginInstance(const QString & name)182 Kwave::Plugin *Kwave::PluginManager::createPluginInstance(const QString &name)
183 {
184     // check: this must be called from the GUI thread only!
185     Q_ASSERT(this->thread() == QThread::currentThread());
186     Q_ASSERT(this->thread() == qApp->thread());
187 
188     // show an error message and abort if the plugin is unknown
189     if (!(m_plugin_modules.contains(name))) {
190 	Kwave::MessageBox::error(m_parent_widget,
191 	    i18n("The plugin '%1' is unknown or invalid.", name),
192 	    i18n("Error On Loading Plugin"));
193         return Q_NULLPTR;
194     }
195 
196     PluginModule &info = m_plugin_modules[name];
197 //     qDebug("loadPlugin(%s) [module use count=%d]",
198 //         DBG(name), info.m_use_count);
199 
200     KPluginFactory *factory = info.m_factory;
201     Q_ASSERT(factory);
202 
203     // call the loader function to create an instance
204     QVariantList args;
205     args << info.m_name;
206     args << info.m_description;
207     Kwave::Plugin *plugin = factory->create<Kwave::Plugin>(this, args);
208     Q_ASSERT(plugin);
209     if (!plugin) {
210 	qWarning("PluginManager::loadPlugin('%s'): out of memory", DBG(name));
211         return Q_NULLPTR;
212     }
213     // now we have a newly created plugin, the use count is 1
214 
215     // append to our list of loaded plugins
216     m_plugin_instances.append(plugin);
217 
218     // connect all necessary signals/slots
219     connectPlugin(plugin);
220 
221     return plugin;
222 }
223 
224 //***************************************************************************
executePlugin(const QString & name,QStringList * params)225 int Kwave::PluginManager::executePlugin(const QString &name,
226                                         QStringList *params)
227 {
228     QString command;
229     int result = 0;
230 
231     // check: this must be called from the GUI thread only!
232     Q_ASSERT(this->thread() == QThread::currentThread());
233     Q_ASSERT(this->thread() == qApp->thread());
234 
235     // synchronize: wait until any currently running plugins are done
236     this->sync();
237 
238     // load the new plugin
239     KwavePluginPointer plugin = createPluginInstance(name);
240     if (!plugin) return -ENOMEM;
241 
242     if (params) {
243 	// parameters were specified -> call directly
244 	// without setup dialog
245 	result = plugin->start(*params);
246 
247 	// maybe the start() function has called close() ?
248 	if (!m_plugin_instances.contains(plugin)) {
249 	    qDebug("PluginManager: plugin closed itself in start()");
250 	    result = -1;
251             plugin = Q_NULLPTR;
252 	}
253 
254 	if (plugin && (result >= 0)) {
255 	    plugin->execute(*params);
256 	}
257     } else {
258 	// load previous parameters from config
259 	QStringList last_params = defaultParams(name);
260 
261 	// call the plugin's setup function
262 	params = plugin->setup(last_params);
263 	if (params) {
264 	    // we have a non-zero parameter list, so
265 	    // the setup function has not been aborted.
266 	    // Now we can create a command string and
267 	    // emit a new command.
268 
269 	    // store parameters for the next time
270 	    savePluginDefaults(name, *params);
271 
272 	    // We DO NOT call the plugin's "execute"
273 	    // function directly, as it should be possible
274 	    // to record all function calls in the
275 	    // macro recorder
276 	    command = _("plugin:execute(");
277 	    command += name;
278 	    foreach (const QString &p, *params)
279 		command += _(", ") + p;
280 	    delete params;
281 	    command += _(")");
282 //	    qDebug("PluginManager: command='%s'",command.data());
283 	}
284     }
285 
286     // now the plugin is no longer needed here, release it
287     if (plugin) plugin->release();
288 
289     // emit a command, let the toplevel window (and macro recorder) get
290     // it and call us again later...
291     if (command.length()) emit sigCommand(command);
292 
293     return result;
294 }
295 
296 //***************************************************************************
canClose()297 bool Kwave::PluginManager::canClose()
298 {
299     // check: this must be called from the GUI thread only!
300     Q_ASSERT(this->thread() == QThread::currentThread());
301     Q_ASSERT(this->thread() == qApp->thread());
302 
303     if (!m_plugin_instances.isEmpty())
304 	foreach (const KwavePluginPointer &plugin, m_plugin_instances)
305 	    if (plugin && !plugin->canClose()) return false;
306 
307     return true;
308 }
309 
310 //***************************************************************************
onePluginRunning()311 bool Kwave::PluginManager::onePluginRunning()
312 {
313     // check: this must be called from the GUI thread only!
314     Q_ASSERT(this->thread() == QThread::currentThread());
315     Q_ASSERT(this->thread() == qApp->thread());
316 
317     if (!m_plugin_instances.isEmpty())
318 	foreach (const KwavePluginPointer &plugin, m_plugin_instances)
319 	    if (plugin && plugin->isRunning()) return true;
320 
321     return false;
322 }
323 
324 //***************************************************************************
sync()325 void Kwave::PluginManager::sync()
326 {
327     // check: this must be called from the GUI thread only!
328     Q_ASSERT(this->thread() == QThread::currentThread());
329     Q_ASSERT(this->thread() == qApp->thread());
330 
331     // this triggers all kinds of garbage collector (objects queued for
332     // deletion through obj->deleteLater()
333     qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
334 
335     // wait until all plugins have finished their work...
336     while (onePluginRunning()) {
337 	Kwave::yield();
338 	qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
339 	usleep(100000);
340     }
341 }
342 
343 //***************************************************************************
setupPlugin(const QString & name,const QStringList & params)344 int Kwave::PluginManager::setupPlugin(const QString &name,
345                                       const QStringList &params)
346 {
347     // load the plugin
348     Kwave::Plugin *plugin = createPluginInstance(name);
349     if (!plugin) return -ENOMEM;
350 
351     // now the plugin is present and loaded
352     QStringList prev_params = (params.isEmpty()) ?
353 	defaultParams(name) : params;
354 
355     // call the plugins' setup function
356     QStringList *new_params = plugin->setup(prev_params);
357     if (new_params) {
358 	// we have a non-zero parameter list, so
359 	// the setup function has not been aborted.
360 	savePluginDefaults(name, *new_params);
361 	delete new_params;
362     } else {
363 	plugin->release();
364 	return 1;
365     }
366 
367     plugin->release();
368     return 0;
369 }
370 
371 //***************************************************************************
defaultParams(const QString & name)372 QStringList Kwave::PluginManager::defaultParams(const QString &name)
373 {
374     QString def_version;
375     QString section = _("plugin ");
376     QStringList list;
377     section += name;
378 
379     // get the plugin version
380     if (!m_plugin_modules.contains(name)) return list;
381     const PluginModule &info = m_plugin_modules[name];
382     QString version = info.m_version;
383 
384     Q_ASSERT(KSharedConfig::openConfig());
385     if (!KSharedConfig::openConfig()) return list;
386     KConfigGroup cfg = KSharedConfig::openConfig()->group(section);
387 
388     cfg.sync();
389 
390     def_version = cfg.readEntry("version");
391     if (!def_version.length()) {
392 	return list;
393     }
394     if (!(def_version == version)) {
395 	qDebug("PluginManager::defaultParams: "
396 	    "plugin '%s': defaults for version '%s' not loaded, found "
397 	    "old ones of version '%s'.",
398 	    DBG(name), DBG(version), DBG(def_version));
399 
400 	// delete the old settings
401 	cfg.deleteEntry("version");
402 	cfg.deleteEntry("defaults");
403 
404 	return list;
405     }
406 
407     list = cfg.readEntry("defaults").split(QLatin1Char(','));
408     return list;
409 }
410 
411 //***************************************************************************
savePluginDefaults(const QString & name,QStringList & params)412 void Kwave::PluginManager::savePluginDefaults(const QString &name,
413                                               QStringList &params)
414 {
415 
416     // get the plugin version
417     if (!m_plugin_modules.contains(name)) return;
418     const PluginModule &info = m_plugin_modules[name];
419     QString version = info.m_version;
420 
421     QString section = _("plugin ");
422     section += name;
423 
424     Q_ASSERT(KSharedConfig::openConfig());
425     if (!KSharedConfig::openConfig()) return;
426     KConfigGroup cfg = KSharedConfig::openConfig()->group(section);
427 
428     cfg.sync();
429     cfg.writeEntry("version", version);
430     cfg.writeEntry("defaults", params.join(QLatin1Char(',')));
431     cfg.sync();
432 }
433 
434 //***************************************************************************
signalLength()435 sample_index_t Kwave::PluginManager::signalLength()
436 {
437     return m_signal_manager.length();
438 }
439 
440 //***************************************************************************
signalRate()441 double Kwave::PluginManager::signalRate()
442 {
443     return m_signal_manager.rate();
444 }
445 
446 //***************************************************************************
selectionStart()447 sample_index_t Kwave::PluginManager::selectionStart()
448 {
449     return m_signal_manager.selection().first();
450 }
451 
452 //***************************************************************************
selectionEnd()453 sample_index_t Kwave::PluginManager::selectionEnd()
454 {
455     return m_signal_manager.selection().last();
456 }
457 
458 //***************************************************************************
selectRange(sample_index_t offset,sample_index_t length)459 void Kwave::PluginManager::selectRange(sample_index_t offset,
460                                        sample_index_t length)
461 {
462     m_signal_manager.selectRange(offset, length);
463 }
464 
465 //***************************************************************************
openMultiTrackPlayback(unsigned int tracks,const Kwave::PlayBackParam * playback_params)466 Kwave::SampleSink *Kwave::PluginManager::openMultiTrackPlayback(
467     unsigned int tracks,
468     const Kwave::PlayBackParam *playback_params
469 )
470 {
471     Kwave::PlayBackDevice *device =
472 	m_signal_manager.playbackController().openDevice(
473 	    tracks, playback_params);
474     if (!device) return Q_NULLPTR;
475 
476     // create the multi track playback sink
477     Kwave::SampleSink *sink = new(std::nothrow)
478 	Kwave::MultiPlaybackSink(tracks, device);
479     Q_ASSERT(sink);
480     return sink;
481 }
482 
483 //***************************************************************************
playbackController()484 Kwave::PlaybackController &Kwave::PluginManager::playbackController()
485 {
486     return m_signal_manager.playbackController();
487 }
488 
489 //***************************************************************************
insertView(Kwave::SignalView * view,QWidget * controls)490 void Kwave::PluginManager::insertView(Kwave::SignalView *view, QWidget *controls)
491 {
492     if (m_view_manager)
493 	m_view_manager->insertView(view, controls);
494 }
495 
496 //***************************************************************************
registerViewManager(Kwave::ViewManager * view_manager)497 void Kwave::PluginManager::registerViewManager(Kwave::ViewManager *view_manager)
498 {
499     Q_ASSERT(!view_manager || !m_view_manager);
500     m_view_manager = view_manager;
501 }
502 
503 //***************************************************************************
enqueueCommand(const QString & command)504 void Kwave::PluginManager::enqueueCommand(const QString &command)
505 {
506     emit sigCommand(command);
507 }
508 
509 //***************************************************************************
signalClosed()510 void Kwave::PluginManager::signalClosed()
511 {
512     emit sigClosed();
513 }
514 
515 //***************************************************************************
pluginClosed(Kwave::Plugin * p)516 void Kwave::PluginManager::pluginClosed(Kwave::Plugin *p)
517 {
518     // check: this must be called from the GUI thread only!
519     Q_ASSERT(this->thread() == QThread::currentThread());
520     Q_ASSERT(this->thread() == qApp->thread());
521 
522     Q_ASSERT(p);
523     if (!p) return;
524 
525     // disconnect the signals to avoid recursion
526     disconnectPlugin(p);
527 
528     if (m_plugin_instances.contains(p))
529         m_plugin_instances.removeAll(p);
530 
531     // schedule the deferred delete/unload of the plugin
532     p->deleteLater();
533 }
534 
535 //***************************************************************************
pluginStarted(Kwave::Plugin * p)536 void Kwave::PluginManager::pluginStarted(Kwave::Plugin *p)
537 {
538     Q_ASSERT(p);
539     if (!p) return;
540 
541     // the plugin is running -> increase the usage count in order to
542     // prevent our lists from containing invalid entries
543     p->use();
544 
545     // add the plugin to the list of running plugins
546     m_running_plugins.append(p);
547 }
548 
549 //***************************************************************************
pluginDone(Kwave::Plugin * p)550 void Kwave::PluginManager::pluginDone(Kwave::Plugin *p)
551 {
552     // check: this must be called from the GUI thread only!
553     Q_ASSERT(this->thread() == QThread::currentThread());
554     Q_ASSERT(this->thread() == qApp->thread());
555 
556     Q_ASSERT(p);
557     if (!p) return;
558 
559     // remove the plugin from the list of running plugins
560     m_running_plugins.removeAll(p);
561 
562     // release the plugin, at least we do no longer need it
563     p->release();
564 }
565 
566 //***************************************************************************
connectPlugin(Kwave::Plugin * plugin)567 void Kwave::PluginManager::connectPlugin(Kwave::Plugin *plugin)
568 {
569     Q_ASSERT(plugin);
570     if (!plugin) return;
571 
572     connect(this, SIGNAL(sigClosed()),
573 	    plugin, SLOT(close()));
574 
575     connect(plugin, SIGNAL(sigClosed(Kwave::Plugin*)),
576 	    this, SLOT(pluginClosed(Kwave::Plugin*)),
577 	    Qt::QueuedConnection);
578 
579     connect(plugin, SIGNAL(sigRunning(Kwave::Plugin*)),
580 	    this, SLOT(pluginStarted(Kwave::Plugin*)),
581 	    Qt::DirectConnection);
582 
583     connect(plugin, SIGNAL(sigDone(Kwave::Plugin*)),
584 	    this, SLOT(pluginDone(Kwave::Plugin*)),
585 	    Qt::QueuedConnection);
586 }
587 
588 //***************************************************************************
disconnectPlugin(Kwave::Plugin * plugin)589 void Kwave::PluginManager::disconnectPlugin(Kwave::Plugin *plugin)
590 {
591     Q_ASSERT(plugin);
592     if (!plugin) return;
593 
594     disconnect(plugin, SIGNAL(sigDone(Kwave::Plugin*)),
595 	       this, SLOT(pluginDone(Kwave::Plugin*)));
596 
597     disconnect(plugin, SIGNAL(sigRunning(Kwave::Plugin*)),
598 	       this, SLOT(pluginStarted(Kwave::Plugin*)));
599 
600     disconnect(this, SIGNAL(sigClosed()),
601 	       plugin, SLOT(close()));
602 
603     disconnect(plugin, SIGNAL(sigClosed(Kwave::Plugin*)),
604 	       this, SLOT(pluginClosed(Kwave::Plugin*)));
605 
606 }
607 
608 //***************************************************************************
setSignalName(const QString & name)609 void Kwave::PluginManager::setSignalName(const QString &name)
610 {
611     emit sigSignalNameChanged(name);
612 }
613 
614 //***************************************************************************
searchPluginModules()615 void Kwave::PluginManager::searchPluginModules()
616 {
617     if (!m_plugin_modules.isEmpty()) {
618 	// this is not the first call -> increment module use count only
619 	for (QMap<QString, PluginModule>::iterator it(m_plugin_modules.begin());
620 	     it != m_plugin_modules.end(); ++it)
621 	{
622 	    it.value().m_use_count++;
623 	}
624 	return;
625     }
626 
627     QVector<KPluginMetaData> plugins_meta_data =
628 	KPluginMetaData::findPlugins(_("kwave"));
629     foreach (const KPluginMetaData &i, plugins_meta_data) {
630 	QString library     = i.fileName();
631 	QString description = i.name();
632 	QString name        = i.pluginId();
633 	QString version_raw = i.version();
634 	QString version;
635 	QString settings;
636 	QString author      = i.authors().first().name();
637 
638 	if (version_raw.contains(_(":"))) {
639 	    version  = version_raw.split(_(":")).at(0);
640 	    settings = version_raw.split(_(":")).at(1);
641 	}
642 
643 // 	qDebug("file='%s', name='%s', description='%s', binary_version='%s', "
644 // 	       "settings_version='%s', author='%s'",
645 // 	       DBG(library), DBG(name), DBG(description), DBG(version),
646 // 	       DBG(settings), DBG(author)
647 // 	);
648 
649 	if ( library.isEmpty() || description.isEmpty() ||
650 	     name.isEmpty() || version.isEmpty() ) {
651 	    qWarning("plugin '%s' has no library, name or version", DBG(name));
652 	    continue;
653 	}
654 
655 	if (version != _(KWAVE_VERSION)) {
656 	    qWarning("plugin '%s' has wrong ABI version: '%s' (should be %s)",
657 		     DBG(name), DBG(version), KWAVE_VERSION);
658 	    continue;
659 	}
660 
661 	KPluginFactory::Result<KPluginFactory> result =
662 	    KPluginFactory::loadFactory(i);
663 
664 	if (!result) {
665 	    qWarning("plugin '%s': loading failed: '%s'", DBG(name),
666 		     DBG(result.errorString));
667 	    continue;
668 	}
669 
670 	emit sigProgress(i18n("Loading plugin %1...", name));
671 	QApplication::processEvents();
672 
673 	PluginModule info;
674 	info.m_name        = name;
675 	info.m_author      = author;
676 	info.m_description = i18n(description.toUtf8());
677 	info.m_version     = settings;
678 	info.m_factory     = result.plugin;
679 	info.m_use_count   = 1;
680 
681 	m_plugin_modules.insert(info.m_name, info);
682 
683 	qDebug("%16s %5s written by %s", DBG(name), DBG(settings), DBG(author));
684     }
685 
686     qDebug("--- \n found %d plugins\n", m_plugin_modules.count());
687 }
688 
689 //***************************************************************************
690 const QList<Kwave::PluginManager::PluginModule>
pluginInfoList() const691     Kwave::PluginManager::pluginInfoList() const
692 {
693     return m_plugin_modules.values();
694 }
695 
696 //***************************************************************************
migratePluginToActiveContext(Kwave::Plugin * plugin)697 void Kwave::PluginManager::migratePluginToActiveContext(Kwave::Plugin *plugin)
698 {
699     // check: this must be called from the GUI thread only!
700     Q_ASSERT(this->thread() == QThread::currentThread());
701     Q_ASSERT(this->thread() == qApp->thread());
702 
703     Q_ASSERT(plugin);
704     if (!plugin) return;
705     if (m_active_instance == this) return; // nothing to do
706     Q_ASSERT(m_active_instance);
707     if (!m_active_instance) return; // should never happen
708 
709     Kwave::PluginManager *old_mgr = this;
710     old_mgr->m_plugin_instances.removeAll(plugin);
711     old_mgr->m_running_plugins.removeAll(plugin);
712     old_mgr->disconnectPlugin(plugin);
713 
714     Kwave::PluginManager *new_mgr = m_active_instance;
715     new_mgr->m_plugin_instances.append(plugin);
716     new_mgr->m_running_plugins.append(plugin);
717     new_mgr->connectPlugin(plugin);
718 
719     plugin->setPluginManager(new_mgr);
720 }
721 
722 //***************************************************************************
723 //***************************************************************************
724