1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 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:BSD$ |
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 | ** BSD License Usage |
18 | ** Alternatively, you may use this file under the terms of the BSD license |
19 | ** as follows: |
20 | ** |
21 | ** "Redistribution and use in source and binary forms, with or without |
22 | ** modification, are permitted provided that the following conditions are |
23 | ** met: |
24 | ** * Redistributions of source code must retain the above copyright |
25 | ** notice, this list of conditions and the following disclaimer. |
26 | ** * Redistributions in binary form must reproduce the above copyright |
27 | ** notice, this list of conditions and the following disclaimer in |
28 | ** the documentation and/or other materials provided with the |
29 | ** distribution. |
30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
31 | ** contributors may be used to endorse or promote products derived |
32 | ** from this software without specific prior written permission. |
33 | ** |
34 | ** |
35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
46 | ** |
47 | ** $QT_END_LICENSE$ |
48 | ** |
49 | ****************************************************************************/ |
50 | |
51 | #include "formbuilder.h" |
52 | #include "formbuilderextra_p.h" |
53 | #include "ui4_p.h" |
54 | |
55 | #include <QtUiPlugin/customwidget.h> |
56 | #include <QtWidgets/QtWidgets> |
57 | |
58 | QT_BEGIN_NAMESPACE |
59 | |
60 | #ifdef QFORMINTERNAL_NAMESPACE |
61 | namespace QFormInternal { |
62 | #endif |
63 | |
64 | /*! |
65 | \class QFormBuilder |
66 | |
67 | \brief The QFormBuilder class is used to dynamically construct |
68 | user interfaces from UI files at run-time. |
69 | |
70 | \inmodule QtDesigner |
71 | |
72 | The QFormBuilder class provides a mechanism for dynamically |
73 | creating user interfaces at run-time, based on UI files |
74 | created with \QD. For example: |
75 | |
76 | \snippet lib/tools_designer_src_lib_uilib_formbuilder.cpp 0 |
77 | |
78 | By including the user interface in the example's resources (\c |
79 | myForm.qrc), we ensure that it will be present when the example is |
80 | run: |
81 | |
82 | \snippet lib/tools_designer_src_lib_uilib_formbuilder.cpp 1 |
83 | |
84 | QFormBuilder extends the QAbstractFormBuilder base class with a |
85 | number of functions that are used to support custom widget |
86 | plugins: |
87 | |
88 | \list |
89 | \li pluginPaths() returns the list of paths that the form builder |
90 | searches when loading custom widget plugins. |
91 | \li addPluginPath() allows additional paths to be registered with |
92 | the form builder. |
93 | \li setPluginPath() is used to replace the existing list of paths |
94 | with a list obtained from some other source. |
95 | \li clearPluginPaths() removes all paths registered with the form |
96 | builder. |
97 | \li customWidgets() returns a list of interfaces to plugins that |
98 | can be used to create new instances of registered custom widgets. |
99 | \endlist |
100 | |
101 | The QFormBuilder class is typically used by custom components and |
102 | applications that embed \QD. Standalone applications that need to |
103 | dynamically generate user interfaces at run-time use the |
104 | QUiLoader class, found in the QtUiTools module. |
105 | |
106 | \sa QAbstractFormBuilder, {Qt UI Tools} |
107 | */ |
108 | |
109 | /*! |
110 | \fn QFormBuilder::QFormBuilder() |
111 | |
112 | Constructs a new form builder. |
113 | */ |
114 | |
115 | QFormBuilder::QFormBuilder() = default; |
116 | |
117 | /*! |
118 | Destroys the form builder. |
119 | */ |
120 | QFormBuilder::~QFormBuilder() = default; |
121 | |
122 | /*! |
123 | \internal |
124 | */ |
125 | QWidget *QFormBuilder::create(DomWidget *ui_widget, QWidget *parentWidget) |
126 | { |
127 | if (!d->parentWidgetIsSet()) |
128 | d->setParentWidget(parentWidget); |
129 | // Is this a QLayoutWidget with a margin of 0: Not a known page-based |
130 | // container and no method for adding pages registered. |
131 | d->setProcessingLayoutWidget(false); |
132 | if (ui_widget->attributeClass() == QFormBuilderStrings::instance().qWidgetClass && !ui_widget->hasAttributeNative() |
133 | && parentWidget |
134 | #if QT_CONFIG(mainwindow) |
135 | && !qobject_cast<QMainWindow *>(object: parentWidget) |
136 | #endif |
137 | #if QT_CONFIG(toolbox) |
138 | && !qobject_cast<QToolBox *>(object: parentWidget) |
139 | #endif |
140 | #if QT_CONFIG(stackedwidget) |
141 | && !qobject_cast<QStackedWidget *>(object: parentWidget) |
142 | #endif |
143 | #if QT_CONFIG(tabwidget) |
144 | && !qobject_cast<QTabWidget *>(object: parentWidget) |
145 | #endif |
146 | #if QT_CONFIG(scrollarea) |
147 | && !qobject_cast<QScrollArea *>(object: parentWidget) |
148 | #endif |
149 | #if QT_CONFIG(mdiarea) |
150 | && !qobject_cast<QMdiArea *>(object: parentWidget) |
151 | #endif |
152 | #if QT_CONFIG(dockwidget) |
153 | && !qobject_cast<QDockWidget *>(object: parentWidget) |
154 | #endif |
155 | ) { |
156 | const QString parentClassName = QLatin1String(parentWidget->metaObject()->className()); |
157 | if (!d->isCustomWidgetContainer(className: parentClassName)) |
158 | d->setProcessingLayoutWidget(true); |
159 | } |
160 | return QAbstractFormBuilder::create(ui_widget, parentWidget); |
161 | } |
162 | |
163 | |
164 | /*! |
165 | \internal |
166 | */ |
167 | QWidget *QFormBuilder::createWidget(const QString &widgetName, QWidget *parentWidget, const QString &name) |
168 | { |
169 | if (widgetName.isEmpty()) { |
170 | //: Empty class name passed to widget factory method |
171 | qWarning() << QCoreApplication::translate(context: "QFormBuilder" , key: "An empty class name was passed on to %1 (object name: '%2')." ).arg(args: QString::fromUtf8(Q_FUNC_INFO), args: name); |
172 | return nullptr; |
173 | } |
174 | |
175 | QWidget *w = nullptr; |
176 | |
177 | #if QT_CONFIG(tabwidget) |
178 | if (qobject_cast<QTabWidget*>(object: parentWidget)) |
179 | parentWidget = nullptr; |
180 | #endif |
181 | #if QT_CONFIG(stackedwidget) |
182 | if (qobject_cast<QStackedWidget*>(object: parentWidget)) |
183 | parentWidget = nullptr; |
184 | #endif |
185 | #if QT_CONFIG(toolbox) |
186 | if (qobject_cast<QToolBox*>(object: parentWidget)) |
187 | parentWidget = nullptr; |
188 | #endif |
189 | |
190 | // ### special-casing for Line (QFrame) -- fix for 4.2 |
191 | do { |
192 | if (widgetName == QFormBuilderStrings::instance().lineClass) { |
193 | w = new QFrame(parentWidget); |
194 | static_cast<QFrame*>(w)->setFrameStyle(QFrame::HLine | QFrame::Sunken); |
195 | break; |
196 | } |
197 | const QByteArray widgetNameBA = widgetName.toUtf8(); |
198 | const char *widgetNameC = widgetNameBA.constData(); |
199 | if (w) { // symmetry for macro |
200 | } |
201 | |
202 | #define DECLARE_LAYOUT(L, C) |
203 | #define DECLARE_COMPAT_WIDGET(W, C) |
204 | #define DECLARE_WIDGET(W, C) else if (!qstrcmp(widgetNameC, #W)) { Q_ASSERT(w == 0); w = new W(parentWidget); } |
205 | #define DECLARE_WIDGET_1(W, C) else if (!qstrcmp(widgetNameC, #W)) { Q_ASSERT(w == 0); w = new W(0, parentWidget); } |
206 | |
207 | #include "widgets.table" |
208 | |
209 | #undef DECLARE_COMPAT_WIDGET |
210 | #undef DECLARE_LAYOUT |
211 | #undef DECLARE_WIDGET |
212 | #undef DECLARE_WIDGET_1 |
213 | |
214 | if (w) |
215 | break; |
216 | |
217 | // try with a registered custom widget |
218 | QDesignerCustomWidgetInterface *factory = d->m_customWidgets.value(akey: widgetName); |
219 | if (factory != nullptr) |
220 | w = factory->createWidget(parent: parentWidget); |
221 | } while(false); |
222 | |
223 | if (w == nullptr) { // Attempt to instantiate base class of promoted/custom widgets |
224 | const QString baseClassName = d->customWidgetBaseClass(className: widgetName); |
225 | if (!baseClassName.isEmpty()) { |
226 | qWarning() << QCoreApplication::translate(context: "QFormBuilder" , key: "QFormBuilder was unable to create a custom widget of the class '%1'; defaulting to base class '%2'." ).arg(a1: widgetName, a2: baseClassName); |
227 | return createWidget(widgetName: baseClassName, parentWidget, name); |
228 | } |
229 | } |
230 | |
231 | if (w == nullptr) { // nothing to do |
232 | qWarning() << QCoreApplication::translate(context: "QFormBuilder" , key: "QFormBuilder was unable to create a widget of the class '%1'." ).arg(a: widgetName); |
233 | return nullptr; |
234 | } |
235 | |
236 | w->setObjectName(name); |
237 | |
238 | if (qobject_cast<QDialog *>(object: w)) |
239 | w->setParent(parentWidget); |
240 | |
241 | return w; |
242 | } |
243 | |
244 | /*! |
245 | \internal |
246 | */ |
247 | QLayout *QFormBuilder::createLayout(const QString &layoutName, QObject *parent, const QString &name) |
248 | { |
249 | QLayout *l = nullptr; |
250 | |
251 | QWidget *parentWidget = qobject_cast<QWidget*>(o: parent); |
252 | QLayout *parentLayout = qobject_cast<QLayout*>(object: parent); |
253 | |
254 | Q_ASSERT(parentWidget || parentLayout); |
255 | |
256 | #define DECLARE_WIDGET(W, C) |
257 | #define DECLARE_COMPAT_WIDGET(W, C) |
258 | |
259 | #define DECLARE_LAYOUT(L, C) \ |
260 | if (layoutName == QLatin1String(#L)) { \ |
261 | Q_ASSERT(l == 0); \ |
262 | l = parentLayout \ |
263 | ? new L() \ |
264 | : new L(parentWidget); \ |
265 | } |
266 | |
267 | #include "widgets.table" |
268 | |
269 | #undef DECLARE_LAYOUT |
270 | #undef DECLARE_COMPAT_WIDGET |
271 | #undef DECLARE_WIDGET |
272 | |
273 | if (l) { |
274 | l->setObjectName(name); |
275 | } else { |
276 | qWarning() << QCoreApplication::translate(context: "QFormBuilder" , key: "The layout type `%1' is not supported." ).arg(a: layoutName); |
277 | } |
278 | |
279 | return l; |
280 | } |
281 | |
282 | /*! |
283 | \internal |
284 | */ |
285 | bool QFormBuilder::addItem(DomLayoutItem *ui_item, QLayoutItem *item, QLayout *layout) |
286 | { |
287 | return QAbstractFormBuilder::addItem(ui_item, item, layout); |
288 | } |
289 | |
290 | /*! |
291 | \internal |
292 | */ |
293 | bool QFormBuilder::addItem(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) |
294 | { |
295 | return QAbstractFormBuilder::addItem(ui_widget, widget, parentWidget); |
296 | } |
297 | |
298 | /*! |
299 | \internal |
300 | */ |
301 | QWidget *QFormBuilder::widgetByName(QWidget *topLevel, const QString &name) |
302 | { |
303 | Q_ASSERT(topLevel); |
304 | if (topLevel->objectName() == name) |
305 | return topLevel; |
306 | |
307 | return topLevel->findChild<QWidget*>(aName: name); |
308 | } |
309 | |
310 | static QObject *objectByName(QWidget *topLevel, const QString &name) |
311 | { |
312 | Q_ASSERT(topLevel); |
313 | if (topLevel->objectName() == name) |
314 | return topLevel; |
315 | |
316 | return topLevel->findChild<QObject*>(aName: name); |
317 | } |
318 | |
319 | /*! |
320 | \internal |
321 | */ |
322 | void QFormBuilder::createConnections(DomConnections *ui_connections, QWidget *widget) |
323 | { |
324 | Q_ASSERT(widget != nullptr); |
325 | |
326 | if (ui_connections == nullptr) |
327 | return; |
328 | |
329 | const auto &connections = ui_connections->elementConnection(); |
330 | for (const DomConnection *c : connections) { |
331 | QObject *sender = objectByName(topLevel: widget, name: c->elementSender()); |
332 | QObject *receiver = objectByName(topLevel: widget, name: c->elementReceiver()); |
333 | if (!sender || !receiver) |
334 | continue; |
335 | |
336 | QByteArray sig = c->elementSignal().toUtf8(); |
337 | sig.prepend(s: "2" ); |
338 | QByteArray sl = c->elementSlot().toUtf8(); |
339 | sl.prepend(s: "1" ); |
340 | QObject::connect(sender, signal: sig, receiver, member: sl); |
341 | } |
342 | } |
343 | |
344 | /*! |
345 | \internal |
346 | */ |
347 | QWidget *QFormBuilder::create(DomUI *ui, QWidget *parentWidget) |
348 | { |
349 | return QAbstractFormBuilder::create(ui, parentWidget); |
350 | } |
351 | |
352 | /*! |
353 | \internal |
354 | */ |
355 | QLayout *QFormBuilder::create(DomLayout *ui_layout, QLayout *layout, QWidget *parentWidget) |
356 | { |
357 | // Is this a temporary layout widget used to represent QLayout hierarchies in Designer? |
358 | // Set its margins to 0. |
359 | bool layoutWidget = d->processingLayoutWidget(); |
360 | QLayout *l = QAbstractFormBuilder::create(ui_layout, layout, parentWidget); |
361 | if (layoutWidget) { |
362 | const QFormBuilderStrings &strings = QFormBuilderStrings::instance(); |
363 | int left, top, right, bottom; |
364 | left = top = right = bottom = 0; |
365 | const DomPropertyHash properties = propertyMap(properties: ui_layout->elementProperty()); |
366 | |
367 | if (DomProperty *prop = properties.value(akey: strings.leftMarginProperty)) |
368 | left = prop->elementNumber(); |
369 | |
370 | if (DomProperty *prop = properties.value(akey: strings.topMarginProperty)) |
371 | top = prop->elementNumber(); |
372 | |
373 | if (DomProperty *prop = properties.value(akey: strings.rightMarginProperty)) |
374 | right = prop->elementNumber(); |
375 | |
376 | if (DomProperty *prop = properties.value(akey: strings.bottomMarginProperty)) |
377 | bottom = prop->elementNumber(); |
378 | |
379 | l->setContentsMargins(left, top, right, bottom); |
380 | d->setProcessingLayoutWidget(false); |
381 | } |
382 | return l; |
383 | } |
384 | |
385 | /*! |
386 | \internal |
387 | */ |
388 | QLayoutItem *QFormBuilder::create(DomLayoutItem *ui_layoutItem, QLayout *layout, QWidget *parentWidget) |
389 | { |
390 | return QAbstractFormBuilder::create(ui_layoutItem, layout, parentWidget); |
391 | } |
392 | |
393 | /*! |
394 | \internal |
395 | */ |
396 | QAction *QFormBuilder::create(DomAction *ui_action, QObject *parent) |
397 | { |
398 | return QAbstractFormBuilder::create(ui_action, parent); |
399 | } |
400 | |
401 | /*! |
402 | \internal |
403 | */ |
404 | QActionGroup *QFormBuilder::create(DomActionGroup *ui_action_group, QObject *parent) |
405 | { |
406 | return QAbstractFormBuilder::create(ui_action_group, parent); |
407 | } |
408 | |
409 | /*! |
410 | Returns the list of paths the form builder searches for plugins. |
411 | |
412 | \sa addPluginPath() |
413 | */ |
414 | QStringList QFormBuilder::pluginPaths() const |
415 | { |
416 | return d->m_pluginPaths; |
417 | } |
418 | |
419 | /*! |
420 | Clears the list of paths that the form builder uses to search for |
421 | custom widget plugins. |
422 | |
423 | \sa pluginPaths() |
424 | */ |
425 | void QFormBuilder::clearPluginPaths() |
426 | { |
427 | d->m_pluginPaths.clear(); |
428 | updateCustomWidgets(); |
429 | } |
430 | |
431 | /*! |
432 | Adds a new plugin path specified by \a pluginPath to the list of |
433 | paths that will be searched by the form builder when loading a |
434 | custom widget plugin. |
435 | |
436 | \sa setPluginPath(), clearPluginPaths() |
437 | */ |
438 | void QFormBuilder::addPluginPath(const QString &pluginPath) |
439 | { |
440 | d->m_pluginPaths.append(t: pluginPath); |
441 | updateCustomWidgets(); |
442 | } |
443 | |
444 | /*! |
445 | Sets the list of plugin paths to the list specified by \a pluginPaths. |
446 | |
447 | \sa addPluginPath() |
448 | */ |
449 | void QFormBuilder::setPluginPath(const QStringList &pluginPaths) |
450 | { |
451 | d->m_pluginPaths = pluginPaths; |
452 | updateCustomWidgets(); |
453 | } |
454 | |
455 | static void insertPlugins(QObject *o, QMap<QString, QDesignerCustomWidgetInterface*> *customWidgets) |
456 | { |
457 | // step 1) try with a normal plugin |
458 | if (QDesignerCustomWidgetInterface *iface = qobject_cast<QDesignerCustomWidgetInterface *>(object: o)) { |
459 | customWidgets->insert(akey: iface->name(), avalue: iface); |
460 | return; |
461 | } |
462 | // step 2) try with a collection of plugins |
463 | if (QDesignerCustomWidgetCollectionInterface *c = qobject_cast<QDesignerCustomWidgetCollectionInterface *>(object: o)) { |
464 | const auto &collectionCustomWidgets = c->customWidgets(); |
465 | for (QDesignerCustomWidgetInterface *iface : collectionCustomWidgets) |
466 | customWidgets->insert(akey: iface->name(), avalue: iface); |
467 | } |
468 | } |
469 | |
470 | /*! |
471 | \internal |
472 | */ |
473 | void QFormBuilder::updateCustomWidgets() |
474 | { |
475 | d->m_customWidgets.clear(); |
476 | |
477 | #if QT_CONFIG(library) |
478 | for (const QString &path : qAsConst(t&: d->m_pluginPaths)) { |
479 | const QDir dir(path); |
480 | const QStringList candidates = dir.entryList(filters: QDir::Files); |
481 | |
482 | for (const QString &plugin : candidates) { |
483 | if (!QLibrary::isLibrary(fileName: plugin)) |
484 | continue; |
485 | |
486 | QString loaderPath = path; |
487 | loaderPath += QLatin1Char('/'); |
488 | loaderPath += plugin; |
489 | |
490 | QPluginLoader loader(loaderPath); |
491 | if (loader.load()) |
492 | insertPlugins(o: loader.instance(), customWidgets: &d->m_customWidgets); |
493 | } |
494 | } |
495 | #endif // QT_CONFIG(library) |
496 | |
497 | // Check statically linked plugins |
498 | const QObjectList staticPlugins = QPluginLoader::staticInstances(); |
499 | for (QObject *o : staticPlugins) |
500 | insertPlugins(o, customWidgets: &d->m_customWidgets); |
501 | } |
502 | |
503 | /*! |
504 | \fn QList<QDesignerCustomWidgetInterface*> QFormBuilder::customWidgets() const |
505 | |
506 | Returns a list of the available plugins. |
507 | */ |
508 | QList<QDesignerCustomWidgetInterface*> QFormBuilder::customWidgets() const |
509 | { |
510 | return d->m_customWidgets.values(); |
511 | } |
512 | |
513 | /*! |
514 | \internal |
515 | */ |
516 | |
517 | void QFormBuilder::applyProperties(QObject *o, const QList<DomProperty*> &properties) |
518 | { |
519 | |
520 | if (properties.isEmpty()) |
521 | return; |
522 | |
523 | const QFormBuilderStrings &strings = QFormBuilderStrings::instance(); |
524 | |
525 | for (DomProperty *p : properties) { |
526 | const QVariant v = toVariant(meta: o->metaObject(), property: p); |
527 | if (!v.isValid()) // QTBUG-33130, do not fall for QVariant(QString()).isNull() == true. |
528 | continue; |
529 | |
530 | const QString attributeName = p->attributeName(); |
531 | const bool isWidget = o->isWidgetType(); |
532 | if (isWidget && o->parent() == d->parentWidget() && attributeName == strings.geometryProperty) { |
533 | // apply only the size part of a geometry for the root widget |
534 | static_cast<QWidget*>(o)->resize(qvariant_cast<QRect>(v).size()); |
535 | } else if (d->applyPropertyInternally(o, propertyName: attributeName, value: v)) { |
536 | } else if (isWidget && !qstrcmp(str1: "QFrame" , str2: o->metaObject()->className ()) && attributeName == strings.orientationProperty) { |
537 | // ### special-casing for Line (QFrame) -- try to fix me |
538 | o->setProperty(name: "frameShape" , value: v); // v is of QFrame::Shape enum |
539 | } else { |
540 | o->setProperty(name: attributeName.toUtf8(), value: v); |
541 | } |
542 | } |
543 | } |
544 | |
545 | #ifdef QFORMINTERNAL_NAMESPACE |
546 | } // namespace QFormInternal |
547 | #endif |
548 | |
549 | QT_END_NAMESPACE |
550 | |