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