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
widgetdatabase.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
11#include <QtDesigner/private/ui4_p.h>
12
13#include <QtDesigner/propertysheet.h>
14#include <QtDesigner/qextensionmanager.h>
15#include <QtDesigner/abstractformeditor.h>
16
17#include <QtUiPlugin/customwidget.h>
18#include <QtWidgets/QtWidgets>
19#ifdef QT_OPENGLWIDGETS_LIB
20#include <QtOpenGLWidgets/qopenglwidget.h>
21#endif
22
23#include <QtCore/qxmlstream.h>
24
25#include <QtCore/qcoreapplication.h>
26#include <QtCore/qscopedpointer.h>
27#include <QtCore/qdebug.h>
28#include <QtCore/qmetaobject.h>
29#include <QtCore/qset.h>
30#include <QtCore/qstring.h>
31#include <QtCore/qtextstream.h>
32#include <QtCore/qcoreapplication.h>
33
35
36using namespace Qt::StringLiterals;
37
38namespace {
39 enum { debugWidgetDataBase = 0 };
40}
41
42namespace qdesigner_internal {
43
44using namespace Qt::StringLiterals;
45
46// ----------------------------------------------------------
56
58{
59 return m_name;
60}
61
63{
64 m_name = name;
65}
66
68{
69 return m_group;
70}
71
76
78{
79 return m_toolTip;
80}
81
86
88{
89 return m_whatsThis;
90}
91
96
101
106
108{
109 return m_icon;
110}
111
113{
114 m_icon = icon;
115}
116
118{
119 return m_compat;
120}
121
123{
124 m_compat = b;
125}
126
128{
129 return m_container;
130}
131
133{
134 m_container = b;
135}
136
138{
139 return m_custom;
140}
141
143{
144 m_custom = b;
145}
146
148{
149 return m_pluginPath;
150}
151
156
158{
159 return m_promoted;
160}
161
163{
164 m_promoted = b;
165}
166
168{
169 return m_extends;
170}
171
173{
174 m_extends = s;
175}
176
181
186
191
196
201
206
211
216
235
240
245
246static void addWidgetItem(WidgetDataBase *wdb, const char *name, const QMetaObject &mo,
247 const char *comment)
248{
250 if (auto *base = mo.superClass())
252 if (comment[0])
254 wdb->append(item);
255}
256
257// ----------------------------------------------------------
260 m_core(core)
261{
262#define DECLARE_LAYOUT(L, C)
263#define DECLARE_COMPAT_WIDGET(W, C) DECLARE_WIDGET(W, C)
264#define DECLARE_WIDGET(W, C) addWidgetItem(this, #W, W::staticMetaObject, C);
265
266#include <widgets.table>
267
268#undef DECLARE_COMPAT_WIDGET
269#undef DECLARE_LAYOUT
270#undef DECLARE_WIDGET
271#undef DECLARE_WIDGET_1
272
274 QCoreApplication::translate("WidgetDataBase",
275 "Abstract base class that cannot be instantiated. For promotion/custom widget usage only.");
276
277#if QT_CONFIG(abstractbutton)
278 auto *abItem = new WidgetDataBaseItem(u"QAbstractButton"_s);
280 abItem->setBaseClassName(u"QWidget"_s);
281 append(abItem);
282#endif // QT_CONFIG(abstractbutton)
283
284#if QT_CONFIG(itemviews)
285 auto *aivItem = new WidgetDataBaseItem(u"QAbstractItemView"_s);
286 aivItem->setBaseClassName(u"QAbstractScrollArea"_s);
289#endif // QT_CONFIG(itemviews)
290
291 append(new WidgetDataBaseItem(u"Line"_s));
292 append(new WidgetDataBaseItem(u"Spacer"_s));
293 append(new WidgetDataBaseItem(u"QSplitter"_s));
294 append(new WidgetDataBaseItem(u"QLayoutWidget"_s));
295 // QDesignerWidget is used as central widget and as container for tab widgets, etc.
296 WidgetDataBaseItem *designerWidgetItem = new WidgetDataBaseItem(u"QDesignerWidget"_s);
299 append(new WidgetDataBaseItem(u"QDesignerDialog"_s));
300 append(new WidgetDataBaseItem(u"QDesignerMenu"_s));
301 append(new WidgetDataBaseItem(u"QDesignerMenuBar"_s));
302 append(new WidgetDataBaseItem(u"QDesignerDockWidget"_s));
303 append(new WidgetDataBaseItem(u"QAction"_s));
304 append(new WidgetDataBaseItem(u"QButtonGroup"_s));
305
306 // ### remove me
307 // ### check the casts
308
309#if 0 // ### enable me after 4.1
310 item(indexOfClassName(u"QToolBar"_s))->setContainer(true);
311#endif
312
313 item(indexOfClassName(u"QTabWidget"_s))->setContainer(true);
314 item(indexOfClassName(u"QGroupBox"_s))->setContainer(true);
315 item(indexOfClassName(u"QScrollArea"_s))->setContainer(true);
316 item(indexOfClassName(u"QStackedWidget"_s))->setContainer(true);
317 item(indexOfClassName(u"QToolBox"_s))->setContainer(true);
318 item(indexOfClassName(u"QFrame"_s))->setContainer(true);
319 item(indexOfClassName(u"QLayoutWidget"_s))->setContainer(true);
320 item(indexOfClassName(u"QDesignerWidget"_s))->setContainer(true);
321 item(indexOfClassName(u"QDesignerDialog"_s))->setContainer(true);
322 item(indexOfClassName(u"QSplitter"_s))->setContainer(true);
323 item(indexOfClassName(u"QMainWindow"_s))->setContainer(true);
324 item(indexOfClassName(u"QDockWidget"_s))->setContainer(true);
325 item(indexOfClassName(u"QDesignerDockWidget"_s))->setContainer(true);
326 item(indexOfClassName(u"QMdiArea"_s))->setContainer(true);
327 item(indexOfClassName(u"QWizard"_s))->setContainer(true);
328 item(indexOfClassName(u"QWizardPage"_s))->setContainer(true);
329
330 item(indexOfClassName(u"QWidget"_s))->setContainer(true);
331 item(indexOfClassName(u"QDialog"_s))->setContainer(true);
332}
333
334WidgetDataBase::~WidgetDataBase() = default;
335
340
356
358 const QDesignerCustomWidgetData &data)
359{
360 WidgetDataBaseItem *item = new WidgetDataBaseItem(c->name(), c->group());
361 item->setContainer(c->isContainer());
362 item->setCustom(true);
363 item->setIcon(c->icon());
364 item->setIncludeFile(c->includeFile());
365 item->setToolTip(c->toolTip());
366 item->setWhatsThis(c->whatsThis());
367 item->setPluginPath(data.pluginPath());
368 item->setAddPageMethod(data.xmlAddPageMethod());
369 item->setExtends(data.xmlExtends());
370 return item;
371}
372
374{
375 // 1) create a map of existing custom classes
378 for (qsizetype i = 0, count = m_items.size(); i < count; ++i) {
380 if (item->isCustom() && !item->isPromoted())
382 else
384 }
385 // 2) create a list plugins
391
392 // 3) replace custom classes or add new ones, remove them from existingCustomClasses,
393 // leaving behind deleted items
394 unsigned replacedPlugins = 0;
395 unsigned addedPlugins = 0;
396 unsigned removedPlugins = 0;
397 if (!pluginList.isEmpty()) {
402 // Add new class.
404 designerWarning(tr("A custom widget plugin whose class name (%1) matches that of an existing class has been found.").arg(pluginName));
405 } else {
407 addedPlugins++;
408 }
409 } else {
410 // replace existing info
411 const auto existingIndex = existingIt.value();
412 delete m_items[existingIndex];
416
417 }
418 }
419 }
420 // 4) remove classes that have not been matched. The stored indexes become invalid while deleting.
422 const int index = indexOfClassName(it.key());
423 if (index != -1) {
424 remove(index);
426 }
427 }
428
430 qDebug() << "WidgetDataBase::loadPlugins(): " << addedPlugins << " added, " << replacedPlugins << " replaced, " << removedPlugins << "deleted.";
431}
432
434{
436 delete m_items.takeAt(index);
437}
438
440{
443 // Create non-widgets, widgets in order
444 QObject* object = factory->createObject(name, nullptr);
445 if (!object)
446 object = factory->createWidget(name, nullptr);
447 if (!object) {
448 qDebug() << "** WARNING Factory failed to create " << name;
449 return {};
450 }
451 // Get properties from sheet.
454 const int propertyCount = sheet->count();
455 for (int i = 0; i < propertyCount; ++i) {
457 }
458 }
459 delete object;
460 return result;
461}
462
472
474{
475 // At this point, grab the default icons for the non-custom widgets from
476 // the widget box. They will show up in the object inspector.
478 const int itemCount = count();
479 for (int i = 0; i < itemCount; ++i) {
481 if (!dbItem->isCustom() && dbItem->icon().isNull()) {
482 // Careful not to catch the layout icons when looking for
483 // QWidget
484 const QString name = dbItem->name();
485 if (name == "QWidget"_L1) {
486 dbItem->setIcon(wb->iconForWidget(name, u"Containers"_s));
487 } else {
489 }
490 }
491 }
492 }
493}
494
495// --------------------- Functions relevant generation of new forms based on widgets (apart from the standard templates)
496
497enum { NewFormWidth = 400, NewFormHeight = 300 };
498
499// Check if class is suitable to generate a form from
500static inline bool isExistingTemplate(const QString &className)
501{
502 return className == "QWidget"_L1 || className == "QDialog"_L1 || className == "QMainWindow"_L1;
503}
504
505// Check if class is suitable to generate a form from
506static inline bool suitableForNewForm(const QString &className)
507{
508 if (className.isEmpty()) // Missing custom widget information
509 return false;
510 if (className == "QSplitter"_L1)
511 return false;
512 if (className.startsWith("QDesigner"_L1) || className.startsWith("QLayout"_L1))
513 return false;
514 return true;
515}
516
517// Return a list of widget classes from which new forms can be generated.
518// Suitable for 'New form' wizards in integrations.
520{
521 static QStringList rc;
522 if (rc.isEmpty()) {
524 const int widgetCount = wdb->count();
525 for (int i = 0; i < widgetCount; i++) {
527 if (item->isContainer() && !item->isCustom() && !item->isPromoted()) {
528 const QString name = item->name(); // Standard Widgets: no existing templates
530 rc += name;
531 }
532 }
533 }
534 return rc;
535}
536
537// Return a list of custom widget classes from which new forms can be generated.
538// Suitable for 'New form' wizards in integrations.
540{
543 const int widgetCount = wdb->count();
544 for (int i = 0; i < widgetCount; i++) { // Custom widgets: check name and base class.
546 if (item->isContainer() && item->isCustom() && !item->isPromoted()) {
548 rc += item->name();
549 }
550 }
551 return rc;
552}
553
554// Get XML for a new form from the widget box. Change objectName/geometry
555// properties to be suitable for new forms
556static QString xmlFromWidgetBox(const QDesignerFormEditorInterface *core, const QString &className, const QString &objectName)
557{
558 QDesignerWidgetBoxInterface::Widget widget;
559 const bool found = QDesignerWidgetBox::findWidget(core->widgetBox(), className, QString(), &widget);
560 if (!found)
561 return QString();
562 QScopedPointer<DomUI> domUI(QDesignerWidgetBox::xmlToUi(className, widget.domXml(), false));
563 if (domUI.isNull())
564 return QString();
565 domUI->setAttributeVersion(u"4.0"_s);
566 DomWidget *domWidget = domUI->elementWidget();
567 if (!domWidget)
568 return QString();
569 // Properties: Remove the "objectName" property in favour of the name attribute and check geometry.
570 domWidget->setAttributeName(objectName);
571 QList<DomProperty *> properties = domWidget->elementProperty();
572 for (auto it = properties.begin(); it != properties.end(); ) {
573 DomProperty *property = *it;
574 if (property->attributeName() == "objectName"_L1) { // remove "objectName"
575 it = properties.erase(it);
576 delete property;
577 } else {
578 if (property->attributeName() == "geometry"_L1) { // Make sure form is at least 400, 300
579 if (DomRect *geom = property->elementRect()) {
580 if (geom->elementWidth() < NewFormWidth)
581 geom->setElementWidth(NewFormWidth);
582 if (geom->elementHeight() < NewFormHeight)
583 geom->setElementHeight(NewFormHeight);
584 }
585 }
586 ++it;
587 }
588 }
589 // Add a window title property
590 DomString *windowTitleString = new DomString;
591 windowTitleString->setText(objectName);
592 DomProperty *windowTitleProperty = new DomProperty;
593 windowTitleProperty->setAttributeName(u"windowTitle"_s);
594 windowTitleProperty->setElementString(windowTitleString);
595 properties.push_back(windowTitleProperty);
596 // ------
597 domWidget->setElementProperty(properties);
598 // Embed in in DomUI and get string. Omit the version number.
599 domUI->setElementClass(objectName);
600
601 QString rc;
602 { // Serialize domUI
603 QXmlStreamWriter writer(&rc);
604 writer.setAutoFormatting(true);
605 writer.setAutoFormattingIndent(1);
606 writer.writeStartDocument();
607 domUI->write(writer);
608 writer.writeEndDocument();
609 }
610 return rc;
611}
612
613// Generate default standard ui new form xml based on the class passed on as similarClassName.
614static QString generateNewFormXML(const QString &className, const QString &similarClassName, const QString &name)
615{
616 QString rc;
617 QTextStream str(&rc);
618 str << R"(<ui version="4.0"><class>)" << name << "</class>"
619 << R"(<widget class=")" << className << R"(" name=")" << name << R"(">)"
620 << R"(<property name="geometry" ><rect><x>0</x><y>0</y><width>)"
621 << NewFormWidth << "</width><height>" << NewFormHeight << "</height></rect></property>"
622 << R"(<property name="windowTitle"><string>)" << name << "</string></property>\n";
623
624 if (similarClassName == "QMainWindow"_L1) {
625 str << R"(<widget class="QWidget" name="centralwidget"/>)";
626 } else if (similarClassName == "QWizard"_L1) {
627 str << R"(<widget class="QWizardPage" name="wizardPage1"/><widget class="QWizardPage" name="wizardPage2"/>)";
628 } else if (similarClassName == "QDockWidget"_L1) {
629 str << R"(<widget class="QWidget" name="dockWidgetContents"/>)";
630 }
631 str << "</widget></ui>\n";
632 return rc;
633}
634
635// Generate a form template using a class name obtained from formWidgetClasses(), customFormWidgetClasses().
637{
638 // How to find suitable XML for a class:
639 // 1) Look in widget box (as all the required centralwidgets, tab widget pages, etc. should be there).
641 if (!widgetBoxXml.isEmpty())
642 return widgetBoxXml;
643 // 2) If that fails, only custom main windows, custom dialogs and unsupported Qt Widgets should
644 // be left over. Generate something that is similar to the default templates. Find a similar class.
646 QString similarClass = u"QWidget"_s;
647 const int index = wdb->indexOfClassName(className);
648 if (index != -1) {
651 }
652 // Generate standard ui based on the class passed on as baseClassName.
654 return rc;
655}
656
657// Set a fixed size on a XML template
659{
661 if (!domUI)
662 return QString();
664 if (!domWidget)
665 return QString();
666 // Properties: Find/Ensure the geometry, minimum and maximum sizes properties
667 const QString geometryPropertyName = u"geometry"_s;
668 const QString minimumSizePropertyName = u"minimumSize"_s;
669 const QString maximumSizePropertyName = u"maximumSize"_s;
670 DomProperty *geomProperty = nullptr;
673
675 for (DomProperty *p : std::as_const(properties)) {
676 const QString name = p->attributeName();
677 if (name == geometryPropertyName) {
678 geomProperty = p;
679 } else if (name == minimumSizePropertyName) {
681 } else if (name == maximumSizePropertyName) {
683 }
684 }
685 if (!geomProperty) {
690 }
691 if (fixed) {
692 if (!minimumSizeProperty) {
697 }
698 if (!maximumSizeProperty) {
703 }
704 }
705 // Set values of geometry, minimum and maximum sizes properties
706 const int width = size.width();
707 const int height = size.height();
711 }
712 if (fixed) {
716 }
720 }
721 }
722 // write back
724
725 QString rc;
726 { // serialize domUI
733 }
734
735 return rc;
736}
737
738// ---- free functions
748
757
758
759/* Appends a derived class to the database inheriting the data of the base class. Used
760 for custom and promoted widgets.
761
762 Depending on whether an entry exists, the existing or a newly created entry is
763 returned. A return value of 0 indicates that the base class could not be found. */
764
765QDESIGNER_SHARED_EXPORT QDesignerWidgetDataBaseItemInterface *
767 const QString &className, const QString &group,
768 const QString &baseClassName,
769 const QString &includeFile,
770 bool promoted, bool custom)
771{
773 qDebug() << "appendDerived " << className << " derived from " << baseClassName;
774 // Check.
776 qWarning("** WARNING %s called with an empty class names: '%s' extends '%s'.",
778 return nullptr;
779 }
780 // Check whether item already exists.
783 if ( existingIndex != -1)
785 if (derivedItem) {
786 // Check the existing item for base class mismatch. This will likely
787 // happen when loading a file written by an instance with missing plugins.
788 // In that case, just warn and ignore the file properties.
789 //
790 // An empty base class indicates that it is not known (for example, for custom plugins).
791 // In this case, the widget DB is later updated once the widget is created
792 // by DOM (by querying the metaobject). Suppress the warning.
795 return derivedItem;
796
797 // Warn about mismatches
799 "The file contains a custom widget '%1' whose base class (%2)"
800 " differs from the current entry in the widget database (%3)."
801 " The widget database is left unchanged.").
803 return derivedItem;
804 }
805 // Create this item, inheriting its base properties
807 if (baseIndex == -1) {
809 qDebug() << "appendDerived failed due to missing base class";
810 return nullptr;
811 }
814 // Sort of hack: If base class is QWidget, we most likely
815 // do not want to inherit the container attribute.
816 if (baseItem->name() == "QWidget"_L1)
818 // set new props
826 return derivedItem;
827}
828
829/* Return a list of database items to which a class can be promoted to. */
830
831QDESIGNER_SHARED_EXPORT WidgetDataBaseItemList
833 const QString &baseClassName)
834{
836 // find existing promoted widgets deriving from base.
837 const int count = db->count();
838 for (int i = 0; i < count; ++i) {
840 if (item->isPromoted() && item->extends() == baseClassName) {
842 }
843 }
844 return rc;
845}
846} // namespace qdesigner_internal
847
848QT_END_NAMESPACE
Combined button and popup list for selecting options.
Auxiliary methods to store/retrieve settings.
static QString xmlFromWidgetBox(const QDesignerFormEditorInterface *core, const QString &className, const QString &objectName)
static QString generateNewFormXML(const QString &className, const QString &similarClassName, const QString &name)
static bool isExistingTemplate(const QString &className)
static WidgetDataBaseItem * createCustomWidgetItem(const QDesignerCustomWidgetInterface *c, const QDesignerCustomWidgetData &data)
static void addWidgetItem(WidgetDataBase *wdb, const char *name, const QMetaObject &mo, const char *comment)
static bool suitableForNewForm(const QString &className)
#define QDESIGNER_SHARED_EXPORT