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