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