Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
formbuilder.cpp
Go to the documentation of this file.
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"
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
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
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() == "QWidget"_L1 && !ui_widget->hasAttributeNative()
92 && parentWidget
93#if QT_CONFIG(mainwindow)
94 && !qobject_cast<QMainWindow *>(parentWidget)
95#endif
96#if QT_CONFIG(toolbox)
97 && !qobject_cast<QToolBox *>(parentWidget)
98#endif
99#if QT_CONFIG(stackedwidget)
100 && !qobject_cast<QStackedWidget *>(parentWidget)
101#endif
102#if QT_CONFIG(tabwidget)
103 && !qobject_cast<QTabWidget *>(parentWidget)
104#endif
105#if QT_CONFIG(scrollarea)
106 && !qobject_cast<QScrollArea *>(parentWidget)
107#endif
108#if QT_CONFIG(mdiarea)
109 && !qobject_cast<QMdiArea *>(parentWidget)
110#endif
111#if QT_CONFIG(dockwidget)
112 && !qobject_cast<QDockWidget *>(parentWidget)
113#endif
114 ) {
115 const QString parentClassName = QLatin1StringView(parentWidget->metaObject()->className());
116 if (!d->isCustomWidgetContainer(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("QFormBuilder", "An empty class name was passed on to %1 (object name: '%2').").arg(QString::fromUtf8(Q_FUNC_INFO), name);
131 return nullptr;
132 }
133
134 QWidget *w = nullptr;
135
136#if QT_CONFIG(tabwidget)
137 if (qobject_cast<QTabWidget*>(parentWidget))
138 parentWidget = nullptr;
139#endif
140#if QT_CONFIG(stackedwidget)
141 if (qobject_cast<QStackedWidget*>(parentWidget))
142 parentWidget = nullptr;
143#endif
144#if QT_CONFIG(toolbox)
145 if (qobject_cast<QToolBox*>(parentWidget))
146 parentWidget = nullptr;
147#endif
148
149 // ### special-casing for Line (QFrame) -- fix for 4.2
150 do {
151 if (widgetName == "Line"_L1) {
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(widgetName);
178 if (factory != nullptr)
179 w = factory->createWidget(parentWidget);
180 } while(false);
181
182 if (w == nullptr) { // Attempt to instantiate base class of promoted/custom widgets
183 const QString baseClassName = d->customWidgetBaseClass(widgetName);
184 if (!baseClassName.isEmpty()) {
185 qWarning() << QCoreApplication::translate("QFormBuilder", "QFormBuilder was unable to create a custom widget of the class '%1'; defaulting to base class '%2'.").arg(widgetName, baseClassName);
186 return createWidget(baseClassName, parentWidget, name);
187 }
188 }
189
190 if (w == nullptr) { // nothing to do
191 qWarning() << QCoreApplication::translate("QFormBuilder", "QFormBuilder was unable to create a widget of the class '%1'.").arg(widgetName);
192 return nullptr;
193 }
194
195 w->setObjectName(name);
196
197 if (qobject_cast<QDialog *>(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*>(parent);
211 QLayout *parentLayout = qobject_cast<QLayout*>(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("QFormBuilder", "The layout type `%1' is not supported.").arg(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*>(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*>(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(widget, c->elementSender());
291 QObject *receiver = objectByName(widget, c->elementReceiver());
292 if (!sender || !receiver)
293 continue;
294
295 QByteArray sig = c->elementSignal().toUtf8();
296 sig.prepend("2");
297 QByteArray sl = c->elementSlot().toUtf8();
298 sl.prepend("1");
299 QObject::connect(sender, sig, receiver, 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 int left = 0, top = 0, right = 0, bottom = 0;
322 QFormBuilderExtra::getLayoutMargins(ui_layout->elementProperty(),
323 &left, &top, &right, &bottom);
324 l->setContentsMargins(left, top, right, bottom);
325 d->setProcessingLayoutWidget(false);
326 }
327 return l;
328}
329
330/*!
331 \internal
332*/
333QLayoutItem *QFormBuilder::create(DomLayoutItem *ui_layoutItem, QLayout *layout, QWidget *parentWidget)
334{
335 return QAbstractFormBuilder::create(ui_layoutItem, layout, parentWidget);
336}
337
338/*!
339 \internal
340*/
341QAction *QFormBuilder::create(DomAction *ui_action, QObject *parent)
342{
343 return QAbstractFormBuilder::create(ui_action, parent);
344}
345
346/*!
347 \internal
348*/
349QActionGroup *QFormBuilder::create(DomActionGroup *ui_action_group, QObject *parent)
350{
351 return QAbstractFormBuilder::create(ui_action_group, parent);
352}
353
354/*!
355 Returns the list of paths the form builder searches for plugins.
356
357 \sa addPluginPath()
358*/
359QStringList QFormBuilder::pluginPaths() const
360{
361 return d->m_pluginPaths;
362}
363
364/*!
365 Clears the list of paths that the form builder uses to search for
366 custom widget plugins.
367
368 \sa pluginPaths()
369*/
370void QFormBuilder::clearPluginPaths()
371{
372 d->m_pluginPaths.clear();
373 updateCustomWidgets();
374}
375
376/*!
377 Adds a new plugin path specified by \a pluginPath to the list of
378 paths that will be searched by the form builder when loading a
379 custom widget plugin.
380
381 \sa setPluginPath(), clearPluginPaths()
382*/
383void QFormBuilder::addPluginPath(const QString &pluginPath)
384{
385 d->m_pluginPaths.append(pluginPath);
386 updateCustomWidgets();
387}
388
389/*!
390 Sets the list of plugin paths to the list specified by \a pluginPaths.
391
392 \sa addPluginPath()
393*/
394void QFormBuilder::setPluginPath(const QStringList &pluginPaths)
395{
396 d->m_pluginPaths = pluginPaths;
397 updateCustomWidgets();
398}
399
400static void insertPlugins(QObject *o, QMap<QString, QDesignerCustomWidgetInterface*> *customWidgets)
401{
402 // step 1) try with a normal plugin
403 if (QDesignerCustomWidgetInterface *iface = qobject_cast<QDesignerCustomWidgetInterface *>(o)) {
404 customWidgets->insert(iface->name(), iface);
405 return;
406 }
407 // step 2) try with a collection of plugins
408 if (QDesignerCustomWidgetCollectionInterface *c = qobject_cast<QDesignerCustomWidgetCollectionInterface *>(o)) {
409 const auto &collectionCustomWidgets = c->customWidgets();
410 for (QDesignerCustomWidgetInterface *iface : collectionCustomWidgets)
411 customWidgets->insert(iface->name(), iface);
412 }
413}
414
415/*!
416 \internal
417*/
418void QFormBuilder::updateCustomWidgets()
419{
420 d->m_customWidgets.clear();
421
422#if QT_CONFIG(library)
423 for (const QString &path : std::as_const(d->m_pluginPaths)) {
424 const QDir dir(path);
425 const QStringList candidates = dir.entryList(QDir::Files);
426
427 for (const QString &plugin : candidates) {
428 if (!QLibrary::isLibrary(plugin))
429 continue;
430
431 QPluginLoader loader(path + u'/' + plugin);
432 if (loader.load())
433 insertPlugins(loader.instance(), &d->m_customWidgets);
434 }
435 }
436#endif // QT_CONFIG(library)
437
438 // Check statically linked plugins
439 const QObjectList staticPlugins = QPluginLoader::staticInstances();
440 for (QObject *o : staticPlugins)
441 insertPlugins(o, &d->m_customWidgets);
442}
443
444/*!
445 \fn QList<QDesignerCustomWidgetInterface*> QFormBuilder::customWidgets() const
446
447 Returns a list of the available plugins.
448*/
449QList<QDesignerCustomWidgetInterface*> QFormBuilder::customWidgets() const
450{
451 return d->m_customWidgets.values();
452}
453
454/*!
455 \internal
456*/
457
458void QFormBuilder::applyProperties(QObject *o, const QList<DomProperty*> &properties)
459{
460
461 if (properties.isEmpty())
462 return;
463
464 for (DomProperty *p : properties) {
465 const QVariant v = toVariant(o->metaObject(), p);
466 if (!v.isValid()) // QTBUG-33130, do not fall for QVariant(QString()).isNull() == true.
467 continue;
468
469 const QString attributeName = p->attributeName();
470 const bool isWidget = o->isWidgetType();
471 if (isWidget && o->parent() == d->parentWidget() && attributeName == "geometry"_L1) {
472 // apply only the size part of a geometry for the root widget
473 static_cast<QWidget*>(o)->resize(qvariant_cast<QRect>(v).size());
474 } else if (d->applyPropertyInternally(o, attributeName, v)) {
475 } else if (isWidget && qstrcmp("QFrame", o->metaObject()->className()) == 0
476 && attributeName == "orientation"_L1) {
477 // ### special-casing for Line (QFrame) -- try to fix me
478 o->setProperty("frameShape", v); // v is of QFrame::Shape enum
479 } else {
480 o->setProperty(attributeName.toUtf8(), v);
481 }
482 }
483}
484
485#ifdef QFORMINTERNAL_NAMESPACE
486} // namespace QFormInternal
487#endif
488
489QT_END_NAMESPACE
friend class QWidget
Definition qpainter.h:421
static void insertPlugins(QObject *o, QMap< QString, QDesignerCustomWidgetInterface * > *customWidgets)
static QObject * objectByName(QWidget *topLevel, const QString &name)
Combined button and popup list for selecting options.