From da6f5f7da0343f5ce073bdc65cb9d2d641ed79ba Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 28 Feb 2022 15:32:49 +0800 Subject: [PATCH 01/19] Gui: add ShortcutManager to unify shortcut handling Support longest key sequence match with user defined delay (configurable through 'Customize -> Keyboard -> Key sequence delay'). Support user defined priority to resolve shortcut conflict through 'Customize -> Keyboard') Add 'All' category in 'Customize -> Keyboard' to list all command and showing their shortcuts Unify macro command shortcut setting (BaseApp/Preferences/Shortcut). --- src/Gui/Action.cpp | 319 ++++++++++++++++++++- src/Gui/Action.h | 36 ++- src/Gui/CMakeLists.txt | 2 + src/Gui/Command.cpp | 136 ++++----- src/Gui/Command.h | 18 ++ src/Gui/CommandPyImp.cpp | 50 +--- src/Gui/CommandView.cpp | 26 +- src/Gui/DlgActionsImp.cpp | 34 +-- src/Gui/DlgActionsImp.h | 1 + src/Gui/DlgKeyboard.ui | 523 ++++++++++++++++++++------------- src/Gui/DlgKeyboardImp.cpp | 556 ++++++++++++++++++------------------ src/Gui/DlgKeyboardImp.h | 48 +++- src/Gui/DlgToolbarsImp.cpp | 145 +--------- src/Gui/DlgToolbarsImp.h | 2 + src/Gui/ShortcutManager.cpp | 455 +++++++++++++++++++++++++++++ src/Gui/ShortcutManager.h | 171 +++++++++++ src/Gui/Workbench.cpp | 3 + 17 files changed, 1773 insertions(+), 752 deletions(-) create mode 100644 src/Gui/ShortcutManager.cpp create mode 100644 src/Gui/ShortcutManager.h diff --git a/src/Gui/Action.cpp b/src/Gui/Action.cpp index ad210694c1ab..033fb84449af 100644 --- a/src/Gui/Action.cpp +++ b/src/Gui/Action.cpp @@ -53,6 +53,7 @@ #include "WhatsThis.h" #include "Widgets.h" #include "Workbench.h" +#include "ShortcutManager.h" using namespace Gui; @@ -145,6 +146,11 @@ void Action::setEnabled(bool b) _action->setEnabled(b); } +bool Action::isEnabled() const +{ + return _action->isEnabled(); +} + void Action::setVisible(bool b) { _action->setVisible(b); @@ -153,6 +159,7 @@ void Action::setVisible(bool b) void Action::setShortcut(const QString & key) { _action->setShortcut(key); + setToolTip(_tooltip, _title); } QKeySequence Action::shortcut() const @@ -183,6 +190,8 @@ QString Action::statusTip() const void Action::setText(const QString & s) { _action->setText(s); + if (_title.isEmpty()) + setToolTip(_tooltip); } QString Action::text() const @@ -190,14 +199,112 @@ QString Action::text() const return _action->text(); } -void Action::setToolTip(const QString & s) -{ - _action->setToolTip(s); +void Action::setToolTip(const QString & s, const QString & title) +{ + _tooltip = s; + _title = title; + _action->setToolTip(createToolTip(s, + title.isEmpty() ? _action->text() : title, + _action->font(), + _action->shortcut().toString(QKeySequence::NativeText), + this)); +} + +QString Action::createToolTip(QString _tooltip, + const QString & title, + const QFont &font, + const QString &sc, + Action *act) +{ + QString text = title; + text.remove(QLatin1Char('&'));; + while(text.size() && text[text.size()-1].isPunct()) + text.resize(text.size()-1); + + if (text.isEmpty()) + return _tooltip; + + // The following code tries to make a more useful tooltip by inserting at + // the beginning of the tooltip the action title in bold followed by the + // shortcut. + // + // The long winding code is to deal with the fact that Qt will auto wrap + // a rich text tooltip but the width is too short. We can escape the auto + // wrappin using

