1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Designer of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include "widgetfactory_p.h" |
30 | #include "widgetdatabase_p.h" |
31 | #include "metadatabase_p.h" |
32 | #include "qlayout_widget_p.h" |
33 | #include "qdesigner_widget_p.h" |
34 | #include "qdesigner_tabwidget_p.h" |
35 | #include "qdesigner_toolbox_p.h" |
36 | #include "qdesigner_stackedbox_p.h" |
37 | #include "qdesigner_toolbar_p.h" |
38 | #include "qdesigner_menubar_p.h" |
39 | #include "qdesigner_menu_p.h" |
40 | #include "qdesigner_dockwidget_p.h" |
41 | #include "qdesigner_utils_p.h" |
42 | #include "formwindowbase_p.h" |
43 | |
44 | // shared |
45 | #include "layoutinfo_p.h" |
46 | #include "spacer_widget_p.h" |
47 | #include "layout_p.h" |
48 | #include "abstractintrospection_p.h" |
49 | |
50 | // sdk |
51 | #include <QtDesigner/abstractformeditor.h> |
52 | #include <QtDesigner/container.h> |
53 | #include <QtDesigner/qextensionmanager.h> |
54 | #include <QtDesigner/propertysheet.h> |
55 | #include <QtDesigner/abstractlanguage.h> |
56 | #include <QtDesigner/abstractformwindowmanager.h> |
57 | #include <QtDesigner/abstractformwindowcursor.h> |
58 | |
59 | #include <QtUiPlugin/customwidget.h> |
60 | |
61 | #include <QtWidgets/QtWidgets> |
62 | #include <QtWidgets/qscrollbar.h> |
63 | #include <QtWidgets/qfontcombobox.h> |
64 | #include <QtWidgets/qabstractspinbox.h> |
65 | #include <QtWidgets/qlineedit.h> |
66 | #include <QtWidgets/qbuttongroup.h> |
67 | #include <QtWidgets/qstyle.h> |
68 | #include <QtWidgets/qstylefactory.h> |
69 | #include <QtWidgets/qwizard.h> |
70 | #include <QtCore/qdebug.h> |
71 | #include <QtCore/qmetaobject.h> |
72 | #include <QtCore/qpointer.h> |
73 | |
74 | QT_BEGIN_NAMESPACE |
75 | |
76 | #ifdef Q_OS_WIN |
77 | static inline bool isAxWidget(const QObject *o) |
78 | { |
79 | // Is it one of QDesignerAxWidget/QDesignerAxPluginWidget? |
80 | static const char *axWidgetName = "QDesignerAx" ; |
81 | static const unsigned axWidgetNameLen = qstrlen(axWidgetName); |
82 | return qstrncmp(o->metaObject()->className(), axWidgetName, axWidgetNameLen) == 0; |
83 | } |
84 | #endif |
85 | |
86 | /* Dynamic boolean property indicating object was created by the factory |
87 | * for the form editor. */ |
88 | |
89 | static const char *formEditorDynamicProperty = "_q_formEditorObject" ; |
90 | |
91 | namespace qdesigner_internal { |
92 | |
93 | // A friendly SpinBox that grants access to its QLineEdit |
94 | class FriendlySpinBox : public QAbstractSpinBox { |
95 | public: |
96 | friend class WidgetFactory; |
97 | }; |
98 | |
99 | // An event filter for form-combo boxes that prevents the embedded line edit |
100 | // from getting edit focus (and drawing blue artifacts/lines). It catches the |
101 | // ChildPolished event when the "editable" property flips to true and the |
102 | // QLineEdit is created and turns off the LineEdit's focus policy. |
103 | |
104 | class ComboEventFilter : public QObject { |
105 | public: |
106 | explicit ComboEventFilter(QComboBox *parent) : QObject(parent) {} |
107 | bool eventFilter(QObject *watched, QEvent *event) override; |
108 | }; |
109 | |
110 | bool ComboEventFilter::eventFilter(QObject *watched, QEvent *event) |
111 | { |
112 | if (event->type() == QEvent::ChildPolished) { |
113 | QComboBox *cb = static_cast<QComboBox*>(watched); |
114 | if (QLineEdit *le = cb->lineEdit()) { |
115 | le->setFocusPolicy(Qt::NoFocus); |
116 | le->setCursor(Qt::ArrowCursor); |
117 | } |
118 | } |
119 | return QObject::eventFilter(watched, event); |
120 | } |
121 | |
122 | /* Watch out for QWizards changing their pages and make sure that not some |
123 | * selected widget becomes invisible on a hidden page (causing the selection |
124 | * handles to shine through). Select the wizard in that case in analogy to |
125 | * the QTabWidget event filters, etc. */ |
126 | |
127 | class WizardPageChangeWatcher : public QObject { |
128 | Q_OBJECT |
129 | public: |
130 | explicit WizardPageChangeWatcher(QWizard *parent); |
131 | |
132 | public slots: |
133 | void pageChanged(); |
134 | }; |
135 | |
136 | WizardPageChangeWatcher::WizardPageChangeWatcher(QWizard *parent) : |
137 | QObject(parent) |
138 | { |
139 | connect(sender: parent, signal: &QWizard::currentIdChanged, receiver: this, slot: &WizardPageChangeWatcher::pageChanged); |
140 | } |
141 | |
142 | void WizardPageChangeWatcher::pageChanged() |
143 | { |
144 | /* Use a bit more conservative approach than that for the QTabWidget, |
145 | * change the selection only if a selected child becomes invisible by |
146 | * changing the page. */ |
147 | QWizard *wizard = static_cast<QWizard *>(parent()); |
148 | QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w: wizard); |
149 | if (!fw) |
150 | return; |
151 | QDesignerFormWindowCursorInterface *cursor = fw->cursor(); |
152 | const int selCount = cursor->selectedWidgetCount(); |
153 | for (int i = 0; i < selCount; i++) { |
154 | if (!cursor->selectedWidget(index: i)->isVisible()) { |
155 | fw->clearSelection(changePropertyDisplay: false); |
156 | fw->selectWidget(w: wizard, select: true); |
157 | break; |
158 | } |
159 | } |
160 | } |
161 | |
162 | // ---------------- WidgetFactory::Strings |
163 | WidgetFactory::Strings::Strings() : |
164 | m_alignment(QStringLiteral("alignment" )), |
165 | m_bottomMargin(QStringLiteral("bottomMargin" )), |
166 | m_geometry(QStringLiteral("geometry" )), |
167 | m_leftMargin(QStringLiteral("leftMargin" )), |
168 | m_line(QStringLiteral("Line" )), |
169 | m_objectName(QStringLiteral("objectName" )), |
170 | m_spacerName(QStringLiteral("spacerName" )), |
171 | m_orientation(QStringLiteral("orientation" )), |
172 | m_qAction(QStringLiteral("QAction" )), |
173 | m_qButtonGroup(QStringLiteral("QButtonGroup" )), |
174 | m_qAxWidget(QStringLiteral("QAxWidget" )), |
175 | m_qDialog(QStringLiteral("QDialog" )), |
176 | m_qDockWidget(QStringLiteral("QDockWidget" )), |
177 | m_qLayoutWidget(QStringLiteral("QLayoutWidget" )), |
178 | m_qMenu(QStringLiteral("QMenu" )), |
179 | m_qMenuBar(QStringLiteral("QMenuBar" )), |
180 | m_qWidget(QStringLiteral("QWidget" )), |
181 | m_rightMargin(QStringLiteral("rightMargin" )), |
182 | m_sizeHint(QStringLiteral("sizeHint" )), |
183 | m_spacer(QStringLiteral("Spacer" )), |
184 | m_text(QStringLiteral("text" )), |
185 | m_title(QStringLiteral("title" )), |
186 | m_topMargin(QStringLiteral("topMargin" )), |
187 | m_windowIcon(QStringLiteral("windowIcon" )), |
188 | m_windowTitle(QStringLiteral("windowTitle" )) |
189 | { |
190 | } |
191 | // ---------------- WidgetFactory |
192 | const char *WidgetFactory::disableStyleCustomPaintingPropertyC = "_q_custom_style_disabled" ; |
193 | |
194 | WidgetFactory::WidgetFactory(QDesignerFormEditorInterface *core, QObject *parent) |
195 | : QDesignerWidgetFactoryInterface(parent), |
196 | m_core(core), |
197 | m_formWindow(nullptr), |
198 | m_currentStyle(nullptr) |
199 | { |
200 | } |
201 | |
202 | WidgetFactory::~WidgetFactory() = default; |
203 | |
204 | QDesignerFormWindowInterface *WidgetFactory::currentFormWindow(QDesignerFormWindowInterface *fw) |
205 | { |
206 | QDesignerFormWindowInterface *was = m_formWindow; |
207 | m_formWindow = fw; |
208 | return was; |
209 | } |
210 | |
211 | void WidgetFactory::loadPlugins() |
212 | { |
213 | m_customFactory.clear(); |
214 | |
215 | const auto &lst = m_core->pluginManager()->registeredCustomWidgets(); |
216 | for (QDesignerCustomWidgetInterface *c : lst) |
217 | m_customFactory.insert(key: c->name(), value: c); |
218 | } |
219 | |
220 | // Convencience to create non-widget objects. Returns 0 if unknown |
221 | QObject* WidgetFactory::createObject(const QString &className, QObject* parent) const |
222 | { |
223 | if (className.isEmpty()) { |
224 | qWarning(msg: "** WARNING %s called with an empty class name" , Q_FUNC_INFO); |
225 | return nullptr; |
226 | } |
227 | if (className == m_strings.m_qAction) |
228 | return new QAction(parent); |
229 | if (className == m_strings.m_qButtonGroup) |
230 | return new QButtonGroup(parent); |
231 | return nullptr; |
232 | } |
233 | |
234 | // Check for mismatched class names in plugins, which is hard to track. |
235 | static bool classNameMatches(const QObject *created, const QString &className) |
236 | { |
237 | #ifdef Q_OS_WIN |
238 | // Perform literal comparison first for QAxWidget, for which a meta object hack is in effect. |
239 | if (isAxWidget(created)) |
240 | return true; |
241 | #endif |
242 | const char *createdClassNameC = created->metaObject()->className(); |
243 | const QByteArray classNameB = className.toUtf8(); |
244 | const char *classNameC = classNameB.constData(); |
245 | if (qstrcmp(str1: createdClassNameC, str2: classNameC) == 0 || created->inherits(classname: classNameC)) |
246 | return true; |
247 | // QTBUG-53984: QWebEngineView property dummy |
248 | if (classNameB == "QWebEngineView" && qstrcmp(str1: createdClassNameC, str2: "fake::QWebEngineView" ) == 0) |
249 | return true; |
250 | return false; |
251 | } |
252 | |
253 | QWidget* WidgetFactory::createCustomWidget(const QString &className, QWidget *parentWidget, bool *creationError) const |
254 | { |
255 | *creationError = false; |
256 | CustomWidgetFactoryMap::const_iterator it = m_customFactory.constFind(key: className); |
257 | if (it == m_customFactory.constEnd()) |
258 | return nullptr; |
259 | |
260 | QDesignerCustomWidgetInterface *factory = it.value(); |
261 | QWidget *rc = factory->createWidget(parent: parentWidget); |
262 | // shouldn't happen |
263 | if (!rc) { |
264 | *creationError = true; |
265 | designerWarning(message: tr(s: "The custom widget factory registered for widgets of class %1 returned 0." ).arg(a: className)); |
266 | return nullptr; |
267 | } |
268 | // Figure out the base class unless it is known |
269 | static QSet<QString> knownCustomClasses; |
270 | if (!knownCustomClasses.contains(value: className)) { |
271 | QDesignerWidgetDataBaseInterface *wdb = m_core->widgetDataBase(); |
272 | const int widgetInfoIndex = wdb->indexOfObject(object: rc, resolveName: false); |
273 | if (widgetInfoIndex != -1) { |
274 | if (wdb->item(index: widgetInfoIndex)->extends().isEmpty()) { |
275 | const QDesignerMetaObjectInterface *mo = core()->introspection()->metaObject(object: rc)->superClass(); |
276 | // If we hit on a 'Q3DesignerXXWidget' that claims to be a 'Q3XXWidget', step |
277 | // over. |
278 | if (mo && mo->className() == className) |
279 | mo = mo->superClass(); |
280 | while (mo != nullptr) { |
281 | if (core()->widgetDataBase()->indexOfClassName(className: mo->className()) != -1) { |
282 | wdb->item(index: widgetInfoIndex)->setExtends(mo->className()); |
283 | break; |
284 | } |
285 | mo = mo->superClass(); |
286 | } |
287 | } |
288 | knownCustomClasses.insert(value: className); |
289 | } |
290 | } |
291 | // Since a language plugin may lie about its names, like Qt Jambi |
292 | // does, return immediately here... |
293 | QDesignerLanguageExtension *lang = |
294 | qt_extension<QDesignerLanguageExtension *>(manager: m_core->extensionManager(), object: m_core); |
295 | if (lang) |
296 | return rc; |
297 | |
298 | // Check for mismatched class names which is hard to track. |
299 | if (!classNameMatches(created: rc, className)) { |
300 | designerWarning(message: tr(s: "A class name mismatch occurred when creating a widget using the custom widget factory registered for widgets of class %1." |
301 | " It returned a widget of class %2." ) |
302 | .arg(args: className, args: QString::fromUtf8(str: rc->metaObject()->className()))); |
303 | } |
304 | return rc; |
305 | } |
306 | |
307 | |
308 | QWidget *WidgetFactory::createWidget(const QString &widgetName, QWidget *parentWidget) const |
309 | { |
310 | if (widgetName.isEmpty()) { |
311 | qWarning(msg: "** WARNING %s called with an empty class name" , Q_FUNC_INFO); |
312 | return nullptr; |
313 | } |
314 | // Preview or for form window? |
315 | QDesignerFormWindowInterface *fw = m_formWindow; |
316 | if (! fw) |
317 | fw = QDesignerFormWindowInterface::findFormWindow(w: parentWidget); |
318 | |
319 | QWidget *w = nullptr; |
320 | do { |
321 | // 1) custom. If there is an explicit failure(factory wants to indicate something is wrong), |
322 | // return 0, do not try to find fallback, which might be worse in the case of Q3 widget. |
323 | bool customWidgetCreationError; |
324 | w = createCustomWidget(className: widgetName, parentWidget, creationError: &customWidgetCreationError); |
325 | if (w) |
326 | break; |
327 | if (customWidgetCreationError) |
328 | return nullptr; |
329 | |
330 | // 2) Special widgets |
331 | if (widgetName == m_strings.m_line) { |
332 | w = new Line(parentWidget); |
333 | } else if (widgetName == m_strings.m_qDockWidget) { |
334 | w = new QDesignerDockWidget(parentWidget); |
335 | } else if (widgetName == m_strings.m_qMenuBar) { |
336 | w = new QDesignerMenuBar(parentWidget); |
337 | } else if (widgetName == m_strings.m_qMenu) { |
338 | w = new QDesignerMenu(parentWidget); |
339 | } else if (widgetName == m_strings.m_spacer) { |
340 | w = new Spacer(parentWidget); |
341 | } else if (widgetName == m_strings.m_qLayoutWidget) { |
342 | w = fw ? new QLayoutWidget(fw, parentWidget) : new QWidget(parentWidget); |
343 | } else if (widgetName == m_strings.m_qDialog) { |
344 | if (fw) { |
345 | w = new QDesignerDialog(fw, parentWidget); |
346 | } else { |
347 | w = new QDialog(parentWidget); |
348 | } |
349 | } else if (widgetName == m_strings.m_qWidget) { |
350 | /* We want a 'QDesignerWidget' that draws a grid only for widget |
351 | * forms and container extension pages (not for preview and not |
352 | * for normal QWidget children on forms (legacy) */ |
353 | if (fw && parentWidget) { |
354 | if (qt_extension<QDesignerContainerExtension*>(manager: m_core->extensionManager(), object: parentWidget)) { |
355 | w = new QDesignerWidget(fw, parentWidget); |
356 | } else { |
357 | if (parentWidget == fw->formContainer()) |
358 | w = new QDesignerWidget(fw, parentWidget); |
359 | } |
360 | } |
361 | if (!w) |
362 | w = new QWidget(parentWidget); |
363 | } |
364 | if (w) |
365 | break; |
366 | |
367 | // 3) table |
368 | const QByteArray widgetNameBA = widgetName.toUtf8(); |
369 | const char *widgetNameC = widgetNameBA.constData(); |
370 | |
371 | if (w) { // symmetry for macro |
372 | } |
373 | |
374 | #define DECLARE_LAYOUT(L, C) |
375 | #define DECLARE_COMPAT_WIDGET(W, C) /*DECLARE_WIDGET(W, C)*/ |
376 | #define DECLARE_WIDGET(W, C) else if (!qstrcmp(widgetNameC, #W)) { Q_ASSERT(w == 0); w = new W(parentWidget); } |
377 | #define DECLARE_WIDGET_1(W, C) else if (!qstrcmp(widgetNameC, #W)) { Q_ASSERT(w == 0); w = new W(0, parentWidget); } |
378 | |
379 | #include <widgets.table> |
380 | |
381 | #undef DECLARE_COMPAT_WIDGET |
382 | #undef DECLARE_LAYOUT |
383 | #undef DECLARE_WIDGET |
384 | #undef DECLARE_WIDGET_1 |
385 | |
386 | if (w) |
387 | break; |
388 | // 4) fallBack |
389 | const QString fallBackBaseClass = m_strings.m_qWidget; |
390 | QDesignerWidgetDataBaseInterface *db = core()->widgetDataBase(); |
391 | QDesignerWidgetDataBaseItemInterface *item = db->item(index: db->indexOfClassName(className: widgetName)); |
392 | if (item == nullptr) { |
393 | // Emergency: Create, derived from QWidget |
394 | QString includeFile = widgetName.toLower(); |
395 | includeFile += QStringLiteral(".h" ); |
396 | item = appendDerived(db,className: widgetName, group: tr(s: "%1 Widget" ).arg(a: widgetName),baseClassName: fallBackBaseClass, |
397 | includeFile, promoted: true, custom: true); |
398 | Q_ASSERT(item); |
399 | } |
400 | QString baseClass = item->extends(); |
401 | if (baseClass.isEmpty()) { |
402 | // Currently happens in the case of Q3-Support widgets |
403 | baseClass =fallBackBaseClass; |
404 | } |
405 | if (QWidget *promotedWidget = createWidget(widgetName: baseClass, parentWidget)) { |
406 | promoteWidget(core: core(), widget: promotedWidget, customClassName: widgetName); |
407 | return promotedWidget; // Do not initialize twice. |
408 | } |
409 | } while (false); |
410 | |
411 | Q_ASSERT(w != nullptr); |
412 | if (m_currentStyle) |
413 | w->setStyle(m_currentStyle); |
414 | initializeCommon(object: w); |
415 | if (fw) { // form editor initialization |
416 | initialize(object: w); |
417 | } else { // preview-only initialization |
418 | initializePreview(object: w); |
419 | } |
420 | return w; |
421 | } |
422 | |
423 | QString WidgetFactory::classNameOf(QDesignerFormEditorInterface *c, const QObject* o) |
424 | { |
425 | if (o == nullptr) |
426 | return QString(); |
427 | |
428 | const char *className = o->metaObject()->className(); |
429 | if (!o->isWidgetType()) |
430 | return QLatin1String(className); |
431 | const QWidget *w = static_cast<const QWidget*>(o); |
432 | // check promoted before designer special |
433 | const QString customClassName = promotedCustomClassName(core: c, w: const_cast<QWidget*>(w)); |
434 | if (!customClassName.isEmpty()) |
435 | return customClassName; |
436 | if (qobject_cast<const QDesignerMenuBar*>(object: w)) |
437 | return QStringLiteral("QMenuBar" ); |
438 | if (qobject_cast<const QDesignerMenu*>(object: w)) |
439 | return QStringLiteral("QMenu" ); |
440 | if (qobject_cast<const QDesignerDockWidget*>(object: w)) |
441 | return QStringLiteral("QDockWidget" ); |
442 | if (qobject_cast<const QDesignerDialog*>(object: w)) |
443 | return QStringLiteral("QDialog" ); |
444 | if (qobject_cast<const QDesignerWidget*>(object: w)) |
445 | return QStringLiteral("QWidget" ); |
446 | #ifdef Q_OS_WIN |
447 | if (isAxWidget(w)) |
448 | return QStringLiteral("QAxWidget" ); |
449 | #endif |
450 | return QLatin1String(className); |
451 | } |
452 | |
453 | QLayout *WidgetFactory::createUnmanagedLayout(QWidget *parentWidget, int type) |
454 | { |
455 | switch (type) { |
456 | case LayoutInfo::HBox: |
457 | return new QHBoxLayout(parentWidget); |
458 | case LayoutInfo::VBox: |
459 | return new QVBoxLayout(parentWidget); |
460 | case LayoutInfo::Grid: |
461 | return new QGridLayout(parentWidget); |
462 | case LayoutInfo::Form: |
463 | return new QFormLayout(parentWidget); |
464 | default: |
465 | Q_ASSERT(0); |
466 | break; |
467 | } |
468 | return nullptr; |
469 | } |
470 | |
471 | |
472 | /*! Creates a layout on the widget \a widget of the type \a type |
473 | which can be \c HBox, \c VBox or \c Grid. |
474 | */ |
475 | |
476 | QLayout *WidgetFactory::createLayout(QWidget *widget, QLayout *parentLayout, int type) const // ### (sizepolicy) |
477 | { |
478 | QDesignerMetaDataBaseInterface *metaDataBase = core()->metaDataBase(); |
479 | |
480 | if (parentLayout == nullptr) { |
481 | QWidget *page = containerOfWidget(widget); |
482 | if (page) { |
483 | widget = page; |
484 | } else { |
485 | const QString msg = |
486 | tr(s: "The current page of the container '%1' (%2) could not be determined while creating a layout." |
487 | "This indicates an inconsistency in the ui-file, probably a layout being constructed on a container widget." ) |
488 | .arg(args: widget->objectName(), args: classNameOf(c: core(), o: widget)); |
489 | designerWarning(message: msg); |
490 | } |
491 | } |
492 | |
493 | Q_ASSERT(metaDataBase->item(widget) != nullptr); // ensure the widget is managed |
494 | |
495 | if (parentLayout == nullptr && metaDataBase->item(object: widget->layout()) == nullptr) { |
496 | parentLayout = widget->layout(); |
497 | } |
498 | |
499 | QWidget *parentWidget = parentLayout != nullptr ? nullptr : widget; |
500 | |
501 | QLayout *layout = createUnmanagedLayout(parentWidget, type); |
502 | metaDataBase->add(object: layout); // add the layout in the MetaDataBase |
503 | |
504 | QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: core()->extensionManager(), object: layout); |
505 | |
506 | if (sheet) { |
507 | sheet->setChanged(index: sheet->indexOf(name: m_strings.m_objectName), changed: true); |
508 | if (widget->inherits(classname: "QLayoutWidget" )) { |
509 | sheet->setProperty(index: sheet->indexOf(name: m_strings.m_leftMargin), value: 0); |
510 | sheet->setProperty(index: sheet->indexOf(name: m_strings.m_topMargin), value: 0); |
511 | sheet->setProperty(index: sheet->indexOf(name: m_strings.m_rightMargin), value: 0); |
512 | sheet->setProperty(index: sheet->indexOf(name: m_strings.m_bottomMargin), value: 0); |
513 | } |
514 | |
515 | const int index = sheet->indexOf(name: m_strings.m_alignment); |
516 | if (index != -1) |
517 | sheet->setChanged(index, changed: true); |
518 | } |
519 | |
520 | if (metaDataBase->item(object: widget->layout()) == nullptr) { |
521 | Q_ASSERT(layout->parent() == nullptr); |
522 | QBoxLayout *box = qobject_cast<QBoxLayout*>(object: widget->layout()); |
523 | if (!box) { // we support only unmanaged box layouts |
524 | const QString msg = tr(s: "Attempt to add a layout to a widget '%1' (%2) which already has an unmanaged layout of type %3.\n" |
525 | "This indicates an inconsistency in the ui-file." ). |
526 | arg(args: widget->objectName(), args: classNameOf(c: core(), o: widget), args: classNameOf(c: core(), o: widget->layout())); |
527 | designerWarning(message: msg); |
528 | return nullptr; |
529 | } |
530 | box->addLayout(layout); |
531 | } |
532 | |
533 | return layout; |
534 | } |
535 | |
536 | /*! Returns the widget into which children should be inserted when \a |
537 | w is a container known to designer. |
538 | |
539 | Usually, it is \a w itself, but there are exceptions (for example, a |
540 | tabwidget is known to designer as a container, but the child |
541 | widgets should be inserted into the current page of the |
542 | tabwidget. In this case, the current page of |
543 | the tabwidget would be returned.) |
544 | */ |
545 | QWidget* WidgetFactory::containerOfWidget(QWidget *w) const |
546 | { |
547 | if (QDesignerContainerExtension *container = qt_extension<QDesignerContainerExtension*>(manager: core()->extensionManager(), object: w)) |
548 | return container->widget(index: container->currentIndex()); |
549 | |
550 | return w; |
551 | } |
552 | |
553 | /*! Returns the actual designer widget of the container \a w. This is |
554 | normally \a w itself, but it might be a parent or grand parent of \a w |
555 | (for example, when working with a tabwidget and \a w is the container which |
556 | contains and layouts children, but the actual widget known to |
557 | designer is the tabwidget which is the parent of \a w. In this case, |
558 | the tabwidget would be returned.) |
559 | */ |
560 | |
561 | QWidget* WidgetFactory::widgetOfContainer(QWidget *w) const |
562 | { |
563 | // ### cleanup |
564 | if (!w) |
565 | return nullptr; |
566 | if (w->parentWidget() && w->parentWidget()->parentWidget() && |
567 | w->parentWidget()->parentWidget()->parentWidget() && |
568 | qobject_cast<QToolBox*>(object: w->parentWidget()->parentWidget()->parentWidget())) |
569 | return w->parentWidget()->parentWidget()->parentWidget(); |
570 | |
571 | while (w != nullptr) { |
572 | if (core()->widgetDataBase()->isContainer(object: w) || |
573 | (w && qobject_cast<QDesignerFormWindowInterface*>(object: w->parentWidget()))) |
574 | return w; |
575 | |
576 | w = w->parentWidget(); |
577 | } |
578 | |
579 | return w; |
580 | } |
581 | |
582 | QDesignerFormEditorInterface *WidgetFactory::core() const |
583 | { |
584 | return m_core; |
585 | } |
586 | |
587 | // Necessary initializations for form editor/preview objects |
588 | void WidgetFactory::initializeCommon(QWidget *widget) const |
589 | { |
590 | // Apply style |
591 | if (m_currentStyle) |
592 | widget->setStyle(m_currentStyle); |
593 | } |
594 | |
595 | // Necessary initializations for preview objects |
596 | void WidgetFactory::initializePreview(QWidget *widget) const |
597 | { |
598 | |
599 | if (QStackedWidget *stackedWidget = qobject_cast<QStackedWidget*>(object: widget)) { |
600 | QStackedWidgetPreviewEventFilter::install(stackedWidget); // Add browse button only. |
601 | return; |
602 | } |
603 | } |
604 | |
605 | // Necessary initializations for form editor objects |
606 | void WidgetFactory::initialize(QObject *object) const |
607 | { |
608 | // Indicate that this is a form object (for QDesignerFormWindowInterface::findFormWindow) |
609 | object->setProperty(name: formEditorDynamicProperty, value: QVariant(true)); |
610 | QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: m_core->extensionManager(), object); |
611 | if (!sheet) |
612 | return; |
613 | |
614 | sheet->setChanged(index: sheet->indexOf(name: m_strings.m_objectName), changed: true); |
615 | |
616 | if (!object->isWidgetType()) { |
617 | if (qobject_cast<QAction*>(object)) |
618 | sheet->setChanged(index: sheet->indexOf(name: m_strings.m_text), changed: true); |
619 | return; |
620 | } |
621 | |
622 | QWidget *widget = static_cast<QWidget*>(object); |
623 | const bool = qobject_cast<QMenu*>(object: widget); |
624 | const bool = !isMenu && qobject_cast<QMenuBar*>(object: widget); |
625 | |
626 | widget->setAttribute(Qt::WA_TransparentForMouseEvents, on: false); |
627 | widget->setFocusPolicy((isMenu || isMenuBar) ? Qt::StrongFocus : Qt::NoFocus); |
628 | |
629 | if (!isMenu) |
630 | sheet->setChanged(index: sheet->indexOf(name: m_strings.m_geometry), changed: true); |
631 | |
632 | if (qobject_cast<Spacer*>(object: widget)) { |
633 | sheet->setChanged(index: sheet->indexOf(name: m_strings.m_spacerName), changed: true); |
634 | return; |
635 | } |
636 | |
637 | const int o = sheet->indexOf(name: m_strings.m_orientation); |
638 | if (o != -1 && widget->inherits(classname: "QSplitter" )) |
639 | sheet->setChanged(index: o, changed: true); |
640 | |
641 | if (QToolBar *toolBar = qobject_cast<QToolBar*>(object: widget)) { |
642 | ToolBarEventFilter::install(tb: toolBar); |
643 | sheet->setVisible(index: sheet->indexOf(name: m_strings.m_windowTitle), b: true); |
644 | toolBar->setFloatable(false); // prevent toolbars from being dragged off |
645 | return; |
646 | } |
647 | |
648 | if (qobject_cast<QDockWidget*>(object: widget)) { |
649 | sheet->setVisible(index: sheet->indexOf(name: m_strings.m_windowTitle), b: true); |
650 | sheet->setVisible(index: sheet->indexOf(name: m_strings.m_windowIcon), b: true); |
651 | return; |
652 | } |
653 | |
654 | if (isMenu) { |
655 | sheet->setChanged(index: sheet->indexOf(name: m_strings.m_title), changed: true); |
656 | return; |
657 | } |
658 | // helpers |
659 | if (QToolBox *toolBox = qobject_cast<QToolBox*>(object: widget)) { |
660 | QToolBoxHelper::install(toolbox: toolBox); |
661 | return; |
662 | } |
663 | if (QStackedWidget *stackedWidget = qobject_cast<QStackedWidget*>(object: widget)) { |
664 | QStackedWidgetEventFilter::install(stackedWidget); |
665 | return; |
666 | } |
667 | if (QTabWidget *tabWidget = qobject_cast<QTabWidget*>(object: widget)) { |
668 | QTabWidgetEventFilter::install(tabWidget); |
669 | return; |
670 | } |
671 | // Prevent embedded line edits from getting focus |
672 | if (QAbstractSpinBox *asb = qobject_cast<QAbstractSpinBox *>(object: widget)) { |
673 | if (QLineEdit *lineEdit = static_cast<FriendlySpinBox*>(asb)->lineEdit()) |
674 | lineEdit->setFocusPolicy(Qt::NoFocus); |
675 | return; |
676 | } |
677 | if (QComboBox *cb = qobject_cast<QComboBox *>(object: widget)) { |
678 | if (QFontComboBox *fcb = qobject_cast<QFontComboBox *>(object: widget)) { |
679 | fcb->lineEdit()->setFocusPolicy(Qt::NoFocus); // Always present |
680 | return; |
681 | } |
682 | cb->installEventFilter(filterObj: new ComboEventFilter(cb)); |
683 | return; |
684 | } |
685 | if (QWizard *wz = qobject_cast<QWizard *>(object: widget)) { |
686 | WizardPageChangeWatcher *pw = new WizardPageChangeWatcher(wz); |
687 | Q_UNUSED(pw); |
688 | } |
689 | } |
690 | |
691 | static inline QString classNameOfStyle(const QStyle *s) |
692 | { |
693 | return QLatin1String(s->metaObject()->className()); |
694 | } |
695 | |
696 | QString WidgetFactory::styleName() const |
697 | { |
698 | return classNameOfStyle(s: style()); |
699 | } |
700 | |
701 | static inline bool isApplicationStyle(const QString &styleName) |
702 | { |
703 | return styleName.isEmpty() || styleName == classNameOfStyle(qApp->style()); |
704 | } |
705 | |
706 | void WidgetFactory::setStyleName(const QString &styleName) |
707 | { |
708 | m_currentStyle = isApplicationStyle(styleName) ? nullptr : getStyle(styleName); |
709 | } |
710 | |
711 | QStyle *WidgetFactory::style() const |
712 | { |
713 | return m_currentStyle ? m_currentStyle : qApp->style(); |
714 | } |
715 | |
716 | QStyle *WidgetFactory::getStyle(const QString &styleName) |
717 | { |
718 | if (isApplicationStyle(styleName)) |
719 | return qApp->style(); |
720 | |
721 | StyleCache::iterator it = m_styleCache.find(key: styleName); |
722 | if (it == m_styleCache.end()) { |
723 | QStyle *style = QStyleFactory::create(styleName); |
724 | if (!style) { |
725 | const QString msg = tr(s: "Cannot create style '%1'." ).arg(a: styleName); |
726 | designerWarning(message: msg); |
727 | return nullptr; |
728 | } |
729 | it = m_styleCache.insert(key: styleName, value: style); |
730 | } |
731 | return it.value(); |
732 | } |
733 | |
734 | void WidgetFactory::applyStyleTopLevel(const QString &styleName, QWidget *w) |
735 | { |
736 | if (QStyle *style = getStyle(styleName)) |
737 | applyStyleToTopLevel(style, widget: w); |
738 | } |
739 | |
740 | void WidgetFactory::applyStyleToTopLevel(QStyle *style, QWidget *widget) |
741 | { |
742 | if (!style) |
743 | return; |
744 | const QPalette standardPalette = style->standardPalette(); |
745 | if (widget->style() == style && widget->palette() == standardPalette) |
746 | return; |
747 | |
748 | widget->setStyle(style); |
749 | widget->setPalette(standardPalette); |
750 | const QWidgetList lst = widget->findChildren<QWidget*>(); |
751 | const QWidgetList::const_iterator cend = lst.constEnd(); |
752 | for (QWidgetList::const_iterator it = lst.constBegin(); it != cend; ++it) |
753 | (*it)->setStyle(style); |
754 | } |
755 | |
756 | // Check for 'interactor' click on a tab bar, |
757 | // which can appear within a QTabWidget or as a standalone widget. |
758 | |
759 | static bool isTabBarInteractor(const QTabBar *tabBar) |
760 | { |
761 | // Tabbar embedded in Q(Designer)TabWidget, ie, normal tab widget case |
762 | if (qobject_cast<const QTabWidget*>(object: tabBar->parentWidget())) |
763 | return true; |
764 | |
765 | // Standalone tab bar on the form. Return true for tab rect areas |
766 | // only to allow the user to select the tab bar by clicking outside the actual tabs. |
767 | const int count = tabBar->count(); |
768 | if (count == 0) |
769 | return false; |
770 | |
771 | // click into current tab: No Interaction |
772 | const int currentIndex = tabBar->currentIndex(); |
773 | const QPoint pos = tabBar->mapFromGlobal(QCursor::pos()); |
774 | if (tabBar->tabRect(index: currentIndex).contains(p: pos)) |
775 | return false; |
776 | |
777 | // click outside: No Interaction |
778 | const QRect geometry = QRect(QPoint(0, 0), tabBar->size()); |
779 | if (!geometry.contains(p: pos)) |
780 | return false; |
781 | // click into another tab: Let's interact, switch tabs. |
782 | for (int i = 0; i < count; i++) |
783 | if (tabBar->tabRect(index: i).contains(p: pos)) |
784 | return true; |
785 | return false; |
786 | } |
787 | |
788 | static bool isPassiveInteractorHelper(const QWidget *widget) |
789 | { |
790 | static const QString qtPassive = QStringLiteral("__qt__passive_" ); |
791 | static const QString qtMainWindowSplitter = QStringLiteral("qt_qmainwindow_extended_splitter" ); |
792 | |
793 | if (qobject_cast<const QMenuBar*>(object: widget) |
794 | #if QT_CONFIG(sizegrip) |
795 | || qobject_cast<const QSizeGrip*>(object: widget) |
796 | #endif |
797 | || qobject_cast<const QMdiSubWindow*>(object: widget) |
798 | || qobject_cast<const QToolBar*>(object: widget)) { |
799 | return true; |
800 | } |
801 | |
802 | if (qobject_cast<const QAbstractButton*>(object: widget)) { |
803 | auto parent = widget->parent(); |
804 | if (qobject_cast<const QTabBar*>(object: parent) || qobject_cast<const QToolBox*>(object: parent)) |
805 | return true; |
806 | } else if (const auto tabBar = qobject_cast<const QTabBar*>(object: widget)) { |
807 | if (isTabBarInteractor(tabBar)) |
808 | return true; |
809 | } else if (qobject_cast<const QScrollBar*>(object: widget)) { |
810 | // A scroll bar is an interactor on a QAbstractScrollArea only. |
811 | if (auto parent = widget->parentWidget()) { |
812 | const QString objectName = parent->objectName(); |
813 | static const QString scrollAreaVContainer = QStringLiteral("qt_scrollarea_vcontainer" ); |
814 | static const QString scrollAreaHContainer = QStringLiteral("qt_scrollarea_hcontainer" ); |
815 | if (objectName == scrollAreaVContainer || objectName == scrollAreaHContainer) |
816 | return true; |
817 | } |
818 | } else if (qstrcmp(str1: widget->metaObject()->className(), str2: "QDockWidgetTitle" ) == 0) { |
819 | return true; |
820 | } else if (qstrcmp(str1: widget->metaObject()->className(), str2: "QWorkspaceTitleBar" ) == 0) { |
821 | return true; |
822 | } |
823 | const QString &name = widget->objectName(); |
824 | return name.startsWith(s: qtPassive) || name == qtMainWindowSplitter; |
825 | } |
826 | |
827 | bool WidgetFactory::isPassiveInteractor(QWidget *widget) |
828 | { |
829 | static bool lastWasAPassiveInteractor = false; |
830 | static QPointer<QWidget> lastPassiveInteractor; |
831 | |
832 | if (!lastPassiveInteractor.isNull() && lastPassiveInteractor.data() == widget) |
833 | return lastWasAPassiveInteractor; |
834 | |
835 | // if a popup is open, we have to make sure that this one is closed, |
836 | // else X might do funny things |
837 | if (QApplication::activePopupWidget() || widget == nullptr) |
838 | return true; |
839 | |
840 | lastWasAPassiveInteractor = isPassiveInteractorHelper(widget); |
841 | lastPassiveInteractor = widget; |
842 | |
843 | return lastWasAPassiveInteractor; |
844 | } |
845 | |
846 | void WidgetFactory::formWindowAdded(QDesignerFormWindowInterface *formWindow) |
847 | { |
848 | setFormWindowStyle(formWindow); |
849 | } |
850 | |
851 | void WidgetFactory::activeFormWindowChanged(QDesignerFormWindowInterface *formWindow) |
852 | { |
853 | setFormWindowStyle(formWindow); |
854 | } |
855 | |
856 | void WidgetFactory::setFormWindowStyle(QDesignerFormWindowInterface *formWindow) |
857 | { |
858 | if (FormWindowBase *fwb = qobject_cast<FormWindowBase *>(object: formWindow)) |
859 | setStyleName(fwb->styleName()); |
860 | } |
861 | |
862 | bool WidgetFactory::isFormEditorObject(const QObject *object) |
863 | { |
864 | return object->property(name: formEditorDynamicProperty).isValid(); |
865 | } |
866 | } // namespace qdesigner_internal |
867 | |
868 | QT_END_NAMESPACE |
869 | |
870 | #include "widgetfactory.moc" |
871 | |