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/qdebug.h>
27#include <QtCore/qmetaobject.h>
28#include <QtCore/qset.h>
29#include <QtCore/qstring.h>
30#include <QtCore/qtextstream.h>
31#include <QtCore/qcoreapplication.h>
32
33#include <memory>
34
35QT_BEGIN_NAMESPACE
36
37using namespace Qt::StringLiterals;
38
39namespace {
40 enum { debugWidgetDataBase = 0 };
41}
42
43namespace qdesigner_internal {
44
45using namespace Qt::StringLiterals;
46
47// ----------------------------------------------------------
57
59{
60 return m_name;
61}
62
64{
65 m_name = name;
66}
67
69{
70 return m_group;
71}
72
77
79{
80 return m_toolTip;
81}
82
87
89{
90 return m_whatsThis;
91}
92
97
102
107
109{
110 return m_icon;
111}
112
114{
115 m_icon = icon;
116}
117
119{
120 return m_compat;
121}
122
124{
125 m_compat = b;
126}
127
129{
130 return m_container;
131}
132
134{
135 m_container = b;
136}
137
139{
140 return m_custom;
141}
142
144{
145 m_custom = b;
146}
147
149{
150 return m_pluginPath;
151}
152
157
159{
160 return m_promoted;
161}
162
164{
165 m_promoted = b;
166}
167
169{
170 return m_extends;
171}
172
174{
175 m_extends = s;
176}
177
182
187
192
197
202
207
212
217
236
241
246
248{
250 if (auto *base = mo->superClass())
252#if QT_CONFIG(abstractbutton)
254 //: Widget Box ToolTip of QAbstractButton-derived classes
255 item->setToolTip(WidgetDataBase::tr("text"));
256 }
257#endif
258 wdb->append(item);
259}
260
261// ----------------------------------------------------------
264 m_core(core)
265{
267 for (const QMetaObject *metaObject : metaObjects)
269
271 QCoreApplication::translate("WidgetDataBase",
272 "Abstract base class that cannot be instantiated. For promotion/custom widget usage only.");
273
274#if QT_CONFIG(abstractbutton)
275 auto *abItem = new WidgetDataBaseItem(u"QAbstractButton"_s);
277 abItem->setBaseClassName(u"QWidget"_s);
278 append(abItem);
279#endif // QT_CONFIG(abstractbutton)
280
281#if QT_CONFIG(itemviews)
282 auto *aivItem = new WidgetDataBaseItem(u"QAbstractItemView"_s);
283 aivItem->setBaseClassName(u"QAbstractScrollArea"_s);
286#endif // QT_CONFIG(itemviews)
287
288 append(new WidgetDataBaseItem(u"Line"_s));
289 append(new WidgetDataBaseItem(u"Spacer"_s));
290 append(new WidgetDataBaseItem(u"QSplitter"_s));
291 append(new WidgetDataBaseItem(u"QLayoutWidget"_s));
292 // QDesignerWidget is used as central widget and as container for tab widgets, etc.
293 WidgetDataBaseItem *designerWidgetItem = new WidgetDataBaseItem(u"QDesignerWidget"_s);
296 append(new WidgetDataBaseItem(u"QDesignerDialog"_s));
297 append(new WidgetDataBaseItem(u"QDesignerMenu"_s));
298 append(new WidgetDataBaseItem(u"QDesignerMenuBar"_s));
299 append(new WidgetDataBaseItem(u"QDesignerDockWidget"_s));
300 append(new WidgetDataBaseItem(u"QAction"_s));
301 append(new WidgetDataBaseItem(u"QButtonGroup"_s));
302
303 // ### remove me
304 // ### check the casts
305
306#if 0 // ### enable me after 4.1
307 item(indexOfClassName(u"QToolBar"_s))->setContainer(true);
308#endif
309
310 item(indexOfClassName(u"QTabWidget"_s))->setContainer(true);
311 item(indexOfClassName(u"QGroupBox"_s))->setContainer(true);
312 item(indexOfClassName(u"QScrollArea"_s))->setContainer(true);
313 item(indexOfClassName(u"QStackedWidget"_s))->setContainer(true);
314 item(indexOfClassName(u"QToolBox"_s))->setContainer(true);
315 item(indexOfClassName(u"QFrame"_s))->setContainer(true);
316 item(indexOfClassName(u"QLayoutWidget"_s))->setContainer(true);
317 item(indexOfClassName(u"QDesignerWidget"_s))->setContainer(true);
318 item(indexOfClassName(u"QDesignerDialog"_s))->setContainer(true);
319 item(indexOfClassName(u"QSplitter"_s))->setContainer(true);
320 item(indexOfClassName(u"QMainWindow"_s))->setContainer(true);
321 item(indexOfClassName(u"QDockWidget"_s))->setContainer(true);
322 item(indexOfClassName(u"QDesignerDockWidget"_s))->setContainer(true);
323 item(indexOfClassName(u"QMdiArea"_s))->setContainer(true);
324 item(indexOfClassName(u"QWizard"_s))->setContainer(true);
325 item(indexOfClassName(u"QWizardPage"_s))->setContainer(true);
326
327 item(indexOfClassName(u"QWidget"_s))->setContainer(true);
328 item(indexOfClassName(u"QDialog"_s))->setContainer(true);
329}
330
331WidgetDataBase::~WidgetDataBase() = default;
332
337
353
355 const QDesignerCustomWidgetData &data)
356{
357 WidgetDataBaseItem *item = new WidgetDataBaseItem(c->name(), c->group());
358 item->setContainer(c->isContainer());
359 item->setCustom(true);
360 item->setIcon(c->icon());
361 item->setIncludeFile(c->includeFile());
362 item->setToolTip(c->toolTip());
363 item->setWhatsThis(c->whatsThis());
364 item->setPluginPath(data.pluginPath());
365 item->setAddPageMethod(data.xmlAddPageMethod());
366 item->setExtends(data.xmlExtends());
367 return item;
368}
369
371{
372 // 1) create a map of existing custom classes
375 for (qsizetype i = 0, count = m_items.size(); i < count; ++i) {
377 if (item->isCustom() && !item->isPromoted())
379 else
381 }
382 // 2) create a list plugins
388
389 // 3) replace custom classes or add new ones, remove them from existingCustomClasses,
390 // leaving behind deleted items
391 unsigned replacedPlugins = 0;
392 unsigned addedPlugins = 0;
393 unsigned removedPlugins = 0;
394 if (!pluginList.isEmpty()) {
399 // Add new class.
401 designerWarning(tr("A custom widget plugin whose class name (%1) matches that of an existing class has been found.").arg(pluginName));
402 } else {
404 addedPlugins++;
405 }
406 } else {
407 // replace existing info
408 const auto existingIndex = existingIt.value();
409 delete m_items[existingIndex];
413
414 }
415 }
416 }
417 // 4) remove classes that have not been matched. The stored indexes become invalid while deleting.
419 const int index = indexOfClassName(it.key());
420 if (index != -1) {
421 remove(index);
423 }
424 }
425
427 qDebug() << "WidgetDataBase::loadPlugins(): " << addedPlugins << " added, " << replacedPlugins << " replaced, " << removedPlugins << "deleted.";
428}
429
431{
433 delete m_items.takeAt(index);
434}
435
437{
440 // Create non-widgets, widgets in order
441 QObject* object = factory->createObject(name, nullptr);
442 if (!object)
443 object = factory->createWidget(name, nullptr);
444 if (!object) {
445 qDebug() << "** WARNING Factory failed to create " << name;
446 return {};
447 }
448 // Get properties from sheet.
451 const int propertyCount = sheet->count();
452 for (int i = 0; i < propertyCount; ++i) {
454 }
455 }
456 delete object;
457 return result;
458}
459
469
471{
472 // At this point, grab the default icons for the non-custom widgets from
473 // the widget box. They will show up in the object inspector.
475 const int itemCount = count();
476 for (int i = 0; i < itemCount; ++i) {
478 if (!dbItem->isCustom() && dbItem->icon().isNull()) {
479 // Careful not to catch the layout icons when looking for
480 // QWidget
481 const QString name = dbItem->name();
482 if (name == "QWidget"_L1) {
483 dbItem->setIcon(wb->iconForWidget(name, u"Containers"_s));
484 } else {
486 }
487 }
488 }
489 }
490}
491
492// --------------------- Functions relevant generation of new forms based on widgets (apart from the standard templates)
493
494enum { NewFormWidth = 400, NewFormHeight = 300 };
495
496// Check if class is suitable to generate a form from
497static inline bool isExistingTemplate(const QString &className)
498{
499 return className == "QWidget"_L1 || className == "QDialog"_L1 || className == "QMainWindow"_L1;
500}
501
502// Check if class is suitable to generate a form from
503static inline bool suitableForNewForm(const QString &className)
504{
505 if (className.isEmpty()) // Missing custom widget information
506 return false;
507 if (className == "QSplitter"_L1)
508 return false;
509 if (className.startsWith("QDesigner"_L1) || className.startsWith("QLayout"_L1))
510 return false;
511 return true;
512}
513
514// Return a list of widget classes from which new forms can be generated.
515// Suitable for 'New form' wizards in integrations.
517{
518 static QStringList rc;
519 if (rc.isEmpty()) {
521 const int widgetCount = wdb->count();
522 for (int i = 0; i < widgetCount; i++) {
524 if (item->isContainer() && !item->isCustom() && !item->isPromoted()) {
525 const QString name = item->name(); // Standard Widgets: no existing templates
527 rc += name;
528 }
529 }
530 }
531 return rc;
532}
533
534// Return a list of custom widget classes from which new forms can be generated.
535// Suitable for 'New form' wizards in integrations.
537{
540 const int widgetCount = wdb->count();
541 for (int i = 0; i < widgetCount; i++) { // Custom widgets: check name and base class.
543 if (item->isContainer() && item->isCustom() && !item->isPromoted()) {
545 rc += item->name();
546 }
547 }
548 return rc;
549}
550
551// Get XML for a new form from the widget box. Change objectName/geometry
552// properties to be suitable for new forms
553static QString xmlFromWidgetBox(const QDesignerFormEditorInterface *core, const QString &className, const QString &objectName)
554{
555 QDesignerWidgetBoxInterface::Widget widget;
556 const bool found = QDesignerWidgetBox::findWidget(core->widgetBox(), className, QString(), &widget);
557 if (!found)
558 return QString();
559 std::unique_ptr<DomUI> domUI(QDesignerWidgetBox::xmlToUi(className, widget.domXml(), false));
560 if (!domUI)
561 return QString();
562 domUI->setAttributeVersion(u"4.0"_s);
563 DomWidget *domWidget = domUI->elementWidget();
564 if (!domWidget)
565 return QString();
566 // Properties: Remove the "objectName" property in favour of the name attribute and check geometry.
567 domWidget->setAttributeName(objectName);
568 QList<DomProperty *> properties = domWidget->elementProperty();
569 for (auto it = properties.begin(); it != properties.end(); ) {
570 DomProperty *property = *it;
571 if (property->attributeName() == "objectName"_L1) { // remove "objectName"
572 it = properties.erase(it);
573 delete property;
574 } else {
575 if (property->attributeName() == "geometry"_L1) { // Make sure form is at least 400, 300
576 if (DomRect *geom = property->elementRect()) {
577 if (geom->elementWidth() < NewFormWidth)
578 geom->setElementWidth(NewFormWidth);
579 if (geom->elementHeight() < NewFormHeight)
580 geom->setElementHeight(NewFormHeight);
581 }
582 }
583 ++it;
584 }
585 }
586 // Add a window title property
587 DomString *windowTitleString = new DomString;
588 windowTitleString->setText(objectName);
589 DomProperty *windowTitleProperty = new DomProperty;
590 windowTitleProperty->setAttributeName(u"windowTitle"_s);
591 windowTitleProperty->setElementString(windowTitleString);
592 properties.push_back(windowTitleProperty);
593 // ------
594 domWidget->setElementProperty(properties);
595 // Embed in in DomUI and get string. Omit the version number.
596 domUI->setElementClass(objectName);
597
598 QString rc;
599 { // Serialize domUI
600 QXmlStreamWriter writer(&rc);
601 writer.setAutoFormatting(true);
602 writer.setAutoFormattingIndent(1);
603 writer.writeStartDocument();
604 domUI->write(writer);
605 writer.writeEndDocument();
606 }
607 return rc;
608}
609
610// Generate default standard ui new form xml based on the class passed on as similarClassName.
611static QString generateNewFormXML(const QString &className, const QString &similarClassName, const QString &name)
612{
613 QString rc;
614 QTextStream str(&rc);
615 str << R"(<ui version="4.0"><class>)" << name << "</class>"
616 << R"(<widget class=")" << className << R"(" name=")" << name << R"(">)"
617 << R"(<property name="geometry" ><rect><x>0</x><y>0</y><width>)"
618 << NewFormWidth << "</width><height>" << NewFormHeight << "</height></rect></property>"
619 << R"(<property name="windowTitle"><string>)" << name << "</string></property>\n";
620
621 if (similarClassName == "QMainWindow"_L1) {
622 str << R"(<widget class="QWidget" name="centralwidget"/>)";
623 } else if (similarClassName == "QWizard"_L1) {
624 str << R"(<widget class="QWizardPage" name="wizardPage1"/><widget class="QWizardPage" name="wizardPage2"/>)";
625 } else if (similarClassName == "QDockWidget"_L1) {
626 str << R"(<widget class="QWidget" name="dockWidgetContents"/>)";
627 }
628 str << "</widget></ui>\n";
629 return rc;
630}
631
632// Generate a form template using a class name obtained from formWidgetClasses(), customFormWidgetClasses().
634{
635 // How to find suitable XML for a class:
636 // 1) Look in widget box (as all the required centralwidgets, tab widget pages, etc. should be there).
638 if (!widgetBoxXml.isEmpty())
639 return widgetBoxXml;
640 // 2) If that fails, only custom main windows, custom dialogs and unsupported Qt Widgets should
641 // be left over. Generate something that is similar to the default templates. Find a similar class.
643 QString similarClass = u"QWidget"_s;
644 const int index = wdb->indexOfClassName(className);
645 if (index != -1) {
648 }
649 // Generate standard ui based on the class passed on as baseClassName.
651 return rc;
652}
653
654// Set a fixed size on a XML template
656{
657 std::unique_ptr<DomUI> domUI(QDesignerWidgetBox::xmlToUi(u"Form"_s, xml, false));
658 if (!domUI)
659 return QString();
661 if (!domWidget)
662 return QString();
663 // Properties: Find/Ensure the geometry, minimum and maximum sizes properties
664 const QString geometryPropertyName = u"geometry"_s;
665 const QString minimumSizePropertyName = u"minimumSize"_s;
666 const QString maximumSizePropertyName = u"maximumSize"_s;
667 DomProperty *geomProperty = nullptr;
670
672 for (DomProperty *p : std::as_const(properties)) {
673 const QString name = p->attributeName();
674 if (name == geometryPropertyName) {
675 geomProperty = p;
676 } else if (name == minimumSizePropertyName) {
678 } else if (name == maximumSizePropertyName) {
680 }
681 }
682 if (!geomProperty) {
687 }
688 if (fixed) {
689 if (!minimumSizeProperty) {
694 }
695 if (!maximumSizeProperty) {
700 }
701 }
702 // Set values of geometry, minimum and maximum sizes properties
703 const int width = size.width();
704 const int height = size.height();
708 }
709 if (fixed) {
713 }
717 }
718 }
719 // write back
721
722 QString rc;
723 { // serialize domUI
730 }
731
732 return rc;
733}
734
735// ---- free functions
745
754
755
756/* Appends a derived class to the database inheriting the data of the base class. Used
757 for custom and promoted widgets.
758
759 Depending on whether an entry exists, the existing or a newly created entry is
760 returned. A return value of 0 indicates that the base class could not be found. */
761
762QDESIGNER_SHARED_EXPORT QDesignerWidgetDataBaseItemInterface *
764 const QString &className, const QString &group,
765 const QString &baseClassName,
766 const QString &includeFile,
767 bool promoted, bool custom)
768{
770 qDebug() << "appendDerived " << className << " derived from " << baseClassName;
771 // Check.
773 qWarning("** WARNING %s called with an empty class names: '%s' extends '%s'.",
775 return nullptr;
776 }
777 // Check whether item already exists.
780 if ( existingIndex != -1)
782 if (derivedItem) {
783 // Check the existing item for base class mismatch. This will likely
784 // happen when loading a file written by an instance with missing plugins.
785 // In that case, just warn and ignore the file properties.
786 //
787 // An empty base class indicates that it is not known (for example, for custom plugins).
788 // In this case, the widget DB is later updated once the widget is created
789 // by DOM (by querying the metaobject). Suppress the warning.
792 return derivedItem;
793
794 // Warn about mismatches
796 "The file contains a custom widget '%1' whose base class (%2)"
797 " differs from the current entry in the widget database (%3)."
798 " The widget database is left unchanged.").
800 return derivedItem;
801 }
802 // Create this item, inheriting its base properties
804 if (baseIndex == -1) {
806 qDebug() << "appendDerived failed due to missing base class";
807 return nullptr;
808 }
811 // Sort of hack: If base class is QWidget, we most likely
812 // do not want to inherit the container attribute.
813 if (baseItem->name() == "QWidget"_L1)
815 // set new props
823 return derivedItem;
824}
825
826/* Return a list of database items to which a class can be promoted to. */
827
828QDESIGNER_SHARED_EXPORT WidgetDataBaseItemList
830 const QString &baseClassName)
831{
833 // find existing promoted widgets deriving from base.
834 const int count = db->count();
835 for (int i = 0; i < count; ++i) {
837 if (item->isPromoted() && item->extends() == baseClassName) {
839 }
840 }
841 return rc;
842}
843} // namespace qdesigner_internal
844
845QT_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