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

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