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 "actioneditor_p.h"
30 #include "actionrepository_p.h"
31 #include "iconloader_p.h"
32 #include "newactiondialog_p.h"
33 #include "qdesigner_menu_p.h"
34 #include "qdesigner_command_p.h"
35 #include "qdesigner_propertycommand_p.h"
36 #include "qdesigner_objectinspector_p.h"
37 #include "qdesigner_utils_p.h"
38 #include "qsimpleresource_p.h"
39 #include "formwindowbase_p.h"
40 #include "qdesigner_taskmenu_p.h"
41
42 #include <QtDesigner/abstractformeditor.h>
43 #include <QtDesigner/abstractpropertyeditor.h>
44 #include <QtDesigner/propertysheet.h>
45 #include <QtDesigner/qextensionmanager.h>
46 #include <QtDesigner/abstractmetadatabase.h>
47 #include <QtDesigner/abstractsettings.h>
48
49 #include <QtWidgets/qmenu.h>
50 #include <QtWidgets/qtoolbar.h>
51 #include <QtWidgets/qsplitter.h>
52 #include <QtWidgets/qaction.h>
53 #include <QtWidgets/qapplication.h>
54 #if QT_CONFIG(clipboard)
55 #include <QtGui/qclipboard.h>
56 #endif
57 #include <QtWidgets/qitemdelegate.h>
58 #include <QtGui/qpainter.h>
59 #include <QtWidgets/qboxlayout.h>
60 #include <QtWidgets/qlineedit.h>
61 #include <QtWidgets/qlabel.h>
62 #include <QtWidgets/qpushbutton.h>
63 #include <QtWidgets/qtoolbutton.h>
64 #include <QtGui/qevent.h>
65 #include <QtCore/qitemselectionmodel.h>
66
67 #include <QtCore/qregularexpression.h>
68 #include <QtCore/qdebug.h>
69 #include <QtCore/qbuffer.h>
70
71 Q_DECLARE_METATYPE(QAction*)
72
73 QT_BEGIN_NAMESPACE
74
75 static const char *actionEditorViewModeKey = "ActionEditorViewMode";
76
77 static const char *iconPropertyC = "icon";
78 static const char *shortcutPropertyC = "shortcut";
79 static const char *toolTipPropertyC = "toolTip";
80 static const char *checkablePropertyC = "checkable";
81 static const char *objectNamePropertyC = "objectName";
82 static const char *textPropertyC = "text";
83
84 namespace qdesigner_internal {
85 //-------- ActionGroupDelegate
86 class ActionGroupDelegate: public QItemDelegate
87 {
88 public:
ActionGroupDelegate(QObject * parent)89 ActionGroupDelegate(QObject *parent)
90 : QItemDelegate(parent) {}
91
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const92 void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
93 {
94 if (option.state & QStyle::State_Selected)
95 painter->fillRect(option.rect, option.palette.highlight());
96
97 QItemDelegate::paint(painter, option, index);
98 }
99
drawFocus(QPainter *,const QStyleOptionViewItem &,const QRect &) const100 void drawFocus(QPainter *, const QStyleOptionViewItem &, const QRect &) const override {}
101 };
102
103 //-------- ActionEditor
104 ObjectNamingMode ActionEditor::m_objectNamingMode = Underscore; // fixme Qt 6: CamelCase
105
ActionEditor(QDesignerFormEditorInterface * core,QWidget * parent,Qt::WindowFlags flags)106 ActionEditor::ActionEditor(QDesignerFormEditorInterface *core, QWidget *parent, Qt::WindowFlags flags) :
107 QDesignerActionEditorInterface(parent, flags),
108 m_core(core),
109 m_actionGroups(nullptr),
110 m_actionView(new ActionView),
111 m_actionNew(new QAction(tr("New..."), this)),
112 m_actionEdit(new QAction(tr("Edit..."), this)),
113 m_actionNavigateToSlot(new QAction(tr("Go to slot..."), this)),
114 #if QT_CONFIG(clipboard)
115 m_actionCopy(new QAction(tr("Copy"), this)),
116 m_actionCut(new QAction(tr("Cut"), this)),
117 m_actionPaste(new QAction(tr("Paste"), this)),
118 #endif
119 m_actionSelectAll(new QAction(tr("Select all"), this)),
120 m_actionDelete(new QAction(tr("Delete"), this)),
121 m_viewModeGroup(new QActionGroup(this)),
122 m_iconViewAction(nullptr),
123 m_listViewAction(nullptr),
124 m_filterWidget(nullptr)
125 {
126 m_actionView->initialize(m_core);
127 m_actionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
128 setWindowTitle(tr("Actions"));
129
130 QVBoxLayout *l = new QVBoxLayout(this);
131 l->setContentsMargins(QMargins());
132 l->setSpacing(0);
133
134 QToolBar *toolbar = new QToolBar;
135 toolbar->setIconSize(QSize(22, 22));
136 toolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
137 l->addWidget(toolbar);
138 // edit actions
139 QIcon documentNewIcon = QIcon::fromTheme(QStringLiteral("document-new"), createIconSet(QStringLiteral("filenew.png")));
140 m_actionNew->setIcon(documentNewIcon);
141 m_actionNew->setEnabled(false);
142 connect(m_actionNew, &QAction::triggered, this, &ActionEditor::slotNewAction);
143 toolbar->addAction(m_actionNew);
144
145 connect(m_actionSelectAll, &QAction::triggered, m_actionView, &ActionView::selectAll);
146
147 #if QT_CONFIG(clipboard)
148 m_actionCut->setEnabled(false);
149 connect(m_actionCut, &QAction::triggered, this, &ActionEditor::slotCut);
150 QIcon editCutIcon = QIcon::fromTheme(QStringLiteral("edit-cut"), createIconSet(QStringLiteral("editcut.png")));
151 m_actionCut->setIcon(editCutIcon);
152
153 m_actionCopy->setEnabled(false);
154 connect(m_actionCopy, &QAction::triggered, this, &ActionEditor::slotCopy);
155 QIcon editCopyIcon = QIcon::fromTheme(QStringLiteral("edit-copy"), createIconSet(QStringLiteral("editcopy.png")));
156 m_actionCopy->setIcon(editCopyIcon);
157 toolbar->addAction(m_actionCopy);
158
159 connect(m_actionPaste, &QAction::triggered, this, &ActionEditor::slotPaste);
160 QIcon editPasteIcon = QIcon::fromTheme(QStringLiteral("edit-paste"), createIconSet(QStringLiteral("editpaste.png")));
161 m_actionPaste->setIcon(editPasteIcon);
162 toolbar->addAction(m_actionPaste);
163 #endif
164
165 m_actionEdit->setEnabled(false);
166 connect(m_actionEdit, &QAction::triggered, this, &ActionEditor::editCurrentAction);
167
168 connect(m_actionNavigateToSlot, &QAction::triggered, this, &ActionEditor::navigateToSlotCurrentAction);
169
170 QIcon editDeleteIcon = QIcon::fromTheme(QStringLiteral("edit-delete"), createIconSet(QStringLiteral("editdelete.png")));
171 m_actionDelete->setIcon(editDeleteIcon);
172 m_actionDelete->setEnabled(false);
173 connect(m_actionDelete, &QAction::triggered, this, &ActionEditor::slotDelete);
174 toolbar->addAction(m_actionDelete);
175
176 // Toolbutton with menu containing action group for detailed/icon view. Steal the icons from the file dialog.
177 //
178 QMenu *configureMenu;
179 toolbar->addWidget(createConfigureMenuButton(tr("Configure Action Editor"), &configureMenu));
180
181 connect(m_viewModeGroup, &QActionGroup::triggered, this, &ActionEditor::slotViewMode);
182 m_iconViewAction = m_viewModeGroup->addAction(tr("Icon View"));
183 m_iconViewAction->setData(QVariant(ActionView::IconView));
184 m_iconViewAction->setCheckable(true);
185 m_iconViewAction->setIcon(style()->standardIcon (QStyle::SP_FileDialogListView));
186 configureMenu->addAction(m_iconViewAction);
187
188 m_listViewAction = m_viewModeGroup->addAction(tr("Detailed View"));
189 m_listViewAction->setData(QVariant(ActionView::DetailedView));
190 m_listViewAction->setCheckable(true);
191 m_listViewAction->setIcon(style()->standardIcon (QStyle::SP_FileDialogDetailedView));
192 configureMenu->addAction(m_listViewAction);
193 // filter
194 m_filterWidget = new QWidget(toolbar);
195 QHBoxLayout *filterLayout = new QHBoxLayout(m_filterWidget);
196 filterLayout->setContentsMargins(0, 0, 0, 0);
197 QLineEdit *filterLineEdit = new QLineEdit(m_filterWidget);
198 connect(filterLineEdit, &QLineEdit::textChanged, this, &ActionEditor::setFilter);
199 filterLineEdit->setPlaceholderText(tr("Filter"));
200 filterLineEdit->setClearButtonEnabled(true);
201 filterLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
202 filterLayout->addWidget(filterLineEdit);
203 m_filterWidget->setEnabled(false);
204 toolbar->addWidget(m_filterWidget);
205
206 // main layout
207 QSplitter *splitter = new QSplitter(Qt::Horizontal);
208 splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
209
210 splitter->addWidget(m_actionView);
211 l->addWidget(splitter);
212
213 #if 0 // ### implement me
214 m_actionGroups = new QListWidget(splitter);
215 splitter->addWidget(m_actionGroups);
216 m_actionGroups->setItemDelegate(new ActionGroupDelegate(m_actionGroups));
217 m_actionGroups->setMovement(QListWidget::Static);
218 m_actionGroups->setResizeMode(QListWidget::Fixed);
219 m_actionGroups->setIconSize(QSize(48, 48));
220 m_actionGroups->setFlow(QListWidget::TopToBottom);
221 m_actionGroups->setViewMode(QListWidget::IconMode);
222 m_actionGroups->setWrapping(false);
223 #endif
224
225 connect(m_actionView, &ActionView::resourceImageDropped,
226 this, &ActionEditor::resourceImageDropped);
227
228 connect(m_actionView, &ActionView::currentChanged,this, &ActionEditor::slotCurrentItemChanged);
229 // make it possible for vs integration to reimplement edit action dialog
230 connect(m_actionView, &ActionView::activated, this, &ActionEditor::itemActivated);
231
232 connect(m_actionView, &ActionView::selectionChanged,
233 this, &ActionEditor::slotSelectionChanged);
234
235 connect(m_actionView, &ActionView::contextMenuRequested,
236 this, &ActionEditor::slotContextMenuRequested);
237
238 connect(this, &ActionEditor::itemActivated, this, &ActionEditor::editAction);
239
240 restoreSettings();
241 updateViewModeActions();
242 }
243
244 // Utility to create a configure button with menu for usage on toolbars
createConfigureMenuButton(const QString & t,QMenu ** ptrToMenu)245 QToolButton *ActionEditor::createConfigureMenuButton(const QString &t, QMenu **ptrToMenu)
246 {
247 QToolButton *configureButton = new QToolButton;
248 QAction *configureAction = new QAction(t, configureButton);
249 QIcon configureIcon = QIcon::fromTheme(QStringLiteral("document-properties"), createIconSet(QStringLiteral("configure.png")));
250 configureAction->setIcon(configureIcon);
251 QMenu *configureMenu = new QMenu;
252 configureAction->setMenu(configureMenu);
253 configureButton->setDefaultAction(configureAction);
254 configureButton->setPopupMode(QToolButton::InstantPopup);
255 *ptrToMenu = configureMenu;
256 return configureButton;
257 }
258
~ActionEditor()259 ActionEditor::~ActionEditor()
260 {
261 saveSettings();
262 }
263
actionNew() const264 QAction *ActionEditor::actionNew() const
265 {
266 return m_actionNew;
267 }
268
actionDelete() const269 QAction *ActionEditor::actionDelete() const
270 {
271 return m_actionDelete;
272 }
273
formWindow() const274 QDesignerFormWindowInterface *ActionEditor::formWindow() const
275 {
276 return m_formWindow;
277 }
278
setFormWindow(QDesignerFormWindowInterface * formWindow)279 void ActionEditor::setFormWindow(QDesignerFormWindowInterface *formWindow)
280 {
281 if (formWindow != nullptr && formWindow->mainContainer() == nullptr)
282 formWindow = nullptr;
283
284 // we do NOT rely on this function to update the action editor
285 if (m_formWindow == formWindow)
286 return;
287
288 if (m_formWindow != nullptr) {
289 const ActionList actionList = m_formWindow->mainContainer()->findChildren<QAction*>();
290 for (QAction *action : actionList)
291 disconnect(action, &QAction::changed, this, &ActionEditor::slotActionChanged);
292 }
293
294 m_formWindow = formWindow;
295
296 m_actionView->model()->clearActions();
297
298 m_actionEdit->setEnabled(false);
299 #if QT_CONFIG(clipboard)
300 m_actionCopy->setEnabled(false);
301 m_actionCut->setEnabled(false);
302 #endif
303 m_actionDelete->setEnabled(false);
304
305 if (!formWindow || !formWindow->mainContainer()) {
306 m_actionNew->setEnabled(false);
307 m_filterWidget->setEnabled(false);
308 return;
309 }
310
311 m_actionNew->setEnabled(true);
312 m_filterWidget->setEnabled(true);
313
314 const ActionList actionList = formWindow->mainContainer()->findChildren<QAction*>();
315 for (QAction *action : actionList)
316 if (!action->isSeparator() && core()->metaDataBase()->item(action) != nullptr) {
317 // Show unless it has a menu. However, listen for change on menu actions also as it might be removed
318 if (!action->menu())
319 m_actionView->model()->addAction(action);
320 connect(action, &QAction::changed, this, &ActionEditor::slotActionChanged);
321 }
322
323 setFilter(m_filter);
324 }
325
slotSelectionChanged(const QItemSelection & selected,const QItemSelection &)326 void ActionEditor::slotSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/)
327 {
328 const bool hasSelection = !selected.indexes().isEmpty();
329 #if QT_CONFIG(clipboard)
330 m_actionCopy->setEnabled(hasSelection);
331 m_actionCut->setEnabled(hasSelection);
332 #endif
333 m_actionDelete->setEnabled(hasSelection);
334 }
335
slotCurrentItemChanged(QAction * action)336 void ActionEditor::slotCurrentItemChanged(QAction *action)
337 {
338 QDesignerFormWindowInterface *fw = formWindow();
339 if (!fw)
340 return;
341
342 const bool hasCurrentAction = action != nullptr;
343 m_actionEdit->setEnabled(hasCurrentAction);
344
345 if (!action) {
346 fw->clearSelection();
347 return;
348 }
349
350 QDesignerObjectInspector *oi = qobject_cast<QDesignerObjectInspector *>(core()->objectInspector());
351
352 if (action->associatedWidgets().isEmpty()) {
353 // Special case: action not in object tree. Deselect all and set in property editor
354 fw->clearSelection(false);
355 if (oi)
356 oi->clearSelection();
357 core()->propertyEditor()->setObject(action);
358 } else {
359 if (oi)
360 oi->selectObject(action);
361 }
362 }
363
slotActionChanged()364 void ActionEditor::slotActionChanged()
365 {
366 QAction *action = qobject_cast<QAction*>(sender());
367 Q_ASSERT(action != nullptr);
368
369 ActionModel *model = m_actionView->model();
370 const int row = model->findAction(action);
371 if (row == -1) {
372 if (action->menu() == nullptr) // action got its menu deleted, create item
373 model->addAction(action);
374 } else if (action->menu() != nullptr) { // action got its menu created, remove item
375 model->removeRow(row);
376 } else {
377 // action text or icon changed, update item
378 model->update(row);
379 }
380 }
381
core() const382 QDesignerFormEditorInterface *ActionEditor::core() const
383 {
384 return m_core;
385 }
386
filter() const387 QString ActionEditor::filter() const
388 {
389 return m_filter;
390 }
391
setFilter(const QString & f)392 void ActionEditor::setFilter(const QString &f)
393 {
394 m_filter = f;
395 m_actionView->filter(m_filter);
396 }
397
398 // Set changed state of icon property, reset when icon is cleared
refreshIconPropertyChanged(const QAction * action,QDesignerPropertySheetExtension * sheet)399 static void refreshIconPropertyChanged(const QAction *action, QDesignerPropertySheetExtension *sheet)
400 {
401 sheet->setChanged(sheet->indexOf(QLatin1String(iconPropertyC)), !action->icon().isNull());
402 }
403
manageAction(QAction * action)404 void ActionEditor::manageAction(QAction *action)
405 {
406 action->setParent(formWindow()->mainContainer());
407 core()->metaDataBase()->add(action);
408
409 if (action->isSeparator() || action->menu() != nullptr)
410 return;
411
412 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core()->extensionManager(), action);
413 sheet->setChanged(sheet->indexOf(QLatin1String(objectNamePropertyC)), true);
414 sheet->setChanged(sheet->indexOf(QLatin1String(textPropertyC)), true);
415 refreshIconPropertyChanged(action, sheet);
416
417 m_actionView->setCurrentIndex(m_actionView->model()->addAction(action));
418 connect(action, &QAction::changed, this, &ActionEditor::slotActionChanged);
419 }
420
unmanageAction(QAction * action)421 void ActionEditor::unmanageAction(QAction *action)
422 {
423 core()->metaDataBase()->remove(action);
424 action->setParent(nullptr);
425
426 disconnect(action, &QAction::changed, this, &ActionEditor::slotActionChanged);
427
428 const int row = m_actionView->model()->findAction(action);
429 if (row != -1)
430 m_actionView->model()->remove(row);
431 }
432
433 // Set an initial property and mark it as changed in the sheet
setInitialProperty(QDesignerPropertySheetExtension * sheet,const QString & name,const QVariant & value)434 static void setInitialProperty(QDesignerPropertySheetExtension *sheet, const QString &name, const QVariant &value)
435 {
436 const int index = sheet->indexOf(name);
437 Q_ASSERT(index != -1);
438 sheet->setProperty(index, value);
439 sheet->setChanged(index, true);
440 }
441
slotNewAction()442 void ActionEditor::slotNewAction()
443 {
444 NewActionDialog dlg(this);
445 dlg.setWindowTitle(tr("New action"));
446
447 if (dlg.exec() == QDialog::Accepted) {
448 const ActionData actionData = dlg.actionData();
449 m_actionView->clearSelection();
450 QAction *action = new QAction(formWindow());
451 action->setObjectName(actionData.name);
452 formWindow()->ensureUniqueObjectName(action);
453 action->setText(actionData.text);
454
455 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core()->extensionManager(), action);
456 if (!actionData.toolTip.isEmpty())
457 setInitialProperty(sheet, QLatin1String(toolTipPropertyC), actionData.toolTip);
458
459 if (actionData.checkable)
460 setInitialProperty(sheet, QLatin1String(checkablePropertyC), QVariant(true));
461
462 if (!actionData.keysequence.value().isEmpty())
463 setInitialProperty(sheet, QLatin1String(shortcutPropertyC), QVariant::fromValue(actionData.keysequence));
464
465 sheet->setProperty(sheet->indexOf(QLatin1String(iconPropertyC)), QVariant::fromValue(actionData.icon));
466
467 AddActionCommand *cmd = new AddActionCommand(formWindow());
468 cmd->init(action);
469 formWindow()->commandHistory()->push(cmd);
470 }
471 }
472
473 // return a FormWindow command to apply an icon or a reset command in case it
474 // is empty.
475
setIconPropertyCommand(const PropertySheetIconValue & newIcon,QAction * action,QDesignerFormWindowInterface * fw)476 static QDesignerFormWindowCommand *setIconPropertyCommand(const PropertySheetIconValue &newIcon, QAction *action, QDesignerFormWindowInterface *fw)
477 {
478 const QString iconProperty = QLatin1String(iconPropertyC);
479 if (newIcon.isEmpty()) {
480 ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
481 cmd->init(action, iconProperty);
482 return cmd;
483 }
484 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
485 cmd->init(action, iconProperty, QVariant::fromValue(newIcon));
486 return cmd;
487 }
488
489 // return a FormWindow command to apply a QKeySequence or a reset command
490 // in case it is empty.
491
setKeySequencePropertyCommand(const PropertySheetKeySequenceValue & ks,QAction * action,QDesignerFormWindowInterface * fw)492 static QDesignerFormWindowCommand *setKeySequencePropertyCommand(const PropertySheetKeySequenceValue &ks, QAction *action, QDesignerFormWindowInterface *fw)
493 {
494 const QString shortcutProperty = QLatin1String(shortcutPropertyC);
495 if (ks.value().isEmpty()) {
496 ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
497 cmd->init(action, shortcutProperty);
498 return cmd;
499 }
500 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
501 cmd->init(action, shortcutProperty, QVariant::fromValue(ks));
502 return cmd;
503 }
504
505 // return a FormWindow command to apply a POD value or reset command in case
506 // it is equal to the default value.
507
508 template <class T>
setPropertyCommand(const QString & name,T value,T defaultValue,QObject * o,QDesignerFormWindowInterface * fw)509 QDesignerFormWindowCommand *setPropertyCommand(const QString &name, T value, T defaultValue,
510 QObject *o, QDesignerFormWindowInterface *fw)
511 {
512 if (value == defaultValue) {
513 ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
514 cmd->init(o, name);
515 return cmd;
516 }
517 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
518 cmd->init(o, name, QVariant(value));
519 return cmd;
520 }
521
522 // Return the text value of a string property via PropertySheetStringValue
textPropertyValue(const QDesignerPropertySheetExtension * sheet,const QString & name)523 static inline QString textPropertyValue(const QDesignerPropertySheetExtension *sheet, const QString &name)
524 {
525 const int index = sheet->indexOf(name);
526 Q_ASSERT(index != -1);
527 const PropertySheetStringValue ps = qvariant_cast<PropertySheetStringValue>(sheet->property(index));
528 return ps.value();
529 }
530
editAction(QAction * action,int column)531 void ActionEditor::editAction(QAction *action, int column)
532 {
533 if (!action)
534 return;
535
536 NewActionDialog dlg(this);
537 dlg.setWindowTitle(tr("Edit action"));
538
539 ActionData oldActionData;
540 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core()->extensionManager(), action);
541 oldActionData.name = action->objectName();
542 oldActionData.text = action->text();
543 oldActionData.toolTip = textPropertyValue(sheet, QLatin1String(toolTipPropertyC));
544 oldActionData.icon = qvariant_cast<PropertySheetIconValue>(sheet->property(sheet->indexOf(QLatin1String(iconPropertyC))));
545 oldActionData.keysequence = ActionModel::actionShortCut(sheet);
546 oldActionData.checkable = action->isCheckable();
547 dlg.setActionData(oldActionData);
548
549 switch (column) {
550 case qdesigner_internal::ActionModel::NameColumn:
551 dlg.focusName();
552 break;
553 case qdesigner_internal::ActionModel::TextColumn:
554 dlg.focusText();
555 break;
556 case qdesigner_internal::ActionModel::ShortCutColumn:
557 dlg.focusShortcut();
558 break;
559 case qdesigner_internal::ActionModel::CheckedColumn:
560 dlg.focusCheckable();
561 break;
562 case qdesigner_internal::ActionModel::ToolTipColumn:
563 dlg.focusTooltip();
564 break;
565 }
566
567 if (!dlg.exec())
568 return;
569
570 // figure out changes and whether to start a macro
571 const ActionData newActionData = dlg.actionData();
572 const unsigned changeMask = newActionData.compare(oldActionData);
573 if (changeMask == 0u)
574 return;
575
576 const bool severalChanges = (changeMask != ActionData::TextChanged) && (changeMask != ActionData::NameChanged)
577 && (changeMask != ActionData::ToolTipChanged) && (changeMask != ActionData::IconChanged)
578 && (changeMask != ActionData::CheckableChanged) && (changeMask != ActionData::KeysequenceChanged);
579
580 QDesignerFormWindowInterface *fw = formWindow();
581 QUndoStack *undoStack = fw->commandHistory();
582 if (severalChanges)
583 fw->beginCommand(QStringLiteral("Edit action"));
584
585 if (changeMask & ActionData::NameChanged)
586 undoStack->push(createTextPropertyCommand(QLatin1String(objectNamePropertyC), newActionData.name, action, fw));
587
588 if (changeMask & ActionData::TextChanged)
589 undoStack->push(createTextPropertyCommand(QLatin1String(textPropertyC), newActionData.text, action, fw));
590
591 if (changeMask & ActionData::ToolTipChanged)
592 undoStack->push(createTextPropertyCommand(QLatin1String(toolTipPropertyC), newActionData.toolTip, action, fw));
593
594 if (changeMask & ActionData::IconChanged)
595 undoStack->push(setIconPropertyCommand(newActionData.icon, action, fw));
596
597 if (changeMask & ActionData::CheckableChanged)
598 undoStack->push(setPropertyCommand(QLatin1String(checkablePropertyC), newActionData.checkable, false, action, fw));
599
600 if (changeMask & ActionData::KeysequenceChanged)
601 undoStack->push(setKeySequencePropertyCommand(newActionData.keysequence, action, fw));
602
603 if (severalChanges)
604 fw->endCommand();
605 }
606
editCurrentAction()607 void ActionEditor::editCurrentAction()
608 {
609 if (QAction *a = m_actionView->currentAction())
610 editAction(a);
611 }
612
navigateToSlotCurrentAction()613 void ActionEditor::navigateToSlotCurrentAction()
614 {
615 if (QAction *a = m_actionView->currentAction())
616 QDesignerTaskMenu::navigateToSlot(m_core, a, QStringLiteral("triggered()"));
617 }
618
deleteActions(QDesignerFormWindowInterface * fw,const ActionList & actions)619 void ActionEditor::deleteActions(QDesignerFormWindowInterface *fw, const ActionList &actions)
620 {
621 // We need a macro even in the case of single action because the commands might cause the
622 // scheduling of other commands (signal slots connections)
623 const QString description = actions.size() == 1
624 ? tr("Remove action '%1'").arg(actions.constFirst()->objectName())
625 : tr("Remove actions");
626 fw->beginCommand(description);
627 for (QAction *action : actions) {
628 RemoveActionCommand *cmd = new RemoveActionCommand(fw);
629 cmd->init(action);
630 fw->commandHistory()->push(cmd);
631 }
632 fw->endCommand();
633 }
634
635 #if QT_CONFIG(clipboard)
copyActions(QDesignerFormWindowInterface * fwi,const ActionList & actions)636 void ActionEditor::copyActions(QDesignerFormWindowInterface *fwi, const ActionList &actions)
637 {
638 FormWindowBase *fw = qobject_cast<FormWindowBase *>(fwi);
639 if (!fw )
640 return;
641
642 FormBuilderClipboard clipboard;
643 clipboard.m_actions = actions;
644
645 if (clipboard.empty())
646 return;
647
648 QEditorFormBuilder *formBuilder = fw->createFormBuilder();
649 Q_ASSERT(formBuilder);
650
651 QBuffer buffer;
652 if (buffer.open(QIODevice::WriteOnly))
653 if (formBuilder->copy(&buffer, clipboard))
654 qApp->clipboard()->setText(QString::fromUtf8(buffer.buffer()), QClipboard::Clipboard);
655 delete formBuilder;
656 }
657 #endif
658
slotDelete()659 void ActionEditor::slotDelete()
660 {
661 QDesignerFormWindowInterface *fw = formWindow();
662 if (!fw)
663 return;
664
665 const ActionView::ActionList selection = m_actionView->selectedActions();
666 if (selection.isEmpty())
667 return;
668
669 deleteActions(fw, selection);
670 }
671
672 // UnderScore: "Open file" -> actionOpen_file
underscore(QString text)673 static QString underscore(QString text)
674 {
675 const QString underscore = QString(QLatin1Char('_'));
676 static const QRegularExpression nonAsciiPattern(QStringLiteral("[^a-zA-Z_0-9]"));
677 Q_ASSERT(nonAsciiPattern.isValid());
678 text.replace(nonAsciiPattern, underscore);
679 static const QRegularExpression multipleSpacePattern(QStringLiteral("__*"));
680 Q_ASSERT(multipleSpacePattern.isValid());
681 text.replace(multipleSpacePattern, underscore);
682 if (text.endsWith(underscore.at(0)))
683 text.chop(1);
684 return text;
685 }
686
687 // CamelCase: "Open file" -> actionOpenFile, ignoring non-ASCII letters.
688
689 enum CharacterCategory { OtherCharacter, DigitOrAsciiLetter, NonAsciiLetter };
690
category(QChar c)691 static inline CharacterCategory category(QChar c)
692 {
693 if (c.isDigit())
694 return DigitOrAsciiLetter;
695 if (c.isLetter()) {
696 const ushort uc = c.unicode();
697 return (uc >= 'a' && uc <= 'z') || (uc >= 'A' && uc <= 'Z')
698 ? DigitOrAsciiLetter : NonAsciiLetter;
699 }
700 return OtherCharacter;
701 }
702
camelCase(const QString & text)703 static QString camelCase(const QString &text)
704 {
705 QString result;
706 result.reserve(text.size());
707 bool lastCharAccepted = false;
708 for (QChar c : text) {
709 const CharacterCategory cat = category(c);
710 if (cat != NonAsciiLetter) {
711 const bool acceptable = cat == DigitOrAsciiLetter;
712 if (acceptable)
713 result.append(lastCharAccepted ? c : c.toUpper()); // New word starts
714 lastCharAccepted = acceptable;
715 }
716 }
717 return result;
718 }
719
actionTextToName(const QString & text,const QString & prefix)720 QString ActionEditor::actionTextToName(const QString &text, const QString &prefix)
721 {
722 QString name = text;
723 if (name.isEmpty())
724 return QString();
725 return prefix + (m_objectNamingMode == CamelCase ? camelCase(text) : underscore(text));
726
727 }
728
resourceImageDropped(const QString & path,QAction * action)729 void ActionEditor::resourceImageDropped(const QString &path, QAction *action)
730 {
731 QDesignerFormWindowInterface *fw = formWindow();
732 if (!fw)
733 return;
734
735 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core()->extensionManager(), action);
736 const PropertySheetIconValue oldIcon =
737 qvariant_cast<PropertySheetIconValue>(sheet->property(sheet->indexOf(QLatin1String(iconPropertyC))));
738 PropertySheetIconValue newIcon;
739 newIcon.setPixmap(QIcon::Normal, QIcon::Off, PropertySheetPixmapValue(path));
740 if (newIcon.paths().isEmpty() || newIcon.paths() == oldIcon.paths())
741 return;
742
743 fw->commandHistory()->push(setIconPropertyCommand(newIcon , action, fw));
744 }
745
mainContainerChanged()746 void ActionEditor::mainContainerChanged()
747 {
748 // Invalidate references to objects kept in model
749 if (sender() == formWindow())
750 setFormWindow(nullptr);
751 }
752
slotViewMode(QAction * a)753 void ActionEditor::slotViewMode(QAction *a)
754 {
755 m_actionView->setViewMode(a->data().toInt());
756 updateViewModeActions();
757 }
758
slotSelectAssociatedWidget(QWidget * w)759 void ActionEditor::slotSelectAssociatedWidget(QWidget *w)
760 {
761 QDesignerFormWindowInterface *fw = formWindow();
762 if (!fw )
763 return;
764
765 QDesignerObjectInspector *oi = qobject_cast<QDesignerObjectInspector *>(core()->objectInspector());
766 if (!oi)
767 return;
768
769 fw->clearSelection(); // Actually, there are no widgets selected due to focus in event handling. Just to be sure.
770 oi->selectObject(w);
771 }
772
restoreSettings()773 void ActionEditor::restoreSettings()
774 {
775 QDesignerSettingsInterface *settings = m_core->settingsManager();
776 m_actionView->setViewMode(settings->value(QLatin1String(actionEditorViewModeKey), 0).toInt());
777 updateViewModeActions();
778 }
779
saveSettings()780 void ActionEditor::saveSettings()
781 {
782 QDesignerSettingsInterface *settings = m_core->settingsManager();
783 settings->setValue(QLatin1String(actionEditorViewModeKey), m_actionView->viewMode());
784 }
785
updateViewModeActions()786 void ActionEditor::updateViewModeActions()
787 {
788 switch (m_actionView->viewMode()) {
789 case ActionView::IconView:
790 m_iconViewAction->setChecked(true);
791 break;
792 case ActionView::DetailedView:
793 m_listViewAction->setChecked(true);
794 break;
795 }
796 }
797
798 #if QT_CONFIG(clipboard)
slotCopy()799 void ActionEditor::slotCopy()
800 {
801 QDesignerFormWindowInterface *fw = formWindow();
802 if (!fw )
803 return;
804
805 const ActionView::ActionList selection = m_actionView->selectedActions();
806 if (selection.isEmpty())
807 return;
808
809 copyActions(fw, selection);
810 }
811
slotCut()812 void ActionEditor::slotCut()
813 {
814 QDesignerFormWindowInterface *fw = formWindow();
815 if (!fw )
816 return;
817
818 const ActionView::ActionList selection = m_actionView->selectedActions();
819 if (selection.isEmpty())
820 return;
821
822 copyActions(fw, selection);
823 deleteActions(fw, selection);
824 }
825
slotPaste()826 void ActionEditor::slotPaste()
827 {
828 FormWindowBase *fw = qobject_cast<FormWindowBase *>(formWindow());
829 if (!fw)
830 return;
831 m_actionView->clearSelection();
832 fw->paste(FormWindowBase::PasteActionsOnly);
833 }
834 #endif
835
slotContextMenuRequested(QContextMenuEvent * e,QAction * item)836 void ActionEditor::slotContextMenuRequested(QContextMenuEvent *e, QAction *item)
837 {
838 QMenu menu(this);
839 menu.addAction(m_actionNew);
840 menu.addSeparator();
841 menu.addAction(m_actionEdit);
842 if (QDesignerTaskMenu::isSlotNavigationEnabled(m_core))
843 menu.addAction(m_actionNavigateToSlot);
844
845 // Associated Widgets
846 if (QAction *action = m_actionView->currentAction()) {
847 const QWidgetList associatedWidgets = ActionModel::associatedWidgets(action);
848 if (!associatedWidgets.isEmpty()) {
849 QMenu *associatedWidgetsSubMenu = menu.addMenu(tr("Used In"));
850 for (QWidget *w : associatedWidgets) {
851 associatedWidgetsSubMenu->addAction(w->objectName(),
852 this, [this, w] { this->slotSelectAssociatedWidget(w); });
853 }
854 }
855 }
856
857 menu.addSeparator();
858 #if QT_CONFIG(clipboard)
859 menu.addAction(m_actionCut);
860 menu.addAction(m_actionCopy);
861 menu.addAction(m_actionPaste);
862 #endif
863 menu.addAction(m_actionSelectAll);
864 menu.addAction(m_actionDelete);
865 menu.addSeparator();
866 menu.addAction(m_iconViewAction);
867 menu.addAction(m_listViewAction);
868
869 emit contextMenuRequested(&menu, item);
870
871 menu.exec(e->globalPos());
872 e->accept();
873 }
874
875 } // namespace qdesigner_internal
876
877 QT_END_NAMESPACE
878
879