Skip to content

Commit f068207

Browse files
Make ctrl-tab and ctrl-shift-tab work again
In the previous commit, these bindings were moved to EditorTab and registered in a cleaner way, but this move also allows more components to hijack these keystrokes and prevent them from reaching EditorTab. This commit makes the keybindings work again, by preventing other components from handling the keys. In particular: - JSplitPane had a binding to switch between its two panes, which is now removed after creating the JSplitPane. - The default focus traversal manager in Swing uses these keys to traverse focus (in addition to the the normal tab and shift-tab keys). By removing these keys from the set of "focus traversal keys" defined for the window, this should be prevented when the focus is on any component inside the window. - JTextPane didn't respond to the previous modification of the window-default focus traversal keys, since it defines its own set (to only contain ctrl-tab and ctrl-shift-tab, but not tab and shift-tab, for undocumented reasons). To fix this, focus traversal is simply disabled on the JTextPane, since this wasn't really being used anyway. There was some code in SketchTextArea that tried to modify the focus traversal keys for just the text area, which is now removed. This code wasn't really useful, since focus traversal is disabled for the text area already. Also, the code contained a bug where it would not actually set the new set of keys for the backward focus traversal. Closes arduino#195
1 parent fc4b202 commit f068207

File tree

5 files changed

+94
-27
lines changed

5 files changed

+94
-27
lines changed

app/src/processing/app/Editor.java