. + + QString shortcut = sc; + if (shortcut.size() && _tooltip.endsWith(shortcut)) + _tooltip.resize(_tooltip.size() - shortcut.size()); + if (shortcut.size()) + shortcut = QString::fromLatin1(" (%1)").arg(shortcut); + + QString tooltip = QString::fromLatin1( + "

%1%2

").arg( + text.toHtmlEscaped(), shortcut.toHtmlEscaped()); + + QString cmdName; + auto pcCmd = act ? act->_pcCmd : nullptr; + if (pcCmd && pcCmd->getName()) { + cmdName = QString::fromLatin1(pcCmd->getName()); + if (auto groupcmd = dynamic_cast(pcCmd)) { + int idx = act->property("defaultAction").toInt(); + auto cmd = groupcmd->getCommand(idx); + if (cmd && cmd->getName()) + cmdName = QStringLiteral("%1 (%2:%3)") + .arg(QString::fromLatin1(cmd->getName())) + .arg(cmdName) + .arg(idx); + } + cmdName = QStringLiteral("

%1

") + .arg(cmdName.toHtmlEscaped()); + } + + if (shortcut.size() && _tooltip.endsWith(shortcut)) + _tooltip.resize(_tooltip.size() - shortcut.size()); + + if (_tooltip.isEmpty() + || _tooltip == text + || _tooltip == title) + { + return tooltip + cmdName; + } + if (Qt::mightBeRichText(_tooltip)) { + // already rich text, so let it be to avoid duplicated unwrapping + return tooltip + _tooltip + cmdName; + } + + tooltip += QString::fromLatin1( + "

"); + + // If the user supplied tooltip contains line break, we shall honour it. + if (_tooltip.indexOf(QLatin1Char('\n')) >= 0) + tooltip += _tooltip.toHtmlEscaped() + QString::fromLatin1("

") ; + else { + // If not, try to end the non wrapping paragraph at some pre defined + // width, so that the following text can wrap at that width. + float tipWidth = 400; + QFontMetrics fm(font); + int width = fm.width(_tooltip); + if (width <= tipWidth) + tooltip += _tooltip.toHtmlEscaped() + QString::fromLatin1("

") ; + else { + int index = tipWidth / width * _tooltip.size(); + // Try to only break at white space + for(int i=0; i<50 && index<_tooltip.size(); ++i, ++index) { + if (_tooltip[index] == QLatin1Char(' ')) + break; + } + tooltip += _tooltip.left(index).toHtmlEscaped() + + QString::fromLatin1("

") + + _tooltip.right(_tooltip.size()-index).trimmed().toHtmlEscaped(); + } + } + return tooltip + cmdName; } QString Action::toolTip() const { - return _action->toolTip(); + return _tooltip; } void Action::setWhatsThis(const QString & s) @@ -544,7 +651,7 @@ void WorkbenchGroup::addTo(QWidget *w) QToolBar* bar = qobject_cast(w); QComboBox* box = new WorkbenchComboBox(this, w); box->setIconSize(QSize(16, 16)); - box->setToolTip(_action->toolTip()); + box->setToolTip(_tooltip); box->setStatusTip(_action->statusTip()); box->setWhatsThis(_action->whatsThis()); box->addActions(_group->actions()); @@ -1243,4 +1350,206 @@ void WindowAction::addTo ( QWidget * w ) } } +// -------------------------------------------------------------------- + +struct CmdInfo { + Command *cmd = nullptr; + QString text; + QString tooltip; + QIcon icon; + bool iconChecked = false; +}; +static std::vector _Commands; +static int _CommandRevision; + +class CommandModel : public QAbstractItemModel +{ +public: + +public: + CommandModel(QObject* parent) + : QAbstractItemModel(parent) + { + update(); + } + + void update() + { + auto &manager = Application::Instance->commandManager(); + if (_CommandRevision == manager.getRevision()) + return; + beginResetModel(); + _CommandRevision = manager.getRevision(); + _Commands.clear(); + for (auto &v : manager.getCommands()) { + _Commands.emplace_back(); + auto &info = _Commands.back(); + info.cmd = v.second; + } + endResetModel(); + } + + virtual QModelIndex parent(const QModelIndex &) const + { + return QModelIndex(); + } + + virtual QVariant data(const QModelIndex & index, int role) const + { + if (index.row() < 0 || index.row() >= (int)_Commands.size()) + return QVariant(); + + auto &info = _Commands[index.row()]; + + switch(role) { + case Qt::DisplayRole: + case Qt::EditRole: + if (info.text.isEmpty()) { +#if QT_VERSION>=QT_VERSION_CHECK(5,2,0) + info.text = QString::fromLatin1("%2 (%1)").arg( + QString::fromLatin1(info.cmd->getName()), + qApp->translate(info.cmd->className(), info.cmd->getMenuText())); +#else + info.text = qApp->translate(info.cmd->className(), info.cmd->getMenuText()); +#endif + info.text.replace(QLatin1Char('&'), QString()); + if (info.text.isEmpty()) + info.text = QString::fromLatin1(info.cmd->getName()); + } + return info.text; + + case Qt::DecorationRole: + if (!info.iconChecked) { + info.iconChecked = true; + if(info.cmd->getPixmap()) + info.icon = BitmapFactory().iconFromTheme(info.cmd->getPixmap()); + } + return info.icon; + + case Qt::ToolTipRole: + if (info.tooltip.isEmpty()) { + info.tooltip = QString::fromLatin1("%1: %2").arg( + QString::fromLatin1(info.cmd->getName()), + qApp->translate(info.cmd->className(), info.cmd->getMenuText())); + QString tooltip = qApp->translate(info.cmd->className(), info.cmd->getToolTipText()); + if (tooltip.size()) + info.tooltip += QString::fromLatin1("\n\n") + tooltip; + } + return info.tooltip; + + case Qt::UserRole: + return QByteArray(info.cmd->getName()); + + default: + return QVariant(); + } + } + + virtual QModelIndex index(int row, int, const QModelIndex &) const + { + return this->createIndex(row, 0); + } + + virtual int rowCount(const QModelIndex &) const + { + return (int)(_Commands.size()); + } + + virtual int columnCount(const QModelIndex &) const + { + return 1; + } +}; + + +// -------------------------------------------------------------------- + +CommandCompleter::CommandCompleter(QLineEdit *lineedit, QObject *parent) + : QCompleter(parent) +{ + this->setModel(new CommandModel(this)); +#if QT_VERSION>=QT_VERSION_CHECK(5,2,0) + this->setFilterMode(Qt::MatchContains); +#endif + this->setCaseSensitivity(Qt::CaseInsensitive); + this->setCompletionMode(QCompleter::PopupCompletion); + this->setWidget(lineedit); + connect(lineedit, SIGNAL(textEdited(QString)), this, SLOT(onTextChanged(QString))); + connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(onCommandActivated(QModelIndex))); + connect(this, SIGNAL(highlighted(QString)), lineedit, SLOT(setText(QString))); +} + +bool CommandCompleter::eventFilter(QObject *o, QEvent *ev) +{ + if (ev->type() == QEvent::KeyPress + && (o == this->widget() || o == this->popup())) + { + QKeyEvent * ke = static_cast(ev); + switch(ke->key()) { + case Qt::Key_Escape: { + auto edit = qobject_cast(this->widget()); + if (edit && edit->text().size()) { + edit->setText(QString()); + popup()->hide(); + return true; + } else if (popup()->isVisible()) { + popup()->hide(); + return true; + } + break; + } + case Qt::Key_Tab: { + if (this->popup()->isVisible()) { + QKeyEvent kevent(ke->type(),Qt::Key_Down,0); + qApp->sendEvent(this->popup(), &kevent); + return true; + } + break; + } + case Qt::Key_Backtab: { + if (this->popup()->isVisible()) { + QKeyEvent kevent(ke->type(),Qt::Key_Up,0); + qApp->sendEvent(this->popup(), &kevent); + return true; + } + break; + } + case Qt::Key_Enter: + case Qt::Key_Return: + if (o == this->widget()) { + auto index = currentIndex(); + if (index.isValid()) + onCommandActivated(index); + else + complete(); + ev->setAccepted(true); + return true; + } + default: + break; + } + } + return QCompleter::eventFilter(o, ev); +} + +void CommandCompleter::onCommandActivated(const QModelIndex &index) +{ + QByteArray name = completionModel()->data(index, Qt::UserRole).toByteArray(); + Q_EMIT commandActivated(name); +} + +void CommandCompleter::onTextChanged(const QString &txt) +{ + if (txt.size() < 3 || !widget()) + return; + + static_cast(this->model())->update(); + + this->setCompletionPrefix(txt); + QRect rect = widget()->rect(); + if (rect.width() < 300) + rect.setWidth(300); + this->complete(rect); +} + #include "moc_Action.cpp" diff --git a/src/Gui/Action.h b/src/Gui/Action.h index d50deb8cb0b8..5c656fc746c6 100644 --- a/src/Gui/Action.h +++ b/src/Gui/Action.h @@ -28,6 +28,7 @@ #include #include #include +#include namespace Gui { @@ -57,6 +58,7 @@ class GuiExport Action : public QObject void setCheckable(bool); void setChecked (bool, bool no_signal=false); bool isChecked() const; + bool isEnabled() const; void setShortcut (const QString &); QKeySequence shortcut() const; @@ -66,7 +68,7 @@ class GuiExport Action : public QObject QString statusTip() const; void setText (const QString &); QString text() const; - void setToolTip (const QString &); + void setToolTip (const QString &, const QString &title = QString()); QString toolTip() const; void setWhatsThis (const QString &); QString whatsThis() const; @@ -75,6 +77,16 @@ class GuiExport Action : public QObject return _action; } + static QString createToolTip(QString tooltip, + const QString &title, + const QFont &font, + const QString &shortcut, + Action *action = nullptr); + + Command *command() const { + return _pcCmd; + } + public Q_SLOTS: virtual void onActivated (); virtual void onToggled (bool); @@ -82,6 +94,8 @@ public Q_SLOTS: protected: QAction* _action; Command *_pcCmd; + QString _tooltip; + QString _title; }; // -------------------------------------------------------------------- @@ -357,6 +371,26 @@ class GuiExport WindowAction : public ActionGroup QMenu* _menu; }; +/** + * Command name completer. + */ +class GuiExport CommandCompleter : public QCompleter +{ + Q_OBJECT +public: + CommandCompleter(QLineEdit *edit, QObject *parent = nullptr); + +Q_SIGNALS: + void commandActivated(const QByteArray &name); + +protected Q_SLOTS: + void onTextChanged(const QString &); + void onCommandActivated(const QModelIndex &); + +protected: + bool eventFilter(QObject *, QEvent *ev); +}; + } // namespace Gui #endif // GUI_ACTION_H diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 0f9dd088c0dc..431f62f9204a 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -395,6 +395,7 @@ SET(Command_CPP_SRCS CommandStructure.cpp CommandLink.cpp CommandPyImp.cpp + ShortcutManager.cpp ) SET(Command_SRCS ${Command_CPP_SRCS} @@ -402,6 +403,7 @@ SET(Command_SRCS ActionFunction.h Command.h CommandT.h + ShortcutManager.h ) SOURCE_GROUP("Command" FILES ${Command_SRCS}) diff --git a/src/Gui/Command.cpp b/src/Gui/Command.cpp index 83c31092124d..7d90f071c237 100644 --- a/src/Gui/Command.cpp +++ b/src/Gui/Command.cpp @@ -61,6 +61,7 @@ #include "WhatsThis.h" #include "WorkbenchManager.h" #include "Workbench.h" +#include "ShortcutManager.h" FC_LOG_LEVEL_INIT("Command", true, true) @@ -158,6 +159,11 @@ CommandBase::~CommandBase() //Note: The Action object becomes a children of MainWindow which gets destroyed _before_ the //command manager hence before any command object. So the action pointer is a dangling pointer //at this state. + + // Command can be destroyed before the the MainWindow, for example, dynamic + // command created (and later deleted) by user for a pie menu. + if (getMainWindow()) + delete _pcAction; } Action* CommandBase::getAction() const @@ -223,6 +229,19 @@ Command::~Command() { } +void Command::setShortcut(const QString &shortcut) +{ + if (_pcAction) + _pcAction->setShortcut(shortcut); +} + +QString Command::getShortcut() const +{ + if (_pcAction) + return _pcAction->shortcut().toString(); + return ShortcutManager::instance()->getShortcut(getName()); +} + bool Command::isViewOfType(Base::Type t) const { Gui::Document *d = getGuiApplication()->activeDocument(); @@ -239,6 +258,12 @@ void Command::addTo(QWidget *pcWidget) { if (!_pcAction) { _pcAction = createAction(); +#ifdef FC_DEBUG + // Accelerator conflict can now be dynamically resolved in ShortcutManager + // + // printConflictingAccelerators(); +#endif + setShortcut(ShortcutManager::instance()->getShortcut(getName(), getAccel())); testActive(); } @@ -255,6 +280,12 @@ void Command::addToGroup(ActionGroup* group) { if (!_pcAction) { _pcAction = createAction(); +#ifdef FC_DEBUG + // Accelerator conflict can now be dynamically resolved in ShortcutManager + // + // printConflictingAccelerators(); +#endif + setShortcut(ShortcutManager::instance()->getShortcut(getName(), getAccel())); testActive(); } group->addAction(_pcAction->findChild()); @@ -856,37 +887,14 @@ const char * Command::endCmdHelp(void) return "\n\n"; } -void Command::recreateTooltip(const char* context, Action* action) +void Command::applyCommandData(const char* context, Action* action) { - QString tooltip; - tooltip.append(QString::fromLatin1("

")); - tooltip.append(QCoreApplication::translate( + action->setText(QCoreApplication::translate( context, getMenuText())); - tooltip.append(QString::fromLatin1("

")); - QRegularExpression re(QString::fromLatin1("([^&])&([^&])")); - tooltip.replace(re, QString::fromLatin1("\\1\\2")); - tooltip.replace(QString::fromLatin1("&&"), QString::fromLatin1("&")); - tooltip.append(QCoreApplication::translate( + action->setToolTip(QCoreApplication::translate( context, getToolTipText())); - tooltip.append(QString::fromLatin1("
(")); - tooltip.append(QCoreApplication::translate( + action->setWhatsThis(QCoreApplication::translate( context, getWhatsThis())); - tooltip.append(QString::fromLatin1(") ")); - action->setToolTip(tooltip); - - QString accel = action->shortcut().toString(QKeySequence::NativeText); - if (!accel.isEmpty()) { - // show shortcut inside tooltip - QString ttip = QString::fromLatin1("%1 (%2)") - .arg(action->toolTip(), accel); - action->setToolTip(ttip); - - // show shortcut inside status tip - QString stip = QString::fromLatin1("(%1)\t%2") - .arg(accel, action->statusTip()); - action->setStatusTip(stip); - } - if (sStatusTip) action->setStatusTip(QCoreApplication::translate( context, getStatusTip())); @@ -895,14 +903,6 @@ void Command::recreateTooltip(const char* context, Action* action) context, getToolTipText())); } -void Command::applyCommandData(const char* context, Action* action) -{ - action->setText(QCoreApplication::translate( - context, getMenuText())); - recreateTooltip(context, action); - action->setWhatsThis(QCoreApplication::translate( - context, getWhatsThis())); -} const char* Command::keySequenceToAccel(int sk) const { @@ -973,10 +973,6 @@ Action * Command::createAction(void) { Action *pcAction; pcAction = new Action(this,getMainWindow()); -#ifdef FC_DEBUG - printConflictingAccelerators(); -#endif - pcAction->setShortcut(QString::fromLatin1(sAccel)); applyCommandData(this->className(), pcAction); if (sPixmap) pcAction->setIcon(Gui::BitmapFactory().iconFromTheme(sPixmap)); @@ -1016,6 +1012,13 @@ Command *GroupCommand::addCommand(const char *name) { return cmd; } +Command *GroupCommand::getCommand(int idx) const +{ + if (idx >= 0 && idx < (int)cmds.size()) + return cmds[idx].first; + return nullptr; +} + Action * GroupCommand::createAction(void) { ActionGroup* pcAction = new ActionGroup(this, getMainWindow()); pcAction->setMenuRole(QAction::NoRole); @@ -1062,20 +1065,26 @@ void GroupCommand::languageChange() { void GroupCommand::setup(Action *pcAction) { - pcAction->setText(QCoreApplication::translate(className(), getMenuText())); - int idx = pcAction->property("defaultAction").toInt(); if(idx>=0 && idx<(int)cmds.size() && cmds[idx].first) { auto cmd = cmds[idx].first; - pcAction->setIcon(BitmapFactory().iconFromTheme(cmd->getPixmap())); - pcAction->setChecked(cmd->getAction()->isChecked(),true); + pcAction->setText(QCoreApplication::translate(className(), getMenuText())); + if (auto childAction = cmd->getAction()) + pcAction->setIcon(childAction->icon()); + else + pcAction->setIcon(BitmapFactory().iconFromTheme(cmd->getPixmap())); const char *context = dynamic_cast(cmd) ? cmd->getName() : cmd->className(); const char *tooltip = cmd->getToolTipText(); const char *statustip = cmd->getStatusTip(); if (!statustip || '\0' == *statustip) statustip = tooltip; - recreateTooltip(context, pcAction); + pcAction->setToolTip(QCoreApplication::translate(context,tooltip), + QCoreApplication::translate(cmd->className(), cmd->getMenuText())); pcAction->setStatusTip(QCoreApplication::translate(context,statustip)); + } else { + applyCommandData(this->className(), pcAction); + if (sPixmap) + pcAction->setIcon(Gui::BitmapFactory().iconFromTheme(sPixmap)); } } @@ -1143,23 +1152,6 @@ Action * MacroCommand::createAction(void) pcAction->setWhatsThis(QString::fromUtf8(sWhatsThis)); if (sPixmap) pcAction->setIcon(Gui::BitmapFactory().pixmap(sPixmap)); -#ifdef FC_DEBUG - printConflictingAccelerators(); -#endif - pcAction->setShortcut(QString::fromLatin1(sAccel)); - - QString accel = pcAction->shortcut().toString(QKeySequence::NativeText); - if (!accel.isEmpty()) { - // show shortcut inside tooltip - QString ttip = QString::fromLatin1("%1 (%2)") - .arg(pcAction->toolTip(), accel); - pcAction->setToolTip(ttip); - - // show shortcut inside status tip - QString stip = QString::fromLatin1("(%1)\t%2") - .arg(accel, pcAction->statusTip()); - pcAction->setStatusTip(stip); - } return pcAction; } @@ -1348,10 +1340,6 @@ Action * PythonCommand::createAction(void) Action *pcAction; pcAction = new Action(this, qtAction, getMainWindow()); -#ifdef FC_DEBUG - printConflictingAccelerators(); -#endif - pcAction->setShortcut(QString::fromLatin1(getAccel())); applyCommandData(this->getName(), pcAction); if (strcmp(getResource("Pixmap"),"") != 0) pcAction->setIcon(Gui::BitmapFactory().iconFromTheme(getResource("Pixmap"))); @@ -1753,15 +1741,25 @@ CommandManager::~CommandManager() void CommandManager::addCommand(Command* pCom) { - _sCommands[pCom->getName()] = pCom;// pCom->Init(); + auto &cmd = _sCommands[pCom->getName()]; + if (cmd) { + if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_ERR("duplicate command " << pCom->getName()); + return; + } + ++_revision; + cmd = pCom; + signalChanged(); } void CommandManager::removeCommand(Command* pCom) { std::map ::iterator It = _sCommands.find(pCom->getName()); if (It != _sCommands.end()) { + ++_revision; delete It->second; _sCommands.erase(It); + signalChanged(); } } @@ -1790,12 +1788,13 @@ std::string CommandManager::newMacroName() const return name; } - void CommandManager::clearCommands() { for ( std::map::iterator it = _sCommands.begin(); it != _sCommands.end(); ++it ) delete it->second; _sCommands.clear(); + ++_revision; + signalChanged(); } bool CommandManager::addTo(const char* Name, QWidget *pcWidget) @@ -1880,6 +1879,7 @@ void CommandManager::addCommandMode(const char* sContext, const char* sName) void CommandManager::updateCommands(const char* sContext, int mode) { std::map >::iterator it = _sCommandModes.find(sContext); + int rev = _revision; if (it != _sCommandModes.end()) { for (std::list::iterator jt = it->second.begin(); jt != it->second.end(); ++jt) { Command* cmd = getCommandByName(jt->c_str()); @@ -1888,6 +1888,8 @@ void CommandManager::updateCommands(const char* sContext, int mode) } } } + if (rev != _revision) + signalChanged(); } const Command* Gui::CommandManager::checkAcceleratorForConflicts(const char* accel, const Command* ignore) const diff --git a/src/Gui/Command.h b/src/Gui/Command.h index c30473735efc..78e308e3e310 100644 --- a/src/Gui/Command.h +++ b/src/Gui/Command.h @@ -25,6 +25,10 @@ #define GUI_COMMAND_H #include +#include +#include +#include +#include #include #include @@ -568,6 +572,11 @@ class GuiExport Command : public CommandBase //@} + /// Override shortcut of this command + virtual void setShortcut (const QString &); + /// Obtain the current shortcut of this command + virtual QString getShortcut() const; + /** @name arbitrary helper methods */ //@{ void adjustCameraPosition(); @@ -641,6 +650,7 @@ class GuiExport GroupCommand : public Command { */ Command *addCommand(const char *cmdName); + Command *getCommand(int idx) const; protected: virtual void activated(int iMsg); virtual Gui::Action * createAction(void); @@ -871,6 +881,12 @@ class GuiExport CommandManager void addCommandMode(const char* sContext, const char* sName); void updateCommands(const char* sContext, int mode); + /// Return a revision number to check for addition or removal of any command + int getRevision() { return _revision; } + + /// Signal on any addition or removal of command + boost::signals2::signal signalChanged; + /** * Returns a pointer to a conflicting command, or nullptr if there is no conflict. * In the case of multiple conflicts, only the first is returned. @@ -891,6 +907,8 @@ class GuiExport CommandManager void clearCommands(); std::map _sCommands; std::map > _sCommandModes; + + int _revision = 0; }; } // namespace Gui diff --git a/src/Gui/CommandPyImp.cpp b/src/Gui/CommandPyImp.cpp index f40a292bea57..7623e0dda080 100644 --- a/src/Gui/CommandPyImp.cpp +++ b/src/Gui/CommandPyImp.cpp @@ -36,6 +36,7 @@ // inclusion of the generated files (generated out of AreaPy.xml) #include "CommandPy.h" #include "CommandPy.cpp" +#include "ShortcutManager.h" // returns a string which represents the object e.g. when printed in python @@ -187,33 +188,8 @@ PyObject* CommandPy::setShortcut(PyObject *args) Command* cmd = this->getCommandPtr(); if (cmd) { - Action* action = cmd->getAction(); - if (action) { - QKeySequence shortcut = QString::fromLatin1(pShortcut); - QString nativeText = shortcut.toString(QKeySequence::NativeText); - action->setShortcut(nativeText); - bool success = action->shortcut() == nativeText; - /** - * avoid cluttering parameters unnecessarily by saving only - * when new shortcut is not the default shortcut - * remove spaces to handle cases such as shortcut = "C,L" or "C, L" - */ - QString default_shortcut = QString::fromLatin1(cmd->getAccel()); - QString spc = QString::fromLatin1(" "); - - ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Shortcut"); - const char* pName = cmd->getName(); - if (success && default_shortcut.remove(spc).toUpper() != nativeText.remove(spc).toUpper()) { - hGrp->SetASCII(pName, pShortcut); - } - else { - hGrp->RemoveASCII(pName); - } - return Py::new_reference_to(Py::Boolean(success)); - } - else { - return Py::new_reference_to(Py::Boolean(false)); - } + ShortcutManager::instance()->setShortcut(cmd->getName(), pShortcut); + return Py::new_reference_to(Py::Boolean(true)); } else { PyErr_Format(Base::BaseExceptionFreeCADError, "No such command"); @@ -229,24 +205,8 @@ PyObject* CommandPy::resetShortcut(PyObject *args) Command* cmd = this->getCommandPtr(); if (cmd) { - Action* action = cmd->getAction(); - if (action){ - QString default_shortcut = QString::fromLatin1(cmd->getAccel()); - action->setShortcut(default_shortcut); - ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Shortcut"); - hGrp->RemoveASCII(cmd->getName()); - /** test to see if we successfully reset the shortcut by loading it back and comparing */ - QString spc = QString::fromLatin1(" "); - QString new_shortcut = action->shortcut().toString(); - if (default_shortcut.remove(spc).toUpper() == new_shortcut.remove(spc).toUpper()){ - return Py::new_reference_to(Py::Boolean(true)); - } else { - return Py::new_reference_to(Py::Boolean(false)); - } - } else { - return Py::new_reference_to(Py::Boolean(false)); - } - + ShortcutManager::instance()->reset(cmd->getName()); + return Py::new_reference_to(Py::Boolean(true)); } else { PyErr_Format(Base::BaseExceptionFreeCADError, "No such command"); return nullptr; diff --git a/src/Gui/CommandView.cpp b/src/Gui/CommandView.cpp index 8055c0bc4416..04b8eb23ee2f 100644 --- a/src/Gui/CommandView.cpp +++ b/src/Gui/CommandView.cpp @@ -254,14 +254,17 @@ class StdCmdFreezeViews : public Gui::Command public: StdCmdFreezeViews(); virtual ~StdCmdFreezeViews(){} - const char* className() const + const char* className() const override { return "StdCmdFreezeViews"; } + void setShortcut (const QString &) override; + QString getShortcut() const override; + protected: - virtual void activated(int iMsg); - virtual bool isActive(void); - virtual Action * createAction(void); - virtual void languageChange(); + virtual void activated(int iMsg) override; + virtual bool isActive(void) override; + virtual Action * createAction(void) override; + virtual void languageChange() override; private: void onSaveViews(); @@ -324,6 +327,19 @@ Action * StdCmdFreezeViews::createAction(void) return pcAction; } +void StdCmdFreezeViews::setShortcut(const QString &shortcut) +{ + if (freezeView) + freezeView->setShortcut(shortcut); +} + +QString StdCmdFreezeViews::getShortcut() const +{ + if (freezeView) + return freezeView->shortcut().toString(); + return Command::getShortcut(); +} + void StdCmdFreezeViews::activated(int iMsg) { ActionGroup* pcAction = qobject_cast(_pcAction); diff --git a/src/Gui/DlgActionsImp.cpp b/src/Gui/DlgActionsImp.cpp index 1228b4629ce8..2b93a5085559 100644 --- a/src/Gui/DlgActionsImp.cpp +++ b/src/Gui/DlgActionsImp.cpp @@ -43,6 +43,7 @@ #include "Command.h" #include "BitmapFactory.h" #include "Widgets.h" +#include "ShortcutManager.h" #include "ui_DlgChooseIcon.h" using namespace Gui::Dialog; @@ -90,6 +91,8 @@ DlgCustomActionsImp::DlgCustomActionsImp( QWidget* parent ) /** Destroys the object and frees any allocated resources */ DlgCustomActionsImp::~DlgCustomActionsImp() { + if (bChanged) + MacroCommand::save(); } bool DlgCustomActionsImp::event(QEvent* e) @@ -129,17 +132,18 @@ bool DlgCustomActionsImp::event(QEvent* e) void DlgCustomActionsImp::onAddMacroAction(const QByteArray&) { - // does nothing + bChanged = true; } -void DlgCustomActionsImp::onRemoveMacroAction(const QByteArray&) +void DlgCustomActionsImp::onRemoveMacroAction(const QByteArray &name) { - // does nothing + bChanged = true; + ShortcutManager::instance()->reset(name.constData()); } void DlgCustomActionsImp::onModifyMacroAction(const QByteArray&) { - // does nothing + bChanged = true; } void DlgCustomActionsImp::showActions() @@ -195,7 +199,8 @@ void DlgCustomActionsImp::on_actionListWidget_itemActivated(QTreeWidgetItem *ite ui->actionMenu -> setText(QString::fromUtf8(pScript->getMenuText())); ui->actionToolTip -> setText(QString::fromUtf8(pScript->getToolTipText())); ui->actionStatus -> setText(QString::fromUtf8(pScript->getStatusTip())); - ui->actionAccel -> setText(QString::fromLatin1(pScript->getAccel())); + ui->actionAccel -> setText(ShortcutManager::instance()->getShortcut( + actionName.constData(), pScript->getAccel())); ui->pixmapLabel->clear(); m_sPixmap.clear(); const char* name = pScript->getPixmap(); @@ -266,7 +271,8 @@ void DlgCustomActionsImp::on_buttonAddAction_clicked() m_sPixmap.clear(); if (!ui->actionAccel->text().isEmpty()) { - macro->setAccel(ui->actionAccel->text().toLatin1()); + ShortcutManager::instance()->setShortcut( + actionName.constData(), ui->actionAccel->text().toLatin1().constData()); } ui->actionAccel->clear(); @@ -338,20 +344,8 @@ void DlgCustomActionsImp::on_buttonReplaceAction_clicked() action->setStatusTip(QString::fromUtf8(macro->getStatusTip())); if (macro->getPixmap()) action->setIcon(Gui::BitmapFactory().pixmap(macro->getPixmap())); - action->setShortcut(QString::fromLatin1(macro->getAccel())); - - QString accel = action->shortcut().toString(QKeySequence::NativeText); - if (!accel.isEmpty()) { - // show shortcut inside tooltip - QString ttip = QString::fromLatin1("%1 (%2)") - .arg(action->toolTip(), accel); - action->setToolTip(ttip); - - // show shortcut inside status tip - QString stip = QString::fromLatin1("(%1)\t%2") - .arg(accel, action->statusTip()); - action->setStatusTip(stip); - } + action->setShortcut(ShortcutManager::instance()->getShortcut( + actionName.constData(), macro->getAccel())); } // emit signal to notify the container widget diff --git a/src/Gui/DlgActionsImp.h b/src/Gui/DlgActionsImp.h index 216884156706..8167b7e6fec3 100644 --- a/src/Gui/DlgActionsImp.h +++ b/src/Gui/DlgActionsImp.h @@ -88,6 +88,7 @@ protected Q_SLOTS: private: std::unique_ptr ui; QString m_sPixmap; /**< Name of the specified pixmap */ + bool bChanged = false; }; class Ui_DlgChooseIcon; diff --git a/src/Gui/DlgKeyboard.ui b/src/Gui/DlgKeyboard.ui index e32224d09159..f48ea1df127e 100644 --- a/src/Gui/DlgKeyboard.ui +++ b/src/Gui/DlgKeyboard.ui @@ -6,237 +6,339 @@ 0 0 - 578 - 344 + 642 + 376 Keyboard - - - 9 - - - 6 - - - - - 6 - - - 0 - - - - - Description: - - - - - - - - - - - - - - + + + Qt::Horizontal - - QSizePolicy::Preferred - - - - 40 - 20 - - - - - - - - 6 - - - 0 - - - - - &Category: - - - categoryBox - - - - - - - - - - C&ommands: - - - commandTreeWidget - - - - - - - - 220 - 0 - - - - false - - - - 1 - - - - - - - - - - 6 - - - 0 - - - - - Current shortcut: + + + + 0 - - - - - - false + + 0 - - - - - - Press &new shortcut: + + 0 - - editShortcut + + 0 - - - - - - - - - Currently assigned to: - - - - - - - false - - - - 220 - 0 - - - - false - - - - 1 - - - - - + + + + + + + + 0 + 0 + + + + &Category: + + + categoryBox + + + + + + + + + + + 220 + 0 + + + + false + + + true + + + + 1 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Multi-key sequence delay: + + + + + + + Current shortcut: + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + Time in milliseconds to wait for the next key stroke of the current key sequence. +For example, pressing 'F' twice in less than the time delay setting here will be +be treated as shorctcut key sequence 'F, F'. + + + 10000 + + + 100 + + + 300 + + + Shortcut/Settings + + + ShortcutTimeout + + + + + + + + 220 + 0 + + + + true + + + false + + + + 1 + + + + + + + + true + + + + + + + + + &New shortcut: + + + editShortcut + + + + + + + + + + + + + 0 + 20 + + + + This list shows commands having the same shortcut in the priority from hight +to low. If more than one command with the same shortcut are active at the +same time. The one with the highest prioirty will be triggered. + + + Shorcut priority list: + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + &Assign + + + Alt+A + + + + + + + Clear + + + + + + + &Reset + + + Alt+R + + + + + + + Re&set All + + + Alt+S + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 41 + 150 + + + + + + + + false + + + Up + + + + + + + false + + + Down + + + + + + + + - - - - 6 - - - 0 + + + + QLayout::SetDefaultConstraint - - - &Assign + + + + 0 + 0 + - - Alt+A - - - - - - Clear + Description: - - - &Reset - - - Alt+R + + + + 0 + 0 + - - - - - Re&set All - - - Alt+S + - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 41 - 150 - - - - @@ -248,14 +350,25 @@ QLineEdit
Gui/Widgets.h
+ + Gui::PrefSpinBox + QSpinBox +
Gui/PrefWidgets.h
+
+ editCommand categoryBox commandTreeWidget accelLineEditShortcut buttonAssign + buttonClear buttonReset buttonResetAll + shortcutTimeout + assignedTreeWidget + buttonUp + buttonDown diff --git a/src/Gui/DlgKeyboardImp.cpp b/src/Gui/DlgKeyboardImp.cpp index f993a687ebc1..52bb18824450 100644 --- a/src/Gui/DlgKeyboardImp.cpp +++ b/src/Gui/DlgKeyboardImp.cpp @@ -22,13 +22,17 @@ #include "PreCompiled.h" +#include #ifndef _PreComp_ # include # include # include +# include #endif #include +#include +#include #include "DlgKeyboardImp.h" #include "ui_DlgKeyboard.h" @@ -38,6 +42,8 @@ #include "Command.h" #include "Widgets.h" #include "Window.h" +#include "PrefWidgets.h" +#include "ShortcutManager.h" using namespace Gui::Dialog; @@ -71,6 +77,231 @@ DlgCustomKeyboardImp::DlgCustomKeyboardImp( QWidget* parent ) { ui->setupUi(this); + conn = initCommandWidgets(ui->commandTreeWidget, + ui->categoryBox, + ui->editCommand, + ui->assignedTreeWidget, + ui->buttonUp, + ui->buttonDown, + ui->editShortcut, + ui->accelLineEditShortcut); + + ui->shortcutTimeout->onRestore(); + QTimer *timer = new QTimer(this); + QObject::connect(ui->shortcutTimeout, QOverload::of(&QSpinBox::valueChanged), + timer, [=](int) {timer->start(100);}); + QObject::connect(timer, &QTimer::timeout, [=](){ui->shortcutTimeout->onSave();}); +} + +/** Destroys the object and frees any allocated resources */ +DlgCustomKeyboardImp::~DlgCustomKeyboardImp() +{ +} + +void DlgCustomKeyboardImp::initCommandCompleter(QLineEdit *edit, QComboBox *combo, QTreeWidget *commandTreeWidget) +{ + edit->setPlaceholderText(tr("Type to search...")); + auto completer = new CommandCompleter(edit, edit); + + QObject::connect(completer, &CommandCompleter::commandActivated, + [combo, commandTreeWidget](const QByteArray &name) { + CommandManager & cCmdMgr = Application::Instance->commandManager(); + Command *cmd = cCmdMgr.getCommandByName(name.constData()); + if (!cmd) + return; + + QString group = QString::fromLatin1(cmd->getGroupName()); + int index = combo->findData(group); + if (index < 0) + return; + if (index != combo->currentIndex()) { + combo->setCurrentIndex(index); + combo->activated(index); + } + for (int i=0 ; itopLevelItemCount(); ++i) { + QTreeWidgetItem *item = commandTreeWidget->topLevelItem(i); + if (item->data(1, Qt::UserRole).toByteArray() == name) { + commandTreeWidget->setCurrentItem(item); + return; + } + } + }); +} + +boost::signals2::connection +DlgCustomKeyboardImp::initCommandList(QTreeWidget *commandTreeWidget, QComboBox *combo) +{ + QStringList labels; + labels << tr("Icon") << tr("Command") << tr("Shortcut") << tr("Default"); + commandTreeWidget->setHeaderLabels(labels); + commandTreeWidget->setIconSize(QSize(32, 32)); + commandTreeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + commandTreeWidget->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + commandTreeWidget->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); + + QObject::connect(combo, QOverload::of(&QComboBox::activated), [=](int index) { + QByteArray current; + if (auto item = commandTreeWidget->currentItem()) + current = item->data(1, Qt::UserRole).toByteArray(); + + commandTreeWidget->clear(); + CommandManager & cCmdMgr = Application::Instance->commandManager(); + QString group = combo->itemData(index, Qt::UserRole).toString(); + auto cmds = group == QStringLiteral("All") ? cCmdMgr.getAllCommands() + : cCmdMgr.getGroupCommands(group.toLatin1()); + QTreeWidgetItem *currentItem = nullptr; + for (const Command *cmd : cmds) { + QTreeWidgetItem* item = new QTreeWidgetItem(commandTreeWidget); + if (dynamic_cast(cmd)) { + item->setText(1, QString::fromUtf8(cmd->getMenuText())); + item->setToolTip(1, QString::fromUtf8(cmd->getToolTipText())); + } else { + item->setText(1, qApp->translate(cmd->className(), cmd->getMenuText())); + item->setToolTip(1, qApp->translate(cmd->className(), cmd->getToolTipText())); + } + item->setData(1, Qt::UserRole, QByteArray(cmd->getName())); + item->setSizeHint(0, QSize(32, 32)); + if (auto pixmap = cmd->getPixmap()) + item->setIcon(0, BitmapFactory().iconFromTheme(pixmap)); + item->setText(2, cmd->getShortcut()); + if (auto accel = cmd->getAccel()) + item->setText(3, QKeySequence(QString::fromLatin1(accel)).toString()); + + if (current == cmd->getName()) + currentItem = item; + } + if (currentItem) + commandTreeWidget->setCurrentItem(currentItem); + commandTreeWidget->resizeColumnToContents(2); + commandTreeWidget->resizeColumnToContents(3); + }); + + QObject::connect(ShortcutManager::instance(), &ShortcutManager::shortcutChanged, + combo, [combo]() { combo->activated(combo->currentIndex()); }); + + populateCommandGroups(combo); + + return Application::Instance->commandManager().signalChanged.connect([combo](){ + if (combo) { + populateCommandGroups(combo); + combo->activated(combo->currentIndex()); + } + }); +} + +void DlgCustomKeyboardImp::initPriorityList(QTreeWidget *priorityList, + QAbstractButton *buttonUp, + QAbstractButton *buttonDown) +{ + QStringList labels; + labels << tr("Name") << tr("Title"); + priorityList->setHeaderLabels(labels); + priorityList->header()->hide(); + priorityList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + priorityList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + + auto updatePriorityList = [priorityList](bool up) { + auto item = priorityList->currentItem(); + if (!item) + return; + + int index = priorityList->indexOfTopLevelItem(item); + if (index < 0) + return; + if ((index == 0 && up) + || (index == priorityList->topLevelItemCount()-1 && !up)) + return; + + std::vector actions; + for (int i=0; itopLevelItemCount(); ++i) { + auto item = priorityList->topLevelItem(i); + actions.push_back(item->data(0, Qt::UserRole).toByteArray()); + } + + auto it = actions.begin() + index; + auto itNext = up ? it - 1 : it + 1; + std::swap(*it, *itNext); + ShortcutManager::instance()->setPriorities(actions); + }; + + QObject::connect(buttonUp, &QAbstractButton::clicked, [=](){updatePriorityList(true);}); + QObject::connect(buttonDown, &QAbstractButton::clicked, [=](){updatePriorityList(false);}); + QObject::connect(priorityList, &QTreeWidget::currentItemChanged, + [=](QTreeWidgetItem *item){ + buttonUp->setEnabled(item!=nullptr); + buttonDown->setEnabled(item!=nullptr); + } + ); +} + +boost::signals2::connection +DlgCustomKeyboardImp::initCommandWidgets(QTreeWidget *commandTreeWidget, + QComboBox *comboGroups, + QLineEdit *editCommand, + QTreeWidget *priorityList, + QAbstractButton *buttonUp, + QAbstractButton *buttonDown, + AccelLineEdit *editShortcut, + AccelLineEdit *currentShortcut) +{ + initCommandCompleter(editCommand, comboGroups, commandTreeWidget); + auto conn = initCommandList(commandTreeWidget, comboGroups); + + if (priorityList && buttonUp && buttonDown) { + initPriorityList(priorityList, buttonUp, buttonDown); + + auto timer = new QTimer(priorityList); + timer->setSingleShot(true); + if (currentShortcut) + QObject::connect(currentShortcut, &QLineEdit::textChanged, timer, [timer](){timer->start(200);}); + QObject::connect(editShortcut, &QLineEdit::textChanged, timer, [timer](){timer->start(200);}); + QObject::connect(ShortcutManager::instance(), &ShortcutManager::priorityChanged, timer, [timer](){timer->start(200);}); + QObject::connect(timer, &QTimer::timeout, [=](){ + populatePriorityList(priorityList, editShortcut, currentShortcut); + }); + } + + return conn; +} + +void DlgCustomKeyboardImp::populatePriorityList(QTreeWidget *priorityList, + AccelLineEdit *editor, + AccelLineEdit *curShortcut) +{ + QByteArray current; + if (auto currentItem = priorityList->currentItem()) + current = currentItem->data(0, Qt::UserRole).toByteArray(); + + priorityList->clear(); + QString sc; + if (!editor->isNone() && editor->text().size()) + sc = editor->text(); + else if (curShortcut && !curShortcut->isNone()) + sc = curShortcut->text(); + + auto actionList = ShortcutManager::instance()->getActionsByShortcut(sc); + QTreeWidgetItem *currentItem = nullptr; + for (size_t i=0; isetText(0, QString::fromUtf8(info.first)); + item->setText(1, info.second->text()); + item->setToolTip(0, info.second->toolTip()); + item->setIcon(0, info.second->icon()); + item->setData(0, Qt::UserRole, info.first); + if (current == info.first) + currentItem = item; + } + priorityList->resizeColumnToContents(0); + priorityList->resizeColumnToContents(1); + if (currentItem) + priorityList->setCurrentItem(currentItem); +} + +void DlgCustomKeyboardImp::populateCommandGroups(QComboBox *combo) +{ CommandManager & cCmdMgr = Application::Instance->commandManager(); std::map sCommands = cCmdMgr.getCommands(); @@ -97,27 +328,14 @@ DlgCustomKeyboardImp::DlgCustomKeyboardImp( QWidget* parent ) groupMap.push_back(std::make_pair(group, text)); } } + groupMap.push_back(std::make_pair(QLatin1String("All"), tr("All"))); - int index = 0; - for (GroupMap::iterator it = groupMap.begin(); it != groupMap.end(); ++it, ++index) { - ui->categoryBox->addItem(it->second); - ui->categoryBox->setItemData(index, QVariant(it->first), Qt::UserRole); + for (GroupMap::iterator it = groupMap.begin(); it != groupMap.end(); ++it) { + if (combo->findData(it->first) < 0) { + combo->addItem(it->second); + combo->setItemData(combo->count()-1, QVariant(it->first), Qt::UserRole); + } } - - QStringList labels; - labels << tr("Icon") << tr("Command"); - ui->commandTreeWidget->setHeaderLabels(labels); - ui->commandTreeWidget->header()->hide(); - ui->commandTreeWidget->setIconSize(QSize(32, 32)); - ui->commandTreeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); - - ui->assignedTreeWidget->setHeaderLabels(labels); - ui->assignedTreeWidget->header()->hide(); -} - -/** Destroys the object and frees any allocated resources */ -DlgCustomKeyboardImp::~DlgCustomKeyboardImp() -{ } void DlgCustomKeyboardImp::showEvent(QShowEvent* e) @@ -126,7 +344,7 @@ void DlgCustomKeyboardImp::showEvent(QShowEvent* e) // If we did this already in the constructor we wouldn't get the vertical scrollbar if needed. // The problem was noticed with Qt 4.1.4 but may arise with any later version. if (firstShow) { - on_categoryBox_activated(ui->categoryBox->currentIndex()); + ui->categoryBox->activated(ui->categoryBox->currentIndex()); firstShow = false; } } @@ -143,68 +361,29 @@ void DlgCustomKeyboardImp::on_commandTreeWidget_currentItemChanged(QTreeWidgetIt CommandManager & cCmdMgr = Application::Instance->commandManager(); Command* cmd = cCmdMgr.getCommandByName(name.constData()); if (cmd) { - if (cmd->getAction()) { - QKeySequence ks = cmd->getAction()->shortcut(); - QKeySequence ks2 = QString::fromLatin1(cmd->getAccel()); - QKeySequence ks3 = ui->editShortcut->text(); - - if (ks.isEmpty()) - ui->accelLineEditShortcut->setText( tr("none") ); - else - ui->accelLineEditShortcut->setText(ks.toString(QKeySequence::NativeText)); - - ui->buttonAssign->setEnabled(!ui->editShortcut->text().isEmpty() && (ks != ks3)); - ui->buttonReset->setEnabled((ks != ks2)); - } else { - QKeySequence ks = QString::fromLatin1(cmd->getAccel()); - if (ks.isEmpty()) - ui->accelLineEditShortcut->setText( tr("none") ); - else - ui->accelLineEditShortcut->setText(ks.toString(QKeySequence::NativeText)); - ui->buttonAssign->setEnabled(false); - ui->buttonReset->setEnabled(false); - } + QKeySequence ks = ShortcutManager::instance()->getShortcut( + cmd->getName(), cmd->getAccel()); + QKeySequence ks2 = QString::fromLatin1(cmd->getAccel()); + QKeySequence ks3 = ui->editShortcut->text(); + if (ks.isEmpty()) + ui->accelLineEditShortcut->setText( tr("none") ); + else + ui->accelLineEditShortcut->setText(ks.toString(QKeySequence::NativeText)); + + ui->buttonAssign->setEnabled(!ui->editShortcut->text().isEmpty() && (ks != ks3)); + ui->buttonReset->setEnabled((ks != ks2)); } ui->textLabelDescription->setText(item->toolTip(1)); } /** Shows all commands of this category */ -void DlgCustomKeyboardImp::on_categoryBox_activated(int index) +void DlgCustomKeyboardImp::on_categoryBox_activated(int) { - QVariant data = ui->categoryBox->itemData(index, Qt::UserRole); - QString group = data.toString(); - ui->commandTreeWidget->clear(); ui->buttonAssign->setEnabled(false); ui->buttonReset->setEnabled(false); ui->accelLineEditShortcut->clear(); ui->editShortcut->clear(); - - CommandManager & cCmdMgr = Application::Instance->commandManager(); - std::vector aCmds = cCmdMgr.getGroupCommands( group.toLatin1() ); - - if (group == QLatin1String("Macros")) { - for (std::vector::iterator it = aCmds.begin(); it != aCmds.end(); ++it) { - QTreeWidgetItem* item = new QTreeWidgetItem(ui->commandTreeWidget); - item->setText(1, QString::fromUtf8((*it)->getMenuText())); - item->setToolTip(1, QString::fromUtf8((*it)->getToolTipText())); - item->setData(1, Qt::UserRole, QByteArray((*it)->getName())); - item->setSizeHint(0, QSize(32, 32)); - if ((*it)->getPixmap()) - item->setIcon(0, BitmapFactory().iconFromTheme((*it)->getPixmap())); - } - } - else { - for (std::vector::iterator it = aCmds.begin(); it != aCmds.end(); ++it) { - QTreeWidgetItem* item = new QTreeWidgetItem(ui->commandTreeWidget); - item->setText(1, qApp->translate((*it)->className(), (*it)->getMenuText())); - item->setToolTip(1, qApp->translate((*it)->className(), (*it)->getToolTipText())); - item->setData(1, Qt::UserRole, QByteArray((*it)->getName())); - item->setSizeHint(0, QSize(32, 32)); - if ((*it)->getPixmap()) - item->setIcon(0, BitmapFactory().iconFromTheme((*it)->getPixmap())); - } - } } void DlgCustomKeyboardImp::setShortcutOfCurrentAction(const QString& accelText) @@ -216,44 +395,21 @@ void DlgCustomKeyboardImp::setShortcutOfCurrentAction(const QString& accelText) QVariant data = item->data(1, Qt::UserRole); QByteArray name = data.toByteArray(); // command name - CommandManager & cCmdMgr = Application::Instance->commandManager(); - Command* cmd = cCmdMgr.getCommandByName(name.constData()); - if (cmd && cmd->getAction()) { - QString nativeText; - Action* action = cmd->getAction(); - if (!accelText.isEmpty()) { - QKeySequence shortcut = accelText; - nativeText = shortcut.toString(QKeySequence::NativeText); - action->setShortcut(nativeText); - ui->accelLineEditShortcut->setText(accelText); - ui->editShortcut->clear(); - } - else { - action->setShortcut(QString()); - ui->accelLineEditShortcut->clear(); - ui->editShortcut->clear(); - } - - // update the tool tip (and status tip) - cmd->recreateTooltip(cmd->className(), action); - - // The shortcuts for macros are store in a different location, - // also override the command's shortcut directly - if (dynamic_cast(cmd)) { - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Macro/Macros"); - if (hGrp->HasGroup(cmd->getName())) { - hGrp = hGrp->GetGroup(cmd->getName()); - hGrp->SetASCII("Accel", ui->accelLineEditShortcut->text().toUtf8()); - cmd->setAccel(ui->accelLineEditShortcut->text().toUtf8()); - } - } - else { - ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Shortcut"); - hGrp->SetASCII(name.constData(), ui->accelLineEditShortcut->text().toUtf8()); - } - ui->buttonAssign->setEnabled(false); - ui->buttonReset->setEnabled(true); + QString nativeText; + if (!accelText.isEmpty()) { + QKeySequence shortcut = accelText; + nativeText = shortcut.toString(QKeySequence::NativeText); + ui->accelLineEditShortcut->setText(accelText); + ui->editShortcut->clear(); + } + else { + ui->accelLineEditShortcut->clear(); + ui->editShortcut->clear(); } + ShortcutManager::instance()->setShortcut(name, nativeText.toLatin1()); + + ui->buttonAssign->setEnabled(false); + ui->buttonReset->setEnabled(true); } /** Assigns a new accelerator to the selected command. */ @@ -277,200 +433,54 @@ void DlgCustomKeyboardImp::on_buttonReset_clicked() QVariant data = item->data(1, Qt::UserRole); QByteArray name = data.toByteArray(); // command name + ShortcutManager::instance()->reset(name); - CommandManager & cCmdMgr = Application::Instance->commandManager(); - Command* cmd = cCmdMgr.getCommandByName(name.constData()); - if (cmd && cmd->getAction()) { - cmd->getAction()->setShortcut(QString::fromLatin1(cmd->getAccel())); - QString txt = cmd->getAction()->shortcut().toString(QKeySequence::NativeText); - ui->accelLineEditShortcut->setText((txt.isEmpty() ? tr("none") : txt)); - ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Shortcut"); - hGrp->RemoveASCII(name.constData()); - - // update the tool tip (and status tip) - cmd->recreateTooltip(cmd->className(), cmd->getAction()); - } - + QString txt = ShortcutManager::instance()->getShortcut(name); + ui->accelLineEditShortcut->setText((txt.isEmpty() ? tr("none") : txt)); ui->buttonReset->setEnabled( false ); } /** Resets the accelerator of all commands to the default. */ void DlgCustomKeyboardImp::on_buttonResetAll_clicked() { - CommandManager & cCmdMgr = Application::Instance->commandManager(); - std::vector cmds = cCmdMgr.getAllCommands(); - for (std::vector::iterator it = cmds.begin(); it != cmds.end(); ++it) { - if ((*it)->getAction()) { - (*it)->getAction()->setShortcut(QKeySequence(QString::fromLatin1((*it)->getAccel())) - .toString(QKeySequence::NativeText)); - - - // update the tool tip (and status tip) - (*it)->recreateTooltip((*it)->className(), (*it)->getAction()); - } - } - - WindowParameter::getDefaultParameter()->RemoveGrp("Shortcut"); + ShortcutManager::instance()->resetAll(); ui->buttonReset->setEnabled(false); } /** Checks for an already occupied shortcut. */ -void DlgCustomKeyboardImp::on_editShortcut_textChanged(const QString& sc) +void DlgCustomKeyboardImp::on_editShortcut_textChanged(const QString& ) { - ui->assignedTreeWidget->clear(); QTreeWidgetItem* item = ui->commandTreeWidget->currentItem(); - if (!item) - return; - QVariant data = item->data(1, Qt::UserRole); - QByteArray name = data.toByteArray(); // command name - - CommandManager & cCmdMgr = Application::Instance->commandManager(); - Command* cmd = cCmdMgr.getCommandByName(name.constData()); - if (cmd && !cmd->getAction()) { - Base::Console().Warning("Command %s not in use yet\n", cmd->getName()); - ui->buttonAssign->setEnabled(false); // command not in use - return; - } - - ui->buttonAssign->setEnabled(true); - QKeySequence ks(sc); - if (!ks.isEmpty() && !ui->editShortcut->isNone()) { - int countAmbiguous = 0; - QString ambiguousCommand; - QString ambiguousMenu; - std::vector ambiguousCommands; + if (item) { + QVariant data = item->data(1, Qt::UserRole); + QByteArray name = data.toByteArray(); // command name CommandManager & cCmdMgr = Application::Instance->commandManager(); - std::vector cmds = cCmdMgr.getAllCommands(); - for (std::vector::iterator it = cmds.begin(); it != cmds.end(); ++it) { - if ((*it)->getAction()) { - // A command may have several QAction's. So, check all of them if one of them matches (See bug #0002160) - QList acts = (*it)->getAction()->findChildren(); - for (QList::iterator jt = acts.begin(); jt != acts.end(); ++jt) { - if ((*jt)->shortcut() == ks) { - ++countAmbiguous; - ambiguousCommands.push_back(*it); - ambiguousCommand = QString::fromLatin1((*it)->getName()); // store the last one - ambiguousMenu = qApp->translate((*it)->className(), (*it)->getMenuText()); - - QTreeWidgetItem* item = new QTreeWidgetItem(ui->assignedTreeWidget); - item->setText(1, qApp->translate((*it)->className(), (*it)->getMenuText())); - item->setToolTip(1, qApp->translate((*it)->className(), (*it)->getToolTipText())); - item->setData(1, Qt::UserRole, QByteArray((*it)->getName())); - item->setSizeHint(0, QSize(32, 32)); - if ((*it)->getPixmap()) - item->setIcon(0, BitmapFactory().iconFromTheme((*it)->getPixmap())); - break; - } - } - } - } - - if (countAmbiguous > 0) - ui->assignedTreeWidget->resizeColumnToContents(0); + Command* cmd = cCmdMgr.getCommandByName(name.constData()); - if (countAmbiguous > 1) { - QMessageBox::warning(this, tr("Multiple defined shortcut"), - tr("The shortcut '%1' is defined more than once. This could result in unexpected behaviour.").arg(sc) ); - ui->editShortcut->setFocus(); - ui->buttonAssign->setEnabled(false); - } - else if (countAmbiguous == 1 && ambiguousCommand != QLatin1String(name)) { - QMessageBox box(this); - box.setIcon(QMessageBox::Warning); - box.setWindowTitle(tr("Already defined shortcut")); - box.setText(tr("The shortcut '%1' is already assigned to '%2'.").arg(sc, ambiguousMenu)); - box.setInformativeText(tr("Do you want to override it?")); - box.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - box.setDefaultButton(QMessageBox::No); - box.setEscapeButton(QMessageBox::No); - int ret = box.exec(); - if (ret == QMessageBox::Yes) { - for (auto* cmd : ambiguousCommands) { - Action* action = cmd->getAction(); - action->setShortcut(QString()); - - ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Shortcut"); - hGrp->RemoveASCII(cmd->getName()); - } - } - else { - ui->editShortcut->setFocus(); - ui->buttonAssign->setEnabled(false); - } - } + if (!ui->editShortcut->isNone()) + ui->buttonAssign->setEnabled(true); else { - if (cmd && cmd->getAction() && cmd->getAction()->shortcut() == ks) - ui->buttonAssign->setEnabled(false); + if (cmd && cmd->getAction() && cmd->getAction()->shortcut().isEmpty()) + ui->buttonAssign->setEnabled(false); // both key sequences are empty } } - else { - if (cmd && cmd->getAction() && cmd->getAction()->shortcut().isEmpty()) - ui->buttonAssign->setEnabled(false); // both key sequences are empty - } } -void DlgCustomKeyboardImp::onAddMacroAction(const QByteArray& macro) +void DlgCustomKeyboardImp::onAddMacroAction(const QByteArray&) { - QVariant data = ui->categoryBox->itemData(ui->categoryBox->currentIndex(), Qt::UserRole); - QString group = data.toString(); - if (group == QLatin1String("Macros")) - { - CommandManager & cCmdMgr = Application::Instance->commandManager(); - Command* pCmd = cCmdMgr.getCommandByName(macro); - - QTreeWidgetItem* item = new QTreeWidgetItem(ui->commandTreeWidget); - item->setText(1, QString::fromUtf8(pCmd->getMenuText())); - item->setToolTip(1, QString::fromUtf8(pCmd->getToolTipText())); - item->setData(1, Qt::UserRole, macro); - item->setSizeHint(0, QSize(32, 32)); - if (pCmd->getPixmap()) - item->setIcon(0, BitmapFactory().iconFromTheme(pCmd->getPixmap())); - } } -void DlgCustomKeyboardImp::onRemoveMacroAction(const QByteArray& macro) +void DlgCustomKeyboardImp::onRemoveMacroAction(const QByteArray&) { - QVariant data = ui->categoryBox->itemData(ui->categoryBox->currentIndex(), Qt::UserRole); - QString group = data.toString(); - if (group == QLatin1String("Macros")) - { - for (int i=0; icommandTreeWidget->topLevelItemCount(); i++) { - QTreeWidgetItem* item = ui->commandTreeWidget->topLevelItem(i); - QByteArray command = item->data(1, Qt::UserRole).toByteArray(); - if (command == macro) { - ui->commandTreeWidget->takeTopLevelItem(i); - delete item; - break; - } - } - } } -void DlgCustomKeyboardImp::onModifyMacroAction(const QByteArray& macro) +void DlgCustomKeyboardImp::onModifyMacroAction(const QByteArray&) { QVariant data = ui->categoryBox->itemData(ui->categoryBox->currentIndex(), Qt::UserRole); QString group = data.toString(); if (group == QLatin1String("Macros")) - { - CommandManager & cCmdMgr = Application::Instance->commandManager(); - Command* pCmd = cCmdMgr.getCommandByName(macro); - for (int i=0; icommandTreeWidget->topLevelItemCount(); i++) { - QTreeWidgetItem* item = ui->commandTreeWidget->topLevelItem(i); - QByteArray command = item->data(1, Qt::UserRole).toByteArray(); - if (command == macro) { - item->setText(1, QString::fromUtf8(pCmd->getMenuText())); - item->setToolTip(1, QString::fromUtf8(pCmd->getToolTipText())); - item->setData(1, Qt::UserRole, macro); - item->setSizeHint(0, QSize(32, 32)); - if (pCmd->getPixmap()) - item->setIcon(0, BitmapFactory().iconFromTheme(pCmd->getPixmap())); - if (item->isSelected()) - ui->textLabelDescription->setText(item->toolTip(1)); - break; - } - } - } + ui->categoryBox->activated(ui->categoryBox->currentIndex()); } void DlgCustomKeyboardImp::changeEvent(QEvent *e) @@ -488,8 +498,10 @@ void DlgCustomKeyboardImp::changeEvent(QEvent *e) ui->categoryBox->setItemText(i, text); } } - on_categoryBox_activated(ui->categoryBox->currentIndex()); + ui->categoryBox->activated(ui->categoryBox->currentIndex()); } + else if (e->type() == QEvent::StyleChange) + ui->categoryBox->activated(ui->categoryBox->currentIndex()); QWidget::changeEvent(e); } diff --git a/src/Gui/DlgKeyboardImp.h b/src/Gui/DlgKeyboardImp.h index 682e95458fe0..f4b94ec0fa04 100644 --- a/src/Gui/DlgKeyboardImp.h +++ b/src/Gui/DlgKeyboardImp.h @@ -24,12 +24,22 @@ #ifndef GUI_DIALOG_DLGKEYBOARD_IMP_H #define GUI_DIALOG_DLGKEYBOARD_IMP_H -#include "PropertyPage.h" +#include #include +#include +#include +#include "PropertyPage.h" +class QTreeWidget; class QTreeWidgetItem; +class QComboBox; +class QLineEdit; +class QAbstractButton; namespace Gui { + +class AccelLineEdit; + namespace Dialog { class Ui_DlgCustomKeyboard; @@ -48,9 +58,44 @@ class DlgCustomKeyboardImp : public CustomizeActionPage DlgCustomKeyboardImp( QWidget* parent = 0 ); ~DlgCustomKeyboardImp(); + /** Public helper function for handling command widgets + * + * @param commandTreeWidget: a tree widget listing commands + * @param comboGroups: a combo box widget for choosing categories of commands + * @param editCommand: a line edit for searching command with auto complete + * @param priroityList: a tree widget listing commands with the same shortcut in order of priority + * @param buttonUp: a button widget to increase command priority + * @param buttonDown: a button widget to decrease command priority + * @param editShortcut: an accelerator editor for setting user defined shortcut + * @param currentShortcut: optional accelerator editor showing the current shortcut of a command + * + * @return Return a boost signal connection for monitoring command changes. + * Most disconnect the signal before widgets gets destroyed. + */ + static boost::signals2::connection + initCommandWidgets(QTreeWidget *commandTreeWidget, + QComboBox *comboGroups, + QLineEdit *editCommand, + QTreeWidget *priorityList = nullptr, + QAbstractButton *buttonUp = nullptr, + QAbstractButton *buttonDown = nullptr, + AccelLineEdit *editShortcut = nullptr, + AccelLineEdit *currentShortcut = nullptr); + protected: void showEvent(QShowEvent* e); + /** @name Internal helper function for handling command list widgets + */ + //@{ + static void initCommandCompleter(QLineEdit *, QComboBox *combo, QTreeWidget *treeWidget); + static boost::signals2::connection initCommandList(QTreeWidget *, QComboBox *combo); + static void initPriorityList(QTreeWidget *, QAbstractButton *buttonUp, QAbstractButton *buttonDown); + static void populateCommandGroups(QComboBox *); + static void populatePriorityList(QTreeWidget *priorityList, + AccelLineEdit *editor, + AccelLineEdit *current); + //@} protected Q_SLOTS: void on_categoryBox_activated(int index); void on_commandTreeWidget_currentItemChanged(QTreeWidgetItem*); @@ -70,6 +115,7 @@ protected Q_SLOTS: private: std::unique_ptr ui; bool firstShow; + boost::signals2::scoped_connection conn; }; } // namespace Dialog diff --git a/src/Gui/DlgToolbarsImp.cpp b/src/Gui/DlgToolbarsImp.cpp index bf7f43a59aec..112e276aa526 100644 --- a/src/Gui/DlgToolbarsImp.cpp +++ b/src/Gui/DlgToolbarsImp.cpp @@ -32,6 +32,7 @@ #endif #include "DlgToolbarsImp.h" +#include "DlgKeyboardImp.h" #include "ui_DlgToolbars.h" #include "Application.h" #include "BitmapFactory.h" @@ -44,20 +45,6 @@ using namespace Gui::Dialog; -namespace Gui { namespace Dialog { -typedef std::vector< std::pair > GroupMap; - -struct GroupMap_find { - const QLatin1String& item; - GroupMap_find(const QLatin1String& item) : item(item) {} - bool operator () (const std::pair& elem) const - { - return elem.first == item; - } -}; -} -} - /* TRANSLATOR Gui::Dialog::DlgCustomToolbars */ /** @@ -78,43 +65,15 @@ DlgCustomToolbars::DlgCustomToolbars(DlgCustomToolbars::Type t, QWidget* parent) ui->moveActionDownButton->setIcon(BitmapFactory().iconFromTheme("button_down")); ui->moveActionUpButton->setIcon(BitmapFactory().iconFromTheme("button_up")); - CommandManager & cCmdMgr = Application::Instance->commandManager(); - std::map sCommands = cCmdMgr.getCommands(); - - GroupMap groupMap; - groupMap.push_back(std::make_pair(QLatin1String("File"), QString())); - groupMap.push_back(std::make_pair(QLatin1String("Edit"), QString())); - groupMap.push_back(std::make_pair(QLatin1String("View"), QString())); - groupMap.push_back(std::make_pair(QLatin1String("Standard-View"), QString())); - groupMap.push_back(std::make_pair(QLatin1String("Tools"), QString())); - groupMap.push_back(std::make_pair(QLatin1String("Window"), QString())); - groupMap.push_back(std::make_pair(QLatin1String("Help"), QString())); - groupMap.push_back(std::make_pair(QLatin1String("Macros"), qApp->translate("Gui::MacroCommand", "Macros"))); - - for (std::map::iterator it = sCommands.begin(); it != sCommands.end(); ++it) { - QLatin1String group(it->second->getGroupName()); - QString text = it->second->translatedGroupName(); - GroupMap::iterator jt; - jt = std::find_if(groupMap.begin(), groupMap.end(), GroupMap_find(group)); - if (jt != groupMap.end()) { - if (jt->second.isEmpty()) - jt->second = text; - } - else { - groupMap.push_back(std::make_pair(group, text)); - } - } + conn = DlgCustomKeyboardImp::initCommandWidgets(ui->commandTreeWidget, + ui->categoryBox, + ui->editCommand); - int index = 0; - for (GroupMap::iterator it = groupMap.begin(); it != groupMap.end(); ++it, ++index) { - ui->categoryBox->addItem(it->second); - ui->categoryBox->setItemData(index, QVariant(it->first), Qt::UserRole); - } // fills the combo box with all available workbenches QStringList workbenches = Application::Instance->workbenches(); workbenches.sort(); - index = 1; + int index = 1; ui->workbenchBox->addItem(QApplication::windowIcon(), tr("Global")); ui->workbenchBox->setItemData(0, QVariant(QString::fromLatin1("Global")), Qt::UserRole); for (QStringList::Iterator it = workbenches.begin(); it != workbenches.end(); ++it) { @@ -131,13 +90,7 @@ DlgCustomToolbars::DlgCustomToolbars(DlgCustomToolbars::Type t, QWidget* parent) } QStringList labels; - labels << tr("Icon") << tr("Command"); - ui->commandTreeWidget->setHeaderLabels(labels); - ui->commandTreeWidget->header()->hide(); - ui->commandTreeWidget->setIconSize(QSize(32, 32)); - ui->commandTreeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); - - labels.clear(); labels << tr("Command"); + labels << tr("Command"); ui->toolbarTreeWidget->setHeaderLabels(labels); ui->toolbarTreeWidget->header()->hide(); @@ -193,43 +146,13 @@ void DlgCustomToolbars::hideEvent(QHideEvent * event) CustomizeActionPage::hideEvent(event); } -void DlgCustomToolbars::on_categoryBox_activated(int index) +void DlgCustomToolbars::on_categoryBox_activated(int) { - QVariant data = ui->categoryBox->itemData(index, Qt::UserRole); - QString group = data.toString(); - ui->commandTreeWidget->clear(); - - CommandManager & cCmdMgr = Application::Instance->commandManager(); - std::vector aCmds = cCmdMgr.getGroupCommands(group.toLatin1()); - - // Create a separator item - QTreeWidgetItem* sepitem = new QTreeWidgetItem(ui->commandTreeWidget); + QTreeWidgetItem* sepitem = new QTreeWidgetItem; sepitem->setText(1, tr("")); sepitem->setData(1, Qt::UserRole, QByteArray("Separator")); sepitem->setSizeHint(0, QSize(32, 32)); - - if (group == QLatin1String("Macros")) { - for (std::vector::iterator it = aCmds.begin(); it != aCmds.end(); ++it) { - QTreeWidgetItem* item = new QTreeWidgetItem(ui->commandTreeWidget); - item->setText(1, QString::fromUtf8((*it)->getMenuText())); - item->setToolTip(1, QString::fromUtf8((*it)->getToolTipText())); - item->setData(1, Qt::UserRole, QByteArray((*it)->getName())); - item->setSizeHint(0, QSize(32, 32)); - if ((*it)->getPixmap()) - item->setIcon(0, BitmapFactory().iconFromTheme((*it)->getPixmap())); - } - } - else { - for (std::vector::iterator it = aCmds.begin(); it != aCmds.end(); ++it) { - QTreeWidgetItem* item = new QTreeWidgetItem(ui->commandTreeWidget); - item->setText(1, qApp->translate((*it)->className(), (*it)->getMenuText())); - item->setToolTip(1, qApp->translate((*it)->className(), (*it)->getToolTipText())); - item->setData(1, Qt::UserRole, QByteArray((*it)->getName())); - item->setSizeHint(0, QSize(32, 32)); - if ((*it)->getPixmap()) - item->setIcon(0, BitmapFactory().iconFromTheme((*it)->getPixmap())); - } - } + ui->commandTreeWidget->insertTopLevelItem(0, sepitem); } void DlgCustomToolbars::on_workbenchBox_activated(int index) @@ -556,41 +479,12 @@ void DlgCustomToolbars::on_renameButton_clicked() } } -void DlgCustomToolbars::onAddMacroAction(const QByteArray& macro) +void DlgCustomToolbars::onAddMacroAction(const QByteArray&) { - QVariant data = ui->categoryBox->itemData(ui->categoryBox->currentIndex(), Qt::UserRole); - QString group = data.toString(); - if (group == QLatin1String("Macros")) - { - CommandManager & cCmdMgr = Application::Instance->commandManager(); - Command* pCmd = cCmdMgr.getCommandByName(macro); - - QTreeWidgetItem* item = new QTreeWidgetItem(ui->commandTreeWidget); - item->setText(1, QString::fromUtf8(pCmd->getMenuText())); - item->setToolTip(1, QString::fromUtf8(pCmd->getToolTipText())); - item->setData(1, Qt::UserRole, macro); - item->setSizeHint(0, QSize(32, 32)); - if (pCmd->getPixmap()) - item->setIcon(0, BitmapFactory().iconFromTheme(pCmd->getPixmap())); - } } -void DlgCustomToolbars::onRemoveMacroAction(const QByteArray& macro) +void DlgCustomToolbars::onRemoveMacroAction(const QByteArray&) { - QVariant data = ui->categoryBox->itemData(ui->categoryBox->currentIndex(), Qt::UserRole); - QString group = data.toString(); - if (group == QLatin1String("Macros")) - { - for (int i=0; icommandTreeWidget->topLevelItemCount(); i++) { - QTreeWidgetItem* item = ui->commandTreeWidget->topLevelItem(i); - QByteArray command = item->data(1, Qt::UserRole).toByteArray(); - if (command == macro) { - ui->commandTreeWidget->takeTopLevelItem(i); - delete item; - break; - } - } - } } void DlgCustomToolbars::onModifyMacroAction(const QByteArray& macro) @@ -601,20 +495,6 @@ void DlgCustomToolbars::onModifyMacroAction(const QByteArray& macro) { CommandManager & cCmdMgr = Application::Instance->commandManager(); Command* pCmd = cCmdMgr.getCommandByName(macro); - // the left side - for (int i=0; icommandTreeWidget->topLevelItemCount(); i++) { - QTreeWidgetItem* item = ui->commandTreeWidget->topLevelItem(i); - QByteArray command = item->data(1, Qt::UserRole).toByteArray(); - if (command == macro) { - item->setText(1, QString::fromUtf8(pCmd->getMenuText())); - item->setToolTip(1, QString::fromUtf8(pCmd->getToolTipText())); - item->setData(1, Qt::UserRole, macro); - item->setSizeHint(0, QSize(32, 32)); - if (pCmd->getPixmap()) - item->setIcon(0, BitmapFactory().iconFromTheme(pCmd->getPixmap())); - break; - } - } // the right side for (int i=0; itoolbarTreeWidget->topLevelItemCount(); i++) { QTreeWidgetItem* toplevel = ui->toolbarTreeWidget->topLevelItem(i); @@ -628,6 +508,7 @@ void DlgCustomToolbars::onModifyMacroAction(const QByteArray& macro) } } } + on_categoryBox_activated(ui->categoryBox->currentIndex()); } } @@ -648,6 +529,8 @@ void DlgCustomToolbars::changeEvent(QEvent *e) } on_categoryBox_activated(ui->categoryBox->currentIndex()); } + else if (e->type() == QEvent::StyleChange) + ui->categoryBox->activated(ui->categoryBox->currentIndex()); QWidget::changeEvent(e); } diff --git a/src/Gui/DlgToolbarsImp.h b/src/Gui/DlgToolbarsImp.h index 12a7b7ea80a1..48da979e6540 100644 --- a/src/Gui/DlgToolbarsImp.h +++ b/src/Gui/DlgToolbarsImp.h @@ -24,6 +24,7 @@ #ifndef GUI_DIALOG_DLGTOOLBARS_IMP_H #define GUI_DIALOG_DLGTOOLBARS_IMP_H +#include #include "PropertyPage.h" #include @@ -82,6 +83,7 @@ protected Q_SLOTS: std::unique_ptr ui; private: Type type; + boost::signals2::scoped_connection conn; }; /** This class implements the creation of user defined toolbars. diff --git a/src/Gui/ShortcutManager.cpp b/src/Gui/ShortcutManager.cpp new file mode 100644 index 000000000000..32f3ca731fbb --- /dev/null +++ b/src/Gui/ShortcutManager.cpp @@ -0,0 +1,455 @@ +/**************************************************************************** + * Copyright (c) 2022 Zheng Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +#endif + +#include + +#include +#include +#include "ShortcutManager.h" +#include "Command.h" +#include "Window.h" +#include "Action.h" + +using namespace Gui; + +ShortcutManager::ShortcutManager() +{ + hShortcuts = WindowParameter::getDefaultParameter()->GetGroup("Shortcut"); + hShortcuts->Attach(this); + hPriorities = hShortcuts->GetGroup("Priorities"); + hPriorities->Attach(this); + hSetting = hShortcuts->GetGroup("Settings"); + hSetting->Attach(this); + timeout = hSetting->GetInt("ShortcutTimeout", 300); + timer.setSingleShot(true); + + QObject::connect(&timer, &QTimer::timeout, [this](){onTimer();}); + + topPriority = 0; + for (const auto &v : hPriorities->GetIntMap()) { + priorities[v.first] = v.second; + if (topPriority < v.second) + topPriority = v.second; + } + if (topPriority == 0) + topPriority = 100; + + QApplication::instance()->installEventFilter(this); +} + +ShortcutManager::~ShortcutManager() +{ + hShortcuts->Detach(this); + hSetting->Detach(this); + hPriorities->Detach(this); +} + +static ShortcutManager *Instance; +ShortcutManager *ShortcutManager::instance() +{ + if (!Instance) + Instance = new ShortcutManager; + return Instance; +} + +void ShortcutManager::destroy() +{ + delete Instance; + Instance = nullptr; +} + +void ShortcutManager::OnChange(Base::Subject &src, const char *reason) +{ + if (hSetting == &src) { + if (boost::equals(reason, "ShortcutTimeout")) + timeout = hSetting->GetInt("ShortcutTimeout"); + return; + } + + if (busy) + return; + + if (hPriorities == &src) { + int p = hPriorities->GetInt(reason, 0); + if (p == 0) + priorities.erase(reason); + else + priorities[reason] = p; + if (topPriority < p) + topPriority = p; + priorityChanged(reason, p); + return; + } + + Base::StateLocker lock(busy); + auto cmd = Application::Instance->commandManager().getCommandByName(reason); + if (cmd) { + auto accel = cmd->getAccel(); + if (!accel) accel = ""; + QKeySequence oldShortcut = cmd->getShortcut(); + QKeySequence newShortcut = getShortcut(reason, accel); + if (oldShortcut != newShortcut) { + cmd->setShortcut(newShortcut.toString()); + shortcutChanged(reason, oldShortcut); + } + } +} + +void ShortcutManager::reset(const char *cmd) +{ + if (cmd && cmd[0]) { + QKeySequence oldShortcut = getShortcut(cmd); + hShortcuts->RemoveASCII(cmd); + if (oldShortcut != getShortcut(cmd)) + shortcutChanged(cmd, oldShortcut); + + int oldPriority = getPriority(cmd); + hPriorities->RemoveInt(cmd); + if (oldPriority != getPriority(cmd)) + priorityChanged(cmd, oldPriority); + } +} + +void ShortcutManager::resetAll() +{ + { + Base::StateLocker lock(busy); + hShortcuts->Clear(); + hPriorities->Clear(); + for (auto cmd : Application::Instance->commandManager().getAllCommands()) { + if (cmd->getAction()) { + auto accel = cmd->getAccel(); + if (!accel) accel = ""; + cmd->setShortcut(getShortcut(nullptr, accel)); + } + } + } + shortcutChanged("", QKeySequence()); + priorityChanged("", 0); +} + +QString ShortcutManager::getShortcut(const char *cmdName, const char *accel) +{ + if (!accel) { + if (auto cmd = Application::Instance->commandManager().getCommandByName(cmdName)) { + accel = cmd->getAccel(); + if (!accel) + accel = ""; + } + } + QString shortcut; + if (cmdName) + shortcut = QString::fromLatin1(hShortcuts->GetASCII(cmdName, accel).c_str()); + else + shortcut = QString::fromLatin1(accel); + return QKeySequence(shortcut).toString(QKeySequence::NativeText); +} + +void ShortcutManager::setShortcut(const char *cmdName, const char *accel) +{ + if (cmdName && cmdName[0]) { + setTopPriority(cmdName); + if (!accel) + accel = ""; + if (auto cmd = Application::Instance->commandManager().getCommandByName(cmdName)) { + auto defaultAccel = cmd->getAccel(); + if (!defaultAccel) + defaultAccel = ""; + if (QKeySequence(QString::fromLatin1(accel)) == QKeySequence(QString::fromLatin1(defaultAccel))) { + hShortcuts->RemoveASCII(cmdName); + return; + } + } + hShortcuts->SetASCII(cmdName, accel); + } +} + +bool ShortcutManager::checkShortcut(QObject *o, const QKeySequence &key) +{ + auto focus = QApplication::focusWidget(); + if (!focus) + return false; + auto action = qobject_cast(o); + if (!action) + return false; + + const auto &index = actionMap.get<1>(); + auto iter = index.lower_bound(ActionKey(key)); + if (iter == index.end()) + return false; + + auto it = iter; + // skip to the first not exact matched key + for (; it != index.end() && key == it->key.shortcut; ++it); + + // check for potential partial match, i.e. longer key sequences + bool flush = true; + for (; it != index.end(); ++it) { + if (key.matches(it->key.shortcut) == QKeySequence::NoMatch) + break; + if (it->action && it->action->isEnabled()) { + flush = false; + break; + } + } + + int count = 0; + for (it = iter; it != index.end() && key == it->key.shortcut; ++it) { + if (it->action && it->action->isEnabled()) { + if (!flush) { + // temporary disable the action so that we can try potential + // match with further keystrokes. + it->action->setEnabled(false); + } + pendingActions.emplace_back(it->action, key.count(), getPriority(it->key.name)); + ++count; + } + } + if (!count) { + // action not found in the map, shouldn't happen! + pendingActions.emplace_back(action, key.count(), 0); + } + if (flush) { + // We'll flush now because there is no poential match with further + // keystrokes, so no need to wait for timer. + lastFocus = nullptr; + onTimer(); + return true; + } + + lastFocus = focus; + pendingSequence = key; + + // Qt's shortcut state machine favors shortest match (which is ridiculous, + // unless I'm mistaken?). We'll do longest match. We've disabled all + // shortcuts that can match the current key sequence. Now reply the sequence + // and wait for the next keystroke. + for (int i=0; itype()) { + case QEvent::KeyPress: + lastFocus = nullptr; + break; + case QEvent::Shortcut: + if (timeout > 0) { + auto sev = static_cast(ev); + if (checkShortcut(o, sev->key())) { + // shortcut event handled here, so filter out the event + return true; + } else { + // Not handled. Clear any existing pending actions. + timer.stop(); + for (const auto &info : pendingActions) { + if (info.action) + info.action->setEnabled(true); + } + pendingActions.clear(); + lastFocus = nullptr; + } + } + break; + case QEvent::ActionChanged: + if (auto action = qobject_cast(o)) { + auto &index = actionMap.get<0>(); + auto it = index.find(reinterpret_cast(action)); + if (action->shortcut().isEmpty()) { + if (it != index.end()) { + QKeySequence oldShortcut = it->key.shortcut; + index.erase(it); + actionShortcutChanged(action, oldShortcut); + } + break; + } + + QByteArray name; + if (auto fcAction = qobject_cast(action->parent())) { + if (fcAction->command() && fcAction->command()->getName()) + name = fcAction->command()->getName(); + } + if (name.isEmpty()) { + name = action->objectName().size() ? + action->objectName().toUtf8() : action->text().toUtf8(); + if (name.isEmpty()) + name = "~"; + else + name = QByteArray("~ ") + name; + } + if (it != index.end()) { + if (it->key.shortcut == action->shortcut() && it->key.name == name) + break; + QKeySequence oldShortcut = it->key.shortcut; + index.replace(it, {action, name}); + actionShortcutChanged(action, oldShortcut); + } else { + index.insert({action, name}); + actionShortcutChanged(action, QKeySequence()); + } + } + break; + default: + break; + } + return false; +} + +std::vector> ShortcutManager::getActionsByShortcut(const QKeySequence &shortcut) +{ + const auto &index = actionMap.get<1>(); + std::vector> res; + std::multimap> map; + for (auto it = index.lower_bound(ActionKey(shortcut)); it != index.end(); ++it) { + if (it->key.shortcut != shortcut) + break; + if (it->key.name != "~" && it->action) + map.emplace(getPriority(it->key.name), &(*it)); + } + for (const auto &v : map) + res.emplace_back(v.second->key.name, v.second->action); + return res; +} + +void ShortcutManager::setPriorities(const std::vector &actions) +{ + if (actions.empty()) + return; + // Keep the same top priority of the given action, and adjust the rest. Can + // go negative if necessary + int current = 0; + for (const auto &name : actions) + current = std::max(current, getPriority(name)); + if (current == 0) + current = (int)actions.size(); + setPriority(actions.front(), current); + ++current; + for (const auto &name : actions) { + int p = getPriority(name); + if (p <= 0 || p >= current) { + if (--current == 0) + --current; + setPriority(name, current); + } else + current = p; + } +} + +int ShortcutManager::getPriority(const char *cmdName) +{ + if (!cmdName) + return 0; + auto it = priorities.find(cmdName); + if (it == priorities.end()) + return 0; + return it->second; +} + +void ShortcutManager::setPriority(const char *cmdName, int p) +{ + if (p == 0) + hPriorities->RemoveInt(cmdName); + else + hPriorities->SetInt(cmdName, p); +} + +void ShortcutManager::setTopPriority(const char *cmdName) +{ + ++topPriority; + hPriorities->SetInt(cmdName, topPriority); +} + +void ShortcutManager::onTimer() +{ + QAction *found = nullptr; + int priority = -INT_MAX; + int seq_length = 0; + for (const auto &info : pendingActions) { + if (info.action) { + info.action->setEnabled(true); + if (info.seq_length > seq_length + || (info.seq_length == seq_length + && info.priority > priority)) + { + priority = info.priority; + seq_length = info.seq_length; + found = info.action; + } + } + } + if (found) + found->activate(QAction::Trigger); + pendingActions.clear(); + + if (lastFocus && lastFocus == QApplication::focusWidget()) { + // We are here because we have withheld some previous triggered action. + // We then disabled the action, and faked the same key strokes in order + // to wait for more for potential match of longer key sequence. We use + // a timer to end the wait and triggered the pending action. + // + // However, Qt's internal shorcutmap state machine is still armed with + // our fake key strokes. So we try to fake some more obscure symbol key + // stroke below, hoping to reset Qt's state machine. + + const auto &index = actionMap.get<1>(); + static const std::string symbols = "~!@#$%^&*()_+"; + QString shortcut = pendingSequence.toString() + QStringLiteral(", Ctrl+"); + for (int s : symbols) { + QKeySequence k(shortcut + QLatin1Char(s)); + auto it = index.lower_bound(ActionKey(k)); + if (it->key.shortcut != k) { + QKeyEvent *kev = new QKeyEvent(QEvent::KeyPress, s, Qt::ControlModifier, 0, 0, 0); + QApplication::postEvent(lastFocus, kev); + kev = new QKeyEvent(QEvent::KeyRelease, s, Qt::ControlModifier, 0, 0, 0); + QApplication::postEvent(lastFocus, kev); + break; + } + } + } + timer.stop(); +} + +#include "moc_ShortcutManager.cpp" diff --git a/src/Gui/ShortcutManager.h b/src/Gui/ShortcutManager.h new file mode 100644 index 000000000000..a928b811b106 --- /dev/null +++ b/src/Gui/ShortcutManager.h @@ -0,0 +1,171 @@ +/**************************************************************************** + * Copyright (c) 2022 Zheng Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#ifndef GUI_SHORTCUT_MANAGER_H +#define GUI_SHORTCUT_MANAGER_H + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace Gui { + +class Command; +namespace bmi = boost::multi_index; + +class GuiExport ShortcutManager : public QObject, public ParameterGrp::ObserverType +{ + Q_OBJECT + +public: + ShortcutManager(); + ~ShortcutManager(); + + static ShortcutManager *instance(); + static void destroy(); + + void OnChange(Base::Subject &, const char *reason) override; + + /// Clear all user defined shortcut + void resetAll(); + /// Clear the user defined shortcut of a given command + void reset(const char *cmd); + /// Set shortcut of a given command + void setShortcut(const char *cmd, const char *accel); + /** Get shortcut of a given command + * @param cmd: command name + * @param defaultAccel: default shortcut + */ + QString getShortcut(const char *cmd, const char *defaultAccel = nullptr); + + /// Return actions having a given shortcut in order of decreasing priority + std::vector> getActionsByShortcut(const QKeySequence &shortcut); + + /// Set properties for a given list of actions in order of decreasing priority + void setPriorities(const std::vector &actions); + + /** Set the priority of a given command + * @param cmd: command name + * @param priority: priority of the command, bigger value means higher priority + */ + void setPriority(const char *cmd, int priority); + + /// Get the priority of a given command + int getPriority(const char *cmd); + + /** Set the top priority of a given command + * Make the given command the top priority of all commands. + * + * @param cmd: command name + */ + void setTopPriority(const char *cmd); + +Q_SIGNALS: + void shortcutChanged(const char *name, const QKeySequence &oldShortcut); + void actionShortcutChanged(QAction *, const QKeySequence &oldShortcut); + void priorityChanged(const char *name, int priority); + +protected: + bool eventFilter(QObject *, QEvent *ev) override; + bool checkShortcut(QObject *o, const QKeySequence &key); + void onTimer(); + +private: + ParameterGrp::handle hShortcuts; + ParameterGrp::handle hPriorities; + ParameterGrp::handle hSetting; + bool busy = false; + + struct ActionKey { + QKeySequence shortcut; + QByteArray name; + ActionKey(const QKeySequence &shortcut, const char *name = "") + : shortcut(shortcut) + , name(name) + {} + bool operator<(const ActionKey &other) const + { + if (shortcut > other.shortcut) + return false; + if (shortcut < other.shortcut) + return true; + return name < other.name; + } + }; + struct ActionData { + ActionKey key; + intptr_t pointer; + QPointer action; + + ActionData(QAction *action, const char *name = "") + : key(action->shortcut(), name) + , pointer(reinterpret_cast(action)) + , action(action) + {} + }; + bmi::multi_index_container< + ActionData, + bmi::indexed_by< + // hashed index on ActionData::Action pointer + bmi::hashed_unique>, + // ordered index on shortcut + name + bmi::ordered_non_unique> + > + > actionMap; + + std::unordered_map priorities; + int topPriority; + + struct ActionInfo { + QPointer action; + int seq_length; + int priority; + + ActionInfo(QAction *action, int l, int p) + : action(action) + , seq_length(l) + , priority(p) + {} + }; + std::vector pendingActions; + + QKeySequence pendingSequence; + + QPointer lastFocus; + + QTimer timer; + int timeout; +}; + +} + +#endif // GUI_SHORTCUT_MANAGER_H diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index 966bef1d1d3c..d3bdb947ed7f 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -311,6 +311,8 @@ void Workbench::setupCustomToolbars(ToolBarItem* root, const Base::ReferenceHasGroup("Shortcut")) { @@ -329,6 +331,7 @@ void Workbench::setupCustomShortcuts() const } } } +#endif } void Workbench::setupContextMenu(const char* recipient,MenuItem* item) const From ae91008049efa8daebd8390655a4511a62d0f3cd Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 4 Mar 2022 09:07:00 +0800 Subject: [PATCH 02/19] Gui: refactor 'Customize -> Toolbars' command list handling By reusing code in DlgCustomKeyboardImp --- src/Gui/DlgToolbars.ui | 577 +++++++++++++++++++++++------------------ 1 file changed, 327 insertions(+), 250 deletions(-) diff --git a/src/Gui/DlgToolbars.ui b/src/Gui/DlgToolbars.ui index 98bae2db0fcc..8eebc7657693 100644 --- a/src/Gui/DlgToolbars.ui +++ b/src/Gui/DlgToolbars.ui @@ -1,8 +1,5 @@ - - - Gui::Dialog::DlgCustomToolbars @@ -10,270 +7,350 @@ 0 0 576 - 318 + 352 Toolbars - - - 9 - - - 6 - - - - - <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:MS Shell Dlg 2; font-size:7.8pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><span style=" font-weight:600;">Note:</span> The changes become active the next time you load the appropriate workbench</p></body></html> - - - - - - - true - - - - 30 - 30 - - - - Move right - - - <b>Move the selected item one level down.</b><p>This will also change the level of the parent item.</p> - - - + + + + + + 0 + 0 + - - :/icons/button_right.svg - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 33 - 57 - - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding + Qt::Horizontal - - - 33 - 58 - - - - - - - - true - - - - 30 - 30 - - - - Move left - - - <b>Move the selected item one level up.</b><p>This will also change the level of the parent item.</p> - - - - - - :/icons/button_left.svg - - - true - - - false - - - - - - - true - - - - 30 - 30 - - - - Move down - - - <b>Move the selected item down.</b><p>The item will be moved within the hierarchy level.</p> - - - - - - :/icons/button_down.svg - - - true - - - - - - - true - - - - 30 - 30 - - - - Move up - - - <b>Move the selected item up.</b><p>The item will be moved within the hierarchy level.</p> - - - - - - :/icons/button_up.svg - - - - - - - 0 - - - 6 - - - - - - - - false + + + + 0 - - - - - - - - 0 - - - 6 - - - - - - - + 0 - - - + + 0 + + + 0 + + + + + + + + + 0 + 0 + + + + Category: + + + + + + + + + + false + + + + 1 + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 33 + 58 + + + + + + + + true + + + + 30 + 30 + + + + Move right + + + <b>Move the selected item one level down.</b><p>This will also change the level of the parent item.</p> + + + + + + + :/icons/button_right.svg:/icons/button_right.svg + + + + + + + true + + + + 30 + 30 + + + + Move left + + + <b>Move the selected item one level up.</b><p>This will also change the level of the parent item.</p> + + + + + + + :/icons/button_left.svg:/icons/button_left.svg + + + true + + + false + + + + + + + true + + + + 30 + 30 + + + + Move up + + + <b>Move the selected item up.</b><p>The item will be moved within the hierarchy level.</p> + + + + + + + :/icons/button_up.svg:/icons/button_up.svg + + + + + + + true + + + + 30 + 30 + + + + Move down + + + <b>Move the selected item down.</b><p>The item will be moved within the hierarchy level.</p> + + + + + + + :/icons/button_down.svg:/icons/button_down.svg + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 33 + 57 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 21 + + + + + + + + New... + + + + + + + Rename... + + + + + + + Delete + + + + + + + Qt::Vertical + + + + 20 + 152 + + + + + + + + +
- - - - 0 + + + + + 0 + 0 + - - 6 + + <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:MS Shell Dlg 2; font-size:7.8pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><span style=" font-weight:600;">Note:</span> The changes become active the next time you load the appropriate workbench</p></body></html> - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 21 - - - - - - - - New... - - - - - - - Rename... - - - - - - - Delete - - - - - - - Qt::Vertical - - - - 20 - 152 - - - - - + - + + editCommand + categoryBox + commandTreeWidget + moveActionRightButton + moveActionLeftButton + moveActionUpButton + moveActionDownButton + workbenchBox + toolbarTreeWidget + newButton + renameButton + deleteButton + From 4c10bb37be3e31ed5c1e4f10d816275d6962b79c Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 28 Feb 2022 21:50:03 +0800 Subject: [PATCH 03/19] Gui: fix shortcut editor --- src/Gui/Widgets.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Gui/Widgets.cpp b/src/Gui/Widgets.cpp index 3a71d700f529..89a2720ecfd2 100644 --- a/src/Gui/Widgets.cpp +++ b/src/Gui/Widgets.cpp @@ -420,18 +420,23 @@ void AccelLineEdit::keyPressEvent (QKeyEvent * e) break; } - // 4 keys are allowed for QShortcut - switch(keyPressedCount) { - case 4: + if (txtLine.isEmpty()) { + // Text maybe cleared by QLineEdit's built in clear button keyPressedCount = 0; - txtLine.clear(); - break; - case 0: - txtLine.clear(); - break; - default: - txtLine += QChar::fromLatin1(','); - break; + } else { + // 4 keys are allowed for QShortcut + switch(keyPressedCount) { + case 4: + keyPressedCount = 0; + txtLine.clear(); + break; + case 0: + txtLine.clear(); + break; + default: + txtLine += QString::fromLatin1(","); + break; + } } // Handles modifiers applying a mask. From 62fce1cbb04a290858a733d9d9605985b8cea6fd Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 4 Mar 2022 11:50:50 +0800 Subject: [PATCH 04/19] Fix comment typo --- src/Gui/DlgKeyboard.ui | 2 +- src/Gui/ShortcutManager.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Gui/DlgKeyboard.ui b/src/Gui/DlgKeyboard.ui index f48ea1df127e..e8b484cda9c3 100644 --- a/src/Gui/DlgKeyboard.ui +++ b/src/Gui/DlgKeyboard.ui @@ -201,7 +201,7 @@ be treated as shorctcut key sequence 'F, F'.
- This list shows commands having the same shortcut in the priority from hight + This list shows commands having the same shortcut in the priority from high to low. If more than one command with the same shortcut are active at the same time. The one with the highest prioirty will be triggered. diff --git a/src/Gui/ShortcutManager.cpp b/src/Gui/ShortcutManager.cpp index 32f3ca731fbb..6541e395b52f 100644 --- a/src/Gui/ShortcutManager.cpp +++ b/src/Gui/ShortcutManager.cpp @@ -235,7 +235,7 @@ bool ShortcutManager::checkShortcut(QObject *o, const QKeySequence &key) pendingActions.emplace_back(action, key.count(), 0); } if (flush) { - // We'll flush now because there is no poential match with further + // We'll flush now because there is no potential match with further // keystrokes, so no need to wait for timer. lastFocus = nullptr; onTimer(); From 2ed5f65d75cb6f3cb9a5dc2d7f7eb72c6a85ca34 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sun, 6 Mar 2022 20:43:17 +0800 Subject: [PATCH 05/19] Gui: fix action title processing --- src/Gui/Action.cpp | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/Gui/Action.cpp b/src/Gui/Action.cpp index 033fb84449af..2ff16acce9a0 100644 --- a/src/Gui/Action.cpp +++ b/src/Gui/Action.cpp @@ -35,6 +35,8 @@ # include #endif +#include + #include #include #include @@ -210,16 +212,30 @@ void Action::setToolTip(const QString & s, const QString & title) this)); } +static QString & actionTitle(QString & title) +{ + // Deal with QAction title mnemonic + static QRegularExpression re(QStringLiteral("&(.)")); + title.replace(re, QStringLiteral("\\1")); + // Trim line ending punctuation + static QRegularExpression rePunct(QStringLiteral("[[:punct:]]+$")); + title.replace(rePunct, QString()); + return title; +} + +static inline QString actionTitle(const QString &title) +{ + QString text(title); + return actionTitle(text); +} + QString Action::createToolTip(QString _tooltip, const QString & title, const QFont &font, const QString &sc, Action *act) { - QString text = title; - text.remove(QLatin1Char('&'));; - while(text.size() && text[text.size()-1].isPunct()) - text.resize(text.size()-1); + QString text = actionTitle(title); if (text.isEmpty()) return _tooltip; @@ -1405,14 +1421,10 @@ class CommandModel : public QAbstractItemModel case Qt::DisplayRole: case Qt::EditRole: if (info.text.isEmpty()) { -#if QT_VERSION>=QT_VERSION_CHECK(5,2,0) - info.text = QString::fromLatin1("%2 (%1)").arg( + info.text = QStringLiteral("%2 (%1)").arg( QString::fromLatin1(info.cmd->getName()), qApp->translate(info.cmd->className(), info.cmd->getMenuText())); -#else - info.text = qApp->translate(info.cmd->className(), info.cmd->getMenuText()); -#endif - info.text.replace(QLatin1Char('&'), QString()); + actionTitle(info.text); if (info.text.isEmpty()) info.text = QString::fromLatin1(info.cmd->getName()); } From 12008ededb66b20cd9e4e962d9757b6004831cb4 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sun, 6 Mar 2022 20:44:52 +0800 Subject: [PATCH 06/19] Minor code change according to suggestions --- src/Gui/Action.cpp | 11 +++++------ src/Gui/CommandView.cpp | 8 ++++---- src/Gui/ShortcutManager.cpp | 7 ++++--- src/Gui/Workbench.cpp | 20 -------------------- 4 files changed, 13 insertions(+), 33 deletions(-) diff --git a/src/Gui/Action.cpp b/src/Gui/Action.cpp index 2ff16acce9a0..f8a572fdc106 100644 --- a/src/Gui/Action.cpp +++ b/src/Gui/Action.cpp @@ -1377,11 +1377,10 @@ struct CmdInfo { }; static std::vector _Commands; static int _CommandRevision; +static const int CommandNameRole = Qt::UserRole; class CommandModel : public QAbstractItemModel { -public: - public: CommandModel(QObject* parent) : QAbstractItemModel(parent) @@ -1449,7 +1448,7 @@ class CommandModel : public QAbstractItemModel } return info.tooltip; - case Qt::UserRole: + case CommandNameRole: return QByteArray(info.cmd->getName()); default: @@ -1480,9 +1479,7 @@ CommandCompleter::CommandCompleter(QLineEdit *lineedit, QObject *parent) : QCompleter(parent) { this->setModel(new CommandModel(this)); -#if QT_VERSION>=QT_VERSION_CHECK(5,2,0) this->setFilterMode(Qt::MatchContains); -#endif this->setCaseSensitivity(Qt::CaseInsensitive); this->setCompletionMode(QCompleter::PopupCompletion); this->setWidget(lineedit); @@ -1546,12 +1543,14 @@ bool CommandCompleter::eventFilter(QObject *o, QEvent *ev) void CommandCompleter::onCommandActivated(const QModelIndex &index) { - QByteArray name = completionModel()->data(index, Qt::UserRole).toByteArray(); + QByteArray name = completionModel()->data(index, CommandNameRole).toByteArray(); Q_EMIT commandActivated(name); } void CommandCompleter::onTextChanged(const QString &txt) { + // Do not activate completer if less than 3 characters for better + // performance. if (txt.size() < 3 || !widget()) return; diff --git a/src/Gui/CommandView.cpp b/src/Gui/CommandView.cpp index 04b8eb23ee2f..c24184f30ca1 100644 --- a/src/Gui/CommandView.cpp +++ b/src/Gui/CommandView.cpp @@ -261,10 +261,10 @@ class StdCmdFreezeViews : public Gui::Command QString getShortcut() const override; protected: - virtual void activated(int iMsg) override; - virtual bool isActive(void) override; - virtual Action * createAction(void) override; - virtual void languageChange() override; + void activated(int iMsg) override; + bool isActive(void) override; + Action * createAction(void) override; + void languageChange() override; private: void onSaveViews(); diff --git a/src/Gui/ShortcutManager.cpp b/src/Gui/ShortcutManager.cpp index 6541e395b52f..a1f3f24658e7 100644 --- a/src/Gui/ShortcutManager.cpp +++ b/src/Gui/ShortcutManager.cpp @@ -404,6 +404,8 @@ void ShortcutManager::setTopPriority(const char *cmdName) void ShortcutManager::onTimer() { + timer.stop(); + QAction *found = nullptr; int priority = -INT_MAX; int seq_length = 0; @@ -427,8 +429,8 @@ void ShortcutManager::onTimer() if (lastFocus && lastFocus == QApplication::focusWidget()) { // We are here because we have withheld some previous triggered action. // We then disabled the action, and faked the same key strokes in order - // to wait for more for potential match of longer key sequence. We use - // a timer to end the wait and triggered the pending action. + // to wait for more potential match of longer key sequence. We use + // a timer to end the wait and trigger the pending action. // // However, Qt's internal shorcutmap state machine is still armed with // our fake key strokes. So we try to fake some more obscure symbol key @@ -449,7 +451,6 @@ void ShortcutManager::onTimer() } } } - timer.stop(); } #include "moc_ShortcutManager.cpp" diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index d3bdb947ed7f..caf04a6c725e 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -312,26 +312,6 @@ void Workbench::setupCustomToolbars(ToolBarItem* root, const Base::ReferenceHasGroup("Shortcut")) { - hGrp = hGrp->GetGroup("Shortcut"); - // Get all user defined shortcuts - const CommandManager& cCmdMgr = Application::Instance->commandManager(); - std::vector > items = hGrp->GetASCIIMap(); - for (std::vector >::iterator it = items.begin(); it != items.end(); ++it) { - Command* cmd = cCmdMgr.getCommandByName(it->first.c_str()); - if (cmd && cmd->getAction()) { - // may be UTF-8 encoded - QString str = QString::fromUtf8(it->second.c_str()); - QKeySequence shortcut = str; - cmd->getAction()->setShortcut(shortcut.toString(QKeySequence::NativeText)); - cmd->recreateTooltip(it->first.c_str(), cmd->getAction()); // The tooltip has the shortcut in it... - } - } - } -#endif } void Workbench::setupContextMenu(const char* recipient,MenuItem* item) const From b56a169d54c4a44ff65f540110a4865e35c0dfc0 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sun, 6 Mar 2022 21:03:37 +0800 Subject: [PATCH 07/19] Fix build warning --- src/Gui/ShortcutManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gui/ShortcutManager.h b/src/Gui/ShortcutManager.h index a928b811b106..bca1f73c76d9 100644 --- a/src/Gui/ShortcutManager.h +++ b/src/Gui/ShortcutManager.h @@ -128,7 +128,7 @@ class GuiExport ShortcutManager : public QObject, public ParameterGrp::ObserverT ActionData(QAction *action, const char *name = "") : key(action->shortcut(), name) - , pointer(reinterpret_cast(action)) + , pointer(reinterpret_cast(action)) , action(action) {} }; From 2d50b091305b9180290e11859a4f95a24e86aa41 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 7 Mar 2022 08:57:29 +0800 Subject: [PATCH 08/19] Gui: fix ShortcutManager modifier detection --- src/Gui/ShortcutManager.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Gui/ShortcutManager.cpp b/src/Gui/ShortcutManager.cpp index a1f3f24658e7..510570b38018 100644 --- a/src/Gui/ShortcutManager.cpp +++ b/src/Gui/ShortcutManager.cpp @@ -247,18 +247,18 @@ bool ShortcutManager::checkShortcut(QObject *o, const QKeySequence &key) // Qt's shortcut state machine favors shortest match (which is ridiculous, // unless I'm mistaken?). We'll do longest match. We've disabled all - // shortcuts that can match the current key sequence. Now reply the sequence + // shortcuts that can match the current key sequence. Now replay the sequence // and wait for the next keystroke. for (int i=0; i Date: Mon, 7 Mar 2022 13:02:42 +0800 Subject: [PATCH 09/19] Gui: fix GroupCommand icon setup --- src/Gui/Command.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Gui/Command.cpp b/src/Gui/Command.cpp index 7d90f071c237..afd247b14cc8 100644 --- a/src/Gui/Command.cpp +++ b/src/Gui/Command.cpp @@ -1069,10 +1069,12 @@ void GroupCommand::setup(Action *pcAction) { if(idx>=0 && idx<(int)cmds.size() && cmds[idx].first) { auto cmd = cmds[idx].first; pcAction->setText(QCoreApplication::translate(className(), getMenuText())); + QIcon icon; if (auto childAction = cmd->getAction()) - pcAction->setIcon(childAction->icon()); - else - pcAction->setIcon(BitmapFactory().iconFromTheme(cmd->getPixmap())); + icon = childAction->icon(); + if (icon.isNull()) + icon = BitmapFactory().iconFromTheme(cmd->getPixmap()); + pcAction->setIcon(icon); const char *context = dynamic_cast(cmd) ? cmd->getName() : cmd->className(); const char *tooltip = cmd->getToolTipText(); const char *statustip = cmd->getStatusTip(); From 1d6020ea753ddaad5999d72c452e32657e1fc869 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sat, 12 Mar 2022 13:16:34 +0800 Subject: [PATCH 10/19] Gui: move CommandCompleter into its own source file --- src/Gui/Action.cpp | 275 ++++++++--------------------------- src/Gui/Action.h | 38 +++-- src/Gui/CMakeLists.txt | 2 + src/Gui/CommandCompleter.cpp | 239 ++++++++++++++++++++++++++++++ src/Gui/CommandCompleter.h | 59 ++++++++ src/Gui/DlgKeyboardImp.cpp | 20 +-- src/Gui/DlgToolbarsImp.cpp | 4 +- 7 files changed, 390 insertions(+), 247 deletions(-) create mode 100644 src/Gui/CommandCompleter.cpp create mode 100644 src/Gui/CommandCompleter.h diff --git a/src/Gui/Action.cpp b/src/Gui/Action.cpp index f8a572fdc106..233c45b4a612 100644 --- a/src/Gui/Action.cpp +++ b/src/Gui/Action.cpp @@ -209,11 +209,12 @@ void Action::setToolTip(const QString & s, const QString & title) title.isEmpty() ? _action->text() : title, _action->font(), _action->shortcut().toString(QKeySequence::NativeText), - this)); + _pcCmd)); } -static QString & actionTitle(QString & title) +QString Action::cleanTitle(const QString & text) { + QString title(text); // Deal with QAction title mnemonic static QRegularExpression re(QStringLiteral("&(.)")); title.replace(re, QStringLiteral("\\1")); @@ -223,19 +224,63 @@ static QString & actionTitle(QString & title) return title; } -static inline QString actionTitle(const QString &title) +QString Action::commandToolTip(const Command *cmd, bool richFormat) { - QString text(title); - return actionTitle(text); + if (!cmd) + return QString(); + + if (richFormat) { + if (auto action = cmd->getAction()) + return action->_action->toolTip(); + } + + QString title, tooltip; + if (dynamic_cast(cmd)) { + if (auto txt = cmd->getMenuText()) + title = QString::fromUtf8(txt); + if (auto txt = cmd->getToolTipText()) + tooltip = QString::fromUtf8(txt); + } else { + if (auto txt = cmd->getMenuText()) + title = qApp->translate(cmd->className(), txt); + if (auto txt = cmd->getToolTipText()) + tooltip = qApp->translate(cmd->className(), txt); + } + + if (!richFormat) + return tooltip; + return createToolTip(tooltip, title, QFont(), cmd->getShortcut(), cmd); +} + +QString Action::commandMenuText(const Command *cmd) +{ + if (!cmd) + return QString(); + + QString title; + if (auto action = cmd->getAction()) + title = action->text(); + else if (dynamic_cast(cmd)) { + if (auto txt = cmd->getMenuText()) + title = QString::fromUtf8(txt); + } else { + if (auto txt = cmd->getMenuText()) + title = qApp->translate(cmd->className(), txt); + } + if (title.isEmpty()) + title = QString::fromUtf8(cmd->getName()); + else + title = cleanTitle(title); + return title; } QString Action::createToolTip(QString _tooltip, const QString & title, const QFont &font, const QString &sc, - Action *act) + const Command *pcCmd) { - QString text = actionTitle(title); + QString text = cleanTitle(title); if (text.isEmpty()) return _tooltip; @@ -259,17 +304,18 @@ QString Action::createToolTip(QString _tooltip, text.toHtmlEscaped(), shortcut.toHtmlEscaped()); QString cmdName; - auto pcCmd = act ? act->_pcCmd : nullptr; if (pcCmd && pcCmd->getName()) { cmdName = QString::fromLatin1(pcCmd->getName()); - if (auto groupcmd = dynamic_cast(pcCmd)) { - int idx = act->property("defaultAction").toInt(); - auto cmd = groupcmd->getCommand(idx); - if (cmd && cmd->getName()) - cmdName = QStringLiteral("%1 (%2:%3)") - .arg(QString::fromLatin1(cmd->getName())) - .arg(cmdName) - .arg(idx); + if (auto groupcmd = dynamic_cast(pcCmd)) { + if (auto act = pcCmd->getAction()) { + int idx = act->property("defaultAction").toInt(); + auto cmd = groupcmd->getCommand(idx); + if (cmd && cmd->getName()) + cmdName = QStringLiteral("%1 (%2:%3)") + .arg(QString::fromLatin1(cmd->getName())) + .arg(cmdName) + .arg(idx); + } } cmdName = QStringLiteral("

%1

") .arg(cmdName.toHtmlEscaped()); @@ -1366,201 +1412,4 @@ void WindowAction::addTo ( QWidget * w ) } } -// -------------------------------------------------------------------- - -struct CmdInfo { - Command *cmd = nullptr; - QString text; - QString tooltip; - QIcon icon; - bool iconChecked = false; -}; -static std::vector _Commands; -static int _CommandRevision; -static const int CommandNameRole = Qt::UserRole; - -class CommandModel : public QAbstractItemModel -{ -public: - CommandModel(QObject* parent) - : QAbstractItemModel(parent) - { - update(); - } - - void update() - { - auto &manager = Application::Instance->commandManager(); - if (_CommandRevision == manager.getRevision()) - return; - beginResetModel(); - _CommandRevision = manager.getRevision(); - _Commands.clear(); - for (auto &v : manager.getCommands()) { - _Commands.emplace_back(); - auto &info = _Commands.back(); - info.cmd = v.second; - } - endResetModel(); - } - - virtual QModelIndex parent(const QModelIndex &) const - { - return QModelIndex(); - } - - virtual QVariant data(const QModelIndex & index, int role) const - { - if (index.row() < 0 || index.row() >= (int)_Commands.size()) - return QVariant(); - - auto &info = _Commands[index.row()]; - - switch(role) { - case Qt::DisplayRole: - case Qt::EditRole: - if (info.text.isEmpty()) { - info.text = QStringLiteral("%2 (%1)").arg( - QString::fromLatin1(info.cmd->getName()), - qApp->translate(info.cmd->className(), info.cmd->getMenuText())); - actionTitle(info.text); - if (info.text.isEmpty()) - info.text = QString::fromLatin1(info.cmd->getName()); - } - return info.text; - - case Qt::DecorationRole: - if (!info.iconChecked) { - info.iconChecked = true; - if(info.cmd->getPixmap()) - info.icon = BitmapFactory().iconFromTheme(info.cmd->getPixmap()); - } - return info.icon; - - case Qt::ToolTipRole: - if (info.tooltip.isEmpty()) { - info.tooltip = QString::fromLatin1("%1: %2").arg( - QString::fromLatin1(info.cmd->getName()), - qApp->translate(info.cmd->className(), info.cmd->getMenuText())); - QString tooltip = qApp->translate(info.cmd->className(), info.cmd->getToolTipText()); - if (tooltip.size()) - info.tooltip += QString::fromLatin1("\n\n") + tooltip; - } - return info.tooltip; - - case CommandNameRole: - return QByteArray(info.cmd->getName()); - - default: - return QVariant(); - } - } - - virtual QModelIndex index(int row, int, const QModelIndex &) const - { - return this->createIndex(row, 0); - } - - virtual int rowCount(const QModelIndex &) const - { - return (int)(_Commands.size()); - } - - virtual int columnCount(const QModelIndex &) const - { - return 1; - } -}; - - -// -------------------------------------------------------------------- - -CommandCompleter::CommandCompleter(QLineEdit *lineedit, QObject *parent) - : QCompleter(parent) -{ - this->setModel(new CommandModel(this)); - this->setFilterMode(Qt::MatchContains); - this->setCaseSensitivity(Qt::CaseInsensitive); - this->setCompletionMode(QCompleter::PopupCompletion); - this->setWidget(lineedit); - connect(lineedit, SIGNAL(textEdited(QString)), this, SLOT(onTextChanged(QString))); - connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(onCommandActivated(QModelIndex))); - connect(this, SIGNAL(highlighted(QString)), lineedit, SLOT(setText(QString))); -} - -bool CommandCompleter::eventFilter(QObject *o, QEvent *ev) -{ - if (ev->type() == QEvent::KeyPress - && (o == this->widget() || o == this->popup())) - { - QKeyEvent * ke = static_cast(ev); - switch(ke->key()) { - case Qt::Key_Escape: { - auto edit = qobject_cast(this->widget()); - if (edit && edit->text().size()) { - edit->setText(QString()); - popup()->hide(); - return true; - } else if (popup()->isVisible()) { - popup()->hide(); - return true; - } - break; - } - case Qt::Key_Tab: { - if (this->popup()->isVisible()) { - QKeyEvent kevent(ke->type(),Qt::Key_Down,0); - qApp->sendEvent(this->popup(), &kevent); - return true; - } - break; - } - case Qt::Key_Backtab: { - if (this->popup()->isVisible()) { - QKeyEvent kevent(ke->type(),Qt::Key_Up,0); - qApp->sendEvent(this->popup(), &kevent); - return true; - } - break; - } - case Qt::Key_Enter: - case Qt::Key_Return: - if (o == this->widget()) { - auto index = currentIndex(); - if (index.isValid()) - onCommandActivated(index); - else - complete(); - ev->setAccepted(true); - return true; - } - default: - break; - } - } - return QCompleter::eventFilter(o, ev); -} - -void CommandCompleter::onCommandActivated(const QModelIndex &index) -{ - QByteArray name = completionModel()->data(index, CommandNameRole).toByteArray(); - Q_EMIT commandActivated(name); -} - -void CommandCompleter::onTextChanged(const QString &txt) -{ - // Do not activate completer if less than 3 characters for better - // performance. - if (txt.size() < 3 || !widget()) - return; - - static_cast(this->model())->update(); - - this->setCompletionPrefix(txt); - QRect rect = widget()->rect(); - if (rect.width() < 300) - rect.setWidth(300); - this->complete(rect); -} - #include "moc_Action.cpp" diff --git a/src/Gui/Action.h b/src/Gui/Action.h index 5c656fc746c6..aa67ebf57237 100644 --- a/src/Gui/Action.h +++ b/src/Gui/Action.h @@ -81,7 +81,23 @@ class GuiExport Action : public QObject const QString &title, const QFont &font, const QString &shortcut, - Action *action = nullptr); + const Command *cmd = nullptr); + + /** Obtain tool tip of a given command + * @param cmd: input command + * @param richFormat: whether to output rich text formatted tooltip + */ + static QString commandToolTip(const Command *cmd, bool richFormat = true); + + /** Obtain the menu text of a given command + * @param cmd: input command + * @return Return the command menu text that is stripped with its mnemonic + * symbol '&' and ending punctuations + */ + static QString commandMenuText(const Command *cmd); + + /// Clean the title by stripping the mnemonic symbol '&' and ending punctuations + static QString cleanTitle(const QString &title); Command *command() const { return _pcCmd; @@ -371,26 +387,6 @@ class GuiExport WindowAction : public ActionGroup QMenu* _menu; }; -/** - * Command name completer. - */ -class GuiExport CommandCompleter : public QCompleter -{ - Q_OBJECT -public: - CommandCompleter(QLineEdit *edit, QObject *parent = nullptr); - -Q_SIGNALS: - void commandActivated(const QByteArray &name); - -protected Q_SLOTS: - void onTextChanged(const QString &); - void onCommandActivated(const QModelIndex &); - -protected: - bool eventFilter(QObject *, QEvent *ev); -}; - } // namespace Gui #endif // GUI_ACTION_H diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 431f62f9204a..1e547c437f76 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -396,6 +396,7 @@ SET(Command_CPP_SRCS CommandLink.cpp CommandPyImp.cpp ShortcutManager.cpp + CommandCompleter.cpp ) SET(Command_SRCS ${Command_CPP_SRCS} @@ -404,6 +405,7 @@ SET(Command_SRCS Command.h CommandT.h ShortcutManager.h + CommandCompleter.h ) SOURCE_GROUP("Command" FILES ${Command_SRCS}) diff --git a/src/Gui/CommandCompleter.cpp b/src/Gui/CommandCompleter.cpp new file mode 100644 index 000000000000..aa12acd94d61 --- /dev/null +++ b/src/Gui/CommandCompleter.cpp @@ -0,0 +1,239 @@ +/**************************************************************************** + * Copyright (c) 2022 Zheng Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +# include +# include +#endif + +#include "Application.h" +#include "ShortcutManager.h" +#include "Command.h" +#include "Action.h" +#include "BitmapFactory.h" +#include "CommandCompleter.h" + +using namespace Gui; + +namespace { + +struct CmdInfo { + Command *cmd = nullptr; + QIcon icon; + bool iconChecked = false; +}; +std::vector _Commands; +int _CommandRevision; +const int CommandNameRole = Qt::UserRole; +bool _ShortcutSignalConnected = false; + +class CommandModel : public QAbstractItemModel +{ + int revision = 0; + +public: + CommandModel(QObject* parent) + : QAbstractItemModel(parent) + { + update(); + if (!_ShortcutSignalConnected) { + _ShortcutSignalConnected = true; + QObject::connect(ShortcutManager::instance(), &ShortcutManager::shortcutChanged, []{_CommandRevision = 0;}); + } + } + + void update() + { + auto &manager = Application::Instance->commandManager(); + if (revision == _CommandRevision && _CommandRevision == manager.getRevision()) + return; + beginResetModel(); + revision = manager.getRevision(); + if (revision != _CommandRevision) { + _CommandRevision = revision; + _CommandRevision = manager.getRevision(); + _Commands.clear(); + for (auto &v : manager.getCommands()) { + _Commands.emplace_back(); + auto &info = _Commands.back(); + info.cmd = v.second; + } + } + endResetModel(); + } + + virtual QModelIndex parent(const QModelIndex &) const + { + return QModelIndex(); + } + + virtual QVariant data(const QModelIndex & index, int role) const + { + if (index.row() < 0 || index.row() >= (int)_Commands.size()) + return QVariant(); + + auto &info = _Commands[index.row()]; + + switch(role) { + case Qt::DisplayRole: + case Qt::EditRole: { + QString title = QStringLiteral("%1 (%2)").arg( + Action::commandMenuText(info.cmd), + QString::fromUtf8(info.cmd->getName())); + QString shortcut = info.cmd->getShortcut(); + if (!shortcut.isEmpty()) + title += QStringLiteral(" (%1)").arg(shortcut); + return title; + } + case Qt::ToolTipRole: + return Action::commandToolTip(info.cmd); + + case Qt::DecorationRole: + if (!info.iconChecked) { + info.iconChecked = true; + if(info.cmd->getPixmap()) + info.icon = BitmapFactory().iconFromTheme(info.cmd->getPixmap()); + } + return info.icon; + + case CommandNameRole: + return QByteArray(info.cmd->getName()); + + default: + break; + } + return QVariant(); + } + + virtual QModelIndex index(int row, int, const QModelIndex &) const + { + return this->createIndex(row, 0); + } + + virtual int rowCount(const QModelIndex &) const + { + return (int)(_Commands.size()); + } + + virtual int columnCount(const QModelIndex &) const + { + return 1; + } +}; + +} // anonymous namespace + +// -------------------------------------------------------------------- + +CommandCompleter::CommandCompleter(QLineEdit *lineedit, QObject *parent) + : QCompleter(parent) +{ + this->setModel(new CommandModel(this)); + this->setFilterMode(Qt::MatchContains); + this->setCaseSensitivity(Qt::CaseInsensitive); + this->setCompletionMode(QCompleter::PopupCompletion); + this->setWidget(lineedit); + connect(lineedit, SIGNAL(textEdited(QString)), this, SLOT(onTextChanged(QString))); + connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(onCommandActivated(QModelIndex))); + connect(this, SIGNAL(highlighted(QString)), lineedit, SLOT(setText(QString))); +} + +bool CommandCompleter::eventFilter(QObject *o, QEvent *ev) +{ + if (ev->type() == QEvent::KeyPress + && (o == this->widget() || o == this->popup())) + { + QKeyEvent * ke = static_cast(ev); + switch(ke->key()) { + case Qt::Key_Escape: { + auto edit = qobject_cast(this->widget()); + if (edit && edit->text().size()) { + edit->setText(QString()); + popup()->hide(); + return true; + } else if (popup()->isVisible()) { + popup()->hide(); + return true; + } + break; + } + case Qt::Key_Tab: { + if (this->popup()->isVisible()) { + QKeyEvent kevent(ke->type(),Qt::Key_Down,0); + qApp->sendEvent(this->popup(), &kevent); + return true; + } + break; + } + case Qt::Key_Backtab: { + if (this->popup()->isVisible()) { + QKeyEvent kevent(ke->type(),Qt::Key_Up,0); + qApp->sendEvent(this->popup(), &kevent); + return true; + } + break; + } + case Qt::Key_Enter: + case Qt::Key_Return: + if (o == this->widget()) { + auto index = currentIndex(); + if (index.isValid()) + onCommandActivated(index); + else + complete(); + ev->setAccepted(true); + return true; + } + default: + break; + } + } + return QCompleter::eventFilter(o, ev); +} + +void CommandCompleter::onCommandActivated(const QModelIndex &index) +{ + QByteArray name = completionModel()->data(index, CommandNameRole).toByteArray(); + Q_EMIT commandActivated(name); +} + +void CommandCompleter::onTextChanged(const QString &txt) +{ + // Do not activate completer if less than 3 characters for better + // performance. + if (txt.size() < 3 || !widget()) + return; + + static_cast(this->model())->update(); + + this->setCompletionPrefix(txt); + QRect rect = widget()->rect(); + if (rect.width() < 300) + rect.setWidth(300); + this->complete(rect); +} + +#include "moc_CommandCompleter.cpp" diff --git a/src/Gui/CommandCompleter.h b/src/Gui/CommandCompleter.h new file mode 100644 index 000000000000..a2edd0723671 --- /dev/null +++ b/src/Gui/CommandCompleter.h @@ -0,0 +1,59 @@ +/**************************************************************************** + * Copyright (c) 2022 Zheng Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#ifndef GUI_COMMAND_COMPLETER_H +#define GUI_COMMAND_COMPLETER_H + +#include + +class QLineEdit; + +namespace Gui { + +/** + * Command name auto completer. + * + * This class provides an auto completer for a QLineEdit widget. The auto + * completer supports keyword search in command title, internal name, and + * shortcut. + */ +class GuiExport CommandCompleter : public QCompleter +{ + Q_OBJECT +public: + CommandCompleter(QLineEdit *edit, QObject *parent = nullptr); + +Q_SIGNALS: + /// Triggered when a command is selected in the completer + void commandActivated(const QByteArray &name); + +protected Q_SLOTS: + void onTextChanged(const QString &); + void onCommandActivated(const QModelIndex &); + +protected: + bool eventFilter(QObject *, QEvent *ev); +}; + +} // namespace Gui + +#endif diff --git a/src/Gui/DlgKeyboardImp.cpp b/src/Gui/DlgKeyboardImp.cpp index 52bb18824450..aa260ffd990f 100644 --- a/src/Gui/DlgKeyboardImp.cpp +++ b/src/Gui/DlgKeyboardImp.cpp @@ -44,6 +44,7 @@ #include "Window.h" #include "PrefWidgets.h" #include "ShortcutManager.h" +#include "CommandCompleter.h" using namespace Gui::Dialog; @@ -146,19 +147,14 @@ DlgCustomKeyboardImp::initCommandList(QTreeWidget *commandTreeWidget, QComboBox commandTreeWidget->clear(); CommandManager & cCmdMgr = Application::Instance->commandManager(); - QString group = combo->itemData(index, Qt::UserRole).toString(); - auto cmds = group == QStringLiteral("All") ? cCmdMgr.getAllCommands() - : cCmdMgr.getGroupCommands(group.toLatin1()); + auto group = combo->itemData(index, Qt::UserRole).toByteArray(); + auto cmds = group == "All" ? cCmdMgr.getAllCommands() + : cCmdMgr.getGroupCommands(group.constData()); QTreeWidgetItem *currentItem = nullptr; for (const Command *cmd : cmds) { QTreeWidgetItem* item = new QTreeWidgetItem(commandTreeWidget); - if (dynamic_cast(cmd)) { - item->setText(1, QString::fromUtf8(cmd->getMenuText())); - item->setToolTip(1, QString::fromUtf8(cmd->getToolTipText())); - } else { - item->setText(1, qApp->translate(cmd->className(), cmd->getMenuText())); - item->setToolTip(1, qApp->translate(cmd->className(), cmd->getToolTipText())); - } + item->setText(1, Action::commandMenuText(cmd)); + item->setToolTip(1, Action::commandToolTip(cmd)); item->setData(1, Qt::UserRole, QByteArray(cmd->getName())); item->setSizeHint(0, QSize(32, 32)); if (auto pixmap = cmd->getPixmap()) @@ -287,7 +283,7 @@ void DlgCustomKeyboardImp::populatePriorityList(QTreeWidget *priorityList, continue; QTreeWidgetItem* item = new QTreeWidgetItem(priorityList); item->setText(0, QString::fromUtf8(info.first)); - item->setText(1, info.second->text()); + item->setText(1, Action::cleanTitle(info.second->text())); item->setToolTip(0, info.second->toolTip()); item->setIcon(0, info.second->icon()); item->setData(0, Qt::UserRole, info.first); @@ -374,7 +370,7 @@ void DlgCustomKeyboardImp::on_commandTreeWidget_currentItemChanged(QTreeWidgetIt ui->buttonReset->setEnabled((ks != ks2)); } - ui->textLabelDescription->setText(item->toolTip(1)); + ui->textLabelDescription->setText(Action::commandToolTip(cmd, false)); } /** Shows all commands of this category */ diff --git a/src/Gui/DlgToolbarsImp.cpp b/src/Gui/DlgToolbarsImp.cpp index 112e276aa526..0cea0e155f16 100644 --- a/src/Gui/DlgToolbarsImp.cpp +++ b/src/Gui/DlgToolbarsImp.cpp @@ -37,6 +37,7 @@ #include "Application.h" #include "BitmapFactory.h" #include "Command.h" +#include "Action.h" #include "ToolBarManager.h" #include "MainWindow.h" #include "Widgets.h" @@ -206,7 +207,8 @@ void DlgCustomToolbars::importCustomToolbars(const QByteArray& name) if (pCmd) { // command name QTreeWidgetItem* item = new QTreeWidgetItem(toplevel); - item->setText(0, qApp->translate(pCmd->className(), pCmd->getMenuText())); + item->setText(0, Action::commandMenuText(pCmd)); + item->setToolTip(0, Action::commandToolTip(pCmd)); item->setData(0, Qt::UserRole, QByteArray(it2->first.c_str())); if (pCmd->getPixmap()) item->setIcon(0, BitmapFactory().iconFromTheme(pCmd->getPixmap())); From f99b031345297c338a52462bfd8b57ab6f84eb47 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sat, 12 Mar 2022 13:38:46 +0800 Subject: [PATCH 11/19] Gui: fix macro command sync in Customize -> Keyboard/Toolbar --- src/Gui/DlgKeyboardImp.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Gui/DlgKeyboardImp.cpp b/src/Gui/DlgKeyboardImp.cpp index aa260ffd990f..b6187999ab6f 100644 --- a/src/Gui/DlgKeyboardImp.cpp +++ b/src/Gui/DlgKeyboardImp.cpp @@ -177,11 +177,18 @@ DlgCustomKeyboardImp::initCommandList(QTreeWidget *commandTreeWidget, QComboBox populateCommandGroups(combo); - return Application::Instance->commandManager().signalChanged.connect([combo](){ - if (combo) { - populateCommandGroups(combo); - combo->activated(combo->currentIndex()); - } + // Using a timer to respond for command change for performance, and also + // because macro command may be added before proper initialization (null + // menu text, etc.) + QTimer *timer = new QTimer(combo); + timer->setSingleShot(true); + QObject::connect(timer, &QTimer::timeout, [combo](){ + populateCommandGroups(combo); + combo->activated(combo->currentIndex()); + }); + + return Application::Instance->commandManager().signalChanged.connect([timer](){ + timer->start(100); }); } From 02982d5ea4848f1947c17fe7c70e5d83b38aba32 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sat, 12 Mar 2022 13:26:22 +0800 Subject: [PATCH 12/19] Gui: fix missing separator item in Customize -> Toolbar --- src/Gui/DlgKeyboardImp.cpp | 95 ++++++++++++++++++++++---------------- src/Gui/DlgKeyboardImp.h | 5 +- src/Gui/DlgToolbarsImp.cpp | 22 ++++----- src/Gui/DlgToolbarsImp.h | 4 +- 4 files changed, 73 insertions(+), 53 deletions(-) diff --git a/src/Gui/DlgKeyboardImp.cpp b/src/Gui/DlgKeyboardImp.cpp index b6187999ab6f..47e094148b04 100644 --- a/src/Gui/DlgKeyboardImp.cpp +++ b/src/Gui/DlgKeyboardImp.cpp @@ -79,6 +79,7 @@ DlgCustomKeyboardImp::DlgCustomKeyboardImp( QWidget* parent ) ui->setupUi(this); conn = initCommandWidgets(ui->commandTreeWidget, + nullptr, ui->categoryBox, ui->editCommand, ui->assignedTreeWidget, @@ -129,8 +130,49 @@ void DlgCustomKeyboardImp::initCommandCompleter(QLineEdit *edit, QComboBox *comb }); } +void DlgCustomKeyboardImp::populateCommandList(QTreeWidget *commandTreeWidget, + QTreeWidgetItem *separatorItem, + QComboBox *combo) +{ + QByteArray current; + if (auto item = commandTreeWidget->currentItem()) + current = item->data(1, Qt::UserRole).toByteArray(); + + if (separatorItem) + commandTreeWidget->takeTopLevelItem(commandTreeWidget->indexOfTopLevelItem(separatorItem)); + commandTreeWidget->clear(); + if (separatorItem) + commandTreeWidget->addTopLevelItem(separatorItem); + CommandManager & cCmdMgr = Application::Instance->commandManager(); + auto group = combo->itemData(combo->currentIndex(), Qt::UserRole).toByteArray(); + auto cmds = group == "All" ? cCmdMgr.getAllCommands() + : cCmdMgr.getGroupCommands(group.constData()); + QTreeWidgetItem *currentItem = nullptr; + for (const Command *cmd : cmds) { + QTreeWidgetItem* item = new QTreeWidgetItem(commandTreeWidget); + item->setText(1, Action::commandMenuText(cmd)); + item->setToolTip(1, Action::commandToolTip(cmd)); + item->setData(1, Qt::UserRole, QByteArray(cmd->getName())); + item->setSizeHint(0, QSize(32, 32)); + if (auto pixmap = cmd->getPixmap()) + item->setIcon(0, BitmapFactory().iconFromTheme(pixmap)); + item->setText(2, cmd->getShortcut()); + if (auto accel = cmd->getAccel()) + item->setText(3, QKeySequence(QString::fromLatin1(accel)).toString()); + + if (current == cmd->getName()) + currentItem = item; + } + if (currentItem) + commandTreeWidget->setCurrentItem(currentItem); + commandTreeWidget->resizeColumnToContents(2); + commandTreeWidget->resizeColumnToContents(3); +} + boost::signals2::connection -DlgCustomKeyboardImp::initCommandList(QTreeWidget *commandTreeWidget, QComboBox *combo) +DlgCustomKeyboardImp::initCommandList(QTreeWidget *commandTreeWidget, + QTreeWidgetItem *separatorItem, + QComboBox *combo) { QStringList labels; labels << tr("Icon") << tr("Command") << tr("Shortcut") << tr("Default"); @@ -140,53 +182,25 @@ DlgCustomKeyboardImp::initCommandList(QTreeWidget *commandTreeWidget, QComboBox commandTreeWidget->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); commandTreeWidget->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); - QObject::connect(combo, QOverload::of(&QComboBox::activated), [=](int index) { - QByteArray current; - if (auto item = commandTreeWidget->currentItem()) - current = item->data(1, Qt::UserRole).toByteArray(); - - commandTreeWidget->clear(); - CommandManager & cCmdMgr = Application::Instance->commandManager(); - auto group = combo->itemData(index, Qt::UserRole).toByteArray(); - auto cmds = group == "All" ? cCmdMgr.getAllCommands() - : cCmdMgr.getGroupCommands(group.constData()); - QTreeWidgetItem *currentItem = nullptr; - for (const Command *cmd : cmds) { - QTreeWidgetItem* item = new QTreeWidgetItem(commandTreeWidget); - item->setText(1, Action::commandMenuText(cmd)); - item->setToolTip(1, Action::commandToolTip(cmd)); - item->setData(1, Qt::UserRole, QByteArray(cmd->getName())); - item->setSizeHint(0, QSize(32, 32)); - if (auto pixmap = cmd->getPixmap()) - item->setIcon(0, BitmapFactory().iconFromTheme(pixmap)); - item->setText(2, cmd->getShortcut()); - if (auto accel = cmd->getAccel()) - item->setText(3, QKeySequence(QString::fromLatin1(accel)).toString()); - - if (current == cmd->getName()) - currentItem = item; - } - if (currentItem) - commandTreeWidget->setCurrentItem(currentItem); - commandTreeWidget->resizeColumnToContents(2); - commandTreeWidget->resizeColumnToContents(3); - }); - - QObject::connect(ShortcutManager::instance(), &ShortcutManager::shortcutChanged, - combo, [combo]() { combo->activated(combo->currentIndex()); }); - populateCommandGroups(combo); - // Using a timer to respond for command change for performance, and also + // Using a timer to respond to command change for performance, and also // because macro command may be added before proper initialization (null // menu text, etc.) QTimer *timer = new QTimer(combo); timer->setSingleShot(true); - QObject::connect(timer, &QTimer::timeout, [combo](){ + + QObject::connect(timer, &QTimer::timeout, [=](){ populateCommandGroups(combo); - combo->activated(combo->currentIndex()); + populateCommandList(commandTreeWidget, separatorItem, combo); }); + QObject::connect(ShortcutManager::instance(), &ShortcutManager::shortcutChanged, + [timer]() { timer->start(100); }); + + QObject::connect(combo, QOverload::of(&QComboBox::activated), + [timer]() { timer->start(100); }); + return Application::Instance->commandManager().signalChanged.connect([timer](){ timer->start(100); }); @@ -239,6 +253,7 @@ void DlgCustomKeyboardImp::initPriorityList(QTreeWidget *priorityList, boost::signals2::connection DlgCustomKeyboardImp::initCommandWidgets(QTreeWidget *commandTreeWidget, + QTreeWidgetItem *separatorItem, QComboBox *comboGroups, QLineEdit *editCommand, QTreeWidget *priorityList, @@ -248,7 +263,7 @@ DlgCustomKeyboardImp::initCommandWidgets(QTreeWidget *commandTreeWidget, AccelLineEdit *currentShortcut) { initCommandCompleter(editCommand, comboGroups, commandTreeWidget); - auto conn = initCommandList(commandTreeWidget, comboGroups); + auto conn = initCommandList(commandTreeWidget, separatorItem, comboGroups); if (priorityList && buttonUp && buttonDown) { initPriorityList(priorityList, buttonUp, buttonDown); diff --git a/src/Gui/DlgKeyboardImp.h b/src/Gui/DlgKeyboardImp.h index f4b94ec0fa04..3605f908ba3e 100644 --- a/src/Gui/DlgKeyboardImp.h +++ b/src/Gui/DlgKeyboardImp.h @@ -61,6 +61,7 @@ class DlgCustomKeyboardImp : public CustomizeActionPage /** Public helper function for handling command widgets * * @param commandTreeWidget: a tree widget listing commands + * @param separatorItem: optional separator item * @param comboGroups: a combo box widget for choosing categories of commands * @param editCommand: a line edit for searching command with auto complete * @param priroityList: a tree widget listing commands with the same shortcut in order of priority @@ -74,6 +75,7 @@ class DlgCustomKeyboardImp : public CustomizeActionPage */ static boost::signals2::connection initCommandWidgets(QTreeWidget *commandTreeWidget, + QTreeWidgetItem *separatorItem, QComboBox *comboGroups, QLineEdit *editCommand, QTreeWidget *priorityList = nullptr, @@ -89,9 +91,10 @@ class DlgCustomKeyboardImp : public CustomizeActionPage */ //@{ static void initCommandCompleter(QLineEdit *, QComboBox *combo, QTreeWidget *treeWidget); - static boost::signals2::connection initCommandList(QTreeWidget *, QComboBox *combo); + static boost::signals2::connection initCommandList(QTreeWidget *, QTreeWidgetItem *, QComboBox *combo); static void initPriorityList(QTreeWidget *, QAbstractButton *buttonUp, QAbstractButton *buttonDown); static void populateCommandGroups(QComboBox *); + static void populateCommandList(QTreeWidget *, QTreeWidgetItem *, QComboBox *); static void populatePriorityList(QTreeWidget *priorityList, AccelLineEdit *editor, AccelLineEdit *current); diff --git a/src/Gui/DlgToolbarsImp.cpp b/src/Gui/DlgToolbarsImp.cpp index 0cea0e155f16..021761ca5eb0 100644 --- a/src/Gui/DlgToolbarsImp.cpp +++ b/src/Gui/DlgToolbarsImp.cpp @@ -66,11 +66,16 @@ DlgCustomToolbars::DlgCustomToolbars(DlgCustomToolbars::Type t, QWidget* parent) ui->moveActionDownButton->setIcon(BitmapFactory().iconFromTheme("button_down")); ui->moveActionUpButton->setIcon(BitmapFactory().iconFromTheme("button_up")); + auto sepItem = new QTreeWidgetItem; + sepItem->setText(1, tr("")); + sepItem->setData(1, Qt::UserRole, QByteArray("Separator")); + sepItem->setSizeHint(0, QSize(32, 32)); + conn = DlgCustomKeyboardImp::initCommandWidgets(ui->commandTreeWidget, + sepItem, ui->categoryBox, ui->editCommand); - // fills the combo box with all available workbenches QStringList workbenches = Application::Instance->workbenches(); workbenches.sort(); @@ -95,7 +100,6 @@ DlgCustomToolbars::DlgCustomToolbars(DlgCustomToolbars::Type t, QWidget* parent) ui->toolbarTreeWidget->setHeaderLabels(labels); ui->toolbarTreeWidget->header()->hide(); - on_categoryBox_activated(ui->categoryBox->currentIndex()); Workbench* w = WorkbenchManager::instance()->active(); if (w) { QString name = QString::fromLatin1(w->name().c_str()); @@ -147,13 +151,8 @@ void DlgCustomToolbars::hideEvent(QHideEvent * event) CustomizeActionPage::hideEvent(event); } -void DlgCustomToolbars::on_categoryBox_activated(int) +void DlgCustomToolbars::onActivateCategoryBox() { - QTreeWidgetItem* sepitem = new QTreeWidgetItem; - sepitem->setText(1, tr("")); - sepitem->setData(1, Qt::UserRole, QByteArray("Separator")); - sepitem->setSizeHint(0, QSize(32, 32)); - ui->commandTreeWidget->insertTopLevelItem(0, sepitem); } void DlgCustomToolbars::on_workbenchBox_activated(int index) @@ -504,13 +503,14 @@ void DlgCustomToolbars::onModifyMacroAction(const QByteArray& macro) QTreeWidgetItem* item = toplevel->child(j); QByteArray command = item->data(0, Qt::UserRole).toByteArray(); if (command == macro) { - item->setText(0, QString::fromUtf8(pCmd->getMenuText())); + item->setText(0, Action::commandMenuText(pCmd)); + item->setToolTip(0, Action::commandToolTip(pCmd)); if (pCmd->getPixmap()) item->setIcon(0, BitmapFactory().iconFromTheme(pCmd->getPixmap())); } } } - on_categoryBox_activated(ui->categoryBox->currentIndex()); + ui->categoryBox->activated(ui->categoryBox->currentIndex()); } } @@ -529,7 +529,7 @@ void DlgCustomToolbars::changeEvent(QEvent *e) ui->categoryBox->setItemText(i, text); } } - on_categoryBox_activated(ui->categoryBox->currentIndex()); + ui->categoryBox->activated(ui->categoryBox->currentIndex()); } else if (e->type() == QEvent::StyleChange) ui->categoryBox->activated(ui->categoryBox->currentIndex()); diff --git a/src/Gui/DlgToolbarsImp.h b/src/Gui/DlgToolbarsImp.h index 48da979e6540..8d6aa8ca8bd6 100644 --- a/src/Gui/DlgToolbarsImp.h +++ b/src/Gui/DlgToolbarsImp.h @@ -28,6 +28,8 @@ #include "PropertyPage.h" #include +class QTreeWidgetItem; + namespace Gui { namespace Dialog { class Ui_DlgCustomToolbars; @@ -51,7 +53,6 @@ class DlgCustomToolbars : public CustomizeActionPage virtual ~DlgCustomToolbars(); protected Q_SLOTS: - void on_categoryBox_activated(int index); void on_workbenchBox_activated(int index); void on_moveActionRightButton_clicked(); void on_moveActionLeftButton_clicked(); @@ -74,6 +75,7 @@ protected Q_SLOTS: virtual void removeCustomCommand(const QString&, const QByteArray&); virtual void moveUpCustomCommand(const QString&, const QByteArray&); virtual void moveDownCustomCommand(const QString&, const QByteArray&); + void onActivateCategoryBox(); private: void importCustomToolbars(const QByteArray&); From e89453c1d4ed24d84af3c0ab12d281e11e44a222 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sat, 12 Mar 2022 14:41:35 +0800 Subject: [PATCH 13/19] Gui: change Command::getRevision() to const --- src/Gui/Command.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gui/Command.h b/src/Gui/Command.h index 78e308e3e310..f9235e7d1250 100644 --- a/src/Gui/Command.h +++ b/src/Gui/Command.h @@ -882,7 +882,7 @@ class GuiExport CommandManager void updateCommands(const char* sContext, int mode); /// Return a revision number to check for addition or removal of any command - int getRevision() { return _revision; } + int getRevision() const { return _revision; } /// Signal on any addition or removal of command boost::signals2::signal signalChanged; From 866873a8a4c14cdf7b5582ecd8fe3a76d72d8ff8 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sat, 12 Mar 2022 15:06:50 +0800 Subject: [PATCH 14/19] Gui: remove command description label in Customize -> Keyboard Because long description may cause undesired dialog layout changes. The description is available through tool tip of the command tree widget. --- src/Gui/DlgKeyboard.ui | 35 +---------------------------------- src/Gui/DlgKeyboardImp.cpp | 2 -- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/src/Gui/DlgKeyboard.ui b/src/Gui/DlgKeyboard.ui index e8b484cda9c3..d50cff090f82 100644 --- a/src/Gui/DlgKeyboard.ui +++ b/src/Gui/DlgKeyboard.ui @@ -13,7 +13,7 @@ Keyboard - + @@ -308,39 +308,6 @@ same time. The one with the highest prioirty will be triggered. - - - - QLayout::SetDefaultConstraint - - - - - - 0 - 0 - - - - Description: - - - - - - - - 0 - 0 - - - - - - - - - diff --git a/src/Gui/DlgKeyboardImp.cpp b/src/Gui/DlgKeyboardImp.cpp index 47e094148b04..8e89236e3c9e 100644 --- a/src/Gui/DlgKeyboardImp.cpp +++ b/src/Gui/DlgKeyboardImp.cpp @@ -391,8 +391,6 @@ void DlgCustomKeyboardImp::on_commandTreeWidget_currentItemChanged(QTreeWidgetIt ui->buttonAssign->setEnabled(!ui->editShortcut->text().isEmpty() && (ks != ks3)); ui->buttonReset->setEnabled((ks != ks2)); } - - ui->textLabelDescription->setText(Action::commandToolTip(cmd, false)); } /** Shows all commands of this category */ From 9f40b819cb801944bce709fcecf7d23559792c4e Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sat, 12 Mar 2022 15:33:39 +0800 Subject: [PATCH 15/19] Gui: fix Customize -> Keyboard shortcut priroity list Add API Command::initAction() to force create action for all commands with shortcut in order to register with ShortcutManager to obtain a complete list of actions with the same shortcut. --- src/Gui/Command.cpp | 17 ++++++----------- src/Gui/Command.h | 4 ++-- src/Gui/DlgKeyboardImp.cpp | 11 +++++++++++ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Gui/Command.cpp b/src/Gui/Command.cpp index afd247b14cc8..62abb14c4c3a 100644 --- a/src/Gui/Command.cpp +++ b/src/Gui/Command.cpp @@ -254,7 +254,7 @@ bool Command::isViewOfType(Base::Type t) const return false; } -void Command::addTo(QWidget *pcWidget) +void Command::initAction() { if (!_pcAction) { _pcAction = createAction(); @@ -266,7 +266,11 @@ void Command::addTo(QWidget *pcWidget) setShortcut(ShortcutManager::instance()->getShortcut(getName(), getAccel())); testActive(); } +} +void Command::addTo(QWidget *pcWidget) +{ + initAction(); _pcAction->addTo(pcWidget); } @@ -278,16 +282,7 @@ void Command::addToGroup(ActionGroup* group, bool checkable) void Command::addToGroup(ActionGroup* group) { - if (!_pcAction) { - _pcAction = createAction(); -#ifdef FC_DEBUG - // Accelerator conflict can now be dynamically resolved in ShortcutManager - // - // printConflictingAccelerators(); -#endif - setShortcut(ShortcutManager::instance()->getShortcut(getName(), getAccel())); - testActive(); - } + initAction(); group->addAction(_pcAction->findChild()); } diff --git a/src/Gui/Command.h b/src/Gui/Command.h index f9235e7d1250..6ef07f3fcb83 100644 --- a/src/Gui/Command.h +++ b/src/Gui/Command.h @@ -345,8 +345,6 @@ class GuiExport Command : public CommandBase void testActive(void); /// Enables or disables the command void setEnabled(bool); - /// (Re)Create the text for the tooltip (for example, when the shortcut is changed) - void recreateTooltip(const char* context, Action*); /// Command trigger source enum TriggerSource { /// No external trigger, e.g. invoked through Python @@ -370,6 +368,8 @@ class GuiExport Command : public CommandBase void addTo(QWidget *); void addToGroup(ActionGroup *, bool checkable); void addToGroup(ActionGroup *); + /// Create the action if not exist + void initAction(); //@} diff --git a/src/Gui/DlgKeyboardImp.cpp b/src/Gui/DlgKeyboardImp.cpp index 8e89236e3c9e..b83d9994f2d0 100644 --- a/src/Gui/DlgKeyboardImp.cpp +++ b/src/Gui/DlgKeyboardImp.cpp @@ -78,6 +78,17 @@ DlgCustomKeyboardImp::DlgCustomKeyboardImp( QWidget* parent ) { ui->setupUi(this); + // Force create actions for all commands with shortcut to register with ShortcutManager + for (auto cmd : Application::Instance->commandManager().getAllCommands()) { + if (cmd->getShortcut().size()) + cmd->initAction(); + } + QObject::connect(ShortcutManager::instance(), &ShortcutManager::shortcutChanged, this, + [](const char *cmdName) { + if (auto cmd = Application::Instance->commandManager().getCommandByName(cmdName)) + cmd->initAction(); + }); + conn = initCommandWidgets(ui->commandTreeWidget, nullptr, ui->categoryBox, From 755cd93b9f5ddd21efb5cfc8d8d372e79c3c1089 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 14 Mar 2022 10:24:57 +0800 Subject: [PATCH 16/19] Gui: fix command completer selection on item activate --- src/Gui/DlgKeyboardImp.cpp | 16 ++++++++++------ src/Gui/DlgKeyboardImp.h | 5 ++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Gui/DlgKeyboardImp.cpp b/src/Gui/DlgKeyboardImp.cpp index b83d9994f2d0..967cfcdebe9d 100644 --- a/src/Gui/DlgKeyboardImp.cpp +++ b/src/Gui/DlgKeyboardImp.cpp @@ -111,13 +111,16 @@ DlgCustomKeyboardImp::~DlgCustomKeyboardImp() { } -void DlgCustomKeyboardImp::initCommandCompleter(QLineEdit *edit, QComboBox *combo, QTreeWidget *commandTreeWidget) +void DlgCustomKeyboardImp::initCommandCompleter(QLineEdit *edit, + QComboBox *combo, + QTreeWidget *commandTreeWidget, + QTreeWidgetItem *separatorItem) { edit->setPlaceholderText(tr("Type to search...")); auto completer = new CommandCompleter(edit, edit); QObject::connect(completer, &CommandCompleter::commandActivated, - [combo, commandTreeWidget](const QByteArray &name) { + [=](const QByteArray &name) { CommandManager & cCmdMgr = Application::Instance->commandManager(); Command *cmd = cCmdMgr.getCommandByName(name.constData()); if (!cmd) @@ -128,8 +131,9 @@ void DlgCustomKeyboardImp::initCommandCompleter(QLineEdit *edit, QComboBox *comb if (index < 0) return; if (index != combo->currentIndex()) { + QSignalBlocker blocker(combo); combo->setCurrentIndex(index); - combo->activated(index); + populateCommandList(commandTreeWidget, separatorItem, combo); } for (int i=0 ; itopLevelItemCount(); ++i) { QTreeWidgetItem *item = commandTreeWidget->topLevelItem(i); @@ -207,10 +211,10 @@ DlgCustomKeyboardImp::initCommandList(QTreeWidget *commandTreeWidget, }); QObject::connect(ShortcutManager::instance(), &ShortcutManager::shortcutChanged, - [timer]() { timer->start(100); }); + timer, [timer]() { timer->start(100); }); QObject::connect(combo, QOverload::of(&QComboBox::activated), - [timer]() { timer->start(100); }); + timer, [timer]() { timer->start(100); }); return Application::Instance->commandManager().signalChanged.connect([timer](){ timer->start(100); @@ -273,7 +277,7 @@ DlgCustomKeyboardImp::initCommandWidgets(QTreeWidget *commandTreeWidget, AccelLineEdit *editShortcut, AccelLineEdit *currentShortcut) { - initCommandCompleter(editCommand, comboGroups, commandTreeWidget); + initCommandCompleter(editCommand, comboGroups, commandTreeWidget, separatorItem); auto conn = initCommandList(commandTreeWidget, separatorItem, comboGroups); if (priorityList && buttonUp && buttonDown) { diff --git a/src/Gui/DlgKeyboardImp.h b/src/Gui/DlgKeyboardImp.h index 3605f908ba3e..e797881c3f95 100644 --- a/src/Gui/DlgKeyboardImp.h +++ b/src/Gui/DlgKeyboardImp.h @@ -90,7 +90,10 @@ class DlgCustomKeyboardImp : public CustomizeActionPage /** @name Internal helper function for handling command list widgets */ //@{ - static void initCommandCompleter(QLineEdit *, QComboBox *combo, QTreeWidget *treeWidget); + static void initCommandCompleter(QLineEdit *, + QComboBox *combo, + QTreeWidget *treeWidget, + QTreeWidgetItem *separatorItem); static boost::signals2::connection initCommandList(QTreeWidget *, QTreeWidgetItem *, QComboBox *combo); static void initPriorityList(QTreeWidget *, QAbstractButton *buttonUp, QAbstractButton *buttonDown); static void populateCommandGroups(QComboBox *); From 1c3719c2e8200ac9d3c12531a6e282434ee50d79 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 14 Mar 2022 16:03:49 +0800 Subject: [PATCH 17/19] Gu: fix readonly AccelLineEdit --- src/Gui/Widgets.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Gui/Widgets.cpp b/src/Gui/Widgets.cpp index 89a2720ecfd2..5e62a56f93bd 100644 --- a/src/Gui/Widgets.cpp +++ b/src/Gui/Widgets.cpp @@ -389,6 +389,11 @@ bool AccelLineEdit::isNone() const */ void AccelLineEdit::keyPressEvent (QKeyEvent * e) { + if (isReadOnly()) { + QLineEdit::keyPressEvent(e); + return; + } + QString txtLine = text(); int key = e->key(); From ffdb938d492203d85d990bdb43ef1f0876f1390f Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 14 Mar 2022 16:28:18 +0800 Subject: [PATCH 18/19] Gui: fix shortcut context handling in ShortcutManager Related #6097 Qt ignores shortcut of actions in invisible toolbar, but not for actions in a hidden menu action of menu bar, which is likely a Qt bug. The desired behavior should be that of toolbar actions, so that actions belong to different workbenches can have the same shortcut without conflict. This commit works around this inconsistency by ensuring only the active actions are added in menu bar. In addition, all active actions will be added to a zero sized child widget of the main window, which ensures the shortcuts of these actions being active regardless whether the action is in toolbar or menu bar, visible or not. --- src/Base/Console.cpp | 2 ++ src/Gui/MenuManager.cpp | 26 +++++++++++++++++++++++ src/Gui/ShortcutManager.cpp | 42 ++++++++++++++++++------------------- src/Gui/ToolBarManager.cpp | 13 +++++++++++- src/Gui/ToolBarManager.h | 2 +- 5 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/Base/Console.cpp b/src/Base/Console.cpp index c47c07b7ebf1..ce46ecb94f3e 100644 --- a/src/Base/Console.cpp +++ b/src/Base/Console.cpp @@ -32,6 +32,7 @@ # include #endif +#include "Interpreter.h" #include "Console.h" #include "Exception.h" #include "frameobject.h" @@ -871,6 +872,7 @@ std::stringstream &LogLevel::prefix(std::stringstream &str, const char *src, int } if (print_tag) str << '<' << tag << "> "; if (print_src==2) { + Base::PyGILStateLocker lock; PyFrameObject* frame = PyEval_GetFrame(); if (frame) { line = PyFrame_GetLineNumber(frame); diff --git a/src/Gui/MenuManager.cpp b/src/Gui/MenuManager.cpp index f84058234ad2..1f983caa6544 100644 --- a/src/Gui/MenuManager.cpp +++ b/src/Gui/MenuManager.cpp @@ -209,6 +209,7 @@ void MenuManager::setup(MenuItem* menuItems) const QMenuBar* menuBar = getMainWindow()->menuBar(); +#if 0 #if defined(FC_OS_MACOSX) && QT_VERSION >= 0x050900 // Unknown Qt macOS bug observed with Qt >= 5.9.4 causes random crashes when viewing reused top level menus. menuBar->clear(); @@ -222,6 +223,31 @@ void MenuManager::setup(MenuItem* menuItems) const ("User parameter:BaseApp/Preferences/MainWindow")->GetBool("ClearMenuBar",false)) { menuBar->clear(); } +#else + // In addition to the reason described in the above comments, there is + // another more subtle one that's making clearing menu bar a necessity for + // all platforms. + // + // By right, it should be fine for more than one command action having the + // same shortcut but in different workbench. It should not require manual + // conflict resolving in this case, as the action in an inactive workbench + // is expected to be inactive as well, or else user may experience + // seemingly random shortcut miss firing based on the order he/she + // switches workbenches. In fact, this may be considered as an otherwise + // difficult to implement feature of context aware shortcut, where a + // specific shortcut can activate different actions under different + // workbenches. + // + // This works as expected for action adding to a toolbar. As Qt will ignore + // actions inside an invisible toolbar. However, Qt refuse to do the same + // for actions in a hidden menu action of a menu bar. This is very likely a + // Qt bug, as the behavior does not seem to conform to Qt's documentation + // of Qt::ShortcutContext. + // + // Clearing the menu bar, and recreate it every time when switching + // workbench with only the active actions can solve this problem. + menuBar->clear(); +#endif QList items = menuItems->getItems(); QList actions = menuBar->actions(); diff --git a/src/Gui/ShortcutManager.cpp b/src/Gui/ShortcutManager.cpp index 510570b38018..e56ff25b011f 100644 --- a/src/Gui/ShortcutManager.cpp +++ b/src/Gui/ShortcutManager.cpp @@ -203,37 +203,35 @@ bool ShortcutManager::checkShortcut(QObject *o, const QKeySequence &key) if (iter == index.end()) return false; - auto it = iter; - // skip to the first not exact matched key - for (; it != index.end() && key == it->key.shortcut; ++it); + // disable and enqueue the action in order to try other alternativeslll + action->setEnabled(false); + pendingActions.emplace_back(action, key.count(), 0); // check for potential partial match, i.e. longer key sequences bool flush = true; - for (; it != index.end(); ++it) { + bool found = false; + for (auto it = iter; it != index.end(); ++it) { if (key.matches(it->key.shortcut) == QKeySequence::NoMatch) break; - if (it->action && it->action->isEnabled()) { + if (action == it->action) { + // There maybe more than one action with the exact same shortcut. + // However, we only disable and enqueue the triggered action. + // Because, QAction::isEnabled() does not check if the action is + // active under its current ShortcutContext. We would have to check + // its parent widgets visibility which may or may not be reliable. + // Instead, we rely on QEvent::Shortcut to be sure to enqueue only + // active shortcuts. We'll fake the current key sequence below, + // which will trigger all possible matches one by one. + pendingActions.back().priority = getPriority(it->key.name); + found = true; + } + else if (it->action && it->action->isEnabled()) { flush = false; - break; + if (found) + break; } } - int count = 0; - for (it = iter; it != index.end() && key == it->key.shortcut; ++it) { - if (it->action && it->action->isEnabled()) { - if (!flush) { - // temporary disable the action so that we can try potential - // match with further keystrokes. - it->action->setEnabled(false); - } - pendingActions.emplace_back(it->action, key.count(), getPriority(it->key.name)); - ++count; - } - } - if (!count) { - // action not found in the map, shouldn't happen! - pendingActions.emplace_back(action, key.count(), 0); - } if (flush) { // We'll flush now because there is no potential match with further // keystrokes, so no need to wait for timer. diff --git a/src/Gui/ToolBarManager.cpp b/src/Gui/ToolBarManager.cpp index d4bbba78c749..44e9238bf49c 100644 --- a/src/Gui/ToolBarManager.cpp +++ b/src/Gui/ToolBarManager.cpp @@ -27,6 +27,7 @@ # include # include # include +# include #endif #include "ToolBarManager.h" @@ -56,7 +57,7 @@ void ToolBarItem::setCommand(const std::string& name) _name = name; } -std::string ToolBarItem::command() const +const std::string & ToolBarItem::command() const { return _name; } @@ -178,6 +179,14 @@ void ToolBarManager::setup(ToolBarItem* toolBarItems) if (!toolBarItems) return; // empty menu bar + static QPointer _ActionWidget; + if (!_ActionWidget) + _ActionWidget = new QWidget(getMainWindow()); + else { + for (auto action : _ActionWidget->actions()) + _ActionWidget->removeAction(action); + } + saveState(); this->toolbarNames.clear(); @@ -222,6 +231,8 @@ void ToolBarManager::setup(ToolBarItem* toolBarItems) // setup the toolbar setup(*it, toolbar); + for (auto action : toolbar->actions()) + _ActionWidget->addAction(action); // try to add some breaks to avoid to have all toolbars in one line if (toolbar_added) { diff --git a/src/Gui/ToolBarManager.h b/src/Gui/ToolBarManager.h index ed92657642f3..c67aa20b66fb 100644 --- a/src/Gui/ToolBarManager.h +++ b/src/Gui/ToolBarManager.h @@ -40,7 +40,7 @@ class GuiExport ToolBarItem ~ToolBarItem(); void setCommand(const std::string&); - std::string command() const; + const std::string &command() const; bool hasItems() const; ToolBarItem* findItem(const std::string&); From 1a8d6fef3d554a6f33b86d7793f9e3ec8cd7599b Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 14 Mar 2022 18:31:03 +0800 Subject: [PATCH 19/19] Gui: do not remove ending puncuation when clean action title --- src/Gui/Action.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Gui/Action.cpp b/src/Gui/Action.cpp index 233c45b4a612..44cf134e0e6c 100644 --- a/src/Gui/Action.cpp +++ b/src/Gui/Action.cpp @@ -218,9 +218,13 @@ QString Action::cleanTitle(const QString & text) // Deal with QAction title mnemonic static QRegularExpression re(QStringLiteral("&(.)")); title.replace(re, QStringLiteral("\\1")); + + // Probably not a good idea to trim ending punctuation +#if 0 // Trim line ending punctuation static QRegularExpression rePunct(QStringLiteral("[[:punct:]]+$")); title.replace(rePunct, QString()); +#endif return title; }