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
widgetfactory.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
18
19// shared
20#include "layoutinfo_p.h"
22#include "layout_p.h"
24
25// sdk
26#include <QtDesigner/abstractformeditor.h>
27#include <QtDesigner/container.h>
28#include <QtDesigner/qextensionmanager.h>
29#include <QtDesigner/propertysheet.h>
30#include <QtDesigner/abstractlanguage.h>
31#include <QtDesigner/abstractformwindowmanager.h>
32#include <QtDesigner/abstractformwindowcursor.h>
33
34#include <QtUiPlugin/customwidget.h>
35
36#include <QtWidgets/QtWidgets>
37#include <QtWidgets/qscrollbar.h>
38#include <QtWidgets/qfontcombobox.h>
39#include <QtWidgets/qabstractspinbox.h>
40#include <QtWidgets/qlineedit.h>
41#include <QtWidgets/qbuttongroup.h>
42#include <QtWidgets/qstyle.h>
43#include <QtWidgets/qstylefactory.h>
44#include <QtWidgets/qwizard.h>
45#include <QtCore/qdebug.h>
46#include <QtCore/qmetaobject.h>
47#include <QtCore/qpointer.h>
48
49#if QT_CONFIG(abstractbutton)
50# include <QtWidgets/qabstractbutton.h>
51#endif
52
53#if QT_CONFIG(itemviews)
54# include <QtWidgets/qabstractitemview.h>
55#endif
56
57#ifdef QT_OPENGLWIDGETS_LIB
58# include <QtOpenGLWidgets/qopenglwidget.h>
59#endif
60
62
63using namespace Qt::StringLiterals;
64
65#ifdef Q_OS_WIN
66static inline bool isAxWidget(const QObject *o)
67{
68 // Is it one of QDesignerAxWidget/QDesignerAxPluginWidget?
69 static const char axWidgetName[] = "QDesignerAx";
70 static const size_t axWidgetNameLen = qstrlen(axWidgetName);
71 return qstrncmp(o->metaObject()->className(), axWidgetName, axWidgetNameLen) == 0;
72}
73#endif
74
75/* Dynamic boolean property indicating object was created by the factory
76 * for the form editor. */
77
78static const char formEditorDynamicProperty[] = "_q_formEditorObject";
79
80namespace qdesigner_internal {
81
82#if QT_CONFIG(abstractbutton)
83
85{
86public:
88
89protected:
91};
92
93#endif
94
95#if QT_CONFIG(itemviews)
96
98{
99public:
101
102 QRect visualRect(const QModelIndex &) const override
103 {
104 return QRect(QPoint(), QSize(10, 10));
105 }
106
108
109 QModelIndex indexAt(const QPoint &) const override
110 {
111 return {};
112 }
113
114protected:
116 {
117 return {};
118 }
119
120 int horizontalOffset() const override { return 0; }
121 int verticalOffset() const override { return 0; }
122
123 bool isIndexHidden(const QModelIndex &) const override { return false; }
124
126
128 {
129 return QRegion(QRect(QPoint(), QSize(10, 10)));
130 }
131};
132
133#endif // QT_CONFIG(itemviews)
134
135// A friendly SpinBox that grants access to its QLineEdit
137public:
138 friend class WidgetFactory;
139};
140
141// An event filter for form-combo boxes that prevents the embedded line edit
142// from getting edit focus (and drawing blue artifacts/lines). It catches the
143// ChildPolished event when the "editable" property flips to true and the
144// QLineEdit is created and turns off the LineEdit's focus policy.
145
146class ComboEventFilter : public QObject {
147public:
148 explicit ComboEventFilter(QComboBox *parent) : QObject(parent) {}
149 bool eventFilter(QObject *watched, QEvent *event) override;
150};
151
152bool ComboEventFilter::eventFilter(QObject *watched, QEvent *event)
153{
154 if (event->type() == QEvent::ChildPolished) {
155 QComboBox *cb = static_cast<QComboBox*>(watched);
156 if (QLineEdit *le = cb->lineEdit()) {
157 le->setFocusPolicy(Qt::NoFocus);
158 le->setCursor(Qt::ArrowCursor);
159 }
160 }
161 return QObject::eventFilter(watched, event);
162}
163
164/* Watch out for QWizards changing their pages and make sure that not some
165 * selected widget becomes invisible on a hidden page (causing the selection
166 * handles to shine through). Select the wizard in that case in analogy to
167 * the QTabWidget event filters, etc. */
168
171public:
173
174public slots:
176};
177
178WizardPageChangeWatcher::WizardPageChangeWatcher(QWizard *parent) :
179 QObject(parent)
180{
181 connect(parent, &QWizard::currentIdChanged, this, &WizardPageChangeWatcher::pageChanged);
182}
183
184void WizardPageChangeWatcher::pageChanged()
185{
186 /* Use a bit more conservative approach than that for the QTabWidget,
187 * change the selection only if a selected child becomes invisible by
188 * changing the page. */
189 QWizard *wizard = static_cast<QWizard *>(parent());
190 QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(wizard);
191 if (!fw)
192 return;
193 QDesignerFormWindowCursorInterface *cursor = fw->cursor();
194 const int selCount = cursor->selectedWidgetCount();
195 for (int i = 0; i < selCount; i++) {
196 if (!cursor->selectedWidget(i)->isVisible()) {
197 fw->clearSelection(false);
198 fw->selectWidget(wizard, true);
199 break;
200 }
201 }
202}
203
204// ---------------- WidgetFactory
205const char *WidgetFactory::disableStyleCustomPaintingPropertyC = "_q_custom_style_disabled";
206
214
215WidgetFactory::~WidgetFactory() = default;
216
223
232
233// Convencience to create non-widget objects. Returns 0 if unknown
235{
236 if (className.isEmpty()) {
237 qWarning("** WARNING %s called with an empty class name", Q_FUNC_INFO);
238 return nullptr;
239 }
240 if (className == "QAction"_L1)
241 return new QAction(parent);
242 if (className == "QButtonGroup"_L1)
243 return new QButtonGroup(parent);
244 return nullptr;
245}
246
247// Check for mismatched class names in plugins, which is hard to track.
248static bool classNameMatches(const QObject *created, const QString &className)
249{
250#ifdef Q_OS_WIN
251 // Perform literal comparison first for QAxWidget, for which a meta object hack is in effect.
252 if (isAxWidget(created))
253 return true;
254#endif
255 const char *createdClassNameC = created->metaObject()->className();
256 const QByteArray classNameB = className.toUtf8();
257 const char *classNameC = classNameB.constData();
258 if (qstrcmp(createdClassNameC, classNameC) == 0 || created->inherits(classNameC))
259 return true;
260 // QTBUG-53984: QWebEngineView property dummy
261 if (classNameB == "QWebEngineView" && qstrcmp(createdClassNameC, "fake::QWebEngineView") == 0)
262 return true;
263 return false;
264}
265
267{
268 *creationError = false;
269
270 auto *factory = m_customFactory.value(className, nullptr);
271 if (factory == nullptr)
272 return nullptr;
273
275 // shouldn't happen
276 if (!rc) {
277 *creationError = true;
278 designerWarning(tr("The custom widget factory registered for widgets of class %1 returned 0.").arg(className));
279 return nullptr;
280 }
281 // Figure out the base class unless it is known
285 const int widgetInfoIndex = wdb->indexOfObject(rc, false);
286 if (widgetInfoIndex != -1) {
289 // If we hit on a 'Q3DesignerXXWidget' that claims to be a 'Q3XXWidget', step
290 // over.
291 if (mo && mo->className() == className)
292 mo = mo->superClass();
293 while (mo != nullptr) {
294 if (core()->widgetDataBase()->indexOfClassName(mo->className()) != -1) {
296 break;
297 }
298 mo = mo->superClass();
299 }
300 }
302 }
303 }
304 // Since a language plugin may lie about its names, like Qt Jambi
305 // does, return immediately here...
308 if (lang)
309 return rc;
310
311 // Check for mismatched class names which is hard to track.
313 designerWarning(tr("A class name mismatch occurred when creating a widget using the custom widget factory registered for widgets of class %1."
314 " It returned a widget of class %2.")
316 }
317 return rc;
318}
319
320
322{
323 if (widgetName.isEmpty()) {
324 qWarning("** WARNING %s called with an empty class name", Q_FUNC_INFO);
325 return nullptr;
326 }
327 // Preview or for form window?
329 if (! fw)
331
332 QWidget *w = nullptr;
333 do {
334 // 1) custom. If there is an explicit failure(factory wants to indicate something is wrong),
335 // return 0, do not try to find fallback, which might be worse in the case of Q3 widget.
338 if (w)
339 break;
341 return nullptr;
342
343 // 2) Special widgets
344 if (widgetName == "Line"_L1) {
345 w = new Line(parentWidget);
346#if QT_CONFIG(abstractbutton)
347 } else if (widgetName == u"QAbstractButton") {
349#endif
350#if QT_CONFIG(itemviews)
351 } else if (widgetName == u"QAbstractItemView") {
353#endif
354 } else if (widgetName == "QDockWidget"_L1) {
356 } else if (widgetName == "QMenuBar"_L1) {
358 } else if (widgetName == "QMenu"_L1) {
360 } else if (widgetName == "Spacer"_L1) {
361 w = new Spacer(parentWidget);
362 } else if (widgetName == "QLayoutWidget"_L1) {
364 } else if (widgetName == "QDialog"_L1) {
365 if (fw) {
367 } else {
368 w = new QDialog(parentWidget);
369 }
370 } else if (widgetName == "QWidget"_L1) {
371 /* We want a 'QDesignerWidget' that draws a grid only for widget
372 * forms and container extension pages (not for preview and not
373 * for normal QWidget children on forms (legacy) */
374 if (fw && parentWidget) {
377 } else {
378 if (parentWidget == fw->formContainer())
380 }
381 }
382 if (!w)
383 w = new QWidget(parentWidget);
384 }
385 if (w)
386 break;
387
388 // 3) table
390 const char *widgetNameC = widgetNameBA.constData();
391
392 if (w) { // symmetry for macro
393 }
394
395#define DECLARE_LAYOUT(L, C)
396#define DECLARE_COMPAT_WIDGET(W, C) /*DECLARE_WIDGET(W, C)*/
397#define DECLARE_WIDGET(W, C) else if (!qstrcmp(widgetNameC, #W)) { Q_ASSERT(w == 0); w = new W(parentWidget); }
398#define DECLARE_WIDGET_1(W, C) else if (!qstrcmp(widgetNameC, #W)) { Q_ASSERT(w == 0); w = new W(0, parentWidget); }
399
400// AXIVION DISABLE Style Qt-Generic-NoIrregularInclude Required functionality
401#include <widgets.table>
402// AXIVION ENABLE Style Qt-Generic-NoIrregularInclude
403
404#undef DECLARE_COMPAT_WIDGET
405#undef DECLARE_LAYOUT
406#undef DECLARE_WIDGET
407#undef DECLARE_WIDGET_1
408
409 if (w)
410 break;
411 // 4) fallBack
412 const QString fallBackBaseClass = "QWidget"_L1;
415 if (item == nullptr) {
416 // Emergency: Create, derived from QWidget
418 includeFile += ".h"_L1;
420 includeFile, true, true);
421 Q_ASSERT(item);
422 }
424 if (baseClass.isEmpty()) {
425 // Currently happens in the case of Q3-Support widgets
427 }
430 return promotedWidget; // Do not initialize twice.
431 }
432 } while (false);
433
434 Q_ASSERT(w != nullptr);
435 if (m_currentStyle)
438 if (fw) { // form editor initialization
439 initialize(w);
440 } else { // preview-only initialization
442 }
443 return w;
444}
445
447{
448 if (o == nullptr)
449 return QString();
450
451 const char *className = o->metaObject()->className();
452 if (!o->isWidgetType())
454 const QWidget *w = static_cast<const QWidget*>(o);
455 // check promoted before designer special
458 return customClassName;
459 if (qobject_cast<const QDesignerMenuBar*>(w))
460 return u"QMenuBar"_s;
461 if (qobject_cast<const QDesignerMenu*>(w))
462 return u"QMenu"_s;
464 return u"QDockWidget"_s;
465 if (qobject_cast<const QDesignerDialog*>(w))
466 return u"QDialog"_s;
467 if (qobject_cast<const QDesignerWidget*>(w))
468 return u"QWidget"_s;
469#ifdef Q_OS_WIN
470 if (isAxWidget(w))
471 return u"QAxWidget"_s;
472#endif
474}
475
477{
478 switch (type) {
479 case LayoutInfo::HBox:
480 return new QHBoxLayout(parentWidget);
481 case LayoutInfo::VBox:
482 return new QVBoxLayout(parentWidget);
483 case LayoutInfo::Grid:
484 return new QGridLayout(parentWidget);
485 case LayoutInfo::Form:
486 return new QFormLayout(parentWidget);
487 default:
488 Q_ASSERT(0);
489 break;
490 }
491 return nullptr;
492}
493
494
495/*!
496 \internal
497 Creates a layout on the widget \a widget of the type \a type
498 which can be \c HBox, \c VBox or \c Grid.
499*/
500
502{
504
505 if (parentLayout == nullptr) {
507 if (page) {
508 widget = page;
509 } else {
510 const QString msg =
511 tr("The current page of the container '%1' (%2) could not be determined while creating a layout."
512 "This indicates an inconsistency in the ui-file, probably a layout being constructed on a container widget.")
515 }
516 }
517
518 Q_ASSERT(metaDataBase->item(widget) != nullptr); // ensure the widget is managed
519
520 if (parentLayout == nullptr && metaDataBase->item(widget->layout()) == nullptr) {
522 }
523
524 QWidget *parentWidget = parentLayout != nullptr ? nullptr : widget;
525
527 metaDataBase->add(layout); // add the layout in the MetaDataBase
528
530
531 if (sheet) {
532 sheet->setChanged(sheet->indexOf(u"objectName"_s), true);
533 if (widget->inherits("QLayoutWidget")) {
534 sheet->setProperty(sheet->indexOf(u"leftMargin"_s), 0);
535 sheet->setProperty(sheet->indexOf(u"topMargin"_s), 0);
536 sheet->setProperty(sheet->indexOf(u"rightMargin"_s), 0);
537 sheet->setProperty(sheet->indexOf(u"bottomMargin"_s), 0);
538 }
539
540 const int index = sheet->indexOf(u"alignment"_s);
541 if (index != -1)
542 sheet->setChanged(index, true);
543 }
544
545 if (metaDataBase->item(widget->layout()) == nullptr) {
546 Q_ASSERT(layout->parent() == nullptr);
548 if (!box) { // we support only unmanaged box layouts
549 const QString msg = tr("Attempt to add a layout to a widget '%1' (%2) which already has an unmanaged layout of type %3.\n"
550 "This indicates an inconsistency in the ui-file.").
553 return nullptr;
554 }
556 }
557
558 return layout;
559}
560
561/*!
562 \internal
563 Returns the widget into which children should be inserted when \a
564 w is a container known to designer.
565
566 Usually, it is \a w itself, but there are exceptions (for example, a
567 tabwidget is known to designer as a container, but the child
568 widgets should be inserted into the current page of the
569 tabwidget. In this case, the current page of
570 the tabwidget would be returned.)
571 */
579
580/*!
581 \internal
582 Returns the actual designer widget of the container \a w. This is
583 normally \a w itself, but it might be a parent or grand parent of \a w
584 (for example, when working with a tabwidget and \a w is the container which
585 contains and layouts children, but the actual widget known to
586 designer is the tabwidget which is the parent of \a w. In this case,
587 the tabwidget would be returned.)
588*/
589
591{
592 // ### cleanup
593 if (!w)
594 return nullptr;
595 if (w->parentWidget() && w->parentWidget()->parentWidget() &&
598 return w->parentWidget()->parentWidget()->parentWidget();
599
600 while (w != nullptr) {
601 if (core()->widgetDataBase()->isContainer(w) ||
603 return w;
604
605 w = w->parentWidget();
606 }
607
608 return w;
609}
610
615
616// Necessary initializations for form editor/preview objects
618{
619 // Apply style
620 if (m_currentStyle)
622}
623
624// Necessary initializations for preview objects
626{
627
629 QStackedWidgetPreviewEventFilter::install(stackedWidget); // Add browse button only.
630 return;
631 }
632}
633
634// Necessary initializations for form editor objects
636{
637 // Indicate that this is a form object (for QDesignerFormWindowInterface::findFormWindow)
640 if (!sheet)
641 return;
642
643 sheet->setChanged(sheet->indexOf(u"objectName"_s), true);
644
645 if (!object->isWidgetType()) {
647 sheet->setChanged(sheet->indexOf(u"text"_s), true);
648 return;
649 }
650
651 QWidget *widget = static_cast<QWidget*>(object);
652 const bool isMenu = qobject_cast<QMenu*>(widget);
653 const bool isMenuBar = !isMenu && qobject_cast<QMenuBar*>(widget);
654
657
658 if (!isMenu)
659 sheet->setChanged(sheet->indexOf(u"geometry"_s), true);
660
661 if (qobject_cast<Spacer*>(widget)) {
662 sheet->setChanged(sheet->indexOf(u"spacerName"_s), true);
663 return;
664 }
665
666 const int o = sheet->indexOf(u"orientation"_s);
667 if (o != -1 && widget->inherits("QSplitter"))
668 sheet->setChanged(o, true);
669
672 sheet->setVisible(sheet->indexOf(u"windowTitle"_s), true);
673 toolBar->setFloatable(false); // prevent toolbars from being dragged off
674 return;
675 }
676
678 sheet->setVisible(sheet->indexOf(u"windowTitle"_s), true);
679 sheet->setVisible(sheet->indexOf(u"windowIcon"_s), true);
680 return;
681 }
682
683 if (isMenu) {
684 sheet->setChanged(sheet->indexOf(u"title"_s), true);
685 return;
686 }
687 // helpers
690 return;
691 }
694 return;
695 }
698 return;
699 }
700 // Prevent embedded line edits from getting focus
702 if (QLineEdit *lineEdit = static_cast<FriendlySpinBox*>(asb)->lineEdit())
704 return;
705 }
708 fcb->lineEdit()->setFocusPolicy(Qt::NoFocus); // Always present
709 return;
710 }
712 return;
713 }
714 if (QWizard *wz = qobject_cast<QWizard *>(widget)) {
716 Q_UNUSED(pw);
717 }
718}
719
720static inline QString classNameOfStyle(const QStyle *s)
721{
722 return QLatin1StringView(s->metaObject()->className());
723}
724
726{
727 return classNameOfStyle(style());
728}
729
730static inline bool isApplicationStyle(const QString &styleName)
731{
732 return styleName.isEmpty() || styleName == classNameOfStyle(qApp->style());
733}
734
739
741{
743}
744
746{
748 return qApp->style();
749
751 if (it == m_styleCache.end()) {
753 if (!style) {
754 const QString msg = tr("Cannot create style '%1'.").arg(styleName);
756 return nullptr;
757 }
759 }
760 return it.value();
761}
762
768
770{
771 if (!style)
772 return;
774 if (widget->style() == style && widget->palette() == standardPalette)
775 return;
776
780 for (auto *w : lst)
781 w->setStyle(style);
782}
783
784// Check for 'interactor' click on a tab bar,
785// which can appear within a QTabWidget or as a standalone widget.
786
787static bool isTabBarInteractor(const QTabBar *tabBar)
788{
789 // Tabbar embedded in Q(Designer)TabWidget, ie, normal tab widget case
790 if (qobject_cast<const QTabWidget*>(tabBar->parentWidget()))
791 return true;
792
793 // Standalone tab bar on the form. Return true for tab rect areas
794 // only to allow the user to select the tab bar by clicking outside the actual tabs.
795 const int count = tabBar->count();
796 if (count == 0)
797 return false;
798
799 // click into current tab: No Interaction
800 const int currentIndex = tabBar->currentIndex();
801 const QPoint pos = tabBar->mapFromGlobal(QCursor::pos());
802 if (tabBar->tabRect(currentIndex).contains(pos))
803 return false;
804
805 // click outside: No Interaction
806 const QRect geometry = QRect(QPoint(0, 0), tabBar->size());
807 if (!geometry.contains(pos))
808 return false;
809 // click into another tab: Let's interact, switch tabs.
810 for (int i = 0; i < count; i++)
811 if (tabBar->tabRect(i).contains(pos))
812 return true;
813 return false;
814}
815
816static bool isPassiveInteractorHelper(const QWidget *widget)
817{
818 if (qobject_cast<const QMenuBar*>(widget)
819#if QT_CONFIG(sizegrip)
820 || qobject_cast<const QSizeGrip*>(widget)
821#endif
822 || qobject_cast<const QMdiSubWindow*>(widget)
823 || qobject_cast<const QToolBar*>(widget)) {
824 return true;
825 }
826
827 if (qobject_cast<const QAbstractButton*>(widget)) {
828 auto parent = widget->parent();
829 if (qobject_cast<const QTabBar*>(parent) || qobject_cast<const QToolBox*>(parent))
830 return true;
831 } else if (const auto tabBar = qobject_cast<const QTabBar*>(widget)) {
832 if (isTabBarInteractor(tabBar))
833 return true;
834 } else if (qobject_cast<const QScrollBar*>(widget)) {
835 // A scroll bar is an interactor on a QAbstractScrollArea only.
836 if (auto parent = widget->parentWidget()) {
837 const QString &objectName = parent->objectName();
838 if (objectName == "qt_scrollarea_vcontainer"_L1 || objectName == "qt_scrollarea_hcontainer"_L1)
839 return true;
840 }
841 } else if (qstrcmp(widget->metaObject()->className(), "QDockWidgetTitle") == 0) {
842 return true;
843 } else if (qstrcmp(widget->metaObject()->className(), "QWorkspaceTitleBar") == 0) {
844 return true;
845 }
846 const QString &name = widget->objectName();
847 return name.startsWith("__qt__passive_"_L1)
848 || name == "qt_qmainwindow_extended_splitter"_L1;
849}
850
852{
853 static bool lastWasAPassiveInteractor = false;
855
858
859 // if a popup is open, we have to make sure that this one is closed,
860 // else X might do funny things
861 if (QApplication::activePopupWidget() || widget == nullptr)
862 return true;
863
866
868}
869
874
879
881{
884}
885
890} // namespace qdesigner_internal
891
892QT_END_NAMESPACE
893
894#include "widgetfactory.moc"
friend class QWidget
Definition qpainter.h:432
bool eventFilter(QObject *watched, QEvent *event) override
Filters events if this object has been installed as an event filter for the watched object.
Combined button and popup list for selecting options.
Auxiliary methods to store/retrieve settings.
static bool isPassiveInteractorHelper(const QWidget *widget)
static bool isTabBarInteractor(const QTabBar *tabBar)
static QString classNameOfStyle(const QStyle *s)
static bool classNameMatches(const QObject *created, const QString &className)
static bool isApplicationStyle(const QString &styleName)
static const char formEditorDynamicProperty[]