Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++ // Qt-Security score:significant reason:default
++
++ #include "actioneditor_p.h"
++ #include "actionrepository_p.h"
++ #include "iconloader_p.h"
++ #include "newactiondialog_p.h"
++ #include "qdesigner_menu_p.h"
++ #include "qdesigner_command_p.h"
++ #include "qdesigner_propertycommand_p.h"
++ #include "qdesigner_objectinspector_p.h"
++ #include "qdesigner_utils_p.h"
++ #include "qsimpleresource_p.h"
++ #include "formwindowbase_p.h"
++ #include "qdesigner_taskmenu_p.h"
++
++ #include <QtDesigner/abstractformeditor.h>
++ #include <QtDesigner/abstractpropertyeditor.h>
++ #include <QtDesigner/propertysheet.h>
++ #include <QtDesigner/qextensionmanager.h>
++ #include <QtDesigner/abstractmetadatabase.h>
++ #include <QtDesigner/abstractsettings.h>
++
++ #include <QtWidgets/qmenu.h>
++ #include <QtWidgets/qtoolbar.h>
++ #include <QtWidgets/qsplitter.h>
++ #include <QtWidgets/qapplication.h>
++ #if QT_CONFIG(clipboard)
++ #include <QtGui/qclipboard.h>
++ #endif
++ #include <QtWidgets/qitemdelegate.h>
++ #include <QtWidgets/qboxlayout.h>
++ #include <QtWidgets/qlineedit.h>
++ #include <QtWidgets/qlabel.h>
++ #include <QtWidgets/qpushbutton.h>
++ #include <QtWidgets/qtoolbutton.h>
++
++ #include <QtGui/qaction.h>
++ #include <QtGui/qactiongroup.h>
++ #include <QtGui/qevent.h>
++ #include <QtGui/qpainter.h>
++
++ #include <QtCore/qitemselectionmodel.h>
++ #include <QtCore/qregularexpression.h>
++ #include <QtCore/qdebug.h>
++ #include <QtCore/qbuffer.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ static constexpr auto actionEditorViewModeKey = "ActionEditorViewMode"_L1;
++
++ static constexpr auto iconPropertyC = "icon"_L1;
++ static constexpr auto shortcutPropertyC = "shortcut"_L1;
++ static constexpr auto menuRolePropertyC = "menuRole"_L1;
++ static constexpr auto toolTipPropertyC = "toolTip"_L1;
++ static constexpr auto checkablePropertyC = "checkable"_L1;
++ static constexpr auto objectNamePropertyC = "objectName"_L1;
++ static constexpr auto textPropertyC = "text"_L1;
++
++ namespace qdesigner_internal {
++ //-------- ActionGroupDelegate
++ class ActionGroupDelegate: public QItemDelegate
++ {
++ public:
++ ActionGroupDelegate(QObject *parent)
++ : QItemDelegate(parent) {}
++
++ void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
++ {
++ if (option.state & QStyle::State_Selected)
++ painter->fillRect(option.rect, option.palette.highlight());
++
++ QItemDelegate::paint(painter, option, index);
++ }
++
++ void drawFocus(QPainter *, const QStyleOptionViewItem &, const QRect &) const override {}
++ };
++
++ //-------- ActionEditor
++ ObjectNamingMode ActionEditor::m_objectNamingMode = CamelCase;
++
++ ActionEditor::ActionEditor(QDesignerFormEditorInterface *core, QWidget *parent, Qt::WindowFlags flags) :
++ QDesignerActionEditorInterface(parent, flags),
++ m_core(core),
++ m_actionGroups(nullptr),
++ m_actionView(new ActionView),
++ m_actionNew(new QAction(tr("New..."), this)),
++ m_actionEdit(new QAction(tr("Edit..."), this)),
++ m_actionNavigateToSlot(new QAction(tr("Go to slot..."), this)),
++ #if QT_CONFIG(clipboard)
++ m_actionCopy(new QAction(tr("Copy"), this)),
++ m_actionCut(new QAction(tr("Cut"), this)),
++ m_actionPaste(new QAction(tr("Paste"), this)),
++ #endif
++ m_actionSelectAll(new QAction(tr("Select all"), this)),
++ m_actionDelete(new QAction(tr("Delete"), this)),
++ m_viewModeGroup(new QActionGroup(this)),
++ m_iconViewAction(nullptr),
++ m_listViewAction(nullptr),
++ m_filterWidget(nullptr)
++ {
++ m_actionView->initialize(m_core);
++ m_actionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
++ setWindowTitle(tr("Actions"));
++
++ QVBoxLayout *l = new QVBoxLayout(this);
++ l->setContentsMargins(QMargins());
++ l->setSpacing(0);
++
++ QToolBar *toolbar = new QToolBar;
++ toolbar->setIconSize(QSize(22, 22));
++ toolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
++ l->addWidget(toolbar);
++ // edit actions
++ QIcon documentNewIcon = createIconSet(QIcon::ThemeIcon::DocumentNew,
++ "filenew.png"_L1);
++ m_actionNew->setIcon(documentNewIcon);
++ m_actionNew->setEnabled(false);
++ connect(m_actionNew, &QAction::triggered, this, &ActionEditor::slotNewAction);
++ toolbar->addAction(m_actionNew);
++
++ connect(m_actionSelectAll, &QAction::triggered, m_actionView, &ActionView::selectAll);
++
++ #if QT_CONFIG(clipboard)
++ m_actionCut->setEnabled(false);
++ connect(m_actionCut, &QAction::triggered, this, &ActionEditor::slotCut);
++ QIcon editCutIcon = createIconSet(QIcon::ThemeIcon::EditCut,
++ "editcut.png"_L1);
++ m_actionCut->setIcon(editCutIcon);
++
++ m_actionCopy->setEnabled(false);
++ connect(m_actionCopy, &QAction::triggered, this, &ActionEditor::slotCopy);
++ QIcon editCopyIcon = createIconSet(QIcon::ThemeIcon::EditCopy,
++ "editcopy.png"_L1);
++ m_actionCopy->setIcon(editCopyIcon);
++ toolbar->addAction(m_actionCopy);
++
++ connect(m_actionPaste, &QAction::triggered, this, &ActionEditor::slotPaste);
++ QIcon editPasteIcon = createIconSet(QIcon::ThemeIcon::EditPaste,
++ "editpaste.png"_L1);
++ m_actionPaste->setIcon(editPasteIcon);
++ toolbar->addAction(m_actionPaste);
++ #endif
++
++ m_actionEdit->setEnabled(false);
++ connect(m_actionEdit, &QAction::triggered, this, &ActionEditor::editCurrentAction);
++
++ connect(m_actionNavigateToSlot, &QAction::triggered, this, &ActionEditor::navigateToSlotCurrentAction);
++
++ QIcon editDeleteIcon = createIconSet(QIcon::ThemeIcon::EditDelete,
++ "editdelete.png"_L1);
++ m_actionDelete->setIcon(editDeleteIcon);
++ m_actionDelete->setEnabled(false);
++ connect(m_actionDelete, &QAction::triggered, this, &ActionEditor::slotDelete);
++ toolbar->addAction(m_actionDelete);
++
++ // Toolbutton with menu containing action group for detailed/icon view. Steal the icons from the file dialog.
++ //
++ QMenu *configureMenu;
++ toolbar->addWidget(createConfigureMenuButton(tr("Configure Action Editor"), &configureMenu));
++
++ connect(m_viewModeGroup, &QActionGroup::triggered, this, &ActionEditor::slotViewMode);
++ m_iconViewAction = m_viewModeGroup->addAction(tr("Icon View"));
++ m_iconViewAction->setData(QVariant(ActionView::IconView));
++ m_iconViewAction->setCheckable(true);
++ m_iconViewAction->setIcon(style()->standardIcon (QStyle::SP_FileDialogListView));
++ configureMenu->addAction(m_iconViewAction);
++
++ m_listViewAction = m_viewModeGroup->addAction(tr("Detailed View"));
++ m_listViewAction->setData(QVariant(ActionView::DetailedView));
++ m_listViewAction->setCheckable(true);
++ m_listViewAction->setIcon(style()->standardIcon (QStyle::SP_FileDialogDetailedView));
++ configureMenu->addAction(m_listViewAction);
++ // filter
++ m_filterWidget = new QWidget(toolbar);
++ QHBoxLayout *filterLayout = new QHBoxLayout(m_filterWidget);
++ filterLayout->setContentsMargins(0, 0, 0, 0);
++ QLineEdit *filterLineEdit = new QLineEdit(m_filterWidget);
++ connect(filterLineEdit, &QLineEdit::textChanged, this, &ActionEditor::setFilter);
++ filterLineEdit->setPlaceholderText(tr("Filter"));
++ filterLineEdit->setClearButtonEnabled(true);
++ filterLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
++ filterLayout->addWidget(filterLineEdit);
++ m_filterWidget->setEnabled(false);
++ toolbar->addWidget(m_filterWidget);
++
++ // main layout
++ QSplitter *splitter = new QSplitter(Qt::Horizontal);
++ splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
++
++ splitter->addWidget(m_actionView);
++ l->addWidget(splitter);
++
++ #if 0 // ### implement me
++ m_actionGroups = new QListWidget(splitter);
++ splitter->addWidget(m_actionGroups);
++ m_actionGroups->setItemDelegate(new ActionGroupDelegate(m_actionGroups));
++ m_actionGroups->setMovement(QListWidget::Static);
++ m_actionGroups->setResizeMode(QListWidget::Fixed);
++ m_actionGroups->setIconSize(QSize(48, 48));
++ m_actionGroups->setFlow(QListWidget::TopToBottom);
++ m_actionGroups->setViewMode(QListWidget::IconMode);
++ m_actionGroups->setWrapping(false);
++ #endif
++
++ connect(m_actionView, &ActionView::resourceImageDropped,
++ this, &ActionEditor::resourceImageDropped);
++
++ connect(m_actionView, &ActionView::currentActionChanged,
++ this, &ActionEditor::slotCurrentItemChanged);
++ // make it possible for vs integration to reimplement edit action dialog
++ connect(m_actionView, &ActionView::activated, this, &ActionEditor::itemActivated);
++
++ connect(m_actionView, &ActionView::selectionChanged,
++ this, &ActionEditor::slotSelectionChanged);
++
++ connect(m_actionView, &ActionView::contextMenuRequested,
++ this, &ActionEditor::slotContextMenuRequested);
++
++ connect(this, &ActionEditor::itemActivated, this, &ActionEditor::editAction);
++
++ restoreSettings();
++ updateViewModeActions();
++ }
++
++ // Utility to create a configure button with menu for usage on toolbars
++ QToolButton *ActionEditor::createConfigureMenuButton(const QString &t, QMenu **ptrToMenu)
++ {
++ QToolButton *configureButton = new QToolButton;
++ QAction *configureAction = new QAction(t, configureButton);
++ QIcon configureIcon = QIcon::fromTheme(QIcon::ThemeIcon::DocumentProperties,
++ createIconSet("configure.png"_L1));
++ configureAction->setIcon(configureIcon);
++ QMenu *configureMenu = new QMenu(configureButton);
++ configureAction->setMenu(configureMenu);
++ configureButton->setDefaultAction(configureAction);
++ configureButton->setPopupMode(QToolButton::InstantPopup);
++ *ptrToMenu = configureMenu;
++ return configureButton;
++ }
++
++ ActionEditor::~ActionEditor()
++ {
++ saveSettings();
++ }
++
++ QAction *ActionEditor::actionNew() const
++ {
++ return m_actionNew;
++ }
++
++ QAction *ActionEditor::actionDelete() const
++ {
++ return m_actionDelete;
++ }
++
++ QDesignerFormWindowInterface *ActionEditor::formWindow() const
++ {
++ return m_formWindow;
++ }
++
++ void ActionEditor::setFormWindow(QDesignerFormWindowInterface *formWindow)
++ {
++ if (formWindow != nullptr && formWindow->mainContainer() == nullptr)
++ formWindow = nullptr;
++
++ // we do NOT rely on this function to update the action editor
++ if (m_formWindow == formWindow)
++ return;
++
++ if (m_formWindow != nullptr) {
++ const ActionList actionList = m_formWindow->mainContainer()->findChildren<QAction*>();
++ for (QAction *action : actionList)
++ disconnect(action, &QAction::changed, this, &ActionEditor::slotActionChanged);
++ }
++
++ m_formWindow = formWindow;
++
++ m_actionView->model()->clearActions();
++
++ m_actionEdit->setEnabled(false);
++ #if QT_CONFIG(clipboard)
++ m_actionCopy->setEnabled(false);
++ m_actionCut->setEnabled(false);
++ #endif
++ m_actionDelete->setEnabled(false);
++
++ if (!formWindow || !formWindow->mainContainer()) {
++ m_actionNew->setEnabled(false);
++ m_filterWidget->setEnabled(false);
++ return;
++ }
++
++ m_actionNew->setEnabled(true);
++ m_filterWidget->setEnabled(true);
++
++ const ActionList actionList = formWindow->mainContainer()->findChildren<QAction*>();
++ for (QAction *action : actionList)
++ if (!action->isSeparator() && core()->metaDataBase()->item(action) != nullptr) {
++ // Show unless it has a menu. However, listen for change on menu actions also as it might be removed
++ if (!action->menu())
++ m_actionView->model()->addAction(action);
++ connect(action, &QAction::changed, this, &ActionEditor::slotActionChanged);
++ }
++
++ setFilter(m_filter);
++ }
++
++ void ActionEditor::slotSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/)
++ {
++ const bool hasSelection = !selected.indexes().isEmpty();
++ #if QT_CONFIG(clipboard)
++ m_actionCopy->setEnabled(hasSelection);
++ m_actionCut->setEnabled(hasSelection);
++ #endif
++ m_actionDelete->setEnabled(hasSelection);
++ }
++
++ void ActionEditor::slotCurrentItemChanged(QAction *action)
++ {
++ QDesignerFormWindowInterface *fw = formWindow();
++ if (m_withinSelectAction || fw == nullptr)
++ return;
++
++ const bool hasCurrentAction = action != nullptr;
++ m_actionEdit->setEnabled(hasCurrentAction);
++
++ if (!action) {
++ fw->clearSelection();
++ return;
++ }
++
++ QDesignerObjectInspector *oi = qobject_cast<QDesignerObjectInspector *>(core()->objectInspector());
++
++ // Check if we have at least one associated QWidget:
++ const auto associatedObjects = action->associatedObjects();
++ auto it = std::find_if(associatedObjects.cbegin(), associatedObjects.cend(),
++ [](QObject *obj) {
++ return qobject_cast<QWidget *>(obj) != nullptr;
++ });
++ if (it == associatedObjects.cend()) {
++ // Special case: action not in object tree. Deselect all and set in property editor
++ fw->clearSelection(false);
++ if (oi)
++ oi->clearSelection();
++ core()->propertyEditor()->setObject(action);
++ } else {
++ if (oi)
++ oi->selectObject(action);
++ }
++ }
++
++ void ActionEditor::slotActionChanged()
++ {
++ QAction *action = qobject_cast<QAction*>(sender());
++ Q_ASSERT(action != nullptr);
++
++ ActionModel *model = m_actionView->model();
++ const int row = model->findAction(action);
++ if (row == -1) {
++ if (action->menu() == nullptr) // action got its menu deleted, create item
++ model->addAction(action);
++ } else if (action->menu() != nullptr) { // action got its menu created, remove item
++ model->removeRow(row);
++ } else {
++ // action text or icon changed, update item
++ model->update(row);
++ }
++ }
++
++ QDesignerFormEditorInterface *ActionEditor::core() const
++ {
++ return m_core;
++ }
++
++ QString ActionEditor::filter() const
++ {
++ return m_filter;
++ }
++
++ void ActionEditor::setFilter(const QString &f)
++ {
++ m_filter = f;
++ m_actionView->filter(m_filter);
++ }
++
++ // Set changed state of icon property, reset when icon is cleared
++ static void refreshIconPropertyChanged(const QAction *action, QDesignerPropertySheetExtension *sheet)
++ {
++ sheet->setChanged(sheet->indexOf(iconPropertyC), !action->icon().isNull());
++ }
++
++ void ActionEditor::manageAction(QAction *action)
++ {
++ action->setParent(formWindow()->mainContainer());
++ core()->metaDataBase()->add(action);
++
++ if (action->isSeparator() || action->menu() != nullptr)
++ return;
++
++ QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core()->extensionManager(), action);
++ sheet->setChanged(sheet->indexOf(objectNamePropertyC), true);
++ sheet->setChanged(sheet->indexOf(textPropertyC), true);
++ refreshIconPropertyChanged(action, sheet);
++
++ m_actionView->setCurrentIndex(m_actionView->model()->addAction(action));
++ connect(action, &QAction::changed, this, &ActionEditor::slotActionChanged);
++ }
++
++ void ActionEditor::unmanageAction(QAction *action)
++ {
++ core()->metaDataBase()->remove(action);
++ action->setParent(nullptr);
++
++ disconnect(action, &QAction::changed, this, &ActionEditor::slotActionChanged);
++
++ const int row = m_actionView->model()->findAction(action);
++ if (row != -1)
++ m_actionView->model()->remove(row);
++ }
++
++ // Set an initial property and mark it as changed in the sheet
++ static void setInitialProperty(QDesignerPropertySheetExtension *sheet, const QString &name, const QVariant &value)
++ {
++ const int index = sheet->indexOf(name);
++ Q_ASSERT(index != -1);
++ sheet->setProperty(index, value);
++ sheet->setChanged(index, true);
++ }
++
++ void ActionEditor::slotNewAction()
++ {
++ NewActionDialog dlg(this);
++ dlg.setWindowTitle(tr("New action"));
++
++ if (dlg.exec() == QDialog::Accepted) {
++ const ActionData actionData = dlg.actionData();
++ m_actionView->clearSelection();
++ QAction *action = new QAction(formWindow());
++ action->setObjectName(actionData.name);
++ formWindow()->ensureUniqueObjectName(action);
++ action->setText(actionData.text);
++
++ QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core()->extensionManager(), action);
++ if (!actionData.toolTip.isEmpty())
++ setInitialProperty(sheet, toolTipPropertyC, actionData.toolTip);
++
++ if (actionData.checkable)
++ setInitialProperty(sheet, checkablePropertyC, QVariant(true));
++
++ if (!actionData.keysequence.value().isEmpty())
++ setInitialProperty(sheet, shortcutPropertyC, QVariant::fromValue(actionData.keysequence));
++
++ sheet->setProperty(sheet->indexOf(iconPropertyC), QVariant::fromValue(actionData.icon));
++
++ setInitialProperty(sheet, menuRolePropertyC, QVariant::fromValue(actionData.menuRole));
++
++ AddActionCommand *cmd = new AddActionCommand(formWindow());
++ cmd->init(action);
++ formWindow()->commandHistory()->push(cmd);
++ }
++ }
++
++ // return a FormWindow command to apply an icon or a reset command in case it
++ // is empty.
++
++ static QDesignerFormWindowCommand *setIconPropertyCommand(const PropertySheetIconValue &newIcon, QAction *action, QDesignerFormWindowInterface *fw)
++ {
++ const QString iconProperty = iconPropertyC;
++ if (newIcon.isEmpty()) {
++ ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
++ cmd->init(action, iconProperty);
++ return cmd;
++ }
++ SetPropertyCommand *cmd = new SetPropertyCommand(fw);
++ cmd->init(action, iconProperty, QVariant::fromValue(newIcon));
++ return cmd;
++ }
++
++ // return a FormWindow command to apply a QKeySequence or a reset command
++ // in case it is empty.
++
++ static QDesignerFormWindowCommand *setKeySequencePropertyCommand(const PropertySheetKeySequenceValue &ks, QAction *action, QDesignerFormWindowInterface *fw)
++ {
++ const QString shortcutProperty = shortcutPropertyC;
++ if (ks.value().isEmpty()) {
++ ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
++ cmd->init(action, shortcutProperty);
++ return cmd;
++ }
++ SetPropertyCommand *cmd = new SetPropertyCommand(fw);
++ cmd->init(action, shortcutProperty, QVariant::fromValue(ks));
++ return cmd;
++ }
++
++ // return a FormWindow command to apply a POD value or reset command in case
++ // it is equal to the default value.
++
++ template <class T>
++ QDesignerFormWindowCommand *setPropertyCommand(const QString &name, T value, T defaultValue,
++ QObject *o, QDesignerFormWindowInterface *fw)
++ {
++ if (value == defaultValue) {
++ ResetPropertyCommand *cmd = new ResetPropertyCommand(fw);
++ cmd->init(o, name);
++ return cmd;
++ }
++ SetPropertyCommand *cmd = new SetPropertyCommand(fw);
++ cmd->init(o, name, QVariant(value));
++ return cmd;
++ }
++
++ // Return the text value of a string property via PropertySheetStringValue
++ static inline QString textPropertyValue(const QDesignerPropertySheetExtension *sheet, const QString &name)
++ {
++ const int index = sheet->indexOf(name);
++ Q_ASSERT(index != -1);
++ const PropertySheetStringValue ps = qvariant_cast<PropertySheetStringValue>(sheet->property(index));
++ return ps.value();
++ }
++
++ void ActionEditor::editAction(QAction *action, int column)
++ {
++ if (!action)
++ return;
++
++ NewActionDialog dlg(this);
++ dlg.setWindowTitle(tr("Edit action"));
++
++ ActionData oldActionData;
++ QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core()->extensionManager(), action);
++ oldActionData.name = action->objectName();
++ oldActionData.text = action->text();
++ oldActionData.toolTip = textPropertyValue(sheet, toolTipPropertyC);
++ oldActionData.icon = qvariant_cast<PropertySheetIconValue>(sheet->property(sheet->indexOf(iconPropertyC)));
++ oldActionData.keysequence = ActionModel::actionShortCut(sheet);
++ oldActionData.checkable = action->isCheckable();
++ oldActionData.menuRole.value = action->menuRole();
++ dlg.setActionData(oldActionData);
++
++ switch (column) {
++ case qdesigner_internal::ActionModel::NameColumn:
++ dlg.focusName();
++ break;
++ case qdesigner_internal::ActionModel::TextColumn:
++ dlg.focusText();
++ break;
++ case qdesigner_internal::ActionModel::ShortCutColumn:
++ dlg.focusShortcut();
++ break;
++ case qdesigner_internal::ActionModel::CheckedColumn:
++ dlg.focusCheckable();
++ break;
++ case qdesigner_internal::ActionModel::ToolTipColumn:
++ dlg.focusTooltip();
++ break;
++ case qdesigner_internal::ActionModel::MenuRoleColumn:
++ dlg.focusMenuRole();
++ break;
++ }
++
++ if (!dlg.exec())
++ return;
++
++ // figure out changes and whether to start a macro
++ const ActionData newActionData = dlg.actionData();
++ const unsigned changeMask = newActionData.compare(oldActionData);
++ if (changeMask == 0u)
++ return;
++
++ const bool severalChanges = (changeMask != ActionData::TextChanged) && (changeMask != ActionData::NameChanged)
++ && (changeMask != ActionData::ToolTipChanged) && (changeMask != ActionData::IconChanged)
++ && (changeMask != ActionData::CheckableChanged) && (changeMask != ActionData::KeysequenceChanged)
++ && (changeMask != ActionData::MenuRoleChanged);
++
++ QDesignerFormWindowInterface *fw = formWindow();
++ QUndoStack *undoStack = fw->commandHistory();
++ if (severalChanges)
++ fw->beginCommand(u"Edit action"_s);
++
++ if (changeMask & ActionData::NameChanged)
++ undoStack->push(createTextPropertyCommand(objectNamePropertyC, newActionData.name, action, fw));
++
++ if (changeMask & ActionData::TextChanged)
++ undoStack->push(createTextPropertyCommand(textPropertyC, newActionData.text, action, fw));
++
++ if (changeMask & ActionData::ToolTipChanged)
++ undoStack->push(createTextPropertyCommand(toolTipPropertyC, newActionData.toolTip, action, fw));
++
++ if (changeMask & ActionData::IconChanged)
++ undoStack->push(setIconPropertyCommand(newActionData.icon, action, fw));
++
++ if (changeMask & ActionData::CheckableChanged)
++ undoStack->push(setPropertyCommand(checkablePropertyC, newActionData.checkable, false, action, fw));
++
++ if (changeMask & ActionData::KeysequenceChanged)
++ undoStack->push(setKeySequencePropertyCommand(newActionData.keysequence, action, fw));
++
++ if (changeMask & ActionData::MenuRoleChanged)
++ undoStack->push(setPropertyCommand(menuRolePropertyC, static_cast<QAction::MenuRole>(newActionData.menuRole.value), QAction::NoRole, action, fw));
++
++ if (severalChanges)
++ fw->endCommand();
++ }
++
++ void ActionEditor::editCurrentAction()
++ {
++ if (QAction *a = m_actionView->currentAction())
++ editAction(a);
++ }
++
++ void ActionEditor::navigateToSlotCurrentAction()
++ {
++ if (QAction *a = m_actionView->currentAction())
++ QDesignerTaskMenu::navigateToSlot(m_core, a, u"triggered()"_s);
++ }
++
++ void ActionEditor::deleteActions(QDesignerFormWindowInterface *fw, const ActionList &actions)
++ {
++ // We need a macro even in the case of single action because the commands might cause the
++ // scheduling of other commands (signal slots connections)
++ const QString description = actions.size() == 1
++ ? tr("Remove action '%1'").arg(actions.constFirst()->objectName())
++ : tr("Remove actions");
++ fw->beginCommand(description);
++ for (QAction *action : actions) {
++ RemoveActionCommand *cmd = new RemoveActionCommand(fw);
++ cmd->init(action);
++ fw->commandHistory()->push(cmd);
++ }
++ fw->endCommand();
++ }
++
++ #if QT_CONFIG(clipboard)
++ void ActionEditor::copyActions(QDesignerFormWindowInterface *fwi, const ActionList &actions)
++ {
++ FormWindowBase *fw = qobject_cast<FormWindowBase *>(fwi);
++ if (!fw )
++ return;
++
++ FormBuilderClipboard clipboard;
++ clipboard.m_actions = actions;
++
++ if (clipboard.empty())
++ return;
++
++ QEditorFormBuilder *formBuilder = fw->createFormBuilder();
++ Q_ASSERT(formBuilder);
++
++ QBuffer buffer;
++ if (buffer.open(QIODevice::WriteOnly))
++ if (formBuilder->copy(&buffer, clipboard))
++ qApp->clipboard()->setText(QString::fromUtf8(buffer.buffer()), QClipboard::Clipboard);
++ delete formBuilder;
++ }
++ #endif
++
++ void ActionEditor::slotDelete()
++ {
++ QDesignerFormWindowInterface *fw = formWindow();
++ if (!fw)
++ return;
++
++ const ActionView::ActionList selection = m_actionView->selectedActions();
++ if (selection.isEmpty())
++ return;
++
++ deleteActions(fw, selection);
++ }
++
++ // UnderScore: "Open file" -> actionOpen_file
++ static QString underscore(QString text)
++ {
++ static const QRegularExpression nonAsciiPattern(u"[^a-zA-Z_0-9]"_s);
++ Q_ASSERT(nonAsciiPattern.isValid());
++ text.replace(nonAsciiPattern, "_"_L1);
++ static const QRegularExpression multipleSpacePattern(u"__*"_s);
++ Q_ASSERT(multipleSpacePattern.isValid());
++ text.replace(multipleSpacePattern, "_"_L1);
++ if (text.endsWith(u'_'))
++ text.chop(1);
++ return text;
++ }
++
++ // CamelCase: "Open file" -> actionOpenFile, ignoring non-ASCII letters.
++
++ enum CharacterCategory { OtherCharacter, DigitOrAsciiLetter, NonAsciiLetter };
++
++ static inline CharacterCategory category(QChar c)
++ {
++ if (c.isDigit())
++ return DigitOrAsciiLetter;
++ if (c.isLetter()) {
++ const ushort uc = c.unicode();
++ return (uc >= 'a' && uc <= 'z') || (uc >= 'A' && uc <= 'Z')
++ ? DigitOrAsciiLetter : NonAsciiLetter;
++ }
++ return OtherCharacter;
++ }
++
++ static QString camelCase(const QString &text)
++ {
++ QString result;
++ result.reserve(text.size());
++ bool lastCharAccepted = false;
++ for (QChar c : text) {
++ const CharacterCategory cat = category(c);
++ if (cat != NonAsciiLetter) {
++ const bool acceptable = cat == DigitOrAsciiLetter;
++ if (acceptable)
++ result.append(lastCharAccepted ? c : c.toUpper()); // New word starts
++ lastCharAccepted = acceptable;
++ }
++ }
++ return result;
++ }
++
++ QString ActionEditor::actionTextToName(const QString &text, const QString &prefix)
++ {
++ QString name = text;
++ if (name.isEmpty())
++ return QString();
++ return prefix + (m_objectNamingMode == CamelCase ? camelCase(text) : underscore(text));
++
++ }
++
++ void ActionEditor::resourceImageDropped(const QString &path, QAction *action)
++ {
++ QDesignerFormWindowInterface *fw = formWindow();
++ if (!fw)
++ return;
++
++ QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core()->extensionManager(), action);
++ const PropertySheetIconValue oldIcon =
++ qvariant_cast<PropertySheetIconValue>(sheet->property(sheet->indexOf(iconPropertyC)));
++ PropertySheetIconValue newIcon;
++ newIcon.setPixmap(QIcon::Normal, QIcon::Off, PropertySheetPixmapValue(path));
++ if (newIcon.paths().isEmpty() || newIcon.paths() == oldIcon.paths())
++ return;
++
++ fw->commandHistory()->push(setIconPropertyCommand(newIcon , action, fw));
++ }
++
++ void ActionEditor::mainContainerChanged()
++ {
++ // Invalidate references to objects kept in model
++ if (sender() == formWindow())
++ setFormWindow(nullptr);
++ }
++
++ void ActionEditor::clearSelection()
++ {
++ // For use by the menu editor; block the syncing of the object inspector
++ // in slotCurrentItemChanged() since the menu editor updates it itself.
++ m_withinSelectAction = true;
++ m_actionView->clearSelection();
++ m_withinSelectAction = false;
++ }
++
++ void ActionEditor::selectAction(QAction *a)
++ {
++ // For use by the menu editor; block the syncing of the object inspector
++ // in slotCurrentItemChanged() since the menu editor updates it itself.
++ m_withinSelectAction = true;
++ m_actionView->selectAction(a);
++ m_withinSelectAction = false;
++ }
++
++ void ActionEditor::slotViewMode(QAction *a)
++ {
++ m_actionView->setViewMode(a->data().toInt());
++ updateViewModeActions();
++ }
++
++ void ActionEditor::slotSelectAssociatedWidget(QWidget *w)
++ {
++ QDesignerFormWindowInterface *fw = formWindow();
++ if (!fw )
++ return;
++
++ QDesignerObjectInspector *oi = qobject_cast<QDesignerObjectInspector *>(core()->objectInspector());
++ if (!oi)
++ return;
++
++ fw->clearSelection(); // Actually, there are no widgets selected due to focus in event handling. Just to be sure.
++ oi->selectObject(w);
++ }
++
++ void ActionEditor::restoreSettings()
++ {
++ QDesignerSettingsInterface *settings = m_core->settingsManager();
++ m_actionView->setViewMode(settings->value(actionEditorViewModeKey, 0).toInt());
++ updateViewModeActions();
++ }
++
++ void ActionEditor::saveSettings()
++ {
++ QDesignerSettingsInterface *settings = m_core->settingsManager();
++ settings->setValue(actionEditorViewModeKey, m_actionView->viewMode());
++ }
++
++ void ActionEditor::updateViewModeActions()
++ {
++ switch (m_actionView->viewMode()) {
++ case ActionView::IconView:
++ m_iconViewAction->setChecked(true);
++ break;
++ case ActionView::DetailedView:
++ m_listViewAction->setChecked(true);
++ break;
++ }
++ }
++
++ #if QT_CONFIG(clipboard)
++ void ActionEditor::slotCopy()
++ {
++ QDesignerFormWindowInterface *fw = formWindow();
++ if (!fw )
++ return;
++
++ const ActionView::ActionList selection = m_actionView->selectedActions();
++ if (selection.isEmpty())
++ return;
++
++ copyActions(fw, selection);
++ }
++
++ void ActionEditor::slotCut()
++ {
++ QDesignerFormWindowInterface *fw = formWindow();
++ if (!fw )
++ return;
++
++ const ActionView::ActionList selection = m_actionView->selectedActions();
++ if (selection.isEmpty())
++ return;
++
++ copyActions(fw, selection);
++ deleteActions(fw, selection);
++ }
++
++ void ActionEditor::slotPaste()
++ {
++ FormWindowBase *fw = qobject_cast<FormWindowBase *>(formWindow());
++ if (!fw)
++ return;
++ m_actionView->clearSelection();
++ fw->paste(FormWindowBase::PasteActionsOnly);
++ }
++ #endif
++
++ void ActionEditor::slotContextMenuRequested(QContextMenuEvent *e, QAction *item)
++ {
++ QMenu menu(this);
++ menu.addAction(m_actionNew);
++ menu.addSeparator();
++ menu.addAction(m_actionEdit);
++ if (QDesignerTaskMenu::isSlotNavigationEnabled(m_core))
++ menu.addAction(m_actionNavigateToSlot);
++
++ // Associated Widgets
++ if (QAction *action = m_actionView->currentAction()) {
++ const QWidgetList associatedWidgets = ActionModel::associatedWidgets(action);
++ if (!associatedWidgets.isEmpty()) {
++ QMenu *associatedWidgetsSubMenu = menu.addMenu(tr("Used In"));
++ for (QWidget *w : associatedWidgets) {
++ associatedWidgetsSubMenu->addAction(w->objectName(),
++ this, [this, w] { this->slotSelectAssociatedWidget(w); });
++ }
++ }
++ }
++
++ menu.addSeparator();
++ #if QT_CONFIG(clipboard)
++ menu.addAction(m_actionCut);
++ menu.addAction(m_actionCopy);
++ menu.addAction(m_actionPaste);
++ #endif
++ menu.addAction(m_actionSelectAll);
++ menu.addAction(m_actionDelete);
++ menu.addSeparator();
++ menu.addAction(m_iconViewAction);
++ menu.addAction(m_listViewAction);
++
++ emit contextMenuRequested(&menu, item);
++
++ menu.exec(e->globalPos());
++ e->accept();
++ }
++
++ } // namespace qdesigner_internal
++
++ QT_END_NAMESPACE
++