New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Gui: introduce ShortcutManager for shortcut management and conflict resolving #6506
Conversation
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).
By reusing code in DlgCustomKeyboardImp
This looks awesome! |
src/Gui/Action.cpp
Outdated
Action *act) | ||
{ | ||
QString text = title; | ||
text.remove(QLatin1Char('&'));; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will remove even "escaped" ampersands (&&).
src/Gui/Action.cpp
Outdated
public: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public: | |
src/Gui/Action.cpp
Outdated
case Qt::DisplayRole: | ||
case Qt::EditRole: | ||
if (info.text.isEmpty()) { | ||
#if QT_VERSION>=QT_VERSION_CHECK(5,2,0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't support older versions of Qt anymore, so this should be unnecessary.
src/Gui/Action.cpp
Outdated
} | ||
return info.tooltip; | ||
|
||
case Qt::UserRole: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's usually clearer to make a constant for the Qt::UserRole
with a more descriptive name.
src/Gui/Action.cpp
Outdated
#if QT_VERSION>=QT_VERSION_CHECK(5,2,0) | ||
this->setFilterMode(Qt::MatchContains); | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#if QT_VERSION>=QT_VERSION_CHECK(5,2,0) | |
this->setFilterMode(Qt::MatchContains); | |
#endif | |
this->setFilterMode(Qt::MatchContains); |
src/Gui/Action.cpp
Outdated
connect(lineedit, SIGNAL(textEdited(QString)), this, SLOT(onTextChanged(QString))); | ||
connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(onCommandActivated(QModelIndex))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer new-style connections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noted. I do use new connection now. These code were added before we fully switch to Qt5.
src/Gui/Action.cpp
Outdated
popup()->hide(); | ||
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you refactor to avoid repeating yourself here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is subtle difference in handling of these two conditions. The ESC is supposed to clear the line edit text regardless of popup visibility. And if the edit is empty and popup is hidden, the ESC key shouldn't be filtered.
src/Gui/Command.cpp
Outdated
#ifdef FC_DEBUG | ||
// Accelerator conflict can now be dynamically resolved in ShortcutManager | ||
// | ||
// printConflictingAccelerators(); | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#ifdef FC_DEBUG | |
// Accelerator conflict can now be dynamically resolved in ShortcutManager | |
// | |
// printConflictingAccelerators(); | |
#endif |
src/Gui/CommandView.cpp
Outdated
virtual void activated(int iMsg) override; | ||
virtual bool isActive(void) override; | ||
virtual Action * createAction(void) override; | ||
virtual void languageChange() override; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; | |
priorityList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); | ||
priorityList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); | ||
|
||
auto updatePriorityList = [priorityList](bool up) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a boolean here feels pretty kludgy. I can live with it here since it's in such an isolated use-case, but I think it would be better to use a more explicit UP/DOWN specifier.
auto timer = new QTimer(priorityList); | ||
timer->setSingleShot(true); | ||
if (currentShortcut) | ||
QObject::connect(currentShortcut, &QLineEdit::textChanged, timer, [timer](){timer->start(200);}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How did you arrive at the 200ms for these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to prevent refreshing priority list too often, since it gets triggered in a few signals.
// Qt's shortcut state machine favors shortest match (which is ridiculous, | ||
// unless I'm mistaken?). We'll do longest match. We've disabled all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if you're mistaken, but I agree with you that longest match should be the correct behavior.
return; | ||
// Keep the same top priority of the given action, and adjust the rest. Can | ||
// go negative if necessary | ||
int current = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If negative priorities are allowed, does it make sense to initialize current
to zero?
int current = 0; | |
int current = std::numeric_limits<int>::lowest(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose it can work. It's just that the priority needs to rise from some base level. If the base is the lowest, then we'd have bunch of huge negative integers in the parameters, which would be confusing.
The reason the priority is allow to go negative is because I try to preserve the top priority (among actions with the same shortcuts) while reordering. This is a partial solution to a corner case where the user may have set priority in the past for some action in a workbench that hasn't been loaded yet. Keep the top priority unchanged can provide some degree of stability in this case.
src/Gui/ShortcutManager.cpp
Outdated
} | ||
} | ||
} | ||
timer.stop(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this would make more sense at the top of the function, rather than the bottom.
src/Gui/ShortcutManager.h
Outdated
|
||
ActionData(QAction *action, const char *name = "") | ||
: key(action->shortcut(), name) | ||
, pointer(reinterpret_cast<int64_t>(action)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type name mismatch, you are using intptr_t
above.
src/Gui/Workbench.cpp
Outdated
@@ -311,6 +311,8 @@ void Workbench::setupCustomToolbars(ToolBarItem* root, const Base::Reference<Par | |||
|
|||
void Workbench::setupCustomShortcuts() const | |||
{ | |||
// Now managed by ShortcutManager | |||
#if 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any reason to leave more than just the comment here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall I really like the feature, and the code looks good. Just a few comments:
- The user interface for setting the multi-key delay makes it look like we are storing a delay per-keystroke. I don't have a great suggestion for improving it, but I don't know if we can include it as-is.
- I think there needs to be a timeout, after which an in-progress keystroke sequence is cancelled even if the user presses no more keys.
- I'm not familiar enough with the Boost multi-index containers to really evaluate, I assume the reinterpret casts are related to the usage there? Obviously those set off red flags and warning sirens when reviewing code.
The timer in ShortcutManager is used for this purpose. In fact, the
I intend to use |
Because long description may cause undesired dialog layout changes. The description is available through tool tip of the command tree widget.
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.
@wwmayer New commits added for suggested changes and bug fix. |
Related FreeCAD#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.
fdb7e8b
to
1a8d6fe
Compare
We will have to postpone this to 0.21 to make 0.20 ready. @realthunder maybe you have a fix or could review pending PRs for the 0.20 milestone?: https://github.com/FreeCAD/FreeCAD/milestone/2 |
This would be an incredible upgrade to the UI/UX. Hopefully this gets priority love |
@realthunder if you are bored with working on the TNP mega-merge and want a break 😆, my impression of this PR is that it's ready for a final review once its conflicts are resolved. |
Great. I'll work on it soon. |
@chennes I guess we all still agree in merging this PR? If yes, I will start to rebase it and fix the merge conflicts because this PR offers some functions that are needed in another PR. |
Yes, I think this is good functionality--go for it! |
Thanks, I will start with it now... |
#7771 replaces this PR. |
Forum post https://forum.freecadweb.org/viewtopic.php?f=17&t=66821
Add new class ShortcutManager to unify shortcut management. Implement through global event filter to support longest key sequence match with user defined delay (configurable through 'Customize -> Keyboard -> Key sequence delay'). Also supports user defined priority to resolve shortcut conflict through 'Customize -> Keyboard'.
Refactored 'Customize -> Keyboard' page for easier command search and better synchronization.
shortcut-manager.mp4