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 | |
89 | static QStringList unique(const QStringList &lst) |
90 | { |
91 | const QSet<QString> s(lst.cbegin(), lst.cend()); |
92 | return s.values(); |
93 | } |
94 | |
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(t: 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(t: homeLibPath); |
116 | return result; |
117 | } |
118 | |
119 | // Figure out the language designer is running. ToDo: Introduce some |
120 | // Language name API to QDesignerLanguageExtension? |
121 | |
122 | static inline QString getDesignerLanguage(QDesignerFormEditorInterface *core) |
123 | { |
124 | if (QDesignerLanguageExtension *lang = qt_extension<QDesignerLanguageExtension *>(manager: core->extensionManager(), object: 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 | |
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 | |
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 | |
168 | QDesignerCustomWidgetData::QDesignerCustomWidgetData(const QString &pluginPath) : |
169 | m_d(new QDesignerCustomWidgetSharedData(pluginPath)) |
170 | { |
171 | } |
172 | |
173 | QDesignerCustomWidgetData::QDesignerCustomWidgetData(const QDesignerCustomWidgetData &o) : |
174 | m_d(o.m_d) |
175 | { |
176 | } |
177 | |
178 | QDesignerCustomWidgetData& QDesignerCustomWidgetData::operator=(const QDesignerCustomWidgetData &o) |
179 | { |
180 | m_d.operator=(o: o.m_d); |
181 | return *this; |
182 | } |
183 | |
184 | QDesignerCustomWidgetData::~QDesignerCustomWidgetData() |
185 | { |
186 | } |
187 | |
188 | bool QDesignerCustomWidgetData::isNull() const |
189 | { |
190 | return m_d->xmlClassName.isEmpty() || m_d->pluginPath.isEmpty(); |
191 | } |
192 | |
193 | QString QDesignerCustomWidgetData::xmlClassName() const |
194 | { |
195 | return m_d->xmlClassName; |
196 | } |
197 | |
198 | QString QDesignerCustomWidgetData::xmlLanguage() const |
199 | { |
200 | return m_d->xmlLanguage; |
201 | } |
202 | |
203 | QString QDesignerCustomWidgetData::xmlAddPageMethod() const |
204 | { |
205 | return m_d->xmlAddPageMethod; |
206 | } |
207 | |
208 | QString QDesignerCustomWidgetData::xmlExtends() const |
209 | { |
210 | return m_d->xmlExtends; |
211 | } |
212 | |
213 | QString QDesignerCustomWidgetData::xmlDisplayName() const |
214 | { |
215 | return m_d->xmlDisplayName; |
216 | } |
217 | |
218 | QString QDesignerCustomWidgetData::pluginPath() const |
219 | { |
220 | return m_d->pluginPath; |
221 | } |
222 | |
223 | bool QDesignerCustomWidgetData::xmlStringPropertyType(const QString &name, StringPropertyType *type) const |
224 | { |
225 | QDesignerCustomWidgetSharedData::StringPropertyTypeMap::const_iterator it = m_d->xmlStringPropertyTypeMap.constFind(akey: 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 | |
234 | QString QDesignerCustomWidgetData::propertyToolTip(const QString &name) const |
235 | { |
236 | return m_d->propertyToolTipMap.value(akey: 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 | |
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(t: 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 | |
263 | static inline QString msgXmlError(const QString &name, const QString &errorMessage) |
264 | { |
265 | return QDesignerPluginManager::tr(s: "An XML error was encountered when parsing the XML of the custom widget %1: %2" ).arg(a1: name, a2: errorMessage); |
266 | } |
267 | |
268 | static inline QString msgAttributeMissing(const QString &name) |
269 | { |
270 | return QDesignerPluginManager::tr(s: "A required attribute ('%1') is missing." ).arg(a: name); |
271 | } |
272 | |
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 | |
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(qualifiedName: stringPropertyNameAttr).toString(); |
311 | const QString type = atts.value(qualifiedName: stringPropertyTypeAttr).toString(); |
312 | const QString notrS = atts.value(qualifiedName: stringPropertyNoTrAttr).toString(); //Optional |
313 | |
314 | if (type.isEmpty()) { |
315 | *errorMessage = msgAttributeMissing(name: stringPropertyTypeAttr); |
316 | return false; |
317 | } |
318 | if (name.isEmpty()) { |
319 | *errorMessage = msgAttributeMissing(name: stringPropertyNameAttr); |
320 | return false; |
321 | } |
322 | bool typeOk; |
323 | const bool noTr = notrS == QStringLiteral("true" ) || notrS == QStringLiteral("1" ); |
324 | QDesignerCustomWidgetSharedData::StringPropertyType v(typeStringToType(v: type, ok: &typeOk), !noTr); |
325 | if (!typeOk) { |
326 | *errorMessage = QDesignerPluginManager::tr(s: "'%1' is not a valid string property specification." ).arg(a: type); |
327 | return false; |
328 | } |
329 | data->xmlStringPropertyTypeMap.insert(akey: name, avalue: v); |
330 | } else if (sr.name() == propertyToolTip) { |
331 | const QString name = sr.attributes().value(qualifiedName: stringPropertyNameAttr).toString(); |
332 | if (name.isEmpty()) { |
333 | *errorMessage = msgAttributeMissing(name: stringPropertyNameAttr); |
334 | return false; |
335 | } |
336 | data->propertyToolTipMap.insert(akey: name, avalue: sr.readElementText().trimmed()); |
337 | } else { |
338 | *errorMessage = QDesignerPluginManager::tr(s: "An invalid property specification ('%1') was encountered. Supported types: %2" ).arg(args: sr.name().toString(), args: 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 |
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(t: QLatin1String(uiElementC)); |
370 | elements.push_back(t: QLatin1String(widgetElementC)); |
371 | for (int i = 0; i < 2 && !foundWidget; i++) { |
372 | switch (findElement(desiredElts: elements, sr)) { |
373 | case FindError: |
374 | *errorMessage = msgXmlError(name, errorMessage: sr.errorString()); |
375 | return ParseError; |
376 | case ElementNotFound: |
377 | *errorMessage = QDesignerPluginManager::tr(s: "The XML of the custom widget %1 does not contain any of the elements <widget> or <ui>." ).arg(a: name); |
378 | return ParseError; |
379 | case 0: { // <ui> |
380 | const QXmlStreamAttributes attributes = sr.attributes(); |
381 | data.xmlLanguage = attributes.value(qualifiedName: QLatin1String(languageAttributeC)).toString(); |
382 | data.xmlDisplayName = attributes.value(qualifiedName: QLatin1String(displayNameAttributeC)).toString(); |
383 | foundUI = true; |
384 | } |
385 | break; |
386 | case 1: // <widget>: Do some sanity checks |
387 | data.xmlClassName = sr.attributes().value(qualifiedName: QLatin1String(classAttributeC)).toString(); |
388 | if (data.xmlClassName.isEmpty()) { |
389 | *errorMessage = QDesignerPluginManager::tr(s: "The class attribute for the class %1 is missing." ).arg(a: name); |
390 | rc = ParseWarning; |
391 | } else { |
392 | if (data.xmlClassName != name) { |
393 | *errorMessage = QDesignerPluginManager::tr(s: "The class attribute for the class %1 does not match the class name %2." ).arg(args&: data.xmlClassName, args: 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(t: QLatin1String(customwidgetElementC)); |
406 | switch (findElement(desiredElts: elements, sr)) { |
407 | case FindError: |
408 | *errorMessage = msgXmlError(name, errorMessage: 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(t: QLatin1String(extendsElementC)); |
418 | elements.push_back(t: QLatin1String(addPageMethodC)); |
419 | elements.push_back(t: QLatin1String(propertySpecsC)); |
420 | while (true) { |
421 | switch (findElement(desiredElts: elements, sr)) { |
422 | case FindError: |
423 | *errorMessage = msgXmlError(name, errorMessage: 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, errorMessage: 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, errorMessage: sr.errorString()); |
438 | return ParseError; |
439 | } |
440 | break; |
441 | case 2: // <stringproperties> |
442 | if (!parsePropertySpecs(sr, data: m_d.data(), errorMessage)) { |
443 | *errorMessage = msgXmlError(name, errorMessage: *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 | |
487 | QDesignerPluginManagerPrivate::QDesignerPluginManagerPrivate(QDesignerFormEditorInterface *core) : |
488 | m_core(core), |
489 | m_initialized(false) |
490 | { |
491 | } |
492 | |
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 |
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(core: 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(xml: domXml, name: c->name(), errorMessage: &errorMessage); |
516 | switch (pr) { |
517 | case QDesignerCustomWidgetData::ParseOk: |
518 | break; |
519 | case QDesignerCustomWidgetData::ParseWarning: |
520 | qdesigner_internal::designerWarning(message: errorMessage); |
521 | break; |
522 | case QDesignerCustomWidgetData::ParseError: |
523 | qdesigner_internal::designerWarning(message: errorMessage); |
524 | return false; |
525 | } |
526 | // Does the language match? |
527 | const QString pluginLanguage = data.xmlLanguage(); |
528 | if (!pluginLanguage.isEmpty() && pluginLanguage.compare(s: designerLanguage, cs: Qt::CaseInsensitive)) |
529 | return false; |
530 | } |
531 | m_customWidgets.push_back(t: c); |
532 | m_customWidgetData.push_back(t: 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. |
538 | void QDesignerPluginManagerPrivate::addCustomWidgets(const QObject *o, |
539 | const QString &pluginPath, |
540 | const QString &designerLanguage) |
541 | { |
542 | if (QDesignerCustomWidgetInterface *c = qobject_cast<QDesignerCustomWidgetInterface*>(object: o)) { |
543 | addCustomWidget(c, pluginPath, designerLanguage); |
544 | return; |
545 | } |
546 | if (const QDesignerCustomWidgetCollectionInterface *coll = qobject_cast<QDesignerCustomWidgetCollectionInterface*>(object: 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 | |
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(lst: 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 | |
572 | QDesignerPluginManager::~QDesignerPluginManager() |
573 | { |
574 | syncSettings(); |
575 | delete m_d; |
576 | } |
577 | |
578 | QDesignerFormEditorInterface *QDesignerPluginManager::core() const |
579 | { |
580 | return m_d->m_core; |
581 | } |
582 | |
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(filters: 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(str: fileName)) |
609 | result += fileName; |
610 | } |
611 | return result; |
612 | } |
613 | |
614 | void QDesignerPluginManager::setDisabledPlugins(const QStringList &disabled_plugins) |
615 | { |
616 | m_d->m_disabledPlugins = disabled_plugins; |
617 | updateRegisteredPlugins(); |
618 | } |
619 | |
620 | void QDesignerPluginManager::setPluginPaths(const QStringList &plugin_paths) |
621 | { |
622 | m_d->m_pluginPaths = plugin_paths; |
623 | updateRegisteredPlugins(); |
624 | } |
625 | |
626 | QStringList QDesignerPluginManager::disabledPlugins() const |
627 | { |
628 | return m_d->m_disabledPlugins; |
629 | } |
630 | |
631 | QStringList QDesignerPluginManager::failedPlugins() const |
632 | { |
633 | return m_d->m_failedPlugins.keys(); |
634 | } |
635 | |
636 | QString QDesignerPluginManager::failureReason(const QString &pluginName) const |
637 | { |
638 | return m_d->m_failedPlugins.value(akey: pluginName); |
639 | } |
640 | |
641 | QStringList QDesignerPluginManager::registeredPlugins() const |
642 | { |
643 | return m_d->m_registeredPlugins; |
644 | } |
645 | |
646 | QStringList QDesignerPluginManager::pluginPaths() const |
647 | { |
648 | return m_d->m_pluginPaths; |
649 | } |
650 | |
651 | QObject *QDesignerPluginManager::instance(const QString &plugin) const |
652 | { |
653 | if (m_d->m_disabledPlugins.contains(str: plugin)) |
654 | return nullptr; |
655 | |
656 | QPluginLoader loader(plugin); |
657 | return loader.instance(); |
658 | } |
659 | |
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(t&: m_d->m_pluginPaths)) |
666 | registerPath(path); |
667 | } |
668 | |
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(t&: 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 | |
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 | |
695 | void QDesignerPluginManager::registerPlugin(const QString &plugin) |
696 | { |
697 | if (debugPluginManager) |
698 | qDebug() << Q_FUNC_INFO << plugin; |
699 | if (m_d->m_disabledPlugins.contains(str: plugin)) |
700 | return; |
701 | if (m_d->m_registeredPlugins.contains(str: 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(akey: plugin); |
708 | if (fit != m_d->m_failedPlugins.end()) |
709 | m_d->m_failedPlugins.erase(it: fit); |
710 | return; |
711 | } |
712 | |
713 | const QString errorMessage = loader.errorString(); |
714 | m_d->m_failedPlugins.insert(akey: plugin, avalue: errorMessage); |
715 | } |
716 | |
717 | |
718 | |
719 | bool QDesignerPluginManager::syncSettings() |
720 | { |
721 | QSettings settings(qApp->organizationName(), QDesignerQSettings::settingsApplicationName()); |
722 | settings.beginGroup(QStringLiteral("PluginManager" )); |
723 | settings.setValue(QStringLiteral("DisabledPlugins" ), value: m_d->m_disabledPlugins); |
724 | settings.endGroup(); |
725 | return settings.status() == QSettings::NoError; |
726 | } |
727 | |
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(core: 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, pluginPath: staticPluginPath, designerLanguage); |
745 | } |
746 | for (const QString &plugin : qAsConst(t&: m_d->m_registeredPlugins)) { |
747 | if (QObject *o = instance(plugin)) |
748 | m_d->addCustomWidgets(o, pluginPath: plugin, designerLanguage); |
749 | } |
750 | |
751 | m_d->m_initialized = true; |
752 | } |
753 | |
754 | QDesignerPluginManager::CustomWidgetList QDesignerPluginManager::registeredCustomWidgets() const |
755 | { |
756 | const_cast<QDesignerPluginManager*>(this)->ensureInitialized(); |
757 | return m_d->m_customWidgets; |
758 | } |
759 | |
760 | QDesignerCustomWidgetData QDesignerPluginManager::customWidgetData(QDesignerCustomWidgetInterface *w) const |
761 | { |
762 | const int index = m_d->m_customWidgets.indexOf(t: w); |
763 | if (index == -1) |
764 | return QDesignerCustomWidgetData(); |
765 | return m_d->m_customWidgetData.at(i: index); |
766 | } |
767 | |
768 | 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 | |
777 | 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(t: o); |
785 | } |
786 | |
787 | return lst; |
788 | } |
789 | |
790 | QT_END_NAMESPACE |
791 | |