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