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