From 5d2e3819d56fdefe3680bbb7bea2b5c092cca39b Mon Sep 17 00:00:00 2001 From: Liu Zhangjian Date: Wed, 21 Aug 2024 15:54:03 +0800 Subject: [PATCH] refactor: [shortcut] Refactor the shortcut settings view as title Log: code refactor --- src/common/actionmanager/actionmanager.cpp | 4 +- .../configure/binarytoolsmanager.cpp | 6 +- .../codeeditor/gui/workspacewidget.cpp | 2 +- .../debugger/interface/menumanager.cpp | 3 +- src/plugins/git/utils/gitmenumanager.cpp | 6 +- src/plugins/option/optioncore/CMakeLists.txt | 44 +- .../optioncore/icons/shortcut_group_128px.svg | 151 ++++ .../optionshortcutsettinggenerator.cpp | 15 - .../optionshortcutsettinggenerator.h | 19 - .../optioncore/mainframe/shortcutdialog.cpp | 152 ++++ .../optioncore/mainframe/shortcutdialog.h | 38 + .../optioncore/mainframe/shortcutedit.cpp | 19 - .../optioncore/mainframe/shortcutedit.h | 34 - .../optioncore/mainframe/shortcutitem.cpp | 102 +++ .../optioncore/mainframe/shortcutitem.h | 50 ++ .../mainframe/shortcutsettingwidget.cpp | 777 ++++++++++++------ .../mainframe/shortcutsettingwidget.h | 55 +- src/plugins/option/optioncore/optioncore.cpp | 2 +- src/plugins/option/optioncore/optioncore.qrc | 7 +- .../option/optioncore/texts/edit_16px.svg | 9 + .../option/optioncore/texts/export_16px.svg | 7 + .../option/optioncore/texts/import_16px.svg | 7 + .../optioncore/texts/shortcut_search_16px.svg | 7 + 23 files changed, 1105 insertions(+), 411 deletions(-) create mode 100644 src/plugins/option/optioncore/icons/shortcut_group_128px.svg delete mode 100644 src/plugins/option/optioncore/mainframe/optionshortcutsettinggenerator.cpp delete mode 100644 src/plugins/option/optioncore/mainframe/optionshortcutsettinggenerator.h create mode 100644 src/plugins/option/optioncore/mainframe/shortcutdialog.cpp create mode 100644 src/plugins/option/optioncore/mainframe/shortcutdialog.h delete mode 100644 src/plugins/option/optioncore/mainframe/shortcutedit.cpp delete mode 100644 src/plugins/option/optioncore/mainframe/shortcutedit.h create mode 100644 src/plugins/option/optioncore/mainframe/shortcutitem.cpp create mode 100644 src/plugins/option/optioncore/mainframe/shortcutitem.h create mode 100644 src/plugins/option/optioncore/texts/edit_16px.svg create mode 100644 src/plugins/option/optioncore/texts/export_16px.svg create mode 100644 src/plugins/option/optioncore/texts/import_16px.svg create mode 100644 src/plugins/option/optioncore/texts/shortcut_search_16px.svg diff --git a/src/common/actionmanager/actionmanager.cpp b/src/common/actionmanager/actionmanager.cpp index 96e74c0d2..f486f616f 100644 --- a/src/common/actionmanager/actionmanager.cpp +++ b/src/common/actionmanager/actionmanager.cpp @@ -123,7 +123,7 @@ void ActionManagerPrivate::saveSettings(Command *cmd) settings.setValue(settingsKey, keys.first().toString()); } else { QStringList shortcutList; - std::transform(keys.begin(), keys.end(), shortcutList.begin(), + std::transform(keys.begin(), keys.end(), std::back_inserter(shortcutList), [](const QKeySequence &k) { return k.toString(); }); @@ -142,7 +142,7 @@ void ActionManagerPrivate::readUserSettings(const QString &id, Command *cmd) if (QMetaType::Type(v.type()) == QMetaType::QStringList) { auto list = v.toStringList(); QList keySequenceList; - std::transform(list.begin(), list.end(), keySequenceList.begin(), + std::transform(list.begin(), list.end(), std::back_inserter(keySequenceList), [](const QString &s) { return QKeySequence::fromString(s); }); diff --git a/src/plugins/binarytools/configure/binarytoolsmanager.cpp b/src/plugins/binarytools/configure/binarytoolsmanager.cpp index 1a1898547..622675f50 100644 --- a/src/plugins/binarytools/configure/binarytoolsmanager.cpp +++ b/src/plugins/binarytools/configure/binarytoolsmanager.cpp @@ -425,7 +425,8 @@ void BinaryToolsManager::updateToolMenu(const BinaryTools &tools) for (const auto &tool : iter.value()) { auto act = new QAction(QIcon::fromTheme(tool.icon), tool.name, mGroup); - auto cmd = ActionManager::instance()->registerAction(act, tool.id); + auto actId = QString("BinaryTools.Tool.%1").arg(tool.id); + auto cmd = ActionManager::instance()->registerAction(act, actId); mGroup->addAction(cmd); connect(act, &QAction::triggered, this, std::bind(&BinaryToolsManager::executeTool, this, tool.id)); @@ -578,7 +579,8 @@ void BinaryToolsManager::addToToolBar(const ToolInfo &tool) act->setIcon(QIcon::fromTheme(tool.icon)); connect(act, &QAction::triggered, this, std::bind(&BinaryToolsManager::executeTool, this, tool.id)); - auto cmd = ActionManager::instance()->registerAction(act, tool.id); + auto actId = QString("BinaryTools.Tool.%1").arg(tool.id); + auto cmd = ActionManager::instance()->registerAction(act, actId); return cmd; }; diff --git a/src/plugins/codeeditor/gui/workspacewidget.cpp b/src/plugins/codeeditor/gui/workspacewidget.cpp index 05732ca91..3a0e00dba 100644 --- a/src/plugins/codeeditor/gui/workspacewidget.cpp +++ b/src/plugins/codeeditor/gui/workspacewidget.cpp @@ -73,7 +73,7 @@ void WorkspaceWidgetPrivate::initActions() // show opened files QAction *showOpenedAction = new QAction(tr("Show opened files"), q); - cmd = ActionManager::instance()->registerAction(commentAction, "Editor.ShowOpenedFiles"); + cmd = ActionManager::instance()->registerAction(showOpenedAction, "Editor.ShowOpenedFiles"); cmd->setDefaultKeySequence(Qt::CTRL | Qt::Key_Tab); connect(showOpenedAction, &QAction::triggered, this, &WorkspaceWidgetPrivate::handleShowOpenedFiles); } diff --git a/src/plugins/debugger/interface/menumanager.cpp b/src/plugins/debugger/interface/menumanager.cpp index 96d2700ff..56c234c09 100644 --- a/src/plugins/debugger/interface/menumanager.cpp +++ b/src/plugins/debugger/interface/menumanager.cpp @@ -30,8 +30,9 @@ void MenuManager::initialize(WindowService *windowService) QKeySequence key, const QString &iconName = {}) -> Command * { action->setIcon(QIcon::fromTheme(iconName)); auto cmd = ActionManager::instance()->registerAction(action, id); - cmd->setDefaultKeySequence(key); cmd->setDescription(description); + if (!key.isEmpty()) + cmd->setDefaultKeySequence(key); return cmd; }; diff --git a/src/plugins/git/utils/gitmenumanager.cpp b/src/plugins/git/utils/gitmenumanager.cpp index 9f1151f24..0576ae0c5 100644 --- a/src/plugins/git/utils/gitmenumanager.cpp +++ b/src/plugins/git/utils/gitmenumanager.cpp @@ -149,17 +149,17 @@ void GitMenuManager::createFileSubMenu() { auto mCurFile = ActionManager::instance()->actionContainer(M_GIT_FILE); auto fileLogAct = new QAction(this); - auto cmd = registerShortcut(fileLogAct, A_GIT_LOG_FILE, tr("Git Log"), QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_L)); + auto cmd = registerShortcut(fileLogAct, A_GIT_LOG_FILE, tr("Git Log"), QKeySequence("Alt+G,Alt+L")); mCurFile->addAction(cmd); connect(fileLogAct, &QAction::triggered, this, std::bind(&GitMenuManager::actionHandler, this, cmd, GitLog)); auto fileBlameAct = new QAction(this); - cmd = registerShortcut(fileBlameAct, A_GIT_BLAME_FILE, tr("Git Blame"), QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_B)); + cmd = registerShortcut(fileBlameAct, A_GIT_BLAME_FILE, tr("Git Blame"), QKeySequence("Alt+G,Alt+B")); mCurFile->addAction(cmd); connect(fileBlameAct, &QAction::triggered, this, std::bind(&GitMenuManager::actionHandler, this, cmd, GitBlame)); auto fileDiffAct = new QAction(this); - cmd = registerShortcut(fileDiffAct, A_GIT_DIFF_FILE, tr("Git Diff"), QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_D)); + cmd = registerShortcut(fileDiffAct, A_GIT_DIFF_FILE, tr("Git Diff"), QKeySequence("Alt+G,Alt+D")); mCurFile->addAction(cmd); connect(fileDiffAct, &QAction::triggered, this, std::bind(&GitMenuManager::actionHandler, this, cmd, GitDiff)); } diff --git a/src/plugins/option/optioncore/CMakeLists.txt b/src/plugins/option/optioncore/CMakeLists.txt index b57a4c75e..0a26afcab 100644 --- a/src/plugins/option/optioncore/CMakeLists.txt +++ b/src/plugins/option/optioncore/CMakeLists.txt @@ -4,43 +4,19 @@ project(optioncore) find_package(Qt5 COMPONENTS Xml REQUIRED) -set(CXX_CPP - - mainframe/shortcutsettingwidget.cpp - mainframe/persistentsettings.cpp - mainframe/optionsdialog.cpp - mainframe/optiondefaultkeeper.cpp - mainframe/profilesettingwidget.cpp - - mainframe/optionprofilesettinggenerator.cpp - mainframe/optionshortcutsettinggenerator.cpp - mainframe/navigationdelegate.cpp - mainframe/shortcutedit.cpp - optioncore.cpp - optioncore.json - ) - -set(CXX_H - - mainframe/shortcutsettingwidget.h - mainframe/persistentsettings.h - mainframe/optionsdialog.h - mainframe/optiondefaultkeeper.h - mainframe/profilesettingwidget.h - - mainframe/optionprofilesettinggenerator.h - mainframe/optionshortcutsettinggenerator.h - mainframe/navigationdelegate.h - mainframe/shortcutedit.h - optioncore.h - ) +FILE(GLOB_RECURSE PROJECT_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/*/*.h" + "${CMAKE_CURRENT_SOURCE_DIR}/*/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/*.json" +) add_library(${PROJECT_NAME} SHARED - ${CXX_CPP} - ${CXX_H} + ${PROJECT_SOURCES} optioncore.qrc - ) +) target_link_libraries(${PROJECT_NAME} framework @@ -50,6 +26,6 @@ target_link_libraries(${PROJECT_NAME} ${QtUseModules} ${PkgUserModules} Qt5::Xml - ) +) install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${PLUGIN_INSTALL_PATH}) diff --git a/src/plugins/option/optioncore/icons/shortcut_group_128px.svg b/src/plugins/option/optioncore/icons/shortcut_group_128px.svg new file mode 100644 index 000000000..218ace246 --- /dev/null +++ b/src/plugins/option/optioncore/icons/shortcut_group_128px.svg @@ -0,0 +1,151 @@ + + + ICON / action /shortcut_group + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/plugins/option/optioncore/mainframe/optionshortcutsettinggenerator.cpp b/src/plugins/option/optioncore/mainframe/optionshortcutsettinggenerator.cpp deleted file mode 100644 index 6787aa359..000000000 --- a/src/plugins/option/optioncore/mainframe/optionshortcutsettinggenerator.cpp +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "optionshortcutsettinggenerator.h" -#include "shortcutsettingwidget.h" - -OptionShortcutsettingGenerator::OptionShortcutsettingGenerator() -{ -} - -QWidget *OptionShortcutsettingGenerator::optionWidget() -{ - return new ShortcutSettingWidget(); -} diff --git a/src/plugins/option/optioncore/mainframe/optionshortcutsettinggenerator.h b/src/plugins/option/optioncore/mainframe/optionshortcutsettinggenerator.h deleted file mode 100644 index 7a5609258..000000000 --- a/src/plugins/option/optioncore/mainframe/optionshortcutsettinggenerator.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef OPTIONSHORTCUTSETTINGGENERATOR_H -#define OPTIONSHORTCUTSETTINGGENERATOR_H - -#include "services/option/optiongenerator.h" - -class OptionShortcutsettingGenerator : public dpfservice::OptionGenerator -{ - Q_OBJECT -public: - OptionShortcutsettingGenerator(); - inline static QString kitName() {return QObject::tr("Commands");} - virtual QWidget *optionWidget() override; -}; - -#endif // OPTIONSHORTCUTSETTINGGENERATOR_H diff --git a/src/plugins/option/optioncore/mainframe/shortcutdialog.cpp b/src/plugins/option/optioncore/mainframe/shortcutdialog.cpp new file mode 100644 index 000000000..f4ae1839b --- /dev/null +++ b/src/plugins/option/optioncore/mainframe/shortcutdialog.cpp @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "shortcutdialog.h" + +#include +#include + +#include +#include + +DWIDGET_USE_NAMESPACE + +ShortcutDialog::ShortcutDialog(QWidget *parent) + : DDialog(parent) +{ + initUI(); +} + +void ShortcutDialog::initUI() +{ + setIcon(QIcon::fromTheme("ide")); + setWindowTitle(tr("Add Shortcut")); + setWordWrapMessage(true); + setMessage(tr("Press desired key combination to add shortcut")); + layout()->setSizeConstraint(QLayout::SetMinAndMaxSize); + + keyEdit = new DLineEdit(this); + keyEdit->installEventFilter(this); + keyEdit->lineEdit()->installEventFilter(this); + connect(keyEdit, &DLineEdit::textChanged, this, &ShortcutDialog::keyValueChanged); + + msgLabel = new DLabel(this); + msgLabel->setVisible(false); + msgLabel->setWordWrap(true); + msgLabel->setAlignment(Qt::AlignCenter); + msgLabel->setForegroundRole(QPalette::Highlight); + auto f = msgLabel->font(); + f.setUnderline(true); + msgLabel->setFont(f); + + QWidget *contentWidget = new QWidget(this); + QVBoxLayout *vLayout = new QVBoxLayout(contentWidget); + vLayout->setContentsMargins(0, 0, 0, 0); + vLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); + vLayout->addWidget(keyEdit); + vLayout->addWidget(msgLabel); + + addContent(contentWidget); + addButton(tr("Cancel", "button")); + addButton(tr("Ok", "button"), true, DDialog::ButtonRecommend); + setFocusProxy(keyEdit); +} + +void ShortcutDialog::keyValueChanged() +{ + if (keyEdit->text().isEmpty()) + resetState(); + + if (!checkHandler) + return; + + int count = checkHandler(shortcutKey); + if (count > 0) { + QString msg = count == 1 ? tr("1 same shortcut command exist") + : tr("%1 same shortcut commands exist").arg(count); + msgLabel->setText(msg); + } + msgLabel->setVisible(count != 0); +} + +void ShortcutDialog::resetState() +{ + keyNum = 0; + shortcutKey = QKeySequence(); + keyEdit->clear(); + keyArray.fill(0); +} + +int ShortcutDialog::translateModifiers(Qt::KeyboardModifiers state, const QString &text) +{ + int result = 0; + if ((state & Qt::ShiftModifier) + && (text.isEmpty() + || !text.at(0).isPrint() + || text.at(0).isLetterOrNumber() + || text.at(0).isSpace())) + result |= Qt::SHIFT; + if (state & Qt::ControlModifier) + result |= Qt::CTRL; + if (state & Qt::MetaModifier) + result |= Qt::META; + if (state & Qt::AltModifier) + result |= Qt::ALT; + return result; +} + +QKeySequence ShortcutDialog::keySequece() const +{ + return shortcutKey; +} + +void ShortcutDialog::setConflictCheckHandler(ConflictCheckHandler handler) +{ + checkHandler = handler; +} + +bool ShortcutDialog::eventFilter(QObject *watched, QEvent *event) +{ + if (keyEdit && watched != keyEdit && watched != keyEdit->lineEdit()) + return DDialog::eventFilter(watched, event); + + if (keyEdit && event->type() == QEvent::KeyPress) { + auto k = static_cast(event); + int nextKey = k->key(); + if (keyNum > 3) + resetState(); + + if (nextKey == Qt::Key_Control + || nextKey == Qt::Key_Shift + || nextKey == Qt::Key_Meta + || nextKey == Qt::Key_Alt) { + return false; + } + + nextKey |= translateModifiers(k->modifiers(), k->text()); + switch (keyNum) { + case 0: + keyArray[0] = nextKey; + break; + case 1: + keyArray[1] = nextKey; + break; + case 2: + keyArray[2] = nextKey; + break; + case 3: + keyArray[3] = nextKey; + break; + default: + break; + } + keyNum++; + k->accept(); + shortcutKey = QKeySequence(keyArray[0], keyArray[1], keyArray[2], keyArray[3]); + keyEdit->setText(shortcutKey.toString(QKeySequence::NativeText)); + return true; + } + + return DDialog::eventFilter(watched, event); +} diff --git a/src/plugins/option/optioncore/mainframe/shortcutdialog.h b/src/plugins/option/optioncore/mainframe/shortcutdialog.h new file mode 100644 index 000000000..1a60caea1 --- /dev/null +++ b/src/plugins/option/optioncore/mainframe/shortcutdialog.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +DWIDGET_BEGIN_NAMESPACE +class DLineEdit; +class DLabel; +DWIDGET_END_NAMESPACE + +class ShortcutItem; +class ShortcutDialog : public DTK_WIDGET_NAMESPACE::DDialog +{ + Q_OBJECT +public: + explicit ShortcutDialog(QWidget *parent = nullptr); + + QKeySequence keySequece() const; + using ConflictCheckHandler = std::function; + void setConflictCheckHandler(ConflictCheckHandler handler); + +protected: + bool eventFilter(QObject *watched, QEvent *event) override; + +private: + void initUI(); + void keyValueChanged(); + void resetState(); + int translateModifiers(Qt::KeyboardModifiers state, const QString &text); + + DTK_WIDGET_NAMESPACE::DLineEdit *keyEdit { nullptr }; + DTK_WIDGET_NAMESPACE::DLabel *msgLabel { nullptr }; + ConflictCheckHandler checkHandler { nullptr }; + QKeySequence shortcutKey; + std::array keyArray { 0 }; + int keyNum { 0 }; +}; diff --git a/src/plugins/option/optioncore/mainframe/shortcutedit.cpp b/src/plugins/option/optioncore/mainframe/shortcutedit.cpp deleted file mode 100644 index 06d5e9a2f..000000000 --- a/src/plugins/option/optioncore/mainframe/shortcutedit.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "shortcutedit.h" - -ShortCutEdit::ShortCutEdit(QWidget *parent) - : DKeySequenceEdit(parent) -{ - menu = new DMenu(this); - QAction *clearAction = menu->addAction(tr("Clear")); - connect(clearAction, &QAction::triggered, this, &ShortCutEdit::clearText); -} - -void ShortCutEdit::clearText() -{ - this->clear(); - emit shortcutCleared(); -} \ No newline at end of file diff --git a/src/plugins/option/optioncore/mainframe/shortcutedit.h b/src/plugins/option/optioncore/mainframe/shortcutedit.h deleted file mode 100644 index 42be6d41c..000000000 --- a/src/plugins/option/optioncore/mainframe/shortcutedit.h +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include - -#include -#include - -using Dtk::Widget::DMenu; - -class ShortCutEdit : public Dtk::Widget::DKeySequenceEdit -{ - Q_OBJECT -public: - ShortCutEdit(QWidget *parent = nullptr); - -protected: - void contextMenuEvent(QContextMenuEvent *event) override - { - menu->exec(event->globalPos()); - } - -signals: - void shortcutCleared(); - -private slots: - void clearText(); - -private: - DMenu *menu = nullptr; -}; - diff --git a/src/plugins/option/optioncore/mainframe/shortcutitem.cpp b/src/plugins/option/optioncore/mainframe/shortcutitem.cpp new file mode 100644 index 000000000..9697c63fa --- /dev/null +++ b/src/plugins/option/optioncore/mainframe/shortcutitem.cpp @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "shortcutitem.h" + +#include +#include +#ifdef DTKWIDGET_CLASS_DPaletteHelper +# include +#endif + +#include +#include +#include + +DWIDGET_USE_NAMESPACE + +KeyLabel::KeyLabel(QWidget *parent) + : DFrame(parent) +{ + label = new DLabel(this); + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(layout->contentsMargins().left(), 0, layout->contentsMargins().right(), 0); + layout->setSpacing(0); + label->setForegroundRole(QPalette::ButtonText); + layout->addWidget(label); +} + +void KeyLabel::setKeySquece(const QString &keySequece) +{ + label->setText(keySequece); + update(); +} + +void KeyLabel::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + QStyleOptionFrame opt; + initStyleOption(&opt); + QPainter p(this); + drawShadow(&p, event->rect() - contentsMargins(), QColor(0, 0, 0, 20)); + + opt.features |= QStyleOptionFrame::Rounded; + +#ifdef DTKWIDGET_CLASS_DPaletteHelper + const DPalette &dp = DPaletteHelper::instance()->palette(this); +#else + const DPalette &dp = DGuiApplicationHelper::instance()->applicationPalette(); +#endif + + if (DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::LightType) { + p.setBackground(QColor(255, 255, 255)); + } else { + QColor bgColor(109, 109, 109); + if ((opt.state & QStyle::State_Active) == 0) { + auto inactive_mask_color = dp.color(QPalette::Window); + inactive_mask_color.setAlphaF(0.6); + bgColor = DGuiApplicationHelper::blendColor(bgColor, inactive_mask_color); + } + p.setBackground(bgColor); + } + + p.setPen(QPen(dp.frameBorder(), opt.lineWidth)); + style()->drawControl(QStyle::CE_ShapedFrame, &opt, &p, this); +} + +void KeyLabel::drawShadow(QPainter *p, const QRect &rect, const QColor &color) const +{ + DStyle dstyle; + int frame_radius = dstyle.pixelMetric(DStyle::PM_FrameRadius); + int shadow_xoffset = dstyle.pixelMetric(DStyle::PM_ShadowHOffset); + int shadow_yoffset = dstyle.pixelMetric(DStyle::PM_ShadowVOffset); + + QRect shadow = rect; + QPoint pointOffset(rect.center().x() + shadow_xoffset, rect.center().y() + shadow_yoffset); + shadow.moveCenter(pointOffset); + + p->setBrush(color); + p->setPen(Qt::NoPen); + p->setRenderHint(QPainter::Antialiasing); + p->drawRoundedRect(shadow, frame_radius, frame_radius); +} + +ShortcutLabel::ShortcutLabel(QWidget *parent) + : QWidget(parent) +{ + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(10); +} + +void ShortcutLabel::setKeySqueces(const QStringList &keySequeces) +{ + for (int i = 0; i < keySequeces.size(); ++i) { + auto keyLabel = new KeyLabel(this); + keyLabel->setKeySquece(keySequeces[i]); + layout()->addWidget(keyLabel); + } + + qobject_cast(layout())->addStretch(1); +} diff --git a/src/plugins/option/optioncore/mainframe/shortcutitem.h b/src/plugins/option/optioncore/mainframe/shortcutitem.h new file mode 100644 index 000000000..b88c083b6 --- /dev/null +++ b/src/plugins/option/optioncore/mainframe/shortcutitem.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SHORTCUTITEM_H +#define SHORTCUTITEM_H + +#include "common/actionmanager/command.h" + +#include +#include + +#include + +struct ShortcutItem +{ + Command *cmd; + QList shortcutKeys; + QTreeWidgetItem *item; +}; + +class KeyLabel : public DTK_WIDGET_NAMESPACE::DFrame +{ + Q_OBJECT +public: + explicit KeyLabel(QWidget *parent = nullptr); + + void setKeySquece(const QString &keySequece); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + void drawShadow(QPainter *p, const QRect &rect, const QColor &color) const; + +private: + DTK_WIDGET_NAMESPACE::DLabel *label { nullptr }; +}; + +class ShortcutLabel : public QWidget +{ + Q_OBJECT +public: + explicit ShortcutLabel(QWidget *parent = nullptr); + + void setKeySqueces(const QStringList &keySequeces); +}; + +Q_DECLARE_METATYPE(ShortcutItem *) +#endif // SHORTCUTITEM_H diff --git a/src/plugins/option/optioncore/mainframe/shortcutsettingwidget.cpp b/src/plugins/option/optioncore/mainframe/shortcutsettingwidget.cpp index 6c26b2faa..52e7fb591 100644 --- a/src/plugins/option/optioncore/mainframe/shortcutsettingwidget.cpp +++ b/src/plugins/option/optioncore/mainframe/shortcutsettingwidget.cpp @@ -3,337 +3,650 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "shortcutsettingwidget.h" -#include "shortcutedit.h" -#include "common/common.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include +#include "shortcutitem.h" +#include "shortcutdialog.h" + +#include "common/actionmanager/actionmanager.h" +#include "base/baseitemdelegate.h" + +#include +#include +#include + #include +#include +#include +#include +#include +#include +#include + +constexpr char kKeyboardShortcuts[] = "KeyboardShortcuts"; DWIDGET_USE_NAMESPACE -class ShortCutPrivate +QWidget *OptionShortcutsettingGenerator::optionWidget() { - QMap shortcutItemMap; - QMap shortcutItemShadowMap; - QString configFilePath; - QVBoxLayout *vlayout = nullptr; - QVBoxLayout *bgGplayout = nullptr; - DBackgroundGroup *bgGroup = nullptr; + return new ShortcutSettingWidget; +} - friend class ShortCut; +class ShortcutSettingWidgetPrivate : public QObject +{ +public: + struct Tr + { + Q_DECLARE_TR_FUNCTIONS(ShortcutSettingWidget); + }; + + explicit ShortcutSettingWidgetPrivate(ShortcutSettingWidget *qq); + + void initUI(); + void inintConnection(); + + void apply(); + void clear(); + void updateCommands(); + void updateShortcut(QTreeWidgetItem *item, const QList &keyList); + void showMenu(const QPoint &pos); + + void addShortcut(QTreeWidgetItem *item); + void changeShortcut(QTreeWidgetItem *item, int index); + void removeShortcut(QTreeWidgetItem *item, int index); + void removeAllShortcut(QTreeWidgetItem *item); + void resetShortcut(QTreeWidgetItem *item); + void exportAction(); + void importAction(); + void handleKeyRecord(); + void handleFilterChanged(const QString &f); + bool filter(const QString &filterString, QTreeWidgetItem *item); + bool filterColumn(const QString &filterString, QTreeWidgetItem *item, int column); + + ShortcutItem *shortcutItem(QTreeWidgetItem *item); + int checkConflict(const QKeySequence &key, int index); + QStringList keySequencesToNativeString(const QList &sequences); + void setModified(QTreeWidgetItem *item, bool modified); + void resetRecordState(); + int translateModifiers(Qt::KeyboardModifiers state, const QString &text); + QString createPlaceholderString(const QStringList &list); + +protected: + bool eventFilter(QObject *watched, QEvent *event) override; + +public: + ShortcutSettingWidget *q; + + DSearchEdit *searchEdit { nullptr }; + DToolButton *recordKeyBtn { nullptr }; + DIconButton *exportBtn { nullptr }; + DIconButton *importBtn { nullptr }; + QTreeWidget *commandWidget { nullptr }; + QList shortcutItems; + + // for key record + std::array keyArray { 0 }; + int keyNum { 0 }; }; -ShortCut::ShortCut(QWidget *parent) - : DFrame(parent), d(new ShortCutPrivate()) +ShortcutSettingWidgetPrivate::ShortcutSettingWidgetPrivate(ShortcutSettingWidget *qq) + : q(qq) { - d->configFilePath = (CustomPaths::user(CustomPaths::Flags::Configures) - + QDir::separator() + QString("shortcut.support")); - readShortcut(); - setFrameShape(QFrame::NoFrame); +} - d->vlayout = new QVBoxLayout(this); - d->bgGplayout = new QVBoxLayout; - d->bgGroup = new DBackgroundGroup(d->bgGplayout); - d->bgGroup->setBackgroundRole(QPalette::Window); - d->bgGroup->setUseWidgetBackground(false); - d->vlayout->addWidget(d->bgGroup); - updateUi(); +void ShortcutSettingWidgetPrivate::initUI() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(q); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + + QHBoxLayout *hSearchLayout = new QHBoxLayout; + searchEdit = new DSearchEdit(q); + searchEdit->setPlaceholderText(Tr::tr("Type to search in keybindings")); + searchEdit->installEventFilter(this); + searchEdit->lineEdit()->installEventFilter(this); + + recordKeyBtn = new DToolButton(q); + recordKeyBtn->setCheckable(true); + recordKeyBtn->setIcon(QIcon::fromTheme("shortcut_search")); + recordKeyBtn->setToolTip(Tr::tr("Record Keys")); + hSearchLayout->addWidget(searchEdit); + hSearchLayout->addWidget(recordKeyBtn); + + commandWidget = new QTreeWidget(q); + commandWidget->setHeaderLabels({ Tr::tr("Command"), Tr::tr("Label"), Tr::tr("Shortcut") }); + commandWidget->setColumnWidth(0, 290); + commandWidget->setColumnWidth(1, 250); + commandWidget->setAlternatingRowColors(true); + commandWidget->setIconSize({ 16, 16 }); + commandWidget->setFrameShape(QFrame::NoFrame); + commandWidget->header()->setStretchLastSection(false); + commandWidget->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + commandWidget->setMinimumHeight(360); + commandWidget->setItemDelegate(new BaseItemDelegate(commandWidget)); + commandWidget->setContextMenuPolicy(Qt::CustomContextMenu); + commandWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + exportBtn = new DIconButton(q); + exportBtn->setIconSize({ 16, 16 }); + exportBtn->setFlat(true); + exportBtn->setToolTip(Tr::tr("Export")); + exportBtn->setIcon(QIcon::fromTheme("export")); + + importBtn = new DIconButton(q); + importBtn->setIconSize({ 16, 16 }); + importBtn->setFlat(true); + importBtn->setToolTip(Tr::tr("Import")); + importBtn->setIcon(QIcon::fromTheme("import")); + + QHBoxLayout *hBtnLayout = new QHBoxLayout; + hBtnLayout->setAlignment(Qt::AlignRight); + hBtnLayout->setContentsMargins(0, 6, 20, 6); + hBtnLayout->setSpacing(10); + hBtnLayout->addWidget(importBtn); + hBtnLayout->addWidget(exportBtn); + + DFrame *frame = new DFrame(q); + QVBoxLayout *frameLayout = new QVBoxLayout(frame); + frameLayout->setSpacing(0); + frameLayout->setContentsMargins(0, 0, 0, 0); + frameLayout->addWidget(commandWidget); + frameLayout->addWidget(new DHorizontalLine(q)); + frameLayout->addLayout(hBtnLayout); + + mainLayout->addLayout(hSearchLayout); + mainLayout->addSpacing(10); + mainLayout->addWidget(frame); +} - QObject::connect(&dpf::Listener::instance(), &dpf::Listener::pluginsStarted, [=] { - updateDescriptions(); - }); +void ShortcutSettingWidgetPrivate::inintConnection() +{ + connect(commandWidget, &QTreeWidget::customContextMenuRequested, this, &ShortcutSettingWidgetPrivate::showMenu); + connect(importBtn, &DIconButton::clicked, this, &ShortcutSettingWidgetPrivate::importAction); + connect(exportBtn, &DIconButton::clicked, this, &ShortcutSettingWidgetPrivate::exportAction); + connect(recordKeyBtn, &DToolButton::clicked, this, &ShortcutSettingWidgetPrivate::handleKeyRecord); + connect(searchEdit, &DSearchEdit::textChanged, this, &ShortcutSettingWidgetPrivate::handleFilterChanged); } -ShortCut::~ShortCut() +void ShortcutSettingWidgetPrivate::apply() { + for (const auto item : std::as_const(shortcutItems)) + item->cmd->setKeySequences(item->shortcutKeys); } -void ShortCut::updateUi() +void ShortcutSettingWidgetPrivate::clear() { - //更新配置时先删除旧项 - if (d->bgGplayout->count()) { - while (auto layout = d->bgGplayout->takeAt(0)) - delete layout->widget(); + for (int i = commandWidget->topLevelItemCount() - 1; i >= 0; --i) { + delete commandWidget->takeTopLevelItem(i); } - QStringList ids = d->shortcutItemMap.keys(); - - for (auto id : ids) { - QStringList valueList = d->shortcutItemMap.value(id); - QString description = valueList.first(); - QString shortcut = valueList.last(); - - auto keyEdit = new ShortCutEdit(this); - keyEdit->setKeySequence(QKeySequence(shortcut)); - keyEdit->setFixedWidth(300); - keyEdit->ShortcutDirection(Qt::AlignLeft); - keyEdit->setFocusPolicy(Qt::StrongFocus); - - auto label = new DLabel(description, this); - auto wrapper = new DWidget; - auto hlayout = new QHBoxLayout(wrapper); - hlayout->setSpacing(0); - hlayout->setContentsMargins(0, 0, 0, 0); - hlayout->addWidget(label); - hlayout->addWidget(keyEdit); - hlayout->setAlignment(keyEdit, Qt::AlignRight); - - d->bgGplayout->addWidget(wrapper); - connect(keyEdit, &ShortCutEdit::shortcutCleared, [=]() { - updateShortcut(id, ""); - }); - connect(keyEdit, &DKeySequenceEdit::editingFinished, this, [=](const QKeySequence &sequence) { - bool inValid = keySequenceIsInvalid(sequence); - QString oldShortcut = d->shortcutItemMap.value(id).last(); - QKeySequence oldSequence(oldShortcut); - - if (inValid) { - showWarning(tr("Shortcut Invalid"), tr("Shortcut Invalid, Please enter again!")); - qWarning() << "keySequence is invalid"; - if (oldSequence.isEmpty()) { - keyEdit->clear(); - } else { - keyEdit->setKeySequence(oldSequence); - } - return; - } - bool isRepeat = shortcutRepeat(sequence.toString()); - if (isRepeat) { - showWarning(tr("Shortcut Repeated"), tr("Shortcut Repeated, Please enter again!")); - qWarning() << "isRepeat!!"; - if (oldSequence.isEmpty()) { - keyEdit->clear(); - } else { - keyEdit->setKeySequence(oldSequence); - } - return; - } - updateShortcut(id, sequence.toString()); - }); - } + qDeleteAll(shortcutItems); + shortcutItems.clear(); } -inline void ShortCut::showWarning(const QString &title, const QString &message) +void ShortcutSettingWidgetPrivate::updateCommands() { - DDialog warningDialog; - warningDialog.setWindowTitle(title); - warningDialog.setMessage(message); - warningDialog.setIcon(QIcon::fromTheme("dialog-warning")); - warningDialog.addButton(tr("OK")); - warningDialog.exec(); - return; + clear(); + QMap sectionMap; + + auto commands = ActionManager::instance()->commandList(); + for (auto cmd : commands) { + if (cmd->hasAttribute(Command::CA_NonConfigurable)) + continue; + if (cmd->action() && cmd->action()->isSeparator()) + continue; + + QTreeWidgetItem *item = new QTreeWidgetItem; + item->setSizeHint(0, { 0, 24 }); + ShortcutItem *shortcutItem = new ShortcutItem; + shortcutItem->cmd = cmd; + shortcutItem->item = item; + shortcutItem->shortcutKeys = cmd->keySequences(); + + const QString identifier = cmd->id(); + int pos = identifier.indexOf(QLatin1Char('.')); + const QString section = identifier.left(pos); + const QString subId = identifier.mid(pos + 1); + if (!sectionMap.contains(section)) { + auto groupItem = new QTreeWidgetItem(commandWidget, QStringList(section)); + sectionMap.insert(section, groupItem); + groupItem->setIcon(0, QIcon::fromTheme("shortcut_group")); + commandWidget->expandItem(groupItem); + } + sectionMap[section]->addChild(item); + + item->setData(0, Qt::UserRole, QVariant::fromValue(shortcutItem)); + item->setText(0, subId); + item->setText(1, cmd->description()); + updateShortcut(item, shortcutItem->shortcutKeys); + shortcutItems << shortcutItem; + } + + commandWidget->sortByColumn(0, Qt::AscendingOrder); } -int ShortCut::rowCount() const +void ShortcutSettingWidgetPrivate::updateShortcut(QTreeWidgetItem *item, const QList &keyList) { - return d->shortcutItemMap.size(); + auto scItem = shortcutItem(item); + if (!scItem) + return; + + setModified(item, scItem->cmd->defaultKeySequences() != keyList); + scItem->shortcutKeys = keyList; + commandWidget->removeItemWidget(item, 2); + if (keyList.isEmpty()) { + item->setText(2, ""); + return; + } + + const auto &keyStrList = keySequencesToNativeString(scItem->shortcutKeys); + ShortcutLabel *keyLabel = new ShortcutLabel(q); + keyLabel->setKeySqueces(keyStrList); + commandWidget->setItemWidget(item, 2, keyLabel); + + // for auto-adjusting the column width + item->setText(2, createPlaceholderString(keyStrList)); } -bool ShortCut::keySequenceIsInvalid(const QKeySequence &sequence) const +void ShortcutSettingWidgetPrivate::showMenu(const QPoint &pos) { - for (uint i = 0; i < static_cast(sequence.count()); i++) { - if (Qt::Key_unknown == sequence[i]) { - return true; - } + auto item = commandWidget->itemAt(pos); + auto scItem = shortcutItem(item); + if (!scItem) + return; + + QMenu menu; + menu.addAction(Tr::tr("Add Shortcut"), this, std::bind(&ShortcutSettingWidgetPrivate::addShortcut, this, item)); + + // change action + for (int i = 0; i < scItem->shortcutKeys.size(); ++i) { + QString keyText = Tr::tr("Change %1"); + menu.addAction(keyText.arg(scItem->shortcutKeys[i].toString(QKeySequence::NativeText)), + this, std::bind(&ShortcutSettingWidgetPrivate::changeShortcut, this, item, i)); + } + menu.addSeparator(); + // remove action + for (int i = 0; i < scItem->shortcutKeys.size(); ++i) { + QString keyText = Tr::tr("Remove %1"); + menu.addAction(keyText.arg(scItem->shortcutKeys[i].toString(QKeySequence::NativeText)), + this, std::bind(&ShortcutSettingWidgetPrivate::removeShortcut, this, item, i)); } + menu.addSeparator(); + if (scItem->shortcutKeys.size() > 1) + menu.addAction(Tr::tr("Remove All Shortcut"), this, std::bind(&ShortcutSettingWidgetPrivate::removeAllShortcut, this, item)); + + if (scItem->shortcutKeys != scItem->cmd->defaultKeySequences()) + menu.addAction(Tr::tr("Reset Shortcut"), this, std::bind(&ShortcutSettingWidgetPrivate::resetShortcut, this, item)); - return false; + menu.exec(QCursor::pos()); } -bool ShortCut::shortcutRepeat(const QString &text) const +void ShortcutSettingWidgetPrivate::addShortcut(QTreeWidgetItem *item) { - int count = 0; - foreach (QString key, d->shortcutItemMap.keys()) { - QStringList valueList = d->shortcutItemMap.value(key); - if (0 == text.compare(valueList.last(), Qt::CaseInsensitive)) { - if (++count > 0) - return true; - } - } + auto scItem = shortcutItem(item); + if (!scItem) + return; + + ShortcutDialog dlg; + dlg.setConflictCheckHandler(std::bind(&ShortcutSettingWidgetPrivate::checkConflict, + this, std::placeholders::_1, scItem->shortcutKeys.size())); + if (dlg.exec() != 1) + return; + + const auto &key = dlg.keySequece(); + scItem->shortcutKeys << key; + updateShortcut(item, scItem->shortcutKeys); +} - return false; +void ShortcutSettingWidgetPrivate::changeShortcut(QTreeWidgetItem *item, int index) +{ + auto scItem = shortcutItem(item); + if (!scItem) + return; + + ShortcutDialog dlg; + dlg.setConflictCheckHandler(std::bind(&ShortcutSettingWidgetPrivate::checkConflict, + this, std::placeholders::_1, index)); + if (dlg.exec() != 1) + return; + + const auto &key = dlg.keySequece(); + scItem->shortcutKeys.replace(index, key); + updateShortcut(item, scItem->shortcutKeys); } -void ShortCut::updateShortcut(QString id, QString shortcut) +void ShortcutSettingWidgetPrivate::removeShortcut(QTreeWidgetItem *item, int index) { - if (d->shortcutItemMap.keys().contains(id)) { - QStringList valueList = d->shortcutItemMap.value(id); - QStringList newValueList = { valueList.first(), shortcut }; - d->shortcutItemMap[id] = newValueList; - } + auto scItem = shortcutItem(item); + if (!scItem || index >= scItem->shortcutKeys.size()) + return; + + scItem->shortcutKeys.removeAt(index); + updateShortcut(item, scItem->shortcutKeys); } -void ShortCut::resetAllShortcut() +void ShortcutSettingWidgetPrivate::removeAllShortcut(QTreeWidgetItem *item) { - d->shortcutItemMap = d->shortcutItemShadowMap; + updateShortcut(item, {}); } -void ShortCut::saveShortcut() +void ShortcutSettingWidgetPrivate::resetShortcut(QTreeWidgetItem *item) { - ShortcutUtil::writeToJson(d->configFilePath, d->shortcutItemMap); + auto scItem = shortcutItem(item); + if (!scItem) + return; - QList commandList = ActionManager::instance()->commandList(); - for (auto cmd : commandList) { - QString id = cmd->id(); - if (d->shortcutItemMap.contains(id)) { - QStringList valueList = d->shortcutItemMap[id]; - cmd->setKeySequences({ QKeySequence(valueList.last()) }); + updateShortcut(item, scItem->cmd->defaultKeySequences()); +} + +void ShortcutSettingWidgetPrivate::exportAction() +{ + const auto &filePath = QFileDialog::getSaveFileName(q, Tr::tr("Export Keyboard Mapping Scheme"), + QStandardPaths::writableLocation(QStandardPaths::HomeLocation), + Tr::tr("Keyboard Mapping Scheme (*.kms)")); + if (filePath.isEmpty()) + return; + + QSettings settings(filePath, QSettings::IniFormat); + for (auto item : qAsConst(shortcutItems)) { + const auto &id = item->cmd->id(); + const auto settingsKey = QString(kKeyboardShortcuts) + '/' + id; + const QList keys = item->shortcutKeys; + if (keys.isEmpty()) { + settings.setValue(settingsKey, QString()); + } else if (keys.size() == 1) { + settings.setValue(settingsKey, keys.first().toString()); + } else { + QStringList shortcutList; + std::transform(keys.begin(), keys.end(), std::back_inserter(shortcutList), + [](const QKeySequence &k) { + return k.toString(); + }); + settings.setValue(settingsKey, shortcutList); } } } -void ShortCut::readShortcut() +void ShortcutSettingWidgetPrivate::importAction() { - QList commandList = ActionManager::instance()->commandList(); - for (auto cmd : commandList) { - QString id = cmd->id(); - QStringList valueList = QStringList { cmd->description(), cmd->keySequence().toString() }; - d->shortcutItemMap[id] = valueList; + const auto &filePath = QFileDialog::getOpenFileName(q, Tr::tr("Import Keyboard Mapping Scheme"), + QStandardPaths::writableLocation(QStandardPaths::HomeLocation), + Tr::tr("Keyboard Mapping Scheme (*.kms)")); + if (filePath.isEmpty()) + return; + + // read shortcut from `filePath` + QMap> mapping; + QSettings settings(filePath, QSettings::IniFormat); + settings.beginGroup(kKeyboardShortcuts); + QStringList keys = settings.allKeys(); + for (const QString &key : keys) { + const QVariant v = settings.value(key); + if (QMetaType::Type(v.type()) == QMetaType::QStringList) { + auto list = v.toStringList(); + QList keySequenceList; + std::transform(list.begin(), list.end(), std::back_inserter(keySequenceList), + [](const QString &s) { + return QKeySequence::fromString(s); + }); + mapping.insert(key, keySequenceList); + } else { + mapping.insert(key, { QKeySequence::fromString(v.toString()) }); + } } + settings.endGroup(); - QMap shortcutItemMap; - ShortcutUtil::readFromJson(d->configFilePath, shortcutItemMap); - foreach (const QString key, shortcutItemMap.keys()) { - if (d->shortcutItemMap[key].isEmpty()) - d->shortcutItemMap[key] = shortcutItemMap.value(key); - else - d->shortcutItemMap[key].last() = shortcutItemMap.value(key).last(); + // set shortcut + for (ShortcutItem *item : std::as_const(shortcutItems)) { + QString sid = item->cmd->id(); + if (mapping.contains(sid)) + updateShortcut(item->item, mapping.value(sid)); } +} - d->shortcutItemShadowMap = d->shortcutItemMap; +void ShortcutSettingWidgetPrivate::handleKeyRecord() +{ + if (recordKeyBtn->isChecked()) { + searchEdit->setPlaceholderText(Tr::tr("Recording Keys. Press Escape to exit")); + } else { + searchEdit->setPlaceholderText(Tr::tr("Type to search in keybindings")); + } + + searchEdit->setFocus(); + searchEdit->lineEdit()->selectAll(); } -void ShortCut::updateDescriptions() +void ShortcutSettingWidgetPrivate::handleFilterChanged(const QString &f) { - QList commandList = ActionManager::instance()->commandList(); - bool update = false; - for (auto cmd : commandList) { - QString id = cmd->id(); - //已存在该Id的快捷键,但描述不同 -- 比如 配置中为中文 但应用为英文,显示会异常。 - if (!d->shortcutItemMap[id].isEmpty() && (d->shortcutItemMap[id].first() != cmd->description())) { - d->shortcutItemMap[id].first() = cmd->description(); - update = true; - } + if (f.isEmpty()) + resetRecordState(); + + for (int i = 0; i < commandWidget->topLevelItemCount(); ++i) { + QTreeWidgetItem *item = commandWidget->topLevelItem(i); + filter(f, item); } +} - if (update) { - updateUi(); - d->shortcutItemShadowMap = d->shortcutItemMap; +bool ShortcutSettingWidgetPrivate::filter(const QString &filterString, QTreeWidgetItem *item) +{ + bool visible = filterString.isEmpty(); + int columnCount = item->columnCount(); + for (int i = 0; !visible && i < columnCount; ++i) + visible |= !filterColumn(filterString, item, i); + + int childCount = item->childCount(); + if (childCount > 0) { + // force visibility if this item matches + QString tmpFilterString = visible ? QString() : filterString; + for (int i = 0; i < childCount; ++i) { + QTreeWidgetItem *citem = item->child(i); + // parent visible if any child visible + visible |= !filter(tmpFilterString, citem); + } } + item->setHidden(!visible); + return !visible; } -void ShortCut::importExternalJson(const QString &filePath) +bool ShortcutSettingWidgetPrivate::filterColumn(const QString &filterString, QTreeWidgetItem *item, int column) { - QMap shortcutItemMap; - ShortcutUtil::readFromJson(filePath, shortcutItemMap); - foreach (QString key, shortcutItemMap.keys()) { - d->shortcutItemMap[key] = shortcutItemMap.value(key); + const auto scItem = shortcutItem(item); + // shortcut + if (column == item->columnCount() - 1) { + if (!scItem) + return true; + + for (const auto &k : scItem->shortcutKeys) { + const auto &keyString = k.toString(QKeySequence::PortableText); + bool found = keyString.contains(filterString, Qt::CaseInsensitive); + if (found) + return false; + } + return true; } - d->shortcutItemShadowMap = d->shortcutItemMap; - updateUi(); + QString text; + // command id + if (column == 0 && scItem) { + text = scItem->cmd->id(); + } else { + text = item->text(column); + } + return !text.contains(filterString, Qt::CaseInsensitive); } -void ShortCut::exportExternalJson(const QString &filePath) +ShortcutItem *ShortcutSettingWidgetPrivate::shortcutItem(QTreeWidgetItem *item) { - ShortcutUtil::writeToJson(filePath, d->shortcutItemMap); + if (!item) + return nullptr; + return item->data(0, Qt::UserRole).value(); } -class ShortCut; -class ShortcutSettingWidgetPrivate +int ShortcutSettingWidgetPrivate::checkConflict(const QKeySequence &key, int index) { - ShortcutSettingWidgetPrivate(); - ShortCut *shortcut; + if (key.isEmpty()) + return 0; + + QTreeWidgetItem *current = commandWidget->currentItem(); + ShortcutItem *item = shortcutItem(current); + if (!item) + return 0; + + int conflictCount = 0; + for (ShortcutItem *currentItem : std::as_const(shortcutItems)) { + if (item == currentItem) + continue; + + for (const auto &k : qAsConst(currentItem->shortcutKeys)) { + if (k == key) + conflictCount++; + } + } - friend class ShortcutSettingWidget; -}; + return conflictCount; +} -ShortcutSettingWidgetPrivate::ShortcutSettingWidgetPrivate() - : shortcut(nullptr) +QStringList ShortcutSettingWidgetPrivate::keySequencesToNativeString(const QList &sequences) { + QList validSequences; + std::copy_if(sequences.begin(), sequences.end(), std::back_inserter(validSequences), + [](const QKeySequence &k) { + return !k.isEmpty(); + }); + + QStringList keyList; + std::transform(validSequences.begin(), validSequences.end(), std::back_inserter(keyList), + [](const QKeySequence &k) { + return k.toString(QKeySequence::NativeText); + }); + + return keyList; } -ShortcutSettingWidget::ShortcutSettingWidget(QWidget *parent) - : PageWidget(parent), d(new ShortcutSettingWidgetPrivate()) +void ShortcutSettingWidgetPrivate::setModified(QTreeWidgetItem *item, bool modified) { - setupUi(); - readConfig(); + QFont f = item->font(0); + f.setItalic(modified); + item->setFont(0, f); + item->setFont(1, f); + f.setBold(modified); + item->setFont(2, f); } -ShortcutSettingWidget::~ShortcutSettingWidget() +void ShortcutSettingWidgetPrivate::resetRecordState() { + keyNum = 0; + searchEdit->clear(); + keyArray.fill(0); } -void ShortcutSettingWidget::setupUi() +int ShortcutSettingWidgetPrivate::translateModifiers(Qt::KeyboardModifiers state, const QString &text) { - QVBoxLayout *vLayout = new QVBoxLayout(this); - - auto shortcutFrame = new DFrame(this); - auto shortcutLayout = new QVBoxLayout(shortcutFrame); - - d->shortcut = new ShortCut(shortcutFrame); - - shortcutLayout->addWidget(d->shortcut); + int result = 0; + if ((state & Qt::ShiftModifier) + && (text.isEmpty() + || !text.at(0).isPrint() + || text.at(0).isLetterOrNumber() + || text.at(0).isSpace())) + result |= Qt::SHIFT; + if (state & Qt::ControlModifier) + result |= Qt::CTRL; + if (state & Qt::MetaModifier) + result |= Qt::META; + if (state & Qt::AltModifier) + result |= Qt::ALT; + return result; +} - vLayout->addWidget(shortcutFrame); +QString ShortcutSettingWidgetPrivate::createPlaceholderString(const QStringList &list) +{ + if (list.isEmpty()) + return {}; + + QFontMetrics metrics(q->font()); + QString placeholderString(20, ' '); + for (int i = 0; i < list.size(); ++i) { + if (i != 0) + placeholderString.append(QString(22, ' ')); + + placeholderString.append(QString(list[i].size(), ' ')); + int width = metrics.horizontalAdvance(list[i]); + while (metrics.horizontalAdvance(placeholderString) < width) { + placeholderString.append(' '); + } + } - DCommandLinkButton *resetbtn = new DCommandLinkButton(tr("Reset All")); - DCommandLinkButton *exportbtn = new DCommandLinkButton(tr("Export")); - DCommandLinkButton *importbtn = new DCommandLinkButton(tr("Import")); + return placeholderString; +} - auto spliter = new DVerticalLine(this); +bool ShortcutSettingWidgetPrivate::eventFilter(QObject *watched, QEvent *event) +{ + if (!searchEdit || !recordKeyBtn || (watched != searchEdit && watched != searchEdit->lineEdit())) + return QObject::eventFilter(watched, event); + + if (recordKeyBtn->isChecked() && event->type() == QEvent::KeyPress) { + auto k = static_cast(event); + int nextKey = k->key(); + if (nextKey == Qt::Key_Escape) { + recordKeyBtn->click(); + return true; + } - auto hlayout = new QHBoxLayout; - hlayout->setAlignment(Qt::AlignRight); - hlayout->addWidget(resetbtn); - hlayout->addWidget(spliter); - hlayout->addWidget(exportbtn); - hlayout->addWidget(importbtn); + if (keyNum > 3) + resetRecordState(); - vLayout->addLayout(hlayout); + if (nextKey == Qt::Key_Control + || nextKey == Qt::Key_Shift + || nextKey == Qt::Key_Meta + || nextKey == Qt::Key_Alt) { + return false; + } - connect(resetbtn, &DCommandLinkButton::clicked, this, &ShortcutSettingWidget::onBtnResetAllClicked); - connect(exportbtn, &DCommandLinkButton::clicked, this, &ShortcutSettingWidget::onBtnExportClicked); - connect(importbtn, &DCommandLinkButton::clicked, this, &ShortcutSettingWidget::onBtnImportClicked); -} + nextKey |= translateModifiers(k->modifiers(), k->text()); + switch (keyNum) { + case 0: + keyArray[0] = nextKey; + break; + case 1: + keyArray[1] = nextKey; + break; + case 2: + keyArray[2] = nextKey; + break; + case 3: + keyArray[3] = nextKey; + break; + default: + break; + } + keyNum++; + k->accept(); + QKeySequence shortcutKey(keyArray[0], keyArray[1], keyArray[2], keyArray[3]); + searchEdit->setText(shortcutKey.toString(QKeySequence::NativeText)); + return true; + } -void ShortcutSettingWidget::onBtnResetAllClicked() -{ - d->shortcut->resetAllShortcut(); - d->shortcut->updateUi(); + return QObject::eventFilter(watched, event); } -void ShortcutSettingWidget::saveConfig() +ShortcutSettingWidget::ShortcutSettingWidget(QWidget *parent) + : PageWidget(parent), + d(new ShortcutSettingWidgetPrivate(this)) { - d->shortcut->saveShortcut(); + d->initUI(); + d->inintConnection(); } -void ShortcutSettingWidget::readConfig() +ShortcutSettingWidget::~ShortcutSettingWidget() { - d->shortcut->readShortcut(); + delete d; } -void ShortcutSettingWidget::onBtnImportClicked() +void ShortcutSettingWidget::saveConfig() { - QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), tr(""), tr("Json File(*.json)")); - if (!fileName.isEmpty()) { - d->shortcut->importExternalJson(fileName); - } + d->apply(); + ActionManager::instance()->saveSettings(); } -void ShortcutSettingWidget::onBtnExportClicked() +void ShortcutSettingWidget::readConfig() { - QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), tr(""), tr("Json File(*.json)")); - if (!fileName.isEmpty()) { - d->shortcut->exportExternalJson(fileName); - } + d->updateCommands(); } diff --git a/src/plugins/option/optioncore/mainframe/shortcutsettingwidget.h b/src/plugins/option/optioncore/mainframe/shortcutsettingwidget.h index 2fb7d69af..fcc0c84e2 100644 --- a/src/plugins/option/optioncore/mainframe/shortcutsettingwidget.h +++ b/src/plugins/option/optioncore/mainframe/shortcutsettingwidget.h @@ -5,47 +5,15 @@ #ifndef SHORTCUTSETTINGWIDGET_H #define SHORTCUTSETTINGWIDGET_H -#include -#include -#include -#include -#include +#include "common/widget/pagewidget.h" +#include "services/option/optiongenerator.h" -enum ColumnID -{ - - kDescriptions, - kShortcut, - _KCount -}; - -class ShortCutPrivate; -class ShortCut : public DTK_WIDGET_NAMESPACE::DFrame +class OptionShortcutsettingGenerator : public dpfservice::OptionGenerator { Q_OBJECT public: - explicit ShortCut(QWidget *parent = nullptr); - virtual ~ShortCut(); - - int rowCount() const; - int columnCount() const; - - void updateUi(); - void updateShortcut(QString id, QString shortcut); - void resetAllShortcut(); - void saveShortcut(); - void readShortcut(); - void importExternalJson(const QString &filePath); - void exportExternalJson(const QString &filePath); - void updateDescriptions(); - bool shortcutRepeat(const QString &text) const; - bool keySequenceIsInvalid(const QKeySequence &sequence) const; - void showWarning(const QString& title, const QString& message); - -signals: - -private: - ShortCutPrivate *d; + inline static QString kitName() { return QObject::tr("Commands"); } + virtual QWidget *optionWidget() override; }; class ShortcutSettingWidgetPrivate; @@ -55,19 +23,12 @@ class ShortcutSettingWidget : public PageWidget public: explicit ShortcutSettingWidget(QWidget *parent = nullptr); virtual ~ShortcutSettingWidget(); - void saveConfig(); - void readConfig(); - void checkShortcutValidity(const int row, const QString &shortcut); -signals: + void saveConfig() override; + void readConfig() override; -public slots: - void onBtnResetAllClicked(); - void onBtnImportClicked(); - void onBtnExportClicked(); private: - void setupUi(); ShortcutSettingWidgetPrivate *const d; }; -#endif // SHORTCUTSETTINGWIDGET_H +#endif // SHORTCUTSETTINGWIDGET_H diff --git a/src/plugins/option/optioncore/optioncore.cpp b/src/plugins/option/optioncore/optioncore.cpp index 4515e5e4c..9f67c9113 100644 --- a/src/plugins/option/optioncore/optioncore.cpp +++ b/src/plugins/option/optioncore/optioncore.cpp @@ -5,7 +5,7 @@ #include "optioncore.h" #include "mainframe/optiondefaultkeeper.h" #include "mainframe/optionprofilesettinggenerator.h" -#include "mainframe/optionshortcutsettinggenerator.h" +#include "mainframe/shortcutsettingwidget.h" #include "common/common.h" #include "base/abstractwidget.h" diff --git a/src/plugins/option/optioncore/optioncore.qrc b/src/plugins/option/optioncore/optioncore.qrc index b886efe29..a11183b12 100644 --- a/src/plugins/option/optioncore/optioncore.qrc +++ b/src/plugins/option/optioncore/optioncore.qrc @@ -1,6 +1,11 @@ - + icons/options_alert_16px.svg texts/options_setting_32px.svg + texts/edit_16px.svg + texts/export_16px.svg + texts/import_16px.svg + texts/shortcut_search_16px.svg + icons/shortcut_group_128px.svg diff --git a/src/plugins/option/optioncore/texts/edit_16px.svg b/src/plugins/option/optioncore/texts/edit_16px.svg new file mode 100644 index 000000000..0b0f3beab --- /dev/null +++ b/src/plugins/option/optioncore/texts/edit_16px.svg @@ -0,0 +1,9 @@ + + + ICON / action /edit + + + + + + \ No newline at end of file diff --git a/src/plugins/option/optioncore/texts/export_16px.svg b/src/plugins/option/optioncore/texts/export_16px.svg new file mode 100644 index 000000000..aa0d4cd7d --- /dev/null +++ b/src/plugins/option/optioncore/texts/export_16px.svg @@ -0,0 +1,7 @@ + + + ICON / action /export + + + + \ No newline at end of file diff --git a/src/plugins/option/optioncore/texts/import_16px.svg b/src/plugins/option/optioncore/texts/import_16px.svg new file mode 100644 index 000000000..9caa7bc4f --- /dev/null +++ b/src/plugins/option/optioncore/texts/import_16px.svg @@ -0,0 +1,7 @@ + + + ICON / action /import + + + + \ No newline at end of file diff --git a/src/plugins/option/optioncore/texts/shortcut_search_16px.svg b/src/plugins/option/optioncore/texts/shortcut_search_16px.svg new file mode 100644 index 000000000..45a186cd0 --- /dev/null +++ b/src/plugins/option/optioncore/texts/shortcut_search_16px.svg @@ -0,0 +1,7 @@ + + + ICON / action /shortcut_search + + + + \ No newline at end of file