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 */
create(DomWidget * ui_widget,QWidget * parentWidget)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 *>(parentWidget)
136 #endif
137 #if QT_CONFIG(toolbox)
138             && !qobject_cast<QToolBox *>(parentWidget)
139 #endif
140 #if QT_CONFIG(stackedwidget)
141             && !qobject_cast<QStackedWidget *>(parentWidget)
142 #endif
143 #if QT_CONFIG(tabwidget)
144             && !qobject_cast<QTabWidget *>(parentWidget)
145 #endif
146 #if QT_CONFIG(scrollarea)
147             && !qobject_cast<QScrollArea *>(parentWidget)
148 #endif
149 #if QT_CONFIG(mdiarea)
150             && !qobject_cast<QMdiArea *>(parentWidget)
151 #endif
152 #if QT_CONFIG(dockwidget)
153             && !qobject_cast<QDockWidget *>(parentWidget)
154 #endif
155         ) {
156         const QString parentClassName = QLatin1String(parentWidget->metaObject()->className());
157         if (!d->isCustomWidgetContainer(parentClassName))
158             d->setProcessingLayoutWidget(true);
159     }
160     return QAbstractFormBuilder::create(ui_widget, parentWidget);
161 }
162 
163 
164 /*!
165     \internal
166 */
createWidget(const QString & widgetName,QWidget * parentWidget,const QString & name)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("QFormBuilder", "An empty class name was passed on to %1 (object name: '%2').").arg(QString::fromUtf8(Q_FUNC_INFO), name);
172         return nullptr;
173     }
174 
175     QWidget *w = nullptr;
176 
177 #if QT_CONFIG(tabwidget)
178     if (qobject_cast<QTabWidget*>(parentWidget))
179         parentWidget = nullptr;
180 #endif
181 #if QT_CONFIG(stackedwidget)
182     if (qobject_cast<QStackedWidget*>(parentWidget))
183         parentWidget = nullptr;
184 #endif
185 #if QT_CONFIG(toolbox)
186     if (qobject_cast<QToolBox*>(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(widgetName);
219         if (factory != nullptr)
220             w = factory->createWidget(parentWidget);
221     } while(false);
222 
223     if (w == nullptr) { // Attempt to instantiate base class of promoted/custom widgets
224         const QString baseClassName = d->customWidgetBaseClass(widgetName);
225         if (!baseClassName.isEmpty()) {
226             qWarning() << QCoreApplication::translate("QFormBuilder", "QFormBuilder was unable to create a custom widget of the class '%1'; defaulting to base class '%2'.").arg(widgetName, baseClassName);
227             return createWidget(baseClassName, parentWidget, name);
228         }
229     }
230 
231     if (w == nullptr) { // nothing to do
232         qWarning() << QCoreApplication::translate("QFormBuilder", "QFormBuilder was unable to create a widget of the class '%1'.").arg(widgetName);
233         return nullptr;
234     }
235 
236     w->setObjectName(name);
237 
238     if (qobject_cast<QDialog *>(w))
239         w->setParent(parentWidget);
240 
241     return w;
242 }
243 
244 /*!
245     \internal
246 */
createLayout(const QString & layoutName,QObject * parent,const QString & name)247 QLayout *QFormBuilder::createLayout(const QString &layoutName, QObject *parent, const QString &name)
248 {
249     QLayout *l = nullptr;
250 
251     QWidget *parentWidget = qobject_cast<QWidget*>(parent);
252     QLayout *parentLayout = qobject_cast<QLayout*>(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("QFormBuilder", "The layout type `%1' is not supported.").arg(layoutName);
277     }
278 
279     return l;
280 }
281 
282 /*!
283     \internal
284 */
addItem(DomLayoutItem * ui_item,QLayoutItem * item,QLayout * layout)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 */
addItem(DomWidget * ui_widget,QWidget * widget,QWidget * parentWidget)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 */
widgetByName(QWidget * topLevel,const QString & name)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*>(name);
308 }
309 
objectByName(QWidget * topLevel,const QString & name)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*>(name);
317 }
318 
319 /*!
320     \internal
321 */
createConnections(DomConnections * ui_connections,QWidget * widget)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(widget, c->elementSender());
332         QObject *receiver = objectByName(widget, c->elementReceiver());
333         if (!sender || !receiver)
334             continue;
335 
336         QByteArray sig = c->elementSignal().toUtf8();
337         sig.prepend("2");
338         QByteArray sl = c->elementSlot().toUtf8();
339         sl.prepend("1");
340         QObject::connect(sender, sig, receiver, sl);
341     }
342 }
343 
344 /*!
345     \internal
346 */
create(DomUI * ui,QWidget * parentWidget)347 QWidget *QFormBuilder::create(DomUI *ui, QWidget *parentWidget)
348 {
349     return QAbstractFormBuilder::create(ui, parentWidget);
350 }
351 
352 /*!
353     \internal
354 */
create(DomLayout * ui_layout,QLayout * layout,QWidget * parentWidget)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(ui_layout->elementProperty());
366 
367         if (DomProperty *prop = properties.value(strings.leftMarginProperty))
368             left = prop->elementNumber();
369 
370         if (DomProperty *prop = properties.value(strings.topMarginProperty))
371             top = prop->elementNumber();
372 
373         if (DomProperty *prop = properties.value(strings.rightMarginProperty))
374             right = prop->elementNumber();
375 
376         if (DomProperty *prop = properties.value(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 */
create(DomLayoutItem * ui_layoutItem,QLayout * layout,QWidget * parentWidget)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 */
create(DomAction * ui_action,QObject * parent)396 QAction *QFormBuilder::create(DomAction *ui_action, QObject *parent)
397 {
398     return QAbstractFormBuilder::create(ui_action, parent);
399 }
400 
401 /*!
402     \internal
403 */
create(DomActionGroup * ui_action_group,QObject * parent)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 */
pluginPaths() const414 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 */
clearPluginPaths()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 */
addPluginPath(const QString & pluginPath)438 void QFormBuilder::addPluginPath(const QString &pluginPath)
439 {
440     d->m_pluginPaths.append(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 */
setPluginPath(const QStringList & pluginPaths)449 void QFormBuilder::setPluginPath(const QStringList &pluginPaths)
450 {
451     d->m_pluginPaths = pluginPaths;
452     updateCustomWidgets();
453 }
454 
insertPlugins(QObject * o,QMap<QString,QDesignerCustomWidgetInterface * > * customWidgets)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 *>(o)) {
459         customWidgets->insert(iface->name(), iface);
460         return;
461     }
462     // step 2) try with a collection of plugins
463     if (QDesignerCustomWidgetCollectionInterface *c = qobject_cast<QDesignerCustomWidgetCollectionInterface *>(o)) {
464         const auto &collectionCustomWidgets = c->customWidgets();
465         for (QDesignerCustomWidgetInterface *iface : collectionCustomWidgets)
466             customWidgets->insert(iface->name(), iface);
467     }
468 }
469 
470 /*!
471     \internal
472 */
updateCustomWidgets()473 void QFormBuilder::updateCustomWidgets()
474 {
475     d->m_customWidgets.clear();
476 
477 #if QT_CONFIG(library)
478     for (const QString &path : qAsConst(d->m_pluginPaths)) {
479         const QDir dir(path);
480         const QStringList candidates = dir.entryList(QDir::Files);
481 
482         for (const QString &plugin : candidates) {
483             if (!QLibrary::isLibrary(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(loader.instance(), &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, &d->m_customWidgets);
501 }
502 
503 /*!
504     \fn QList<QDesignerCustomWidgetInterface*> QFormBuilder::customWidgets() const
505 
506     Returns a list of the available plugins.
507 */
customWidgets() const508 QList<QDesignerCustomWidgetInterface*> QFormBuilder::customWidgets() const
509 {
510     return d->m_customWidgets.values();
511 }
512 
513 /*!
514     \internal
515 */
516 
applyProperties(QObject * o,const QList<DomProperty * > & properties)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(o->metaObject(), 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, attributeName, v)) {
536         } else if (isWidget && !qstrcmp("QFrame", o->metaObject()->className ()) && attributeName == strings.orientationProperty) {
537             // ### special-casing for Line (QFrame) -- try to fix me
538             o->setProperty("frameShape", v); // v is of QFrame::Shape enum
539         } else {
540             o->setProperty(attributeName.toUtf8(), v);
541         }
542     }
543 }
544 
545 #ifdef QFORMINTERNAL_NAMESPACE
546 } // namespace QFormInternal
547 #endif
548 
549 QT_END_NAMESPACE
550