/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Qt Software Information (qt-info@nokia.com) ** ** This file is part of the Qt Designer of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the either Technology Preview License Agreement or the ** Beta Release License Agreement. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "pluginmanager_p.h" #include "qdesigner_utils_p.h" #include "qdesigner_qsettings_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char *uiElementC = "ui"; static const char *languageAttributeC = "language"; static const char *widgetElementC = "widget"; static const char *displayNameAttributeC = "displayname"; static const char *classAttributeC = "class"; static const char *customwidgetElementC = "customwidget"; static const char *extendsElementC = "extends"; static const char *addPageMethodC = "addpagemethod"; static const char *jambiLanguageC = "jambi"; enum { debugPluginManager = 0 }; /* Custom widgets: Loading custom widgets is a 2-step process: PluginManager * scans for its plugins in the constructor. At this point, it might not be safe * to immediately initialize the custom widgets it finds, because the rest of * Designer is not initialized yet. * Later on, in ensureInitialized(), the plugin instances (including static ones) * are iterated and the custom widget plugins are initialized and added to internal * list of custom widgets and parsed data. Should there be a parse error or a language * mismatch, it kicks out the respective custom widget. The m_initialized flag * is used to indicate the state. * Later, someone might call registerNewPlugins(), which agains clears the flag via * registerPlugin() and triggers the process again. * Also note that Jambi fakes a custom widget collection that changes its contents * every time the project is switched. So, custom widget plugins can actually * disappear, and the custom widget list must be cleared and refilled in * ensureInitialized() after registerNewPlugins. */ QT_BEGIN_NAMESPACE static QStringList unique(const QStringList &lst) { const QSet s = QSet::fromList(lst); return s.toList(); } QStringList QDesignerPluginManager::defaultPluginPaths() { QStringList result; const QStringList path_list = QCoreApplication::libraryPaths(); const QString designer = QLatin1String("designer"); foreach (const QString &path, path_list) { QString libPath = path; libPath += QDir::separator(); libPath += designer; result.append(libPath); } QString homeLibPath = QDir::homePath(); homeLibPath += QDir::separator(); homeLibPath += QLatin1String(".designer"); homeLibPath += QDir::separator(); homeLibPath += QLatin1String("plugins"); result.append(homeLibPath); return result; } // Figure out the language designer is running. ToDo: Introduce some // Language name API to QDesignerLanguageExtension? static inline QString getDesignerLanguage(QDesignerFormEditorInterface *core) { if (QDesignerLanguageExtension *lang = qt_extension(core->extensionManager(), core)) { if (lang->uiExtension() == QLatin1String("jui")) return QLatin1String(jambiLanguageC); return QLatin1String("unknown"); } return QLatin1String("c++"); } // ---------------- QDesignerCustomWidgetSharedData class QDesignerCustomWidgetSharedData : public QSharedData { public: explicit QDesignerCustomWidgetSharedData(const QString &thePluginPath) : pluginPath(thePluginPath) {} void clearXML(); QString pluginPath; QString xmlClassName; QString xmlDisplayName; QString xmlLanguage; QString xmlAddPageMethod; QString xmlExtends; }; void QDesignerCustomWidgetSharedData::clearXML() { xmlClassName.clear(); xmlDisplayName.clear(); xmlLanguage.clear(); xmlAddPageMethod.clear(); xmlExtends.clear(); } // ---------------- QDesignerCustomWidgetData QDesignerCustomWidgetData::QDesignerCustomWidgetData(const QString &pluginPath) : m_d(new QDesignerCustomWidgetSharedData(pluginPath)) { } QDesignerCustomWidgetData::QDesignerCustomWidgetData(const QDesignerCustomWidgetData &o) : m_d(o.m_d) { } QDesignerCustomWidgetData& QDesignerCustomWidgetData::operator=(const QDesignerCustomWidgetData &o) { m_d.operator=(o.m_d); return *this; } QDesignerCustomWidgetData::~QDesignerCustomWidgetData() { } bool QDesignerCustomWidgetData::isNull() const { return m_d->xmlClassName.isEmpty() || m_d->pluginPath.isEmpty(); } QString QDesignerCustomWidgetData::xmlClassName() const { return m_d->xmlClassName; } QString QDesignerCustomWidgetData::xmlLanguage() const { return m_d->xmlLanguage; } QString QDesignerCustomWidgetData::xmlAddPageMethod() const { return m_d->xmlAddPageMethod; } QString QDesignerCustomWidgetData::xmlExtends() const { return m_d->xmlExtends; } QString QDesignerCustomWidgetData::xmlDisplayName() const { return m_d->xmlDisplayName; } QString QDesignerCustomWidgetData::pluginPath() const { return m_d->pluginPath; } // Wind a QXmlStreamReader until it finds an element. Returns index or one of FindResult enum FindResult { FindError = -2, ElementNotFound = -1 }; static int findElement(const QStringList &desiredElts, QXmlStreamReader &sr) { while (true) { switch(sr.readNext()) { case QXmlStreamReader::EndDocument: return ElementNotFound; case QXmlStreamReader::Invalid: return FindError; case QXmlStreamReader::StartElement: { const int index = desiredElts.indexOf(sr.name().toString().toLower()); if (index >= 0) return index; } break; default: break; } } return FindError; } static inline QString msgXmlError(const QString &name, const QString &errorMessage) { return QDesignerPluginManager::tr("An XML error was encountered when parsing the XML of the custom widget %1: %2").arg(name, errorMessage); } QDesignerCustomWidgetData::ParseResult QDesignerCustomWidgetData::parseXml(const QString &xml, const QString &name, QString *errorMessage) { if (debugPluginManager) qDebug() << Q_FUNC_INFO << name; QDesignerCustomWidgetSharedData &data = *m_d; data.clearXML(); QXmlStreamReader sr(xml); bool foundUI = false; bool foundWidget = false; ParseResult rc = ParseOk; // Parse for the (optional) or the first element QStringList elements; elements.push_back(QLatin1String(uiElementC)); elements.push_back(QLatin1String(widgetElementC)); for (int i = 0; i < 2 && !foundWidget; i++) { switch (findElement(elements, sr)) { case FindError: *errorMessage = msgXmlError(name, sr.errorString()); return ParseError; case ElementNotFound: *errorMessage = QDesignerPluginManager::tr("The XML of the custom widget %1 does not contain any of the elements or .").arg(name); return ParseError; case 0: { // const QXmlStreamAttributes attributes = sr.attributes(); data.xmlLanguage = attributes.value(QLatin1String(languageAttributeC)).toString(); data.xmlDisplayName = attributes.value(QLatin1String(displayNameAttributeC)).toString(); foundUI = true; } break; case 1: // : Do some sanity checks data.xmlClassName = sr.attributes().value(QLatin1String(classAttributeC)).toString(); if (data.xmlClassName.isEmpty()) { *errorMessage = QDesignerPluginManager::tr("The class attribute for the class %1 is missing.").arg(name); rc = ParseWarning; } else { if (data.xmlClassName != name) { *errorMessage = QDesignerPluginManager::tr("The class attribute for the class %1 does not match the class name %2.").arg(data.xmlClassName, name); rc = ParseWarning; } } foundWidget = true; break; } } // Parse out the element which might be present if was there if (!foundUI) return rc; elements.clear(); elements.push_back(QLatin1String(customwidgetElementC)); switch (findElement(elements, sr)) { case FindError: *errorMessage = msgXmlError(name, sr.errorString()); return ParseError; case ElementNotFound: return rc; default: break; } // Find , elements.clear(); elements.push_back(QLatin1String(extendsElementC)); elements.push_back(QLatin1String(addPageMethodC)); while (true) { switch (findElement(elements, sr)) { case FindError: *errorMessage = msgXmlError(name, sr.errorString()); return ParseError; case ElementNotFound: return rc; case 0: // data.xmlExtends = sr.readElementText(); if (sr.tokenType() != QXmlStreamReader::EndElement) { *errorMessage = msgXmlError(name, sr.errorString()); return ParseError; } break; case 1: // data.xmlAddPageMethod = sr.readElementText(); if (sr.tokenType() != QXmlStreamReader::EndElement) { *errorMessage = msgXmlError(name, sr.errorString()); return ParseError; } break; } } return rc; } // ---------------- QDesignerPluginManagerPrivate class QDesignerPluginManagerPrivate { public: QDesignerPluginManagerPrivate(QDesignerFormEditorInterface *core); void clearCustomWidgets(); bool addCustomWidget(QDesignerCustomWidgetInterface *c, const QString &pluginPath, const QString &designerLanguage); void addCustomWidgets(const QObject *o, const QString &pluginPath, const QString &designerLanguage); QDesignerFormEditorInterface *m_core; QStringList m_pluginPaths; QStringList m_registeredPlugins; // TODO: QPluginLoader also caches invalid plugins -> This seems to be dead code QStringList m_disabledPlugins; typedef QMap FailedPluginMap; FailedPluginMap m_failedPlugins; // Synced lists of custom widgets and their data. Note that the list // must be ordered for collections to appear in order. QList m_customWidgets; QList m_customWidgetData; QStringList defaultPluginPaths() const; bool m_initialized; }; QDesignerPluginManagerPrivate::QDesignerPluginManagerPrivate(QDesignerFormEditorInterface *core) : m_core(core), m_initialized(false) { } void QDesignerPluginManagerPrivate::clearCustomWidgets() { m_customWidgets.clear(); m_customWidgetData.clear(); } // Add a custom widget to the list if it parses correctly // and is of the right language bool QDesignerPluginManagerPrivate::addCustomWidget(QDesignerCustomWidgetInterface *c, const QString &pluginPath, const QString &designerLanguage) { if (debugPluginManager) qDebug() << Q_FUNC_INFO << c->name(); if (!c->isInitialized()) c->initialize(m_core); // Parse the XML even if the plugin is initialized as Jambi might play tricks here QDesignerCustomWidgetData data(pluginPath); const QString domXml = c->domXml(); if (!domXml.isEmpty()) { // Legacy: Empty XML means: Do not show up in widget box. QString errorMessage; const QDesignerCustomWidgetData::ParseResult pr = data.parseXml(domXml, c->name(), &errorMessage); switch (pr) { case QDesignerCustomWidgetData::ParseOk: break; case QDesignerCustomWidgetData::ParseWarning: qdesigner_internal::designerWarning(errorMessage); break; case QDesignerCustomWidgetData::ParseError: qdesigner_internal::designerWarning(errorMessage); return false; } // Does the language match? const QString pluginLanguage = data.xmlLanguage(); if (!pluginLanguage.isEmpty() && pluginLanguage.compare(designerLanguage, Qt::CaseInsensitive)) return false; } m_customWidgets.push_back(c); m_customWidgetData.push_back(data); return true; } // Check the plugin interface for either a custom widget or a collection and // add all contained custom widgets. void QDesignerPluginManagerPrivate::addCustomWidgets(const QObject *o, const QString &pluginPath, const QString &designerLanguage) { if (QDesignerCustomWidgetInterface *c = qobject_cast(o)) { addCustomWidget(c, pluginPath, designerLanguage); return; } if (const QDesignerCustomWidgetCollectionInterface *coll = qobject_cast(o)) { foreach(QDesignerCustomWidgetInterface *c, coll->customWidgets()) addCustomWidget(c, pluginPath, designerLanguage); } } // ---------------- QDesignerPluginManager // As of 4.4, the header will be distributed with the Eclipse plugin. QDesignerPluginManager::QDesignerPluginManager(QDesignerFormEditorInterface *core) : QObject(core), m_d(new QDesignerPluginManagerPrivate(core)) { m_d->m_pluginPaths = defaultPluginPaths(); const QSettings settings(qApp->organizationName(), QDesignerQSettings::settingsApplicationName()); m_d->m_disabledPlugins = unique(settings.value(QLatin1String("PluginManager/DisabledPlugins")).toStringList()); // Register plugins updateRegisteredPlugins(); if (debugPluginManager) qDebug() << "QDesignerPluginManager::disabled: " << m_d->m_disabledPlugins << " static " << m_d->m_customWidgets.size(); } QDesignerPluginManager::~QDesignerPluginManager() { syncSettings(); delete m_d; } QDesignerFormEditorInterface *QDesignerPluginManager::core() const { return m_d->m_core; } QStringList QDesignerPluginManager::findPlugins(const QString &path) { if (debugPluginManager) qDebug() << Q_FUNC_INFO << path; const QDir dir(path); if (!dir.exists()) return QStringList(); const QFileInfoList infoList = dir.entryInfoList(QDir::Files); if (infoList.empty()) return QStringList(); // Load symbolic links but make sure all file names are unique as not // to fall for something like 'libplugin.so.1 -> libplugin.so' QStringList result; const QFileInfoList::const_iterator icend = infoList.constEnd(); for (QFileInfoList::const_iterator it = infoList.constBegin(); it != icend; ++it) { QString fileName; if (it->isSymLink()) { const QFileInfo linkTarget = QFileInfo(it->symLinkTarget()); if (linkTarget.exists() && linkTarget.isFile()) fileName = linkTarget.absoluteFilePath(); } else { fileName = it->absoluteFilePath(); } if (!fileName.isEmpty() && QLibrary::isLibrary(fileName) && !result.contains(fileName)) result += fileName; } return result; } void QDesignerPluginManager::setDisabledPlugins(const QStringList &disabled_plugins) { m_d->m_disabledPlugins = disabled_plugins; updateRegisteredPlugins(); } void QDesignerPluginManager::setPluginPaths(const QStringList &plugin_paths) { m_d->m_pluginPaths = plugin_paths; updateRegisteredPlugins(); } QStringList QDesignerPluginManager::disabledPlugins() const { return m_d->m_disabledPlugins; } QStringList QDesignerPluginManager::failedPlugins() const { return m_d->m_failedPlugins.keys(); } QString QDesignerPluginManager::failureReason(const QString &pluginName) const { return m_d->m_failedPlugins.value(pluginName); } QStringList QDesignerPluginManager::registeredPlugins() const { return m_d->m_registeredPlugins; } QStringList QDesignerPluginManager::pluginPaths() const { return m_d->m_pluginPaths; } QObject *QDesignerPluginManager::instance(const QString &plugin) const { if (m_d->m_disabledPlugins.contains(plugin)) return 0; QPluginLoader loader(plugin); return loader.instance(); } void QDesignerPluginManager::updateRegisteredPlugins() { if (debugPluginManager) qDebug() << Q_FUNC_INFO; m_d->m_registeredPlugins.clear(); foreach (const QString &path, m_d->m_pluginPaths) registerPath(path); } bool QDesignerPluginManager::registerNewPlugins() { if (debugPluginManager) qDebug() << Q_FUNC_INFO; const int before = m_d->m_registeredPlugins.size(); foreach (const QString &path, m_d->m_pluginPaths) registerPath(path); const bool newPluginsFound = m_d->m_registeredPlugins.size() > before; // We force a re-initialize as Jambi collection might return // different widget lists when switching projects. m_d->m_initialized = false; ensureInitialized(); return newPluginsFound; } void QDesignerPluginManager::registerPath(const QString &path) { if (debugPluginManager) qDebug() << Q_FUNC_INFO << path; QStringList candidates = findPlugins(path); foreach (const QString &plugin, candidates) registerPlugin(plugin); } void QDesignerPluginManager::registerPlugin(const QString &plugin) { if (debugPluginManager) qDebug() << Q_FUNC_INFO << plugin; if (m_d->m_disabledPlugins.contains(plugin)) return; if (m_d->m_registeredPlugins.contains(plugin)) return; QPluginLoader loader(plugin); if (loader.isLoaded() || loader.load()) { m_d->m_registeredPlugins += plugin; QDesignerPluginManagerPrivate::FailedPluginMap::iterator fit = m_d->m_failedPlugins.find(plugin); if (fit != m_d->m_failedPlugins.end()) m_d->m_failedPlugins.erase(fit); return; } const QString errorMessage = loader.errorString(); m_d->m_failedPlugins.insert(plugin, errorMessage); } bool QDesignerPluginManager::syncSettings() { QSettings settings(qApp->organizationName(), QDesignerQSettings::settingsApplicationName()); settings.beginGroup(QLatin1String("PluginManager")); settings.setValue(QLatin1String("DisabledPlugins"), m_d->m_disabledPlugins); settings.endGroup(); return settings.status() == QSettings::NoError; } void QDesignerPluginManager::ensureInitialized() { if (debugPluginManager) qDebug() << Q_FUNC_INFO << m_d->m_initialized << m_d->m_customWidgets.size(); if (m_d->m_initialized) return; const QString designerLanguage = getDesignerLanguage(m_d->m_core); m_d->clearCustomWidgets(); // Add the static custom widgets const QObjectList staticPluginObjects = QPluginLoader::staticInstances(); if (!staticPluginObjects.empty()) { const QString staticPluginPath = QCoreApplication::applicationFilePath(); foreach(QObject *o, staticPluginObjects) m_d->addCustomWidgets(o, staticPluginPath, designerLanguage); } foreach (const QString &plugin, m_d->m_registeredPlugins) if (QObject *o = instance(plugin)) m_d->addCustomWidgets(o, plugin, designerLanguage); m_d->m_initialized = true; } QDesignerPluginManager::CustomWidgetList QDesignerPluginManager::registeredCustomWidgets() const { const_cast(this)->ensureInitialized(); return m_d->m_customWidgets; } QDesignerCustomWidgetData QDesignerPluginManager::customWidgetData(QDesignerCustomWidgetInterface *w) const { const int index = m_d->m_customWidgets.indexOf(w); if (index == -1) return QDesignerCustomWidgetData(); return m_d->m_customWidgetData.at(index); } QObjectList QDesignerPluginManager::instances() const { QStringList plugins = registeredPlugins(); QObjectList lst; foreach (const QString &plugin, plugins) { if (QObject *o = instance(plugin)) lst.append(o); } return lst; } QT_END_NAMESPACE