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
58QT_BEGIN_NAMESPACE
59
60#ifdef QFORMINTERNAL_NAMESPACE
61namespace 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
115QFormBuilder::QFormBuilder() = default;
116
117/*!
118 Destroys the form builder.
119*/
120QFormBuilder::~QFormBuilder() = default;
121
122/*!
123 \internal
124*/
125QWidget *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*/
167QWidget *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*/
247QLayout *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*/
285bool QFormBuilder::addItem(DomLayoutItem *ui_item, QLayoutItem *item, QLayout *layout)
286{
287 return QAbstractFormBuilder::addItem(ui_item, item, layout);
288}
289
290/*!
291 \internal
292*/
293bool QFormBuilder::addItem(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget)
294{
295 return QAbstractFormBuilder::addItem(ui_widget, widget, parentWidget);
296}
297
298/*!
299 \internal
300*/
301QWidget *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
310static 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*/
322void 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*/
347QWidget *QFormBuilder::create(DomUI *ui, QWidget *parentWidget)
348{
349 return QAbstractFormBuilder::create(ui, parentWidget);
350}
351
352/*!
353 \internal
354*/
355QLayout *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*/
388QLayoutItem *QFormBuilder::create(DomLayoutItem *ui_layoutItem, QLayout *layout, QWidget *parentWidget)
389{
390 return QAbstractFormBuilder::create(ui_layoutItem, layout, parentWidget);
391}
392
393/*!
394 \internal
395*/
396QAction *QFormBuilder::create(DomAction *ui_action, QObject *parent)
397{
398 return QAbstractFormBuilder::create(ui_action, parent);
399}
400
401/*!
402 \internal
403*/
404QActionGroup *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*/
414QStringList 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*/
425void 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*/
438void 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*/
449void QFormBuilder::setPluginPath(const QStringList &pluginPaths)
450{
451 d->m_pluginPaths = pluginPaths;
452 updateCustomWidgets();
453}
454
455static 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*/
473void 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*/
508QList<QDesignerCustomWidgetInterface*> QFormBuilder::customWidgets() const
509{
510 return d->m_customWidgets.values();
511}
512
513/*!
514 \internal
515*/
516
517void 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
549QT_END_NAMESPACE
550

source code of qttools/src/designer/src/lib/uilib/formbuilder.cpp