Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4412066

JToolTip/VK_ESCAPE problem still exists; Action.isEnabled unreliable

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Fixed
    • Icon: P4 P4
    • 1.4.0
    • 1.3.0
    • client-libs
    • beta
    • x86
    • windows_98



      Name: yyT116575 Date: 02/05/2001


      java version "1.3.0"
      Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0-C)
      Java HotSpot(TM) Client VM (build 1.3.0-C, mixed mode)


      I've run into what seems to be a bug involving Action.isEnabled,
        ToolTipManager and VK_ESCAPE. It's rather difficult to explain,
      but here are the points that lead to the heart of the problem:

      1. Tooltips should only consume the VK_ESCAPE key event
         when they're visible (see bug 4256046). If they are,
         they should hide themselves, if they're not, they
         should ignore VK_ESCAPE (allowing it to be handled
         elsewhere).

      2. In order to achieve this, ToolTipManager will register
         an action for the VK_ESCAPE keystroke that contains
         the following lines:

         public boolean isEnabled() {
           // Only enable when the tooltip is showing, otherwise
           // we will get in the way of any UI actions.
           return tipShowing;
         }

      3. This design relies on isEnabled being called and checked
         each time before the actionPerformed method is called.
         However, my understanding is that changes in the enabled
         state of an action must be done by calling setEnabled in
         order to let the attached listeners know about it (see
         also bug 4200318).

      4. Anyway, even if dynamically calling isEnabled instead is
         an intentional and proper design, it doesn't always work.
         In some of my experiments with the key event handling of
         JComponents there was at least one case in which the
         actionPerformed method of an action was called although
         isEnabled returned false at that time. This was depending
         on several factors and was not easily reproducible.

      5. Please take a look at the attached test case ActionProblem1.
         It puts a JTree on the screen that has an action registered
         for the X key - however, the isEnabled method of the action
         always returns false. Using the logic applied to the tooltip
         case that means that the action isn't enabled and therefore
         may never be performed. And indeed, typing X will not
         activate the action. But if you actually press X and hold
         down the key for a while you'll see (at least I do on my
         machine) that the action WILL now be performed (see the
         debug output). Something's wrong here.

      6. The evaluation of bug 4256046 says that the problem
         causing invisible tooltips to consume VK_ESCAPE is fixed
         in 1.3 and therefore not reproducible. I'm not sure it
         really is. See the second test case, ActionProblem2.
         When you click on the button, a dialog containing a tree
         will appear. The dialog is programmed to hide itself when
         a VK_ESCAPE event occurs (this is done manually, see also
         bug 4144757). The tree is programmed *not* to consume
         VK_ESCAPE (this is done manually too, see also bug 4306584).
         Note that the tree also has a tooltip set(!)

      7. So where's the problem here? Run the test case, open the
         dialog by clicking on the button, press ESCAPE, the dialog
         will close as expected. But this will only work once.
         If you open the dialog again and then press ESCAPE again,
         nothing happens. You need to hit ESCAPE a second time to
         actually close the dialog. My guess is that what happens
         is that the action registered by the ToolTipManager somehow
         catches the first ESCAPE even though no tooltip is visible.
         If the two lines responsible for registering the tooltip(s)
         are removed from the test case, everything works fine.

      8. My personal experience is that if you have a component
         that sometimes should "catch" a specific key event (to
         perform some action when it occurs) and sometimes should
         just ignore that key event (without consuming it), your
         best bet is to use a plain KeyListener and stay away
         from Actions and isEnabled.

      ===

      Test case 1:

      import java.awt.*;
      import java.awt.event.*;
      import javax.swing.*;

      public class ActionProblem1 extends JFrame {
        public ActionProblem1() {
          JTree tree = new JTree();
          
          tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_X, 0), "xAction");
          tree.getActionMap().put("xAction", new AbstractAction() {
            public boolean isEnabled() {
              return false;
            }
              
            public void actionPerformed(ActionEvent ev) {
              System.out.println("xAction.actionPerformed called.");
            }
          });
            
          setContentPane(tree);
          
          setSize(new Dimension(384, 384));
          setLocation((getToolkit().getScreenSize().width - getWidth()) / 2,
              ((getToolkit().getScreenSize().height) - getHeight()) / 2);
        }
        
        public static void main(String[] args) {
          new ActionProblem1().setVisible(true);
        }
      }

      ===

      Test case 2:

      import java.awt.*;
      import java.awt.event.*;
      import javax.swing.*;

      public class ActionProblem2 extends JFrame {
        private static class TestDialog extends JDialog {
          public TestDialog(Frame owner) {
            // modal dialog
            super(owner, "TestDialog", true);
            
            // pressing VK_ESCAPE should close the dialog
            getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
                KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancelDialog");
            getRootPane().getActionMap().put("cancelDialog", new AbstractAction() {
              public void actionPerformed(ActionEvent ev) {
                processWindowEvent(new WindowEvent(
                    TestDialog.this, WindowEvent.WINDOW_CLOSING));
              }
            });
              
            // create a tree
            JTree tree = new JTree();
            
            // "remove" VK_ESCAPE from the tree's input map
            // by shadowing it with a bogus action
            // this means that the tree will NOT consume VK_ESCAPE
            tree.getInputMap(JTree.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
                KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "noAction");
            
            // register component and set tooltip
            ToolTipManager.sharedInstance().registerComponent(tree);
            tree.setToolTipText("This is a tree.");
            // overriding getToolTipText(MouseEvent ev)
            // instead will have the same effect
            
            setContentPane(tree);
            setSize(new Dimension(256, 256));
            setLocation((getToolkit().getScreenSize().width - getWidth()) / 2,
                ((getToolkit().getScreenSize().height) - getHeight()) / 2);
          }
        }
        
        public ActionProblem2() {
          final TestDialog dlg = new TestDialog(ActionProblem2.this);
          
          JButton but = new JButton("Show Dialog");
          but.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ev) {
              dlg.setVisible(true);
            }
          });
            
          setContentPane(but);
          
          setSize(new Dimension(128, 128));
          setLocation((getToolkit().getScreenSize().width - getWidth()) / 2,
              ((getToolkit().getScreenSize().height) - getHeight()) / 2);
        }
        
        public static void main(String[] args) {
          new ActionProblem2().setVisible(true);
        }
      }
      (Review ID: 116315)
      ======================================================================

            svioletsunw Scott Violet (Inactive)
            yyoungsunw Yung-ching Young (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: