1. /*
  2. * @(#)BasicComboPopup.java 1.73 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.basic;
  8. import javax.swing.*;
  9. import javax.swing.event.*;
  10. import java.awt.*;
  11. import java.awt.event.*;
  12. import java.beans.PropertyChangeListener;
  13. import java.beans.PropertyChangeEvent;
  14. import java.io.Serializable;
  15. /**
  16. * This is a basic implementation of the <code>ComboPopup</code> interface.
  17. *
  18. * This class represents the ui for the popup portion of the combo box.
  19. * <p>
  20. * All event handling is handled by listener classes created with the
  21. * <code>createxxxListener()</code> methods and internal classes.
  22. * You can change the behavior of this class by overriding the
  23. * <code>createxxxListener()</code> methods and supplying your own
  24. * event listeners or subclassing from the ones supplied in this class.
  25. * <p>
  26. * <strong>Warning:</strong>
  27. * Serialized objects of this class will not be compatible with
  28. * future Swing releases. The current serialization support is
  29. * appropriate for short term storage or RMI between applications running
  30. * the same version of Swing. As of 1.4, support for long term storage
  31. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  32. * has been added to the <code>java.beans</code> package.
  33. * Please see {@link java.beans.XMLEncoder}.
  34. *
  35. * @version 1.73 01/23/03
  36. * @author Tom Santos
  37. * @author Mark Davidson
  38. */
  39. public class BasicComboPopup extends JPopupMenu implements ComboPopup {
  40. // An empty ListMode, this is used when the UI changes to allow
  41. // the JList to be gc'ed.
  42. static final ListModel EmptyListModel = new ListModel() {
  43. public int getSize() { return 0; }
  44. public Object getElementAt(int index) { return null; }
  45. public void addListDataListener(ListDataListener l) {}
  46. public void removeListDataListener(ListDataListener l) {}
  47. };
  48. protected JComboBox comboBox;
  49. /**
  50. * This protected field is implementation specific. Do not access directly
  51. * or override. Use the accessor methods instead.
  52. *
  53. * @see #getList
  54. * @see #createList
  55. */
  56. protected JList list;
  57. /**
  58. * This protected field is implementation specific. Do not access directly
  59. * or override. Use the create method instead
  60. *
  61. * @see #createScroller
  62. */
  63. protected JScrollPane scroller;
  64. /**
  65. * As of Java 2 platform v1.4 this previously undocumented field is no
  66. * longer used.
  67. */
  68. protected boolean valueIsAdjusting = false;
  69. // Listeners that are required by the ComboPopup interface
  70. /**
  71. * This protected field is implementation specific. Do not access directly
  72. * or override. Use the accessor or create methods instead.
  73. *
  74. * @see #getMouseMotionListener
  75. * @see #createMouseMotionListener
  76. */
  77. protected MouseMotionListener mouseMotionListener;
  78. /**
  79. * This protected field is implementation specific. Do not access directly
  80. * or override. Use the accessor or create methods instead.
  81. *
  82. * @see #getMouseListener
  83. * @see #createMouseListener
  84. */
  85. protected MouseListener mouseListener;
  86. /**
  87. * This protected field is implementation specific. Do not access directly
  88. * or override. Use the accessor or create methods instead.
  89. *
  90. * @see #getKeyListener
  91. * @see #createKeyListener
  92. */
  93. protected KeyListener keyListener;
  94. /**
  95. * This protected field is implementation specific. Do not access directly
  96. * or override. Use the create method instead.
  97. *
  98. * @see #createListSelectionListener
  99. */
  100. protected ListSelectionListener listSelectionListener;
  101. // Listeners that are attached to the list
  102. /**
  103. * This protected field is implementation specific. Do not access directly
  104. * or override. Use the create method instead.
  105. *
  106. * @see #createListMouseListener
  107. */
  108. protected MouseListener listMouseListener;
  109. /**
  110. * This protected field is implementation specific. Do not access directly
  111. * or override. Use the create method instead
  112. *
  113. * @see #createListMouseMotionListener
  114. */
  115. protected MouseMotionListener listMouseMotionListener;
  116. // Added to the combo box for bound properties
  117. /**
  118. * This protected field is implementation specific. Do not access directly
  119. * or override. Use the create method instead
  120. *
  121. * @see #createPropertyChangeListener
  122. */
  123. protected PropertyChangeListener propertyChangeListener;
  124. // Added to the combo box model
  125. /**
  126. * This protected field is implementation specific. Do not access directly
  127. * or override. Use the create method instead
  128. *
  129. * @see #createListDataListener
  130. */
  131. protected ListDataListener listDataListener;
  132. /**
  133. * This protected field is implementation specific. Do not access directly
  134. * or override. Use the create method instead
  135. *
  136. * @see #createItemListener
  137. */
  138. protected ItemListener itemListener;
  139. /**
  140. * This protected field is implementation specific. Do not access directly
  141. * or override.
  142. */
  143. protected Timer autoscrollTimer;
  144. protected boolean hasEntered = false;
  145. protected boolean isAutoScrolling = false;
  146. protected int scrollDirection = SCROLL_UP;
  147. protected static final int SCROLL_UP = 0;
  148. protected static final int SCROLL_DOWN = 1;
  149. //========================================
  150. // begin ComboPopup method implementations
  151. //
  152. /**
  153. * Implementation of ComboPopup.show().
  154. */
  155. public void show() {
  156. setListSelection(comboBox.getSelectedIndex());
  157. Point location = getPopupLocation();
  158. show( comboBox, location.x, location.y );
  159. }
  160. /**
  161. * Implementation of ComboPopup.hide().
  162. */
  163. public void hide() {
  164. MenuSelectionManager manager = MenuSelectionManager.defaultManager();
  165. MenuElement [] selection = manager.getSelectedPath();
  166. for ( int i = 0 ; i < selection.length ; i++ ) {
  167. if ( selection[i] == this ) {
  168. manager.clearSelectedPath();
  169. break;
  170. }
  171. }
  172. if (selection.length > 0) {
  173. comboBox.repaint();
  174. }
  175. }
  176. /**
  177. * Implementation of ComboPopup.getList().
  178. */
  179. public JList getList() {
  180. return list;
  181. }
  182. /**
  183. * Implementation of ComboPopup.getMouseListener().
  184. *
  185. * @return a <code>MouseListener</code> or null
  186. * @see ComboPopup#getMouseListener
  187. */
  188. public MouseListener getMouseListener() {
  189. if (mouseListener == null) {
  190. mouseListener = createMouseListener();
  191. }
  192. return mouseListener;
  193. }
  194. /**
  195. * Implementation of ComboPopup.getMouseMotionListener().
  196. *
  197. * @return a <code>MouseMotionListener</code> or null
  198. * @see ComboPopup#getMouseMotionListener
  199. */
  200. public MouseMotionListener getMouseMotionListener() {
  201. if (mouseMotionListener == null) {
  202. mouseMotionListener = createMouseMotionListener();
  203. }
  204. return mouseMotionListener;
  205. }
  206. /**
  207. * Implementation of ComboPopup.getKeyListener().
  208. *
  209. * @return a <code>KeyListener</code> or null
  210. * @see ComboPopup#getKeyListener
  211. */
  212. public KeyListener getKeyListener() {
  213. if (keyListener == null) {
  214. keyListener = createKeyListener();
  215. }
  216. return keyListener;
  217. }
  218. /**
  219. * Called when the UI is uninstalling. Since this popup isn't in the component
  220. * tree, it won't get it's uninstallUI() called. It removes the listeners that
  221. * were added in addComboBoxListeners().
  222. */
  223. public void uninstallingUI() {
  224. if (propertyChangeListener != null) {
  225. comboBox.removePropertyChangeListener( propertyChangeListener );
  226. }
  227. if (itemListener != null) {
  228. comboBox.removeItemListener( itemListener );
  229. }
  230. uninstallComboBoxModelListeners(comboBox.getModel());
  231. uninstallKeyboardActions();
  232. uninstallListListeners();
  233. // We do this, otherwise the listener the ui installs on
  234. // the model (the combobox model in this case) will keep a
  235. // reference to the list, causing the list (and us) to never get gced.
  236. list.setModel(EmptyListModel);
  237. }
  238. //
  239. // end ComboPopup method implementations
  240. //======================================
  241. /**
  242. * Removes the listeners from the combo box model
  243. *
  244. * @param model The combo box model to install listeners
  245. * @see #installComboBoxModelListeners
  246. */
  247. protected void uninstallComboBoxModelListeners( ComboBoxModel model ) {
  248. if (model != null && listDataListener != null) {
  249. model.removeListDataListener(listDataListener);
  250. }
  251. }
  252. protected void uninstallKeyboardActions() {
  253. // XXX - shouldn't call this method
  254. // comboBox.unregisterKeyboardAction( KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ) );
  255. }
  256. //===================================================================
  257. // begin Initialization routines
  258. //
  259. public BasicComboPopup( JComboBox combo ) {
  260. super();
  261. comboBox = combo;
  262. installComboBoxListeners();
  263. setLightWeightPopupEnabled( comboBox.isLightWeightPopupEnabled() );
  264. // UI construction of the popup.
  265. list = createList();
  266. configureList();
  267. scroller = createScroller();
  268. configureScroller();
  269. configurePopup();
  270. installKeyboardActions();
  271. }
  272. // Overriden PopupMenuListener notification methods to inform combo box
  273. // PopupMenuListeners.
  274. protected void firePopupMenuWillBecomeVisible() {
  275. super.firePopupMenuWillBecomeVisible();
  276. comboBox.firePopupMenuWillBecomeVisible();
  277. }
  278. protected void firePopupMenuWillBecomeInvisible() {
  279. super.firePopupMenuWillBecomeInvisible();
  280. comboBox.firePopupMenuWillBecomeInvisible();
  281. }
  282. protected void firePopupMenuCanceled() {
  283. super.firePopupMenuCanceled();
  284. comboBox.firePopupMenuCanceled();
  285. }
  286. /**
  287. * Creates a listener
  288. * that will watch for mouse-press and release events on the combo box.
  289. *
  290. * <strong>Warning:</strong>
  291. * When overriding this method, make sure to maintain the existing
  292. * behavior.
  293. *
  294. * @return a <code>MouseListener</code> which will be added to
  295. * the combo box or null
  296. */
  297. protected MouseListener createMouseListener() {
  298. return new InvocationMouseHandler();
  299. }
  300. /**
  301. * Creates the mouse motion listener which will be added to the combo
  302. * box.
  303. *
  304. * <strong>Warning:</strong>
  305. * When overriding this method, make sure to maintain the existing
  306. * behavior.
  307. *
  308. * @return a <code>MouseMotionListener</code> which will be added to
  309. * the combo box or null
  310. */
  311. protected MouseMotionListener createMouseMotionListener() {
  312. return new InvocationMouseMotionHandler();
  313. }
  314. /**
  315. * Creates the key listener that will be added to the combo box. If
  316. * this method returns null then it will not be added to the combo box.
  317. *
  318. * @return a <code>KeyListener</code> or null
  319. */
  320. protected KeyListener createKeyListener() {
  321. return null;
  322. }
  323. /**
  324. * Creates a list selection listener that watches for selection changes in
  325. * the popup's list. If this method returns null then it will not
  326. * be added to the popup list.
  327. *
  328. * @return an instance of a <code>ListSelectionListener</code> or null
  329. */
  330. protected ListSelectionListener createListSelectionListener() {
  331. return null;
  332. }
  333. /**
  334. * Creates a list data listener which will be added to the
  335. * <code>ComboBoxModel</code>. If this method returns null then
  336. * it will not be added to the combo box model.
  337. *
  338. * @return an instance of a <code>ListDataListener</code> or null
  339. */
  340. protected ListDataListener createListDataListener() {
  341. return null;
  342. }
  343. /**
  344. * Creates a mouse listener that watches for mouse events in
  345. * the popup's list. If this method returns null then it will
  346. * not be added to the combo box.
  347. *
  348. * @return an instance of a <code>MouseListener</code> or null
  349. */
  350. protected MouseListener createListMouseListener() {
  351. return new ListMouseHandler();
  352. }
  353. /**
  354. * Creates a mouse motion listener that watches for mouse motion
  355. * events in the popup's list. If this method returns null then it will
  356. * not be added to the combo box.
  357. *
  358. * @return an instance of a <code>MouseMotionListener</code> or null
  359. */
  360. protected MouseMotionListener createListMouseMotionListener() {
  361. return new ListMouseMotionHandler();
  362. }
  363. /**
  364. * Creates a <code>PropertyChangeListener</code> which will be added to
  365. * the combo box. If this method returns null then it will not
  366. * be added to the combo box.
  367. *
  368. * @return an instance of a <code>PropertyChangeListener</code> or null
  369. */
  370. protected PropertyChangeListener createPropertyChangeListener() {
  371. return new PropertyChangeHandler();
  372. }
  373. /**
  374. * Creates an <code>ItemListener</code> which will be added to the
  375. * combo box. If this method returns null then it will not
  376. * be added to the combo box.
  377. * <p>
  378. * Subclasses may override this method to return instances of their own
  379. * ItemEvent handlers.
  380. *
  381. * @return an instance of an <code>ItemListener</code> or null
  382. */
  383. protected ItemListener createItemListener() {
  384. return new ItemHandler();
  385. }
  386. /**
  387. * Creates the JList used in the popup to display
  388. * the items in the combo box model. This method is called when the UI class
  389. * is created.
  390. *
  391. * @return a <code>JList</code> used to display the combo box items
  392. */
  393. protected JList createList() {
  394. return new JList( comboBox.getModel() ) {
  395. public void processMouseEvent(MouseEvent e) {
  396. if (e.isControlDown()) {
  397. // Fix for 4234053. Filter out the Control Key from the list.
  398. // ie., don't allow CTRL key deselection.
  399. e = new MouseEvent((Component)e.getSource(), e.getID(), e.getWhen(),
  400. e.getModifiers() ^ InputEvent.CTRL_MASK,
  401. e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger());
  402. }
  403. super.processMouseEvent(e);
  404. }
  405. };
  406. }
  407. /**
  408. * Configures the list which is used to hold the combo box items in the
  409. * popup. This method is called when the UI class
  410. * is created.
  411. *
  412. * @see #createList
  413. */
  414. protected void configureList() {
  415. list.setFont( comboBox.getFont() );
  416. list.setForeground( comboBox.getForeground() );
  417. list.setBackground( comboBox.getBackground() );
  418. list.setSelectionForeground( UIManager.getColor( "ComboBox.selectionForeground" ) );
  419. list.setSelectionBackground( UIManager.getColor( "ComboBox.selectionBackground" ) );
  420. list.setBorder( null );
  421. list.setCellRenderer( comboBox.getRenderer() );
  422. list.setFocusable( false );
  423. list.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
  424. setListSelection( comboBox.getSelectedIndex() );
  425. installListListeners();
  426. }
  427. /**
  428. * Adds the listeners to the list control.
  429. */
  430. protected void installListListeners() {
  431. if ((listMouseListener = createListMouseListener()) != null) {
  432. list.addMouseListener( listMouseListener );
  433. }
  434. if ((listMouseMotionListener = createListMouseMotionListener()) != null) {
  435. list.addMouseMotionListener( listMouseMotionListener );
  436. }
  437. if ((listSelectionListener = createListSelectionListener()) != null) {
  438. list.addListSelectionListener( listSelectionListener );
  439. }
  440. }
  441. void uninstallListListeners() {
  442. if (listMouseListener != null) {
  443. list.removeMouseListener(listMouseListener);
  444. listMouseListener = null;
  445. }
  446. if (listMouseMotionListener != null) {
  447. list.removeMouseMotionListener(listMouseMotionListener);
  448. listMouseMotionListener = null;
  449. }
  450. if (listSelectionListener != null) {
  451. list.removeListSelectionListener(listSelectionListener);
  452. listSelectionListener = null;
  453. }
  454. }
  455. /**
  456. * Creates the scroll pane which houses the scrollable list.
  457. */
  458. protected JScrollPane createScroller() {
  459. return new JScrollPane( list,
  460. ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
  461. ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
  462. }
  463. /**
  464. * Configures the scrollable portion which holds the list within
  465. * the combo box popup. This method is called when the UI class
  466. * is created.
  467. */
  468. protected void configureScroller() {
  469. scroller.setFocusable( false );
  470. scroller.getVerticalScrollBar().setFocusable( false );
  471. scroller.setBorder( null );
  472. }
  473. /**
  474. * Configures the popup portion of the combo box. This method is called
  475. * when the UI class is created.
  476. */
  477. protected void configurePopup() {
  478. setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) );
  479. setBorderPainted( true );
  480. setBorder( BorderFactory.createLineBorder( Color.black ) );
  481. setOpaque( false );
  482. add( scroller );
  483. setDoubleBuffered( true );
  484. setFocusable( false );
  485. }
  486. /**
  487. * This method adds the necessary listeners to the JComboBox.
  488. */
  489. protected void installComboBoxListeners() {
  490. if ((propertyChangeListener = createPropertyChangeListener()) != null) {
  491. comboBox.addPropertyChangeListener(propertyChangeListener);
  492. }
  493. if ((itemListener = createItemListener()) != null) {
  494. comboBox.addItemListener(itemListener);
  495. }
  496. installComboBoxModelListeners(comboBox.getModel());
  497. }
  498. /**
  499. * Installs the listeners on the combo box model. Any listeners installed
  500. * on the combo box model should be removed in
  501. * <code>uninstallComboBoxModelListeners</code>.
  502. *
  503. * @param model The combo box model to install listeners
  504. * @see #uninstallComboBoxModelListeners
  505. */
  506. protected void installComboBoxModelListeners( ComboBoxModel model ) {
  507. if (model != null && (listDataListener = createListDataListener()) != null) {
  508. model.addListDataListener(listDataListener);
  509. }
  510. }
  511. protected void installKeyboardActions() {
  512. /* XXX - shouldn't call this method. take it out for testing.
  513. ActionListener action = new ActionListener() {
  514. public void actionPerformed(ActionEvent e){
  515. }
  516. };
  517. comboBox.registerKeyboardAction( action,
  518. KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ),
  519. JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); */
  520. }
  521. //
  522. // end Initialization routines
  523. //=================================================================
  524. //===================================================================
  525. // begin Event Listenters
  526. //
  527. /**
  528. * A listener to be registered upon the combo box
  529. * (<em>not</em> its popup menu)
  530. * to handle mouse events
  531. * that affect the state of the popup menu.
  532. * The main purpose of this listener is to make the popup menu
  533. * appear and disappear.
  534. * This listener also helps
  535. * with click-and-drag scenarios by setting the selection if the mouse was
  536. * released over the list during a drag.
  537. *
  538. * <p>
  539. * <strong>Warning:</strong>
  540. * We recommend that you <em>not</em>
  541. * create subclasses of this class.
  542. * If you absolutely must create a subclass,
  543. * be sure to invoke the superclass
  544. * version of each method.
  545. *
  546. * @see BasicComboPopup#createMouseListener
  547. */
  548. protected class InvocationMouseHandler extends MouseAdapter {
  549. /**
  550. * Responds to mouse-pressed events on the combo box.
  551. *
  552. * @param e the mouse-press event to be handled
  553. */
  554. public void mousePressed( MouseEvent e ) {
  555. if (!SwingUtilities.isLeftMouseButton(e) || !comboBox.isEnabled())
  556. return;
  557. if ( comboBox.isEditable() ) {
  558. Component comp = comboBox.getEditor().getEditorComponent();
  559. if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) {
  560. comp.requestFocus();
  561. }
  562. }
  563. else if (comboBox.isRequestFocusEnabled()) {
  564. comboBox.requestFocus();
  565. }
  566. togglePopup();
  567. }
  568. /**
  569. * Responds to the user terminating
  570. * a click or drag that began on the combo box.
  571. *
  572. * @param e the mouse-release event to be handled
  573. */
  574. public void mouseReleased( MouseEvent e ) {
  575. Component source = (Component)e.getSource();
  576. Dimension size = source.getSize();
  577. Rectangle bounds = new Rectangle( 0, 0, size.width - 1, size.height - 1 );
  578. if ( !bounds.contains( e.getPoint() ) ) {
  579. MouseEvent newEvent = convertMouseEvent( e );
  580. Point location = newEvent.getPoint();
  581. Rectangle r = new Rectangle();
  582. list.computeVisibleRect( r );
  583. if ( r.contains( location ) ) {
  584. comboBox.setSelectedIndex( list.getSelectedIndex() );
  585. }
  586. comboBox.setPopupVisible(false);
  587. }
  588. hasEntered = false;
  589. stopAutoScrolling();
  590. }
  591. }
  592. /**
  593. * This listener watches for dragging and updates the current selection in the
  594. * list if it is dragging over the list.
  595. */
  596. protected class InvocationMouseMotionHandler extends MouseMotionAdapter {
  597. public void mouseDragged( MouseEvent e ) {
  598. if ( isVisible() ) {
  599. MouseEvent newEvent = convertMouseEvent( e );
  600. Rectangle r = new Rectangle();
  601. list.computeVisibleRect( r );
  602. if ( newEvent.getPoint().y >= r.y && newEvent.getPoint().y <= r.y + r.height - 1 ) {
  603. hasEntered = true;
  604. if ( isAutoScrolling ) {
  605. stopAutoScrolling();
  606. }
  607. Point location = newEvent.getPoint();
  608. if ( r.contains( location ) ) {
  609. updateListBoxSelectionForEvent( newEvent, false );
  610. }
  611. }
  612. else {
  613. if ( hasEntered ) {
  614. int directionToScroll = newEvent.getPoint().y < r.y ? SCROLL_UP : SCROLL_DOWN;
  615. if ( isAutoScrolling && scrollDirection != directionToScroll ) {
  616. stopAutoScrolling();
  617. startAutoScrolling( directionToScroll );
  618. }
  619. else if ( !isAutoScrolling ) {
  620. startAutoScrolling( directionToScroll );
  621. }
  622. }
  623. else {
  624. if ( e.getPoint().y < 0 ) {
  625. hasEntered = true;
  626. startAutoScrolling( SCROLL_UP );
  627. }
  628. }
  629. }
  630. }
  631. }
  632. }
  633. /**
  634. * As of Java 2 platform v 1.4, this class is now obsolete and is only included for
  635. * backwards API compatibility. Do not instantiate or subclass.
  636. * <p>
  637. * All the functionality of this class has been included in
  638. * BasicComboBoxUI ActionMap/InputMap methods.
  639. */
  640. public class InvocationKeyHandler extends KeyAdapter {
  641. public void keyReleased( KeyEvent e ) {}
  642. }
  643. /**
  644. * As of Java 2 platform v 1.4, this class is now obsolete, doesn't do anything, and
  645. * is only included for backwards API compatibility. Do not call or
  646. * override.
  647. */
  648. protected class ListSelectionHandler implements ListSelectionListener {
  649. public void valueChanged( ListSelectionEvent e ) {}
  650. }
  651. /**
  652. * As of 1.4, this class is now obsolete, doesn't do anything, and
  653. * is only included for backwards API compatibility. Do not call or
  654. * override.
  655. * <p>
  656. * The functionality has been migrated into <code>ItemHandler</code>.
  657. *
  658. * @see #createItemListener
  659. */
  660. public class ListDataHandler implements ListDataListener {
  661. public void contentsChanged( ListDataEvent e ) {}
  662. public void intervalAdded( ListDataEvent e ) {
  663. }
  664. public void intervalRemoved( ListDataEvent e ) {
  665. }
  666. }
  667. /**
  668. * This listener hides the popup when the mouse is released in the list.
  669. */
  670. protected class ListMouseHandler extends MouseAdapter {
  671. public void mousePressed( MouseEvent e ) {
  672. }
  673. public void mouseReleased(MouseEvent anEvent) {
  674. comboBox.setSelectedIndex( list.getSelectedIndex() );
  675. comboBox.setPopupVisible(false);
  676. // workaround for cancelling an edited item (bug 4530953)
  677. if (comboBox.isEditable() && comboBox.getEditor() != null) {
  678. comboBox.configureEditor(comboBox.getEditor(),
  679. comboBox.getSelectedItem());
  680. }
  681. }
  682. }
  683. /**
  684. * This listener changes the selected item as you move the mouse over the list.
  685. * The selection change is not committed to the model, this is for user feedback only.
  686. */
  687. protected class ListMouseMotionHandler extends MouseMotionAdapter {
  688. public void mouseMoved( MouseEvent anEvent ) {
  689. Point location = anEvent.getPoint();
  690. Rectangle r = new Rectangle();
  691. list.computeVisibleRect( r );
  692. if ( r.contains( location ) ) {
  693. updateListBoxSelectionForEvent( anEvent, false );
  694. }
  695. }
  696. }
  697. /**
  698. * This listener watches for changes to the selection in the
  699. * combo box.
  700. */
  701. protected class ItemHandler implements ItemListener {
  702. public void itemStateChanged( ItemEvent e ) {
  703. if (e.getStateChange() == ItemEvent.SELECTED) {
  704. JComboBox comboBox = (JComboBox)e.getSource();
  705. setListSelection(comboBox.getSelectedIndex());
  706. }
  707. }
  708. }
  709. /**
  710. * This listener watches for bound properties that have changed in the
  711. * combo box.
  712. * <p>
  713. * Subclasses which wish to listen to combo box property changes should
  714. * call the superclass methods to ensure that the combo popup correctly
  715. * handles property changes.
  716. *
  717. * @see #createPropertyChangeListener
  718. */
  719. protected class PropertyChangeHandler implements PropertyChangeListener {
  720. public void propertyChange( PropertyChangeEvent e ) {
  721. JComboBox comboBox = (JComboBox)e.getSource();
  722. String propertyName = e.getPropertyName();
  723. if ( propertyName.equals("model") ) {
  724. ComboBoxModel oldModel = (ComboBoxModel)e.getOldValue();
  725. ComboBoxModel newModel = (ComboBoxModel)e.getNewValue();
  726. uninstallComboBoxModelListeners(oldModel);
  727. installComboBoxModelListeners(newModel);
  728. list.setModel(newModel);
  729. if ( isVisible() ) {
  730. hide();
  731. }
  732. }
  733. else if ( propertyName.equals( "renderer" ) ) {
  734. list.setCellRenderer( comboBox.getRenderer() );
  735. if ( isVisible() ) {
  736. hide();
  737. }
  738. }
  739. else if (propertyName.equals("componentOrientation")) {
  740. // Pass along the new component orientation
  741. // to the list and the scroller
  742. ComponentOrientation o =(ComponentOrientation)e.getNewValue();
  743. JList list = getList();
  744. if (list!=null && list.getComponentOrientation()!=o) {
  745. list.setComponentOrientation(o);
  746. }
  747. if (scroller!=null && scroller.getComponentOrientation()!=o) {
  748. scroller.setComponentOrientation(o);
  749. }
  750. if (o!=getComponentOrientation()) {
  751. setComponentOrientation(o);
  752. }
  753. }
  754. else if (propertyName.equals("lightWeightPopupEnabled")) {
  755. setLightWeightPopupEnabled(comboBox.isLightWeightPopupEnabled());
  756. }
  757. }
  758. }
  759. //
  760. // end Event Listeners
  761. //=================================================================
  762. /**
  763. * Overridden to unconditionally return false.
  764. */
  765. public boolean isFocusTraversable() {
  766. return false;
  767. }
  768. //===================================================================
  769. // begin Autoscroll methods
  770. //
  771. /**
  772. * This protected method is implementation specific and should be private.
  773. * do not call or override.
  774. */
  775. protected void startAutoScrolling( int direction ) {
  776. // XXX - should be a private method within InvocationMouseMotionHandler
  777. // if possible.
  778. if ( isAutoScrolling ) {
  779. autoscrollTimer.stop();
  780. }
  781. isAutoScrolling = true;
  782. if ( direction == SCROLL_UP ) {
  783. scrollDirection = SCROLL_UP;
  784. Point convertedPoint = SwingUtilities.convertPoint( scroller, new Point( 1, 1 ), list );
  785. int top = list.locationToIndex( convertedPoint );
  786. list.setSelectedIndex( top );
  787. ActionListener timerAction = new ActionListener() {
  788. public void actionPerformed(ActionEvent e) {
  789. autoScrollUp();
  790. }
  791. };
  792. autoscrollTimer = new Timer( 100, timerAction );
  793. }
  794. else if ( direction == SCROLL_DOWN ) {
  795. scrollDirection = SCROLL_DOWN;
  796. Dimension size = scroller.getSize();
  797. Point convertedPoint = SwingUtilities.convertPoint( scroller,
  798. new Point( 1, (size.height - 1) - 2 ),
  799. list );
  800. int bottom = list.locationToIndex( convertedPoint );
  801. list.setSelectedIndex( bottom );
  802. ActionListener timerAction = new ActionListener() {
  803. public void actionPerformed(ActionEvent e) {
  804. autoScrollDown();
  805. }
  806. };
  807. autoscrollTimer = new Timer( 100, timerAction );
  808. }
  809. autoscrollTimer.start();
  810. }
  811. /**
  812. * This protected method is implementation specific and should be private.
  813. * do not call or override.
  814. */
  815. protected void stopAutoScrolling() {
  816. isAutoScrolling = false;
  817. if ( autoscrollTimer != null ) {
  818. autoscrollTimer.stop();
  819. autoscrollTimer = null;
  820. }
  821. }
  822. /**
  823. * This protected method is implementation specific and should be private.
  824. * do not call or override.
  825. */
  826. protected void autoScrollUp() {
  827. int index = list.getSelectedIndex();
  828. if ( index > 0 ) {
  829. list.setSelectedIndex( index - 1 );
  830. list.ensureIndexIsVisible( index - 1 );
  831. }
  832. }
  833. /**
  834. * This protected method is implementation specific and should be private.
  835. * do not call or override.
  836. */
  837. protected void autoScrollDown() {
  838. int index = list.getSelectedIndex();
  839. int lastItem = list.getModel().getSize() - 1;
  840. if ( index < lastItem ) {
  841. list.setSelectedIndex( index + 1 );
  842. list.ensureIndexIsVisible( index + 1 );
  843. }
  844. }
  845. //
  846. // end Autoscroll methods
  847. //=================================================================
  848. //===================================================================
  849. // begin Utility methods
  850. //
  851. /**
  852. * This is is a utility method that helps event handlers figure out where to
  853. * send the focus when the popup is brought up. The standard implementation
  854. * delegates the focus to the editor (if the combo box is editable) or to
  855. * the JComboBox if it is not editable.
  856. */
  857. protected void delegateFocus( MouseEvent e ) {
  858. if ( comboBox.isEditable() ) {
  859. Component comp = comboBox.getEditor().getEditorComponent();
  860. if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) {
  861. comp.requestFocus();
  862. }
  863. }
  864. else if (comboBox.isRequestFocusEnabled()) {
  865. comboBox.requestFocus();
  866. }
  867. }
  868. /**
  869. * Makes the popup visible if it is hidden and makes it hidden if it is
  870. * visible.
  871. */
  872. protected void togglePopup() {
  873. if ( isVisible() ) {
  874. hide();
  875. }
  876. else {
  877. show();
  878. }
  879. }
  880. /**
  881. * Sets the list selection index to the selectedIndex. This
  882. * method is used to synchronize the list selection with the
  883. * combo box selection.
  884. *
  885. * @param selectedIndex the index to set the list
  886. */
  887. private void setListSelection(int selectedIndex) {
  888. if ( selectedIndex == -1 ) {
  889. list.clearSelection();
  890. }
  891. else {
  892. list.setSelectedIndex( selectedIndex );
  893. list.ensureIndexIsVisible( selectedIndex );
  894. }
  895. }
  896. protected MouseEvent convertMouseEvent( MouseEvent e ) {
  897. Point convertedPoint = SwingUtilities.convertPoint( (Component)e.getSource(),
  898. e.getPoint(), list );
  899. MouseEvent newEvent = new MouseEvent( (Component)e.getSource(),
  900. e.getID(),
  901. e.getWhen(),
  902. e.getModifiers(),
  903. convertedPoint.x,
  904. convertedPoint.y,
  905. e.getClickCount(),
  906. e.isPopupTrigger() );
  907. return newEvent;
  908. }
  909. /**
  910. * Retrieves the height of the popup based on the current
  911. * ListCellRenderer and the maximum row count.
  912. */
  913. protected int getPopupHeightForRowCount(int maxRowCount) {
  914. // Set the cached value of the minimum row count
  915. int minRowCount = Math.min( maxRowCount, comboBox.getItemCount() );
  916. int height = 0;
  917. ListCellRenderer renderer = list.getCellRenderer();
  918. Object value = null;
  919. for ( int i = 0; i < minRowCount; ++i ) {
  920. value = list.getModel().getElementAt( i );
  921. Component c = renderer.getListCellRendererComponent( list, value, i, false, false );
  922. height += c.getPreferredSize().height;
  923. }
  924. return height == 0 ? 100 : height;
  925. }
  926. /**
  927. * Calculate the placement and size of the popup portion of the combo box based
  928. * on the combo box location and the enclosing screen bounds. If
  929. * no transformations are required, then the returned rectangle will
  930. * have the same values as the parameters.
  931. *
  932. * @param px starting x location
  933. * @param py starting y location
  934. * @param pw starting width
  935. * @param ph starting height
  936. * @return a rectangle which represents the placement and size of the popup
  937. */
  938. protected Rectangle computePopupBounds(int px,int py,int pw,int ph) {
  939. Toolkit toolkit = Toolkit.getDefaultToolkit();
  940. Rectangle screenBounds;
  941. // Calculate the desktop dimensions relative to the combo box.
  942. GraphicsConfiguration gc = comboBox.getGraphicsConfiguration();
  943. Point p = new Point();
  944. SwingUtilities.convertPointFromScreen(p, comboBox);
  945. if (gc != null) {
  946. Insets screenInsets = toolkit.getScreenInsets(gc);
  947. screenBounds = gc.getBounds();
  948. screenBounds.width -= (screenInsets.left + screenInsets.right);
  949. screenBounds.height -= (screenInsets.top + screenInsets.bottom);
  950. screenBounds.x += (p.x + screenInsets.left);
  951. screenBounds.y += (p.y + screenInsets.top);
  952. }
  953. else {
  954. screenBounds = new Rectangle(p, toolkit.getScreenSize());
  955. }
  956. Rectangle rect = new Rectangle(px,py,pw,ph);
  957. if (py+ph > screenBounds.y+screenBounds.height
  958. && ph < screenBounds.height) {
  959. rect.y = -rect.height;
  960. }
  961. return rect;
  962. }
  963. /**
  964. * Calculates the upper left location of the Popup.
  965. */
  966. private Point getPopupLocation() {
  967. Dimension popupSize = comboBox.getSize();
  968. Insets insets = getInsets();
  969. // reduce the width of the scrollpane by the insets so that the popup
  970. // is the same width as the combo box.
  971. popupSize.setSize(popupSize.width - (insets.right + insets.left),
  972. getPopupHeightForRowCount( comboBox.getMaximumRowCount()));
  973. Rectangle popupBounds = computePopupBounds( 0, comboBox.getBounds().height,
  974. popupSize.width, popupSize.height);
  975. Dimension scrollSize = popupBounds.getSize();
  976. Point popupLocation = popupBounds.getLocation();
  977. scroller.setMaximumSize( scrollSize );
  978. scroller.setPreferredSize( scrollSize );
  979. scroller.setMinimumSize( scrollSize );
  980. list.revalidate();
  981. return popupLocation;
  982. }
  983. /**
  984. * A utility method used by the event listeners. Given a mouse event, it changes
  985. * the list selection to the list item below the mouse.
  986. */
  987. protected void updateListBoxSelectionForEvent(MouseEvent anEvent,boolean shouldScroll) {
  988. // XXX - only seems to be called from this class. shouldScroll flag is
  989. // never true
  990. Point location = anEvent.getPoint();
  991. if ( list == null )
  992. return;
  993. int index = list.locationToIndex(location);
  994. if ( index == -1 ) {
  995. if ( location.y < 0 )
  996. index = 0;
  997. else
  998. index = comboBox.getModel().getSize() - 1;
  999. }
  1000. if ( list.getSelectedIndex() != index ) {
  1001. list.setSelectedIndex(index);
  1002. if ( shouldScroll )
  1003. list.ensureIndexIsVisible(index);
  1004. }
  1005. }
  1006. //
  1007. // end Utility methods
  1008. //=================================================================
  1009. }