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/*! Creates a layout on the widget \a widget of the type \a type
494 which can be \c HBox, \c VBox or \c Grid.
495*/
496
498{
500
501 if (parentLayout == nullptr) {
503 if (page) {
504 widget = page;
505 } else {
506 const QString msg =
507 tr("The current page of the container '%1' (%2) could not be determined while creating a layout."
508 "This indicates an inconsistency in the ui-file, probably a layout being constructed on a container widget.")
511 }
512 }
513
514 Q_ASSERT(metaDataBase->item(widget) != nullptr); // ensure the widget is managed
515
516 if (parentLayout == nullptr && metaDataBase->item(widget->layout()) == nullptr) {
518 }
519
520 QWidget *parentWidget = parentLayout != nullptr ? nullptr : widget;
521
523 metaDataBase->add(layout); // add the layout in the MetaDataBase
524
526
527 if (sheet) {
528 sheet->setChanged(sheet->indexOf(u"objectName"_s), true);
529 if (widget->inherits("QLayoutWidget")) {
530 sheet->setProperty(sheet->indexOf(u"leftMargin"_s), 0);
531 sheet->setProperty(sheet->indexOf(u"topMargin"_s), 0);
532 sheet->setProperty(sheet->indexOf(u"rightMargin"_s), 0);
533 sheet->setProperty(sheet->indexOf(u"bottomMargin"_s), 0);
534 }
535
536 const int index = sheet->indexOf(u"alignment"_s);
537 if (index != -1)
538 sheet->setChanged(index, true);
539 }
540
541 if (metaDataBase->item(widget->layout()) == nullptr) {
542 Q_ASSERT(layout->parent() == nullptr);
544 if (!box) { // we support only unmanaged box layouts
545 const QString msg = tr("Attempt to add a layout to a widget '%1' (%2) which already has an unmanaged layout of type %3.\n"
546 "This indicates an inconsistency in the ui-file.").
549 return nullptr;
550 }
552 }
553
554 return layout;
555}
556
557/*! Returns the widget into which children should be inserted when \a
558 w is a container known to designer.
559
560 Usually, it is \a w itself, but there are exceptions (for example, a
561 tabwidget is known to designer as a container, but the child
562 widgets should be inserted into the current page of the
563 tabwidget. In this case, the current page of
564 the tabwidget would be returned.)
565 */
573
574/*! Returns the actual designer widget of the container \a w. This is
575 normally \a w itself, but it might be a parent or grand parent of \a w
576 (for example, when working with a tabwidget and \a w is the container which
577 contains and layouts children, but the actual widget known to
578 designer is the tabwidget which is the parent of \a w. In this case,
579 the tabwidget would be returned.)
580*/
581
583{
584 // ### cleanup
585 if (!w)
586 return nullptr;
587 if (w->parentWidget() && w->parentWidget()->parentWidget() &&
590 return w->parentWidget()->parentWidget()->parentWidget();
591
592 while (w != nullptr) {
593 if (core()->widgetDataBase()->isContainer(w) ||
595 return w;
596
597 w = w->parentWidget();
598 }
599
600 return w;
601}
602
607
608// Necessary initializations for form editor/preview objects
610{
611 // Apply style
612 if (m_currentStyle)
614}
615
616// Necessary initializations for preview objects
618{
619
621 QStackedWidgetPreviewEventFilter::install(stackedWidget); // Add browse button only.
622 return;
623 }
624}
625
626// Necessary initializations for form editor objects
628{
629 // Indicate that this is a form object (for QDesignerFormWindowInterface::findFormWindow)
632 if (!sheet)
633 return;
634
635 sheet->setChanged(sheet->indexOf(u"objectName"_s), true);
636
637 if (!object->isWidgetType()) {
639 sheet->setChanged(sheet->indexOf(u"text"_s), true);
640 return;
641 }
642
643 QWidget *widget = static_cast<QWidget*>(object);
644 const bool isMenu = qobject_cast<QMenu*>(widget);
645 const bool isMenuBar = !isMenu && qobject_cast<QMenuBar*>(widget);
646
649
650 if (!isMenu)
651 sheet->setChanged(sheet->indexOf(u"geometry"_s), true);
652
653 if (qobject_cast<Spacer*>(widget)) {
654 sheet->setChanged(sheet->indexOf(u"spacerName"_s), true);
655 return;
656 }
657
658 const int o = sheet->indexOf(u"orientation"_s);
659 if (o != -1 && widget->inherits("QSplitter"))
660 sheet->setChanged(o, true);
661
664 sheet->setVisible(sheet->indexOf(u"windowTitle"_s), true);
665 toolBar->setFloatable(false); // prevent toolbars from being dragged off
666 return;
667 }
668
670 sheet->setVisible(sheet->indexOf(u"windowTitle"_s), true);
671 sheet->setVisible(sheet->indexOf(u"windowIcon"_s), true);
672 return;
673 }
674
675 if (isMenu) {
676 sheet->setChanged(sheet->indexOf(u"title"_s), true);
677 return;
678 }
679 // helpers
682 return;
683 }
686 return;
687 }
690 return;
691 }
692 // Prevent embedded line edits from getting focus
694 if (QLineEdit *lineEdit = static_cast<FriendlySpinBox*>(asb)->lineEdit())
696 return;
697 }
700 fcb->lineEdit()->setFocusPolicy(Qt::NoFocus); // Always present
701 return;
702 }
704 return;
705 }
706 if (QWizard *wz = qobject_cast<QWizard *>(widget)) {
708 Q_UNUSED(pw);
709 }
710}
711
712static inline QString classNameOfStyle(const QStyle *s)
713{
714 return QLatin1StringView(s->metaObject()->className());
715}
716
718{
719 return classNameOfStyle(style());
720}
721
722static inline bool isApplicationStyle(const QString &styleName)
723{
724 return styleName.isEmpty() || styleName == classNameOfStyle(qApp->style());
725}
726
731
733{
735}
736
738{
740 return qApp->style();
741
743 if (it == m_styleCache.end()) {
745 if (!style) {
746 const QString msg = tr("Cannot create style '%1'.").arg(styleName);
748 return nullptr;
749 }
751 }
752 return it.value();
753}
754
760
762{
763 if (!style)
764 return;
766 if (widget->style() == style && widget->palette() == standardPalette)
767 return;
768
772 for (auto *w : lst)
773 w->setStyle(style);
774}
775
776// Check for 'interactor' click on a tab bar,
777// which can appear within a QTabWidget or as a standalone widget.
778
779static bool isTabBarInteractor(const QTabBar *tabBar)
780{
781 // Tabbar embedded in Q(Designer)TabWidget, ie, normal tab widget case
782 if (qobject_cast<const QTabWidget*>(tabBar->parentWidget()))
783 return true;
784
785 // Standalone tab bar on the form. Return true for tab rect areas
786 // only to allow the user to select the tab bar by clicking outside the actual tabs.
787 const int count = tabBar->count();
788 if (count == 0)
789 return false;
790
791 // click into current tab: No Interaction
792 const int currentIndex = tabBar->currentIndex();
793 const QPoint pos = tabBar->mapFromGlobal(QCursor::pos());
794 if (tabBar->tabRect(currentIndex).contains(pos))
795 return false;
796
797 // click outside: No Interaction
798 const QRect geometry = QRect(QPoint(0, 0), tabBar->size());
799 if (!geometry.contains(pos))
800 return false;
801 // click into another tab: Let's interact, switch tabs.
802 for (int i = 0; i < count; i++)
803 if (tabBar->tabRect(i).contains(pos))
804 return true;
805 return false;
806}
807
808static bool isPassiveInteractorHelper(const QWidget *widget)
809{
810 if (qobject_cast<const QMenuBar*>(widget)
811#if QT_CONFIG(sizegrip)
812 || qobject_cast<const QSizeGrip*>(widget)
813#endif
814 || qobject_cast<const QMdiSubWindow*>(widget)
815 || qobject_cast<const QToolBar*>(widget)) {
816 return true;
817 }
818
819 if (qobject_cast<const QAbstractButton*>(widget)) {
820 auto parent = widget->parent();
821 if (qobject_cast<const QTabBar*>(parent) || qobject_cast<const QToolBox*>(parent))
822 return true;
823 } else if (const auto tabBar = qobject_cast<const QTabBar*>(widget)) {
824 if (isTabBarInteractor(tabBar))
825 return true;
826 } else if (qobject_cast<const QScrollBar*>(widget)) {
827 // A scroll bar is an interactor on a QAbstractScrollArea only.
828 if (auto parent = widget->parentWidget()) {
829 const QString &objectName = parent->objectName();
830 if (objectName == "qt_scrollarea_vcontainer"_L1 || objectName == "qt_scrollarea_hcontainer"_L1)
831 return true;
832 }
833 } else if (qstrcmp(widget->metaObject()->className(), "QDockWidgetTitle") == 0) {
834 return true;
835 } else if (qstrcmp(widget->metaObject()->className(), "QWorkspaceTitleBar") == 0) {
836 return true;
837 }
838 const QString &name = widget->objectName();
839 return name.startsWith("__qt__passive_"_L1)
840 || name == "qt_qmainwindow_extended_splitter"_L1;
841}
842
844{
845 static bool lastWasAPassiveInteractor = false;
847
850
851 // if a popup is open, we have to make sure that this one is closed,
852 // else X might do funny things
853 if (QApplication::activePopupWidget() || widget == nullptr)
854 return true;
855
858
860}
861
866
871
873{
876}
877
882} // namespace qdesigner_internal
883
884QT_END_NAMESPACE
885
886#include "widgetfactory.moc"
friend class QWidget
Definition qpainter.h:421
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[]