1. /*
  2. * @(#)SynthComboPopup.java 1.5 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 com.sun.java.swing.plaf.gtk;
  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.5, 01/23/03 (based on BasicComboPopup v 1.71)
  36. * @author Tom Santos
  37. * @author Mark Davidson
  38. */
  39. class SynthComboPopup extends JPopupMenu {
  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 SynthComboPopup( JComboBox combo ) {
  260. super();
  261. comboBox = combo;
  262. installComboBoxListeners();
  263. setLightWeightPopupEnabled( comboBox.isLightWeightPopupEnabled() );
  264. // UI construction of the popup.
  265. list = createList();
  266. list.setName("ComboBox.list");
  267. configureList();
  268. scroller = createScroller();
  269. scroller.setName("ComboBox.scrollPane");
  270. configureScroller();
  271. configurePopup();
  272. installKeyboardActions();
  273. }
  274. // Overriden PopupMenuListener notification methods to inform combo box
  275. // PopupMenuListeners.
  276. protected void firePopupMenuWillBecomeVisible() {
  277. super.firePopupMenuWillBecomeVisible();
  278. comboBox.firePopupMenuWillBecomeVisible();
  279. }
  280. protected void firePopupMenuWillBecomeInvisible() {
  281. super.firePopupMenuWillBecomeInvisible();
  282. comboBox.firePopupMenuWillBecomeInvisible();
  283. }
  284. protected void firePopupMenuCanceled() {
  285. super.firePopupMenuCanceled();
  286. comboBox.firePopupMenuCanceled();
  287. }
  288. /**
  289. * Creates a listener
  290. * that will watch for mouse-press and release events on the combo box.
  291. *
  292. * <strong>Warning:</strong>
  293. * When overriding this method, make sure to maintain the existing
  294. * behavior.
  295. *
  296. * @return a <code>MouseListener</code> which will be added to
  297. * the combo box or null
  298. */
  299. protected MouseListener createMouseListener() {
  300. return new InvocationMouseHandler();
  301. }
  302. /**
  303. * Creates the mouse motion listener which will be added to the combo
  304. * box.
  305. *
  306. * <strong>Warning:</strong>
  307. * When overriding this method, make sure to maintain the existing
  308. * behavior.
  309. *
  310. * @return a <code>MouseMotionListener</code> which will be added to
  311. * the combo box or null
  312. */
  313. protected MouseMotionListener createMouseMotionListener() {
  314. return new InvocationMouseMotionHandler();
  315. }
  316. /**
  317. * Creates the key listener that will be added to the combo box. If
  318. * this method returns null then it will not be added to the combo box.
  319. *
  320. * @return a <code>KeyListener</code> or null
  321. */
  322. protected KeyListener createKeyListener() {
  323. return null;
  324. }
  325. /**
  326. * Creates a list selection listener that watches for selection changes in
  327. * the popup's list. If this method returns null then it will not
  328. * be added to the popup list.
  329. *
  330. * @return an instance of a <code>ListSelectionListener</code> or null
  331. */
  332. protected ListSelectionListener createListSelectionListener() {
  333. return null;
  334. }
  335. /**
  336. * Creates a list data listener which will be added to the
  337. * <code>ComboBoxModel</code>. If this method returns null then
  338. * it will not be added to the combo box model.
  339. *
  340. * @return an instance of a <code>ListDataListener</code> or null
  341. */
  342. protected ListDataListener createListDataListener() {
  343. return null;
  344. }
  345. /**
  346. * Creates a mouse listener that watches for mouse events in
  347. * the popup's list. If this method returns null then it will
  348. * not be added to the combo box.
  349. *
  350. * @return an instance of a <code>MouseListener</code> or null
  351. */
  352. protected MouseListener createListMouseListener() {
  353. return new ListMouseHandler();
  354. }
  355. /**
  356. * Creates a mouse motion listener that watches for mouse motion
  357. * events in the popup's list. If this method returns null then it will
  358. * not be added to the combo box.
  359. *
  360. * @return an instance of a <code>MouseMotionListener</code> or null
  361. */
  362. protected MouseMotionListener createListMouseMotionListener() {
  363. return new ListMouseMotionHandler();
  364. }
  365. /**
  366. * Creates a <code>PropertyChangeListener</code> which will be added to
  367. * the combo box. If this method returns null then it will not
  368. * be added to the combo box.
  369. *
  370. * @return an instance of a <code>PropertyChangeListener</code> or null
  371. */
  372. protected PropertyChangeListener createPropertyChangeListener() {
  373. return new PropertyChangeHandler();
  374. }
  375. /**
  376. * Creates an <code>ItemListener</code> which will be added to the
  377. * combo box. If this method returns null then it will not
  378. * be added to the combo box.
  379. * <p>
  380. * Subclasses may override this method to return instances of their own
  381. * ItemEvent handlers.
  382. *
  383. * @return an instance of an <code>ItemListener</code> or null
  384. */
  385. protected ItemListener createItemListener() {
  386. return new ItemHandler();
  387. }
  388. /**
  389. * Creates the JList used in the popup to display
  390. * the items in the combo box model. This method is called when the UI class
  391. * is created.
  392. *
  393. * @return a <code>JList</code> used to display the combo box items
  394. */
  395. protected JList createList() {
  396. return new JList( comboBox.getModel() ) {
  397. public void processMouseEvent(MouseEvent e) {
  398. if (e.isControlDown()) {
  399. // Fix for 4234053. Filter out the Control Key from the list.
  400. // ie., don't allow CTRL key deselection.
  401. e = new MouseEvent((Component)e.getSource(), e.getID(), e.getWhen(),
  402. e.getModifiers() ^ InputEvent.CTRL_MASK,
  403. e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger());
  404. }
  405. super.processMouseEvent(e);
  406. }
  407. };
  408. }
  409. /**
  410. * Configures the list which is used to hold the combo box items in the
  411. * popup. This method is called when the UI class
  412. * is created.
  413. *
  414. * @see #createList
  415. */
  416. protected void configureList() {
  417. list.setFont( comboBox.getFont() );
  418. list.setCellRenderer( comboBox.getRenderer() );
  419. list.setFocusable( false );
  420. list.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
  421. setListSelection( comboBox.getSelectedIndex() );
  422. installListListeners();
  423. }
  424. /**
  425. * Adds the listeners to the list control.
  426. */
  427. protected void installListListeners() {
  428. if ((listMouseListener = createListMouseListener()) != null) {
  429. list.addMouseListener( listMouseListener );
  430. }
  431. if ((listMouseMotionListener = createListMouseMotionListener()) != null) {
  432. list.addMouseMotionListener( listMouseMotionListener );
  433. }
  434. if ((listSelectionListener = createListSelectionListener()) != null) {
  435. list.addListSelectionListener( listSelectionListener );
  436. }
  437. }
  438. void uninstallListListeners() {
  439. if (listMouseListener != null) {
  440. list.removeMouseListener(listMouseListener);
  441. listMouseListener = null;
  442. }
  443. if (listMouseMotionListener != null) {
  444. list.removeMouseMotionListener(listMouseMotionListener);
  445. listMouseMotionListener = null;
  446. }
  447. if (listSelectionListener != null) {
  448. list.removeListSelectionListener(listSelectionListener);
  449. listSelectionListener = null;
  450. }
  451. }
  452. /**
  453. * Creates the scroll pane which houses the scrollable list.
  454. */
  455. protected JScrollPane createScroller() {
  456. return new JScrollPane( list,
  457. ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
  458. ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER );
  459. }
  460. /**
  461. * Configures the scrollable portion which holds the list within
  462. * the combo box popup. This method is called when the UI class
  463. * is created.
  464. */
  465. protected void configureScroller() {
  466. scroller.setFocusable( false );
  467. scroller.getVerticalScrollBar().setFocusable( false );
  468. scroller.setBorder( null );
  469. }
  470. /**
  471. * Configures the popup portion of the combo box. This method is called
  472. * when the UI class is created.
  473. */
  474. protected void configurePopup() {
  475. setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) );
  476. setBorderPainted( true );
  477. setBorder( BorderFactory.createLineBorder( Color.black ) );
  478. setOpaque( false );
  479. add( scroller );
  480. setDoubleBuffered( true );
  481. setFocusable( false );
  482. }
  483. /**
  484. * This method adds the necessary listeners to the JComboBox.
  485. */
  486. protected void installComboBoxListeners() {
  487. if ((propertyChangeListener = createPropertyChangeListener()) != null) {
  488. comboBox.addPropertyChangeListener(propertyChangeListener);
  489. }
  490. if ((itemListener = createItemListener()) != null) {
  491. comboBox.addItemListener(itemListener);
  492. }
  493. installComboBoxModelListeners(comboBox.getModel());
  494. }
  495. /**
  496. * Installs the listeners on the combo box model. Any listeners installed
  497. * on the combo box model should be removed in
  498. * <code>uninstallComboBoxModelListeners</code>.
  499. *
  500. * @param model The combo box model to install listeners
  501. * @see #uninstallComboBoxModelListeners
  502. */
  503. protected void installComboBoxModelListeners( ComboBoxModel model ) {
  504. if (model != null && (listDataListener = createListDataListener()) != null) {
  505. model.addListDataListener(listDataListener);
  506. }
  507. }
  508. protected void installKeyboardActions() {
  509. /* XXX - shouldn't call this method. take it out for testing.
  510. ActionListener action = new ActionListener() {
  511. public void actionPerformed(ActionEvent e){
  512. }
  513. };
  514. comboBox.registerKeyboardAction( action,
  515. KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ),
  516. JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); */
  517. }
  518. //
  519. // end Initialization routines
  520. //=================================================================
  521. //===================================================================
  522. // begin Event Listenters
  523. //
  524. /**
  525. * A listener to be registered upon the combo box
  526. * (<em>not</em> its popup menu)
  527. * to handle mouse events
  528. * that affect the state of the popup menu.
  529. * The main purpose of this listener is to make the popup menu
  530. * appear and disappear.
  531. * This listener also helps
  532. * with click-and-drag scenarios by setting the selection if the mouse was
  533. * released over the list during a drag.
  534. *
  535. * <p>
  536. * <strong>Warning:</strong>
  537. * We recommend that you <em>not</em>
  538. * create subclasses of this class.
  539. * If you absolutely must create a subclass,
  540. * be sure to invoke the superclass
  541. * version of each method.
  542. *
  543. * @see BasicComboPopup#createMouseListener
  544. */
  545. protected class InvocationMouseHandler extends MouseAdapter {
  546. /**
  547. * Responds to mouse-pressed events on the combo box.
  548. *
  549. * @param e the mouse-press event to be handled
  550. */
  551. public void mousePressed( MouseEvent e ) {
  552. if (!SwingUtilities.isLeftMouseButton(e) || !comboBox.isEnabled())
  553. return;
  554. if ( comboBox.isEditable() ) {
  555. Component comp = comboBox.getEditor().getEditorComponent();
  556. if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) {
  557. comp.requestFocus();
  558. }
  559. }
  560. else if (comboBox.isRequestFocusEnabled()) {
  561. comboBox.requestFocus();
  562. }
  563. togglePopup();
  564. }
  565. /**
  566. * Responds to the user terminating
  567. * a click or drag that began on the combo box.
  568. *
  569. * @param e the mouse-release event to be handled
  570. */
  571. public void mouseReleased( MouseEvent e ) {
  572. Component source = (Component)e.getSource();
  573. Dimension size = source.getSize();
  574. Rectangle bounds = new Rectangle( 0, 0, size.width - 1, size.height - 1 );
  575. if ( !bounds.contains( e.getPoint() ) ) {
  576. MouseEvent newEvent = convertMouseEvent( e );
  577. Point location = newEvent.getPoint();
  578. Rectangle r = new Rectangle();
  579. list.computeVisibleRect( r );
  580. if ( r.contains( location ) ) {
  581. comboBox.setSelectedIndex( list.getSelectedIndex() );
  582. }
  583. comboBox.setPopupVisible(false);
  584. }
  585. hasEntered = false;
  586. stopAutoScrolling();
  587. }
  588. }
  589. /**
  590. * This listener watches for dragging and updates the current selection in the
  591. * list if it is dragging over the list.
  592. */
  593. protected class InvocationMouseMotionHandler extends MouseMotionAdapter {
  594. public void mouseDragged( MouseEvent e ) {
  595. if ( isVisible() ) {
  596. MouseEvent newEvent = convertMouseEvent( e );
  597. Rectangle r = new Rectangle();
  598. list.computeVisibleRect( r );
  599. if ( newEvent.getPoint().y >= r.y && newEvent.getPoint().y <= r.y + r.height - 1 ) {
  600. hasEntered = true;
  601. if ( isAutoScrolling ) {
  602. stopAutoScrolling();
  603. }
  604. Point location = newEvent.getPoint();
  605. if ( r.contains( location ) ) {
  606. updateListBoxSelectionForEvent( newEvent, false );
  607. }
  608. }
  609. else {
  610. if ( hasEntered ) {
  611. int directionToScroll = newEvent.getPoint().y < r.y ? SCROLL_UP : SCROLL_DOWN;
  612. if ( isAutoScrolling && scrollDirection != directionToScroll ) {
  613. stopAutoScrolling();
  614. startAutoScrolling( directionToScroll );
  615. }
  616. else if ( !isAutoScrolling ) {
  617. startAutoScrolling( directionToScroll );
  618. }
  619. }
  620. else {
  621. if ( e.getPoint().y < 0 ) {
  622. hasEntered = true;
  623. startAutoScrolling( SCROLL_UP );
  624. }
  625. }
  626. }
  627. }
  628. }
  629. }
  630. /**
  631. * As of Java 2 platform v 1.4, this class is now obsolete and is only included for
  632. * backwards API compatibility. Do not instantiate or subclass.
  633. * <p>
  634. * All the functionality of this class has been included in
  635. * BasicComboBoxUI ActionMap/InputMap methods.
  636. */
  637. class InvocationKeyHandler extends KeyAdapter {
  638. public void keyReleased( KeyEvent e ) {}
  639. }
  640. /**
  641. * As of Java 2 platform v 1.4, this class is now obsolete, doesn't do anything, and
  642. * is only included for backwards API compatibility. Do not call or
  643. * override.
  644. */
  645. protected class ListSelectionHandler implements ListSelectionListener {
  646. public void valueChanged( ListSelectionEvent e ) {}
  647. }
  648. /**
  649. * As of 1.4, this class is now obsolete, doesn't do anything, and
  650. * is only included for backwards API compatibility. Do not call or
  651. * override.
  652. * <p>
  653. * The functionality has been migrated into <code>ItemHandler</code>.
  654. *
  655. * @see #createItemListener
  656. */
  657. class ListDataHandler implements ListDataListener {
  658. public void contentsChanged( ListDataEvent e ) {}
  659. public void intervalAdded( ListDataEvent e ) {
  660. }
  661. public void intervalRemoved( ListDataEvent e ) {
  662. }
  663. }
  664. /**
  665. * This listener hides the popup when the mouse is released in the list.
  666. */
  667. protected class ListMouseHandler extends MouseAdapter {
  668. public void mousePressed( MouseEvent e ) {
  669. }
  670. public void mouseReleased(MouseEvent anEvent) {
  671. comboBox.setSelectedIndex( list.getSelectedIndex() );
  672. comboBox.setPopupVisible(false);
  673. // workaround for cancelling an edited item (bug 4530953)
  674. if (comboBox.isEditable() && comboBox.getEditor() != null) {
  675. comboBox.configureEditor(comboBox.getEditor(),
  676. comboBox.getSelectedItem());
  677. }
  678. }
  679. }
  680. /**
  681. * This listener changes the selected item as you move the mouse over the list.
  682. * The selection change is not committed to the model, this is for user feedback only.
  683. */
  684. protected class ListMouseMotionHandler extends MouseMotionAdapter {
  685. public void mouseMoved( MouseEvent anEvent ) {
  686. Point location = anEvent.getPoint();
  687. Rectangle r = new Rectangle();
  688. list.computeVisibleRect( r );
  689. if ( r.contains( location ) ) {
  690. updateListBoxSelectionForEvent( anEvent, false );
  691. }
  692. }
  693. }
  694. /**
  695. * This listener watches for changes to the selection in the
  696. * combo box.
  697. */
  698. protected class ItemHandler implements ItemListener {
  699. public void itemStateChanged( ItemEvent e ) {
  700. if (e.getStateChange() == ItemEvent.SELECTED) {
  701. JComboBox comboBox = (JComboBox)e.getSource();
  702. setListSelection(comboBox.getSelectedIndex());
  703. }
  704. }
  705. }
  706. /**
  707. * This listener watches for bound properties that have changed in the
  708. * combo box.
  709. * <p>
  710. * Subclasses which wish to listen to combo box property changes should
  711. * call the superclass methods to ensure that the combo popup correctly
  712. * handles property changes.
  713. *
  714. * @see #createPropertyChangeListener
  715. */
  716. protected class PropertyChangeHandler implements PropertyChangeListener {
  717. public void propertyChange( PropertyChangeEvent e ) {
  718. JComboBox comboBox = (JComboBox)e.getSource();
  719. String propertyName = e.getPropertyName();
  720. if ( propertyName.equals("model") ) {
  721. ComboBoxModel oldModel = (ComboBoxModel)e.getOldValue();
  722. ComboBoxModel newModel = (ComboBoxModel)e.getNewValue();
  723. uninstallComboBoxModelListeners(oldModel);
  724. installComboBoxModelListeners(newModel);
  725. list.setModel(newModel);
  726. if ( isVisible() ) {
  727. hide();
  728. }
  729. }
  730. else if ( propertyName.equals( "renderer" ) ) {
  731. list.setCellRenderer( comboBox.getRenderer() );
  732. if ( isVisible() ) {
  733. hide();
  734. }
  735. }
  736. else if (propertyName.equals("componentOrientation")) {
  737. // Pass along the new component orientation
  738. // to the list and the scroller
  739. ComponentOrientation o =(ComponentOrientation)e.getNewValue();
  740. JList list = getList();
  741. if (list!=null && list.getComponentOrientation()!=o) {
  742. list.setComponentOrientation(o);
  743. }
  744. if (scroller!=null && scroller.getComponentOrientation()!=o) {
  745. scroller.setComponentOrientation(o);
  746. }
  747. if (o!=getComponentOrientation()) {
  748. setComponentOrientation(o);
  749. }
  750. }
  751. else if (propertyName.equals("lightWeightPopupEnabled")) {
  752. setLightWeightPopupEnabled(comboBox.isLightWeightPopupEnabled());
  753. }
  754. }
  755. }
  756. //
  757. // end Event Listeners
  758. //=================================================================
  759. /**
  760. * Overridden to unconditionally return false.
  761. */
  762. public boolean isFocusTraversable() {
  763. return false;
  764. }
  765. //===================================================================
  766. // begin Autoscroll methods
  767. //
  768. /**
  769. * This protected method is implementation specific and should be private.
  770. * do not call or override.
  771. */
  772. protected void startAutoScrolling( int direction ) {
  773. // XXX - should be a private method within InvocationMouseMotionHandler
  774. // if possible.
  775. if ( isAutoScrolling ) {
  776. autoscrollTimer.stop();
  777. }
  778. isAutoScrolling = true;
  779. if ( direction == SCROLL_UP ) {
  780. scrollDirection = SCROLL_UP;
  781. Point convertedPoint = SwingUtilities.convertPoint( scroller, new Point( 1, 1 ), list );
  782. int top = list.locationToIndex( convertedPoint );
  783. list.setSelectedIndex( top );
  784. ActionListener timerAction = new ActionListener() {
  785. public void actionPerformed(ActionEvent e) {
  786. autoScrollUp();
  787. }
  788. };
  789. autoscrollTimer = new Timer( 100, timerAction );
  790. }
  791. else if ( direction == SCROLL_DOWN ) {
  792. scrollDirection = SCROLL_DOWN;
  793. Dimension size = scroller.getSize();
  794. Point convertedPoint = SwingUtilities.convertPoint( scroller,
  795. new Point( 1, (size.height - 1) - 2 ),
  796. list );
  797. int bottom = list.locationToIndex( convertedPoint );
  798. list.setSelectedIndex( bottom );
  799. ActionListener timerAction = new ActionListener() {
  800. public void actionPerformed(ActionEvent e) {
  801. autoScrollDown();
  802. }
  803. };
  804. autoscrollTimer = new Timer( 100, timerAction );
  805. }
  806. autoscrollTimer.start();
  807. }
  808. /**
  809. * This protected method is implementation specific and should be private.
  810. * do not call or override.
  811. */
  812. protected void stopAutoScrolling() {
  813. isAutoScrolling = false;
  814. if ( autoscrollTimer != null ) {
  815. autoscrollTimer.stop();
  816. autoscrollTimer = null;
  817. }
  818. }
  819. /**
  820. * This protected method is implementation specific and should be private.
  821. * do not call or override.
  822. */
  823. protected void autoScrollUp() {
  824. int index = list.getSelectedIndex();
  825. if ( index > 0 ) {
  826. list.setSelectedIndex( index - 1 );
  827. list.ensureIndexIsVisible( index - 1 );
  828. }
  829. }
  830. /**
  831. * This protected method is implementation specific and should be private.
  832. * do not call or override.
  833. */
  834. protected void autoScrollDown() {
  835. int index = list.getSelectedIndex();
  836. int lastItem = list.getModel().getSize() - 1;
  837. if ( index < lastItem ) {
  838. list.setSelectedIndex( index + 1 );
  839. list.ensureIndexIsVisible( index + 1 );
  840. }
  841. }
  842. //
  843. // end Autoscroll methods
  844. //=================================================================
  845. //===================================================================
  846. // begin Utility methods
  847. //
  848. /**
  849. * This is is a utility method that helps event handlers figure out where to
  850. * send the focus when the popup is brought up. The standard implementation
  851. * delegates the focus to the editor (if the combo box is editable) or to
  852. * the JComboBox if it is not editable.
  853. */
  854. protected void delegateFocus( MouseEvent e ) {
  855. if ( comboBox.isEditable() ) {
  856. Component comp = comboBox.getEditor().getEditorComponent();
  857. if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) {
  858. comp.requestFocus();
  859. }
  860. }
  861. else if (comboBox.isRequestFocusEnabled()) {
  862. comboBox.requestFocus();
  863. }
  864. }
  865. /**
  866. * Makes the popup visible if it is hidden and makes it hidden if it is
  867. * visible.
  868. */
  869. protected void togglePopup() {
  870. if ( isVisible() ) {
  871. hide();
  872. }
  873. else {
  874. show();
  875. }
  876. }
  877. /**
  878. * Sets the list selection index to the selectedIndex. This
  879. * method is used to synchronize the list selection with the
  880. * combo box selection.
  881. *
  882. * @param selectedIndex the index to set the list
  883. */
  884. private void setListSelection(int selectedIndex) {
  885. if ( selectedIndex == -1 ) {
  886. list.clearSelection();
  887. }
  888. else {
  889. list.setSelectedIndex( selectedIndex );
  890. list.ensureIndexIsVisible( selectedIndex );
  891. }
  892. }
  893. protected MouseEvent convertMouseEvent( MouseEvent e ) {
  894. Point convertedPoint = SwingUtilities.convertPoint( (Component)e.getSource(),
  895. e.getPoint(), list );
  896. MouseEvent newEvent = new MouseEvent( (Component)e.getSource(),
  897. e.getID(),
  898. e.getWhen(),
  899. e.getModifiers(),
  900. convertedPoint.x,
  901. convertedPoint.y,
  902. e.getClickCount(),
  903. e.isPopupTrigger() );
  904. return newEvent;
  905. }
  906. /**
  907. * Retrieves the height of the popup based on the current
  908. * ListCellRenderer and the maximum row count.
  909. */
  910. protected int getPopupHeightForRowCount(int maxRowCount) {
  911. // Set the cached value of the minimum row count
  912. int minRowCount = Math.min( maxRowCount, comboBox.getItemCount() );
  913. int height = 0;
  914. ListCellRenderer renderer = list.getCellRenderer();
  915. Object value = null;
  916. for ( int i = 0; i < minRowCount; ++i ) {
  917. value = list.getModel().getElementAt( i );
  918. Component c = renderer.getListCellRendererComponent( list, value, i, false, false );
  919. height += c.getPreferredSize().height;
  920. }
  921. return height == 0 ? 100 : height;
  922. }
  923. /**
  924. * Calculate the placement and size of the popup portion of the combo box based
  925. * on the combo box location and the enclosing screen bounds. If
  926. * no transformations are required, then the returned rectangle will
  927. * have the same values as the parameters.
  928. *
  929. * @param px starting x location
  930. * @param py starting y location
  931. * @param pw starting width
  932. * @param ph starting height
  933. * @return a rectangle which represents the placement and size of the popup
  934. */
  935. protected Rectangle computePopupBounds(int px,int py,int pw,int ph) {
  936. Toolkit toolkit = Toolkit.getDefaultToolkit();
  937. Rectangle screenBounds;
  938. // Calculate the desktop dimensions relative to the combo box.
  939. GraphicsConfiguration gc = comboBox.getGraphicsConfiguration();
  940. Point p = new Point();
  941. SwingUtilities.convertPointFromScreen(p, comboBox);
  942. if (gc != null) {
  943. Insets screenInsets = toolkit.getScreenInsets(gc);
  944. screenBounds = gc.getBounds();
  945. screenBounds.width -= (screenInsets.left + screenInsets.right);
  946. screenBounds.height -= (screenInsets.top + screenInsets.bottom);
  947. screenBounds.x += (p.x + screenInsets.left);
  948. screenBounds.y += (p.y + screenInsets.top);
  949. }
  950. else {
  951. screenBounds = new Rectangle(p, toolkit.getScreenSize());
  952. }
  953. Rectangle rect = new Rectangle(px,py,pw,ph);
  954. if (py+ph > screenBounds.y+screenBounds.height
  955. && ph < screenBounds.height) {
  956. rect.y = -rect.height;
  957. }
  958. return rect;
  959. }
  960. /**
  961. * Calculates the upper left location of the Popup.
  962. */
  963. private Point getPopupLocation() {
  964. Dimension popupSize = comboBox.getSize();
  965. Insets insets = getInsets();
  966. // reduce the width of the scrollpane by the insets so that the popup
  967. // is the same width as the combo box.
  968. popupSize.setSize(popupSize.width - (insets.right + insets.left),
  969. getPopupHeightForRowCount( comboBox.getMaximumRowCount()));
  970. Rectangle popupBounds = computePopupBounds( 0, comboBox.getBounds().height,
  971. popupSize.width, popupSize.height);
  972. Dimension scrollSize = popupBounds.getSize();
  973. Point popupLocation = popupBounds.getLocation();
  974. scroller.setMaximumSize( scrollSize );
  975. scroller.setPreferredSize( scrollSize );
  976. scroller.setMinimumSize( scrollSize );
  977. list.revalidate();
  978. return popupLocation;
  979. }
  980. /**
  981. * A utility method used by the event listeners. Given a mouse event, it changes
  982. * the list selection to the list item below the mouse.
  983. */
  984. protected void updateListBoxSelectionForEvent(MouseEvent anEvent,boolean shouldScroll) {
  985. // XXX - only seems to be called from this class. shouldScroll flag is
  986. // never true
  987. Point location = anEvent.getPoint();
  988. if ( list == null )
  989. return;
  990. int index = list.locationToIndex(location);
  991. if ( index == -1 ) {
  992. if ( location.y < 0 )
  993. index = 0;
  994. else
  995. index = comboBox.getModel().getSize() - 1;
  996. }
  997. if ( list.getSelectedIndex() != index ) {
  998. list.setSelectedIndex(index);
  999. if ( shouldScroll )
  1000. list.ensureIndexIsVisible(index);
  1001. }
  1002. }
  1003. //
  1004. // end Utility methods
  1005. //=================================================================
  1006. }