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