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