Scribus Developer Documentation - API
logo-pen

Scribus Developer Documentation - API

pluginmanager.cpp

Go to the documentation of this file.
00001 /*
00002 For general Scribus (>=1.3.2) copyright and licensing information please refer
00003 to the COPYING file provided with the program. Following this notice may exist
00004 a copyright and/or license notice that predates the release of Scribus 1.3.2
00005 for which a new license (GPL+exception) is in place.
00006 */
00007 #include "pluginmanager.h"
00008 #include "scplugin.h"
00009 #include "loadsaveplugin.h"
00010 
00011 #include <QDir>
00012 #include <QEvent>
00013 
00014 #include "scconfig.h"
00015 
00016 #include "scribus.h"
00017 #include "scribusdoc.h"
00018 #include "scribuscore.h"
00019 #include "sctoolbar.h"
00020 #include "menumanager.h"
00021 #include "scraction.h"
00022 #include "splash.h"
00023 #include "prefsmanager.h"
00024 #include "prefsfile.h"
00025 #include "scpaths.h"
00026 #include "commonstrings.h"
00027 
00028 #ifdef HAVE_DLFCN_H
00029 #include <dlfcn.h>
00030 #elif defined(DLL_USE_NATIVE_API) && defined(_WIN32)
00031 #include <windows.h>
00032 #else
00033 #include <QLibrary>
00034 #endif
00035 
00036 
00037 extern ScribusQApp *ScQApp;
00038 
00039 PluginManager::PluginManager() :
00040     QObject(0),
00041     prefs(PrefsManager::instance()->prefsFile->getPluginContext("pluginmanager"))
00042 {
00043 }
00044 
00045 PluginManager::~PluginManager()
00046 {
00047 }
00048 
00049 void* PluginManager::loadDLL( QString plugin )
00050 {
00051     void* lib = NULL;
00052 #ifdef HAVE_DLFCN_H
00053     QString libpath = QDir::convertSeparators( plugin );
00054     lib = dlopen(libpath.toLocal8Bit().data(), RTLD_LAZY | RTLD_GLOBAL);
00055     if (!lib)
00056     {
00057         const char* error = dlerror();
00058         qDebug("%s: %s",
00059                 tr("Cannot find plugin", "plugin manager").toLocal8Bit().data(),
00060                 error ? error : tr("unknown error","plugin manager").toLocal8Bit().data());
00061     }
00062     dlerror();
00063 #elif defined(DLL_USE_NATIVE_API) && defined(_WIN32)
00064     QString libpath = QDir::convertSeparators( plugin );
00065     HINSTANCE hdll = LoadLibraryW( (const wchar_t*) libpath.utf16() );
00066     lib = (void*) hdll;
00067 #else
00068     if( QFile::exists(plugin) )
00069         lib = (void*) new QLibrary( plugin );
00070     else
00071         qDebug("%s \"%s\"", tr("Cannot find plugin", "plugin manager").toLocal8Bit().data(), plugin.toLocal8Bit().data());
00072 #endif
00073     return lib;
00074 }
00075 
00076 void* PluginManager::resolveSym( void* plugin, const char* sym )
00077 {
00078     void* symAddr = NULL;
00079 #ifdef HAVE_DLFCN_H
00080     const char* error;
00081     dlerror();
00082     symAddr = dlsym( plugin, sym );
00083     if ((error = dlerror()) != NULL)
00084     {
00085         qDebug("%s", tr("Cannot find symbol (%1)", "plugin manager").arg(error).toLocal8Bit().data());
00086         symAddr = NULL;
00087     }
00088 #elif defined(DLL_USE_NATIVE_API) && defined(_WIN32)
00089     symAddr = (void* ) GetProcAddress( (HMODULE) plugin, sym);
00090     if ( symAddr == NULL)
00091         qDebug("%s", tr("Cannot find symbol (%1)", "plugin manager").arg(sym).toLocal8Bit().data());
00092 #else
00093     QLibrary* qlib = (QLibrary*) plugin;
00094     if( plugin ) symAddr = qlib->resolve(sym);
00095     if ( symAddr == NULL)
00096         qDebug("%s", tr("Cannot find symbol (%1)", "plugin manager").arg(sym).toLocal8Bit().data());
00097 #endif
00098     return symAddr;
00099 }
00100 
00101 void  PluginManager::unloadDLL( void* plugin )
00102 {
00103 #ifdef HAVE_DLFCN_H
00104     dlclose( plugin );
00105     dlerror();
00106 #elif defined(DLL_USE_NATIVE_API) && defined(_WIN32)
00107     FreeLibrary( (HMODULE) plugin );
00108 #else
00109     delete ( (QLibrary*) plugin );
00110 #endif
00111 }
00112 
00113 void PluginManager::savePreferences()
00114 {
00115     // write configuration
00116     for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
00117         prefs->set(it.value().pluginName, it.value().enableOnStartup);
00118 }
00119 
00120 QString PluginManager::getPluginName(QString fileName)
00121 {
00122     // Must return plugin name. Note that this may be platform dependent;
00123     // it's likely to need some adjustment for platform naming schemes.
00124     // It currently handles:
00125     //    (lib)?pluginname(\.pluginext)?
00126     QFileInfo fi(fileName);
00127     QString baseName(fi.baseName());
00128     if (baseName.startsWith("lib"))
00129         baseName = baseName.remove(0,3);
00130     if (baseName.endsWith(platformDllExtension()))
00131         baseName = baseName.left(baseName.length() - 1 - platformDllExtension().length());
00132     // check name
00133     for (int i = 0; i < (int)baseName.length(); i++)
00134         if (! baseName[i].isLetterOrNumber() && baseName[i] != '_' )
00135         {
00136             qDebug("Invalid character in plugin name for %s; skipping",
00137                     fileName.toLocal8Bit().data());
00138             return QString();
00139         }
00140     return baseName.toLatin1();
00141 }
00142 
00143 int PluginManager::initPlugin(const QString fileName)
00144 {
00145     PluginData pda;
00146     pda.pluginFile = QString("%1/%2").arg(ScPaths::instance().pluginDir()).arg(fileName);
00147     pda.pluginName = getPluginName(pda.pluginFile);
00148     if (pda.pluginName.isNull())
00149         // Couldn't determine plugname from filename. We've already complained, so
00150         // move on to the next one.
00151         return 0;
00152     pda.plugin = 0;
00153     pda.pluginDLL = 0;
00154     pda.enabled = false;
00155     pda.enableOnStartup = prefs->getBool(pda.pluginName, true);
00156     ScCore->setSplashStatus( tr("Plugin: loading %1", "plugin manager").arg(pda.pluginName));
00157     if (loadPlugin(pda))
00158     {
00159         if (pda.enableOnStartup)
00160             enablePlugin(pda);
00161         pluginMap.insert(pda.pluginName, pda);
00162         return 1;
00163     }
00164     return 0;
00165 }
00166 
00167 void PluginManager::initPlugs()
00168 {
00169     Q_ASSERT(!pluginMap.count());
00170     QString libPattern = QString("*.%1*").arg(platformDllExtension());
00171     QMap<QString,int> allPlugs;
00172     int loaded = 0;
00173     uint changes = 1;
00174     QStringList failedPlugs; // output string for warn dialog
00175 
00179     QDir dirList(ScPaths::instance().pluginDir(),
00180                  libPattern, QDir::Name | QDir::Reversed,
00181                  (QDir::Filter) PluginManager::platformDllSearchFlags());
00182 
00183     if ((!dirList.exists()) || (dirList.count() == 0))
00184         return;
00185     for (uint dc = 0; dc < dirList.count(); ++dc)
00186     {
00187         int res = initPlugin(dirList[dc]);
00188         allPlugs[dirList[dc]] = res;
00189         if (res != 0)
00190             ++loaded;
00191         else
00192             failedPlugs.append(dirList[dc]);
00193     }
00194     /* Re-try the failed plugins again and again until it promote
00195     any progress (changes variable is changing ;)) */
00196     QMap<QString,int>::Iterator it;
00197     while (loaded < allPlugs.count() && changes!=0)
00198     {
00199         changes = 0;
00200         for (it = allPlugs.begin(); it != allPlugs.end(); ++it)
00201         {
00202             if (it.value() != 0)
00203                 continue;
00204             int res = initPlugin(it.key());
00205             allPlugs[it.key()] = res;
00206             if (res == 1)
00207             {
00208                 ++loaded;
00209                 ++changes;
00210                 failedPlugs.removeAll(it.key());
00211             }
00212         }
00213     }
00214     if (loaded != allPlugs.count())
00215     {
00216         if (ScCore->usingGUI())
00217         {
00218             bool splashShown = ScCore->splashShowing();
00219             QString failedStr("<ul>");
00220             for (QStringList::Iterator it = failedPlugs.begin(); it != failedPlugs.end(); ++it)
00221                 failedStr += "<li>" + *it + "</li>";
00222             failedStr += "</ul>";
00223             if (splashShown)
00224                 ScCore->showSplash(false);
00225             QMessageBox::warning(ScCore->primaryMainWindow(), CommonStrings::trWarning,
00226                                  "<qt>" + tr("There is a problem loading %1 of %2 plugins. %3 This is probably caused by some kind of dependency issue or old plugins existing in your install directory. If you clean out your install directory and reinstall and this still occurs, please report it on bugs.scribus.net."
00227                                         ).arg(allPlugs.count()-loaded).arg(allPlugs.count()).arg(failedStr)
00228                                      + "</qt>",
00229                              CommonStrings::tr_OK);
00230             if (splashShown)
00231                 ScCore->showSplash(true);
00232         }
00233     }
00234 }
00235 
00236 // After a plugin has been initialized, this method calls its setup
00237 // routines and connects it to the application.
00238 void PluginManager::enablePlugin(PluginData & pda)
00239 {
00240     Q_ASSERT(pda.enabled == false);
00241     QString failReason;
00242     bool isActionPlugin=false;
00243     if (pda.plugin->inherits("ScActionPlugin"))
00244     {
00245 //      ScActionPlugin* plugin = dynamic_cast<ScActionPlugin*>(pda.plugin);
00246 //      Q_ASSERT(plugin);
00247         isActionPlugin=true;
00248         /*
00249         pda.enabled = setupPluginActions(plugin);
00250         if (!pda.enabled)
00251             failReason = tr("init failed", "plugin load error");
00252         */
00253     }
00254     else if (pda.plugin->inherits("ScPersistentPlugin"))
00255     {
00256         ScPersistentPlugin* plugin = dynamic_cast<ScPersistentPlugin*>(pda.plugin);
00257         assert(plugin);
00258         pda.enabled = plugin->initPlugin();
00259         if (!pda.enabled)
00260             failReason = tr("init failed", "plugin load error");
00261     }
00262 /* temporary hack to enable the import plugins */
00263     else if (pda.plugin->inherits("LoadSavePlugin"))
00264         pda.enabled = true;
00265     else
00266         failReason = tr("unknown plugin type", "plugin load error");
00267     if (pda.enabled || isActionPlugin)
00268         ScCore->setSplashStatus(
00269                 tr("Plugin: %1 loaded", "plugin manager")
00270                 .arg(pda.plugin->fullTrName()));
00271     else
00272         ScCore->setSplashStatus(
00273                 tr("Plugin: %1 failed to load: %2", "plugin manager")
00274                 .arg(pda.plugin->fullTrName()).arg(failReason));
00275 }
00276 
00277 bool PluginManager::setupPluginActions(ScribusMainWindow *mw)
00278 {
00279     Q_CHECK_PTR(mw);
00280     ScActionPlugin* plugin = 0;
00281 
00282     mw->scrMenuMgr->addMenuSeparator("Extras");
00283     for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
00284     {
00285         if (it.value().plugin->inherits("ScActionPlugin"))
00286         {
00287             //Add in ScrAction based plugin linkage
00288             //Insert DLL Action into Dictionary with values from plugin interface
00289             plugin = dynamic_cast<ScActionPlugin*>(it.value().plugin);
00290             assert(plugin);
00291             ScActionPlugin::ActionInfo ai(plugin->actionInfo());
00292             ScrAction* action = new ScrAction(ScrAction::ActionDLL, ai.icon1, ai.icon2, ai.text, ai.keySequence, mw);
00293             Q_CHECK_PTR(action);
00294             mw->scrActions.insert(ai.name, action);
00295 
00296             // then enable and connect up the action
00297             mw->scrActions[ai.name]->setEnabled(ai.enabledOnStartup);
00298             // Connect action's activated signal with the plugin's run method
00299             it.value().enabled = connect( mw->scrActions[ai.name], SIGNAL(triggeredData(ScribusDoc*)),
00300                             plugin, SLOT(run(ScribusDoc*)) );
00301             //Get the menu manager to add the DLL's menu item to the right menu, after the chosen existing item
00302             if ( ai.menuAfterName.isEmpty() )
00303             {
00304                 if (!ai.menu.isEmpty())
00305                 {
00306                     if ((!ai.subMenuName.isEmpty()) && (!ai.parentMenu.isEmpty()))
00307                     {
00308                         if (!mw->scrMenuMgr->menuExists(ai.menu))
00309                             mw->scrMenuMgr->createMenu(ai.menu, ai.subMenuName, ai.parentMenu);
00310                     }
00311                     mw->scrMenuMgr->addMenuItem(mw->scrActions[ai.name], ai.menu);
00312                 }
00313             }
00314             else
00315             {
00316                 QString actionName(ai.menu.toLower()+ai.menuAfterName);
00317                 ScrAction* afterAction=0;
00318                 if (mw->scrActions.contains(actionName))
00319                     afterAction=mw->scrActions[actionName];
00320                 if ((!ai.subMenuName.isEmpty()) && (!ai.parentMenu.isEmpty()))
00321                 {
00322                     if (!mw->scrMenuMgr->menuExists(ai.menu))
00323                         mw->scrMenuMgr->createMenu(ai.menu, ai.subMenuName, ai.parentMenu);
00324                 }
00325                 mw->scrMenuMgr->addMenuItemAfter(mw->scrActions[ai.name], ai.menu, afterAction);
00326             }
00327             if (!ai.toolbar.isEmpty())
00328             {
00329                 QString tbName = QString("ToolBar-%1").arg(ai.toolbar);
00330                 if (mw->scrToolBars.contains(tbName))
00331                     mw->scrToolBars[tbName]->addAction(mw->scrActions[ai.name]);
00332                 else
00333                 {
00334                     ScToolBar *tb = new ScToolBar(ai.toolBarName, ai.toolbar, mw);
00335                     tb->addAction(mw->scrActions[ai.name]);
00336                     mw->addScToolBar(tb, tbName);
00337                 }
00338             }
00339             if (it.value().enabled)
00340                 ScCore->setSplashStatus( tr("Plugin: %1 initialized ok ", "plugin manager").arg(plugin->fullTrName()));
00341             else
00342                 ScCore->setSplashStatus( tr("Plugin: %1 failed post initialization", "plugin manager").arg(plugin->fullTrName()));
00343         }
00344         else
00345         {
00346             it.value().plugin->addToMainWindowMenu(mw);
00347         }
00348 
00349     }
00350     return true;
00351 }
00352 
00353 bool PluginManager::DLLexists(QString name, bool includeDisabled) const
00354 {
00355     // the plugin name must be known
00356     if (pluginMap.contains(name))
00357     {
00358         // the plugin must be loaded
00359         if (pluginMap[name].plugin)
00360         {
00361             // and the plugin must be enabled unless we were told otherwise
00362             if (pluginMap[name].enabled)
00363                 return true;
00364             else
00365                 return includeDisabled;
00366         }
00367     }
00368     return false;
00369 }
00370 
00371 bool PluginManager::loadPlugin(PluginData & pda)
00372 {
00373     typedef int (*getPluginAPIVersionPtr)();
00374     typedef ScPlugin* (*getPluginPtr)();
00375     getPluginAPIVersionPtr getPluginAPIVersion;
00376     getPluginPtr getPlugin;
00377 
00378     Q_ASSERT(pda.plugin == 0);
00379     Q_ASSERT(pda.pluginDLL == 0);
00380     Q_ASSERT(!pda.enabled);
00381     pda.plugin = 0;
00382 
00383     pda.pluginDLL = loadDLL(pda.pluginFile);
00384     if (!pda.pluginDLL)
00385         return false;
00386 
00387     getPluginAPIVersion = (getPluginAPIVersionPtr)
00388         resolveSym(pda.pluginDLL, QString(pda.pluginName + "_getPluginAPIVersion").toLocal8Bit().data());
00389     if (getPluginAPIVersion)
00390     {
00391         int gotVersion = (*getPluginAPIVersion)();
00392         if ( gotVersion != PLUGIN_API_VERSION )
00393         {
00394             qDebug("API version mismatch when loading %s: Got %i, expected %i",
00395                     pda.pluginFile.toLocal8Bit().data(), gotVersion, PLUGIN_API_VERSION);
00396         }
00397         else
00398         {
00399             getPlugin = (getPluginPtr)
00400                 resolveSym(pda.pluginDLL, QString(pda.pluginName + "_getPlugin").toLocal8Bit().data());
00401             if (getPlugin)
00402             {
00403                 pda.plugin = (*getPlugin)();
00404                 if (!pda.plugin)
00405                 {
00406                     qDebug("Unable to get ScPlugin when loading %s",
00407                             pda.pluginFile.toLocal8Bit().data());
00408                 }
00409                 else
00410                     return true;
00411             }
00412         }
00413     }
00414     unloadDLL(pda.pluginDLL);
00415     pda.pluginDLL = 0;
00416     Q_ASSERT(!pda.plugin);
00417     return false;
00418 }
00419 
00420 void PluginManager::cleanupPlugins()
00421 {
00422     for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
00423         if (it.value().enabled == true)
00424             finalizePlug(it.value());
00425 }
00426 
00427 void PluginManager::finalizePlug(PluginData & pda)
00428 {
00429     typedef void (*freePluginPtr)(ScPlugin* plugin);
00430     if (pda.plugin)
00431     {
00432         if (pda.enabled)
00433             disablePlugin(pda);
00434         Q_ASSERT(!pda.enabled);
00435         freePluginPtr freePlugin =
00436             (freePluginPtr) resolveSym(pda.pluginDLL, QString(pda.pluginName + "_freePlugin").toLocal8Bit().data());
00437         if ( freePlugin )
00438             (*freePlugin)( pda.plugin );
00439         pda.plugin = 0;
00440     }
00441     Q_ASSERT(!pda.enabled);
00442     if (pda.pluginDLL)
00443     {
00444         unloadDLL(pda.pluginDLL);
00445         pda.pluginDLL = 0;
00446     }
00447 }
00448 
00449 void PluginManager::disablePlugin(PluginData & pda)
00450 {
00451     Q_ASSERT(pda.enabled);
00452     Q_ASSERT(pda.plugin);
00453     if (pda.plugin->inherits("ScActionPlugin"))
00454     {
00455         ScActionPlugin* plugin = dynamic_cast<ScActionPlugin*>(pda.plugin);
00456         assert(plugin);
00457         // FIXME: Correct way to delete action?
00458         delete ScCore->primaryMainWindow()->scrActions[plugin->actionInfo().name];
00459     }
00460     else if (pda.plugin->inherits("ScPersistentPlugin"))
00461     {
00462         ScPersistentPlugin* plugin = dynamic_cast<ScPersistentPlugin*>(pda.plugin);
00463         assert(plugin);
00464         plugin->cleanupPlugin();
00465     }
00466 /* temporary hack to enable the import plugins */
00467     else if (pda.plugin->inherits("LoadSavePlugin"))
00468         pda.enabled = false;
00469     else
00470         Q_ASSERT(false); // We shouldn't ever have enabled an unknown plugin type.
00471     pda.enabled = false;
00472 }
00473 
00474 QString PluginManager::platformDllExtension()
00475 {
00476 #ifdef __hpux
00477     // HP/UX
00478     return "sl";
00479 #elif defined(__APPLE__) && defined(__MACH__)
00480     // MacOS/X, Darwin
00481 
00482     // MacOS/X may actually use both 'so' and 'dylib'. .so is usually used
00483     // for plugins etc, dylib for system and app libraries We need to
00484     // support this distinction in the plugin manager, but for now it's
00485     // most appropriate to return the extension used by plugins -- CR
00486 
00487     //return "dylib";
00488     return "so";
00489 #elif defined(_WIN32) || defined(_WIN64)
00490     return "dll";
00491 #else
00492     // Generic *NIX
00493     return "so";
00494 #endif
00495 }
00496 
00497 int PluginManager::platformDllSearchFlags()
00498 {
00499 #if defined(_WIN32) || defined(_WIN64)
00500     return (QDir::Files | QDir::NoSymLinks);
00501 #else
00502     return (QDir::Files | QDir::Executable | QDir::NoSymLinks);
00503 #endif
00504 }
00505 
00506 void PluginManager::changeEvent(QEvent *e)
00507 {
00508     if (e->type() == QEvent::LanguageChange)
00509     {
00510         languageChange();
00511     }
00512 }
00513 
00514 void PluginManager::languageChange()
00515 {
00516     ScPlugin* plugin = 0;
00517     ScActionPlugin* ixplug = 0;
00518     ScrAction* pluginAction = 0;
00519     for (PluginMap::Iterator it = pluginMap.begin(); it != pluginMap.end(); ++it)
00520     {
00521         plugin = it.value().plugin;
00522         if (plugin)
00523         {
00524             plugin->languageChange();
00525             ixplug = dynamic_cast<ScActionPlugin*>(plugin);
00526             if (ixplug)
00527             {
00528                 ScActionPlugin::ActionInfo ai(ixplug->actionInfo());
00529                 pluginAction = ScCore->primaryMainWindow()->scrActions[ai.name];
00530                 if (pluginAction != 0)
00531                     pluginAction->setText( ai.text );
00532             }
00533         }
00534     }
00535 }
00536 
00537 ScPlugin* PluginManager::getPlugin(const QString & pluginName, bool includeDisabled) const
00538 {
00539     if (DLLexists(pluginName, includeDisabled))
00540         return pluginMap[pluginName].plugin;
00541     return 0;
00542 }
00543 
00544 PluginManager & PluginManager::instance()
00545 {
00546     return (*ScCore->pluginManager);
00547 }
00548 
00549 const QString PluginManager::getPluginPath(const QString & pluginName) const
00550 {
00551     // It is not legal to call this function without a valid
00552     // plug in name.
00553     Q_ASSERT(pluginMap.contains(pluginName));
00554     return pluginMap[pluginName].pluginFile;
00555 }
00556 
00557 bool & PluginManager::enableOnStartup(const QString & pluginName)
00558 {
00559     // It is not legal to call this function without a valid
00560     // plug in name.
00561     Q_ASSERT(pluginMap.contains(pluginName));
00562     return pluginMap[pluginName].enableOnStartup;
00563 }
00564 
00565 bool PluginManager::enabled(const QString & pluginName)
00566 {
00567     // It is not legal to call this function without a valid
00568     // plug in name.
00569     Q_ASSERT(pluginMap.contains(pluginName));
00570     return pluginMap[pluginName].enabled;
00571 }
00572 
00573 QStringList PluginManager::pluginNames(
00574         bool includeDisabled, const char* inherits) const
00575 {
00576     // Scan the plugin map for plugins...
00577     QStringList names;
00578     for (PluginMap::ConstIterator it = pluginMap.constBegin(); it != pluginMap.constEnd(); ++it)
00579         if (includeDisabled || it.value().enabled)
00580             // Only including plugins that inherit a named parent (if
00581             // specified), using the QMetaObject system.
00582             if (!inherits || it.value().plugin->inherits(inherits))
00583                 names.append(it.value().pluginName);
00584     return names;
00585 }
Created with personalized Doxygen. This is documentation for GPL software Scribus - Open Source Desktop Publishing.