/*
* Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
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;
import sun.awt.
AWTAccessor;
import sun.awt.
AWTAccessor.
MouseEventAccessor;
/**
* 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™
* 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<
Object>,
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;
private
MouseWheelListener scrollerMouseWheelListener;
/**
* 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 <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();
uninstallScrollerListeners();
// 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() {
if (
scrollerMouseWheelListener != null) {
comboBox.
addMouseWheelListener(
scrollerMouseWheelListener);
}
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() {
if (
scrollerMouseWheelListener != null) {
comboBox.
removeMouseWheelListener(
scrollerMouseWheelListener);
}
super.firePopupMenuWillBecomeInvisible();
comboBox.
firePopupMenuWillBecomeInvisible();
}
protected void
firePopupMenuCanceled() {
if (
scrollerMouseWheelListener != null) {
comboBox.
removeMouseWheelListener(
scrollerMouseWheelListener);
}
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 (
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();
MouseEvent newEvent = 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);
MouseEventAccessor meAccessor =
AWTAccessor.
getMouseEventAccessor();
meAccessor.
setCausedByTouchEvent(
newEvent,
meAccessor.
isCausedByTouchEvent(
e));
e =
newEvent;
}
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 );
installScrollerListeners();
}
/**
* 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 );
}
private void
installScrollerListeners() {
scrollerMouseWheelListener =
getHandler();
if (
scrollerMouseWheelListener != null) {
scroller.
addMouseWheelListener(
scrollerMouseWheelListener);
}
}
private void
uninstallScrollerListeners() {
if (
scrollerMouseWheelListener != null) {
scroller.
removeMouseWheelListener(
scrollerMouseWheelListener);
scrollerMouseWheelListener = null;
}
}
/**
* 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,
MouseWheelListener,
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());
} else {
setListSelection(-1);
}
}
//
// MouseWheelListener
//
public void
mouseWheelMoved(
MouseWheelEvent e) {
e.
consume();
}
}
//
// 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 );
MouseEventAccessor meAccessor =
AWTAccessor.
getMouseEventAccessor();
meAccessor.
setCausedByTouchEvent(
newEvent,
meAccessor.
isCausedByTouchEvent(
e));
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
//=================================================================
}