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#include "objectutils_p.h"
19
20// shared
21#include "layoutinfo_p.h"
23#include "layout_p.h"
25
26// sdk
27#include <QtDesigner/abstractformeditor.h>
28#include <QtDesigner/container.h>
29#include <QtDesigner/qextensionmanager.h>
30#include <QtDesigner/propertysheet.h>
31#include <QtDesigner/abstractlanguage.h>
32#include <QtDesigner/abstractformwindowmanager.h>
33#include <QtDesigner/abstractformwindowcursor.h>
34
35#include <QtUiPlugin/customwidget.h>
36
37#include <QtWidgets/QtWidgets>
38#include <QtWidgets/qscrollbar.h>
39#include <QtWidgets/qfontcombobox.h>
40#include <QtWidgets/qabstractspinbox.h>
41#include <QtWidgets/qlineedit.h>
42#include <QtWidgets/qbuttongroup.h>
43#include <QtWidgets/qstyle.h>
44#include <QtWidgets/qstylefactory.h>
45#include <QtWidgets/qwizard.h>
46#include <QtCore/qdebug.h>
47#include <QtCore/qmetaobject.h>
48#include <QtCore/qpointer.h>
49
50#if QT_CONFIG(abstractbutton)
51# include <QtWidgets/qabstractbutton.h>
52#endif
53
54#if QT_CONFIG(itemviews)
55# include <QtWidgets/qabstractitemview.h>
56#endif
57
58#ifdef QT_OPENGLWIDGETS_LIB
59# include <QtOpenGLWidgets/qopenglwidget.h>
60#endif
61
63
64using namespace Qt::StringLiterals;
65
66#ifdef Q_OS_WIN
67static inline bool isAxWidget(const QObject *o)
68{
69 // Is it one of QDesignerAxWidget/QDesignerAxPluginWidget?
70 static const char axWidgetName[] = "QDesignerAx";
71 static const size_t axWidgetNameLen = qstrlen(axWidgetName);
72 return qstrncmp(o->metaObject()->className(), axWidgetName, axWidgetNameLen) == 0;
73}
74#endif
75
76/* Dynamic boolean property indicating object was created by the factory
77 * for the form editor. */
78
79static const char formEditorDynamicProperty[] = "_q_formEditorObject";
80
81namespace qdesigner_internal {
82
83#if QT_CONFIG(abstractbutton)
84
86{
87public:
89
90protected:
92};
93
94#endif
95
96#if QT_CONFIG(itemviews)
97
99{
100public:
102
103 QRect visualRect(const QModelIndex &) const override
104 {
105 return QRect(QPoint(), QSize(10, 10));
106 }
107
109
110 QModelIndex indexAt(const QPoint &) const override
111 {
112 return {};
113 }
114
115protected:
117 {
118 return {};
119 }
120
121 int horizontalOffset() const override { return 0; }
122 int verticalOffset() const override { return 0; }
123
124 bool isIndexHidden(const QModelIndex &) const override { return false; }
125
127
129 {
130 return QRegion(QRect(QPoint(), QSize(10, 10)));
131 }
132};
133
134#endif // QT_CONFIG(itemviews)
135
136// A friendly SpinBox that grants access to its QLineEdit
138public:
139 friend class WidgetFactory;
140};
141
142// An event filter for form-combo boxes that prevents the embedded line edit
143// from getting edit focus (and drawing blue artifacts/lines). It catches the
144// ChildPolished event when the "editable" property flips to true and the
145// QLineEdit is created and turns off the LineEdit's focus policy.
146
147class ComboEventFilter : public QObject {
148public:
149 explicit ComboEventFilter(QComboBox *parent) : QObject(parent) {}
150 bool eventFilter(QObject *watched, QEvent *event) override;
151};
152
153bool ComboEventFilter::eventFilter(QObject *watched, QEvent *event)
154{
155 if (event->type() == QEvent::ChildPolished) {
156 QComboBox *cb = static_cast<QComboBox*>(watched);
157 if (QLineEdit *le = cb->lineEdit()) {
158 le->setFocusPolicy(Qt::NoFocus);
159 le->setCursor(Qt::ArrowCursor);
160 }
161 }
162 return QObject::eventFilter(watched, event);
163}
164
165/* Watch out for QWizards changing their pages and make sure that not some
166 * selected widget becomes invisible on a hidden page (causing the selection
167 * handles to shine through). Select the wizard in that case in analogy to
168 * the QTabWidget event filters, etc. */
169
172public:
174
175public slots:
177};
178
179WizardPageChangeWatcher::WizardPageChangeWatcher(QWizard *parent) :
180 QObject(parent)
181{
182 connect(parent, &QWizard::currentIdChanged, this, &WizardPageChangeWatcher::pageChanged);
183}
184
185void WizardPageChangeWatcher::pageChanged()
186{
187 /* Use a bit more conservative approach than that for the QTabWidget,
188 * change the selection only if a selected child becomes invisible by
189 * changing the page. */
190 QWizard *wizard = static_cast<QWizard *>(parent());
191 QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(wizard);
192 if (!fw)
193 return;
194 QDesignerFormWindowCursorInterface *cursor = fw->cursor();
195 const int selCount = cursor->selectedWidgetCount();
196 for (int i = 0; i < selCount; i++) {
197 if (!cursor->selectedWidget(i)->isVisible()) {
198 fw->clearSelection(false);
199 fw->selectWidget(wizard, true);
200 break;
201 }
202 }
203}
204
205// ---------------- WidgetFactory
206const char *WidgetFactory::disableStyleCustomPaintingPropertyC = "_q_custom_style_disabled";
207
215
216WidgetFactory::~WidgetFactory() = default;
217
224
233
234// Convencience to create non-widget objects. Returns 0 if unknown
236{
237 if (className.isEmpty()) {
238 qWarning("** WARNING %s called with an empty class name", Q_FUNC_INFO);
239 return nullptr;
240 }
241 if (className == "QAction"_L1)
242 return new QAction(parent);
243 if (className == "QButtonGroup"_L1)
244 return new QButtonGroup(parent);
245 return nullptr;
246}
247
248// Check for mismatched class names in plugins, which is hard to track.
249static bool classNameMatches(const QObject *created, const QString &className)
250{
251#ifdef Q_OS_WIN
252 // Perform literal comparison first for QAxWidget, for which a meta object hack is in effect.
253 if (isAxWidget(created))
254 return true;
255#endif
256 const char *createdClassNameC = created->metaObject()->className();
257 const QByteArray classNameB = className.toUtf8();
258 const char *classNameC = classNameB.constData();
259 if (qstrcmp(createdClassNameC, classNameC) == 0 || created->inherits(classNameC))
260 return true;
261 // QTBUG-53984: QWebEngineView property dummy
262 if (classNameB == "QWebEngineView" && qstrcmp(createdClassNameC, "fake::QWebEngineView") == 0)
263 return true;
264 return false;
265}
266
268{
269 *creationError = false;
270
271 auto *factory = m_customFactory.value(className, nullptr);
272 if (factory == nullptr)
273 return nullptr;
274
276 // shouldn't happen
277 if (!rc) {
278 *creationError = true;
279 designerWarning(tr("The custom widget factory registered for widgets of class %1 returned 0.").arg(className));
280 return nullptr;
281 }
282 // Figure out the base class unless it is known
286 const int widgetInfoIndex = wdb->indexOfObject(rc, false);
287 if (widgetInfoIndex != -1) {
290 // If we hit on a 'Q3DesignerXXWidget' that claims to be a 'Q3XXWidget', step
291 // over.
292 if (mo && mo->className() == className)
293 mo = mo->superClass();
294 while (mo != nullptr) {
295 if (core()->widgetDataBase()->indexOfClassName(mo->className()) != -1) {
297 break;
298 }
299 mo = mo->superClass();
300 }
301 }
303 }
304 }
305 // Since a language plugin may lie about its names, like Qt Jambi
306 // does, return immediately here...
309 if (lang)
310 return rc;
311
312 // Check for mismatched class names which is hard to track.
314 designerWarning(tr("A class name mismatch occurred when creating a widget using the custom widget factory registered for widgets of class %1."
315 " It returned a widget of class %2.")
317 }
318 return rc;
319}
320
321
323{
324 if (widgetName.isEmpty()) {
325 qWarning("** WARNING %s called with an empty class name", Q_FUNC_INFO);
326 return nullptr;
327 }
328 // Preview or for form window?
330 if (! fw)
332
333 QWidget *w = nullptr;
334 do {
335 // 1) custom. If there is an explicit failure(factory wants to indicate something is wrong),
336 // return 0, do not try to find fallback, which might be worse in the case of Q3 widget.
339 if (w)
340 break;
342 return nullptr;
343
344 // 2) Special widgets
345 if (widgetName == "Line"_L1) {
346 w = new Line(parentWidget);
347#if QT_CONFIG(abstractbutton)
348 } else if (widgetName == u"QAbstractButton") {
350#endif
351#if QT_CONFIG(itemviews)
352 } else if (widgetName == u"QAbstractItemView") {
354#endif
355 } else if (widgetName == "QDockWidget"_L1) {
357 } else if (widgetName == "QMenuBar"_L1) {
359 } else if (widgetName == "QMenu"_L1) {
361 } else if (widgetName == "Spacer"_L1) {
362 w = new Spacer(parentWidget);
363 } else if (widgetName == "QLayoutWidget"_L1) {
365 } else if (widgetName == "QDialog"_L1) {
366 if (fw) {
368 } else {
369 w = new QDialog(parentWidget);
370 }
371 } else if (widgetName == "QWidget"_L1) {
372 /* We want a 'QDesignerWidget' that draws a grid only for widget
373 * forms and container extension pages (not for preview and not
374 * for normal QWidget children on forms (legacy) */
375 if (fw && parentWidget) {
378 } else {
379 if (parentWidget == fw->formContainer())
381 }
382 }
383 if (!w)
384 w = new QWidget(parentWidget);
385 }
386 if (w)
387 break;
388
389 // 3) table
391 if (newWidget) {
392 Q_ASSERT(w == nullptr);
393 w = newWidget;
394 break;
395 }
396
397 // 4) fallBack
398 const QString fallBackBaseClass = "QWidget"_L1;
401 if (item == nullptr) {
402 // Emergency: Create, derived from QWidget
404 includeFile += ".h"_L1;
406 includeFile, true, true);
407 Q_ASSERT(item);
408 }
410 if (baseClass.isEmpty()) {
411 // Currently happens in the case of Q3-Support widgets
413 }
416 return promotedWidget; // Do not initialize twice.
417 }
418 } while (false);
419
420 Q_ASSERT(w != nullptr);
421 if (m_currentStyle)
424 if (fw) { // form editor initialization
425 initialize(w);
426 } else { // preview-only initialization
428 }
429 return w;
430}
431
433{
434 if (o == nullptr)
435 return QString();
436
437 const char *className = o->metaObject()->className();
438 if (!o->isWidgetType())
440 const QWidget *w = static_cast<const QWidget*>(o);
441 // check promoted before designer special
444 return customClassName;
445 if (qobject_cast<const QDesignerMenuBar*>(w))
446 return u"QMenuBar"_s;
447 if (qobject_cast<const QDesignerMenu*>(w))
448 return u"QMenu"_s;
450 return u"QDockWidget"_s;
451 if (qobject_cast<const QDesignerDialog*>(w))
452 return u"QDialog"_s;
453 if (qobject_cast<const QDesignerWidget*>(w))
454 return u"QWidget"_s;
455#ifdef Q_OS_WIN
456 if (isAxWidget(w))
457 return u"QAxWidget"_s;
458#endif
460}
461
463{
464 switch (type) {
465 case LayoutInfo::HBox:
466 return new QHBoxLayout(parentWidget);
467 case LayoutInfo::VBox:
468 return new QVBoxLayout(parentWidget);
469 case LayoutInfo::Grid:
470 return new QGridLayout(parentWidget);
471 case LayoutInfo::Form:
472 return new QFormLayout(parentWidget);
473 default:
474 Q_ASSERT(0);
475 break;
476 }
477 return nullptr;
478}
479
480
481/*!
482 \internal
483 Creates a layout on the widget \a widget of the type \a type
484 which can be \c HBox, \c VBox or \c Grid.
485*/
486
488{
490
491 if (parentLayout == nullptr) {
493 if (page) {
494 widget = page;
495 } else {
496 const QString msg =
497 tr("The current page of the container '%1' (%2) could not be determined while creating a layout."
498 "This indicates an inconsistency in the ui-file, probably a layout being constructed on a container widget.")
501 }
502 }
503
504 Q_ASSERT(metaDataBase->item(widget) != nullptr); // ensure the widget is managed
505
506 if (parentLayout == nullptr && metaDataBase->item(widget->layout()) == nullptr) {
508 }
509
510 QWidget *parentWidget = parentLayout != nullptr ? nullptr : widget;
511
513 metaDataBase->add(layout); // add the layout in the MetaDataBase
514
516
517 if (sheet) {
518 sheet->setChanged(sheet->indexOf(u"objectName"_s), true);
519 if (widget->inherits("QLayoutWidget")) {
520 sheet->setProperty(sheet->indexOf(u"leftMargin"_s), 0);
521 sheet->setProperty(sheet->indexOf(u"topMargin"_s), 0);
522 sheet->setProperty(sheet->indexOf(u"rightMargin"_s), 0);
523 sheet->setProperty(sheet->indexOf(u"bottomMargin"_s), 0);
524 }
525
526 const int index = sheet->indexOf(u"alignment"_s);
527 if (index != -1)
528 sheet->setChanged(index, true);
529 }
530
531 if (metaDataBase->item(widget->layout()) == nullptr) {
532 Q_ASSERT(layout->parent() == nullptr);
534 if (!box) { // we support only unmanaged box layouts
535 const QString msg = tr("Attempt to add a layout to a widget '%1' (%2) which already has an unmanaged layout of type %3.\n"
536 "This indicates an inconsistency in the ui-file.").
539 return nullptr;
540 }
542 }
543
544 return layout;
545}
546
547/*!
548 \internal
549 Returns the widget into which children should be inserted when \a
550 w is a container known to designer.
551
552 Usually, it is \a w itself, but there are exceptions (for example, a
553 tabwidget is known to designer as a container, but the child
554 widgets should be inserted into the current page of the
555 tabwidget. In this case, the current page of
556 the tabwidget would be returned.)
557 */
565
566/*!
567 \internal
568 Returns the actual designer widget of the container \a w. This is
569 normally \a w itself, but it might be a parent or grand parent of \a w
570 (for example, when working with a tabwidget and \a w is the container which
571 contains and layouts children, but the actual widget known to
572 designer is the tabwidget which is the parent of \a w. In this case,
573 the tabwidget would be returned.)
574*/
575
577{
578 // ### cleanup
579 if (!w)
580 return nullptr;
581 if (w->parentWidget() && w->parentWidget()->parentWidget() &&
584 return w->parentWidget()->parentWidget()->parentWidget();
585
586 while (w != nullptr) {
587 if (core()->widgetDataBase()->isContainer(w) ||
589 return w;
590
591 w = w->parentWidget();
592 }
593
594 return w;
595}
596
601
602// Necessary initializations for form editor/preview objects
604{
605 // Apply style
606 if (m_currentStyle)
608}
609
610// Necessary initializations for preview objects
612{
613
615 QStackedWidgetPreviewEventFilter::install(stackedWidget); // Add browse button only.
616 return;
617 }
618}
619
620// Necessary initializations for form editor objects
622{
623 // Indicate that this is a form object (for QDesignerFormWindowInterface::findFormWindow)
626 if (!sheet)
627 return;
628
629 sheet->setChanged(sheet->indexOf(u"objectName"_s), true);
630
631 if (!object->isWidgetType()) {
633 sheet->setChanged(sheet->indexOf(u"text"_s), true);
634 return;
635 }
636
637 QWidget *widget = static_cast<QWidget*>(object);
638 const bool isMenu = qobject_cast<QMenu*>(widget);
639 const bool isMenuBar = !isMenu && qobject_cast<QMenuBar*>(widget);
640
643
644 if (!isMenu)
645 sheet->setChanged(sheet->indexOf(u"geometry"_s), true);
646
647 if (qobject_cast<Spacer*>(widget)) {
648 sheet->setChanged(sheet->indexOf(u"spacerName"_s), true);
649 return;
650 }
651
652 const int o = sheet->indexOf(u"orientation"_s);
653 if (o != -1 && widget->inherits("QSplitter"))
654 sheet->setChanged(o, true);
655
658 sheet->setVisible(sheet->indexOf(u"windowTitle"_s), true);
659 toolBar->setFloatable(false); // prevent toolbars from being dragged off
660 return;
661 }
662
664 sheet->setVisible(sheet->indexOf(u"windowTitle"_s), true);
665 sheet->setVisible(sheet->indexOf(u"windowIcon"_s), true);
666 return;
667 }
668
669 if (isMenu) {
670 sheet->setChanged(sheet->indexOf(u"title"_s), true);
671 return;
672 }
673 // helpers
676 return;
677 }
680 return;
681 }
684 return;
685 }
686 // Prevent embedded line edits from getting focus
688 if (QLineEdit *lineEdit = static_cast<FriendlySpinBox*>(asb)->lineEdit())
690 return;
691 }
694 fcb->lineEdit()->setFocusPolicy(Qt::NoFocus); // Always present
695 return;
696 }
698 return;
699 }
700 if (QWizard *wz = qobject_cast<QWizard *>(widget)) {
702 Q_UNUSED(pw);
703 }
704}
705
706static inline QString classNameOfStyle(const QStyle *s)
707{
708 return QLatin1StringView(s->metaObject()->className());
709}
710
712{
713 return classNameOfStyle(style());
714}
715
716static inline bool isApplicationStyle(const QString &styleName)
717{
718 return styleName.isEmpty() || styleName == classNameOfStyle(qApp->style());
719}
720
725
727{
729}
730
732{
734 return qApp->style();
735
737 if (it == m_styleCache.end()) {
739 if (!style) {
740 const QString msg = tr("Cannot create style '%1'.").arg(styleName);
742 return nullptr;
743 }
745 }
746 return it.value();
747}
748
754
756{
757 if (!style)
758 return;
760 if (widget->style() == style && widget->palette() == standardPalette)
761 return;
762
766 for (auto *w : lst)
767 w->setStyle(style);
768}
769
770// Check for 'interactor' click on a tab bar,
771// which can appear within a QTabWidget or as a standalone widget.
772
773static bool isTabBarInteractor(const QTabBar *tabBar)
774{
775 // Tabbar embedded in Q(Designer)TabWidget, ie, normal tab widget case
776 if (qobject_cast<const QTabWidget*>(tabBar->parentWidget()))
777 return true;
778
779 // Standalone tab bar on the form. Return true for tab rect areas
780 // only to allow the user to select the tab bar by clicking outside the actual tabs.
781 const int count = tabBar->count();
782 if (count == 0)
783 return false;
784
785 // click into current tab: No Interaction
786 const int currentIndex = tabBar->currentIndex();
787 const QPoint pos = tabBar->mapFromGlobal(QCursor::pos());
788 if (tabBar->tabRect(currentIndex).contains(pos))
789 return false;
790
791 // click outside: No Interaction
792 const QRect geometry = QRect(QPoint(0, 0), tabBar->size());
793 if (!geometry.contains(pos))
794 return false;
795 // click into another tab: Let's interact, switch tabs.
796 for (int i = 0; i < count; i++)
797 if (tabBar->tabRect(i).contains(pos))
798 return true;
799 return false;
800}
801
802static bool isPassiveInteractorHelper(const QWidget *widget)
803{
804 if (qobject_cast<const QMenuBar*>(widget)
805#if QT_CONFIG(sizegrip)
806 || qobject_cast<const QSizeGrip*>(widget)
807#endif
808 || qobject_cast<const QMdiSubWindow*>(widget)
809 || qobject_cast<const QToolBar*>(widget)) {
810 return true;
811 }
812
813 if (qobject_cast<const QAbstractButton*>(widget)) {
814 auto parent = widget->parent();
815 if (qobject_cast<const QTabBar*>(parent) || qobject_cast<const QToolBox*>(parent))
816 return true;
817 } else if (const auto tabBar = qobject_cast<const QTabBar*>(widget)) {
818 if (isTabBarInteractor(tabBar))
819 return true;
820 } else if (qobject_cast<const QScrollBar*>(widget)) {
821 // A scroll bar is an interactor on a QAbstractScrollArea only.
822 if (auto parent = widget->parentWidget()) {
823 const QString &objectName = parent->objectName();
824 if (objectName == "qt_scrollarea_vcontainer"_L1 || objectName == "qt_scrollarea_hcontainer"_L1)
825 return true;
826 }
827 } else if (qstrcmp(widget->metaObject()->className(), "QDockWidgetTitle") == 0) {
828 return true;
829 } else if (qstrcmp(widget->metaObject()->className(), "QWorkspaceTitleBar") == 0) {
830 return true;
831 }
832 const QString &name = widget->objectName();
833 return name.startsWith("__qt__passive_"_L1)
834 || name == "qt_qmainwindow_extended_splitter"_L1;
835}
836
838{
839 static bool lastWasAPassiveInteractor = false;
841
844
845 // if a popup is open, we have to make sure that this one is closed,
846 // else X might do funny things
847 if (QApplication::activePopupWidget() || widget == nullptr)
848 return true;
849
852
854}
855
860
865
867{
870}
871
876} // namespace qdesigner_internal
877
878QT_END_NAMESPACE
879
880#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[]