--- /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
+ //=================================================================
+}