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#include <widgets.table>
401
402#undef DECLARE_COMPAT_WIDGET
403#undef DECLARE_LAYOUT
404#undef DECLARE_WIDGET
405#undef DECLARE_WIDGET_1
406
407 if (w)
408 break;
409 // 4) fallBack
410 const QString fallBackBaseClass = "QWidget"_L1;
413 if (item == nullptr) {
414 // Emergency: Create, derived from QWidget
416 includeFile += ".h"_L1;
418 includeFile, true, true);
419 Q_ASSERT(item);
420 }
422 if (baseClass.isEmpty()) {
423 // Currently happens in the case of Q3-Support widgets
425 }
428 return promotedWidget; // Do not initialize twice.
429 }
430 } while (false);
431
432 Q_ASSERT(w != nullptr);
433 if (m_currentStyle)
436 if (fw) { // form editor initialization
437 initialize(w);
438 } else { // preview-only initialization
440 }
441 return w;
442}
443
445{
446 if (o == nullptr)
447 return QString();
448
449 const char *className = o->metaObject()->className();
450 if (!o->isWidgetType())
452 const QWidget *w = static_cast<const QWidget*>(o);
453 // check promoted before designer special
456 return customClassName;
457 if (qobject_cast<const QDesignerMenuBar*>(w))
458 return u"QMenuBar"_s;
459 if (qobject_cast<const QDesignerMenu*>(w))
460 return u"QMenu"_s;
462 return u"QDockWidget"_s;
463 if (qobject_cast<const QDesignerDialog*>(w))
464 return u"QDialog"_s;
465 if (qobject_cast<const QDesignerWidget*>(w))
466 return u"QWidget"_s;
467#ifdef Q_OS_WIN
468 if (isAxWidget(w))
469 return u"QAxWidget"_s;
470#endif
472}
473
475{
476 switch (type) {
477 case LayoutInfo::HBox:
478 return new QHBoxLayout(parentWidget);
479 case LayoutInfo::VBox:
480 return new QVBoxLayout(parentWidget);
481 case LayoutInfo::Grid:
482 return new QGridLayout(parentWidget);
483 case LayoutInfo::Form:
484 return new QFormLayout(parentWidget);
485 default:
486 Q_ASSERT(0);
487 break;
488 }
489 return nullptr;
490}
491
492
493/*!
494 \internal
495 Creates a layout on the widget \a widget of the type \a type
496 which can be \c HBox, \c VBox or \c Grid.
497*/
498
500{
502
503 if (parentLayout == nullptr) {
505 if (page) {
506 widget = page;
507 } else {
508 const QString msg =
509 tr("The current page of the container '%1' (%2) could not be determined while creating a layout."
510 "This indicates an inconsistency in the ui-file, probably a layout being constructed on a container widget.")
513 }
514 }
515
516 Q_ASSERT(metaDataBase->item(widget) != nullptr); // ensure the widget is managed
517
518 if (parentLayout == nullptr && metaDataBase->item(widget->layout()) == nullptr) {
520 }
521
522 QWidget *parentWidget = parentLayout != nullptr ? nullptr : widget;
523
525 metaDataBase->add(layout); // add the layout in the MetaDataBase
526
528
529 if (sheet) {
530 sheet->setChanged(sheet->indexOf(u"objectName"_s), true);
531 if (widget->inherits("QLayoutWidget")) {
532 sheet->setProperty(sheet->indexOf(u"leftMargin"_s), 0);
533 sheet->setProperty(sheet->indexOf(u"topMargin"_s), 0);
534 sheet->setProperty(sheet->indexOf(u"rightMargin"_s), 0);
535 sheet->setProperty(sheet->indexOf(u"bottomMargin"_s), 0);
536 }
537
538 const int index = sheet->indexOf(u"alignment"_s);
539 if (index != -1)
540 sheet->setChanged(index, true);
541 }
542
543 if (metaDataBase->item(widget->layout()) == nullptr) {
544 Q_ASSERT(layout->parent() == nullptr);
546 if (!box) { // we support only unmanaged box layouts
547 const QString msg = tr("Attempt to add a layout to a widget '%1' (%2) which already has an unmanaged layout of type %3.\n"
548 "This indicates an inconsistency in the ui-file.").
551 return nullptr;
552 }
554 }
555
556 return layout;
557}
558
559/*!
560 \internal
561 Returns the widget into which children should be inserted when \a
562 w is a container known to designer.
563
564 Usually, it is \a w itself, but there are exceptions (for example, a
565 tabwidget is known to designer as a container, but the child
566 widgets should be inserted into the current page of the
567 tabwidget. In this case, the current page of
568 the tabwidget would be returned.)
569 */
577
578/*!
579 \internal
580 Returns the actual designer widget of the container \a w. This is
581 normally \a w itself, but it might be a parent or grand parent of \a w
582 (for example, when working with a tabwidget and \a w is the container which
583 contains and layouts children, but the actual widget known to
584 designer is the tabwidget which is the parent of \a w. In this case,
585 the tabwidget would be returned.)
586*/
587
589{
590 // ### cleanup
591 if (!w)
592 return nullptr;
593 if (w->parentWidget() && w->parentWidget()->parentWidget() &&
596 return w->parentWidget()->parentWidget()->parentWidget();
597
598 while (w != nullptr) {
599 if (core()->widgetDataBase()->isContainer(w) ||
601 return w;
602
603 w = w->parentWidget();
604 }
605
606 return w;
607}
608
613
614// Necessary initializations for form editor/preview objects
616{
617 // Apply style
618 if (m_currentStyle)
620}
621
622// Necessary initializations for preview objects
624{
625
627 QStackedWidgetPreviewEventFilter::install(stackedWidget); // Add browse button only.
628 return;
629 }
630}
631
632// Necessary initializations for form editor objects
634{
635 // Indicate that this is a form object (for QDesignerFormWindowInterface::findFormWindow)
638 if (!sheet)
639 return;
640
641 sheet->setChanged(sheet->indexOf(u"objectName"_s), true);
642
643 if (!object->isWidgetType()) {
645 sheet->setChanged(sheet->indexOf(u"text"_s), true);
646 return;
647 }
648
649 QWidget *widget = static_cast<QWidget*>(object);
650 const bool isMenu = qobject_cast<QMenu*>(widget);
651 const bool isMenuBar = !isMenu && qobject_cast<QMenuBar*>(widget);
652
655
656 if (!isMenu)
657 sheet->setChanged(sheet->indexOf(u"geometry"_s), true);
658
659 if (qobject_cast<Spacer*>(widget)) {
660 sheet->setChanged(sheet->indexOf(u"spacerName"_s), true);
661 return;
662 }
663
664 const int o = sheet->indexOf(u"orientation"_s);
665 if (o != -1 && widget->inherits("QSplitter"))
666 sheet->setChanged(o, true);
667
670 sheet->setVisible(sheet->indexOf(u"windowTitle"_s), true);
671 toolBar->setFloatable(false); // prevent toolbars from being dragged off
672 return;
673 }
674
676 sheet->setVisible(sheet->indexOf(u"windowTitle"_s), true);
677 sheet->setVisible(sheet->indexOf(u"windowIcon"_s), true);
678 return;
679 }
680
681 if (isMenu) {
682 sheet->setChanged(sheet->indexOf(u"title"_s), true);
683 return;
684 }
685 // helpers
688 return;
689 }
692 return;
693 }
696 return;
697 }
698 // Prevent embedded line edits from getting focus
700 if (QLineEdit *lineEdit = static_cast<FriendlySpinBox*>(asb)->lineEdit())
702 return;
703 }
706 fcb->lineEdit()->setFocusPolicy(Qt::NoFocus); // Always present
707 return;
708 }
710 return;
711 }
712 if (QWizard *wz = qobject_cast<QWizard *>(widget)) {
714 Q_UNUSED(pw);
715 }
716}
717
718static inline QString classNameOfStyle(const QStyle *s)
719{
720 return QLatin1StringView(s->metaObject()->className());
721}
722
724{
725 return classNameOfStyle(style());
726}
727
728static inline bool isApplicationStyle(const QString &styleName)
729{
730 return styleName.isEmpty() || styleName == classNameOfStyle(qApp->style());
731}
732
737
739{
741}
742
744{
746 return qApp->style();
747
749 if (it == m_styleCache.end()) {
751 if (!style) {
752 const QString msg = tr("Cannot create style '%1'.").arg(styleName);
754 return nullptr;
755 }
757 }
758 return it.value();
759}
760
766
768{
769 if (!style)
770 return;
772 if (widget->style() == style && widget->palette() == standardPalette)
773 return;
774
778 for (auto *w : lst)
779 w->setStyle(style);
780}
781
782// Check for 'interactor' click on a tab bar,
783// which can appear within a QTabWidget or as a standalone widget.
784
785static bool isTabBarInteractor(const QTabBar *tabBar)
786{
787 // Tabbar embedded in Q(Designer)TabWidget, ie, normal tab widget case
788 if (qobject_cast<const QTabWidget*>(tabBar->parentWidget()))
789 return true;
790
791 // Standalone tab bar on the form. Return true for tab rect areas
792 // only to allow the user to select the tab bar by clicking outside the actual tabs.
793 const int count = tabBar->count();
794 if (count == 0)
795 return false;
796
797 // click into current tab: No Interaction
798 const int currentIndex = tabBar->currentIndex();
799 const QPoint pos = tabBar->mapFromGlobal(QCursor::pos());
800 if (tabBar->tabRect(currentIndex).contains(pos))
801 return false;
802
803 // click outside: No Interaction
804 const QRect geometry = QRect(QPoint(0, 0), tabBar->size());
805 if (!geometry.contains(pos))
806 return false;
807 // click into another tab: Let's interact, switch tabs.
808 for (int i = 0; i < count; i++)
809 if (tabBar->tabRect(i).contains(pos))
810 return true;
811 return false;
812}
813
814static bool isPassiveInteractorHelper(const QWidget *widget)
815{
816 if (qobject_cast<const QMenuBar*>(widget)
817#if QT_CONFIG(sizegrip)
818 || qobject_cast<const QSizeGrip*>(widget)
819#endif
820 || qobject_cast<const QMdiSubWindow*>(widget)
821 || qobject_cast<const QToolBar*>(widget)) {
822 return true;
823 }
824
825 if (qobject_cast<const QAbstractButton*>(widget)) {
826 auto parent = widget->parent();
827 if (qobject_cast<const QTabBar*>(parent) || qobject_cast<const QToolBox*>(parent))
828 return true;
829 } else if (const auto tabBar = qobject_cast<const QTabBar*>(widget)) {
830 if (isTabBarInteractor(tabBar))
831 return true;
832 } else if (qobject_cast<const QScrollBar*>(widget)) {
833 // A scroll bar is an interactor on a QAbstractScrollArea only.
834 if (auto parent = widget->parentWidget()) {
835 const QString &objectName = parent->objectName();
836 if (objectName == "qt_scrollarea_vcontainer"_L1 || objectName == "qt_scrollarea_hcontainer"_L1)
837 return true;
838 }
839 } else if (qstrcmp(widget->metaObject()->className(), "QDockWidgetTitle") == 0) {
840 return true;
841 } else if (qstrcmp(widget->metaObject()->className(), "QWorkspaceTitleBar") == 0) {
842 return true;
843 }
844 const QString &name = widget->objectName();
845 return name.startsWith("__qt__passive_"_L1)
846 || name == "qt_qmainwindow_extended_splitter"_L1;
847}
848
850{
851 static bool lastWasAPassiveInteractor = false;
853
856
857 // if a popup is open, we have to make sure that this one is closed,
858 // else X might do funny things
859 if (QApplication::activePopupWidget() || widget == nullptr)
860 return true;
861
864
866}
867
872
877
879{
882}
883
888} // namespace qdesigner_internal
889
890QT_END_NAMESPACE
891
892#include "widgetfactory.moc"
friend class QWidget
Definition qpainter.h:431
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[]