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 |
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*>(object: w)) |
131 | return MorphItemView; |
132 | if (qobject_cast<const QAbstractButton *>(object: w)) |
133 | return MorphButton; |
134 | if (qobject_cast<const QAbstractSpinBox *>(object: w)) |
135 | return MorphSpinBox; |
136 | if (qobject_cast<const QPlainTextEdit *>(object: w) || qobject_cast<const QTextEdit*>(object: 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 | |
147 | static QStringList classesOfCategory(MorphCategory cat) |
148 | { |
149 | typedef QMap<MorphCategory, QStringList> CandidateCache; |
150 | static CandidateCache candidateCache; |
151 | CandidateCache::iterator it = candidateCache.find(akey: cat); |
152 | if (it == candidateCache.end()) { |
153 | it = candidateCache.insert(akey: cat, avalue: 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 |
192 | static QWidgetList childContainers(const QDesignerFormEditorInterface *core, QWidget *w) |
193 | { |
194 | if (const QDesignerContainerExtension *ce = qt_extension<QDesignerContainerExtension*>(manager: core->extensionManager(), object: w)) { |
195 | QWidgetList children; |
196 | if (const int count = ce->count()) { |
197 | for (int i = 0; i < count; i++) |
198 | children.push_back(t: ce->widget(index: i)); |
199 | } |
200 | return children; |
201 | } |
202 | QWidgetList self; |
203 | self.push_back(t: 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' |
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(c: QLatin1Char('Q'))) |
214 | oldClassPart.remove(i: 0, len: 1); |
215 | if (newClassPart.startsWith(c: QLatin1Char('Q'))) |
216 | newClassPart.remove(i: 0, len: 1); |
217 | |
218 | QString newName = oldName; |
219 | newName.replace(before: oldClassPart, after: newClassPart); |
220 | oldClassPart[0] = oldClassPart.at(i: 0).toLower(); |
221 | newClassPart[0] = newClassPart.at(i: 0).toLower(); |
222 | newName.replace(before: oldClassPart, after: newClassPart); |
223 | return newName; |
224 | } |
225 | |
226 | // Find the label whose buddy the widget is. |
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. |
238 | static void replaceWidgetListDynamicProperty(QWidget *parentWidget, |
239 | QWidget *oldWidget, QWidget *newWidget, |
240 | const char *name) |
241 | { |
242 | QWidgetList list = qvariant_cast<QWidgetList>(v: parentWidget->property(name)); |
243 | const int index = list.indexOf(t: oldWidget); |
244 | if (index != -1) { |
245 | list.replace(i: index, t: newWidget); |
246 | parentWidget->setProperty(name, value: QVariant::fromValue(value: 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 | |
266 | 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 | |
281 | bool MorphWidgetCommand::addMorphMacro(QDesignerFormWindowInterface *fw, QWidget *w, const QString &newClass) |
282 | { |
283 | MorphWidgetCommand *morphCmd = new MorphWidgetCommand(fw); |
284 | if (!morphCmd->init(widget: w, newClassName: newClass)) { |
285 | qWarning(msg: "*** 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(text: morphCmd->text()); |
293 | // Have the signal slot/buddy editors add their commands to delete widget |
294 | if (FormWindowBase *fwb = qobject_cast<FormWindowBase*>(object: fw)) |
295 | fwb->emitWidgetRemoved(w); |
296 | |
297 | const QString newWidgetName = morphCmd->newWidgetName(); |
298 | us->push(cmd: morphCmd); |
299 | |
300 | // restore buddy using the QByteArray name. |
301 | if (buddyLabel) { |
302 | SetPropertyCommand *buddyCmd = new SetPropertyCommand(fw); |
303 | buddyCmd->init(object: buddyLabel, QStringLiteral("buddy" ), newValue: QVariant(newWidgetName.toUtf8())); |
304 | us->push(cmd: buddyCmd); |
305 | } |
306 | us->endMacro(); |
307 | return true; |
308 | } |
309 | |
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 | |
319 | bool MorphWidgetCommand::init(QWidget *widget, const QString &newClassName) |
320 | { |
321 | QDesignerFormWindowInterface *fw = formWindow(); |
322 | QDesignerFormEditorInterface *core = fw->core(); |
323 | |
324 | if (!canMorph(fw, w: widget)) |
325 | return false; |
326 | |
327 | const QString oldClassName = WidgetFactory::classNameOf(core, o: widget); |
328 | const QString oldName = widget->objectName(); |
329 | //: MorphWidgetCommand description |
330 | setText(QApplication::translate(context: "Command" , key: "Morph %1/'%2' into %3" ).arg(a1: oldClassName, a2: oldName, a3: newClassName)); |
331 | |
332 | m_beforeWidget = widget; |
333 | m_afterWidget = core->widgetFactory()->createWidget(name: newClassName, parentWidget: 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*>(manager: core->extensionManager(), object: m_afterWidget)) { |
343 | if (const int pageCount = childContainers(core, w: 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(name: qWidget); |
351 | page->setObjectName(name); |
352 | fw->ensureUniqueObjectName(object: page); |
353 | c->addWidget(widget: page); |
354 | core->metaDataBase()->add(object: page); |
355 | } |
356 | } |
357 | } |
358 | |
359 | // Copy over applicable properties |
360 | const QDesignerPropertySheetExtension *beforeSheet = qt_extension<QDesignerPropertySheetExtension*>(manager: core->extensionManager(), object: widget); |
361 | QDesignerPropertySheetExtension *afterSheet = qt_extension<QDesignerPropertySheetExtension*>(manager: core->extensionManager(), object: 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(index: i) && beforeSheet->isChanged(index: i)) { |
366 | const QString name = beforeSheet->propertyName(index: i); |
367 | if (name != objectNameProperty) { |
368 | const int afterIndex = afterSheet->indexOf(name); |
369 | if (afterIndex != -1 && afterSheet->isVisible(index: afterIndex) && afterSheet->propertyGroup(index: afterIndex) == beforeSheet->propertyGroup(index: i)) { |
370 | afterSheet->setProperty(index: i, value: beforeSheet->property(index: i)); |
371 | afterSheet->setChanged(index: i, changed: true); |
372 | } else { |
373 | // Some mismatch. The rest won't match, either |
374 | break; |
375 | } |
376 | } |
377 | } |
378 | return true; |
379 | } |
380 | |
381 | void MorphWidgetCommand::redo() |
382 | { |
383 | morph(before: m_beforeWidget, after: m_afterWidget); |
384 | } |
385 | |
386 | void MorphWidgetCommand::undo() |
387 | { |
388 | morph(before: m_afterWidget, after: m_beforeWidget); |
389 | } |
390 | |
391 | void MorphWidgetCommand::morph(QWidget *before, QWidget *after) |
392 | { |
393 | QDesignerFormWindowInterface *fw = formWindow(); |
394 | |
395 | fw->unmanageWidget(widget: 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(core: fw->core(), w: before); |
406 | QWidgetList afterChildContainers = childContainers(core: fw->core(), w: 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(widget: w)) { |
421 | const QRect geom = w->geometry(); |
422 | w->setParent(afterChildContainer); |
423 | w->setGeometry(geom); |
424 | } |
425 | } |
426 | } |
427 | } |
428 | afterChildContainer->setProperty(name: widgetOrderPropertyC, value: beforeChildContainer->property(name: widgetOrderPropertyC)); |
429 | afterChildContainer->setProperty(name: zOrderPropertyC, value: beforeChildContainer->property(name: zOrderPropertyC)); |
430 | } |
431 | |
432 | // 2) Replace the actual widget in the parent layout |
433 | after->setGeometry(oldGeom); |
434 | if (QLayout *containingLayout = LayoutInfo::managedLayout(core: fw->core(), widget: parent)) { |
435 | LayoutHelper *lh = LayoutHelper::createLayoutHelper(type: LayoutInfo::layoutType(core: fw->core(), layout: containingLayout)); |
436 | Q_ASSERT(lh); |
437 | lh->replaceWidget(lt: containingLayout, before, after); |
438 | delete lh; |
439 | } else if (QSplitter *splitter = qobject_cast<QSplitter *>(object: parent)) { |
440 | const int index = splitter->indexOf(w: before); |
441 | before->hide(); |
442 | before->setParent(nullptr); |
443 | splitter->insertWidget(index, widget: 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(parentWidget: parent, oldWidget: before, newWidget: after, name: widgetOrderPropertyC); |
455 | replaceWidgetListDynamicProperty(parentWidget: parent, oldWidget: before, newWidget: after, name: zOrderPropertyC); |
456 | |
457 | QDesignerMetaDataBaseItemInterface *formItem = fw->core()->metaDataBase()->item(object: fw); |
458 | QWidgetList tabOrder = formItem->tabOrder(); |
459 | const int tabIndex = tabOrder.indexOf(t: before); |
460 | if (tabIndex != -1) { |
461 | tabOrder.replace(i: tabIndex, t: after); |
462 | formItem->setTabOrder(tabOrder); |
463 | } |
464 | |
465 | after->show(); |
466 | fw->manageWidget(widget: after); |
467 | |
468 | fw->clearSelection(changePropertyDisplay: false); |
469 | fw->selectWidget(w: 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. */ |
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 *>(manager: core->extensionManager(), object: core)) |
488 | return false; |
489 | if (!fw->isManaged(widget: 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, widget: parent)) |
497 | if (pl->indexOf(w) < 0 || !core->metaDataBase()->item(object: pl)) |
498 | return false; |
499 | // Check Widget database |
500 | const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase(); |
501 | const int wdbindex = wdb->indexOfObject(object: w); |
502 | if (wdbindex == -1) |
503 | return false; |
504 | const bool isContainer = wdb->item(index: 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(object: cl)) |
516 | return false; |
517 | } |
518 | return true; |
519 | } |
520 | |
521 | QStringList MorphWidgetCommand::candidateClasses(QDesignerFormWindowInterface *fw, QWidget *w) |
522 | { |
523 | int childContainerCount; |
524 | MorphCategory cat; |
525 | if (!canMorph(fw, w, ptrToChildContainerCount: &childContainerCount, ptrToCat: &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(cat: 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(cat: MorphSimpleContainer); |
539 | break; |
540 | default: |
541 | break; |
542 | } |
543 | return rc; |
544 | } |
545 | |
546 | // MorphMenu |
547 | MorphMenu::(QObject *parent) : |
548 | QObject(parent) |
549 | { |
550 | } |
551 | |
552 | void MorphMenu::(QWidget *w, QDesignerFormWindowInterface *fw, ActionList& al) |
553 | { |
554 | if (populateMenu(w, fw)) |
555 | al.push_back(t: m_subMenuAction); |
556 | } |
557 | |
558 | void MorphMenu::(QWidget *w, QDesignerFormWindowInterface *fw, QMenu& m) |
559 | { |
560 | if (populateMenu(w, fw)) |
561 | m.addAction(action: m_subMenuAction); |
562 | } |
563 | |
564 | void MorphMenu::(const QString &newClassName) |
565 | { |
566 | MorphWidgetCommand::addMorphMacro(fw: m_formWindow, w: m_widget, newClass: newClassName); |
567 | } |
568 | |
569 | bool MorphMenu::(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(core: fw->core(), o: w); |
592 | |
593 | if (!m_subMenuAction) { |
594 | m_subMenuAction = new QAction(tr(s: "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(text: className, |
605 | object: this, slot: [this, className] { this->slotMorph(newClassName: className); }); |
606 | } |
607 | } |
608 | m_subMenuAction->setVisible(true); |
609 | return true; |
610 | } |
611 | |
612 | } // namespace qdesigner_internal |
613 | |
614 | QT_END_NAMESPACE |
615 | |