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 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | using namespace Qt::StringLiterals; |
18 | |
19 | #ifdef QFORMINTERNAL_NAMESPACE |
20 | namespace 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 | |
74 | QFormBuilder::QFormBuilder() = default; |
75 | |
76 | /*! |
77 | Destroys the form builder. |
78 | */ |
79 | QFormBuilder::~QFormBuilder() = default; |
80 | |
81 | /*! |
82 | \internal |
83 | */ |
84 | QWidget *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 | */ |
126 | QWidget *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 | */ |
206 | QLayout *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 | */ |
244 | bool QFormBuilder::addItem(DomLayoutItem *ui_item, QLayoutItem *item, QLayout *layout) |
245 | { |
246 | return QAbstractFormBuilder::addItem(ui_item, item, layout); |
247 | } |
248 | |
249 | /*! |
250 | \internal |
251 | */ |
252 | bool QFormBuilder::addItem(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) |
253 | { |
254 | return QAbstractFormBuilder::addItem(ui_widget, widget, parentWidget); |
255 | } |
256 | |
257 | /*! |
258 | \internal |
259 | */ |
260 | QWidget *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 | |
269 | static 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 | */ |
281 | void 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 | */ |
306 | QWidget *QFormBuilder::create(DomUI *ui, QWidget *parentWidget) |
307 | { |
308 | return QAbstractFormBuilder::create(ui, parentWidget); |
309 | } |
310 | |
311 | /*! |
312 | \internal |
313 | */ |
314 | QLayout *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 | */ |
347 | QLayoutItem *QFormBuilder::create(DomLayoutItem *ui_layoutItem, QLayout *layout, QWidget *parentWidget) |
348 | { |
349 | return QAbstractFormBuilder::create(ui_layoutItem, layout, parentWidget); |
350 | } |
351 | |
352 | /*! |
353 | \internal |
354 | */ |
355 | QAction *QFormBuilder::create(DomAction *ui_action, QObject *parent) |
356 | { |
357 | return QAbstractFormBuilder::create(ui_action, parent); |
358 | } |
359 | |
360 | /*! |
361 | \internal |
362 | */ |
363 | QActionGroup *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 | */ |
373 | QStringList 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 | */ |
384 | void 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 | */ |
397 | void 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 | */ |
408 | void QFormBuilder::setPluginPath(const QStringList &pluginPaths) |
409 | { |
410 | d->m_pluginPaths = pluginPaths; |
411 | updateCustomWidgets(); |
412 | } |
413 | |
414 | static 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 | */ |
432 | void 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 | */ |
463 | QList<QDesignerCustomWidgetInterface*> QFormBuilder::customWidgets() const |
464 | { |
465 | return d->m_customWidgets.values(); |
466 | } |
467 | |
468 | /*! |
469 | \internal |
470 | */ |
471 | |
472 | void 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 | |
504 | QT_END_NAMESPACE |
505 | |