diff --git a/src/org/infinity/NearInfinity.java b/src/org/infinity/NearInfinity.java index 42d67b029..4c5b9a99c 100644 --- a/src/org/infinity/NearInfinity.java +++ b/src/org/infinity/NearInfinity.java @@ -27,6 +27,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; @@ -47,9 +48,11 @@ import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; +import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JButton; +import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; @@ -60,6 +63,7 @@ import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.JToolBar; +import javax.swing.KeyStroke; import javax.swing.LookAndFeel; import javax.swing.ProgressMonitor; import javax.swing.SwingConstants; @@ -162,6 +166,9 @@ public final class NearInfinity extends JFrame implements ActionListener, Viewab private static final String STATUSBAR_TEXT_FMT = "Welcome to Near Infinity! - %s @ %s - %d files available"; + // Input map key for triggering the quick search feature + private static final String ACTIONMAP_KEY_QUICK_SEARCH = "SHORTCUT_OPEN_QUICK_SEARCH"; + private static final List> CUSTOM_LOOK_AND_FEELS = new ArrayList<>(); static { @@ -529,12 +536,14 @@ public void windowClosing(WindowEvent event) { b.setMargin(new Insets(0, 0, 0, 0)); toolBar.add(b); toolBar.addSeparator(new Dimension(8, 24)); + + final String shortcutModifier = + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() == KeyEvent.CTRL_MASK ? "Ctrl" : "⌘"; bpwQuickSearch = new ButtonPopupWindow(Icons.ICON_MAGNIFY_16.getIcon()); - bpwQuickSearch.setToolTipText("Find resource"); + bpwQuickSearch.setToolTipText("Find resource (Shortcut: " + shortcutModifier + "-/)"); bpwQuickSearch.setMargin(new Insets(4, 4, 4, 4)); toolBar.add(bpwQuickSearch); bpwQuickSearch.addPopupWindowListener(new PopupWindowListener() { - @Override public void popupWindowWillBecomeVisible(PopupWindowEvent event) { // XXX: Working around a visual glitch in QuickSearch's JComboBox popup list @@ -558,6 +567,17 @@ public void popupWindowWillBecomeInvisible(PopupWindowEvent event) { } }); + // registering window-global shortcut for the quick search option + final int ctrl = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) + .put(KeyStroke.getKeyStroke(KeyEvent.VK_DIVIDE, ctrl), ACTIONMAP_KEY_QUICK_SEARCH); + getRootPane().getActionMap().put(ACTIONMAP_KEY_QUICK_SEARCH, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + bpwQuickSearch.showPopupWindow(); + } + }); + toolBar.add(Box.createHorizontalGlue()); btnLaunchGame = new JButton(Icons.ICON_LAUNCH_24.getIcon()); btnLaunchGame.setFocusable(false); diff --git a/src/org/infinity/gui/InfinityTextArea.java b/src/org/infinity/gui/InfinityTextArea.java index cad8d7697..0cdc8703f 100644 --- a/src/org/infinity/gui/InfinityTextArea.java +++ b/src/org/infinity/gui/InfinityTextArea.java @@ -8,6 +8,11 @@ import java.awt.Font; import java.awt.Point; import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.HierarchyEvent; +import java.awt.event.HierarchyListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; @@ -17,8 +22,15 @@ import java.util.TreeMap; import java.util.function.Supplier; +import javax.swing.Action; +import javax.swing.ActionMap; import javax.swing.Icon; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JRootPane; import javax.swing.JViewport; +import javax.swing.KeyStroke; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.text.BadLocationException; @@ -49,7 +61,7 @@ /** * Extends {@link RSyntaxTextArea} by NearInfinity-specific features. */ -public class InfinityTextArea extends RSyntaxTextArea implements ChangeListener { +public class InfinityTextArea extends RSyntaxTextArea implements ChangeListener, KeyListener, HierarchyListener { /** Available languages for syntax highlighting. */ public enum Language { /** Disables syntax highlighting */ @@ -175,6 +187,7 @@ public String getScheme() { private final SortedMap gutterIcons = new TreeMap<>(); private final Map gutterIconsActive = new HashMap<>(); + private final HashMap inputActionMap = new HashMap<>(); private RTextScrollPane scrollPane; @@ -296,8 +309,12 @@ public InfinityTextArea(RSyntaxDocument doc, String text, int rows, int cols, bo * * @param resetUndo Specifies whether the undo history will be discarded. */ - public static void applySettings(RSyntaxTextArea edit, boolean resetUndo) { + public static void applySettings(InfinityTextArea edit, boolean resetUndo) { if (edit != null) { + // Allows key strokes defined by a parent components to be processed when this component has focus + edit.addKeyListener(edit); + edit.addHierarchyListener(edit); + edit.setCurrentLineHighlightColor(DEFAULT_LINE_HIGHLIGHT_COLOR); if (BrowserMenuBar.isInstantiated()) { edit.setTabsEmulated(BrowserMenuBar.getInstance().getOptions().isTextTabEmulated()); @@ -484,6 +501,67 @@ public void stateChanged(ChangeEvent e) { // --------------------- End Interface ChangeListener --------------------- + // --------------------- Begin Interface KeyListener --------------------- + + @Override + public void keyTyped(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + // Processing key strokes defined in parent components. + // Registration of key strokes is handled by the HierarchyListener. + final KeyStroke keyStroke = KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers()); + final Action action = inputActionMap.get(keyStroke); + if (action != null) { + action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "")); + e.consume(); + } + } + + @Override + public void keyReleased(KeyEvent e) { + } + + // --------------------- End Interface KeyListener --------------------- + + // --------------------- Begin Interface HierarchyListener --------------------- + + @Override + public void hierarchyChanged(HierarchyEvent e) { + // InfinityTextArea appears to discard or override certain global key stroke definitions. + // This method registers key strokes from parent components, so that they can still be processed. + // Registration process is placed into a HierarchyListener to register key strokes even if + // parent components are assigned later. + if (!(e.getChanged() instanceof JComponent && getTopLevelAncestor() instanceof JFrame)) { + return; + } + + final JRootPane rootPane = ((JFrame) getTopLevelAncestor()).getRootPane(); + if (rootPane == null) { + return; + } + + final InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + final KeyStroke[] keyStrokes = inputMap.allKeys(); + final ActionMap actionMap = rootPane.getActionMap(); + if (inputMap == null || keyStrokes == null || keyStrokes.length == 0 || actionMap == null) { + return; + } + + for (final KeyStroke keyStroke : keyStrokes) { + final Object binding = inputMap.get(keyStroke); + if (binding != null) { + final Action action = actionMap.get(binding); + if (action != null) { + inputActionMap.put(keyStroke, action); + } + } + } + } + + // --------------------- End Interface HierarchyListener --------------------- + /** * Returns the underlying ScrollPane if available. Returns {@code null} otherwise. */