jdk/src/share/classes/javax/swing/plaf/basic/BasicComboPopup.java
changeset 2 90ce3da70b43
child 3977 0da8e3baf0b5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/swing/plaf/basic/BasicComboPopup.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1293 @@
+/*
+ * Copyright 1998-2006 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.swing.plaf.basic;
+
+import javax.accessibility.AccessibleContext;
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.LineBorder;
+import javax.swing.event.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeEvent;
+import java.io.Serializable;
+
+
+/**
+ * This is a basic implementation of the <code>ComboPopup</code> interface.
+ *
+ * This class represents the ui for the popup portion of the combo box.
+ * <p>
+ * All event handling is handled by listener classes created with the
+ * <code>createxxxListener()</code> methods and internal classes.
+ * You can change the behavior of this class by overriding the
+ * <code>createxxxListener()</code> methods and supplying your own
+ * event listeners or subclassing from the ones supplied in this class.
+ * <p>
+ * <strong>Warning:</strong>
+ * Serialized objects of this class will not be compatible with
+ * future Swing releases. The current serialization support is
+ * appropriate for short term storage or RMI between applications running
+ * the same version of Swing.  As of 1.4, support for long term storage
+ * of all JavaBeans<sup><font size="-2">TM</font></sup>
+ * has been added to the <code>java.beans</code> package.
+ * Please see {@link java.beans.XMLEncoder}.
+ *
+ * @author Tom Santos
+ * @author Mark Davidson
+ */
+public class BasicComboPopup extends JPopupMenu implements ComboPopup {
+    // An empty ListMode, this is used when the UI changes to allow
+    // the JList to be gc'ed.
+    private static class EmptyListModelClass implements ListModel,
+                                                        Serializable {
+        public int getSize() { return 0; }
+        public Object getElementAt(int index) { return null; }
+        public void addListDataListener(ListDataListener l) {}
+        public void removeListDataListener(ListDataListener l) {}
+    };
+
+    static final ListModel EmptyListModel = new EmptyListModelClass();
+
+    private static Border LIST_BORDER = new LineBorder(Color.BLACK, 1);
+
+    protected JComboBox                comboBox;
+    /**
+     * This protected field is implementation specific. Do not access directly
+     * or override. Use the accessor methods instead.
+     *
+     * @see #getList
+     * @see #createList
+     */
+    protected JList                    list;
+    /**
+     * This protected field is implementation specific. Do not access directly
+     * or override. Use the create method instead
+     *
+     * @see #createScroller
+     */
+    protected JScrollPane              scroller;
+
+    /**
+     * As of Java 2 platform v1.4 this previously undocumented field is no
+     * longer used.
+     */
+    protected boolean                  valueIsAdjusting = false;
+
+    // Listeners that are required by the ComboPopup interface
+
+    /**
+     * Implementation of all the listener classes.
+     */
+    private Handler handler;
+
+    /**
+     * This protected field is implementation specific. Do not access directly
+     * or override. Use the accessor or create methods instead.
+     *
+     * @see #getMouseMotionListener
+     * @see #createMouseMotionListener
+     */
+    protected MouseMotionListener      mouseMotionListener;
+    /**
+     * This protected field is implementation specific. Do not access directly
+     * or override. Use the accessor or create methods instead.
+     *
+     * @see #getMouseListener
+     * @see #createMouseListener
+     */
+    protected MouseListener            mouseListener;
+
+    /**
+     * This protected field is implementation specific. Do not access directly
+     * or override. Use the accessor or create methods instead.
+     *
+     * @see #getKeyListener
+     * @see #createKeyListener
+     */
+    protected KeyListener              keyListener;
+
+    /**
+     * This protected field is implementation specific. Do not access directly
+     * or override. Use the create method instead.
+     *
+     * @see #createListSelectionListener
+     */
+    protected ListSelectionListener    listSelectionListener;
+
+    // Listeners that are attached to the list
+    /**
+     * This protected field is implementation specific. Do not access directly
+     * or override. Use the create method instead.
+     *
+     * @see #createListMouseListener
+     */
+    protected MouseListener            listMouseListener;
+    /**
+     * This protected field is implementation specific. Do not access directly
+     * or override. Use the create method instead
+     *
+     * @see #createListMouseMotionListener
+     */
+    protected MouseMotionListener      listMouseMotionListener;
+
+    // Added to the combo box for bound properties
+    /**
+     * This protected field is implementation specific. Do not access directly
+     * or override. Use the create method instead
+     *
+     * @see #createPropertyChangeListener
+     */
+    protected PropertyChangeListener   propertyChangeListener;
+
+    // Added to the combo box model
+    /**
+     * This protected field is implementation specific. Do not access directly
+     * or override. Use the create method instead
+     *
+     * @see #createListDataListener
+     */
+    protected ListDataListener         listDataListener;
+
+    /**
+     * This protected field is implementation specific. Do not access directly
+     * or override. Use the create method instead
+     *
+     * @see #createItemListener
+     */
+    protected ItemListener             itemListener;
+
+    /**
+     * This protected field is implementation specific. Do not access directly
+     * or override.
+     */
+    protected Timer                    autoscrollTimer;
+    protected boolean                  hasEntered = false;
+    protected boolean                  isAutoScrolling = false;
+    protected int                      scrollDirection = SCROLL_UP;
+
+    protected static final int         SCROLL_UP = 0;
+    protected static final int         SCROLL_DOWN = 1;
+
+
+    //========================================
+    // begin ComboPopup method implementations
+    //
+
+    /**
+     * Implementation of ComboPopup.show().
+     */
+    public void show() {
+        setListSelection(comboBox.getSelectedIndex());
+
+        Point location = getPopupLocation();
+        show( comboBox, location.x, location.y );
+    }
+
+
+    /**
+     * Implementation of ComboPopup.hide().
+     */
+    public void hide() {
+        MenuSelectionManager manager = MenuSelectionManager.defaultManager();
+        MenuElement [] selection = manager.getSelectedPath();
+        for ( int i = 0 ; i < selection.length ; i++ ) {
+            if ( selection[i] == this ) {
+                manager.clearSelectedPath();
+                break;
+            }
+        }
+        if (selection.length > 0) {
+            comboBox.repaint();
+        }
+    }
+
+    /**
+     * Implementation of ComboPopup.getList().
+     */
+    public JList getList() {
+        return list;
+    }
+
+    /**
+     * Implementation of ComboPopup.getMouseListener().
+     *
+     * @return a <code>MouseListener</code> or null
+     * @see ComboPopup#getMouseListener
+     */
+    public MouseListener getMouseListener() {
+        if (mouseListener == null) {
+            mouseListener = createMouseListener();
+        }
+        return mouseListener;
+    }
+
+    /**
+     * Implementation of ComboPopup.getMouseMotionListener().
+     *
+     * @return a <code>MouseMotionListener</code> or null
+     * @see ComboPopup#getMouseMotionListener
+     */
+    public MouseMotionListener getMouseMotionListener() {
+        if (mouseMotionListener == null) {
+            mouseMotionListener = createMouseMotionListener();
+        }
+        return mouseMotionListener;
+    }
+
+    /**
+     * Implementation of ComboPopup.getKeyListener().
+     *
+     * @return a <code>KeyListener</code> or null
+     * @see ComboPopup#getKeyListener
+     */
+    public KeyListener getKeyListener() {
+        if (keyListener == null) {
+            keyListener = createKeyListener();
+        }
+        return keyListener;
+    }
+
+    /**
+     * Called when the UI is uninstalling.  Since this popup isn't in the component
+     * tree, it won't get it's uninstallUI() called.  It removes the listeners that
+     * were added in addComboBoxListeners().
+     */
+    public void uninstallingUI() {
+        if (propertyChangeListener != null) {
+            comboBox.removePropertyChangeListener( propertyChangeListener );
+        }
+        if (itemListener != null) {
+            comboBox.removeItemListener( itemListener );
+        }
+        uninstallComboBoxModelListeners(comboBox.getModel());
+        uninstallKeyboardActions();
+        uninstallListListeners();
+        // We do this, otherwise the listener the ui installs on
+        // the model (the combobox model in this case) will keep a
+        // reference to the list, causing the list (and us) to never get gced.
+        list.setModel(EmptyListModel);
+    }
+
+    //
+    // end ComboPopup method implementations
+    //======================================
+
+    /**
+     * Removes the listeners from the combo box model
+     *
+     * @param model The combo box model to install listeners
+     * @see #installComboBoxModelListeners
+     */
+    protected void uninstallComboBoxModelListeners( ComboBoxModel model ) {
+        if (model != null && listDataListener != null) {
+            model.removeListDataListener(listDataListener);
+        }
+    }
+
+    protected void uninstallKeyboardActions() {
+        // XXX - shouldn't call this method
+//        comboBox.unregisterKeyboardAction( KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ) );
+    }
+
+
+
+    //===================================================================
+    // begin Initialization routines
+    //
+    public BasicComboPopup( JComboBox combo ) {
+        super();
+        setName("ComboPopup.popup");
+        comboBox = combo;
+
+        setLightWeightPopupEnabled( comboBox.isLightWeightPopupEnabled() );
+
+        // UI construction of the popup.
+        list = createList();
+        list.setName("ComboBox.list");
+        configureList();
+        scroller = createScroller();
+        scroller.setName("ComboBox.scrollPane");
+        configureScroller();
+        configurePopup();
+
+        installComboBoxListeners();
+        installKeyboardActions();
+    }
+
+    // Overriden PopupMenuListener notification methods to inform combo box
+    // PopupMenuListeners.
+
+    protected void firePopupMenuWillBecomeVisible() {
+        super.firePopupMenuWillBecomeVisible();
+        comboBox.firePopupMenuWillBecomeVisible();
+    }
+
+    protected void firePopupMenuWillBecomeInvisible() {
+        super.firePopupMenuWillBecomeInvisible();
+        comboBox.firePopupMenuWillBecomeInvisible();
+    }
+
+    protected void firePopupMenuCanceled() {
+        super.firePopupMenuCanceled();
+        comboBox.firePopupMenuCanceled();
+    }
+
+    /**
+     * Creates a listener
+     * that will watch for mouse-press and release events on the combo box.
+     *
+     * <strong>Warning:</strong>
+     * When overriding this method, make sure to maintain the existing
+     * behavior.
+     *
+     * @return a <code>MouseListener</code> which will be added to
+     * the combo box or null
+     */
+    protected MouseListener createMouseListener() {
+        return getHandler();
+    }
+
+    /**
+     * Creates the mouse motion listener which will be added to the combo
+     * box.
+     *
+     * <strong>Warning:</strong>
+     * When overriding this method, make sure to maintain the existing
+     * behavior.
+     *
+     * @return a <code>MouseMotionListener</code> which will be added to
+     *         the combo box or null
+     */
+    protected MouseMotionListener createMouseMotionListener() {
+        return getHandler();
+    }
+
+    /**
+     * Creates the key listener that will be added to the combo box. If
+     * this method returns null then it will not be added to the combo box.
+     *
+     * @return a <code>KeyListener</code> or null
+     */
+    protected KeyListener createKeyListener() {
+        return null;
+    }
+
+    /**
+     * Creates a list selection listener that watches for selection changes in
+     * the popup's list.  If this method returns null then it will not
+     * be added to the popup list.
+     *
+     * @return an instance of a <code>ListSelectionListener</code> or null
+     */
+    protected ListSelectionListener createListSelectionListener() {
+        return null;
+    }
+
+    /**
+     * Creates a list data listener which will be added to the
+     * <code>ComboBoxModel</code>. If this method returns null then
+     * it will not be added to the combo box model.
+     *
+     * @return an instance of a <code>ListDataListener</code> or null
+     */
+    protected ListDataListener createListDataListener() {
+        return null;
+    }
+
+    /**
+     * Creates a mouse listener that watches for mouse events in
+     * the popup's list. If this method returns null then it will
+     * not be added to the combo box.
+     *
+     * @return an instance of a <code>MouseListener</code> or null
+     */
+    protected MouseListener createListMouseListener() {
+        return getHandler();
+    }
+
+    /**
+     * Creates a mouse motion listener that watches for mouse motion
+     * events in the popup's list. If this method returns null then it will
+     * not be added to the combo box.
+     *
+     * @return an instance of a <code>MouseMotionListener</code> or null
+     */
+    protected MouseMotionListener createListMouseMotionListener() {
+        return getHandler();
+    }
+
+    /**
+     * Creates a <code>PropertyChangeListener</code> which will be added to
+     * the combo box. If this method returns null then it will not
+     * be added to the combo box.
+     *
+     * @return an instance of a <code>PropertyChangeListener</code> or null
+     */
+    protected PropertyChangeListener createPropertyChangeListener() {
+        return getHandler();
+    }
+
+    /**
+     * Creates an <code>ItemListener</code> which will be added to the
+     * combo box. If this method returns null then it will not
+     * be added to the combo box.
+     * <p>
+     * Subclasses may override this method to return instances of their own
+     * ItemEvent handlers.
+     *
+     * @return an instance of an <code>ItemListener</code> or null
+     */
+    protected ItemListener createItemListener() {
+        return getHandler();
+    }
+
+    private Handler getHandler() {
+        if (handler == null) {
+            handler = new Handler();
+        }
+        return handler;
+    }
+
+    /**
+     * Creates the JList used in the popup to display
+     * the items in the combo box model. This method is called when the UI class
+     * is created.
+     *
+     * @return a <code>JList</code> used to display the combo box items
+     */
+    protected JList createList() {
+        return new JList( comboBox.getModel() ) {
+            public void processMouseEvent(MouseEvent e)  {
+                if (e.isControlDown())  {
+                    // Fix for 4234053. Filter out the Control Key from the list.
+                    // ie., don't allow CTRL key deselection.
+                    e = new MouseEvent((Component)e.getSource(), e.getID(), e.getWhen(),
+                                       e.getModifiers() ^ InputEvent.CTRL_MASK,
+                                       e.getX(), e.getY(),
+                                       e.getXOnScreen(), e.getYOnScreen(),
+                                       e.getClickCount(),
+                                       e.isPopupTrigger(),
+                                       MouseEvent.NOBUTTON);
+                }
+                super.processMouseEvent(e);
+            }
+        };
+    }
+
+    /**
+     * Configures the list which is used to hold the combo box items in the
+     * popup. This method is called when the UI class
+     * is created.
+     *
+     * @see #createList
+     */
+    protected void configureList() {
+        list.setFont( comboBox.getFont() );
+        list.setForeground( comboBox.getForeground() );
+        list.setBackground( comboBox.getBackground() );
+        list.setSelectionForeground( UIManager.getColor( "ComboBox.selectionForeground" ) );
+        list.setSelectionBackground( UIManager.getColor( "ComboBox.selectionBackground" ) );
+        list.setBorder( null );
+        list.setCellRenderer( comboBox.getRenderer() );
+        list.setFocusable( false );
+        list.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
+        setListSelection( comboBox.getSelectedIndex() );
+        installListListeners();
+    }
+
+    /**
+     * Adds the listeners to the list control.
+     */
+    protected void installListListeners() {
+        if ((listMouseListener = createListMouseListener()) != null) {
+            list.addMouseListener( listMouseListener );
+        }
+        if ((listMouseMotionListener = createListMouseMotionListener()) != null) {
+            list.addMouseMotionListener( listMouseMotionListener );
+        }
+        if ((listSelectionListener = createListSelectionListener()) != null) {
+            list.addListSelectionListener( listSelectionListener );
+        }
+    }
+
+    void uninstallListListeners() {
+        if (listMouseListener != null) {
+            list.removeMouseListener(listMouseListener);
+            listMouseListener = null;
+        }
+        if (listMouseMotionListener != null) {
+            list.removeMouseMotionListener(listMouseMotionListener);
+            listMouseMotionListener = null;
+        }
+        if (listSelectionListener != null) {
+            list.removeListSelectionListener(listSelectionListener);
+            listSelectionListener = null;
+        }
+        handler = null;
+    }
+
+    /**
+     * Creates the scroll pane which houses the scrollable list.
+     */
+    protected JScrollPane createScroller() {
+        JScrollPane sp = new JScrollPane( list,
+                                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
+                                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
+        sp.setHorizontalScrollBar(null);
+        return sp;
+    }
+
+    /**
+     * Configures the scrollable portion which holds the list within
+     * the combo box popup. This method is called when the UI class
+     * is created.
+     */
+    protected void configureScroller() {
+        scroller.setFocusable( false );
+        scroller.getVerticalScrollBar().setFocusable( false );
+        scroller.setBorder( null );
+    }
+
+    /**
+     * Configures the popup portion of the combo box. This method is called
+     * when the UI class is created.
+     */
+    protected void configurePopup() {
+        setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) );
+        setBorderPainted( true );
+        setBorder(LIST_BORDER);
+        setOpaque( false );
+        add( scroller );
+        setDoubleBuffered( true );
+        setFocusable( false );
+    }
+
+    /**
+     * This method adds the necessary listeners to the JComboBox.
+     */
+    protected void installComboBoxListeners() {
+        if ((propertyChangeListener = createPropertyChangeListener()) != null) {
+            comboBox.addPropertyChangeListener(propertyChangeListener);
+        }
+        if ((itemListener = createItemListener()) != null) {
+            comboBox.addItemListener(itemListener);
+        }
+        installComboBoxModelListeners(comboBox.getModel());
+    }
+
+    /**
+     * Installs the listeners on the combo box model. Any listeners installed
+     * on the combo box model should be removed in
+     * <code>uninstallComboBoxModelListeners</code>.
+     *
+     * @param model The combo box model to install listeners
+     * @see #uninstallComboBoxModelListeners
+     */
+    protected void installComboBoxModelListeners( ComboBoxModel model ) {
+        if (model != null && (listDataListener = createListDataListener()) != null) {
+            model.addListDataListener(listDataListener);
+        }
+    }
+
+    protected void installKeyboardActions() {
+
+        /* XXX - shouldn't call this method. take it out for testing.
+        ActionListener action = new ActionListener() {
+            public void actionPerformed(ActionEvent e){
+            }
+        };
+
+        comboBox.registerKeyboardAction( action,
+                                         KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ),
+                                         JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); */
+
+    }
+
+    //
+    // end Initialization routines
+    //=================================================================
+
+
+    //===================================================================
+    // begin Event Listenters
+    //
+
+    /**
+     * A listener to be registered upon the combo box
+     * (<em>not</em> its popup menu)
+     * to handle mouse events
+     * that affect the state of the popup menu.
+     * The main purpose of this listener is to make the popup menu
+     * appear and disappear.
+     * This listener also helps
+     * with click-and-drag scenarios by setting the selection if the mouse was
+     * released over the list during a drag.
+     *
+     * <p>
+     * <strong>Warning:</strong>
+     * We recommend that you <em>not</em>
+     * create subclasses of this class.
+     * If you absolutely must create a subclass,
+     * be sure to invoke the superclass
+     * version of each method.
+     *
+     * @see BasicComboPopup#createMouseListener
+     */
+    protected class InvocationMouseHandler extends MouseAdapter {
+        /**
+         * Responds to mouse-pressed events on the combo box.
+         *
+         * @param e the mouse-press event to be handled
+         */
+        public void mousePressed( MouseEvent e ) {
+            getHandler().mousePressed(e);
+        }
+
+        /**
+         * Responds to the user terminating
+         * a click or drag that began on the combo box.
+         *
+         * @param e the mouse-release event to be handled
+         */
+        public void mouseReleased( MouseEvent e ) {
+            getHandler().mouseReleased(e);
+        }
+    }
+
+    /**
+     * This listener watches for dragging and updates the current selection in the
+     * list if it is dragging over the list.
+     */
+    protected class InvocationMouseMotionHandler extends MouseMotionAdapter {
+        public void mouseDragged( MouseEvent e ) {
+            getHandler().mouseDragged(e);
+        }
+    }
+
+    /**
+     * As of Java 2 platform v 1.4, this class is now obsolete and is only included for
+     * backwards API compatibility. Do not instantiate or subclass.
+     * <p>
+     * All the functionality of this class has been included in
+     * BasicComboBoxUI ActionMap/InputMap methods.
+     */
+    public class InvocationKeyHandler extends KeyAdapter {
+        public void keyReleased( KeyEvent e ) {}
+    }
+
+    /**
+     * As of Java 2 platform v 1.4, this class is now obsolete, doesn't do anything, and
+     * is only included for backwards API compatibility. Do not call or
+     * override.
+     */
+    protected class ListSelectionHandler implements ListSelectionListener {
+        public void valueChanged( ListSelectionEvent e ) {}
+    }
+
+    /**
+     * As of 1.4, this class is now obsolete, doesn't do anything, and
+     * is only included for backwards API compatibility. Do not call or
+     * override.
+     * <p>
+     * The functionality has been migrated into <code>ItemHandler</code>.
+     *
+     * @see #createItemListener
+     */
+    public class ListDataHandler implements ListDataListener {
+        public void contentsChanged( ListDataEvent e ) {}
+
+        public void intervalAdded( ListDataEvent e ) {
+        }
+
+        public void intervalRemoved( ListDataEvent e ) {
+        }
+    }
+
+    /**
+     * This listener hides the popup when the mouse is released in the list.
+     */
+    protected class ListMouseHandler extends MouseAdapter {
+        public void mousePressed( MouseEvent e ) {
+        }
+        public void mouseReleased(MouseEvent anEvent) {
+            getHandler().mouseReleased(anEvent);
+        }
+    }
+
+    /**
+     * This listener changes the selected item as you move the mouse over the list.
+     * The selection change is not committed to the model, this is for user feedback only.
+     */
+    protected class ListMouseMotionHandler extends MouseMotionAdapter {
+        public void mouseMoved( MouseEvent anEvent ) {
+            getHandler().mouseMoved(anEvent);
+        }
+    }
+
+    /**
+     * This listener watches for changes to the selection in the
+     * combo box.
+     */
+    protected class ItemHandler implements ItemListener {
+        public void itemStateChanged( ItemEvent e ) {
+            getHandler().itemStateChanged(e);
+        }
+    }
+
+    /**
+     * This listener watches for bound properties that have changed in the
+     * combo box.
+     * <p>
+     * Subclasses which wish to listen to combo box property changes should
+     * call the superclass methods to ensure that the combo popup correctly
+     * handles property changes.
+     *
+     * @see #createPropertyChangeListener
+     */
+    protected class PropertyChangeHandler implements PropertyChangeListener {
+        public void propertyChange( PropertyChangeEvent e ) {
+            getHandler().propertyChange(e);
+        }
+    }
+
+
+    private class AutoScrollActionHandler implements ActionListener {
+        private int direction;
+
+        AutoScrollActionHandler(int direction) {
+            this.direction = direction;
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            if (direction == SCROLL_UP) {
+                autoScrollUp();
+            }
+            else {
+                autoScrollDown();
+            }
+        }
+    }
+
+
+    private class Handler implements ItemListener, MouseListener,
+                          MouseMotionListener, PropertyChangeListener,
+                          Serializable {
+        //
+        // MouseListener
+        // NOTE: this is added to both the JList and JComboBox
+        //
+        public void mouseClicked(MouseEvent e) {
+        }
+
+        public void mousePressed(MouseEvent e) {
+            if (e.getSource() == list) {
+                return;
+            }
+            if (!SwingUtilities.isLeftMouseButton(e) || !comboBox.isEnabled())
+                return;
+
+            if ( comboBox.isEditable() ) {
+                Component comp = comboBox.getEditor().getEditorComponent();
+                if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) {
+                    comp.requestFocus();
+                }
+            }
+            else if (comboBox.isRequestFocusEnabled()) {
+                comboBox.requestFocus();
+            }
+            togglePopup();
+        }
+
+        public void mouseReleased(MouseEvent e) {
+            if (e.getSource() == list) {
+                if (list.getModel().getSize() > 0) {
+                    // JList mouse listener
+                    if (comboBox.getSelectedIndex() == list.getSelectedIndex()) {
+                        comboBox.getEditor().setItem(list.getSelectedValue());
+                    }
+                    comboBox.setSelectedIndex(list.getSelectedIndex());
+                }
+                comboBox.setPopupVisible(false);
+                // workaround for cancelling an edited item (bug 4530953)
+                if (comboBox.isEditable() && comboBox.getEditor() != null) {
+                    comboBox.configureEditor(comboBox.getEditor(),
+                                             comboBox.getSelectedItem());
+                }
+                return;
+            }
+            // JComboBox mouse listener
+            Component source = (Component)e.getSource();
+            Dimension size = source.getSize();
+            Rectangle bounds = new Rectangle( 0, 0, size.width - 1, size.height - 1 );
+            if ( !bounds.contains( e.getPoint() ) ) {
+                MouseEvent newEvent = convertMouseEvent( e );
+                Point location = newEvent.getPoint();
+                Rectangle r = new Rectangle();
+                list.computeVisibleRect( r );
+                if ( r.contains( location ) ) {
+                    if (comboBox.getSelectedIndex() == list.getSelectedIndex()) {
+                        comboBox.getEditor().setItem(list.getSelectedValue());
+                    }
+                    comboBox.setSelectedIndex(list.getSelectedIndex());
+                }
+                comboBox.setPopupVisible(false);
+            }
+            hasEntered = false;
+            stopAutoScrolling();
+        }
+
+        public void mouseEntered(MouseEvent e) {
+        }
+
+        public void mouseExited(MouseEvent e) {
+        }
+
+        //
+        // MouseMotionListener:
+        // NOTE: this is added to both the List and ComboBox
+        //
+        public void mouseMoved(MouseEvent anEvent) {
+            if (anEvent.getSource() == list) {
+                Point location = anEvent.getPoint();
+                Rectangle r = new Rectangle();
+                list.computeVisibleRect( r );
+                if ( r.contains( location ) ) {
+                    updateListBoxSelectionForEvent( anEvent, false );
+                }
+            }
+        }
+
+        public void mouseDragged( MouseEvent e ) {
+            if (e.getSource() == list) {
+                return;
+            }
+            if ( isVisible() ) {
+                MouseEvent newEvent = convertMouseEvent( e );
+                Rectangle r = new Rectangle();
+                list.computeVisibleRect( r );
+
+                if ( newEvent.getPoint().y >= r.y && newEvent.getPoint().y <= r.y + r.height - 1 ) {
+                    hasEntered = true;
+                    if ( isAutoScrolling ) {
+                        stopAutoScrolling();
+                    }
+                    Point location = newEvent.getPoint();
+                    if ( r.contains( location ) ) {
+                        updateListBoxSelectionForEvent( newEvent, false );
+                    }
+                }
+                else {
+                    if ( hasEntered ) {
+                        int directionToScroll = newEvent.getPoint().y < r.y ? SCROLL_UP : SCROLL_DOWN;
+                        if ( isAutoScrolling && scrollDirection != directionToScroll ) {
+                            stopAutoScrolling();
+                            startAutoScrolling( directionToScroll );
+                        }
+                        else if ( !isAutoScrolling ) {
+                            startAutoScrolling( directionToScroll );
+                        }
+                    }
+                    else {
+                        if ( e.getPoint().y < 0 ) {
+                            hasEntered = true;
+                            startAutoScrolling( SCROLL_UP );
+                        }
+                    }
+                }
+            }
+        }
+
+        //
+        // PropertyChangeListener
+        //
+        public void propertyChange(PropertyChangeEvent e) {
+            JComboBox comboBox = (JComboBox)e.getSource();
+            String propertyName = e.getPropertyName();
+
+            if ( propertyName == "model" ) {
+                ComboBoxModel oldModel = (ComboBoxModel)e.getOldValue();
+                ComboBoxModel newModel = (ComboBoxModel)e.getNewValue();
+                uninstallComboBoxModelListeners(oldModel);
+                installComboBoxModelListeners(newModel);
+
+                list.setModel(newModel);
+
+                if ( isVisible() ) {
+                    hide();
+                }
+            }
+            else if ( propertyName == "renderer" ) {
+                list.setCellRenderer( comboBox.getRenderer() );
+                if ( isVisible() ) {
+                    hide();
+                }
+            }
+            else if (propertyName == "componentOrientation") {
+                // Pass along the new component orientation
+                // to the list and the scroller
+
+                ComponentOrientation o =(ComponentOrientation)e.getNewValue();
+
+                JList list = getList();
+                if (list!=null && list.getComponentOrientation()!=o) {
+                    list.setComponentOrientation(o);
+                }
+
+                if (scroller!=null && scroller.getComponentOrientation()!=o) {
+                    scroller.setComponentOrientation(o);
+                }
+
+                if (o!=getComponentOrientation()) {
+                    setComponentOrientation(o);
+                }
+            }
+            else if (propertyName == "lightWeightPopupEnabled") {
+                setLightWeightPopupEnabled(comboBox.isLightWeightPopupEnabled());
+            }
+        }
+
+        //
+        // ItemListener
+        //
+        public void itemStateChanged( ItemEvent e ) {
+            if (e.getStateChange() == ItemEvent.SELECTED) {
+                JComboBox comboBox = (JComboBox)e.getSource();
+                setListSelection(comboBox.getSelectedIndex());
+            }
+        }
+    }
+
+    //
+    // end Event Listeners
+    //=================================================================
+
+
+    /**
+     * Overridden to unconditionally return false.
+     */
+    public boolean isFocusTraversable() {
+        return false;
+    }
+
+    //===================================================================
+    // begin Autoscroll methods
+    //
+
+    /**
+     * This protected method is implementation specific and should be private.
+     * do not call or override.
+     */
+    protected void startAutoScrolling( int direction ) {
+        // XXX - should be a private method within InvocationMouseMotionHandler
+        // if possible.
+        if ( isAutoScrolling ) {
+            autoscrollTimer.stop();
+        }
+
+        isAutoScrolling = true;
+
+        if ( direction == SCROLL_UP ) {
+            scrollDirection = SCROLL_UP;
+            Point convertedPoint = SwingUtilities.convertPoint( scroller, new Point( 1, 1 ), list );
+            int top = list.locationToIndex( convertedPoint );
+            list.setSelectedIndex( top );
+
+            autoscrollTimer = new Timer( 100, new AutoScrollActionHandler(
+                                             SCROLL_UP) );
+        }
+        else if ( direction == SCROLL_DOWN ) {
+            scrollDirection = SCROLL_DOWN;
+            Dimension size = scroller.getSize();
+            Point convertedPoint = SwingUtilities.convertPoint( scroller,
+                                                                new Point( 1, (size.height - 1) - 2 ),
+                                                                list );
+            int bottom = list.locationToIndex( convertedPoint );
+            list.setSelectedIndex( bottom );
+
+            autoscrollTimer = new Timer(100, new AutoScrollActionHandler(
+                                            SCROLL_DOWN));
+        }
+        autoscrollTimer.start();
+    }
+
+    /**
+     * This protected method is implementation specific and should be private.
+     * do not call or override.
+     */
+    protected void stopAutoScrolling() {
+        isAutoScrolling = false;
+
+        if ( autoscrollTimer != null ) {
+            autoscrollTimer.stop();
+            autoscrollTimer = null;
+        }
+    }
+
+    /**
+     * This protected method is implementation specific and should be private.
+     * do not call or override.
+     */
+    protected void autoScrollUp() {
+        int index = list.getSelectedIndex();
+        if ( index > 0 ) {
+            list.setSelectedIndex( index - 1 );
+            list.ensureIndexIsVisible( index - 1 );
+        }
+    }
+
+    /**
+     * This protected method is implementation specific and should be private.
+     * do not call or override.
+     */
+    protected void autoScrollDown() {
+        int index = list.getSelectedIndex();
+        int lastItem = list.getModel().getSize() - 1;
+        if ( index < lastItem ) {
+            list.setSelectedIndex( index + 1 );
+            list.ensureIndexIsVisible( index + 1 );
+        }
+    }
+
+    //
+    // end Autoscroll methods
+    //=================================================================
+
+
+    //===================================================================
+    // begin Utility methods
+    //
+
+    /**
+     * Gets the AccessibleContext associated with this BasicComboPopup.
+     * The AccessibleContext will have its parent set to the ComboBox.
+     *
+     * @return an AccessibleContext for the BasicComboPopup
+     * @since 1.5
+     */
+    public AccessibleContext getAccessibleContext() {
+        AccessibleContext context = super.getAccessibleContext();
+        context.setAccessibleParent(comboBox);
+        return context;
+    }
+
+
+    /**
+     * This is is a utility method that helps event handlers figure out where to
+     * send the focus when the popup is brought up.  The standard implementation
+     * delegates the focus to the editor (if the combo box is editable) or to
+     * the JComboBox if it is not editable.
+     */
+    protected void delegateFocus( MouseEvent e ) {
+        if ( comboBox.isEditable() ) {
+            Component comp = comboBox.getEditor().getEditorComponent();
+            if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) {
+                comp.requestFocus();
+            }
+        }
+        else if (comboBox.isRequestFocusEnabled()) {
+            comboBox.requestFocus();
+        }
+    }
+
+    /**
+     * Makes the popup visible if it is hidden and makes it hidden if it is
+     * visible.
+     */
+    protected void togglePopup() {
+        if ( isVisible() ) {
+            hide();
+        }
+        else {
+            show();
+        }
+    }
+
+    /**
+     * Sets the list selection index to the selectedIndex. This
+     * method is used to synchronize the list selection with the
+     * combo box selection.
+     *
+     * @param selectedIndex the index to set the list
+     */
+    private void setListSelection(int selectedIndex) {
+        if ( selectedIndex == -1 ) {
+            list.clearSelection();
+        }
+        else {
+            list.setSelectedIndex( selectedIndex );
+            list.ensureIndexIsVisible( selectedIndex );
+        }
+    }
+
+    protected MouseEvent convertMouseEvent( MouseEvent e ) {
+        Point convertedPoint = SwingUtilities.convertPoint( (Component)e.getSource(),
+                                                            e.getPoint(), list );
+        MouseEvent newEvent = new MouseEvent( (Component)e.getSource(),
+                                              e.getID(),
+                                              e.getWhen(),
+                                              e.getModifiers(),
+                                              convertedPoint.x,
+                                              convertedPoint.y,
+                                              e.getXOnScreen(),
+                                              e.getYOnScreen(),
+                                              e.getClickCount(),
+                                              e.isPopupTrigger(),
+                                              MouseEvent.NOBUTTON );
+        return newEvent;
+    }
+
+
+    /**
+     * Retrieves the height of the popup based on the current
+     * ListCellRenderer and the maximum row count.
+     */
+    protected int getPopupHeightForRowCount(int maxRowCount) {
+        // Set the cached value of the minimum row count
+        int minRowCount = Math.min( maxRowCount, comboBox.getItemCount() );
+        int height = 0;
+        ListCellRenderer renderer = list.getCellRenderer();
+        Object value = null;
+
+        for ( int i = 0; i < minRowCount; ++i ) {
+            value = list.getModel().getElementAt( i );
+            Component c = renderer.getListCellRendererComponent( list, value, i, false, false );
+            height += c.getPreferredSize().height;
+        }
+
+        if (height == 0) {
+            height = comboBox.getHeight();
+        }
+
+        Border border = scroller.getViewportBorder();
+        if (border != null) {
+            Insets insets = border.getBorderInsets(null);
+            height += insets.top + insets.bottom;
+        }
+
+        border = scroller.getBorder();
+        if (border != null) {
+            Insets insets = border.getBorderInsets(null);
+            height += insets.top + insets.bottom;
+        }
+
+        return height;
+    }
+
+    /**
+     * Calculate the placement and size of the popup portion of the combo box based
+     * on the combo box location and the enclosing screen bounds. If
+     * no transformations are required, then the returned rectangle will
+     * have the same values as the parameters.
+     *
+     * @param px starting x location
+     * @param py starting y location
+     * @param pw starting width
+     * @param ph starting height
+     * @return a rectangle which represents the placement and size of the popup
+     */
+    protected Rectangle computePopupBounds(int px,int py,int pw,int ph) {
+        Toolkit toolkit = Toolkit.getDefaultToolkit();
+        Rectangle screenBounds;
+
+        // Calculate the desktop dimensions relative to the combo box.
+        GraphicsConfiguration gc = comboBox.getGraphicsConfiguration();
+        Point p = new Point();
+        SwingUtilities.convertPointFromScreen(p, comboBox);
+        if (gc != null) {
+            Insets screenInsets = toolkit.getScreenInsets(gc);
+            screenBounds = gc.getBounds();
+            screenBounds.width -= (screenInsets.left + screenInsets.right);
+            screenBounds.height -= (screenInsets.top + screenInsets.bottom);
+            screenBounds.x += (p.x + screenInsets.left);
+            screenBounds.y += (p.y + screenInsets.top);
+        }
+        else {
+            screenBounds = new Rectangle(p, toolkit.getScreenSize());
+        }
+
+        Rectangle rect = new Rectangle(px,py,pw,ph);
+        if (py+ph > screenBounds.y+screenBounds.height
+            && ph < screenBounds.height) {
+            rect.y = -rect.height;
+        }
+        return rect;
+    }
+
+    /**
+     * Calculates the upper left location of the Popup.
+     */
+    private Point getPopupLocation() {
+        Dimension popupSize = comboBox.getSize();
+        Insets insets = getInsets();
+
+        // reduce the width of the scrollpane by the insets so that the popup
+        // is the same width as the combo box.
+        popupSize.setSize(popupSize.width - (insets.right + insets.left),
+                          getPopupHeightForRowCount( comboBox.getMaximumRowCount()));
+        Rectangle popupBounds = computePopupBounds( 0, comboBox.getBounds().height,
+                                                    popupSize.width, popupSize.height);
+        Dimension scrollSize = popupBounds.getSize();
+        Point popupLocation = popupBounds.getLocation();
+
+        scroller.setMaximumSize( scrollSize );
+        scroller.setPreferredSize( scrollSize );
+        scroller.setMinimumSize( scrollSize );
+
+        list.revalidate();
+
+        return popupLocation;
+    }
+
+    /**
+     * A utility method used by the event listeners.  Given a mouse event, it changes
+     * the list selection to the list item below the mouse.
+     */
+    protected void updateListBoxSelectionForEvent(MouseEvent anEvent,boolean shouldScroll) {
+        // XXX - only seems to be called from this class. shouldScroll flag is
+        // never true
+        Point location = anEvent.getPoint();
+        if ( list == null )
+            return;
+        int index = list.locationToIndex(location);
+        if ( index == -1 ) {
+            if ( location.y < 0 )
+                index = 0;
+            else
+                index = comboBox.getModel().getSize() - 1;
+        }
+        if ( list.getSelectedIndex() != index ) {
+            list.setSelectedIndex(index);
+            if ( shouldScroll )
+                list.ensureIndexIsVisible(index);
+        }
+    }
+
+    //
+    // end Utility methods
+    //=================================================================
+}