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 #195
This commit is contained in:
Matthijs Kooijman 2015-12-11 11:37:17 +01:00
parent fc4b2028fa
commit f06820713e
5 changed files with 94 additions and 27 deletions

View File

@ -38,6 +38,7 @@ import org.fife.ui.rtextarea.Gutter;
import org.fife.ui.rtextarea.RTextScrollPane;
import processing.app.debug.RunnerException;
import processing.app.forms.PasswordAuthorizationDialog;
import processing.app.helpers.Keys;
import processing.app.helpers.OSUtils;
import processing.app.helpers.PreferencesMapException;
import processing.app.legacy.PApplet;
@ -312,6 +313,12 @@ public class Editor extends JFrame implements RunnerListener {
// to fix ugliness.. normally macosx java 1.3 puts an
// ugly white border around this object, so turn it off.
splitPane.setBorder(null);
// By default, the split pane binds Ctrl-Tab and Ctrl-Shift-Tab for changing
// focus. Since we do not use that, but want to use these shortcuts for
// switching tabs, remove the bindings from the split pane. This allows the
// events to bubble up and be handled by the EditorHeader.
Keys.killBinding(splitPane, Keys.ctrl(KeyEvent.VK_TAB));
Keys.killBinding(splitPane, Keys.ctrlShift(KeyEvent.VK_TAB));
// the default size on windows is too small and kinda ugly
int dividerSize = PreferencesData.getInteger("editor.divider.size");

View File

@ -63,6 +63,7 @@ public class EditorConsole extends JScrollPane {
consoleTextPane.setEditable(false);
DefaultCaret caret = (DefaultCaret) consoleTextPane.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
consoleTextPane.setFocusTraversalKeysEnabled(false);
Color backgroundColour = Theme.getColor("console.color");
consoleTextPane.setBackground(backgroundColour);

View File

@ -118,6 +118,27 @@ public class EditorHeader extends JComponent {
}
public Actions actions = new Actions();
/**
* Called whenever we, or any of our ancestors, is added to a container.
*/
public void addNotify() {
super.addNotify();
/*
* Once we get added to a window, remove Ctrl-Tab and Ctrl-Shift-Tab from
* the keys used for focus traversal (so our bindings for these keys will
* work). All components inherit from the window eventually, so this should
* work whenever the focus is inside our window. Some components (notably
* JTextPane / JEditorPane) keep their own focus traversal keys, though, and
* have to be treated individually (either the same as below, or by
* disabling focus traversal entirely).
*/
Window window = SwingUtilities.getWindowAncestor(this);
if (window != null) {
Keys.killFocusTraversalBinding(window, Keys.ctrl(KeyEvent.VK_TAB));
Keys.killFocusTraversalBinding(window, Keys.ctrlShift(KeyEvent.VK_TAB));
}
}
public EditorHeader(Editor eddie) {
this.editor = eddie; // weird name for listener

View File

@ -29,11 +29,17 @@
package processing.app.helpers;
import java.awt.AWTKeyStroke;
import java.awt.Component;
import java.awt.KeyboardFocusManager;
import java.awt.Toolkit;
import java.awt.event.InputEvent;
import java.beans.PropertyChangeEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.Action;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
@ -115,6 +121,65 @@ public class Keys {
});
}
/**
* Kill an existing binding from the given condition. If the binding is
* defined on the given component, it is removed, but if it is defined through
* a parent inputmap (typically shared by multiple components, so best not
* touched), this adds a dummy binding for this component, that will never
* match an action in the component's action map, effectively disabling the
* binding.
*
* This method is not intended to unbind a binding created by bind(), since
* such a binding would get re-enabled when the action is re-enabled.
*/
public static void killBinding(final JComponent component,
final KeyStroke keystroke, int condition) {
InputMap map = component.getInputMap(condition);
// First, try removing it
map.remove(keystroke);
// If the binding is defined in a parent map, defining it will not work, so
// instead add an override that will never appear in the action map.
if (map.get(keystroke) != null)
map.put(keystroke, new Object());
}
/**
* Kill an existing binding like above, but from all three conditions
* (WHEN_FOCUSED, WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, WHEN_IN_FOCUSED_WINDOW).
*/
public static void killBinding(final JComponent component,
final KeyStroke key) {
killBinding(component, key, JComponent.WHEN_FOCUSED);
killBinding(component, key, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
killBinding(component, key, JComponent.WHEN_IN_FOCUSED_WINDOW);
}
/**
* Remove a keystroke from the keys used to shift focus in or below the given
* component. This modifies all sets of focus traversal keys on the given
* component to remove the given keystroke. These sets are inherited down the
* component hierarchy (until a component that has a custom set itself).
*/
public static void killFocusTraversalBinding(final Component component,
final KeyStroke keystroke) {
int[] sets = { KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS,
KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS };
for (int set : sets) {
Set<AWTKeyStroke> keys = component.getFocusTraversalKeys(set);
// keys is immutable, so create a new set to allow changes
keys = new HashSet<>(keys);
if (set == 0)
keys.add(ctrlAlt('Z'));
// If the given keystroke was present in the set, replace it with the
// updated set with the keystroke removed.
if (keys.remove(keystroke))
component.setFocusTraversalKeys(set, keys);
}
}
private static void enableBind(final JComponent component,
final Action action, final KeyStroke keystroke,
int condition) {

View File

@ -41,7 +41,6 @@ import processing.app.BaseNoGui;
import processing.app.EditorListener;
import processing.app.PreferencesData;
import javax.swing.*;
import javax.swing.event.EventListenerList;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
@ -57,9 +56,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
/**
@ -91,8 +88,6 @@ public class SketchTextArea extends RSyntaxTextArea {
setLinkGenerator(new DocLinkGenerator(pdeKeywords));
fixControlTab();
setSyntaxEditingStyle(SYNTAX_STYLE_CPLUSPLUS);
}
@ -153,28 +148,6 @@ public class SketchTextArea extends RSyntaxTextArea {
getSyntaxScheme().setStyle(tokenType, style);
}
// Removing the default focus traversal keys
// This is because the DefaultKeyboardFocusManager handles the keypress and consumes the event
private void fixControlTab() {
removeCTRLTabFromFocusTraversal();
removeCTRLSHIFTTabFromFocusTraversal();
}
private void removeCTRLSHIFTTabFromFocusTraversal() {
KeyStroke ctrlShiftTab = KeyStroke.getKeyStroke("ctrl shift TAB");
Set<AWTKeyStroke> backwardKeys = new HashSet<>(this.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
backwardKeys.remove(ctrlShiftTab);
}
private void removeCTRLTabFromFocusTraversal() {
KeyStroke ctrlTab = KeyStroke.getKeyStroke("ctrl TAB");
Set<AWTKeyStroke> forwardKeys = new HashSet<>(this.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
forwardKeys.remove(ctrlTab);
this.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys);
}
public boolean isSelectionActive() {
return this.getSelectedText() != null;
}