JDK-6517340 : requestFocus on component in JWindow fails on sol64
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 6
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: solaris_9
  • CPU: sparc
  • Submitted: 2007-01-25
  • Updated: 2010-04-04
  • Resolved: 2007-01-29
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
Java HotSpot(TM) Server VM (build 1.5.0_07-b03, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
SunOS qa02sol64 5.9 Generic_118558-37 sun4u sparc

A DESCRIPTION OF THE PROBLEM :
I have a popup window (JWindow) that displays a list.  Calling requestFocus on the list fails.  User has to click in the list to give it focus.

Note that this test case is using mix-weight components. User should avoid having mix weight components whenever is possible.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile and run the code below.

Type some text.
Hit the tab key to make the popup window display.
The list should have focus.

This works on all platforms (linux. mac, and windows) except sol64.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I expect that the List would gain focus since I called request focus.
ACTUAL -
The list does not gain focus.

Oddly, a second call to setVisible in the method showPopup() makes this work.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import java.awt.*;
import java.awt.event.*;

public class TestPopup extends JFrame {
    private JTextArea fText;
    private CWJList fJList = new CWJList();
    private MyScrollPane fScrollPane = new MyScrollPane(fJList);
    private TabFocusListener fFocusList = null;
    private ComponentAdapter fComponentAdapter = null;
    private MouseListener fJListMouseListener = null;
    private HeavyweightPopup fPopup = null;


    public TestPopup()
    {
        setTitle("Test Popup");
        fText = new JTextArea();
        getContentPane().add(fText);
        fText.setDragEnabled(true);

        fText.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), DefaultEditorKit.insertTabAction);
        fText.getActionMap().put(DefaultEditorKit.insertTabAction, new TabAction());

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(400, 400);
        setLocation(300, 300);
        setVisible(true);

        fJList.setBackground(UIManager.getColor("ToolTip.background"));
        fJList.setForeground(UIManager.getColor("ToolTip.foreground"));
        fJList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        fJList.setFocusCycleRoot(true);

        Border jlBord = BorderFactory.createLineBorder(fJList.getBackground(), 4);
        fJList.setFocusTraversalKeysEnabled(false);
        fFocusList = new TabFocusListener();
        Border jsBord = BorderFactory.createLineBorder(Color.black, 2);

        fScrollPane.setBorder(jsBord);
        fScrollPane.setViewportBorder(jlBord);
        registerActions();
        fJListMouseListener = new JListMouseListener();
    }

    /**
     * Register actions and keys with input map and action map.
     */
    private void registerActions()
    {
        StopTabComplAction stc = new StopTabComplAction();
        //ctrl-c
        fJList.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK), "ctrlc");
        fJList.getActionMap().put("ctrlc", stc);
        //esc
        fJList.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "esc");
        fJList.getActionMap().put("esc", stc);
        //enter
        fJList.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "return");
        EnterAction enter = new EnterAction();
        fJList.getActionMap().put("return", enter);

        fJList.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "left");
        fJList.getActionMap().put("left", stc);

        fJList.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "right");
        fJList.getActionMap().put("right", stc);

        Action up = (Action) fJList.getActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0));
        fJList.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_P, KeyEvent.CTRL_MASK), "prev");
        fJList.getActionMap().put("prev", up);

        Action down = (Action) fJList.getActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0));
        fJList.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_MASK), "next");
        fJList.getActionMap().put("next", down);

        //tab
        fJList.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), "tab");
        fJList.getActionMap().put("tab", enter);

    }

    private void setSelectedIndexOnEventThread(int index)
    {
        if (SwingUtilities.isEventDispatchThread())
        {
            fJList.setSelectedIndex(index);
            fJList.ensureIndexIsVisible(index);
        }
        else
        {
            final int theIndex = index;
            SwingUtilities.invokeLater(new Runnable() {
                public void run()
                {
                    setSelectedIndexOnEventThread(theIndex);
                }
            }
            );
        }
    }


    private class TabAction extends AbstractAction {
        public void actionPerformed(ActionEvent e)
        {
            showPopup();
        }
    }


    private void showPopup()
    {
        int startOffset = fText.getCaret().getDot();
        fJList.addFocusListener(fFocusList);
        String[] data = new String[]{"aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii", "jjj", "kkk"};
        fJList.setListData(data);
        fScrollPane.getViewport().setView(fJList);

        Rectangle r;
        try
        {
            r = fText.modelToView(startOffset);
        }
        catch (BadLocationException be)
        {

            fPopup.setVisible(false);
            return;
        }

        Point axis = new Point(r.x, r.y);
        Point displaypoint = computePopupLocation(axis,
                fText,
                fScrollPane.getPreferredSize().width,
                fScrollPane.getPreferredSize().height);

        if (fPopup == null)
        {
            fPopup = HeavyweightPopup.getResizablePopup(SwingUtilities.windowForComponent(fText), fScrollPane, displaypoint.x,
                    displaypoint.y);

        }
        else
        {
            try
            {
                fPopup.setWindowSize(fScrollPane.getPreferredSize());
                Point prevLocation = fPopup.getLocation();
                fPopup.setLocation(prevLocation.x, displaypoint.y);
            }
            catch (IllegalStateException e)
            {
                e.printStackTrace();
            }
        }
        fPopup.setFocusableWindowState(true);
        setSelectedIndexOnEventThread(0);

        fPopup.setVisible(true);
        //If the following line is uncommented, the list gets focus as expected.
        //fPopup.setVisible(true);

        if (fComponentAdapter == null)
        {
            fComponentAdapter = new ComponentAdapter() {
                /**
                 * Invoked when the component's size changes.
                 */
                public void componentResized(ComponentEvent e)
                {
                    fPopup.setVisible(false);
                }

                /**
                 * Invoked when the component's position changes.
                 */
                public void componentMoved(ComponentEvent e)
                {
                    fPopup.setVisible(false);
                }

                /**
                 * Invoked when the component has been made invisible.
                 */
                public void componentHidden(ComponentEvent e)
                {
                    fPopup.setVisible(false);
                }
            };
        }

        fText.addComponentListener(fComponentAdapter);
        fJList.addMouseListener(fJListMouseListener);
        fJList.requestFocus();
    }

    public static Rectangle getScreenBounds()
    {
        Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
        return new Rectangle(0, 0, size.width, size.height);
    }


    private Point computePopupLocation(Point offset, Component comp, int width, int height)
    {

        Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
        Rectangle screenBounds = new Rectangle(0, 0, size.width, size.height);
        javax.swing.SwingUtilities.convertPointToScreen(offset, comp);
        FontMetrics f = fText.getFontMetrics(fText.getFont());

        // Pop up by default.  Don't go off the top or under the taskbar if it
        // is on the top.
        if ((offset.y - height) > 0 && screenBounds.contains(offset.x, offset.y - height))
        {
            offset.y -= height;
            if (!screenBounds.contains(offset.x + width, offset.y))
            {
                //pop up and left. Task bar is on right and will cut off.
                offset.x -= width;
            }
        }
        else if (!screenBounds.contains(offset.x + width, offset.y))
        {
            //pop down and left. Task bar is on right and will cut off.
            offset.x -= width;
        }
        else
        {
            //popping down and right.
            offset.y += f.getHeight();
            offset.x += f.charWidth('n') / 2;
        }

        return offset;
    }


    private static class HeavyweightPopup extends JWindow {
        private static JWindow sWindow;

        private HeavyweightPopup(Window owner, Component contents, int x, int y)
        {
            super(owner);
            sWindow = this;
            getContentPane().add(contents, BorderLayout.CENTER);
            setLocation(x, y);
            setWindowSize(contents.getPreferredSize());
            setFocusTraversalKeysEnabled(false);

        }

        public static HeavyweightPopup getResizablePopup(Window owner, Component contents, int x, int y)
        {
            return new HeavyweightPopup(owner, contents, x, y);
        }


        public void setWindowSize(Dimension d)
        {

            sWindow.setSize(d);
            sWindow.invalidate();
            sWindow.validate();
            sWindow.repaint();

        }


    }

    public class CWJList extends JList {
        /**
         * Returns the preferred number of visible rows.
         *
         * @return an integer indicating the preferred number of rows to display
         *         without using a scroll bar
         */
        public int getVisibleRowCount()
        {
            int size = getModel().getSize();
            if (size > 8)
            {
                return 8;
            }
            return size;
        }
    }

    private class MyScrollPane extends JScrollPane {
        public MyScrollPane(Component c)
        {
            super(c);
            setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
            setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
        }

        public void setVisible(boolean vis)
        {
            Border jlBord = BorderFactory.createLineBorder(UIManager.getColor("ToolTip.background"), 4);
            setViewportBorder(jlBord);
            super.setVisible(vis);
        }
    }

    private class StopTabComplAction extends AbstractAction {
        public void actionPerformed(ActionEvent e)
        {
            fPopup.setVisible(false);
        }
    }

    private class EnterAction extends AbstractAction {
        public void actionPerformed(ActionEvent e)
        {
            //processCompletionSelection();
            fPopup.setVisible(false);
        }
    }


    private class TabFocusListener extends FocusAdapter {
        public void focusLost(FocusEvent e)
        {
            if (e.getOppositeComponent() != null && fText == e.getOppositeComponent())
            {
                fPopup.setVisible(false);
                fText.requestFocus();
            }
        }
    }


    /**
     * Make sure that the focus always returns to the command window component
     * regardless of mouse clicks.  Also, if the user double clicks on the
     * <code>JList</code>, treat that as the selection of a completion.
     * Finally, if the user clicks on a JList with an error readout,
     * remove it.
     */
    private class JListMouseListener extends MouseAdapter {
        /**
         * Invoked when the mouse has been clicked on a component.
         *
         * @param e MouseEvent
         */
        public void mouseClicked(MouseEvent e)
        {
            if (e.getClickCount() > 1)
            {
                fPopup.setVisible(false);
            }
        }

        /**
         * Invoked when a mouse button has been pressed on a component.
         *
         * @param e MouseEvent
         */
        public void mousePressed(MouseEvent e)
        {
            fPopup.setVisible(false);
        }
    }


    public static void main(String[] args)
    {
        new TestPopup();
    }

}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
A second call to setVisible on the JWindow makes this work.

Using a lightweight popup instead also might be an alternative for some.

Comments
EVALUATION The bug is reproducible with both 32- and 64-bit solaris, but only with MToolkit. There is already a change request similar to this: 4851685.
29-01-2007