0

I have a Swing application with multiple panes. Some of the panes are text ones (JTextPane), some are dialog-like (with buttons and sliders) and some are graphical (custom painted ). I have a few actions defined in the main menu with simple accelerators like K, P or O.

I would like those accelerators to be processed by the menu actions only if the currently focused pane is not processing them. Specifically, I do not want them to be processed by the menu when the user is just typing in a text pane.

I am creating actions and menu items using:

action = new javax.swing.AbstractAction
new MenuItem(action)

I am registering accelerators with:

action.putValue(javax.swing.Action.ACCELERATOR_KEY, keyStroke)

Is it possible to "eat" (suppress) the key press event for the keys which are processed in the text panes so that they are not passed to the main menu for the global processing?

If not, are there some alternatives to do something similar, like to register the accelerators I know should not be processed when in a text pane for some panes only?

I am adding a code based on an answer to make the question clearer (and to make developing alternate solutions easier):

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;

public class TestMenuBindings {

    public static void main(String[] args) {
        JMenuBar menuBar = new JMenuBar();
        final JMenu menu = new JMenu("Print");
        final Action oAction = new PrintAction("O",KeyStroke.getKeyStroke(KeyEvent.VK_O, 0));
        menu.add(oAction);
        menuBar.add(menu);
        JFrame frm = new JFrame("Frame");
        frm.setJMenuBar(menuBar);
        JTextArea area = new JTextArea("Here I want no accelerators", 10, 40);
        frm.add(new JScrollPane(area));
        frm.add(new JTextField("Here I want accelerators working"), BorderLayout.SOUTH);
        frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frm.pack();
        frm.setVisible(true);
    }

    private static class PrintAction extends AbstractAction {
        private String str;
        public PrintAction(String aPrintStr, KeyStroke aMnemonic) {
            super("Print: " + aPrintStr);
            str = aPrintStr;
            putValue(Action.ACCELERATOR_KEY, aMnemonic);
        }
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println(str);
        }
    }
}
jesterjunk
  • 2,342
  • 22
  • 18
