1. /*
  2. * @(#)BasicComboPopup.java 1.32 00/02/02
  3. *
  4. * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.plaf.basic;
  11. import javax.swing.*;
  12. import javax.swing.event.*;
  13. import java.awt.*;
  14. import java.awt.event.*;
  15. import java.beans.PropertyChangeListener;
  16. import java.beans.PropertyChangeEvent;
  17. import java.io.Serializable;
  18. /**
  19. * This is an implementation of the ComboPopup interface. It is primarily for use by
  20. * BasicComboBoxUI and its subclasses. BasicComboPopup extends JPopupMenu because
  21. * most combo boxes use a popup menu to display the list of possible selections.
  22. * BasicComboBoxUI only requires a ComboPopup, so subclasses of BasicComboBoxUI aren't
  23. * required to use this class.
  24. *
  25. * All event handling is handled by createxxxListener() methods and internal classes.
  26. * You can change the behavior of this class by overriding the createxxxListener()
  27. * methods and supplying your own event listeners or subclassing from the ones supplied
  28. * in this class.
  29. *
  30. * Inner classes for handling events:
  31. * InvocationMouseHandler
  32. * InvocationMouseMotionHandler
  33. * InvocationKeyHandler
  34. * ListSelectionHandler
  35. * ListDataHandler
  36. * ListMouseHandler
  37. * ListMouseMotionHandler
  38. * PropertyChangeHandler
  39. * ItemHandler
  40. *
  41. * <p>
  42. * <strong>Warning:</strong>
  43. * Serialized objects of this class will not be compatible with
  44. * future Swing releases. The current serialization support is appropriate
  45. * for short term storage or RMI between applications running the same
  46. * version of Swing. A future release of Swing will provide support for
  47. * long term persistence.
  48. *
  49. * @version 1.32 02/02/00
  50. * @author Tom Santos
  51. */
  52. public class BasicComboPopup extends JPopupMenu implements ComboPopup {
  53. // An empty ListMode, this is used when the UI changes to allow
  54. // the JList to be gc'ed.
  55. static final ListModel EmptyListModel = new ListModel() {
  56. public int getSize() { return 0; }
  57. public Object getElementAt(int index) { return null; }
  58. public void addListDataListener(ListDataListener l) {}
  59. public void removeListDataListener(ListDataListener l) {}
  60. };
  61. protected JComboBox comboBox;
  62. protected JList list;
  63. protected JScrollPane scroller;
  64. // If the value is adjusting, any changes to the list selection won't affect the model.
  65. protected boolean valueIsAdjusting = false;
  66. // Listeners that are required by the ComboPopup interface
  67. protected MouseMotionListener mouseMotionListener;
  68. protected MouseListener mouseListener;
  69. protected KeyListener keyListener;
  70. // Listeners that are attached to the list
  71. protected ListSelectionListener listSelectionListener;
  72. protected ListDataListener listDataListener;
  73. protected MouseListener listMouseListener;
  74. protected MouseMotionListener listMouseMotionListener;
  75. // Listeners that are attached to the JComboBox
  76. protected PropertyChangeListener propertyChangeListener;
  77. protected ItemListener itemListener;
  78. protected Timer autoscrollTimer;
  79. protected boolean hasEntered = false;
  80. protected boolean isAutoScrolling = false;
  81. protected int scrollDirection = SCROLL_UP;
  82. protected static final int SCROLL_UP = 0;
  83. protected static final int SCROLL_DOWN = 1;
  84. private boolean lightNav = false;
  85. private static final String LIGHTWEIGHT_KEYBOARD_NAVIGATION = "JComboBox.lightweightKeyboardNavigation";
  86. private static final String LIGHTWEIGHT_KEYBOARD_NAVIGATION_ON = "Lightweight";
  87. private static final String LIGHTWEIGHT_KEYBOARD_NAVIGATION_OFF = "Heavyweight";
  88. //========================================
  89. // begin ComboPopup method implementations
  90. //
  91. /**
  92. * Implementation of ComboPopup.show().
  93. */
  94. public void show() {
  95. Dimension popupSize = comboBox.getSize();
  96. popupSize.setSize( popupSize.width, getPopupHeightForRowCount( comboBox.getMaximumRowCount() ) );
  97. Rectangle popupBounds = computePopupBounds( 0, comboBox.getBounds().height,
  98. popupSize.width, popupSize.height);
  99. scroller.setMaximumSize( popupBounds.getSize() );
  100. scroller.setPreferredSize( popupBounds.getSize() );
  101. scroller.setMinimumSize( popupBounds.getSize() );
  102. list.invalidate();
  103. syncListSelectionWithComboBoxSelection();
  104. list.ensureIndexIsVisible( list.getSelectedIndex() );
  105. setLightWeightPopupEnabled( comboBox.isLightWeightPopupEnabled() );
  106. show( comboBox, popupBounds.x, popupBounds.y );
  107. }
  108. /**
  109. * Implementation of ComboPopup.hide().
  110. */
  111. public void hide() {
  112. MenuSelectionManager manager = MenuSelectionManager.defaultManager();
  113. MenuElement [] selection = manager.getSelectedPath();
  114. for ( int i = 0 ; i < selection.length ; i++ ) {
  115. if ( selection[i] == this ) {
  116. manager.clearSelectedPath();
  117. break;
  118. }
  119. }
  120. comboBox.repaint();
  121. }
  122. /**
  123. * Implementation of ComboPopup.getList().
  124. */
  125. public JList getList() {
  126. return list;
  127. }
  128. /**
  129. * Implementation of ComboPopup.getMouseListener().
  130. */
  131. public MouseListener getMouseListener() {
  132. return mouseListener;
  133. }
  134. /**
  135. * Implementation of ComboPopup.getMouseMotionListener().
  136. */
  137. public MouseMotionListener getMouseMotionListener() {
  138. return mouseMotionListener;
  139. }
  140. /**
  141. * Implementation of ComboPopup.getKeyListener().
  142. */
  143. public KeyListener getKeyListener() {
  144. return keyListener;
  145. }
  146. /**
  147. * Called when the UI is uninstalling. Since this popup isn't in the component
  148. * tree, it won't get it's uninstallUI() called. It removes the listeners that
  149. * were added in addComboBoxListeners().
  150. */
  151. public void uninstallingUI() {
  152. comboBox.removePropertyChangeListener( propertyChangeListener );
  153. comboBox.removeItemListener( itemListener );
  154. uninstallComboBoxModelListeners( comboBox.getModel() );
  155. uninstallKeyboardActions();
  156. uninstallListListeners();
  157. // We do this, otherwise the listener the ui installs on
  158. // the model (the combobox model in this case) will keep a
  159. // reference to the list, causing the list (and us) to never get gced.
  160. list.setModel(EmptyListModel);
  161. }
  162. protected void uninstallComboBoxModelListeners( ComboBoxModel model ) {
  163. if ( model != null ) {
  164. model.removeListDataListener( listDataListener );
  165. }
  166. }
  167. protected void uninstallKeyboardActions() {
  168. comboBox.unregisterKeyboardAction( KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ) );
  169. }
  170. //
  171. // end ComboPopup method implementations
  172. //======================================
  173. //===================================================================
  174. // begin Initialization routines
  175. //
  176. public BasicComboPopup( JComboBox combo ) {
  177. super();
  178. comboBox = combo;
  179. Object keyNav = combo.getClientProperty( LIGHTWEIGHT_KEYBOARD_NAVIGATION );
  180. if ( keyNav != null ) {
  181. if ( keyNav.equals( LIGHTWEIGHT_KEYBOARD_NAVIGATION_ON ) ) {
  182. lightNav = true;
  183. }
  184. else if ( keyNav.equals( LIGHTWEIGHT_KEYBOARD_NAVIGATION_OFF ) ) {
  185. lightNav = false;
  186. }
  187. }
  188. mouseListener = createMouseListener();
  189. mouseMotionListener = createMouseMotionListener();
  190. keyListener = createKeyListener();
  191. listSelectionListener = createListSelectionListener();
  192. listDataListener = createListDataListener();
  193. listMouseListener = createListMouseListener();
  194. listMouseMotionListener = createListMouseMotionListener();
  195. propertyChangeListener = createPropertyChangeListener();
  196. itemListener = createItemListener();
  197. list = createList();
  198. configureList();
  199. scroller = createScroller();
  200. configureScroller();
  201. configurePopup();
  202. installComboBoxListeners();
  203. installKeyboardActions();
  204. }
  205. /**
  206. * Creates the mouse listener that is returned by ComboPopup.getMouseListener().
  207. * Returns an instance of BasicComboPopup$InvocationMouseHandler.
  208. */
  209. protected MouseListener createMouseListener() {
  210. return new InvocationMouseHandler();
  211. }
  212. /**
  213. * Creates the mouse motion listener that is returned by
  214. * ComboPopup.getMouseMotionListener().
  215. * Returns an instance of BasicComboPopup$InvocationMouseMotionListener.
  216. */
  217. protected MouseMotionListener createMouseMotionListener() {
  218. return new InvocationMouseMotionHandler();
  219. }
  220. /**
  221. * Creates the key listener that is returned by ComboPopup.getKeyListener().
  222. * Returns an instance of BasicComboPopup$InvocationKeyHandler.
  223. */
  224. protected KeyListener createKeyListener() {
  225. return new InvocationKeyHandler();
  226. }
  227. /**
  228. * Creates a list selection listener that watches for selection changes in
  229. * the popup's list.
  230. * Returns an instance of BasicComboPopup$ListSelectionHandler.
  231. */
  232. protected ListSelectionListener createListSelectionListener() {
  233. return new ListSelectionHandler();
  234. }
  235. /**
  236. * Creates a list data listener that watches for inserted and removed items from the
  237. * combo box model.
  238. */
  239. protected ListDataListener createListDataListener() {
  240. return new ListDataHandler();
  241. }
  242. /**
  243. * Creates a mouse listener that watches for mouse events in
  244. * the popup's list.
  245. * Returns an instance of BasicComboPopup$ListMouseHandler.
  246. */
  247. protected MouseListener createListMouseListener() {
  248. return new ListMouseHandler();
  249. }
  250. /**
  251. * Creates a mouse motion listener that watches for mouse events in
  252. * the popup's list.
  253. * Returns an instance of BasicComboPopup$ListMouseMotionHandler.
  254. */
  255. protected MouseMotionListener createListMouseMotionListener() {
  256. return new ListMouseMotionHandler();
  257. }
  258. /**
  259. * Creates a property change listener that watches for changes in the bound
  260. * properties in the JComboBox.
  261. * Returns an instance of BasicComboPopup$PropertyChangeHandler.
  262. */
  263. protected PropertyChangeListener createPropertyChangeListener() {
  264. return new PropertyChangeHandler();
  265. }
  266. /**
  267. * Creates an item listener that watches for changes in the selected
  268. * item in the JComboBox.
  269. * Returns an instance of BasicComboPopup$ItemHandler.
  270. */
  271. protected ItemListener createItemListener() {
  272. return new ItemHandler();
  273. }
  274. /**
  275. * Creates the JList that is used in the popup to display the items in the model.
  276. */
  277. protected JList createList() {
  278. return new JList( comboBox.getModel() );
  279. }
  280. /**
  281. * Called to configure the list created by createList().
  282. */
  283. protected void configureList() {
  284. list.setFont( comboBox.getFont() );
  285. list.setForeground( comboBox.getForeground() );
  286. list.setBackground( comboBox.getBackground() );
  287. list.setSelectionForeground( UIManager.getColor( "ComboBox.selectionForeground" ) );
  288. list.setSelectionBackground( UIManager.getColor( "ComboBox.selectionBackground" ) );
  289. list.setBorder( null );
  290. list.setCellRenderer( comboBox.getRenderer() );
  291. list.setRequestFocusEnabled( false );
  292. syncListSelectionWithComboBoxSelection();
  293. list.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
  294. installListListeners();
  295. }
  296. /**
  297. * Called by configureList() to add the necessary listeners to the list.
  298. */
  299. protected void installListListeners() {
  300. list.addListSelectionListener( listSelectionListener );
  301. list.addMouseMotionListener( listMouseMotionListener );
  302. list.addMouseListener( listMouseListener );
  303. }
  304. void uninstallListListeners() {
  305. list.removeListSelectionListener( listSelectionListener );
  306. list.removeMouseMotionListener( listMouseMotionListener );
  307. list.removeMouseListener( listMouseListener );
  308. }
  309. /**
  310. * Creates the JScrollPane that is used in the popup to hold the list.
  311. */
  312. protected JScrollPane createScroller() {
  313. return new JScrollPane( list, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
  314. ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
  315. }
  316. /**
  317. * Called to configure the JScrollPane created by createScroller().
  318. */
  319. protected void configureScroller() {
  320. scroller.setRequestFocusEnabled( false );
  321. scroller.getVerticalScrollBar().setRequestFocusEnabled( false );
  322. scroller.setBorder( null );
  323. }
  324. /**
  325. * Called to configure this JPopupMenu (BasicComboPopup is a JPopupMenu).
  326. */
  327. protected void configurePopup() {
  328. setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) );
  329. setBorderPainted( true );
  330. setBorder( BorderFactory.createLineBorder( Color.black ) );
  331. setOpaque( false );
  332. add( scroller );
  333. setDoubleBuffered( true );
  334. setRequestFocusEnabled( false );
  335. }
  336. /**
  337. * This method adds the necessary listeners to the JComboBox.
  338. */
  339. protected void installComboBoxListeners() {
  340. comboBox.addPropertyChangeListener( propertyChangeListener );
  341. comboBox.addItemListener( itemListener );
  342. installComboBoxModelListeners( comboBox.getModel() );
  343. }
  344. protected void installComboBoxModelListeners( ComboBoxModel model ) {
  345. if ( model != null ) {
  346. model.addListDataListener( listDataListener );
  347. }
  348. }
  349. protected void installKeyboardActions() {
  350. ActionListener action = new ActionListener() {
  351. public void actionPerformed(ActionEvent e){
  352. }
  353. };
  354. comboBox.registerKeyboardAction( action,
  355. KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ),
  356. JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
  357. }
  358. //
  359. // end Initialization routines
  360. //=================================================================
  361. //===================================================================
  362. // begin Event Listenters
  363. //
  364. /**
  365. * This listener knows how and when to invoke this popup menu. It also helps
  366. * with click-and-drag scenarios by setting the selection if the mouse was
  367. * released over the list during a drag.
  368. */
  369. protected class InvocationMouseHandler extends MouseAdapter {
  370. public void mousePressed( MouseEvent e ) {
  371. Rectangle r;
  372. if ( !SwingUtilities.isLeftMouseButton(e) )
  373. return;
  374. if ( !comboBox.isEnabled() )
  375. return;
  376. delegateFocus( e );
  377. togglePopup();
  378. }
  379. public void mouseReleased( MouseEvent e ) {
  380. Component source = (Component)e.getSource();
  381. Dimension size = source.getSize();
  382. Rectangle bounds = new Rectangle( 0, 0, size.width - 1, size.height - 1 );
  383. if ( !bounds.contains( e.getPoint() ) ) {
  384. MouseEvent newEvent = convertMouseEvent( e );
  385. Point location = newEvent.getPoint();
  386. Rectangle r = new Rectangle();
  387. list.computeVisibleRect( r );
  388. if ( r.contains( location ) ) {
  389. updateListBoxSelectionForEvent( newEvent, false );
  390. comboBox.setSelectedIndex( list.getSelectedIndex() );
  391. }
  392. hide();
  393. }
  394. hasEntered = false;
  395. stopAutoScrolling();
  396. }
  397. }
  398. /**
  399. * This listener watches for dragging and updates the current selection in the
  400. * list if it is dragging over the list.
  401. */
  402. protected class InvocationMouseMotionHandler extends MouseMotionAdapter {
  403. public void mouseDragged( MouseEvent e ) {
  404. if ( isVisible() ) {
  405. MouseEvent newEvent = convertMouseEvent( e );
  406. Rectangle r = new Rectangle();
  407. list.computeVisibleRect( r );
  408. if ( newEvent.getPoint().y >= r.y && newEvent.getPoint().y <= r.y + r.height - 1 ) {
  409. hasEntered = true;
  410. if ( isAutoScrolling ) {
  411. stopAutoScrolling();
  412. }
  413. Point location = newEvent.getPoint();
  414. if ( r.contains( location ) ) {
  415. valueIsAdjusting = true;
  416. updateListBoxSelectionForEvent( newEvent, false );
  417. valueIsAdjusting = false;
  418. }
  419. }
  420. else {
  421. if ( hasEntered ) {
  422. int directionToScroll = newEvent.getPoint().y < r.y ? SCROLL_UP : SCROLL_DOWN;
  423. if ( isAutoScrolling && scrollDirection != directionToScroll ) {
  424. stopAutoScrolling();
  425. startAutoScrolling( directionToScroll );
  426. }
  427. else if ( !isAutoScrolling ) {
  428. startAutoScrolling( directionToScroll );
  429. }
  430. }
  431. else {
  432. if ( e.getPoint().y < 0 ) {
  433. hasEntered = true;
  434. startAutoScrolling( SCROLL_UP );
  435. }
  436. }
  437. }
  438. }
  439. }
  440. }
  441. /**
  442. * This listener watches for the spacebar being pressed and shows/hides the
  443. * popup accordingly.
  444. */
  445. public class InvocationKeyHandler extends KeyAdapter {
  446. public void keyReleased( KeyEvent e ) {
  447. if ( e.getKeyCode() == KeyEvent.VK_SPACE ||
  448. e.getKeyCode() == KeyEvent.VK_ENTER ) {
  449. if ( isVisible() ) {
  450. if ( lightNav ) {
  451. comboBox.setSelectedIndex( list.getSelectedIndex() );
  452. }
  453. else {
  454. togglePopup();
  455. }
  456. }
  457. else if ( e.getKeyCode() == KeyEvent.VK_SPACE ) {
  458. // Don't toggle if the popup is invisible and
  459. // the key is an <Enter> (conflicts with default
  460. // button)
  461. togglePopup();
  462. }
  463. }
  464. }
  465. }
  466. /**
  467. * This listener watches for changes in the list's selection and reports
  468. * them to the combo box.
  469. */
  470. protected class ListSelectionHandler implements ListSelectionListener {
  471. public void valueChanged( ListSelectionEvent e ) {
  472. if ( !lightNav && !valueIsAdjusting && !e.getValueIsAdjusting() &&
  473. list.getSelectedIndex() != comboBox.getSelectedIndex() &&
  474. list.getSelectedIndex() < comboBox.getItemCount() &&
  475. list.getSelectedIndex() >= -1 ) {
  476. valueIsAdjusting = true;
  477. comboBox.setSelectedIndex( list.getSelectedIndex() );
  478. list.ensureIndexIsVisible( list.getSelectedIndex() );
  479. valueIsAdjusting = false;
  480. }
  481. }
  482. }
  483. /**
  484. * Keeps the selected index in the list in-sync with the combo box's selection.
  485. *
  486. */
  487. public class ListDataHandler implements ListDataListener {
  488. public void contentsChanged( ListDataEvent e ) {
  489. }
  490. public void intervalAdded( ListDataEvent e ) {
  491. valueIsAdjusting = true;
  492. syncListSelectionWithComboBoxSelection();
  493. //list.ensureIndexIsVisible( list.getSelectedIndex() );
  494. valueIsAdjusting = false;
  495. }
  496. public void intervalRemoved( ListDataEvent e ) {
  497. }
  498. }
  499. /**
  500. * This listener hides the popup when the mouse is released in the list.
  501. */
  502. protected class ListMouseHandler extends MouseAdapter {
  503. public void mousePressed( MouseEvent e ) {
  504. }
  505. public void mouseReleased(MouseEvent anEvent) {
  506. comboBox.setSelectedIndex( list.getSelectedIndex() );
  507. hide();
  508. }
  509. }
  510. /**
  511. * This listener changes the selected item as you move the mouse over the list.
  512. * The selection change is not committed to the model, this is for user feedback only.
  513. */
  514. protected class ListMouseMotionHandler extends MouseMotionAdapter {
  515. public void mouseMoved( MouseEvent anEvent ) {
  516. Point location = anEvent.getPoint();
  517. Rectangle r = new Rectangle();
  518. list.computeVisibleRect( r );
  519. if ( r.contains( location ) ) {
  520. valueIsAdjusting = true;
  521. updateListBoxSelectionForEvent( anEvent, false );
  522. valueIsAdjusting = false;
  523. }
  524. }
  525. }
  526. /**
  527. * This listener watches for changes in the JComboBox's selection. It updates
  528. * the list accordingly.
  529. */
  530. protected class ItemHandler implements ItemListener {
  531. public void itemStateChanged( ItemEvent e ) {
  532. if ( e.getStateChange() == ItemEvent.SELECTED &&
  533. !valueIsAdjusting ) {
  534. valueIsAdjusting = true;
  535. syncListSelectionWithComboBoxSelection();
  536. valueIsAdjusting = false;
  537. list.ensureIndexIsVisible( comboBox.getSelectedIndex() );
  538. }
  539. }
  540. }
  541. /**
  542. * This listener watches for bound property changes in JComboBox. If the model
  543. * or the renderer changes, the popup hides itself.
  544. */
  545. protected class PropertyChangeHandler implements PropertyChangeListener {
  546. public void propertyChange( PropertyChangeEvent e ) {
  547. String propertyName = e.getPropertyName();
  548. if ( propertyName.equals("model") ) {
  549. uninstallComboBoxModelListeners( (ComboBoxModel)e.getOldValue() );
  550. list.setModel( (ComboBoxModel)e.getNewValue() );
  551. installComboBoxModelListeners( (ComboBoxModel)e.getNewValue() );
  552. if ( comboBox.getItemCount() > 0 ) {
  553. comboBox.setSelectedIndex( 0 );
  554. }
  555. if ( isVisible() ) {
  556. hide();
  557. }
  558. }
  559. else if ( propertyName.equals( "renderer" ) ) {
  560. list.setCellRenderer( comboBox.getRenderer() );
  561. if ( isVisible() ) {
  562. hide();
  563. }
  564. }
  565. else if ( propertyName.equals( LIGHTWEIGHT_KEYBOARD_NAVIGATION ) ) {
  566. Object newValue = e.getNewValue();
  567. if ( newValue.equals( LIGHTWEIGHT_KEYBOARD_NAVIGATION_ON ) ) {
  568. lightNav = true;
  569. }
  570. else if ( newValue.equals( LIGHTWEIGHT_KEYBOARD_NAVIGATION_OFF ) ) {
  571. lightNav = false;
  572. }
  573. }
  574. else if (propertyName.equals("componentOrientation")) {
  575. // Pass along the new component orientation
  576. // to the list and the scroller
  577. ComponentOrientation o =(ComponentOrientation)e.getNewValue();
  578. JList list = getList();
  579. if (list!=null && list.getComponentOrientation()!=o) {
  580. list.setComponentOrientation(o);
  581. }
  582. if (scroller!=null && scroller.getComponentOrientation()!=o) {
  583. scroller.setComponentOrientation(o);
  584. }
  585. if (o!=getComponentOrientation()) {
  586. setComponentOrientation(o);
  587. }
  588. }
  589. }
  590. }
  591. //
  592. // end Event Listeners
  593. //=================================================================
  594. /**
  595. * Overridden to unconditionally return false.
  596. */
  597. public boolean isFocusTraversable() {
  598. return false;
  599. }
  600. //===================================================================
  601. // begin Autoscroll methods
  602. //
  603. /**
  604. * Called by BasicComboPopup$InvocationMouseMotionHandler to handle auto-
  605. * scrolling the list.
  606. */
  607. protected void startAutoScrolling( int direction ) {
  608. if ( isAutoScrolling ) {
  609. autoscrollTimer.stop();
  610. }
  611. isAutoScrolling = true;
  612. if ( direction == SCROLL_UP ) {
  613. scrollDirection = SCROLL_UP;
  614. Point convertedPoint = SwingUtilities.convertPoint( scroller, new Point( 1, 1 ), list );
  615. int top = list.locationToIndex( convertedPoint );
  616. valueIsAdjusting = true;
  617. list.setSelectedIndex( top );
  618. valueIsAdjusting = false;
  619. ActionListener timerAction = new ActionListener() {
  620. public void actionPerformed(ActionEvent e) {
  621. autoScrollUp();
  622. }
  623. };
  624. autoscrollTimer = new Timer( 100, timerAction );
  625. }
  626. else if ( direction == SCROLL_DOWN ) {
  627. scrollDirection = SCROLL_DOWN;
  628. Dimension size = scroller.getSize();
  629. Point convertedPoint = SwingUtilities.convertPoint( scroller,
  630. new Point( 1, (size.height - 1) - 2 ),
  631. list );
  632. int bottom = list.locationToIndex( convertedPoint );
  633. valueIsAdjusting = true;
  634. list.setSelectedIndex( bottom );
  635. valueIsAdjusting = false;
  636. ActionListener timerAction = new ActionListener() {
  637. public void actionPerformed(ActionEvent e) {
  638. autoScrollDown();
  639. }
  640. };
  641. autoscrollTimer = new Timer( 100, timerAction );
  642. }
  643. autoscrollTimer.start();
  644. }
  645. protected void stopAutoScrolling() {
  646. isAutoScrolling = false;
  647. if ( autoscrollTimer != null ) {
  648. autoscrollTimer.stop();
  649. autoscrollTimer = null;
  650. }
  651. }
  652. protected void autoScrollUp() {
  653. int index = list.getSelectedIndex();
  654. if ( index > 0 ) {
  655. valueIsAdjusting = true;
  656. list.setSelectedIndex( index - 1 );
  657. valueIsAdjusting = false;
  658. list.ensureIndexIsVisible( index - 1 );
  659. }
  660. }
  661. protected void autoScrollDown() {
  662. int index = list.getSelectedIndex();
  663. int lastItem = list.getModel().getSize() - 1;
  664. if ( index < lastItem ) {
  665. valueIsAdjusting = true;
  666. list.setSelectedIndex( index + 1 );
  667. valueIsAdjusting = false;
  668. list.ensureIndexIsVisible( index + 1 );
  669. }
  670. }
  671. //
  672. // end Autoscroll methods
  673. //=================================================================
  674. //===================================================================
  675. // begin Utility methods
  676. //
  677. /**
  678. * This is is a utility method that helps event handlers figure out where to
  679. * send the focus when the popup is brought up. The standard implementation
  680. * delegates the focus to the editor (if the combo box is editable) or to
  681. * the JComboBox if it is not editable.
  682. */
  683. protected void delegateFocus( MouseEvent e ) {
  684. if ( comboBox.isEditable() ) {
  685. comboBox.getEditor().getEditorComponent().requestFocus();
  686. }
  687. else {
  688. comboBox.requestFocus();
  689. }
  690. }
  691. /**
  692. * Makes the popup visible if it is hidden and makes it hidden if it is visible.
  693. */
  694. protected void togglePopup() {
  695. if ( isVisible() ) {
  696. hide();
  697. }
  698. else {
  699. show();
  700. }
  701. }
  702. void syncListSelectionWithComboBoxSelection() {
  703. int selectedIndex = comboBox.getSelectedIndex();
  704. if ( selectedIndex == -1 ) {
  705. list.clearSelection();
  706. }
  707. else {
  708. list.setSelectedIndex( selectedIndex );
  709. }
  710. }
  711. protected MouseEvent convertMouseEvent( MouseEvent e ) {
  712. Point convertedPoint = SwingUtilities.convertPoint( (Component)e.getSource(),
  713. e.getPoint(), list );
  714. MouseEvent newEvent = new MouseEvent( (Component)e.getSource(),
  715. e.getID(),
  716. e.getWhen(),
  717. e.getModifiers(),
  718. convertedPoint.x,
  719. convertedPoint.y,
  720. e.getModifiers(),
  721. e.isPopupTrigger() );
  722. return newEvent;
  723. }
  724. protected int getPopupHeightForRowCount(int maxRowCount) {
  725. int currentElementCount = comboBox.getModel().getSize();
  726. int rowCount = Math.min( maxRowCount, currentElementCount );
  727. int height = 0;
  728. ListCellRenderer renderer = list.getCellRenderer();
  729. Object value = null;
  730. for ( int i = 0; i < rowCount; ++i ) {
  731. value = list.getModel().getElementAt( i );
  732. Component c = renderer.getListCellRendererComponent( list, value, i, false, false );
  733. height += c.getPreferredSize().height;
  734. }
  735. return height == 0 ? 100 : height;
  736. /*
  737. if ( currentElementCount > 0 ) {
  738. Rectangle r = list.getCellBounds(0,0);
  739. if ( maxRowCount < currentElementCount )
  740. return (r.height * maxRowCount) + 2;
  741. else
  742. return (r.height * currentElementCount) + 2;
  743. }
  744. else
  745. return 100;
  746. */
  747. }
  748. /* REMIND: All this menu placement logic is now handled by
  749. * javax.swing.DefaultPopupFactory
  750. * so this code should eventually be migrated to take advantage of it.
  751. */
  752. protected Rectangle computePopupBounds(int px,int py,int pw,int ph) {
  753. Rectangle absBounds = new Rectangle();
  754. Rectangle r = new Rectangle(px,py,pw,ph);
  755. Point p = new Point(0,0);
  756. Dimension scrSize = Toolkit.getDefaultToolkit().getScreenSize();
  757. SwingUtilities.convertPointFromScreen(p,comboBox);
  758. absBounds.x = p.x;
  759. absBounds.y = p.y;
  760. absBounds.width = scrSize.width;
  761. absBounds.height= scrSize.height;
  762. if ( SwingUtilities.isRectangleContainingRectangle(absBounds,r) )
  763. return r;
  764. else {
  765. return new Rectangle(0,-r.height,r.width,r.height);
  766. }
  767. }
  768. /**
  769. * A utility method used by the event listeners. Given a mouse event, it changes
  770. * the list selection to the list item below the mouse.
  771. */
  772. protected void updateListBoxSelectionForEvent(MouseEvent anEvent,boolean shouldScroll) {
  773. Point location = anEvent.getPoint();
  774. if ( list == null )
  775. return;
  776. int index = list.locationToIndex(location);
  777. if ( index == -1 ) {
  778. if ( location.y < 0 )
  779. index = 0;
  780. else
  781. index = comboBox.getModel().getSize() - 1;
  782. }
  783. if ( list.getSelectedIndex() != index ) {
  784. list.setSelectedIndex(index);
  785. if ( shouldScroll )
  786. list.ensureIndexIsVisible(index);
  787. }
  788. }
  789. //
  790. // end Utility methods
  791. //=================================================================
  792. }