Skip to content

Commit 2f5375d

Browse files
Fix accelerator keybindings for the tab menu
Some items in this menu had accelerator keys (shortcuts) defined. Normally, this automatically takes care of registering such keybindings when the menu item is added to a menu. However, this requires adding the item (indirectly) to a menubar, which is again added to a window. Since the tab menu is just a separate popup menu, this did not work. It seems an attempt was made to fix this by adding the popup menu to the EditorHeader JComponent, which indeed made the keybindings work. However, this is a hack at best, and as soon as the popup menu was opened, it would be moved to another container and again detached when the menu was closed, breaking the keyboard shortcuts again (re-adding the popup menu turned out not to work either, then the menu would actually be drawn on top of the tab bar). To properly fix this, keybindings for the menu items are added to the EditorHeader itself. By looking at the existing accelerator keystroke property of the actions, there is no need to duplicate the keystrokes themselves, and the displayed value will always match the actually bound value. To simplify this, some methods are added to the Keys helper class, which will likely come in handy in other places as well.
1 parent 9573312 commit 2f5375d

File tree

2 files changed

+94
-1
lines changed

2 files changed

+94
-1
lines changed

app/src/processing/app/EditorHeader.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ public class Actions {
102102
public final Action nextTab = new SimpleAction(tr("Next Tab"),
103103
Keys.ctrlAlt(KeyEvent.VK_RIGHT),
104104
() -> editor.sketch.handleNextCode());
105+
106+
Actions() {
107+
// Explicitly bind keybindings for the actions with accelerators above
108+
// Normally, this happens automatically for any actions bound to menu
109+
// items, but only for menus attached to a window, not for popup menus.
110+
Keys.bind(EditorHeader.this, newTab);
111+
Keys.bind(EditorHeader.this, prevTab);
112+
Keys.bind(EditorHeader.this, nextTab);
113+
}
105114
}
106115
public Actions actions = new Actions();
107116

@@ -269,7 +278,6 @@ public void rebuildMenu() {
269278
menu = new JMenu();
270279
MenuScroller.setScrollerFor(menu);
271280
popup = menu.getPopupMenu();
272-
add(popup);
273281
popup.setLightWeightPopupEnabled(true);
274282
}
275283
JMenuItem item;

app/src/processing/app/helpers/Keys.java

+85
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,91 @@
4242
*/
4343
public class Keys {
4444

45+
/**
46+
* Register a keybinding in the given components WHEN_IN_FOCUSED_WINDOW input
47+
* map, runing the given action when the action's accelerator key is pressed.
48+
*
49+
* Note that this is typically automatically handled when the action is
50+
* assigned to a JMenuItem, but it can still be needed for other actions, or
51+
* actions mapped to JMenuItems in a popup menu that is not added to any
52+
* window normally (and thus does not fire when the popup is closed).
53+
*
54+
* When the action is disabled, the keybinding is unregistered, and when it is
55+
* enabled, it is registered again.
56+
*/
57+
public static void bind(final JComponent component, final Action action) {
58+
bind(component, action,
59+
(KeyStroke) action.getValue(Action.ACCELERATOR_KEY));
60+
}
61+
62+
/**
63+
* Register a keybinding, running the given action when the given keystroke is
64+
* pressed when the given component is in the focused window.
65+
*
66+
* This is typically used to bind an additional keystroke to a menu item, in
67+
* addition to the primary accelerator key.
68+
*
69+
* When the action is disabled, the keybinding is unregistered, and when it is
70+
* enabled, it is registered again.
71+
*/
72+
public static void bind(final JComponent component, final Action action,
73+
KeyStroke keystroke) {
74+
bind(component, action, keystroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
75+
}
76+
77+
/**
78+
* Register a keybinding to be handled in given condition, running the given
79+
* action when the given keystroke is pressed.
80+
*
81+
* When the action is disabled, the keybinding is unregistered, and when it is
82+
* enabled, it is registered again.
83+
*
84+
* @param component
85+
* The component to register the keybinding on.
86+
* @param action
87+
* The action to run when the keystroke is pressed
88+
* @param action
89+
* The keystroke to bind
90+
* @param condition
91+
* The condition under which to run the keystroke. Should be one of
92+
* JComponent.WHEN_FOCUSED,
93+
* JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT or
94+
* JComponent.WHEN_IN_FOCUSED_WINDOW.
95+
*/
96+
public static void bind(final JComponent component, final Action action,
97+
KeyStroke keystroke, int condition) {
98+
// The input map maps keystrokes to arbitrary objects (originally strings
99+
// that described the option, we just use the Action object itself).
100+
if (action.isEnabled())
101+
enableBind(component, action, keystroke, condition);
102+
103+
// The action map maps the arbitrary option to an Action to execute. These
104+
// be kept in the component even when the action is disabled.
105+
component.getActionMap().put(action, action);
106+
107+
// Enable and disable the binding when the action is enabled / disabled.
108+
action.addPropertyChangeListener((PropertyChangeEvent e) -> {
109+
if (e.getPropertyName().equals("enabled")) {
110+
if (e.getNewValue().equals(Boolean.TRUE))
111+
enableBind(component, action, keystroke, condition);
112+
else
113+
disableBind(component, action, keystroke, condition);
114+
}
115+
});
116+
}
117+
118+
private static void enableBind(final JComponent component,
119+
final Action action, final KeyStroke keystroke,
120+
int condition) {
121+
component.getInputMap(condition).put(keystroke, action);
122+
}
123+
124+
private static void disableBind(final JComponent component,
125+
final Action action,
126+
final KeyStroke keystroke, int condition) {
127+
component.getInputMap(condition).put(keystroke, action);
128+
}
129+
45130
private static final int CTRL = Toolkit.getDefaultToolkit()
46131
.getMenuShortcutKeyMask();
47132

0 commit comments

Comments
 (0)