Suma
  • 33,181
  • 16
  • 123
  • 191
  • 1
    I would simply disable all the action when the text component receive focus and enable when lost. This would be the best solution in your case. – Sergiy Medvynskyy Mar 25 '15 at 10:43
  • Swing Action has isEnabled [together with](http://stackoverflow.com/questions/10880326/jpanel-which-one-of-listeners-is-proper-for-visibility-is-changed), otherwise you have look at [EventHandler](http://stackoverflow.com/a/9007348/714968) or (old fasioned way, but the most safest) add/remove notifiers at runtime [together with](http://stackoverflow.com/questions/10880326/jpanel-which-one-of-listeners-is-proper-for-visibility-is-changed), – mKorbel Mar 25 '15 at 13:40
  • Meanwhile I have learned something: `JTextPane` is marking the event as consumed, but the trouble is it is a different event than the one sent to the main menu. Main menu is sent `KEY_PRESSED`, while the text pane is sent `KEY_TYPED`. As a result, consuming the `KEY_PRESSED` by the `JTextPane` has no influence on the menu event (also note the `KEY_PRESSED` event is queued before the `KEY_TYPED` one). – Suma Mar 25 '15 at 15:11
  • @Suma please read, all what I've wrote in my first comment. It's the best solution for your problem. If you need code example I can write it for you, but I think it's not necessary. – Sergiy Medvynskyy Mar 25 '15 at 16:10
  • `Main menu is sent KEY_PRESSED, while the text pane is sent KEY_TYPED.` - a text pane is also sent KEY_PRESSED, its just that your custom code is handling KEY_TYPED. Do your bindings for key pressed. Post a [SSCCE](http://sscce.org/) that demonstrates your problem is you need more help. – camickr Mar 25 '15 at 17:12
  • @camick I have no custom code for key handling. Letters are typed into the text pane in reaction to KEY_TYPED, which is consumed by it - this is the default handling. Actions in the menu are executed based on their accelerators in reaction to KEY_PRESSED, which is consumed by it - this is the default handling as well. – Suma Mar 25 '15 at 19:36
  • @SergiyMedvynskyy Disabling the action has an unfortunate drawback that I cannot execute the action even from the menu, which I do no like. I want it to be enabled from the menu (using mouse, or F10 + mnemonics), I just do not want the keypresses used to type into text pane to execute the action. Key bindings currently look more promissing to me in this respect. – Suma Mar 25 '15 at 19:39
  • @Suma, Sorry, I didn't understand the question. I have never seen an application use a single character as an accelerator for this exact reason, it causes problems with other components that accept text whether that be a text component or a JList or JComboBox or JTable. I think this is a confusing design. Add the key bindings directly to the component that needs the bindings instead of using the menu bar. Or change the menu bar accelerator so use "Ctrl+?" which is the norm users are expecting. – camickr Mar 26 '15 at 03:14

3 Answers3

1

Here is the example. In text area no key bindings working. In text field work all key bindings. Also all the menu items are accessible (enabled) from the menu.

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;

public class TestMenuBindings {

    public static void main(String[] args) {
        JMenuBar menuBar = new JMenuBar();
        final JMenu menu = new JMenu("Print");
        menu.add(new PrintAction("O", KeyStroke.getKeyStroke(KeyEvent.VK_O, 0)));
        menu.add(new PrintAction("K", KeyStroke.getKeyStroke(KeyEvent.VK_K, 0)));
        menu.add(new PrintAction("P", KeyStroke.getKeyStroke(KeyEvent.VK_P, 0)));
        menuBar.add(menu);
        JFrame frm = new JFrame("Frame");
        frm.setJMenuBar(menuBar);
        JTextArea area = new JTextArea("Here working no accelerators", 10, 40);
        area.addFocusListener(new FocusListener() {

            @Override
            public void focusLost(FocusEvent e) {
                setItemStatus(menu, true);
            }

            @Override
            public void focusGained(FocusEvent e) {
                setItemStatus(menu, false);
            }
        });
        frm.add(new JScrollPane(area));
        frm.add(new JTextField("Here working accelerators"), BorderLayout.SOUTH);
        frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frm.pack();
        frm.setVisible(true);
    }

    private static void setItemStatus(JMenu aMenu, boolean aStatus) {
        for (Component item : aMenu.getMenuComponents()) {
            ((JMenuItem) item).getAction().setEnabled(aStatus);
        }
    }
    private static class PrintAction extends AbstractAction {
        private String str;
        public PrintAction(String aPrintStr, KeyStroke aMnemonic) {
            super("Print: " + aPrintStr);
            str = aPrintStr;
            putValue(Action.ACCELERATOR_KEY, aMnemonic);
        }
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println(str);
        }
    }
}
Sergiy Medvynskyy
  • 11,160
  • 1
  • 32
  • 48
0

Here is a solution using KeyBindinds, as suggested by camickr. It is shorter than the one provided by Sergiy Medvynskyy, and I find it more straightforward, but it has a drawback the shortcut is not displayed in the menu, which is a result of the shortcut not being defined in the action itself.

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;

public class TestMenuBindings {

    public static void main(String[] args) {
        JMenuBar menuBar = new JMenuBar();
        final JMenu menu = new JMenu("Print");
        final Action oAction = new PrintAction("O");
        menu.add(oAction);
        menuBar.add(menu);
        JFrame frm = new JFrame("Frame");
        frm.setJMenuBar(menuBar);
        JTextArea area = new JTextArea("Here working no accelerators", 10, 40);
        frm.add(new JScrollPane(area));
        frm.add(new JTextField("Here working accelerators") {
            {
                getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_O, 0), "command_O");
                getActionMap().put("command_O", oAction);
            }
        }, BorderLayout.SOUTH);
        frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frm.pack();
        frm.setVisible(true);
    }

    private static class PrintAction extends AbstractAction {
        private String str;
        public PrintAction(String aPrintStr) {
            super("Print: " + aPrintStr);
            str = aPrintStr;
        }
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println(str);
        }
    }
}
Community
  • 1
  • 1
Suma
  • 33,181
  • 16
  • 123
  • 191
0

It is possible to use KeyEventDispatcher to filter key events.

(Credit: I have adapted the code from answer to Application wide keyboard shortcut)

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;

import javax.swing.*;

public class TestMenuBindings {

    public static void main(String[] args) {
        JMenuBar menuBar = new JMenuBar();
        final JMenu menu = new JMenu("Print");
        final Action oAction = new PrintAction("O",KeyStroke.getKeyStroke(KeyEvent.VK_O, 0));
        menu.add(oAction);
        menuBar.add(menu);
        JFrame frm = new JFrame("Frame");
        frm.setJMenuBar(menuBar);

        final JTextArea area = new JTextArea("Here working no accelerators", 10, 40);
        frm.add(new JScrollPane(area));

        frm.add(new JTextField("Here working accelerators"), BorderLayout.SOUTH);

        KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
        kfm.addKeyEventDispatcher( new KeyEventDispatcher() {
            @Override
            public boolean dispatchKeyEvent(KeyEvent e) {
                KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
                // pass only KEY_TYPED for letters with no modifiers in the editing area, suppress KEY_PRESSED, KEY_RELEASED
                return area.isFocusOwner() && keyStroke.getModifiers()==0 && e.getID()!=KeyEvent.KEY_TYPED && Character.isLetter(e.getKeyChar());
            }
        });

        frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frm.pack();
        frm.setVisible(true);
    }

    private static class PrintAction extends AbstractAction {
        private String str;
        public PrintAction(String aPrintStr, KeyStroke aMnemonic) {
            super("Print: " + aPrintStr);
            str = aPrintStr;
            putValue(Action.ACCELERATOR_KEY, aMnemonic);
        }
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println(str);
        }
    }
}
Community
  • 1
  • 1
Suma
  • 33,181
  • 16
  • 123
  • 191