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 "promotiontaskmenu_p.h"
30 #include "qdesigner_promotiondialog_p.h"
31 #include "widgetfactory_p.h"
32 #include "metadatabase_p.h"
33 #include "widgetdatabase_p.h"
34 #include "qdesigner_command_p.h"
35 #include "signalslotdialog_p.h"
36 #include "qdesigner_objectinspector_p.h"
37 #include "abstractintrospection_p.h"
38 
39 #include <QtDesigner/abstractformwindow.h>
40 #include <QtDesigner/abstractformwindowcursor.h>
41 #include <QtDesigner/abstractlanguage.h>
42 #include <QtDesigner/abstractformeditor.h>
43 #include <QtDesigner/qextensionmanager.h>
44 
45 #include <QtWidgets/qaction.h>
46 #include <QtWidgets/qwidget.h>
47 #include <QtWidgets/qmenu.h>
48 #include <QtCore/qdebug.h>
49 
50 QT_BEGIN_NAMESPACE
51 
separatorAction(QObject * parent)52 static QAction *separatorAction(QObject *parent)
53 {
54     QAction *rc = new  QAction(parent);
55     rc->setSeparator(true);
56     return rc;
57 }
58 
languageExtension(QDesignerFormEditorInterface * core)59 static inline QDesignerLanguageExtension *languageExtension(QDesignerFormEditorInterface *core)
60 {
61     return qt_extension<QDesignerLanguageExtension*>(core->extensionManager(), core);
62 }
63 
64 namespace qdesigner_internal {
65 
PromotionTaskMenu(QWidget * widget,Mode mode,QObject * parent)66 PromotionTaskMenu::PromotionTaskMenu(QWidget *widget,Mode mode, QObject *parent) :
67     QObject(parent),
68     m_mode(mode),
69     m_widget(widget),
70     m_globalEditAction(new QAction(tr("Promoted widgets..."), this)),
71     m_EditPromoteToAction(new QAction(tr("Promote to ..."), this)),
72     m_EditSignalsSlotsAction(new QAction(tr("Change signals/slots..."), this)),
73     m_promoteLabel(tr("Promote to")),
74     m_demoteLabel(tr("Demote to %1"))
75 {
76     connect(m_globalEditAction, &QAction::triggered, this, &PromotionTaskMenu::slotEditPromotedWidgets);
77     connect(m_EditPromoteToAction, &QAction::triggered, this, &PromotionTaskMenu::slotEditPromoteTo);
78     connect(m_EditSignalsSlotsAction, &QAction::triggered, this, &PromotionTaskMenu::slotEditSignalsSlots);
79 }
80 
mode() const81 PromotionTaskMenu::Mode PromotionTaskMenu::mode() const
82 {
83     return m_mode;
84 }
85 
setMode(Mode m)86 void PromotionTaskMenu::setMode(Mode m)
87 {
88     m_mode = m;
89 }
90 
setWidget(QWidget * widget)91 void PromotionTaskMenu::setWidget(QWidget *widget)
92 {
93     m_widget = widget;
94 }
95 
setPromoteLabel(const QString & promoteLabel)96 void PromotionTaskMenu::setPromoteLabel(const QString &promoteLabel)
97 {
98     m_promoteLabel = promoteLabel;
99 }
100 
setEditPromoteToLabel(const QString & promoteEditLabel)101 void PromotionTaskMenu::setEditPromoteToLabel(const QString &promoteEditLabel)
102 {
103     m_EditPromoteToAction->setText(promoteEditLabel);
104 }
105 
setDemoteLabel(const QString & demoteLabel)106 void PromotionTaskMenu::setDemoteLabel(const QString &demoteLabel)
107 {
108     m_demoteLabel = demoteLabel;
109 }
110 
createPromotionActions(QDesignerFormWindowInterface * formWindow)111 PromotionTaskMenu::PromotionState  PromotionTaskMenu::createPromotionActions(QDesignerFormWindowInterface *formWindow)
112 {
113     // clear out old
114     if (!m_promotionActions.isEmpty()) {
115         qDeleteAll(m_promotionActions);
116         m_promotionActions.clear();
117     }
118     // No promotion of main container
119     if (formWindow->mainContainer() == m_widget)
120         return NotApplicable;
121 
122     // Check for a homogenous selection
123     const PromotionSelectionList promotionSelection = promotionSelectionList(formWindow);
124 
125     if (promotionSelection.isEmpty())
126         return NoHomogenousSelection;
127 
128     QDesignerFormEditorInterface *core = formWindow->core();
129     // if it is promoted: demote only.
130     if (isPromoted(formWindow->core(), m_widget)) {
131         const QString label = m_demoteLabel.arg( promotedExtends(core , m_widget));
132         QAction *demoteAction = new QAction(label, this);
133         connect(demoteAction, &QAction::triggered, this, &PromotionTaskMenu::slotDemoteFromCustomWidget);
134         m_promotionActions.push_back(demoteAction);
135         return CanDemote;
136     }
137     // figure out candidates
138     const QString baseClassName = WidgetFactory::classNameOf(core,  m_widget);
139     const WidgetDataBaseItemList candidates = promotionCandidates(core->widgetDataBase(), baseClassName );
140     if (candidates.isEmpty()) {
141         // Is this thing promotable at all?
142         return QDesignerPromotionDialog::baseClassNames(core->promotion()).contains(baseClassName) ?  CanPromote : NotApplicable;
143     }
144 
145     QMenu *candidatesMenu = new QMenu();
146     // Create a sub menu
147     const WidgetDataBaseItemList::const_iterator cend = candidates.constEnd();
148     // Set up actions and map class names
149     for (WidgetDataBaseItemList::const_iterator it = candidates.constBegin(); it != cend; ++it) {
150         const QString customClassName = (*it)->name();
151         candidatesMenu->addAction(customClassName,
152                                   this, [this, customClassName] { this->slotPromoteToCustomWidget(customClassName); });
153     }
154     // Sub menu action
155     QAction *subMenuAction = new QAction(m_promoteLabel, this);
156     subMenuAction->setMenu(candidatesMenu);
157     m_promotionActions.push_back(subMenuAction);
158     return CanPromote;
159 }
160 
addActions(unsigned separatorFlags,ActionList & actionList)161 void PromotionTaskMenu::addActions(unsigned separatorFlags, ActionList &actionList)
162 {
163     addActions(formWindow(), separatorFlags, actionList);
164 }
165 
addActions(QDesignerFormWindowInterface * fw,unsigned flags,ActionList & actionList)166 void PromotionTaskMenu::addActions(QDesignerFormWindowInterface *fw, unsigned flags,
167                                    ActionList &actionList)
168 {
169     Q_ASSERT(m_widget);
170     const int previousSize = actionList.size();
171     const PromotionState promotionState = createPromotionActions(fw);
172 
173     // Promotion candidates/demote
174     actionList += m_promotionActions;
175 
176     // Edit action depending on context
177     switch (promotionState) {
178     case  CanPromote:
179         actionList += m_EditPromoteToAction;
180         break;
181     case CanDemote:
182         if (!(flags & SuppressGlobalEdit))
183             actionList += m_globalEditAction;
184         if (!languageExtension(fw->core())) {
185             actionList += separatorAction(this);
186             actionList += m_EditSignalsSlotsAction;
187         }
188         break;
189     default:
190         if (!(flags & SuppressGlobalEdit))
191             actionList += m_globalEditAction;
192         break;
193     }
194     // Add separators if required
195     if (actionList.size() > previousSize) {
196         if (flags &  LeadingSeparator)
197             actionList.insert(previousSize, separatorAction(this));
198         if (flags & TrailingSeparator)
199             actionList += separatorAction(this);
200     }
201 }
202 
addActions(QDesignerFormWindowInterface * fw,unsigned flags,QMenu * menu)203 void  PromotionTaskMenu::addActions(QDesignerFormWindowInterface *fw, unsigned flags, QMenu *menu)
204 {
205     ActionList actionList;
206     addActions(fw, flags, actionList);
207     menu->addActions(actionList);
208 }
209 
addActions(unsigned flags,QMenu * menu)210 void  PromotionTaskMenu::addActions(unsigned flags, QMenu *menu)
211 {
212     addActions(formWindow(), flags, menu);
213 }
214 
promoteTo(QDesignerFormWindowInterface * fw,const QString & customClassName)215 void PromotionTaskMenu::promoteTo(QDesignerFormWindowInterface *fw, const QString &customClassName)
216 {
217     Q_ASSERT(m_widget);
218     PromoteToCustomWidgetCommand *cmd = new PromoteToCustomWidgetCommand(fw);
219     cmd->init(promotionSelectionList(fw), customClassName);
220     fw->commandHistory()->push(cmd);
221 }
222 
223 
slotPromoteToCustomWidget(const QString & customClassName)224 void  PromotionTaskMenu::slotPromoteToCustomWidget(const QString &customClassName)
225 {
226     promoteTo(formWindow(), customClassName);
227 }
228 
slotDemoteFromCustomWidget()229 void PromotionTaskMenu::slotDemoteFromCustomWidget()
230 {
231     QDesignerFormWindowInterface *fw = formWindow();
232     const PromotionSelectionList promotedWidgets = promotionSelectionList(fw);
233     Q_ASSERT(!promotedWidgets.isEmpty() && isPromoted(fw->core(), promotedWidgets.constFirst()));
234 
235     // ### use the undo stack
236     DemoteFromCustomWidgetCommand *cmd = new DemoteFromCustomWidgetCommand(fw);
237     cmd->init(promotedWidgets);
238     fw->commandHistory()->push(cmd);
239 }
240 
slotEditPromoteTo()241 void PromotionTaskMenu::slotEditPromoteTo()
242 {
243     Q_ASSERT(m_widget);
244     // Check whether invoked over a promotable widget
245     QDesignerFormWindowInterface *fw = formWindow();
246     QDesignerFormEditorInterface *core = fw->core();
247     const QString base_class_name = WidgetFactory::classNameOf(core, m_widget);
248     Q_ASSERT(QDesignerPromotionDialog::baseClassNames(core->promotion()).contains(base_class_name));
249     // Show over promotable widget
250     QString promoteToClassName;
251     QDialog *promotionEditor = nullptr;
252     if (QDesignerLanguageExtension *lang = languageExtension(core))
253         promotionEditor = lang->createPromotionDialog(core, base_class_name, &promoteToClassName, fw);
254     if (!promotionEditor)
255         promotionEditor = new QDesignerPromotionDialog(core, fw, base_class_name, &promoteToClassName);
256     if (promotionEditor->exec() == QDialog::Accepted && !promoteToClassName.isEmpty()) {
257         promoteTo(fw, promoteToClassName);
258     }
259     delete promotionEditor;
260 }
261 
slotEditPromotedWidgets()262 void PromotionTaskMenu::slotEditPromotedWidgets()
263 {
264     // Global context, show over non-promotable widget
265     QDesignerFormWindowInterface *fw = formWindow();
266     if (!fw)
267         return;
268     editPromotedWidgets(fw->core(), fw);
269 }
270 
promotionSelectionList(QDesignerFormWindowInterface * formWindow) const271 PromotionTaskMenu::PromotionSelectionList PromotionTaskMenu::promotionSelectionList(QDesignerFormWindowInterface *formWindow) const
272 {
273     // In multi selection mode, check for a homogenous selection (same class, same promotion state)
274     // and return the list if this is the case. Also make sure m_widget
275     // is the last widget in the list so that it is re-selected as the last
276     // widget by the promotion commands.
277 
278     PromotionSelectionList rc;
279 
280     if (m_mode != ModeSingleWidget) {
281         QDesignerFormEditorInterface *core = formWindow->core();
282         const QDesignerIntrospectionInterface *intro = core->introspection();
283         const QString className = intro->metaObject(m_widget)->className();
284         const bool promoted = isPromoted(formWindow->core(), m_widget);
285         // Just in case someone plugged an old-style Object Inspector
286         if (QDesignerObjectInspector *designerObjectInspector = qobject_cast<QDesignerObjectInspector *>(core->objectInspector())) {
287             Selection s;
288             designerObjectInspector->getSelection(s);
289             // Find objects of similar state
290             const QWidgetList &source = m_mode == ModeManagedMultiSelection ? s.managed : s.unmanaged;
291             const QWidgetList::const_iterator cend = source.constEnd();
292             for (QWidgetList::const_iterator it = source.constBegin(); it != cend; ++it) {
293                 QWidget *w = *it;
294                 if (w != m_widget) {
295                     // Selection state mismatch
296                     if (intro->metaObject(w)->className() != className || isPromoted(core, w) !=  promoted)
297                         return PromotionSelectionList();
298                     rc.push_back(w);
299                 }
300             }
301         }
302     }
303 
304     rc.push_back(m_widget);
305     return rc;
306 }
307 
formWindow() const308 QDesignerFormWindowInterface *PromotionTaskMenu::formWindow() const
309 {
310     // Use the QObject overload of  QDesignerFormWindowInterface::findFormWindow since that works
311     // for QDesignerMenus also.
312     QObject *o = m_widget;
313     QDesignerFormWindowInterface *result = QDesignerFormWindowInterface::findFormWindow(o);
314     Q_ASSERT(result != nullptr);
315     return result;
316 }
317 
editPromotedWidgets(QDesignerFormEditorInterface * core,QWidget * parent)318 void PromotionTaskMenu::editPromotedWidgets(QDesignerFormEditorInterface *core, QWidget* parent) {
319     QDesignerLanguageExtension *lang = languageExtension(core);
320     // Show over non-promotable widget
321     QDialog *promotionEditor =  nullptr;
322     if (lang)
323         lang->createPromotionDialog(core, parent);
324     if (!promotionEditor)
325         promotionEditor = new QDesignerPromotionDialog(core, parent);
326     promotionEditor->exec();
327     delete promotionEditor;
328 }
329 
slotEditSignalsSlots()330 void PromotionTaskMenu::slotEditSignalsSlots()
331 {
332     QDesignerFormWindowInterface *fw = formWindow();
333     if (!fw)
334         return;
335     SignalSlotDialog::editPromotedClass(fw->core(), m_widget, fw);
336 }
337 } // namespace qdesigner_internal
338 
339 QT_END_NAMESPACE
340