diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 9a30f5250b8..6960e229edd 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -38,6 +38,7 @@ 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 void windowDeactivated(WindowEvent e) { // 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"); @@ -1033,7 +1040,6 @@ private SketchTextArea createTextArea() throws IOException { textArea.setAntiAliasingEnabled(PreferencesData.getBoolean("editor.antialias")); textArea.setTabsEmulated(PreferencesData.getBoolean("editor.tabs.expand")); textArea.setTabSize(PreferencesData.getInteger("editor.tabs.size")); - textArea.setEditorListener(new EditorListener(this)); textArea.addHyperlinkListener(new HyperlinkListener() { @Override public void hyperlinkUpdate(HyperlinkEvent hyperlinkEvent) { diff --git a/app/src/processing/app/EditorConsole.java b/app/src/processing/app/EditorConsole.java index f39f98cb84a..f31a01b00f9 100644 --- a/app/src/processing/app/EditorConsole.java +++ b/app/src/processing/app/EditorConsole.java @@ -63,6 +63,7 @@ public EditorConsole() { 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); diff --git a/app/src/processing/app/EditorHeader.java b/app/src/processing/app/EditorHeader.java index a1e100aea43..84dc49df4c1 100644 --- a/app/src/processing/app/EditorHeader.java +++ b/app/src/processing/app/EditorHeader.java @@ -22,7 +22,10 @@ */ package processing.app; + +import processing.app.helpers.Keys; import processing.app.helpers.OSUtils; +import processing.app.helpers.SimpleAction; import processing.app.tools.MenuScroller; import static processing.app.I18n.tr; @@ -72,12 +75,69 @@ public class EditorHeader extends JComponent { static Image[][] pieces; - // - Image offscreen; int sizeW, sizeH; int imageW, imageH; + public class Actions { + public final Action newTab = new SimpleAction(tr("New Tab"), + Keys.ctrlShift(KeyEvent.VK_N), + () -> editor.getSketch().handleNewCode()); + + public final Action renameTab = new SimpleAction(tr("Rename"), + () -> editor.getSketch().handleRenameCode()); + + public final Action deleteTab = new SimpleAction(tr("Delete"), () -> { + try { + editor.getSketch().handleDeleteCode(); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + public final Action prevTab = new SimpleAction(tr("Previous Tab"), + Keys.ctrlAlt(KeyEvent.VK_LEFT), + () -> editor.sketch.handlePrevCode()); + + public final Action nextTab = new SimpleAction(tr("Next Tab"), + Keys.ctrlAlt(KeyEvent.VK_RIGHT), + () -> editor.sketch.handleNextCode()); + + Actions() { + // Explicitly bind keybindings for the actions with accelerators above + // Normally, this happens automatically for any actions bound to menu + // items, but only for menus attached to a window, not for popup menus. + Keys.bind(EditorHeader.this, newTab); + Keys.bind(EditorHeader.this, prevTab); + Keys.bind(EditorHeader.this, nextTab); + + // Add alternative keybindings to switch tabs + Keys.bind(EditorHeader.this, prevTab, Keys.ctrlShift(KeyEvent.VK_TAB)); + Keys.bind(EditorHeader.this, nextTab, Keys.ctrl(KeyEvent.VK_TAB)); + } + } + 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 @@ -236,7 +296,6 @@ public void rebuild() { public void rebuildMenu() { - //System.out.println("rebuilding"); if (menu != null) { menu.removeAll(); @@ -244,143 +303,34 @@ public void rebuildMenu() { menu = new JMenu(); MenuScroller.setScrollerFor(menu); popup = menu.getPopupMenu(); - add(popup); popup.setLightWeightPopupEnabled(true); - - /* - popup.addPopupMenuListener(new PopupMenuListener() { - public void popupMenuCanceled(PopupMenuEvent e) { - // on redraw, the isVisible() will get checked. - // actually, a repaint may be fired anyway, so this - // may be redundant. - repaint(); - } - - public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { } - public void popupMenuWillBecomeVisible(PopupMenuEvent e) { } - }); - */ } JMenuItem item; - // maybe this shouldn't have a command key anyways.. - // since we're not trying to make this a full ide.. - //item = Editor.newJMenuItem("New", 'T'); - - /* - item = Editor.newJMenuItem("Previous", KeyEvent.VK_PAGE_UP); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - System.out.println("prev"); - } - }); - if (editor.sketch != null) { - item.setEnabled(editor.sketch.codeCount > 1); - } - menu.add(item); - - item = Editor.newJMenuItem("Next", KeyEvent.VK_PAGE_DOWN); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - System.out.println("ext"); - } - }); - if (editor.sketch != null) { - item.setEnabled(editor.sketch.codeCount > 1); - } - menu.add(item); - - menu.addSeparator(); - */ - - //item = new JMenuItem("New Tab"); - item = Editor.newJMenuItemShift(tr("New Tab"), 'N'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - editor.getSketch().handleNewCode(); - } - }); - menu.add(item); - - item = new JMenuItem(tr("Rename")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - editor.getSketch().handleRenameCode(); - /* - // this is already being called by nameCode(), the second stage of rename - if (editor.sketch.current == editor.sketch.code[0]) { - editor.sketchbook.rebuildMenus(); - } - */ - } - }); - menu.add(item); - - item = new JMenuItem(tr("Delete")); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent event) { - try { - editor.getSketch().handleDeleteCode(); - } catch (IOException e) { - e.printStackTrace(); - } - } - }); - menu.add(item); - + menu.add(new JMenuItem(actions.newTab)); + menu.add(new JMenuItem(actions.renameTab)); + menu.add(new JMenuItem(actions.deleteTab)); menu.addSeparator(); - - // KeyEvent.VK_LEFT and VK_RIGHT will make Windows beep - - item = new JMenuItem(tr("Previous Tab")); - KeyStroke ctrlAltLeft = KeyStroke - .getKeyStroke(KeyEvent.VK_LEFT, Editor.SHORTCUT_ALT_KEY_MASK); - item.setAccelerator(ctrlAltLeft); - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - editor.sketch.handlePrevCode(); - } - }); - menu.add(item); - - item = new JMenuItem(tr("Next Tab")); - KeyStroke ctrlAltRight = KeyStroke - .getKeyStroke(KeyEvent.VK_RIGHT, Editor.SHORTCUT_ALT_KEY_MASK); - item.setAccelerator(ctrlAltRight); - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - editor.sketch.handleNextCode(); - } - }); - menu.add(item); + menu.add(new JMenuItem(actions.prevTab)); + menu.add(new JMenuItem(actions.nextTab)); Sketch sketch = editor.getSketch(); if (sketch != null) { menu.addSeparator(); - - ActionListener jumpListener = new ActionListener() { - public void actionPerformed(ActionEvent e) { - editor.getSketch().setCurrentCode(e.getActionCommand()); - } - }; + int i = 0; for (SketchCode code : sketch.getCodes()) { + final int index = i++; item = new JMenuItem(code.isExtension(sketch.getDefaultExtension()) ? code.getPrettyName() : code.getFileName()); - item.setActionCommand(code.getFileName()); - item.addActionListener(jumpListener); + item.addActionListener((ActionEvent e) -> { + editor.getSketch().setCurrentCode(index); + }); menu.add(item); } } } - public void deselectMenu() { - repaint(); - } - - public Dimension getPreferredSize() { return getMinimumSize(); } diff --git a/app/src/processing/app/EditorListener.java b/app/src/processing/app/EditorListener.java deleted file mode 100644 index cbd082cfcad..00000000000 --- a/app/src/processing/app/EditorListener.java +++ /dev/null @@ -1,79 +0,0 @@ -package processing.app; - -import java.awt.Toolkit; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; - -import processing.app.syntax.SketchTextArea; - -public class EditorListener implements KeyListener { - - private Editor editor; - - public EditorListener(Editor editor) { - super(); - this.editor = editor; - } - - /** ctrl-alt on windows and linux, cmd-alt on mac os x */ - private static final int CTRL = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); - private static final int CTRL_ALT = InputEvent.ALT_MASK | CTRL; - private static final int CTRL_SHIFT = InputEvent.SHIFT_MASK | CTRL; - - public void keyTyped(KeyEvent event) { - char c = event.getKeyChar(); - - if ((event.getModifiers() & KeyEvent.CTRL_MASK) != 0) { - // The char is not control code when CTRL key pressed? It should be a shortcut. - if (!Character.isISOControl(c)) { - event.consume(); - } - } - } - - @Override - public void keyPressed(KeyEvent event) { - - SketchTextArea textarea = editor.getTextArea(); - - if (!textarea.isEditable()) return; - - Sketch sketch = editor.getSketch(); - - int code = event.getKeyCode(); - - // Navigation.. - if ((event.getModifiers() & CTRL) == CTRL && code == KeyEvent.VK_TAB) { - sketch.handleNextCode(); - } - - // Navigation.. - // FIXME: not working on LINUX !!! - if ((event.getModifiers() & CTRL_SHIFT) == CTRL_SHIFT && code == KeyEvent.VK_TAB) { - sketch.handlePrevCode(); - } - - // Navigation.. - if ((event.getModifiers() & CTRL_ALT) == CTRL_ALT) { - if (code == KeyEvent.VK_LEFT) { - sketch.handlePrevCode(); - } else if (code == KeyEvent.VK_RIGHT) { - sketch.handleNextCode(); - } - } - -// if (event.isAltDown() && code == KeyEvent.VK_T) { -// int line = textarea.getCaretLineNumber(); -// textarea.setActiveLineRange(line, line + 3); -// } - - } - - @Override - public void keyReleased(KeyEvent e) { - // TODO Auto-generated method stub - - } - -} diff --git a/app/src/processing/app/helpers/Keys.java b/app/src/processing/app/helpers/Keys.java new file mode 100644 index 00000000000..6c0053e50ef --- /dev/null +++ b/app/src/processing/app/helpers/Keys.java @@ -0,0 +1,233 @@ +/* + * This file is part of Arduino. + * + * Copyright 2015 Matthijs Kooijman + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + */ + +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; + +/** + * This class contains some keybinding-related helper methods. + */ +public class Keys { + + /** + * Register a keybinding in the given components WHEN_IN_FOCUSED_WINDOW input + * map, runing the given action when the action's accelerator key is pressed. + * + * Note that this is typically automatically handled when the action is + * assigned to a JMenuItem, but it can still be needed for other actions, or + * actions mapped to JMenuItems in a popup menu that is not added to any + * window normally (and thus does not fire when the popup is closed). + * + * When the action is disabled, the keybinding is unregistered, and when it is + * enabled, it is registered again. + */ + public static void bind(final JComponent component, final Action action) { + bind(component, action, + (KeyStroke) action.getValue(Action.ACCELERATOR_KEY)); + } + + /** + * Register a keybinding, running the given action when the given keystroke is + * pressed when the given component is in the focused window. + * + * This is typically used to bind an additional keystroke to a menu item, in + * addition to the primary accelerator key. + * + * When the action is disabled, the keybinding is unregistered, and when it is + * enabled, it is registered again. + */ + public static void bind(final JComponent component, final Action action, + KeyStroke keystroke) { + bind(component, action, keystroke, JComponent.WHEN_IN_FOCUSED_WINDOW); + } + + /** + * Register a keybinding to be handled in given condition, running the given + * action when the given keystroke is pressed. + * + * When the action is disabled, the keybinding is unregistered, and when it is + * enabled, it is registered again. + * + * @param component + * The component to register the keybinding on. + * @param action + * The action to run when the keystroke is pressed + * @param action + * The keystroke to bind + * @param condition + * The condition under which to run the keystroke. Should be one of + * JComponent.WHEN_FOCUSED, + * JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT or + * JComponent.WHEN_IN_FOCUSED_WINDOW. + */ + public static void bind(final JComponent component, final Action action, + KeyStroke keystroke, int condition) { + // The input map maps keystrokes to arbitrary objects (originally strings + // that described the option, we just use the Action object itself). + if (action.isEnabled()) + enableBind(component, action, keystroke, condition); + + // The action map maps the arbitrary option to an Action to execute. These + // be kept in the component even when the action is disabled. + component.getActionMap().put(action, action); + + // Enable and disable the binding when the action is enabled / disabled. + action.addPropertyChangeListener((PropertyChangeEvent e) -> { + if (e.getPropertyName().equals("enabled")) { + if (e.getNewValue().equals(Boolean.TRUE)) + enableBind(component, action, keystroke, condition); + else + disableBind(component, action, keystroke, condition); + } + }); + } + + /** + * 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 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) { + component.getInputMap(condition).put(keystroke, action); + } + + private static void disableBind(final JComponent component, + final Action action, + final KeyStroke keystroke, int condition) { + component.getInputMap(condition).put(keystroke, action); + } + + private static final int CTRL = Toolkit.getDefaultToolkit() + .getMenuShortcutKeyMask(); + + /** + * Creates a KeyCode for the "menu shortcut" + the key passed in. By default, + * the menu shortcut is the ctrl key (hence the method name), but platforms + * might use a different key (like the Apple key on OSX). + * + * keyCode should be a KeyEvent.VK_* constant (it can also be a char constant, + * but this does not work for all characters, so is not recommended). + */ + public static KeyStroke ctrl(int keyCode) { + return KeyStroke.getKeyStroke(keyCode, CTRL); + } + + /** + * Creates a KeyCode for the "menu shortcut" + shift + the key passed in. By + * default, the menu shortcut is the ctrl key (hence the method name), but + * platforms might use a different key (like the Apple key on OSX). + * + * keyCode should be a KeyEvent.VK_* constant (it can also be a char constant, + * but this does not work for all characters, so is not recommended). + */ + public static KeyStroke ctrlShift(int keyCode) { + return KeyStroke.getKeyStroke(keyCode, CTRL | InputEvent.SHIFT_MASK); + } + + /** + * Creates a KeyCode for the "menu shortcut" + shift + the key passed in. By + * default, the menu shortcut is the ctrl key (hence the method name), but + * platforms might use a different key (like the Apple key on OSX). + * + * keyCode should be a KeyEvent.VK_* constant (it can also be a char constant, + * but this does not work for all characters, so is not recommended). + */ + public static KeyStroke ctrlAlt(int keyCode) { + return KeyStroke.getKeyStroke(keyCode, CTRL | InputEvent.ALT_MASK); + } +} diff --git a/app/src/processing/app/helpers/SimpleAction.java b/app/src/processing/app/helpers/SimpleAction.java new file mode 100644 index 00000000000..85c096392a2 --- /dev/null +++ b/app/src/processing/app/helpers/SimpleAction.java @@ -0,0 +1,112 @@ +/* + * This file is part of Arduino. + * + * Copyright 2015 Matthijs Kooijman + * + * Arduino is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + */ + +package processing.app.helpers; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.AbstractAction; +import javax.swing.KeyStroke; + +/** + * Class to easily define instances of the Swing Action interface. + * + * When using AbstractAction, you have to create a subclass that implements the + * actionPerformed() method, and sets attributes in the constructor, which gets + * verbose quickly. This class implements actionPerformed for you, and forwards + * it to the ActionListener passed to the constructor (intended to be a lambda + * expression). Additional Action attributes can be set by passing constructor + * arguments. + * + * The name of this class refers to the fact that it's simple to create an + * action using this class, but perhaps a better name can be found for it. + * + * @see javax.swing.Action + */ +public class SimpleAction extends AbstractAction { + private ActionListener listener; + + /** + * Version of ActionListener that does not take an ActionEvent as an argument + * This can be used when you do not care about the event itself, just that it + * happened, typically for passing a argumentless lambda or method reference + * to the SimpleAction constructor. + */ + public interface AnonymousActionListener { + public void actionPerformed(); + } + + public SimpleAction(String name, ActionListener listener) { + this(name, null, null, listener); + } + + public SimpleAction(String name, AnonymousActionListener listener) { + this(name, null, null, listener); + } + + public SimpleAction(String name, KeyStroke accelerator, + ActionListener listener) { + this(name, null, accelerator, listener); + } + + public SimpleAction(String name, KeyStroke accelerator, + AnonymousActionListener listener) { + this(name, null, accelerator, listener); + } + + public SimpleAction(String name, String description, + ActionListener listener) { + this(name, description, null, listener); + } + + public SimpleAction(String name, String description, + AnonymousActionListener listener) { + this(name, description, null, listener); + } + + public SimpleAction(String name, String description, KeyStroke accelerator, + AnonymousActionListener listener) { + this(name, description, accelerator, + (ActionEvent) -> listener.actionPerformed()); + } + + public SimpleAction(String name, String description, KeyStroke accelerator, + ActionListener listener) { + this.putValue(NAME, name); + this.putValue(SHORT_DESCRIPTION, description); + this.putValue(ACCELERATOR_KEY, accelerator); + this.listener = listener; + } + + @Override + public void actionPerformed(ActionEvent e) { + listener.actionPerformed(e); + } +} diff --git a/app/src/processing/app/syntax/SketchTextArea.java b/app/src/processing/app/syntax/SketchTextArea.java index 50d946a06a5..ac50a2dc2a6 100644 --- a/app/src/processing/app/syntax/SketchTextArea.java +++ b/app/src/processing/app/syntax/SketchTextArea.java @@ -38,10 +38,8 @@ import org.fife.ui.rtextarea.RUndoManager; import processing.app.Base; 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; @@ -50,16 +48,13 @@ import javax.swing.text.Segment; import javax.swing.undo.UndoManager; import java.awt.*; -import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.io.File; 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; /** @@ -72,8 +67,6 @@ public class SketchTextArea extends RSyntaxTextArea { private final static Logger LOG = Logger.getLogger(SketchTextArea.class.getName()); - private EditorListener editorListener; - private PdeKeywords pdeKeywords; public SketchTextArea(PdeKeywords pdeKeywords) throws IOException { @@ -91,8 +84,6 @@ private void installFeatures() throws IOException { setLinkGenerator(new DocLinkGenerator(pdeKeywords)); - fixControlTab(); - setSyntaxEditingStyle(SYNTAX_STYLE_CPLUSPLUS); } @@ -153,53 +144,10 @@ private void setSyntaxTheme(int tokenType, String id) { 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 backwardKeys = new HashSet<>(this.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)); - backwardKeys.remove(ctrlShiftTab); - } - - private void removeCTRLTabFromFocusTraversal() { - KeyStroke ctrlTab = KeyStroke.getKeyStroke("ctrl TAB"); - Set 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; } - public void processKeyEvent(KeyEvent evt) { - - // this had to be added because the menu key events weren't making it up to the frame. - - switch (evt.getID()) { - case KeyEvent.KEY_TYPED: - if (editorListener != null) editorListener.keyTyped(evt); - break; - case KeyEvent.KEY_PRESSED: - if (editorListener != null) editorListener.keyPressed(evt); - break; - case KeyEvent.KEY_RELEASED: - // inputHandler.keyReleased(evt); - break; - } - - if (!evt.isConsumed()) { - super.processKeyEvent(evt); - } - } - public void switchDocument(Document document, UndoManager newUndo) { // HACK: Dont discard changes on curret UndoManager. @@ -233,11 +181,6 @@ public void getTextLine(int line, Segment segment) { } } - - public void setEditorListener(EditorListener editorListener) { - this.editorListener = editorListener; - } - private static class DocLinkGenerator implements LinkGenerator { private final PdeKeywords pdeKeywords; diff --git a/app/src/processing/app/syntax/SketchTextAreaDefaultInputMap.java b/app/src/processing/app/syntax/SketchTextAreaDefaultInputMap.java index f713e26d5ed..dfa5df928c4 100644 --- a/app/src/processing/app/syntax/SketchTextAreaDefaultInputMap.java +++ b/app/src/processing/app/syntax/SketchTextAreaDefaultInputMap.java @@ -23,6 +23,15 @@ public SketchTextAreaDefaultInputMap() { remove(KeyStroke.getKeyStroke(KeyEvent.VK_K, defaultModifier)); + // Remove a troublesome binding for the / key. By default, RSyntaxTextArea + // binds the / KEY_TYPED event to insert a / and optionally complete any XML + // tags. However, since this also triggeres on ctrl-slash, this means that + // in addition to toggling comments on ctrl-slash, it also inserts a slash. + // Since we don't need the XML completion feature anyway, just unbind it + // here. A future version of RSyntaxTextArea might fix this, see + // https://github.com/bobbylight/RSyntaxTextArea/issues/157. + remove(KeyStroke.getKeyStroke('/')); + if (PreferencesData.getBoolean("editor.advanced")) { put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, alt), RTextAreaEditorKit.rtaLineDownAction); put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, alt), RTextAreaEditorKit.rtaLineUpAction);