1 /* 2 * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.plaf.basic; 27 28 import javax.accessibility.AccessibleContext; 29 import javax.swing.*; 30 import javax.swing.border.Border; 31 import javax.swing.border.LineBorder; 32 import javax.swing.event.*; 33 import java.awt.*; 34 import java.awt.event.*; 35 import java.beans.PropertyChangeListener; 36 import java.beans.PropertyChangeEvent; 37 import java.io.Serializable; 38 39 40 /** 41 * This is a basic implementation of the <code>ComboPopup</code> interface. 42 * 43 * This class represents the ui for the popup portion of the combo box. 44 * <p> 45 * All event handling is handled by listener classes created with the 46 * <code>createxxxListener()</code> methods and internal classes. 47 * You can change the behavior of this class by overriding the 48 * <code>createxxxListener()</code> methods and supplying your own 49 * event listeners or subclassing from the ones supplied in this class. 50 * <p> 51 * <strong>Warning:</strong> 52 * Serialized objects of this class will not be compatible with 53 * future Swing releases. The current serialization support is 54 * appropriate for short term storage or RMI between applications running 55 * the same version of Swing. As of 1.4, support for long term storage 56 * of all JavaBeans™ 57 * has been added to the <code>java.beans</code> package. 58 * Please see {@link java.beans.XMLEncoder}. 59 * 60 * @author Tom Santos 61 * @author Mark Davidson 62 */ 63 @SuppressWarnings("serial") // Same-version serialization only 64 public class BasicComboPopup extends JPopupMenu implements ComboPopup { 65 // An empty ListMode, this is used when the UI changes to allow 66 // the JList to be gc'ed. 67 private static class EmptyListModelClass implements ListModel<Object>, Serializable { 68 public int getSize() { return 0; } 69 public Object getElementAt(int index) { return null; } 70 public void addListDataListener(ListDataListener l) {} 71 public void removeListDataListener(ListDataListener l) {} 72 }; 73 74 static final ListModel<Object> EmptyListModel = new EmptyListModelClass(); 75 76 private static Border LIST_BORDER = new LineBorder(Color.BLACK, 1); 77 78 /** 79 * The instance of {@code JComboBox}. 80 */ 81 protected JComboBox<Object> comboBox; 82 /** 83 * This protected field is implementation specific. Do not access directly 84 * or override. Use the accessor methods instead. 85 * 86 * @see #getList 87 * @see #createList 88 */ 89 protected JList<Object> list; 90 /** 91 * This protected field is implementation specific. Do not access directly 92 * or override. Use the create method instead 93 * 94 * @see #createScroller 95 */ 96 protected JScrollPane scroller; 97 98 /** 99 * As of Java 2 platform v1.4 this previously undocumented field is no 100 * longer used. 101 */ 102 protected boolean valueIsAdjusting = false; 103 104 // Listeners that are required by the ComboPopup interface 105 106 /** 107 * Implementation of all the listener classes. 108 */ 109 private Handler handler; 110 111 /** 112 * This protected field is implementation specific. Do not access directly 113 * or override. Use the accessor or create methods instead. 114 * 115 * @see #getMouseMotionListener 116 * @see #createMouseMotionListener 117 */ 118 protected MouseMotionListener mouseMotionListener; 119 /** 120 * This protected field is implementation specific. Do not access directly 121 * or override. Use the accessor or create methods instead. 122 * 123 * @see #getMouseListener 124 * @see #createMouseListener 125 */ 126 protected MouseListener mouseListener; 127 128 /** 129 * This protected field is implementation specific. Do not access directly 130 * or override. Use the accessor or create methods instead. 131 * 132 * @see #getKeyListener 133 * @see #createKeyListener 134 */ 135 protected KeyListener keyListener; 136 137 /** 138 * This protected field is implementation specific. Do not access directly 139 * or override. Use the create method instead. 140 * 141 * @see #createListSelectionListener 142 */ 143 protected ListSelectionListener listSelectionListener; 144 145 // Listeners that are attached to the list 146 /** 147 * This protected field is implementation specific. Do not access directly 148 * or override. Use the create method instead. 149 * 150 * @see #createListMouseListener 151 */ 152 protected MouseListener listMouseListener; 153 /** 154 * This protected field is implementation specific. Do not access directly 155 * or override. Use the create method instead 156 * 157 * @see #createListMouseMotionListener 158 */ 159 protected MouseMotionListener listMouseMotionListener; 160 161 // Added to the combo box for bound properties 162 /** 163 * This protected field is implementation specific. Do not access directly 164 * or override. Use the create method instead 165 * 166 * @see #createPropertyChangeListener 167 */ 168 protected PropertyChangeListener propertyChangeListener; 169 170 // Added to the combo box model 171 /** 172 * This protected field is implementation specific. Do not access directly 173 * or override. Use the create method instead 174 * 175 * @see #createListDataListener 176 */ 177 protected ListDataListener listDataListener; 178 179 /** 180 * This protected field is implementation specific. Do not access directly 181 * or override. Use the create method instead 182 * 183 * @see #createItemListener 184 */ 185 protected ItemListener itemListener; 186 187 private MouseWheelListener scrollerMouseWheelListener; 188 189 /** 190 * This protected field is implementation specific. Do not access directly 191 * or override. 192 */ 193 protected Timer autoscrollTimer; 194 195 /** 196 * {@code true} if the mouse cursor is in the popup. 197 */ 198 protected boolean hasEntered = false; 199 200 /** 201 * If {@code true} the auto-scrolling is enabled. 202 */ 203 protected boolean isAutoScrolling = false; 204 205 /** 206 * The direction of scrolling. 207 */ 208 protected int scrollDirection = SCROLL_UP; 209 210 /** 211 * The direction of scrolling up. 212 */ 213 protected static final int SCROLL_UP = 0; 214 215 /** 216 * The direction of scrolling down. 217 */ 218 protected static final int SCROLL_DOWN = 1; 219 220 221 //======================================== 222 // begin ComboPopup method implementations 223 // 224 225 /** 226 * Implementation of ComboPopup.show(). 227 */ 228 @SuppressWarnings("deprecation") 229 public void show() { 230 comboBox.firePopupMenuWillBecomeVisible(); 231 setListSelection(comboBox.getSelectedIndex()); 232 Point location = getPopupLocation(); 233 show( comboBox, location.x, location.y ); 234 } 235 236 237 /** 238 * Implementation of ComboPopup.hide(). 239 */ 240 @SuppressWarnings("deprecation") 241 public void hide() { 242 MenuSelectionManager manager = MenuSelectionManager.defaultManager(); 243 MenuElement [] selection = manager.getSelectedPath(); 244 for ( int i = 0 ; i < selection.length ; i++ ) { 245 if ( selection[i] == this ) { 246 manager.clearSelectedPath(); 247 break; 248 } 249 } 250 if (selection.length > 0) { 251 comboBox.repaint(); 252 } 253 } 254 255 /** 256 * Implementation of ComboPopup.getList(). 257 */ 258 public JList<Object> getList() { 259 return list; 260 } 261 262 /** 263 * Implementation of ComboPopup.getMouseListener(). 264 * 265 * @return a <code>MouseListener</code> or null 266 * @see ComboPopup#getMouseListener 267 */ 268 public MouseListener getMouseListener() { 269 if (mouseListener == null) { 270 mouseListener = createMouseListener(); 271 } 272 return mouseListener; 273 } 274 275 /** 276 * Implementation of ComboPopup.getMouseMotionListener(). 277 * 278 * @return a <code>MouseMotionListener</code> or null 279 * @see ComboPopup#getMouseMotionListener 280 */ 281 public MouseMotionListener getMouseMotionListener() { 282 if (mouseMotionListener == null) { 283 mouseMotionListener = createMouseMotionListener(); 284 } 285 return mouseMotionListener; 286 } 287 288 /** 289 * Implementation of ComboPopup.getKeyListener(). 290 * 291 * @return a <code>KeyListener</code> or null 292 * @see ComboPopup#getKeyListener 293 */ 294 public KeyListener getKeyListener() { 295 if (keyListener == null) { 296 keyListener = createKeyListener(); 297 } 298 return keyListener; 299 } 300 301 /** 302 * Called when the UI is uninstalling. Since this popup isn't in the component 303 * tree, it won't get it's uninstallUI() called. It removes the listeners that 304 * were added in addComboBoxListeners(). 305 */ 306 public void uninstallingUI() { 307 if (propertyChangeListener != null) { 308 comboBox.removePropertyChangeListener( propertyChangeListener ); 309 } 310 if (itemListener != null) { 311 comboBox.removeItemListener( itemListener ); 312 } 313 uninstallComboBoxModelListeners(comboBox.getModel()); 314 uninstallKeyboardActions(); 315 uninstallListListeners(); 316 uninstallScrollerListeners(); 317 // We do this, otherwise the listener the ui installs on 318 // the model (the combobox model in this case) will keep a 319 // reference to the list, causing the list (and us) to never get gced. 320 list.setModel(EmptyListModel); 321 } 322 323 // 324 // end ComboPopup method implementations 325 //====================================== 326 327 /** 328 * Removes the listeners from the combo box model 329 * 330 * @param model The combo box model to install listeners 331 * @see #installComboBoxModelListeners 332 */ 333 protected void uninstallComboBoxModelListeners( ComboBoxModel<?> model ) { 334 if (model != null && listDataListener != null) { 335 model.removeListDataListener(listDataListener); 336 } 337 } 338 339 /** 340 * Unregisters keyboard actions. 341 */ 342 protected void uninstallKeyboardActions() { 343 // XXX - shouldn't call this method 344 // comboBox.unregisterKeyboardAction( KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ) ); 345 } 346 347 348 349 //=================================================================== 350 // begin Initialization routines 351 // 352 353 /** 354 * Constructs a new instance of {@code BasicComboPopup}. 355 * 356 * @param combo an instance of {@code JComboBox} 357 */ 358 public BasicComboPopup( JComboBox<Object> combo ) { 359 super(); 360 setName("ComboPopup.popup"); 361 comboBox = combo; 362 363 setLightWeightPopupEnabled( comboBox.isLightWeightPopupEnabled() ); 364 365 // UI construction of the popup. 366 list = createList(); 367 list.setName("ComboBox.list"); 368 configureList(); 369 scroller = createScroller(); 370 scroller.setName("ComboBox.scrollPane"); 371 configureScroller(); 372 configurePopup(); 373 374 installComboBoxListeners(); 375 installKeyboardActions(); 376 } 377 378 // Overriden PopupMenuListener notification methods to inform combo box 379 // PopupMenuListeners. 380 381 protected void firePopupMenuWillBecomeVisible() { 382 super.firePopupMenuWillBecomeVisible(); 383 // comboBox.firePopupMenuWillBecomeVisible() is called from BasicComboPopup.show() method 384 // to let the user change the popup menu from the PopupMenuListener.popupMenuWillBecomeVisible() 385 } 386 387 protected void firePopupMenuWillBecomeInvisible() { 388 super.firePopupMenuWillBecomeInvisible(); 389 comboBox.firePopupMenuWillBecomeInvisible(); 390 } 391 392 protected void firePopupMenuCanceled() { 393 super.firePopupMenuCanceled(); 394 comboBox.firePopupMenuCanceled(); 395 } 396 397 /** 398 * Creates a listener 399 * that will watch for mouse-press and release events on the combo box. 400 * 401 * <strong>Warning:</strong> 402 * When overriding this method, make sure to maintain the existing 403 * behavior. 404 * 405 * @return a <code>MouseListener</code> which will be added to 406 * the combo box or null 407 */ 408 protected MouseListener createMouseListener() { 409 return getHandler(); 410 } 411 412 /** 413 * Creates the mouse motion listener which will be added to the combo 414 * box. 415 * 416 * <strong>Warning:</strong> 417 * When overriding this method, make sure to maintain the existing 418 * behavior. 419 * 420 * @return a <code>MouseMotionListener</code> which will be added to 421 * the combo box or null 422 */ 423 protected MouseMotionListener createMouseMotionListener() { 424 return getHandler(); 425 } 426 427 /** 428 * Creates the key listener that will be added to the combo box. If 429 * this method returns null then it will not be added to the combo box. 430 * 431 * @return a <code>KeyListener</code> or null 432 */ 433 protected KeyListener createKeyListener() { 434 return null; 435 } 436 437 /** 438 * Creates a list selection listener that watches for selection changes in 439 * the popup's list. If this method returns null then it will not 440 * be added to the popup list. 441 * 442 * @return an instance of a <code>ListSelectionListener</code> or null 443 */ 444 protected ListSelectionListener createListSelectionListener() { 445 return null; 446 } 447 448 /** 449 * Creates a list data listener which will be added to the 450 * <code>ComboBoxModel</code>. If this method returns null then 451 * it will not be added to the combo box model. 452 * 453 * @return an instance of a <code>ListDataListener</code> or null 454 */ 455 protected ListDataListener createListDataListener() { 456 return null; 457 } 458 459 /** 460 * Creates a mouse listener that watches for mouse events in 461 * the popup's list. If this method returns null then it will 462 * not be added to the combo box. 463 * 464 * @return an instance of a <code>MouseListener</code> or null 465 */ 466 protected MouseListener createListMouseListener() { 467 return getHandler(); 468 } 469 470 /** 471 * Creates a mouse motion listener that watches for mouse motion 472 * events in the popup's list. If this method returns null then it will 473 * not be added to the combo box. 474 * 475 * @return an instance of a <code>MouseMotionListener</code> or null 476 */ 477 protected MouseMotionListener createListMouseMotionListener() { 478 return getHandler(); 479 } 480 481 /** 482 * Creates a <code>PropertyChangeListener</code> which will be added to 483 * the combo box. If this method returns null then it will not 484 * be added to the combo box. 485 * 486 * @return an instance of a <code>PropertyChangeListener</code> or null 487 */ 488 protected PropertyChangeListener createPropertyChangeListener() { 489 return getHandler(); 490 } 491 492 /** 493 * Creates an <code>ItemListener</code> which will be added to the 494 * combo box. If this method returns null then it will not 495 * be added to the combo box. 496 * <p> 497 * Subclasses may override this method to return instances of their own 498 * ItemEvent handlers. 499 * 500 * @return an instance of an <code>ItemListener</code> or null 501 */ 502 protected ItemListener createItemListener() { 503 return getHandler(); 504 } 505 506 private Handler getHandler() { 507 if (handler == null) { 508 handler = new Handler(); 509 } 510 return handler; 511 } 512 513 /** 514 * Creates the JList used in the popup to display 515 * the items in the combo box model. This method is called when the UI class 516 * is created. 517 * 518 * @return a <code>JList</code> used to display the combo box items 519 */ 520 protected JList<Object> createList() { 521 return new JList<Object>( comboBox.getModel() ) { 522 public void processMouseEvent(MouseEvent e) { 523 if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) { 524 // Fix for 4234053. Filter out the Control Key from the list. 525 // ie., don't allow CTRL key deselection. 526 Toolkit toolkit = Toolkit.getDefaultToolkit(); 527 e = new MouseEvent((Component)e.getSource(), e.getID(), e.getWhen(), 528 e.getModifiers() ^ toolkit.getMenuShortcutKeyMask(), 529 e.getX(), e.getY(), 530 e.getXOnScreen(), e.getYOnScreen(), 531 e.getClickCount(), 532 e.isPopupTrigger(), 533 MouseEvent.NOBUTTON); 534 } 535 super.processMouseEvent(e); 536 } 537 }; 538 } 539 540 /** 541 * Configures the list which is used to hold the combo box items in the 542 * popup. This method is called when the UI class 543 * is created. 544 * 545 * @see #createList 546 */ 547 protected void configureList() { 548 list.setFont( comboBox.getFont() ); 549 list.setForeground( comboBox.getForeground() ); 550 list.setBackground( comboBox.getBackground() ); 551 list.setSelectionForeground( UIManager.getColor( "ComboBox.selectionForeground" ) ); 552 list.setSelectionBackground( UIManager.getColor( "ComboBox.selectionBackground" ) ); 553 list.setBorder( null ); 554 list.setCellRenderer( comboBox.getRenderer() ); 555 list.setFocusable( false ); 556 list.setSelectionMode( ListSelectionModel.SINGLE_SELECTION ); 557 setListSelection( comboBox.getSelectedIndex() ); 558 installListListeners(); 559 } 560 561 /** 562 * Adds the listeners to the list control. 563 */ 564 protected void installListListeners() { 565 if ((listMouseListener = createListMouseListener()) != null) { 566 list.addMouseListener( listMouseListener ); 567 } 568 if ((listMouseMotionListener = createListMouseMotionListener()) != null) { 569 list.addMouseMotionListener( listMouseMotionListener ); 570 } 571 if ((listSelectionListener = createListSelectionListener()) != null) { 572 list.addListSelectionListener( listSelectionListener ); 573 } 574 } 575 576 void uninstallListListeners() { 577 if (listMouseListener != null) { 578 list.removeMouseListener(listMouseListener); 579 listMouseListener = null; 580 } 581 if (listMouseMotionListener != null) { 582 list.removeMouseMotionListener(listMouseMotionListener); 583 listMouseMotionListener = null; 584 } 585 if (listSelectionListener != null) { 586 list.removeListSelectionListener(listSelectionListener); 587 listSelectionListener = null; 588 } 589 handler = null; 590 } 591 592 /** 593 * Creates the scroll pane which houses the scrollable list. 594 * 595 * @return the scroll pane which houses the scrollable list 596 */ 597 protected JScrollPane createScroller() { 598 JScrollPane sp = new JScrollPane( list, 599 ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 600 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); 601 sp.setHorizontalScrollBar(null); 602 return sp; 603 } 604 605 /** 606 * Configures the scrollable portion which holds the list within 607 * the combo box popup. This method is called when the UI class 608 * is created. 609 */ 610 protected void configureScroller() { 611 scroller.setFocusable( false ); 612 scroller.getVerticalScrollBar().setFocusable( false ); 613 scroller.setBorder( null ); 614 installScrollerListeners(); 615 } 616 617 /** 618 * Configures the popup portion of the combo box. This method is called 619 * when the UI class is created. 620 */ 621 protected void configurePopup() { 622 setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) ); 623 setBorderPainted( true ); 624 setBorder(LIST_BORDER); 625 setOpaque( false ); 626 add( scroller ); 627 setDoubleBuffered( true ); 628 setFocusable( false ); 629 } 630 631 private void installScrollerListeners() { 632 scrollerMouseWheelListener = getHandler(); 633 if (scrollerMouseWheelListener != null) { 634 scroller.addMouseWheelListener(scrollerMouseWheelListener); 635 } 636 } 637 638 private void uninstallScrollerListeners() { 639 if (scrollerMouseWheelListener != null) { 640 scroller.removeMouseWheelListener(scrollerMouseWheelListener); 641 scrollerMouseWheelListener = null; 642 } 643 } 644 645 /** 646 * This method adds the necessary listeners to the JComboBox. 647 */ 648 protected void installComboBoxListeners() { 649 if ((propertyChangeListener = createPropertyChangeListener()) != null) { 650 comboBox.addPropertyChangeListener(propertyChangeListener); 651 } 652 if ((itemListener = createItemListener()) != null) { 653 comboBox.addItemListener(itemListener); 654 } 655 installComboBoxModelListeners(comboBox.getModel()); 656 } 657 658 /** 659 * Installs the listeners on the combo box model. Any listeners installed 660 * on the combo box model should be removed in 661 * <code>uninstallComboBoxModelListeners</code>. 662 * 663 * @param model The combo box model to install listeners 664 * @see #uninstallComboBoxModelListeners 665 */ 666 protected void installComboBoxModelListeners( ComboBoxModel<?> model ) { 667 if (model != null && (listDataListener = createListDataListener()) != null) { 668 model.addListDataListener(listDataListener); 669 } 670 } 671 672 /** 673 * Registers keyboard actions. 674 */ 675 protected void installKeyboardActions() { 676 677 /* XXX - shouldn't call this method. take it out for testing. 678 ActionListener action = new ActionListener() { 679 public void actionPerformed(ActionEvent e){ 680 } 681 }; 682 683 comboBox.registerKeyboardAction( action, 684 KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ), 685 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); */ 686 687 } 688 689 // 690 // end Initialization routines 691 //================================================================= 692 693 694 //=================================================================== 695 // begin Event Listenters 696 // 697 698 /** 699 * A listener to be registered upon the combo box 700 * (<em>not</em> its popup menu) 701 * to handle mouse events 702 * that affect the state of the popup menu. 703 * The main purpose of this listener is to make the popup menu 704 * appear and disappear. 705 * This listener also helps 706 * with click-and-drag scenarios by setting the selection if the mouse was 707 * released over the list during a drag. 708 * 709 * <p> 710 * <strong>Warning:</strong> 711 * We recommend that you <em>not</em> 712 * create subclasses of this class. 713 * If you absolutely must create a subclass, 714 * be sure to invoke the superclass 715 * version of each method. 716 * 717 * @see BasicComboPopup#createMouseListener 718 */ 719 protected class InvocationMouseHandler extends MouseAdapter { 720 /** 721 * Responds to mouse-pressed events on the combo box. 722 * 723 * @param e the mouse-press event to be handled 724 */ 725 public void mousePressed( MouseEvent e ) { 726 getHandler().mousePressed(e); 727 } 728 729 /** 730 * Responds to the user terminating 731 * a click or drag that began on the combo box. 732 * 733 * @param e the mouse-release event to be handled 734 */ 735 public void mouseReleased( MouseEvent e ) { 736 getHandler().mouseReleased(e); 737 } 738 } 739 740 /** 741 * This listener watches for dragging and updates the current selection in the 742 * list if it is dragging over the list. 743 */ 744 protected class InvocationMouseMotionHandler extends MouseMotionAdapter { 745 public void mouseDragged( MouseEvent e ) { 746 getHandler().mouseDragged(e); 747 } 748 } 749 750 /** 751 * As of Java 2 platform v 1.4, this class is now obsolete and is only included for 752 * backwards API compatibility. Do not instantiate or subclass. 753 * <p> 754 * All the functionality of this class has been included in 755 * BasicComboBoxUI ActionMap/InputMap methods. 756 */ 757 public class InvocationKeyHandler extends KeyAdapter { 758 public void keyReleased( KeyEvent e ) {} 759 } 760 761 /** 762 * As of Java 2 platform v 1.4, this class is now obsolete, doesn't do anything, and 763 * is only included for backwards API compatibility. Do not call or 764 * override. 765 */ 766 protected class ListSelectionHandler implements ListSelectionListener { 767 public void valueChanged( ListSelectionEvent e ) {} 768 } 769 770 /** 771 * As of 1.4, this class is now obsolete, doesn't do anything, and 772 * is only included for backwards API compatibility. Do not call or 773 * override. 774 * <p> 775 * The functionality has been migrated into <code>ItemHandler</code>. 776 * 777 * @see #createItemListener 778 */ 779 public class ListDataHandler implements ListDataListener { 780 public void contentsChanged( ListDataEvent e ) {} 781 782 public void intervalAdded( ListDataEvent e ) { 783 } 784 785 public void intervalRemoved( ListDataEvent e ) { 786 } 787 } 788 789 /** 790 * This listener hides the popup when the mouse is released in the list. 791 */ 792 protected class ListMouseHandler extends MouseAdapter { 793 public void mousePressed( MouseEvent e ) { 794 } 795 public void mouseReleased(MouseEvent anEvent) { 796 getHandler().mouseReleased(anEvent); 797 } 798 } 799 800 /** 801 * This listener changes the selected item as you move the mouse over the list. 802 * The selection change is not committed to the model, this is for user feedback only. 803 */ 804 protected class ListMouseMotionHandler extends MouseMotionAdapter { 805 public void mouseMoved( MouseEvent anEvent ) { 806 getHandler().mouseMoved(anEvent); 807 } 808 } 809 810 /** 811 * This listener watches for changes to the selection in the 812 * combo box. 813 */ 814 protected class ItemHandler implements ItemListener { 815 public void itemStateChanged( ItemEvent e ) { 816 getHandler().itemStateChanged(e); 817 } 818 } 819 820 /** 821 * This listener watches for bound properties that have changed in the 822 * combo box. 823 * <p> 824 * Subclasses which wish to listen to combo box property changes should 825 * call the superclass methods to ensure that the combo popup correctly 826 * handles property changes. 827 * 828 * @see #createPropertyChangeListener 829 */ 830 protected class PropertyChangeHandler implements PropertyChangeListener { 831 public void propertyChange( PropertyChangeEvent e ) { 832 getHandler().propertyChange(e); 833 } 834 } 835 836 837 private class AutoScrollActionHandler implements ActionListener { 838 private int direction; 839 840 AutoScrollActionHandler(int direction) { 841 this.direction = direction; 842 } 843 844 public void actionPerformed(ActionEvent e) { 845 if (direction == SCROLL_UP) { 846 autoScrollUp(); 847 } 848 else { 849 autoScrollDown(); 850 } 851 } 852 } 853 854 855 private class Handler implements ItemListener, MouseListener, 856 MouseMotionListener, MouseWheelListener, 857 PropertyChangeListener, Serializable { 858 // 859 // MouseListener 860 // NOTE: this is added to both the JList and JComboBox 861 // 862 public void mouseClicked(MouseEvent e) { 863 } 864 865 public void mousePressed(MouseEvent e) { 866 if (e.getSource() == list) { 867 return; 868 } 869 if (!SwingUtilities.isLeftMouseButton(e) || !comboBox.isEnabled()) 870 return; 871 872 if ( comboBox.isEditable() ) { 873 Component comp = comboBox.getEditor().getEditorComponent(); 874 if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) { 875 comp.requestFocus(); 876 } 877 } 878 else if (comboBox.isRequestFocusEnabled()) { 879 comboBox.requestFocus(); 880 } 881 togglePopup(); 882 } 883 884 public void mouseReleased(MouseEvent e) { 885 if (e.getSource() == list) { 886 if (list.getModel().getSize() > 0) { 887 // JList mouse listener 888 if (comboBox.getSelectedIndex() == list.getSelectedIndex()) { 889 comboBox.getEditor().setItem(list.getSelectedValue()); 890 } 891 comboBox.setSelectedIndex(list.getSelectedIndex()); 892 } 893 comboBox.setPopupVisible(false); 894 // workaround for cancelling an edited item (bug 4530953) 895 if (comboBox.isEditable() && comboBox.getEditor() != null) { 896 comboBox.configureEditor(comboBox.getEditor(), 897 comboBox.getSelectedItem()); 898 } 899 return; 900 } 901 // JComboBox mouse listener 902 Component source = (Component)e.getSource(); 903 Dimension size = source.getSize(); 904 Rectangle bounds = new Rectangle( 0, 0, size.width - 1, size.height - 1 ); 905 if ( !bounds.contains( e.getPoint() ) ) { 906 MouseEvent newEvent = convertMouseEvent( e ); 907 Point location = newEvent.getPoint(); 908 Rectangle r = new Rectangle(); 909 list.computeVisibleRect( r ); 910 if ( r.contains( location ) ) { 911 if (comboBox.getSelectedIndex() == list.getSelectedIndex()) { 912 comboBox.getEditor().setItem(list.getSelectedValue()); 913 } 914 comboBox.setSelectedIndex(list.getSelectedIndex()); 915 } 916 comboBox.setPopupVisible(false); 917 } 918 hasEntered = false; 919 stopAutoScrolling(); 920 } 921 922 public void mouseEntered(MouseEvent e) { 923 } 924 925 public void mouseExited(MouseEvent e) { 926 } 927 928 // 929 // MouseMotionListener: 930 // NOTE: this is added to both the List and ComboBox 931 // 932 public void mouseMoved(MouseEvent anEvent) { 933 if (anEvent.getSource() == list) { 934 Point location = anEvent.getPoint(); 935 Rectangle r = new Rectangle(); 936 list.computeVisibleRect( r ); 937 if ( r.contains( location ) ) { 938 updateListBoxSelectionForEvent( anEvent, false ); 939 } 940 } 941 } 942 943 public void mouseDragged( MouseEvent e ) { 944 if (e.getSource() == list) { 945 return; 946 } 947 if ( isVisible() ) { 948 MouseEvent newEvent = convertMouseEvent( e ); 949 Rectangle r = new Rectangle(); 950 list.computeVisibleRect( r ); 951 952 if ( newEvent.getPoint().y >= r.y && newEvent.getPoint().y <= r.y + r.height - 1 ) { 953 hasEntered = true; 954 if ( isAutoScrolling ) { 955 stopAutoScrolling(); 956 } 957 Point location = newEvent.getPoint(); 958 if ( r.contains( location ) ) { 959 updateListBoxSelectionForEvent( newEvent, false ); 960 } 961 } 962 else { 963 if ( hasEntered ) { 964 int directionToScroll = newEvent.getPoint().y < r.y ? SCROLL_UP : SCROLL_DOWN; 965 if ( isAutoScrolling && scrollDirection != directionToScroll ) { 966 stopAutoScrolling(); 967 startAutoScrolling( directionToScroll ); 968 } 969 else if ( !isAutoScrolling ) { 970 startAutoScrolling( directionToScroll ); 971 } 972 } 973 else { 974 if ( e.getPoint().y < 0 ) { 975 hasEntered = true; 976 startAutoScrolling( SCROLL_UP ); 977 } 978 } 979 } 980 } 981 } 982 983 // 984 // PropertyChangeListener 985 // 986 public void propertyChange(PropertyChangeEvent e) { 987 @SuppressWarnings("unchecked") 988 JComboBox<Object> comboBox = (JComboBox)e.getSource(); 989 String propertyName = e.getPropertyName(); 990 991 if ( propertyName == "model" ) { 992 @SuppressWarnings("unchecked") 993 ComboBoxModel<Object> oldModel = (ComboBoxModel)e.getOldValue(); 994 @SuppressWarnings("unchecked") 995 ComboBoxModel<Object> newModel = (ComboBoxModel)e.getNewValue(); 996 uninstallComboBoxModelListeners(oldModel); 997 installComboBoxModelListeners(newModel); 998 999 list.setModel(newModel); 1000 1001 if ( isVisible() ) { 1002 hide(); 1003 } 1004 } 1005 else if ( propertyName == "renderer" ) { 1006 list.setCellRenderer( comboBox.getRenderer() ); 1007 if ( isVisible() ) { 1008 hide(); 1009 } 1010 } 1011 else if (propertyName == "componentOrientation") { 1012 // Pass along the new component orientation 1013 // to the list and the scroller 1014 1015 ComponentOrientation o =(ComponentOrientation)e.getNewValue(); 1016 1017 JList<?> list = getList(); 1018 if (list!=null && list.getComponentOrientation()!=o) { 1019 list.setComponentOrientation(o); 1020 } 1021 1022 if (scroller!=null && scroller.getComponentOrientation()!=o) { 1023 scroller.setComponentOrientation(o); 1024 } 1025 1026 if (o!=getComponentOrientation()) { 1027 setComponentOrientation(o); 1028 } 1029 } 1030 else if (propertyName == "lightWeightPopupEnabled") { 1031 setLightWeightPopupEnabled(comboBox.isLightWeightPopupEnabled()); 1032 } 1033 } 1034 1035 // 1036 // ItemListener 1037 // 1038 public void itemStateChanged( ItemEvent e ) { 1039 if (e.getStateChange() == ItemEvent.SELECTED) { 1040 @SuppressWarnings("unchecked") 1041 JComboBox<Object> comboBox = (JComboBox)e.getSource(); 1042 setListSelection(comboBox.getSelectedIndex()); 1043 } 1044 } 1045 1046 // 1047 // MouseWheelListener 1048 // 1049 public void mouseWheelMoved(MouseWheelEvent e) { 1050 e.consume(); 1051 } 1052 } 1053 1054 // 1055 // end Event Listeners 1056 //================================================================= 1057 1058 1059 /** 1060 * Overridden to unconditionally return false. 1061 */ 1062 @SuppressWarnings("deprecation") 1063 public boolean isFocusTraversable() { 1064 return false; 1065 } 1066 1067 //=================================================================== 1068 // begin Autoscroll methods 1069 // 1070 1071 /** 1072 * This protected method is implementation specific and should be private. 1073 * do not call or override. 1074 * 1075 * @param direction the direction of scrolling 1076 */ 1077 protected void startAutoScrolling( int direction ) { 1078 // XXX - should be a private method within InvocationMouseMotionHandler 1079 // if possible. 1080 if ( isAutoScrolling ) { 1081 autoscrollTimer.stop(); 1082 } 1083 1084 isAutoScrolling = true; 1085 1086 if ( direction == SCROLL_UP ) { 1087 scrollDirection = SCROLL_UP; 1088 Point convertedPoint = SwingUtilities.convertPoint( scroller, new Point( 1, 1 ), list ); 1089 int top = list.locationToIndex( convertedPoint ); 1090 list.setSelectedIndex( top ); 1091 1092 autoscrollTimer = new Timer( 100, new AutoScrollActionHandler( 1093 SCROLL_UP) ); 1094 } 1095 else if ( direction == SCROLL_DOWN ) { 1096 scrollDirection = SCROLL_DOWN; 1097 Dimension size = scroller.getSize(); 1098 Point convertedPoint = SwingUtilities.convertPoint( scroller, 1099 new Point( 1, (size.height - 1) - 2 ), 1100 list ); 1101 int bottom = list.locationToIndex( convertedPoint ); 1102 list.setSelectedIndex( bottom ); 1103 1104 autoscrollTimer = new Timer(100, new AutoScrollActionHandler( 1105 SCROLL_DOWN)); 1106 } 1107 autoscrollTimer.start(); 1108 } 1109 1110 /** 1111 * This protected method is implementation specific and should be private. 1112 * do not call or override. 1113 */ 1114 protected void stopAutoScrolling() { 1115 isAutoScrolling = false; 1116 1117 if ( autoscrollTimer != null ) { 1118 autoscrollTimer.stop(); 1119 autoscrollTimer = null; 1120 } 1121 } 1122 1123 /** 1124 * This protected method is implementation specific and should be private. 1125 * do not call or override. 1126 */ 1127 protected void autoScrollUp() { 1128 int index = list.getSelectedIndex(); 1129 if ( index > 0 ) { 1130 list.setSelectedIndex( index - 1 ); 1131 list.ensureIndexIsVisible( index - 1 ); 1132 } 1133 } 1134 1135 /** 1136 * This protected method is implementation specific and should be private. 1137 * do not call or override. 1138 */ 1139 protected void autoScrollDown() { 1140 int index = list.getSelectedIndex(); 1141 int lastItem = list.getModel().getSize() - 1; 1142 if ( index < lastItem ) { 1143 list.setSelectedIndex( index + 1 ); 1144 list.ensureIndexIsVisible( index + 1 ); 1145 } 1146 } 1147 1148 // 1149 // end Autoscroll methods 1150 //================================================================= 1151 1152 1153 //=================================================================== 1154 // begin Utility methods 1155 // 1156 1157 /** 1158 * Gets the AccessibleContext associated with this BasicComboPopup. 1159 * The AccessibleContext will have its parent set to the ComboBox. 1160 * 1161 * @return an AccessibleContext for the BasicComboPopup 1162 * @since 1.5 1163 */ 1164 public AccessibleContext getAccessibleContext() { 1165 AccessibleContext context = super.getAccessibleContext(); 1166 context.setAccessibleParent(comboBox); 1167 return context; 1168 } 1169 1170 1171 /** 1172 * This is a utility method that helps event handlers figure out where to 1173 * send the focus when the popup is brought up. The standard implementation 1174 * delegates the focus to the editor (if the combo box is editable) or to 1175 * the JComboBox if it is not editable. 1176 * 1177 * @param e a mouse event 1178 */ 1179 protected void delegateFocus( MouseEvent e ) { 1180 if ( comboBox.isEditable() ) { 1181 Component comp = comboBox.getEditor().getEditorComponent(); 1182 if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) { 1183 comp.requestFocus(); 1184 } 1185 } 1186 else if (comboBox.isRequestFocusEnabled()) { 1187 comboBox.requestFocus(); 1188 } 1189 } 1190 1191 /** 1192 * Makes the popup visible if it is hidden and makes it hidden if it is 1193 * visible. 1194 */ 1195 protected void togglePopup() { 1196 if ( isVisible() ) { 1197 hide(); 1198 } 1199 else { 1200 show(); 1201 } 1202 } 1203 1204 /** 1205 * Sets the list selection index to the selectedIndex. This 1206 * method is used to synchronize the list selection with the 1207 * combo box selection. 1208 * 1209 * @param selectedIndex the index to set the list 1210 */ 1211 private void setListSelection(int selectedIndex) { 1212 if ( selectedIndex == -1 ) { 1213 list.clearSelection(); 1214 } 1215 else { 1216 list.setSelectedIndex( selectedIndex ); 1217 list.ensureIndexIsVisible( selectedIndex ); 1218 } 1219 } 1220 1221 /** 1222 * Converts mouse event. 1223 * 1224 * @param e a mouse event 1225 * @return converted mouse event 1226 */ 1227 protected MouseEvent convertMouseEvent( MouseEvent e ) { 1228 Point convertedPoint = SwingUtilities.convertPoint( (Component)e.getSource(), 1229 e.getPoint(), list ); 1230 MouseEvent newEvent = new MouseEvent( (Component)e.getSource(), 1231 e.getID(), 1232 e.getWhen(), 1233 e.getModifiers(), 1234 convertedPoint.x, 1235 convertedPoint.y, 1236 e.getXOnScreen(), 1237 e.getYOnScreen(), 1238 e.getClickCount(), 1239 e.isPopupTrigger(), 1240 MouseEvent.NOBUTTON ); 1241 return newEvent; 1242 } 1243 1244 1245 /** 1246 * Retrieves the height of the popup based on the current 1247 * ListCellRenderer and the maximum row count. 1248 * 1249 * @param maxRowCount the row count 1250 * @return the height of the popup 1251 */ 1252 protected int getPopupHeightForRowCount(int maxRowCount) { 1253 // Set the cached value of the minimum row count 1254 int minRowCount = Math.min( maxRowCount, comboBox.getItemCount() ); 1255 int height = 0; 1256 ListCellRenderer<Object> renderer = list.getCellRenderer(); 1257 Object value = null; 1258 1259 for ( int i = 0; i < minRowCount; ++i ) { 1260 value = list.getModel().getElementAt( i ); 1261 Component c = renderer.getListCellRendererComponent( list, value, i, false, false ); 1262 height += c.getPreferredSize().height; 1263 } 1264 1265 if (height == 0) { 1266 height = comboBox.getHeight(); 1267 } 1268 1269 Border border = scroller.getViewportBorder(); 1270 if (border != null) { 1271 Insets insets = border.getBorderInsets(null); 1272 height += insets.top + insets.bottom; 1273 } 1274 1275 border = scroller.getBorder(); 1276 if (border != null) { 1277 Insets insets = border.getBorderInsets(null); 1278 height += insets.top + insets.bottom; 1279 } 1280 1281 return height; 1282 } 1283 1284 /** 1285 * Calculate the placement and size of the popup portion of the combo box based 1286 * on the combo box location and the enclosing screen bounds. If 1287 * no transformations are required, then the returned rectangle will 1288 * have the same values as the parameters. 1289 * 1290 * @param px starting x location 1291 * @param py starting y location 1292 * @param pw starting width 1293 * @param ph starting height 1294 * @return a rectangle which represents the placement and size of the popup 1295 */ 1296 protected Rectangle computePopupBounds(int px,int py,int pw,int ph) { 1297 Toolkit toolkit = Toolkit.getDefaultToolkit(); 1298 Rectangle screenBounds; 1299 1300 // Calculate the desktop dimensions relative to the combo box. 1301 GraphicsConfiguration gc = comboBox.getGraphicsConfiguration(); 1302 Point p = new Point(); 1303 SwingUtilities.convertPointFromScreen(p, comboBox); 1304 if (gc != null) { 1305 Insets screenInsets = toolkit.getScreenInsets(gc); 1306 screenBounds = gc.getBounds(); 1307 screenBounds.width -= (screenInsets.left + screenInsets.right); 1308 screenBounds.height -= (screenInsets.top + screenInsets.bottom); 1309 screenBounds.x += (p.x + screenInsets.left); 1310 screenBounds.y += (p.y + screenInsets.top); 1311 } 1312 else { 1313 screenBounds = new Rectangle(p, toolkit.getScreenSize()); 1314 } 1315 int borderHeight = 0; 1316 Border popupBorder = getBorder(); 1317 if (popupBorder != null) { 1318 Insets borderInsets = popupBorder.getBorderInsets(this); 1319 borderHeight = borderInsets.top + borderInsets.bottom; 1320 screenBounds.width -= (borderInsets.left + borderInsets.right); 1321 screenBounds.height -= borderHeight; 1322 } 1323 Rectangle rect = new Rectangle(px, py, pw, ph); 1324 if (py + ph > screenBounds.y + screenBounds.height) { 1325 if (ph <= -screenBounds.y - borderHeight) { 1326 // popup goes above 1327 rect.y = -ph - borderHeight; 1328 } else { 1329 // a full screen height popup 1330 rect.y = screenBounds.y + Math.max(0, (screenBounds.height - ph) / 2 ); 1331 rect.height = Math.min(screenBounds.height, ph); 1332 } 1333 } 1334 return rect; 1335 } 1336 1337 /** 1338 * Calculates the upper left location of the Popup. 1339 */ 1340 private Point getPopupLocation() { 1341 Dimension popupSize = comboBox.getSize(); 1342 Insets insets = getInsets(); 1343 1344 // reduce the width of the scrollpane by the insets so that the popup 1345 // is the same width as the combo box. 1346 popupSize.setSize(popupSize.width - (insets.right + insets.left), 1347 getPopupHeightForRowCount( comboBox.getMaximumRowCount())); 1348 Rectangle popupBounds = computePopupBounds( 0, comboBox.getBounds().height, 1349 popupSize.width, popupSize.height); 1350 Dimension scrollSize = popupBounds.getSize(); 1351 Point popupLocation = popupBounds.getLocation(); 1352 1353 scroller.setMaximumSize( scrollSize ); 1354 scroller.setPreferredSize( scrollSize ); 1355 scroller.setMinimumSize( scrollSize ); 1356 1357 list.revalidate(); 1358 1359 return popupLocation; 1360 } 1361 1362 /** 1363 * A utility method used by the event listeners. Given a mouse event, it changes 1364 * the list selection to the list item below the mouse. 1365 * 1366 * @param anEvent a mouse event 1367 * @param shouldScroll if {@code true} list should be scrolled. 1368 */ 1369 protected void updateListBoxSelectionForEvent(MouseEvent anEvent,boolean shouldScroll) { 1370 // XXX - only seems to be called from this class. shouldScroll flag is 1371 // never true 1372 Point location = anEvent.getPoint(); 1373 if ( list == null ) 1374 return; 1375 int index = list.locationToIndex(location); 1376 if ( index == -1 ) { 1377 if ( location.y < 0 ) 1378 index = 0; 1379 else 1380 index = comboBox.getModel().getSize() - 1; 1381 } 1382 if ( list.getSelectedIndex() != index ) { 1383 list.setSelectedIndex(index); 1384 if ( shouldScroll ) 1385 list.ensureIndexIsVisible(index); 1386 } 1387 } 1388 1389 // 1390 // end Utility methods 1391 //================================================================= 1392 } --- EOF ---