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// Qt-Security score:significant reason:default
4
12#include <QtDesigner/private/ui4_p.h>
13
14#include <QtDesigner/propertysheet.h>
15#include <QtDesigner/qextensionmanager.h>
16#include <QtDesigner/abstractformeditor.h>
17
18#include <QtUiPlugin/customwidget.h>
19#include <QtWidgets/QtWidgets>
20#ifdef QT_OPENGLWIDGETS_LIB
21#include <QtOpenGLWidgets/qopenglwidget.h>
22#endif
23
24#include <QtCore/qxmlstream.h>
25
26#include <QtCore/qcoreapplication.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
34#include <memory>
35
36QT_BEGIN_NAMESPACE
37
38using namespace Qt::StringLiterals;
39
40namespace {
41 enum { debugWidgetDataBase = 0 };
42}
43
44namespace qdesigner_internal {
45
46using namespace Qt::StringLiterals;
47
48// ----------------------------------------------------------
58
60{
61 return m_name;
62}
63
65{
66 m_name = name;
67}
68
70{
71 return m_group;
72}
73
78
80{
81 return m_toolTip;
82}
83
88
90{
91 return m_whatsThis;
92}
93
98
100{
101 return m_includeFile;
102}
103
108
110{
111 return m_icon;
112}
113
115{
116 m_icon = icon;
117}
118
120{
121 return m_compat;
122}
123
125{
126 m_compat = b;
127}
128
130{
131 return m_container;
132}
133
135{
136 m_container = b;
137}
138
140{
141 return m_custom;
142}
143
145{
146 m_custom = b;
147}
148
150{
151 return m_pluginPath;
152}
153
158
160{
161 return m_promoted;
162}
163
165{
166 m_promoted = b;
167}
168
170{
171 return m_extends;
172}
173
175{
176 m_extends = s;
177}
178
183
188
193
198
203
208
213
218
237
242
247
249{
251 if (auto *base = mo->superClass())
253#if QT_CONFIG(abstractbutton)
255 //: Widget Box ToolTip of QAbstractButton-derived classes
256 item->setToolTip(WidgetDataBase::tr("text"));
257 }
258#endif
259 wdb->append(item);
260}
261
262// ----------------------------------------------------------
265 m_core(core)
266{
268 for (const QMetaObject *metaObject : metaObjects)
270
272 QCoreApplication::translate("WidgetDataBase",
273 "Abstract base class that cannot be instantiated. For promotion/custom widget usage only.");
274
275#if QT_CONFIG(abstractbutton)
276 auto *abItem = new WidgetDataBaseItem(u"QAbstractButton"_s);
278 abItem->setBaseClassName(u"QWidget"_s);
279 append(abItem);
280#endif // QT_CONFIG(abstractbutton)
281
282#if QT_CONFIG(itemviews)
283 auto *aivItem = new WidgetDataBaseItem(u"QAbstractItemView"_s);
284 aivItem->setBaseClassName(u"QAbstractScrollArea"_s);
287#endif // QT_CONFIG(itemviews)
288
289 append(new WidgetDataBaseItem(u"Line"_s));
290 append(new WidgetDataBaseItem(u"Spacer"_s));
291 append(new WidgetDataBaseItem(u"QSplitter"_s));
292 append(new WidgetDataBaseItem(u"QLayoutWidget"_s));
293 // QDesignerWidget is used as central widget and as container for tab widgets, etc.
294 WidgetDataBaseItem *designerWidgetItem = new WidgetDataBaseItem(u"QDesignerWidget"_s);
297 append(new WidgetDataBaseItem(u"QDesignerDialog"_s));
298 append(new WidgetDataBaseItem(u"QDesignerMenu"_s));
299 append(new WidgetDataBaseItem(u"QDesignerMenuBar"_s));
300 append(new WidgetDataBaseItem(u"QDesignerDockWidget"_s));
301 append(new WidgetDataBaseItem(u"QAction"_s));
302 append(new WidgetDataBaseItem(u"QButtonGroup"_s));
303
304 // ### remove me
305 // ### check the casts
306
307#if 0 // ### enable me after 4.1
308 item(indexOfClassName(u"QToolBar"_s))->setContainer(true);
309#endif
310
311 item(indexOfClassName(u"QTabWidget"_s))->setContainer(true);
312 item(indexOfClassName(u"QGroupBox"_s))->setContainer(true);
313 item(indexOfClassName(u"QScrollArea"_s))->setContainer(true);
314 item(indexOfClassName(u"QStackedWidget"_s))->setContainer(true);
315 item(indexOfClassName(u"QToolBox"_s))->setContainer(true);
316 item(indexOfClassName(u"QFrame"_s))->setContainer(true);
317 item(indexOfClassName(u"QLayoutWidget"_s))->setContainer(true);
318 item(indexOfClassName(u"QDesignerWidget"_s))->setContainer(true);
319 item(indexOfClassName(u"QDesignerDialog"_s))->setContainer(true);
320 item(indexOfClassName(u"QSplitter"_s))->setContainer(true);
321 item(indexOfClassName(u"QMainWindow"_s))->setContainer(true);
322 item(indexOfClassName(u"QDockWidget"_s))->setContainer(true);
323 item(indexOfClassName(u"QDesignerDockWidget"_s))->setContainer(true);
324 item(indexOfClassName(u"QMdiArea"_s))->setContainer(true);
325 item(indexOfClassName(u"QWizard"_s))->setContainer(true);
326 item(indexOfClassName(u"QWizardPage"_s))->setContainer(true);
327
328 item(indexOfClassName(u"QWidget"_s))->setContainer(true);
329 item(indexOfClassName(u"QDialog"_s))->setContainer(true);
330}
331
332WidgetDataBase::~WidgetDataBase() = default;
333
338
354
356 const QDesignerCustomWidgetData &data)
357{
358 WidgetDataBaseItem *item = new WidgetDataBaseItem(c->name(), c->group());
359 item->setContainer(c->isContainer());
360 item->setCustom(true);
361 item->setIcon(c->icon());
362 item->setIncludeFile(c->includeFile());
363 item->setToolTip(c->toolTip());
364 item->setWhatsThis(c->whatsThis());
365 item->setPluginPath(data.pluginPath());
366 item->setAddPageMethod(data.xmlAddPageMethod());
367 item->setExtends(data.xmlExtends());
368 return item;
369}
370
372{
373 // 1) create a map of existing custom classes
376 for (qsizetype i = 0, count = m_items.size(); i < count; ++i) {
378 if (item->isCustom() && !item->isPromoted())
380 else
382 }
383 // 2) create a list plugins
389
390 // 3) replace custom classes or add new ones, remove them from existingCustomClasses,
391 // leaving behind deleted items
392 unsigned replacedPlugins = 0;
393 unsigned addedPlugins = 0;
394 unsigned removedPlugins = 0;
395 if (!pluginList.isEmpty()) {
400 // Add new class.
402 designerWarning(tr("A custom widget plugin whose class name (%1) matches that of an existing class has been found.").arg(pluginName));
403 } else {
405 addedPlugins++;
406 }
407 } else {
408 // replace existing info
409 const auto existingIndex = existingIt.value();
410 delete m_items[existingIndex];
414
415 }
416 }
417 }
418 // 4) remove classes that have not been matched. The stored indexes become invalid while deleting.
420 const int index = indexOfClassName(it.key());
421 if (index != -1) {
422 remove(index);
424 }
425 }
426
428 qDebug() << "WidgetDataBase::loadPlugins(): " << addedPlugins << " added, " << replacedPlugins << " replaced, " << removedPlugins << "deleted.";
429}
430
432{
434 delete m_items.takeAt(index);
435}
436
438{
441 // Create non-widgets, widgets in order
442 QObject* object = factory->createObject(name, nullptr);
443 if (!object)
444 object = factory->createWidget(name, nullptr);
445 if (!object) {
446 qDebug() << "** WARNING Factory failed to create " << name;
447 return {};
448 }
449 // Get properties from sheet.
452 const int propertyCount = sheet->count();
453 for (int i = 0; i < propertyCount; ++i) {
455 }
456 }
457 delete object;
458 return result;
459}
460
470
472{
473 // At this point, grab the default icons for the non-custom widgets from
474 // the widget box. They will show up in the object inspector.
476 const int itemCount = count();
477 for (int i = 0; i < itemCount; ++i) {
479 if (!dbItem->isCustom() && dbItem->icon().isNull()) {
480 // Careful not to catch the layout icons when looking for
481 // QWidget
482 const QString name = dbItem->name();
483 if (name == "QWidget"_L1) {
484 dbItem->setIcon(wb->iconForWidget(name, u"Containers"_s));
485 } else {
487 }
488 }
489 }
490 }
491}
492
493// --------------------- Functions relevant generation of new forms based on widgets (apart from the standard templates)
494
495enum { NewFormWidth = 400, NewFormHeight = 300 };
496
497// Check if class is suitable to generate a form from
498static inline bool isExistingTemplate(const QString &className)
499{
500 return className == "QWidget"_L1 || className == "QDialog"_L1 || className == "QMainWindow"_L1;
501}
502
503// Check if class is suitable to generate a form from
504static inline bool suitableForNewForm(const QString &className)
505{
506 if (className.isEmpty()) // Missing custom widget information
507 return false;
508 if (className == "QSplitter"_L1)
509 return false;
510 if (className.startsWith("QDesigner"_L1) || className.startsWith("QLayout"_L1))
511 return false;
512 return true;
513}
514
515// Return a list of widget classes from which new forms can be generated.
516// Suitable for 'New form' wizards in integrations.
518{
519 static QStringList rc;
520 if (rc.isEmpty()) {
522 const int widgetCount = wdb->count();
523 for (int i = 0; i < widgetCount; i++) {
525 if (item->isContainer() && !item->isCustom() && !item->isPromoted()) {
526 const QString name = item->name(); // Standard Widgets: no existing templates
528 rc += name;
529 }
530 }
531 }
532 return rc;
533}
534
535// Return a list of custom widget classes from which new forms can be generated.
536// Suitable for 'New form' wizards in integrations.
538{
541 const int widgetCount = wdb->count();
542 for (int i = 0; i < widgetCount; i++) { // Custom widgets: check name and base class.
544 if (item->isContainer() && item->isCustom() && !item->isPromoted()) {
546 rc += item->name();
547 }
548 }
549 return rc;
550}
551
552// Get XML for a new form from the widget box. Change objectName/geometry
553// properties to be suitable for new forms
554static QString xmlFromWidgetBox(const QDesignerFormEditorInterface *core, const QString &className, const QString &objectName)
555{
556 QDesignerWidgetBoxInterface::Widget widget;
557 const bool found = QDesignerWidgetBox::findWidget(core->widgetBox(), className, QString(), &widget);
558 if (!found)
559 return QString();
560 std::unique_ptr<DomUI> domUI(QDesignerWidgetBox::xmlToUi(className, widget.domXml(), false));
561 if (!domUI)
562 return QString();
563 domUI->setAttributeVersion(u"4.0"_s);
564 DomWidget *domWidget = domUI->elementWidget();
565 if (!domWidget)
566 return QString();
567 // Properties: Remove the "objectName" property in favour of the name attribute and check geometry.
568 domWidget->setAttributeName(objectName);
569 QList<DomProperty *> properties = domWidget->elementProperty();
570 for (auto it = properties.begin(); it != properties.end(); ) {
571 DomProperty *property = *it;
572 if (property->attributeName() == "objectName"_L1) { // remove "objectName"
573 it = properties.erase(it);
574 delete property;
575 } else {
576 if (property->attributeName() == "geometry"_L1) { // Make sure form is at least 400, 300
577 if (DomRect *geom = property->elementRect()) {
578 if (geom->elementWidth() < NewFormWidth)
579 geom->setElementWidth(NewFormWidth);
580 if (geom->elementHeight() < NewFormHeight)
581 geom->setElementHeight(NewFormHeight);
582 }
583 }
584 ++it;
585 }
586 }
587 // Add a window title property
588 DomString *windowTitleString = new DomString;
589 windowTitleString->setText(objectName);
590 DomProperty *windowTitleProperty = new DomProperty;
591 windowTitleProperty->setAttributeName(u"windowTitle"_s);
592 windowTitleProperty->setElementString(windowTitleString);
593 properties.push_back(windowTitleProperty);
594 // ------
595 domWidget->setElementProperty(properties);
596 // Embed in in DomUI and get string. Omit the version number.
597 domUI->setElementClass(objectName);
598
599 QString rc;
600 { // Serialize domUI
601 QXmlStreamWriter writer(&rc);
602 writer.setAutoFormatting(true);
603 writer.setAutoFormattingIndent(1);
604 writer.writeStartDocument();
605 domUI->write(writer);
606 writer.writeEndDocument();
607 }
608 return rc;
609}
610
611// Generate default standard ui new form xml based on the class passed on as similarClassName.
612static QString generateNewFormXML(const QString &className, const QString &similarClassName, const QString &name)
613{
614 QString rc;
615 QTextStream str(&rc);
616 str << R"(<ui version="4.0"><class>)" << name << "</class>"
617 << R"(<widget class=")" << className << R"(" name=")" << name << R"(">)"
618 << R"(<property name="geometry" ><rect><x>0</x><y>0</y><width>)"
619 << NewFormWidth << "</width><height>" << NewFormHeight << "</height></rect></property>"
620 << R"(<property name="windowTitle"><string>)" << name << "</string></property>\n";
621
622 if (similarClassName == "QMainWindow"_L1) {
623 str << R"(<widget class="QWidget" name="centralwidget"/>)";
624 } else if (similarClassName == "QWizard"_L1) {
625 str << R"(<widget class="QWizardPage" name="wizardPage1"/><widget class="QWizardPage" name="wizardPage2"/>)";
626 } else if (similarClassName == "QDockWidget"_L1) {
627 str << R"(<widget class="QWidget" name="dockWidgetContents"/>)";
628 }
629 str << "</widget></ui>\n";
630 return rc;
631}
632
633// Generate a form template using a class name obtained from formWidgetClasses(), customFormWidgetClasses().
635{
636 // How to find suitable XML for a class:
637 // 1) Look in widget box (as all the required centralwidgets, tab widget pages, etc. should be there).
639 if (!widgetBoxXml.isEmpty())
640 return widgetBoxXml;
641 // 2) If that fails, only custom main windows, custom dialogs and unsupported Qt Widgets should
642 // be left over. Generate something that is similar to the default templates. Find a similar class.
644 QString similarClass = u"QWidget"_s;
645 const int index = wdb->indexOfClassName(className);
646 if (index != -1) {
649 }
650 // Generate standard ui based on the class passed on as baseClassName.
652 return rc;
653}
654
655// Set a fixed size on a XML template
657{
658 std::unique_ptr<DomUI> domUI(QDesignerWidgetBox::xmlToUi(u"Form"_s, xml, false));
659 if (!domUI)
660 return QString();
662 if (!domWidget)
663 return QString();
664 // Properties: Find/Ensure the geometry, minimum and maximum sizes properties
665 const QString geometryPropertyName = u"geometry"_s;
666 const QString minimumSizePropertyName = u"minimumSize"_s;
667 const QString maximumSizePropertyName = u"maximumSize"_s;
668 DomProperty *geomProperty = nullptr;
671
673 for (DomProperty *p : std::as_const(properties)) {
674 const QString name = p->attributeName();
675 if (name == geometryPropertyName) {
676 geomProperty = p;
677 } else if (name == minimumSizePropertyName) {
679 } else if (name == maximumSizePropertyName) {
681 }
682 }
683 if (!geomProperty) {
688 }
689 if (fixed) {
690 if (!minimumSizeProperty) {
695 }
696 if (!maximumSizeProperty) {
701 }
702 }
703 // Set values of geometry, minimum and maximum sizes properties
704 const int width = size.width();
705 const int height = size.height();
709 }
710 if (fixed) {
714 }
718 }
719 }
720 // write back
722
723 QString rc;
724 { // serialize domUI
731 }
732
733 return rc;
734}
735
736// ---- free functions
746
755
756
757/* Appends a derived class to the database inheriting the data of the base class. Used
758 for custom and promoted widgets.
759
760 Depending on whether an entry exists, the existing or a newly created entry is
761 returned. A return value of 0 indicates that the base class could not be found. */
762
763QDESIGNER_SHARED_EXPORT QDesignerWidgetDataBaseItemInterface *
765 const QString &className, const QString &group,
766 const QString &baseClassName,
767 const QString &includeFile,
768 bool promoted, bool custom)
769{
771 qDebug() << "appendDerived " << className << " derived from " << baseClassName;
772 // Check.
774 qWarning("** WARNING %s called with an empty class names: '%s' extends '%s'.",
776 return nullptr;
777 }
778 // Check whether item already exists.
781 if ( existingIndex != -1)
783 if (derivedItem) {
784 // Check the existing item for base class mismatch. This will likely
785 // happen when loading a file written by an instance with missing plugins.
786 // In that case, just warn and ignore the file properties.
787 //
788 // An empty base class indicates that it is not known (for example, for custom plugins).
789 // In this case, the widget DB is later updated once the widget is created
790 // by DOM (by querying the metaobject). Suppress the warning.
793 return derivedItem;
794
795 // Warn about mismatches
797 "The file contains a custom widget '%1' whose base class (%2)"
798 " differs from the current entry in the widget database (%3)."
799 " The widget database is left unchanged.").
801 return derivedItem;
802 }
803 // Create this item, inheriting its base properties
805 if (baseIndex == -1) {
807 qDebug() << "appendDerived failed due to missing base class";
808 return nullptr;
809 }
812 // Sort of hack: If base class is QWidget, we most likely
813 // do not want to inherit the container attribute.
814 if (baseItem->name() == "QWidget"_L1)
816 // set new props
824 return derivedItem;
825}
826
827/* Return a list of database items to which a class can be promoted to. */
828
829QDESIGNER_SHARED_EXPORT WidgetDataBaseItemList
831 const QString &baseClassName)
832{
834 // find existing promoted widgets deriving from base.
835 const int count = db->count();
836 for (int i = 0; i < count; ++i) {
838 if (item->isPromoted() && item->extends() == baseClassName) {
840 }
841 }
842 return rc;
843}
844} // namespace qdesigner_internal
845
846QT_END_NAMESPACE
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 bool suitableForNewForm(const QString &className)
static void addWidgetItem(WidgetDataBase *wdb, const QMetaObject *mo)
#define QDESIGNER_SHARED_EXPORT