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
morphmenu.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
4#include "morphmenu_p.h"
9#include "layoutinfo_p.h"
11
12#include <QtDesigner/qextensionmanager.h>
13#include <QtDesigner/container.h>
14#include <QtDesigner/abstractformwindow.h>
15#include <QtDesigner/abstractformeditor.h>
16#include <QtDesigner/abstractlanguage.h>
17#include <QtDesigner/abstractwidgetdatabase.h>
18#include <QtDesigner/abstractmetadatabase.h>
19#include <QtDesigner/propertysheet.h>
20
21#include <QtWidgets/qwidget.h>
22#include <QtWidgets/qmenu.h>
23#include <QtWidgets/qapplication.h>
24#include <QtWidgets/qlayout.h>
25#include <QtGui/qundostack.h>
26#include <QtWidgets/qsplitter.h>
27
28#include <QtWidgets/qframe.h>
29#include <QtWidgets/qgroupbox.h>
30#include <QtWidgets/qtabwidget.h>
31#include <QtWidgets/qstackedwidget.h>
32#include <QtWidgets/qtoolbox.h>
33#include <QtWidgets/qabstractitemview.h>
34#include <QtWidgets/qabstractbutton.h>
35#include <QtWidgets/qabstractspinbox.h>
36#include <QtWidgets/qtextedit.h>
37#include <QtWidgets/qplaintextedit.h>
38#include <QtWidgets/qlabel.h>
39
40#include <QtGui/qaction.h>
41
42#include <QtCore/qstringlist.h>
43#include <QtCore/qmap.h>
44#include <QtCore/qvariant.h>
45#include <QtCore/qdebug.h>
46
47Q_DECLARE_METATYPE(QWidgetList)
48
49QT_BEGIN_NAMESPACE
50
51using namespace Qt::StringLiterals;
52
53// Helpers for the dynamic properties that store Z/Widget order
54static const char widgetOrderPropertyC[] = "_q_widgetOrder";
55static const char zOrderPropertyC[] = "_q_zOrder";
56
57/* Morphing in Designer:
58 * It is possible to morph:
59 * - Non-Containers into similar widgets by category
60 * - Simple page containers into similar widgets or page-based containers with
61 * a single page (in theory also into a QLayoutWidget, but this might
62 * not always be appropriate).
63 * - Page-based containers into page-based containers or simple containers if
64 * they have just one page
65 * [Page based containers meaning here having a container extension]
66 * Morphing types are restricted to the basic Qt types. Morphing custom
67 * widgets is considered risky since they might have unmanaged layouts
68 * or the like.
69 *
70 * Requirements:
71 * - The widget must be on a non-laid out parent or in a layout managed
72 * by Designer
73 * - Its child widgets must be non-laid out or in a layout managed
74 * by Designer
75 * Note that child widgets can be
76 * - On the widget itself in the case of simple containers
77 * - On several pages in the case of page-based containers
78 * This is what is called 'childContainers' in the code (the widget itself
79 * or the list of container extension pages).
80 *
81 * The Morphing process encompasses:
82 * - Create a target widget and apply properties as far as applicable
83 * If the target widget has a container extension, add a sufficient
84 * number of pages.
85 * - Transferring the child widgets over to the new childContainers.
86 * In the case of a managed layout on a childContainer, this is simply
87 * set on the target childContainer, which is a new Qt 4.5
88 * functionality.
89 * - Replace the widget itself in the parent layout
90 */
91
92namespace qdesigner_internal {
93
98
99// Determine category of a widget
101{
102 // Simple containers: Exact match
103 const QMetaObject *mo = w->metaObject();
104 if (mo == &QWidget::staticMetaObject || mo == &QFrame::staticMetaObject || mo == &QGroupBox::staticMetaObject || mo == &QLayoutWidget::staticMetaObject)
106 if (mo == &QTabWidget::staticMetaObject || mo == &QStackedWidget::staticMetaObject || mo == &QToolBox::staticMetaObject)
107 return MorphPageContainer;
108 if (qobject_cast<const QAbstractItemView*>(w))
109 return MorphItemView;
110 if (qobject_cast<const QAbstractButton *>(w))
111 return MorphButton;
112 if (qobject_cast<const QAbstractSpinBox *>(w))
113 return MorphSpinBox;
114 if (qobject_cast<const QPlainTextEdit *>(w) || qobject_cast<const QTextEdit*>(w))
115 return MorphTextEdit;
116
117 return MorphCategoryNone;
118}
119
120/* Return the similar classes of a category. This is currently restricted
121 * to the known Qt classes with no precautions to parse the Widget Database
122 * (which is too risky, custom classes might have container extensions
123 * or non-managed layouts, etc.). */
124
126{
127 static QMap<MorphCategory, QStringList> candidateCache;
128 auto it = candidateCache.find(cat);
129 if (it == candidateCache.end()) {
130 it = candidateCache.insert(cat, QStringList());
131 QStringList &l = it.value();
132 switch (cat) {
134 break;
135 case MorphSimpleContainer:
136 // Do not generally allow to morph into a layout.
137 // This can be risky in case of container pages,etc.
138 l << u"QWidget"_s << u"QFrame"_s << u"QGroupBox"_s;
139 break;
140 case MorphPageContainer:
141 l << u"QTabWidget"_s << u"QStackedWidget"_s << u"QToolBox"_s;
142 break;
143 case MorphItemView:
144 l << u"QListView"_s << u"QListWidget"_s
145 << u"QTreeView"_s << u"QTreeWidget"_s
146 << u"QTableView"_s << u"QTableWidget"_s
147 << u"QColumnView"_s;
148 break;
149 case MorphButton:
150 l << u"QCheckBox"_s << u"QRadioButton"_s
151 << u"QPushButton"_s << u"QToolButton"_s
152 << u"QCommandLinkButton"_s;
153 break;
154 case MorphSpinBox:
155 l << u"QDateTimeEdit"_s << u"QDateEdit"_s
156 << u"QTimeEdit"_s
157 << u"QSpinBox"_s << u"QDoubleSpinBox"_s;
158 break;
159 case MorphTextEdit:
160 l << u"QTextEdit"_s << u"QPlainTextEdit"_s << u"QTextBrowser"_s;
161 break;
162 }
163 }
164 return it.value();
165}
166
167// Return the widgets containing the children to be transferred to. This is the
168// widget itself in most cases, except for QDesignerContainerExtension cases
169static QWidgetList childContainers(const QDesignerFormEditorInterface *core, QWidget *w)
170{
171 if (const QDesignerContainerExtension *ce = qt_extension<QDesignerContainerExtension*>(core->extensionManager(), w)) {
172 QWidgetList children;
173 if (const int count = ce->count()) {
174 for (int i = 0; i < count; i++)
175 children.push_back(ce->widget(i));
176 }
177 return children;
178 }
179 QWidgetList self;
180 self.push_back(w);
181 return self;
182}
183
184// Suggest a suitable objectname for the widget to be morphed into
185// Replace the class name parts: 'xxFrame' -> 'xxGroupBox', 'frame' -> 'groupBox'
186static QString suggestObjectName(const QString &oldClassName, const QString &newClassName, const QString &oldName)
187{
188 QString oldClassPart = oldClassName;
189 QString newClassPart = newClassName;
190 if (oldClassPart.startsWith(u'Q'))
191 oldClassPart.remove(0, 1);
192 if (newClassPart.startsWith(u'Q'))
193 newClassPart.remove(0, 1);
194
195 QString newName = oldName;
196 newName.replace(oldClassPart, newClassPart);
197 oldClassPart[0] = oldClassPart.at(0).toLower();
198 newClassPart[0] = newClassPart.at(0).toLower();
199 newName.replace(oldClassPart, newClassPart);
200 return newName;
201}
202
203// Find the label whose buddy the widget is.
204QLabel *buddyLabelOf(QDesignerFormWindowInterface *fw, QWidget *w)
205{
206 const auto labelList = fw->findChildren<QLabel*>();
207 for (QLabel *label : labelList)
208 if (label->buddy() == w)
209 return label;
210 return nullptr;
211}
212
213// Replace widgets in a widget-list type dynamic property of the parent
214// used for Z-order, etc.
216 QWidget *oldWidget, QWidget *newWidget,
217 const char *name)
218{
219 QWidgetList list = qvariant_cast<QWidgetList>(parentWidget->property(name));
220 const int index = list.indexOf(oldWidget);
221 if (index != -1) {
222 list.replace(index, newWidget);
223 parentWidget->setProperty(name, QVariant::fromValue(list));
224 }
225}
226
227/* Morph a widget into another class. Use the static addMorphMacro() to
228 * add a respective command sequence to the undo stack as it emits signals
229 * which cause other commands to be added. */
231{
233public:
234
237
238 // Convenience to add a morph command sequence macro
239 static bool addMorphMacro(QDesignerFormWindowInterface *formWindow, QWidget *w, const QString &newClass);
240
241 bool init(QWidget *widget, const QString &newClassName);
242
243 QString newWidgetName() const { return m_afterWidget->objectName(); }
244
247
248 static QStringList candidateClasses(QDesignerFormWindowInterface *fw, QWidget *w);
249
250private:
251 static bool canMorph(QDesignerFormWindowInterface *fw, QWidget *w, int *childContainerCount = nullptr, MorphCategory *cat = nullptr);
252 void morph(QWidget *before, QWidget *after);
253
254 QWidget *m_beforeWidget;
255 QWidget *m_afterWidget;
256};
257
258bool MorphWidgetCommand::addMorphMacro(QDesignerFormWindowInterface *fw, QWidget *w, const QString &newClass)
259{
260 MorphWidgetCommand *morphCmd = new MorphWidgetCommand(fw);
261 if (!morphCmd->init(w, newClass)) {
262 qWarning("*** Unable to create a MorphWidgetCommand");
263 delete morphCmd;
264 return false;
265 }
266 QLabel *buddyLabel = buddyLabelOf(fw, w);
267 // Need a macro since it adds further commands
268 QUndoStack *us = fw->commandHistory();
269 us->beginMacro(morphCmd->text());
270 // Have the signal slot/buddy editors add their commands to delete widget
271 if (FormWindowBase *fwb = qobject_cast<FormWindowBase*>(fw))
272 fwb->emitWidgetRemoved(w);
273
274 const QString newWidgetName = morphCmd->newWidgetName();
275 us->push(morphCmd);
276
277 // restore buddy using the QByteArray name.
278 if (buddyLabel) {
279 SetPropertyCommand *buddyCmd = new SetPropertyCommand(fw);
280 buddyCmd->init(buddyLabel, u"buddy"_s, QVariant(newWidgetName.toUtf8()));
281 us->push(buddyCmd);
282 }
283 us->endMacro();
284 return true;
285}
286
287MorphWidgetCommand::MorphWidgetCommand(QDesignerFormWindowInterface *formWindow) :
288 QDesignerFormWindowCommand(QString(), formWindow),
289 m_beforeWidget(nullptr),
290 m_afterWidget(nullptr)
291{
292}
293
295
296bool MorphWidgetCommand::init(QWidget *widget, const QString &newClassName)
297{
298 QDesignerFormWindowInterface *fw = formWindow();
299 QDesignerFormEditorInterface *core = fw->core();
300
301 if (!canMorph(fw, widget))
302 return false;
303
304 const QString oldClassName = WidgetFactory::classNameOf(core, widget);
305 const QString oldName = widget->objectName();
306 //: MorphWidgetCommand description
307 setText(QApplication::translate("Command", "Morph %1/'%2' into %3").arg(oldClassName, oldName, newClassName));
308
309 m_beforeWidget = widget;
310 m_afterWidget = core->widgetFactory()->createWidget(newClassName, fw);
311 if (!m_afterWidget)
312 return false;
313
314 // Set object name. Do not unique it (as to maintain it).
315 m_afterWidget->setObjectName(suggestObjectName(oldClassName, newClassName, oldName));
316
317 // If the target has a container extension, we add enough new pages to take
318 // up the children of the before widget
319 if (QDesignerContainerExtension* c = qt_extension<QDesignerContainerExtension*>(core->extensionManager(), m_afterWidget)) {
320 if (const auto pageCount = childContainers(core, m_beforeWidget).size()) {
321 const QString containerName = m_afterWidget->objectName();
322 for (qsizetype i = 0; i < pageCount; ++i) {
323 QString name = containerName;
324 name += "Page"_L1;
325 name += QString::number(i + 1);
326 QWidget *page = core->widgetFactory()->createWidget(u"QWidget"_s);
327 page->setObjectName(name);
328 fw->ensureUniqueObjectName(page);
329 c->addWidget(page);
330 core->metaDataBase()->add(page);
331 }
332 }
333 }
334
335 // Copy over applicable properties
336 const QDesignerPropertySheetExtension *beforeSheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), widget);
337 QDesignerPropertySheetExtension *afterSheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), m_afterWidget);
338 const int count = beforeSheet->count();
339 for (int i = 0; i < count; i++)
340 if (beforeSheet->isVisible(i) && beforeSheet->isChanged(i)) {
341 const QString name = beforeSheet->propertyName(i);
342 if (name != "objectName"_L1) {
343 const int afterIndex = afterSheet->indexOf(name);
344 if (afterIndex != -1 && afterSheet->isVisible(afterIndex) && afterSheet->propertyGroup(afterIndex) == beforeSheet->propertyGroup(i)) {
345 afterSheet->setProperty(i, beforeSheet->property(i));
346 afterSheet->setChanged(i, true);
347 } else {
348 // Some mismatch. The rest won't match, either
349 break;
350 }
351 }
352 }
353 return true;
354}
355
357{
358 morph(m_beforeWidget, m_afterWidget);
359}
360
362{
363 morph(m_afterWidget, m_beforeWidget);
364}
365
366void MorphWidgetCommand::morph(QWidget *before, QWidget *after)
367{
368 QDesignerFormWindowInterface *fw = formWindow();
369
370 fw->unmanageWidget(before);
371
372 const QRect oldGeom = before->geometry();
373 QWidget *parent = before->parentWidget();
374 Q_ASSERT(parent);
375 /* Morphing consists of main 2 steps
376 * 1) Move over children (laid out, non-laid out)
377 * 2) Register self with new parent (laid out, non-laid out) */
378
379 // 1) Move children. Loop over child containers
380 QWidgetList beforeChildContainers = childContainers(fw->core(), before);
381 QWidgetList afterChildContainers = childContainers(fw->core(), after);
382 Q_ASSERT(beforeChildContainers.size() == afterChildContainers.size());
383 const auto childContainerCount = beforeChildContainers.size();
384 for (qsizetype i = 0; i < childContainerCount; ++i) {
385 QWidget *beforeChildContainer = beforeChildContainers.at(i);
386 QWidget *afterChildContainer = afterChildContainers.at(i);
387 if (QLayout *childLayout = beforeChildContainer->layout()) {
388 // Laid-out: Move the layout (since 4.5)
389 afterChildContainer->setLayout(childLayout);
390 } else {
391 // Non-Laid-out: Reparent, move over
392 for (QObject *o : beforeChildContainer->children()) {
393 if (o->isWidgetType()) {
394 QWidget *w = static_cast<QWidget*>(o);
395 if (fw->isManaged(w)) {
396 const QRect geom = w->geometry();
397 w->setParent(afterChildContainer);
398 w->setGeometry(geom);
399 }
400 }
401 }
402 }
403 afterChildContainer->setProperty(widgetOrderPropertyC, beforeChildContainer->property(widgetOrderPropertyC));
404 afterChildContainer->setProperty(zOrderPropertyC, beforeChildContainer->property(zOrderPropertyC));
405 }
406
407 // 2) Replace the actual widget in the parent layout
408 after->setGeometry(oldGeom);
409 if (QLayout *containingLayout = LayoutInfo::managedLayout(fw->core(), parent)) {
410 LayoutHelper *lh = LayoutHelper::createLayoutHelper(LayoutInfo::layoutType(fw->core(), containingLayout));
411 Q_ASSERT(lh);
412 lh->replaceWidget(containingLayout, before, after);
413 delete lh;
414 } else if (QSplitter *splitter = qobject_cast<QSplitter *>(parent)) {
415 const int index = splitter->indexOf(before);
416 before->hide();
417 before->setParent(nullptr);
418 splitter->insertWidget(index, after);
419 after->setParent(parent);
420 after->setGeometry(oldGeom);
421 } else {
422 before->hide();
423 before->setParent(nullptr);
424 after->setParent(parent);
425 after->setGeometry(oldGeom);
426 }
427
428 // Check various properties: Z order, form tab order
431
432 QDesignerMetaDataBaseItemInterface *formItem = fw->core()->metaDataBase()->item(fw);
433 QWidgetList tabOrder = formItem->tabOrder();
434 const int tabIndex = tabOrder.indexOf(before);
435 if (tabIndex != -1) {
436 tabOrder.replace(tabIndex, after);
437 formItem->setTabOrder(tabOrder);
438 }
439
440 after->show();
441 fw->manageWidget(after);
442
443 fw->clearSelection(false);
444 fw->selectWidget(after);
445}
446
447/* Check if morphing is possible. It must be a valid category and the parent/
448 * child relationships must be either non-laidout or directly on
449 * Designer-managed layouts. */
450bool MorphWidgetCommand::canMorph(QDesignerFormWindowInterface *fw, QWidget *w, int *ptrToChildContainerCount, MorphCategory *ptrToCat)
451{
452 if (ptrToChildContainerCount)
453 *ptrToChildContainerCount = 0;
454 const MorphCategory cat = category(w);
455 if (ptrToCat)
456 *ptrToCat = cat;
457 if (cat == MorphCategoryNone)
458 return false;
459
460 QDesignerFormEditorInterface *core = fw->core();
461 // Don't know how to fiddle class names in Jambi..
462 if (qt_extension<QDesignerLanguageExtension *>(core->extensionManager(), core))
463 return false;
464 if (!fw->isManaged(w) || w == fw->mainContainer())
465 return false;
466 // Check the parent relationship. We accept only managed parent widgets
467 // with a single, managed layout in which widget is a member.
468 QWidget *parent = w->parentWidget();
469 if (parent == nullptr)
470 return false;
471 if (QLayout *pl = LayoutInfo::managedLayout(core, parent))
472 if (pl->indexOf(w) < 0 || !core->metaDataBase()->item(pl))
473 return false;
474 // Check Widget database
475 const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase();
476 const int wdbindex = wdb->indexOfObject(w);
477 if (wdbindex == -1)
478 return false;
479 const bool isContainer = wdb->item(wdbindex)->isContainer();
480 if (!isContainer)
481 return true;
482 // Check children. All child containers must be non-laid-out or have managed layouts
483 const QWidgetList pages = childContainers(core, w);
484 const auto pageCount = pages.size();
485 if (ptrToChildContainerCount)
486 *ptrToChildContainerCount = pageCount;
487 if (pageCount) {
488 for (qsizetype i = 0; i < pageCount; ++i)
489 if (QLayout *cl = pages.at(i)->layout())
490 if (!core->metaDataBase()->item(cl))
491 return false;
492 }
493 return true;
494}
495
496QStringList MorphWidgetCommand::candidateClasses(QDesignerFormWindowInterface *fw, QWidget *w)
497{
498 int childContainerCount;
499 MorphCategory cat;
500 if (!canMorph(fw, w, &childContainerCount, &cat))
501 return QStringList();
502
503 QStringList rc = classesOfCategory(cat);
504 switch (cat) {
505 // Frames, etc can always be morphed into one-page page containers
506 case MorphSimpleContainer:
507 rc += classesOfCategory(MorphPageContainer);
508 break;
509 // Multipage-Containers can be morphed into simple containers if they
510 // have 1 page.
511 case MorphPageContainer:
512 if (childContainerCount == 1)
513 rc += classesOfCategory(MorphSimpleContainer);
514 break;
515 default:
516 break;
517 }
518 return rc;
519}
520
521// MorphMenu
526
532
538
540{
542}
543
545{
546 m_widget = nullptr;
547 m_formWindow = nullptr;
548
549 // Clear menu
550 if (m_subMenuAction) {
552 m_menu->clear();
553 }
554
555 // Checks: Must not be main container
556 if (w == fw->mainContainer())
557 return false;
558
560 if (c.isEmpty())
561 return false;
562
563 // Pull up
564 m_widget = w;
567
568 if (!m_subMenuAction) {
569 m_subMenuAction = new QAction(tr("Morph into"), this);
570 m_menu = new QMenu;
572 }
573
574 // Add actions
575 for (const auto &className : c) {
576 if (className != oldClassName) {
578 this, [this, className] { this->slotMorph(className); });
579 }
580 }
582 return true;
583}
584
585} // namespace qdesigner_internal
586
587QT_END_NAMESPACE
friend class QWidget
Definition qpainter.h:421
static QStringList candidateClasses(QDesignerFormWindowInterface *fw, QWidget *w)
bool init(QWidget *widget, const QString &newClassName)
void undo() override
Reverts a change to the document.
void redo() override
Applies a change to the document.
static bool addMorphMacro(QDesignerFormWindowInterface *formWindow, QWidget *w, const QString &newClass)
static const char widgetOrderPropertyC[]
Definition morphmenu.cpp:54
static const char zOrderPropertyC[]
Definition morphmenu.cpp:55
Auxiliary methods to store/retrieve settings.
static QStringList classesOfCategory(MorphCategory cat)
static QString suggestObjectName(const QString &oldClassName, const QString &newClassName, const QString &oldName)
QLabel * buddyLabelOf(QDesignerFormWindowInterface *fw, QWidget *w)
static QWidgetList childContainers(const QDesignerFormEditorInterface *core, QWidget *w)
static MorphCategory category(const QWidget *w)
static void replaceWidgetListDynamicProperty(QWidget *parentWidget, QWidget *oldWidget, QWidget *newWidget, const char *name)