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