+7
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.fife.ui.rtextarea.RTextScrollPane;
3939
import processing.app.debug.RunnerException;
4040
import processing.app.forms.PasswordAuthorizationDialog;
41+
import processing.app.helpers.Keys;
4142
import processing.app.helpers.OSUtils;
4243
import processing.app.helpers.PreferencesMapException;
4344
import processing.app.legacy.PApplet;
@@ -312,6 +313,12 @@ public void windowDeactivated(WindowEvent e) {
312313
// to fix ugliness.. normally macosx java 1.3 puts an
313314
// ugly white border around this object, so turn it off.
314315
splitPane.setBorder(null);
316+
// By default, the split pane binds Ctrl-Tab and Ctrl-Shift-Tab for changing
317+
// focus. Since we do not use that, but want to use these shortcuts for
318+
// switching tabs, remove the bindings from the split pane. This allows the
319+
// events to bubble up and be handled by the EditorHeader.
320+
Keys.killBinding(splitPane, Keys.ctrl(KeyEvent.VK_TAB));
321+
Keys.killBinding(splitPane, Keys.ctrlShift(KeyEvent.VK_TAB));
315322

316323
// the default size on windows is too small and kinda ugly
317324
int dividerSize = PreferencesData.getInteger("editor.divider.size");

app/src/processing/app/EditorConsole.java

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public EditorConsole() {
6363
consoleTextPane.setEditable(false);
6464
DefaultCaret caret = (DefaultCaret) consoleTextPane.getCaret();
6565
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
66+
consoleTextPane.setFocusTraversalKeysEnabled(false);
6667

6768
Color backgroundColour = Theme.getColor("console.color");
6869
consoleTextPane.setBackground(backgroundColour);

app/src/processing/app/EditorHeader.java

+21
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,27 @@ public class Actions {
118118
}
119119
public Actions actions = new Actions();
120120

121+
/**
122+
* Called whenever we, or any of our ancestors, is added to a container.
123+
*/
124+
public void addNotify() {
125+
super.addNotify();
126+
/*
127+
* Once we get added to a window, remove Ctrl-Tab and Ctrl-Shift-Tab from
128+
* the keys used for focus traversal (so our bindings for these keys will
129+
* work). All components inherit from the window eventually, so this should
130+
* work whenever the focus is inside our window. Some components (notably
131+
* JTextPane / JEditorPane) keep their own focus traversal keys, though, and
132+
* have to be treated individually (either the same as below, or by
133+
* disabling focus traversal entirely).
134+
*/
135+
Window window = SwingUtilities.getWindowAncestor(this);
136+
if (window != null) {
137+
Keys.killFocusTraversalBinding(window, Keys.ctrl(KeyEvent.VK_TAB));
138+
Keys.killFocusTraversalBinding(window, Keys.ctrlShift(KeyEvent.VK_TAB));
139+
}
140+
}
141+
121142
public EditorHeader(Editor eddie) {
122143
this.editor = eddie; // weird name for listener
123144

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

+65
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,17 @@
2929

3030
package processing.app.helpers;
3131

32+
import java.awt.AWTKeyStroke;
33+
import java.awt.Component;
34+
import java.awt.KeyboardFocusManager;
3235
import java.awt.Toolkit;
3336
import java.awt.event.InputEvent;
3437
import java.beans.PropertyChangeEvent;
38+
import java.util.HashSet;
39+
import java.util.Set;
3540

3641
import javax.swing.Action;
42+
import javax.swing.InputMap;
3743
import javax.swing.JComponent;
3844
import javax.swing.KeyStroke;
3945

@@ -115,6 +121,65 @@ public static void bind(final JComponent component, final Action action,
115121
});
116122
}
117123

124+
/**
125+
* Kill an existing binding from the given condition. If the binding is
126+
* defined on the given component, it is removed, but if it is defined through
127+
* a parent inputmap (typically shared by multiple components, so best not
128+
* touched), this adds a dummy binding for this component, that will never
129+
* match an action in the component's action map, effectively disabling the
130+
* binding.
131+
*
132+
* This method is not intended to unbind a binding created by bind(), since
133+
* such a binding would get re-enabled when the action is re-enabled.
134+
*/
135+
public static void killBinding(final JComponent component,
136+
final KeyStroke keystroke, int condition) {
137+
InputMap map = component.getInputMap(condition);
138+
// First, try removing it
139+
map.remove(keystroke);
140+
// If the binding is defined in a parent map, defining it will not work, so
141+
// instead add an override that will never appear in the action map.
142+
if (map.get(keystroke) != null)
143+
map.put(keystroke, new Object());
144+
}
145+
146+
/**
147+
* Kill an existing binding like above, but from all three conditions
148+
* (WHEN_FOCUSED, WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, WHEN_IN_FOCUSED_WINDOW).
149+
*/
150+
public static void killBinding(final JComponent component,
151+
final KeyStroke key) {
152+
killBinding(component, key, JComponent.WHEN_FOCUSED);
153+
killBinding(component, key, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
154+
killBinding(component, key, JComponent.WHEN_IN_FOCUSED_WINDOW);
155+
}
156+
157+
/**
158+
* Remove a keystroke from the keys used to shift focus in or below the given
159+
* component. This modifies all sets of focus traversal keys on the given
160+
* component to remove the given keystroke. These sets are inherited down the
161+
* component hierarchy (until a component that has a custom set itself).
162+
*/
163+
public static void killFocusTraversalBinding(final Component component,
164+
final KeyStroke keystroke) {
165+
int[] sets = { KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
166+
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
167+
KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS,
168+
KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS };
169+
for (int set : sets) {
170+
Set<AWTKeyStroke> keys = component.getFocusTraversalKeys(set);
171+
// keys is immutable, so create a new set to allow changes
172+
keys = new HashSet<>(keys);
173+
if (set == 0)
174+
keys.add(ctrlAlt('Z'));
175+
176+
// If the given keystroke was present in the set, replace it with the
177+
// updated set with the keystroke removed.
178+
if (keys.remove(keystroke))
179+
component.setFocusTraversalKeys(set, keys);
180+
}
181+
}
182+
118183
private static void enableBind(final JComponent component,
119184
final Action action, final KeyStroke keystroke,
120185
int condition) {

app/src/processing/app/syntax/SketchTextArea.java

-27
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import processing.app.EditorListener;
4242
import processing.app.PreferencesData;
4343

44-
import javax.swing.*;
4544
import javax.swing.event.EventListenerList;
4645
import javax.swing.event.HyperlinkEvent;
4746
import javax.swing.event.HyperlinkListener;
@@ -57,9 +56,7 @@
5756
import java.io.IOException;
5857
import java.net.MalformedURLException;
5958
import java.net.URL;
60-
import java.util.HashSet;
6159
import java.util.Map;
62-
import java.util.Set;
6360
import java.util.logging.Logger;
6461

6562
/**
@@ -91,8 +88,6 @@ private void installFeatures() throws IOException {
9188

9289
setLinkGenerator(new DocLinkGenerator(pdeKeywords));
9390

94-
fixControlTab();
95-
9691
setSyntaxEditingStyle(SYNTAX_STYLE_CPLUSPLUS);
9792
}
9893

@@ -153,28 +148,6 @@ private void setSyntaxTheme(int tokenType, String id) {
153148
getSyntaxScheme().setStyle(tokenType, style);
154149
}
155150

156-
// Removing the default focus traversal keys
157-
// This is because the DefaultKeyboardFocusManager handles the keypress and consumes the event
158-
private void fixControlTab() {
159-
removeCTRLTabFromFocusTraversal();
160-
161-
removeCTRLSHIFTTabFromFocusTraversal();
162-
}
163-
164-
private void removeCTRLSHIFTTabFromFocusTraversal() {
165-
KeyStroke ctrlShiftTab = KeyStroke.getKeyStroke("ctrl shift TAB");
166-
Set<AWTKeyStroke> backwardKeys = new HashSet<>(this.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
167-
backwardKeys.remove(ctrlShiftTab);
168-
}
169-
170-
private void removeCTRLTabFromFocusTraversal() {
171-
KeyStroke ctrlTab = KeyStroke.getKeyStroke("ctrl TAB");
172-
Set<AWTKeyStroke> forwardKeys = new HashSet<>(this.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
173-
forwardKeys.remove(ctrlTab);
174-
this.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys);
175-
}
176-
177-
178151
public boolean isSelectionActive() {
179152
return this.getSelectedText() != null;
180153
}

0 commit comments

Comments
 (0)