1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Designer of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "pluginmanager_p.h"
30 #include "qdesigner_utils_p.h"
31 #include "qdesigner_qsettings_p.h"
32 
33 #include <QtDesigner/abstractformeditor.h>
34 #include <QtDesigner/qextensionmanager.h>
35 #include <QtDesigner/abstractlanguage.h>
36 
37 #include <QtUiPlugin/customwidget.h>
38 
39 #include <QtCore/qdir.h>
40 #include <QtCore/qfile.h>
41 #include <QtCore/qfileinfo.h>
42 #include <QtCore/qset.h>
43 #include <QtCore/qpluginloader.h>
44 #include <QtCore/qlibrary.h>
45 #include <QtCore/qlibraryinfo.h>
46 #include <QtCore/qdebug.h>
47 #include <QtCore/qmap.h>
48 #include <QtCore/qsettings.h>
49 #include <QtCore/qcoreapplication.h>
50 
51 #include <QtCore/qxmlstream.h>
52 
53 static const char *uiElementC = "ui";
54 static const char *languageAttributeC = "language";
55 static const char *widgetElementC = "widget";
56 static const char *displayNameAttributeC = "displayname";
57 static const char *classAttributeC = "class";
58 static const char *customwidgetElementC = "customwidget";
59 static const char *extendsElementC = "extends";
60 static const char *addPageMethodC = "addpagemethod";
61 static const char *propertySpecsC = "propertyspecifications";
62 static const char *stringPropertySpecC = "stringpropertyspecification";
63 static const char propertyToolTipC[] = "tooltip";
64 static const char *stringPropertyNameAttrC = "name";
65 static const char *stringPropertyTypeAttrC = "type";
66 static const char *stringPropertyNoTrAttrC = "notr";
67 static const char *jambiLanguageC = "jambi";
68 
69 enum { debugPluginManager = 0 };
70 
71 /* Custom widgets: Loading custom widgets is a 2-step process: PluginManager
72  * scans for its plugins in the constructor. At this point, it might not be safe
73  * to immediately initialize the custom widgets it finds, because the rest of
74  * Designer is not initialized yet.
75  * Later on, in ensureInitialized(), the plugin instances (including static ones)
76  * are iterated and the custom widget plugins are initialized and added to internal
77  * list of custom widgets and parsed data. Should there be a parse error or a language
78  * mismatch, it kicks out the respective custom widget. The m_initialized flag
79  * is used to indicate the state.
80  * Later, someone might call registerNewPlugins(), which agains clears the flag via
81  * registerPlugin() and triggers the process again.
82  * Also note that Jambi fakes a custom widget collection that changes its contents
83  * every time the project is switched. So, custom widget plugins can actually
84  * disappear, and the custom widget list must be cleared and refilled in
85  * ensureInitialized() after registerNewPlugins. */
86 
87 QT_BEGIN_NAMESPACE
88 
unique(const QStringList & lst)89 static QStringList unique(const QStringList &lst)
90 {
91     const QSet<QString> s(lst.cbegin(), lst.cend());
92     return s.values();
93 }
94 
defaultPluginPaths()95 QStringList QDesignerPluginManager::defaultPluginPaths()
96 {
97     QStringList result;
98 
99     const QStringList path_list = QCoreApplication::libraryPaths();
100 
101     const QString designer = QStringLiteral("designer");
102     for (const QString &path : path_list) {
103         QString libPath = path;
104         libPath += QDir::separator();
105         libPath += designer;
106         result.append(libPath);
107     }
108 
109     QString homeLibPath = QDir::homePath();
110     homeLibPath += QDir::separator();
111     homeLibPath += QStringLiteral(".designer");
112     homeLibPath += QDir::separator();
113     homeLibPath += QStringLiteral("plugins");
114 
115     result.append(homeLibPath);
116     return result;
117 }
118 
119 // Figure out the language designer is running. ToDo: Introduce some
120 // Language name API to QDesignerLanguageExtension?
121 
getDesignerLanguage(QDesignerFormEditorInterface * core)122 static inline QString getDesignerLanguage(QDesignerFormEditorInterface *core)
123 {
124     if (QDesignerLanguageExtension *lang = qt_extension<QDesignerLanguageExtension *>(core->extensionManager(), core)) {
125         if (lang->uiExtension() == QStringLiteral("jui"))
126             return QLatin1String(jambiLanguageC);
127         return QStringLiteral("unknown");
128     }
129     return QStringLiteral("c++");
130 }
131 
132 // ----------------  QDesignerCustomWidgetSharedData
133 
134 class QDesignerCustomWidgetSharedData : public QSharedData {
135 public:
136     // Type of a string property
137     using StringPropertyType = QPair<qdesigner_internal::TextPropertyValidationMode, bool>;
138     using StringPropertyTypeMap = QHash<QString, StringPropertyType>;
139     using PropertyToolTipMap = QHash<QString, QString>;
140 
QDesignerCustomWidgetSharedData(const QString & thePluginPath)141     explicit QDesignerCustomWidgetSharedData(const QString &thePluginPath) : pluginPath(thePluginPath) {}
142     void clearXML();
143 
144     QString pluginPath;
145 
146     QString xmlClassName;
147     QString xmlDisplayName;
148     QString xmlLanguage;
149     QString xmlAddPageMethod;
150     QString xmlExtends;
151 
152     StringPropertyTypeMap xmlStringPropertyTypeMap;
153     PropertyToolTipMap propertyToolTipMap;
154 };
155 
clearXML()156 void QDesignerCustomWidgetSharedData::clearXML()
157 {
158     xmlClassName.clear();
159     xmlDisplayName.clear();
160     xmlLanguage.clear();
161     xmlAddPageMethod.clear();
162     xmlExtends.clear();
163     xmlStringPropertyTypeMap.clear();
164 }
165 
166 // ----------------  QDesignerCustomWidgetData
167 
QDesignerCustomWidgetData(const QString & pluginPath)168 QDesignerCustomWidgetData::QDesignerCustomWidgetData(const QString &pluginPath) :
169     m_d(new QDesignerCustomWidgetSharedData(pluginPath))
170 {
171 }
172 
QDesignerCustomWidgetData(const QDesignerCustomWidgetData & o)173 QDesignerCustomWidgetData::QDesignerCustomWidgetData(const QDesignerCustomWidgetData &o) :
174      m_d(o.m_d)
175 {
176 }
177 
operator =(const QDesignerCustomWidgetData & o)178 QDesignerCustomWidgetData& QDesignerCustomWidgetData::operator=(const QDesignerCustomWidgetData &o)
179 {
180     m_d.operator=(o.m_d);
181     return *this;
182 }
183 
~QDesignerCustomWidgetData()184 QDesignerCustomWidgetData::~QDesignerCustomWidgetData()
185 {
186 }
187 
isNull() const188 bool QDesignerCustomWidgetData::isNull() const
189 {
190     return m_d->xmlClassName.isEmpty() || m_d->pluginPath.isEmpty();
191 }
192 
xmlClassName() const193 QString QDesignerCustomWidgetData::xmlClassName() const
194 {
195     return m_d->xmlClassName;
196 }
197 
xmlLanguage() const198 QString QDesignerCustomWidgetData::xmlLanguage() const
199 {
200     return m_d->xmlLanguage;
201 }
202 
xmlAddPageMethod() const203 QString QDesignerCustomWidgetData::xmlAddPageMethod() const
204 {
205     return m_d->xmlAddPageMethod;
206 }
207 
xmlExtends() const208 QString QDesignerCustomWidgetData::xmlExtends() const
209 {
210     return m_d->xmlExtends;
211 }
212 
xmlDisplayName() const213 QString QDesignerCustomWidgetData::xmlDisplayName() const
214 {
215     return m_d->xmlDisplayName;
216 }
217 
pluginPath() const218 QString QDesignerCustomWidgetData::pluginPath() const
219 {
220     return m_d->pluginPath;
221 }
222 
xmlStringPropertyType(const QString & name,StringPropertyType * type) const223 bool QDesignerCustomWidgetData::xmlStringPropertyType(const QString &name, StringPropertyType *type) const
224 {
225     QDesignerCustomWidgetSharedData::StringPropertyTypeMap::const_iterator it = m_d->xmlStringPropertyTypeMap.constFind(name);
226     if (it == m_d->xmlStringPropertyTypeMap.constEnd()) {
227         *type = StringPropertyType(qdesigner_internal::ValidationRichText, true);
228         return false;
229     }
230     *type = it.value();
231     return true;
232 }
233 
propertyToolTip(const QString & name) const234 QString QDesignerCustomWidgetData::propertyToolTip(const QString &name) const
235 {
236     return m_d->propertyToolTipMap.value(name);
237 }
238 
239 // Wind a QXmlStreamReader  until it finds an element. Returns index or one of FindResult
240 enum FindResult { FindError = -2, ElementNotFound = -1 };
241 
findElement(const QStringList & desiredElts,QXmlStreamReader & sr)242 static int findElement(const QStringList &desiredElts, QXmlStreamReader &sr)
243 {
244     while (true) {
245         switch(sr.readNext()) {
246         case QXmlStreamReader::EndDocument:
247             return ElementNotFound;
248         case QXmlStreamReader::Invalid:
249             return FindError;
250         case QXmlStreamReader::StartElement: {
251             const int index = desiredElts.indexOf(sr.name().toString().toLower());
252             if (index >= 0)
253                 return index;
254         }
255             break;
256         default:
257             break;
258         }
259     }
260     return FindError;
261 }
262 
msgXmlError(const QString & name,const QString & errorMessage)263 static inline QString msgXmlError(const QString &name, const QString &errorMessage)
264 {
265     return QDesignerPluginManager::tr("An XML error was encountered when parsing the XML of the custom widget %1: %2").arg(name, errorMessage);
266 }
267 
msgAttributeMissing(const QString & name)268 static inline QString msgAttributeMissing(const QString &name)
269 {
270     return QDesignerPluginManager::tr("A required attribute ('%1') is missing.").arg(name);
271 }
272 
typeStringToType(const QString & v,bool * ok)273 static qdesigner_internal::TextPropertyValidationMode typeStringToType(const QString &v, bool *ok)
274 {
275     *ok = true;
276     if (v  == QStringLiteral("multiline"))
277         return qdesigner_internal::ValidationMultiLine;
278     if (v  == QStringLiteral("richtext"))
279         return qdesigner_internal::ValidationRichText;
280     if (v  == QStringLiteral("stylesheet"))
281         return qdesigner_internal::ValidationStyleSheet;
282     if (v  == QStringLiteral("singleline"))
283         return qdesigner_internal::ValidationSingleLine;
284     if (v  == QStringLiteral("objectname"))
285         return qdesigner_internal::ValidationObjectName;
286     if (v  == QStringLiteral("objectnamescope"))
287         return qdesigner_internal::ValidationObjectNameScope;
288     if (v  == QStringLiteral("url"))
289         return qdesigner_internal::ValidationURL;
290     *ok = false;
291     return qdesigner_internal::ValidationRichText;
292 }
293 
parsePropertySpecs(QXmlStreamReader & sr,QDesignerCustomWidgetSharedData * data,QString * errorMessage)294 static bool parsePropertySpecs(QXmlStreamReader &sr,
295                                QDesignerCustomWidgetSharedData *data,
296                                QString *errorMessage)
297 {
298     const QString propertySpecs = QLatin1String(propertySpecsC);
299     const QString stringPropertySpec = QLatin1String(stringPropertySpecC);
300     const QString propertyToolTip = QLatin1String(propertyToolTipC);
301     const QString stringPropertyTypeAttr = QLatin1String(stringPropertyTypeAttrC);
302     const QString stringPropertyNoTrAttr = QLatin1String(stringPropertyNoTrAttrC);
303     const QString stringPropertyNameAttr = QLatin1String(stringPropertyNameAttrC);
304 
305     while (!sr.atEnd()) {
306         switch(sr.readNext()) {
307         case QXmlStreamReader::StartElement: {
308             if (sr.name() == stringPropertySpec) {
309                 const QXmlStreamAttributes atts = sr.attributes();
310                 const QString name = atts.value(stringPropertyNameAttr).toString();
311                 const QString type = atts.value(stringPropertyTypeAttr).toString();
312                 const QString notrS = atts.value(stringPropertyNoTrAttr).toString(); //Optional
313 
314                 if (type.isEmpty()) {
315                     *errorMessage = msgAttributeMissing(stringPropertyTypeAttr);
316                     return false;
317                 }
318                 if (name.isEmpty()) {
319                     *errorMessage = msgAttributeMissing(stringPropertyNameAttr);
320                     return false;
321                 }
322                 bool typeOk;
323                 const bool noTr = notrS == QStringLiteral("true") || notrS == QStringLiteral("1");
324                 QDesignerCustomWidgetSharedData::StringPropertyType v(typeStringToType(type, &typeOk), !noTr);
325                 if (!typeOk) {
326                     *errorMessage = QDesignerPluginManager::tr("'%1' is not a valid string property specification.").arg(type);
327                     return false;
328                 }
329                 data->xmlStringPropertyTypeMap.insert(name, v);
330             } else if (sr.name() == propertyToolTip) {
331                 const QString name = sr.attributes().value(stringPropertyNameAttr).toString();
332                 if (name.isEmpty()) {
333                     *errorMessage = msgAttributeMissing(stringPropertyNameAttr);
334                     return false;
335                 }
336                 data->propertyToolTipMap.insert(name, sr.readElementText().trimmed());
337             } else {
338                 *errorMessage = QDesignerPluginManager::tr("An invalid property specification ('%1') was encountered. Supported types: %2").arg(sr.name().toString(), stringPropertySpec);
339                 return false;
340             }
341         }
342             break;
343         case QXmlStreamReader::EndElement: // Outer </stringproperties>
344             if (sr.name() == propertySpecs)
345                 return true;
346         default:
347             break;
348         }
349     }
350     return true;
351 }
352 
353 QDesignerCustomWidgetData::ParseResult
parseXml(const QString & xml,const QString & name,QString * errorMessage)354                        QDesignerCustomWidgetData::parseXml(const QString &xml, const QString &name, QString *errorMessage)
355 {
356     if (debugPluginManager)
357         qDebug() << Q_FUNC_INFO << name;
358 
359     QDesignerCustomWidgetSharedData &data =  *m_d;
360     data.clearXML();
361 
362     QXmlStreamReader sr(xml);
363 
364     bool foundUI = false;
365     bool foundWidget = false;
366     ParseResult rc = ParseOk;
367     // Parse for the (optional) <ui> or the first <widget> element
368     QStringList elements;
369     elements.push_back(QLatin1String(uiElementC));
370     elements.push_back(QLatin1String(widgetElementC));
371     for (int i = 0; i < 2 && !foundWidget; i++) {
372         switch (findElement(elements, sr)) {
373         case FindError:
374             *errorMessage = msgXmlError(name, sr.errorString());
375             return ParseError;
376         case ElementNotFound:
377             *errorMessage = QDesignerPluginManager::tr("The XML of the custom widget %1 does not contain any of the elements <widget> or <ui>.").arg(name);
378             return ParseError;
379         case 0: { // <ui>
380             const QXmlStreamAttributes attributes = sr.attributes();
381             data.xmlLanguage = attributes.value(QLatin1String(languageAttributeC)).toString();
382             data.xmlDisplayName = attributes.value(QLatin1String(displayNameAttributeC)).toString();
383             foundUI = true;
384         }
385             break;
386         case 1: // <widget>: Do some sanity checks
387             data.xmlClassName = sr.attributes().value(QLatin1String(classAttributeC)).toString();
388             if (data.xmlClassName.isEmpty()) {
389                 *errorMessage = QDesignerPluginManager::tr("The class attribute for the class %1 is missing.").arg(name);
390                 rc = ParseWarning;
391             } else {
392                 if (data.xmlClassName != name) {
393                     *errorMessage = QDesignerPluginManager::tr("The class attribute for the class %1 does not match the class name %2.").arg(data.xmlClassName, name);
394                     rc = ParseWarning;
395                 }
396             }
397             foundWidget = true;
398             break;
399         }
400     }
401     // Parse out the <customwidget> element which might be present if  <ui> was there
402     if (!foundUI)
403         return rc;
404     elements.clear();
405     elements.push_back(QLatin1String(customwidgetElementC));
406     switch (findElement(elements, sr)) {
407     case FindError:
408         *errorMessage = msgXmlError(name, sr.errorString());
409         return ParseError;
410     case ElementNotFound:
411         return rc;
412     default:
413         break;
414     }
415     // Find <extends>, <addPageMethod>, <stringproperties>
416     elements.clear();
417     elements.push_back(QLatin1String(extendsElementC));
418     elements.push_back(QLatin1String(addPageMethodC));
419     elements.push_back(QLatin1String(propertySpecsC));
420     while (true) {
421         switch (findElement(elements, sr)) {
422         case FindError:
423             *errorMessage = msgXmlError(name, sr.errorString());
424             return ParseError;
425         case ElementNotFound:
426             return rc;
427         case 0: // <extends>
428             data.xmlExtends = sr.readElementText();
429             if (sr.tokenType() != QXmlStreamReader::EndElement) {
430                 *errorMessage = msgXmlError(name, sr.errorString());
431                 return ParseError;
432             }
433             break;
434         case 1: // <addPageMethod>
435             data.xmlAddPageMethod = sr.readElementText();
436             if (sr.tokenType() != QXmlStreamReader::EndElement) {
437                 *errorMessage = msgXmlError(name, sr.errorString());
438                 return ParseError;
439             }
440             break;
441         case 2: // <stringproperties>
442             if (!parsePropertySpecs(sr, m_d.data(), errorMessage)) {
443                 *errorMessage = msgXmlError(name, *errorMessage);
444                 return ParseError;
445             }
446             break;
447         }
448     }
449     return rc;
450 }
451 
452 // ---------------- QDesignerPluginManagerPrivate
453 
454 class QDesignerPluginManagerPrivate {
455     public:
456     using ClassNamePropertyNameKey = QPair<QString, QString>;
457 
458     QDesignerPluginManagerPrivate(QDesignerFormEditorInterface *core);
459 
460     void clearCustomWidgets();
461     bool addCustomWidget(QDesignerCustomWidgetInterface *c,
462                          const QString &pluginPath,
463                          const QString &designerLanguage);
464     void addCustomWidgets(const QObject *o,
465                           const QString &pluginPath,
466                           const QString &designerLanguage);
467 
468     QDesignerFormEditorInterface *m_core;
469     QStringList m_pluginPaths;
470     QStringList m_registeredPlugins;
471     // TODO: QPluginLoader also caches invalid plugins -> This seems to be dead code
472     QStringList m_disabledPlugins;
473 
474     typedef QMap<QString, QString> FailedPluginMap;
475     FailedPluginMap m_failedPlugins;
476 
477     // Synced lists of custom widgets and their data. Note that the list
478     // must be ordered for collections to appear in order.
479     QList<QDesignerCustomWidgetInterface *> m_customWidgets;
480     QList<QDesignerCustomWidgetData> m_customWidgetData;
481 
482     QStringList defaultPluginPaths() const;
483 
484     bool m_initialized;
485 };
486 
QDesignerPluginManagerPrivate(QDesignerFormEditorInterface * core)487 QDesignerPluginManagerPrivate::QDesignerPluginManagerPrivate(QDesignerFormEditorInterface *core) :
488    m_core(core),
489    m_initialized(false)
490 {
491 }
492 
clearCustomWidgets()493 void QDesignerPluginManagerPrivate::clearCustomWidgets()
494 {
495     m_customWidgets.clear();
496     m_customWidgetData.clear();
497 }
498 
499 // Add a custom widget to the list if it parses correctly
500 // and is of the right language
addCustomWidget(QDesignerCustomWidgetInterface * c,const QString & pluginPath,const QString & designerLanguage)501 bool QDesignerPluginManagerPrivate::addCustomWidget(QDesignerCustomWidgetInterface *c,
502                                                     const QString &pluginPath,
503                                                     const QString &designerLanguage)
504 {
505     if (debugPluginManager)
506         qDebug() << Q_FUNC_INFO << c->name();
507 
508     if (!c->isInitialized())
509         c->initialize(m_core);
510     // Parse the XML even if the plugin is initialized as Jambi might play tricks here
511     QDesignerCustomWidgetData data(pluginPath);
512     const QString domXml = c->domXml();
513     if (!domXml.isEmpty()) { // Legacy: Empty XML means: Do not show up in widget box.
514         QString errorMessage;
515         const QDesignerCustomWidgetData::ParseResult pr = data.parseXml(domXml, c->name(), &errorMessage);
516         switch (pr) {
517             case QDesignerCustomWidgetData::ParseOk:
518             break;
519             case QDesignerCustomWidgetData::ParseWarning:
520             qdesigner_internal::designerWarning(errorMessage);
521             break;
522             case QDesignerCustomWidgetData::ParseError:
523             qdesigner_internal::designerWarning(errorMessage);
524             return false;
525         }
526         // Does the language match?
527         const QString pluginLanguage = data.xmlLanguage();
528         if (!pluginLanguage.isEmpty() && pluginLanguage.compare(designerLanguage, Qt::CaseInsensitive))
529             return false;
530     }
531     m_customWidgets.push_back(c);
532     m_customWidgetData.push_back(data);
533     return true;
534 }
535 
536 // Check the plugin interface for either a custom widget or a collection and
537 // add all contained custom widgets.
addCustomWidgets(const QObject * o,const QString & pluginPath,const QString & designerLanguage)538 void QDesignerPluginManagerPrivate::addCustomWidgets(const QObject *o,
539                                                      const QString &pluginPath,
540                                                      const QString &designerLanguage)
541 {
542     if (QDesignerCustomWidgetInterface *c = qobject_cast<QDesignerCustomWidgetInterface*>(o)) {
543         addCustomWidget(c, pluginPath, designerLanguage);
544         return;
545     }
546     if (const QDesignerCustomWidgetCollectionInterface *coll = qobject_cast<QDesignerCustomWidgetCollectionInterface*>(o)) {
547         const auto &collCustomWidgets = coll->customWidgets();
548         for (QDesignerCustomWidgetInterface *c : collCustomWidgets)
549             addCustomWidget(c, pluginPath, designerLanguage);
550     }
551 }
552 
553 
554 // ---------------- QDesignerPluginManager
555 // As of 4.4, the header will be distributed with the Eclipse plugin.
556 
QDesignerPluginManager(QDesignerFormEditorInterface * core)557 QDesignerPluginManager::QDesignerPluginManager(QDesignerFormEditorInterface *core) :
558     QObject(core),
559     m_d(new QDesignerPluginManagerPrivate(core))
560 {
561     m_d->m_pluginPaths = defaultPluginPaths();
562     const QSettings settings(qApp->organizationName(), QDesignerQSettings::settingsApplicationName());
563     m_d->m_disabledPlugins = unique(settings.value(QStringLiteral("PluginManager/DisabledPlugins")).toStringList());
564 
565     // Register plugins
566     updateRegisteredPlugins();
567 
568     if (debugPluginManager)
569         qDebug() << "QDesignerPluginManager::disabled: " <<  m_d->m_disabledPlugins << " static " << m_d->m_customWidgets.size();
570 }
571 
~QDesignerPluginManager()572 QDesignerPluginManager::~QDesignerPluginManager()
573 {
574     syncSettings();
575     delete m_d;
576 }
577 
core() const578 QDesignerFormEditorInterface *QDesignerPluginManager::core() const
579 {
580     return m_d->m_core;
581 }
582 
findPlugins(const QString & path)583 QStringList QDesignerPluginManager::findPlugins(const QString &path)
584 {
585     if (debugPluginManager)
586         qDebug() << Q_FUNC_INFO << path;
587     const QDir dir(path);
588     if (!dir.exists())
589         return QStringList();
590 
591     const QFileInfoList infoList = dir.entryInfoList(QDir::Files);
592     if (infoList.isEmpty())
593         return QStringList();
594 
595     // Load symbolic links but make sure all file names are unique as not
596     // to fall for something like 'libplugin.so.1 -> libplugin.so'
597     QStringList result;
598     const QFileInfoList::const_iterator icend = infoList.constEnd();
599     for (QFileInfoList::const_iterator it = infoList.constBegin(); it != icend; ++it) {
600         QString fileName;
601         if (it->isSymLink()) {
602             const QFileInfo linkTarget = QFileInfo(it->symLinkTarget());
603             if (linkTarget.exists() && linkTarget.isFile())
604                 fileName = linkTarget.absoluteFilePath();
605         } else {
606             fileName = it->absoluteFilePath();
607         }
608         if (!fileName.isEmpty() && QLibrary::isLibrary(fileName) && !result.contains(fileName))
609             result += fileName;
610     }
611     return result;
612 }
613 
setDisabledPlugins(const QStringList & disabled_plugins)614 void QDesignerPluginManager::setDisabledPlugins(const QStringList &disabled_plugins)
615 {
616     m_d->m_disabledPlugins = disabled_plugins;
617     updateRegisteredPlugins();
618 }
619 
setPluginPaths(const QStringList & plugin_paths)620 void QDesignerPluginManager::setPluginPaths(const QStringList &plugin_paths)
621 {
622     m_d->m_pluginPaths = plugin_paths;
623     updateRegisteredPlugins();
624 }
625 
disabledPlugins() const626 QStringList QDesignerPluginManager::disabledPlugins() const
627 {
628     return m_d->m_disabledPlugins;
629 }
630 
failedPlugins() const631 QStringList QDesignerPluginManager::failedPlugins() const
632 {
633     return m_d->m_failedPlugins.keys();
634 }
635 
failureReason(const QString & pluginName) const636 QString QDesignerPluginManager::failureReason(const QString &pluginName) const
637 {
638     return m_d->m_failedPlugins.value(pluginName);
639 }
640 
registeredPlugins() const641 QStringList QDesignerPluginManager::registeredPlugins() const
642 {
643     return m_d->m_registeredPlugins;
644 }
645 
pluginPaths() const646 QStringList QDesignerPluginManager::pluginPaths() const
647 {
648     return m_d->m_pluginPaths;
649 }
650 
instance(const QString & plugin) const651 QObject *QDesignerPluginManager::instance(const QString &plugin) const
652 {
653     if (m_d->m_disabledPlugins.contains(plugin))
654         return nullptr;
655 
656     QPluginLoader loader(plugin);
657     return loader.instance();
658 }
659 
updateRegisteredPlugins()660 void QDesignerPluginManager::updateRegisteredPlugins()
661 {
662     if (debugPluginManager)
663         qDebug() << Q_FUNC_INFO;
664     m_d->m_registeredPlugins.clear();
665     for (const QString &path : qAsConst(m_d->m_pluginPaths))
666         registerPath(path);
667 }
668 
registerNewPlugins()669 bool QDesignerPluginManager::registerNewPlugins()
670 {
671     if (debugPluginManager)
672         qDebug() << Q_FUNC_INFO;
673 
674     const int before = m_d->m_registeredPlugins.size();
675     for (const QString &path : qAsConst(m_d->m_pluginPaths))
676         registerPath(path);
677     const bool newPluginsFound = m_d->m_registeredPlugins.size() > before;
678     // We force a re-initialize as Jambi collection might return
679     // different widget lists when switching projects.
680     m_d->m_initialized = false;
681     ensureInitialized();
682 
683     return newPluginsFound;
684 }
685 
registerPath(const QString & path)686 void QDesignerPluginManager::registerPath(const QString &path)
687 {
688     if (debugPluginManager)
689         qDebug() << Q_FUNC_INFO << path;
690     const QStringList &candidates = findPlugins(path);
691     for (const QString &plugin : candidates)
692         registerPlugin(plugin);
693 }
694 
registerPlugin(const QString & plugin)695 void QDesignerPluginManager::registerPlugin(const QString &plugin)
696 {
697     if (debugPluginManager)
698         qDebug() << Q_FUNC_INFO << plugin;
699     if (m_d->m_disabledPlugins.contains(plugin))
700         return;
701     if (m_d->m_registeredPlugins.contains(plugin))
702         return;
703 
704     QPluginLoader loader(plugin);
705     if (loader.isLoaded() || loader.load()) {
706         m_d->m_registeredPlugins += plugin;
707         QDesignerPluginManagerPrivate::FailedPluginMap::iterator fit = m_d->m_failedPlugins.find(plugin);
708         if (fit != m_d->m_failedPlugins.end())
709             m_d->m_failedPlugins.erase(fit);
710         return;
711     }
712 
713     const QString errorMessage = loader.errorString();
714     m_d->m_failedPlugins.insert(plugin, errorMessage);
715 }
716 
717 
718 
syncSettings()719 bool QDesignerPluginManager::syncSettings()
720 {
721     QSettings settings(qApp->organizationName(), QDesignerQSettings::settingsApplicationName());
722     settings.beginGroup(QStringLiteral("PluginManager"));
723     settings.setValue(QStringLiteral("DisabledPlugins"), m_d->m_disabledPlugins);
724     settings.endGroup();
725     return settings.status() == QSettings::NoError;
726 }
727 
ensureInitialized()728 void QDesignerPluginManager::ensureInitialized()
729 {
730     if (debugPluginManager)
731         qDebug() << Q_FUNC_INFO <<  m_d->m_initialized << m_d->m_customWidgets.size();
732 
733     if (m_d->m_initialized)
734         return;
735 
736     const QString designerLanguage = getDesignerLanguage(m_d->m_core);
737 
738     m_d->clearCustomWidgets();
739     // Add the static custom widgets
740     const QObjectList staticPluginObjects = QPluginLoader::staticInstances();
741     if (!staticPluginObjects.isEmpty()) {
742         const QString staticPluginPath = QCoreApplication::applicationFilePath();
743         for (QObject *o : staticPluginObjects)
744             m_d->addCustomWidgets(o, staticPluginPath, designerLanguage);
745     }
746     for (const QString &plugin : qAsConst(m_d->m_registeredPlugins)) {
747         if (QObject *o = instance(plugin))
748             m_d->addCustomWidgets(o, plugin, designerLanguage);
749     }
750 
751     m_d->m_initialized = true;
752 }
753 
registeredCustomWidgets() const754 QDesignerPluginManager::CustomWidgetList QDesignerPluginManager::registeredCustomWidgets() const
755 {
756     const_cast<QDesignerPluginManager*>(this)->ensureInitialized();
757     return m_d->m_customWidgets;
758 }
759 
customWidgetData(QDesignerCustomWidgetInterface * w) const760 QDesignerCustomWidgetData QDesignerPluginManager::customWidgetData(QDesignerCustomWidgetInterface *w) const
761 {
762     const int index = m_d->m_customWidgets.indexOf(w);
763     if (index == -1)
764         return QDesignerCustomWidgetData();
765     return m_d->m_customWidgetData.at(index);
766 }
767 
customWidgetData(const QString & name) const768 QDesignerCustomWidgetData QDesignerPluginManager::customWidgetData(const QString &name) const
769 {
770     const int count = m_d->m_customWidgets.size();
771     for (int i = 0; i < count; i++)
772         if (m_d->m_customWidgets.at(i)->name() == name)
773             return m_d->m_customWidgetData.at(i);
774     return QDesignerCustomWidgetData();
775 }
776 
instances() const777 QObjectList QDesignerPluginManager::instances() const
778 {
779     const QStringList &plugins = registeredPlugins();
780 
781     QObjectList lst;
782     for (const QString &plugin : plugins) {
783         if (QObject *o = instance(plugin))
784             lst.append(o);
785     }
786 
787     return lst;
788 }
789 
790 QT_END_NAMESPACE
791