/* * Copyright (c) 1998, 2014, Oracle and/or its affiliates. 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.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 ComboPopup interface. * * This class represents the ui for the popup portion of the combo box. *

* All event handling is handled by listener classes created with the * createxxxListener() methods and internal classes. * You can change the behavior of this class by overriding the * createxxxListener() methods and supplying your own * event listeners or subclassing from the ones supplied in this class. *

* Warning: * 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™ * has been added to the java.beans package. * Please see {@link java.beans.XMLEncoder}. * * @author Tom Santos * @author Mark Davidson */ @SuppressWarnings("serial") // Same-version serialization only 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() { comboBox.firePopupMenuWillBecomeVisible(); 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 MouseListener or null * @see ComboPopup#getMouseListener */ public MouseListener getMouseListener() { if (mouseListener == null) { mouseListener = createMouseListener(); } return mouseListener; } /** * Implementation of ComboPopup.getMouseMotionListener(). * * @return a MouseMotionListener or null * @see ComboPopup#getMouseMotionListener */ public MouseMotionListener getMouseMotionListener() { if (mouseMotionListener == null) { mouseMotionListener = createMouseMotionListener(); } return mouseMotionListener; } /** * Implementation of ComboPopup.getKeyListener(). * * @return a KeyListener 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() is called from BasicComboPopup.show() method // to let the user change the popup menu from the PopupMenuListener.popupMenuWillBecomeVisible() } 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. * * Warning: * When overriding this method, make sure to maintain the existing * behavior. * * @return a MouseListener 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. * * Warning: * When overriding this method, make sure to maintain the existing * behavior. * * @return a MouseMotionListener 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 KeyListener 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 ListSelectionListener or null */ protected ListSelectionListener createListSelectionListener() { return null; } /** * Creates a list data listener which will be added to the * ComboBoxModel. If this method returns null then * it will not be added to the combo box model. * * @return an instance of a ListDataListener 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 MouseListener 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 MouseMotionListener or null */ protected MouseMotionListener createListMouseMotionListener() { return getHandler(); } /** * Creates a PropertyChangeListener 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 PropertyChangeListener or null */ protected PropertyChangeListener createPropertyChangeListener() { return getHandler(); } /** * Creates an ItemListener which will be added to the * combo box. If this method returns null then it will not * be added to the combo box. *

* Subclasses may override this method to return instances of their own * ItemEvent handlers. * * @return an instance of an ItemListener 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 JList used to display the combo box items */ protected JList createList() { return new JList( comboBox.getModel() ) { public void processMouseEvent(MouseEvent e) { if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) { // Fix for 4234053. Filter out the Control Key from the list. // ie., don't allow CTRL key deselection. Toolkit toolkit = Toolkit.getDefaultToolkit(); e = new MouseEvent((Component)e.getSource(), e.getID(), e.getWhen(), e.getModifiers() ^ toolkit.getMenuShortcutKeyMask(), 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 * uninstallComboBoxModelListeners. * * @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 * (not 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. * *

* Warning: * We recommend that you not * 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. *

* 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. *

* The functionality has been migrated into ItemHandler. * * @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. *

* 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) { @SuppressWarnings("unchecked") JComboBox comboBox = (JComboBox)e.getSource(); String propertyName = e.getPropertyName(); if ( propertyName == "model" ) { @SuppressWarnings("unchecked") ComboBoxModel oldModel = (ComboBoxModel)e.getOldValue(); @SuppressWarnings("unchecked") 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) { @SuppressWarnings("unchecked") 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 //================================================================= }