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
71Q_DECLARE_METATYPE(QAction*)
72
73QT_BEGIN_NAMESPACE
74
75static const char *actionEditorViewModeKey = "ActionEditorViewMode";
76
77static const char *iconPropertyC = "icon";
78static const char *shortcutPropertyC = "shortcut";
79static const char *toolTipPropertyC = "toolTip";
80static const char *checkablePropertyC = "checkable";
81static const char *objectNamePropertyC = "objectName";
82static const char *textPropertyC = "text";
83
84namespace qdesigner_internal {
85//-------- ActionGroupDelegate
86class ActionGroupDelegate: public QItemDelegate
87{
88public:
89 ActionGroupDelegate(QObject *parent)
90 : QItemDelegate(parent) {}
91
92 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
100 void drawFocus(QPainter *, const QStyleOptionViewItem &, const QRect &) const override {}
101};
102
103//-------- ActionEditor
104ObjectNamingMode ActionEditor::m_objectNamingMode = Underscore; // fixme Qt 6: CamelCase
105
106ActionEditor::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(s: "New..."), this)),
112 m_actionEdit(new QAction(tr(s: "Edit..."), this)),
113 m_actionNavigateToSlot(new QAction(tr(s: "Go to slot..."), this)),
114#if QT_CONFIG(clipboard)
115 m_actionCopy(new QAction(tr(s: "Copy"), this)),
116 m_actionCut(new QAction(tr(s: "Cut"), this)),
117 m_actionPaste(new QAction(tr(s: "Paste"), this)),
118#endif
119 m_actionSelectAll(new QAction(tr(s: "Select all"), this)),
120 m_actionDelete(new QAction(tr(s: "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(core: m_core);
127 m_actionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
128 setWindowTitle(tr(s: "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(hor: QSizePolicy::Expanding, ver: QSizePolicy::Minimum);
137 l->addWidget(toolbar);
138 // edit actions
139 QIcon documentNewIcon = QIcon::fromTheme(QStringLiteral("document-new"), fallback: createIconSet(QStringLiteral("filenew.png")));
140 m_actionNew->setIcon(documentNewIcon);
141 m_actionNew->setEnabled(false);
142 connect(sender: m_actionNew, signal: &QAction::triggered, receiver: this, slot: &ActionEditor::slotNewAction);
143 toolbar->addAction(action: m_actionNew);
144
145 connect(sender: m_actionSelectAll, signal: &QAction::triggered, receiver: m_actionView, slot: &ActionView::selectAll);
146
147#if QT_CONFIG(clipboard)
148 m_actionCut->setEnabled(false);
149 connect(sender: m_actionCut, signal: &QAction::triggered, receiver: this, slot: &ActionEditor::slotCut);
150 QIcon editCutIcon = QIcon::fromTheme(QStringLiteral("edit-cut"), fallback: createIconSet(QStringLiteral("editcut.png")));
151 m_actionCut->setIcon(editCutIcon);
152
153 m_actionCopy->setEnabled(false);
154 connect(sender: m_actionCopy, signal: &QAction::triggered, receiver: this, slot: &ActionEditor::slotCopy);
155 QIcon editCopyIcon = QIcon::fromTheme(QStringLiteral("edit-copy"), fallback: createIconSet(QStringLiteral("editcopy.png")));
156 m_actionCopy->setIcon(editCopyIcon);
157 toolbar->addAction(action: m_actionCopy);
158
159 connect(sender: m_actionPaste, signal: &QAction::triggered, receiver: this, slot: &ActionEditor::slotPaste);
160 QIcon editPasteIcon = QIcon::fromTheme(QStringLiteral("edit-paste"), fallback: createIconSet(QStringLiteral("editpaste.png")));
161 m_actionPaste->setIcon(editPasteIcon);
162 toolbar->addAction(action: m_actionPaste);
163#endif
164
165 m_actionEdit->setEnabled(false);
166 connect(sender: m_actionEdit, signal: &QAction::triggered, receiver: this, slot: &ActionEditor::editCurrentAction);
167
168 connect(sender: m_actionNavigateToSlot, signal: &QAction::triggered, receiver: this, slot: &ActionEditor::navigateToSlotCurrentAction);
169
170 QIcon editDeleteIcon = QIcon::fromTheme(QStringLiteral("edit-delete"), fallback: createIconSet(QStringLiteral("editdelete.png")));
171 m_actionDelete->setIcon(editDeleteIcon);
172 m_actionDelete->setEnabled(false);
173 connect(sender: m_actionDelete, signal: &QAction::triggered, receiver: this, slot: &ActionEditor::slotDelete);
174 toolbar->addAction(action: 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(widget: createConfigureMenuButton(t: tr(s: "Configure Action Editor"), ptrToMenu: &configureMenu));
180
181 connect(sender: m_viewModeGroup, signal: &QActionGroup::triggered, receiver: this, slot: &ActionEditor::slotViewMode);
182 m_iconViewAction = m_viewModeGroup->addAction(text: tr(s: "Icon View"));
183 m_iconViewAction->setData(QVariant(ActionView::IconView));
184 m_iconViewAction->setCheckable(true);
185 m_iconViewAction->setIcon(style()->standardIcon (standardIcon: QStyle::SP_FileDialogListView));
186 configureMenu->addAction(action: m_iconViewAction);
187
188 m_listViewAction = m_viewModeGroup->addAction(text: tr(s: "Detailed View"));
189 m_listViewAction->setData(QVariant(ActionView::DetailedView));
190 m_listViewAction->setCheckable(true);
191 m_listViewAction->setIcon(style()->standardIcon (standardIcon: QStyle::SP_FileDialogDetailedView));
192 configureMenu->addAction(action: m_listViewAction);
193 // filter
194 m_filterWidget = new QWidget(toolbar);
195 QHBoxLayout *filterLayout = new QHBoxLayout(m_filterWidget);
196 filterLayout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
197 QLineEdit *filterLineEdit = new QLineEdit(m_filterWidget);
198 connect(sender: filterLineEdit, signal: &QLineEdit::textChanged, receiver: this, slot: &ActionEditor::setFilter);
199 filterLineEdit->setPlaceholderText(tr(s: "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(widget: m_filterWidget);
205
206 // main layout
207 QSplitter *splitter = new QSplitter(Qt::Horizontal);
208 splitter->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Expanding);
209
210 splitter->addWidget(widget: 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(sender: m_actionView, signal: &ActionView::resourceImageDropped,
226 receiver: this, slot: &ActionEditor::resourceImageDropped);
227
228 connect(sender: m_actionView, signal: &ActionView::currentChanged,receiver: this, slot: &ActionEditor::slotCurrentItemChanged);
229 // make it possible for vs integration to reimplement edit action dialog
230 connect(sender: m_actionView, signal: &ActionView::activated, receiver: this, slot: &ActionEditor::itemActivated);
231
232 connect(sender: m_actionView, signal: &ActionView::selectionChanged,
233 receiver: this, slot: &ActionEditor::slotSelectionChanged);
234
235 connect(sender: m_actionView, signal: &ActionView::contextMenuRequested,
236 receiver: this, slot: &ActionEditor::slotContextMenuRequested);
237
238 connect(sender: this, signal: &ActionEditor::itemActivated, receiver: this, slot: &ActionEditor::editAction);
239
240 restoreSettings();
241 updateViewModeActions();
242}
243
244// Utility to create a configure button with menu for usage on toolbars
245QToolButton *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"), fallback: 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
259ActionEditor::~ActionEditor()
260{
261 saveSettings();
262}
263
264QAction *ActionEditor::actionNew() const
265{
266 return m_actionNew;
267}
268
269QAction *ActionEditor::actionDelete() const
270{
271 return m_actionDelete;
272}
273
274QDesignerFormWindowInterface *ActionEditor::formWindow() const
275{
276 return m_formWindow;
277}
278
279void 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(sender: action, signal: &QAction::changed, receiver: this, slot: &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(object: 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(a: action);
320 connect(sender: action, signal: &QAction::changed, receiver: this, slot: &ActionEditor::slotActionChanged);
321 }
322
323 setFilter(m_filter);
324}
325
326void 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
336void 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 *>(object: 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(changePropertyDisplay: false);
355 if (oi)
356 oi->clearSelection();
357 core()->propertyEditor()->setObject(action);
358 } else {
359 if (oi)
360 oi->selectObject(o: action);
361 }
362}
363
364void ActionEditor::slotActionChanged()
365{
366 QAction *action = qobject_cast<QAction*>(object: 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(a: action);
374 } else if (action->menu() != nullptr) { // action got its menu created, remove item
375 model->removeRow(arow: row);
376 } else {
377 // action text or icon changed, update item
378 model->update(row);
379 }
380}
381
382QDesignerFormEditorInterface *ActionEditor::core() const
383{
384 return m_core;
385}
386
387QString ActionEditor::filter() const
388{
389 return m_filter;
390}
391
392void ActionEditor::setFilter(const QString &f)
393{
394 m_filter = f;
395 m_actionView->filter(text: m_filter);
396}
397
398// Set changed state of icon property, reset when icon is cleared
399static void refreshIconPropertyChanged(const QAction *action, QDesignerPropertySheetExtension *sheet)
400{
401 sheet->setChanged(index: sheet->indexOf(name: QLatin1String(iconPropertyC)), changed: !action->icon().isNull());
402}
403
404void ActionEditor::manageAction(QAction *action)
405{
406 action->setParent(formWindow()->mainContainer());
407 core()->metaDataBase()->add(object: action);
408
409 if (action->isSeparator() || action->menu() != nullptr)
410 return;
411
412 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: core()->extensionManager(), object: action);
413 sheet->setChanged(index: sheet->indexOf(name: QLatin1String(objectNamePropertyC)), changed: true);
414 sheet->setChanged(index: sheet->indexOf(name: QLatin1String(textPropertyC)), changed: true);
415 refreshIconPropertyChanged(action, sheet);
416
417 m_actionView->setCurrentIndex(m_actionView->model()->addAction(a: action));
418 connect(sender: action, signal: &QAction::changed, receiver: this, slot: &ActionEditor::slotActionChanged);
419}
420
421void ActionEditor::unmanageAction(QAction *action)
422{
423 core()->metaDataBase()->remove(object: action);
424 action->setParent(nullptr);
425
426 disconnect(sender: action, signal: &QAction::changed, receiver: this, slot: &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
434static 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, changed: true);
440}
441
442void ActionEditor::slotNewAction()
443{
444 NewActionDialog dlg(this);
445 dlg.setWindowTitle(tr(s: "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(object: action);
453 action->setText(actionData.text);
454
455 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: core()->extensionManager(), object: action);
456 if (!actionData.toolTip.isEmpty())
457 setInitialProperty(sheet, name: QLatin1String(toolTipPropertyC), value: actionData.toolTip);
458
459 if (actionData.checkable)
460 setInitialProperty(sheet, name: QLatin1String(checkablePropertyC), value: QVariant(true));
461
462 if (!actionData.keysequence.value().isEmpty())
463 setInitialProperty(sheet, name: QLatin1String(shortcutPropertyC), value: QVariant::fromValue(value: actionData.keysequence));
464
465 sheet->setProperty(index: sheet->indexOf(name: QLatin1String(iconPropertyC)), value: QVariant::fromValue(value: 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
476static 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(object: action, propertyName: iconProperty);
482 return cmd;
483 }
484 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
485 cmd->init(object: action, propertyName: iconProperty, newValue: QVariant::fromValue(value: 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
492static 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(object: action, propertyName: shortcutProperty);
498 return cmd;
499 }
500 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
501 cmd->init(object: action, propertyName: shortcutProperty, newValue: QVariant::fromValue(value: 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
508template <class T>
509QDesignerFormWindowCommand *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(object: o, propertyName: name);
515 return cmd;
516 }
517 SetPropertyCommand *cmd = new SetPropertyCommand(fw);
518 cmd->init(object: o, propertyName: name, newValue: QVariant(value));
519 return cmd;
520}
521
522// Return the text value of a string property via PropertySheetStringValue
523static 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>(v: sheet->property(index));
528 return ps.value();
529}
530
531void ActionEditor::editAction(QAction *action, int column)
532{
533 if (!action)
534 return;
535
536 NewActionDialog dlg(this);
537 dlg.setWindowTitle(tr(s: "Edit action"));
538
539 ActionData oldActionData;
540 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: core()->extensionManager(), object: action);
541 oldActionData.name = action->objectName();
542 oldActionData.text = action->text();
543 oldActionData.toolTip = textPropertyValue(sheet, name: QLatin1String(toolTipPropertyC));
544 oldActionData.icon = qvariant_cast<PropertySheetIconValue>(v: sheet->property(index: sheet->indexOf(name: QLatin1String(iconPropertyC))));
545 oldActionData.keysequence = ActionModel::actionShortCut(ps: 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(rhs: 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(cmd: createTextPropertyCommand(propertyName: QLatin1String(objectNamePropertyC), text: newActionData.name, object: action, fw));
587
588 if (changeMask & ActionData::TextChanged)
589 undoStack->push(cmd: createTextPropertyCommand(propertyName: QLatin1String(textPropertyC), text: newActionData.text, object: action, fw));
590
591 if (changeMask & ActionData::ToolTipChanged)
592 undoStack->push(cmd: createTextPropertyCommand(propertyName: QLatin1String(toolTipPropertyC), text: newActionData.toolTip, object: action, fw));
593
594 if (changeMask & ActionData::IconChanged)
595 undoStack->push(cmd: setIconPropertyCommand(newIcon: newActionData.icon, action, fw));
596
597 if (changeMask & ActionData::CheckableChanged)
598 undoStack->push(cmd: setPropertyCommand(name: QLatin1String(checkablePropertyC), value: newActionData.checkable, defaultValue: false, o: action, fw));
599
600 if (changeMask & ActionData::KeysequenceChanged)
601 undoStack->push(cmd: setKeySequencePropertyCommand(ks: newActionData.keysequence, action, fw));
602
603 if (severalChanges)
604 fw->endCommand();
605}
606
607void ActionEditor::editCurrentAction()
608{
609 if (QAction *a = m_actionView->currentAction())
610 editAction(action: a);
611}
612
613void ActionEditor::navigateToSlotCurrentAction()
614{
615 if (QAction *a = m_actionView->currentAction())
616 QDesignerTaskMenu::navigateToSlot(core: m_core, o: a, QStringLiteral("triggered()"));
617}
618
619void 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(s: "Remove action '%1'").arg(a: actions.constFirst()->objectName())
625 : tr(s: "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)
636void ActionEditor::copyActions(QDesignerFormWindowInterface *fwi, const ActionList &actions)
637{
638 FormWindowBase *fw = qobject_cast<FormWindowBase *>(object: 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(openMode: QIODevice::WriteOnly))
653 if (formBuilder->copy(dev: &buffer, selection: clipboard))
654 qApp->clipboard()->setText(QString::fromUtf8(str: buffer.buffer()), mode: QClipboard::Clipboard);
655 delete formBuilder;
656}
657#endif
658
659void 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, actions: selection);
670}
671
672// UnderScore: "Open file" -> actionOpen_file
673static 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(re: nonAsciiPattern, after: underscore);
679 static const QRegularExpression multipleSpacePattern(QStringLiteral("__*"));
680 Q_ASSERT(multipleSpacePattern.isValid());
681 text.replace(re: multipleSpacePattern, after: underscore);
682 if (text.endsWith(c: underscore.at(i: 0)))
683 text.chop(n: 1);
684 return text;
685}
686
687// CamelCase: "Open file" -> actionOpenFile, ignoring non-ASCII letters.
688
689enum CharacterCategory { OtherCharacter, DigitOrAsciiLetter, NonAsciiLetter };
690
691static 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
703static QString camelCase(const QString &text)
704{
705 QString result;
706 result.reserve(asize: 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(c: lastCharAccepted ? c : c.toUpper()); // New word starts
714 lastCharAccepted = acceptable;
715 }
716 }
717 return result;
718}
719
720QString 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
729void ActionEditor::resourceImageDropped(const QString &path, QAction *action)
730{
731 QDesignerFormWindowInterface *fw = formWindow();
732 if (!fw)
733 return;
734
735 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: core()->extensionManager(), object: action);
736 const PropertySheetIconValue oldIcon =
737 qvariant_cast<PropertySheetIconValue>(v: sheet->property(index: sheet->indexOf(name: QLatin1String(iconPropertyC))));
738 PropertySheetIconValue newIcon;
739 newIcon.setPixmap(mode: QIcon::Normal, state: QIcon::Off, path: PropertySheetPixmapValue(path));
740 if (newIcon.paths().isEmpty() || newIcon.paths() == oldIcon.paths())
741 return;
742
743 fw->commandHistory()->push(cmd: setIconPropertyCommand(newIcon , action, fw));
744}
745
746void ActionEditor::mainContainerChanged()
747{
748 // Invalidate references to objects kept in model
749 if (sender() == formWindow())
750 setFormWindow(nullptr);
751}
752
753void ActionEditor::slotViewMode(QAction *a)
754{
755 m_actionView->setViewMode(a->data().toInt());
756 updateViewModeActions();
757}
758
759void ActionEditor::slotSelectAssociatedWidget(QWidget *w)
760{
761 QDesignerFormWindowInterface *fw = formWindow();
762 if (!fw )
763 return;
764
765 QDesignerObjectInspector *oi = qobject_cast<QDesignerObjectInspector *>(object: 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(o: w);
771}
772
773void ActionEditor::restoreSettings()
774{
775 QDesignerSettingsInterface *settings = m_core->settingsManager();
776 m_actionView->setViewMode(settings->value(key: QLatin1String(actionEditorViewModeKey), defaultValue: 0).toInt());
777 updateViewModeActions();
778}
779
780void ActionEditor::saveSettings()
781{
782 QDesignerSettingsInterface *settings = m_core->settingsManager();
783 settings->setValue(key: QLatin1String(actionEditorViewModeKey), value: m_actionView->viewMode());
784}
785
786void 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)
799void 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(fwi: fw, actions: selection);
810}
811
812void 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(fwi: fw, actions: selection);
823 deleteActions(fw, actions: selection);
824}
825
826void ActionEditor::slotPaste()
827{
828 FormWindowBase *fw = qobject_cast<FormWindowBase *>(object: formWindow());
829 if (!fw)
830 return;
831 m_actionView->clearSelection();
832 fw->paste(pasteMode: FormWindowBase::PasteActionsOnly);
833}
834#endif
835
836void ActionEditor::slotContextMenuRequested(QContextMenuEvent *e, QAction *item)
837{
838 QMenu menu(this);
839 menu.addAction(action: m_actionNew);
840 menu.addSeparator();
841 menu.addAction(action: m_actionEdit);
842 if (QDesignerTaskMenu::isSlotNavigationEnabled(core: m_core))
843 menu.addAction(action: 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(title: tr(s: "Used In"));
850 for (QWidget *w : associatedWidgets) {
851 associatedWidgetsSubMenu->addAction(text: w->objectName(),
852 object: this, slot: [this, w] { this->slotSelectAssociatedWidget(w); });
853 }
854 }
855 }
856
857 menu.addSeparator();
858#if QT_CONFIG(clipboard)
859 menu.addAction(action: m_actionCut);
860 menu.addAction(action: m_actionCopy);
861 menu.addAction(action: m_actionPaste);
862#endif
863 menu.addAction(action: m_actionSelectAll);
864 menu.addAction(action: m_actionDelete);
865 menu.addSeparator();
866 menu.addAction(action: m_iconViewAction);
867 menu.addAction(action: m_listViewAction);
868
869 emit contextMenuRequested(menu: &menu, item);
870
871 menu.exec(pos: e->globalPos());
872 e->accept();
873}
874
875} // namespace qdesigner_internal
876
877QT_END_NAMESPACE
878
879

source code of qttools/src/designer/src/lib/shared/actioneditor.cpp