1 /* 2 * Copyright (c) 1998, 2016, 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 if (scrollerMouseWheelListener != null) { 383 comboBox.addMouseWheelListener(scrollerMouseWheelListener); 384 } 385 super.firePopupMenuWillBecomeVisible(); 386 // comboBox.firePopupMenuWillBecomeVisible() is called from BasicComboPopup.show() method 387 // to let the user change the popup menu from the PopupMenuListener.popupMenuWillBecomeVisible() 388 } 389 390 protected void firePopupMenuWillBecomeInvisible() { 391 if (scrollerMouseWheelListener != null) { 392 comboBox.removeMouseWheelListener(scrollerMouseWheelListener); 393 } 394 super.firePopupMenuWillBecomeInvisible(); 395 comboBox.firePopupMenuWillBecomeInvisible(); 396 } 397 398 protected void firePopupMenuCanceled() { 399 if (scrollerMouseWheelListener != null) { 400 comboBox.removeMouseWheelListener(scrollerMouseWheelListener); 401 } 402 super.firePopupMenuCanceled(); 403 comboBox.firePopupMenuCanceled(); 404 } 405 406 /** 407 * Creates a listener 408 * that will watch for mouse-press and release events on the combo box. 409 * 410 * <strong>Warning:</strong> 411 * When overriding this method, make sure to maintain the existing 412 * behavior. 413 * 414 * @return a <code>MouseListener</code> which will be added to 415 * the combo box or null 416 */ 417 protected MouseListener createMouseListener() { 418 return getHandler(); 419 } 420 421 /** 422 * Creates the mouse motion listener which will be added to the combo 423 * box. 424 * 425 * <strong>Warning:</strong> 426 * When overriding this method, make sure to maintain the existing 427 * behavior. 428 * 429 * @return a <code>MouseMotionListener</code> which will be added to 430 * the combo box or null 431 */ 432 protected MouseMotionListener createMouseMotionListener() { 433 return getHandler(); 434 } 435 436 /** 437 * Creates the key listener that will be added to the combo box. If 438 * this method returns null then it will not be added to the combo box. 439 * 440 * @return a <code>KeyListener</code> or null 441 */ 442 protected KeyListener createKeyListener() { 443 return null; 444 } 445 446 /** 447 * Creates a list selection listener that watches for selection changes in 448 * the popup's list. If this method returns null then it will not 449 * be added to the popup list. 450 * 451 * @return an instance of a <code>ListSelectionListener</code> or null 452 */ 453 protected ListSelectionListener createListSelectionListener() { 454 return null; 455 } 456 457 /** 458 * Creates a list data listener which will be added to the 459 * <code>ComboBoxModel</code>. If this method returns null then 460 * it will not be added to the combo box model. 461 * 462 * @return an instance of a <code>ListDataListener</code> or null 463 */ 464 protected ListDataListener createListDataListener() { 465 return null; 466 } 467 468 /** 469 * Creates a mouse listener that watches for mouse events in 470 * the popup's list. If this method returns null then it will 471 * not be added to the combo box. 472 * 473 * @return an instance of a <code>MouseListener</code> or null 474 */ 475 protected MouseListener createListMouseListener() { 476 return getHandler(); 477 } 478 479 /** 480 * Creates a mouse motion listener that watches for mouse motion 481 * events in the popup's list. If this method returns null then it will 482 * not be added to the combo box. 483 * 484 * @return an instance of a <code>MouseMotionListener</code> or null 485 */ 486 protected MouseMotionListener createListMouseMotionListener() { 487 return getHandler(); 488 } 489 490 /** 491 * Creates a <code>PropertyChangeListener</code> which will be added to 492 * the combo box. If this method returns null then it will not 493 * be added to the combo box. 494 * 495 * @return an instance of a <code>PropertyChangeListener</code> or null 496 */ 497 protected PropertyChangeListener createPropertyChangeListener() { 498 return getHandler(); 499 } 500 501 /** 502 * Creates an <code>ItemListener</code> which will be added to the 503 * combo box. If this method returns null then it will not 504 * be added to the combo box. 505 * <p> 506 * Subclasses may override this method to return instances of their own 507 * ItemEvent handlers. 508 * 509 * @return an instance of an <code>ItemListener</code> or null 510 */ 511 protected ItemListener createItemListener() { 512 return getHandler(); 513 } 514 515 private Handler getHandler() { 516 if (handler == null) { 517 handler = new Handler(); 518 } 519 return handler; 520 } 521 522 /** 523 * Creates the JList used in the popup to display 524 * the items in the combo box model. This method is called when the UI class 525 * is created. 526 * 527 * @return a <code>JList</code> used to display the combo box items 528 */ 529 protected JList<Object> createList() { 530 return new JList<Object>( comboBox.getModel() ) { 531 @SuppressWarnings("deprecation") 532 public void processMouseEvent(MouseEvent e) { 533 if (BasicGraphicsUtils.isMenuShortcutKeyDown(e)) { 534 // Fix for 4234053. Filter out the Control Key from the list. 535 // ie., don't allow CTRL key deselection. 536 Toolkit toolkit = Toolkit.getDefaultToolkit(); 537 e = new MouseEvent((Component)e.getSource(), e.getID(), e.getWhen(), 538 e.getModifiers() ^ toolkit.getMenuShortcutKeyMask(), 539 e.getX(), e.getY(), 540 e.getXOnScreen(), e.getYOnScreen(), 541 e.getClickCount(), 542 e.isPopupTrigger(), 543 MouseEvent.NOBUTTON); 544 } 545 super.processMouseEvent(e); 546 } 547 }; 548 } 549 550 /** 551 * Configures the list which is used to hold the combo box items in the 552 * popup. This method is called when the UI class 553 * is created. 554 * 555 * @see #createList 556 */ 557 protected void configureList() { 558 list.setFont( comboBox.getFont() ); 559 list.setForeground( comboBox.getForeground() ); 560 list.setBackground( comboBox.getBackground() ); 561 list.setSelectionForeground( UIManager.getColor( "ComboBox.selectionForeground" ) ); 562 list.setSelectionBackground( UIManager.getColor( "ComboBox.selectionBackground" ) ); 563 list.setBorder( null ); 564 list.setCellRenderer( comboBox.getRenderer() ); 565 list.setFocusable( false ); 566 list.setSelectionMode( ListSelectionModel.SINGLE_SELECTION ); 567 setListSelection( comboBox.getSelectedIndex() ); 568 installListListeners(); 569 } 570 571 /** 572 * Adds the listeners to the list control. 573 */ 574 protected void installListListeners() { 575 if ((listMouseListener = createListMouseListener()) != null) { 576 list.addMouseListener( listMouseListener ); 577 } 578 if ((listMouseMotionListener = createListMouseMotionListener()) != null) { 579 list.addMouseMotionListener( listMouseMotionListener ); 580 } 581 if ((listSelectionListener = createListSelectionListener()) != null) { 582 list.addListSelectionListener( listSelectionListener ); 583 } 584 } 585 586 void uninstallListListeners() { 587 if (listMouseListener != null) { 588 list.removeMouseListener(listMouseListener); 589 listMouseListener = null; 590 } 591 if (listMouseMotionListener != null) { 592 list.removeMouseMotionListener(listMouseMotionListener); 593 listMouseMotionListener = null; 594 } 595 if (listSelectionListener != null) { 596 list.removeListSelectionListener(listSelectionListener); 597 listSelectionListener = null; 598 } 599 handler = null; 600 } 601 602 /** 603 * Creates the scroll pane which houses the scrollable list. 604 * 605 * @return the scroll pane which houses the scrollable list 606 */ 607 protected JScrollPane createScroller() { 608 JScrollPane sp = new JScrollPane( list, 609 ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, 610 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); 611 sp.setHorizontalScrollBar(null); 612 return sp; 613 } 614 615 /** 616 * Configures the scrollable portion which holds the list within 617 * the combo box popup. This method is called when the UI class 618 * is created. 619 */ 620 protected void configureScroller() { 621 scroller.setFocusable( false ); 622 scroller.getVerticalScrollBar().setFocusable( false ); 623 scroller.setBorder( null ); 624 installScrollerListeners(); 625 } 626 627 /** 628 * Configures the popup portion of the combo box. This method is called 629 * when the UI class is created. 630 */ 631 protected void configurePopup() { 632 setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) ); 633 setBorderPainted( true ); 634 setBorder(LIST_BORDER); 635 setOpaque( false ); 636 add( scroller ); 637 setDoubleBuffered( true ); 638 setFocusable( false ); 639 } 640 641 private void installScrollerListeners() { 642 scrollerMouseWheelListener = getHandler(); 643 if (scrollerMouseWheelListener != null) { 644 scroller.addMouseWheelListener(scrollerMouseWheelListener); 645 } 646 } 647 648 private void uninstallScrollerListeners() { 649 if (scrollerMouseWheelListener != null) { 650 scroller.removeMouseWheelListener(scrollerMouseWheelListener); 651 scrollerMouseWheelListener = null; 652 } 653 } 654 655 /** 656 * This method adds the necessary listeners to the JComboBox. 657 */ 658 protected void installComboBoxListeners() { 659 if ((propertyChangeListener = createPropertyChangeListener()) != null) { 660 comboBox.addPropertyChangeListener(propertyChangeListener); 661 } 662 if ((itemListener = createItemListener()) != null) { 663 comboBox.addItemListener(itemListener); 664 } 665 installComboBoxModelListeners(comboBox.getModel()); 666 } 667 668 /** 669 * Installs the listeners on the combo box model. Any listeners installed 670 * on the combo box model should be removed in 671 * <code>uninstallComboBoxModelListeners</code>. 672 * 673 * @param model The combo box model to install listeners 674 * @see #uninstallComboBoxModelListeners 675 */ 676 protected void installComboBoxModelListeners( ComboBoxModel<?> model ) { 677 if (model != null && (listDataListener = createListDataListener()) != null) { 678 model.addListDataListener(listDataListener); 679 } 680 } 681 682 /** 683 * Registers keyboard actions. 684 */ 685 protected void installKeyboardActions() { 686 687 /* XXX - shouldn't call this method. take it out for testing. 688 ActionListener action = new ActionListener() { 689 public void actionPerformed(ActionEvent e){ 690 } 691 }; 692 693 comboBox.registerKeyboardAction( action, 694 KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ), 695 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ); */ 696 697 } 698 699 // 700 // end Initialization routines 701 //================================================================= 702 703 704 //=================================================================== 705 // begin Event Listenters 706 // 707 708 /** 709 * A listener to be registered upon the combo box 710 * (<em>not</em> its popup menu) 711 * to handle mouse events 712 * that affect the state of the popup menu. 713 * The main purpose of this listener is to make the popup menu 714 * appear and disappear. 715 * This listener also helps 716 * with click-and-drag scenarios by setting the selection if the mouse was 717 * released over the list during a drag. 718 * 719 * <p> 720 * <strong>Warning:</strong> 721 * We recommend that you <em>not</em> 722 * create subclasses of this class. 723 * If you absolutely must create a subclass, 724 * be sure to invoke the superclass 725 * version of each method. 726 * 727 * @see BasicComboPopup#createMouseListener 728 */ 729 protected class InvocationMouseHandler extends MouseAdapter { 730 /** 731 * Responds to mouse-pressed events on the combo box. 732 * 733 * @param e the mouse-press event to be handled 734 */ 735 public void mousePressed( MouseEvent e ) { 736 getHandler().mousePressed(e); 737 } 738 739 /** 740 * Responds to the user terminating 741 * a click or drag that began on the combo box. 742 * 743 * @param e the mouse-release event to be handled 744 */ 745 public void mouseReleased( MouseEvent e ) { 746 getHandler().mouseReleased(e); 747 } 748 } 749 750 /** 751 * This listener watches for dragging and updates the current selection in the 752 * list if it is dragging over the list. 753 */ 754 protected class InvocationMouseMotionHandler extends MouseMotionAdapter { 755 public void mouseDragged( MouseEvent e ) { 756 getHandler().mouseDragged(e); 757 } 758 } 759 760 /** 761 * As of Java 2 platform v 1.4, this class is now obsolete and is only included for 762 * backwards API compatibility. Do not instantiate or subclass. 763 * <p> 764 * All the functionality of this class has been included in 765 * BasicComboBoxUI ActionMap/InputMap methods. 766 */ 767 public class InvocationKeyHandler extends KeyAdapter { 768 public void keyReleased( KeyEvent e ) {} 769 } 770 771 /** 772 * As of Java 2 platform v 1.4, this class is now obsolete, doesn't do anything, and 773 * is only included for backwards API compatibility. Do not call or 774 * override. 775 */ 776 protected class ListSelectionHandler implements ListSelectionListener { 777 public void valueChanged( ListSelectionEvent e ) {} 778 } 779 780 /** 781 * As of 1.4, this class is now obsolete, doesn't do anything, and 782 * is only included for backwards API compatibility. Do not call or 783 * override. 784 * <p> 785 * The functionality has been migrated into <code>ItemHandler</code>. 786 * 787 * @see #createItemListener 788 */ 789 public class ListDataHandler implements ListDataListener { 790 public void contentsChanged( ListDataEvent e ) {} 791 792 public void intervalAdded( ListDataEvent e ) { 793 } 794 795 public void intervalRemoved( ListDataEvent e ) { 796 } 797 } 798 799 /** 800 * This listener hides the popup when the mouse is released in the list. 801 */ 802 protected class ListMouseHandler extends MouseAdapter { 803 public void mousePressed( MouseEvent e ) { 804 } 805 public void mouseReleased(MouseEvent anEvent) { 806 getHandler().mouseReleased(anEvent); 807 } 808 } 809 810 /** 811 * This listener changes the selected item as you move the mouse over the list. 812 * The selection change is not committed to the model, this is for user feedback only. 813 */ 814 protected class ListMouseMotionHandler extends MouseMotionAdapter { 815 public void mouseMoved( MouseEvent anEvent ) { 816 getHandler().mouseMoved(anEvent); 817 } 818 } 819 820 /** 821 * This listener watches for changes to the selection in the 822 * combo box. 823 */ 824 protected class ItemHandler implements ItemListener { 825 public void itemStateChanged( ItemEvent e ) { 826 getHandler().itemStateChanged(e); 827 } 828 } 829 830 /** 831 * This listener watches for bound properties that have changed in the 832 * combo box. 833 * <p> 834 * Subclasses which wish to listen to combo box property changes should 835 * call the superclass methods to ensure that the combo popup correctly 836 * handles property changes. 837 * 838 * @see #createPropertyChangeListener 839 */ 840 protected class PropertyChangeHandler implements PropertyChangeListener { 841 public void propertyChange( PropertyChangeEvent e ) { 842 getHandler().propertyChange(e); 843 } 844 } 845 846 847 private class AutoScrollActionHandler implements ActionListener { 848 private int direction; 849 850 AutoScrollActionHandler(int direction) { 851 this.direction = direction; 852 } 853 854 public void actionPerformed(ActionEvent e) { 855 if (direction == SCROLL_UP) { 856 autoScrollUp(); 857 } 858 else { 859 autoScrollDown(); 860 } 861 } 862 } 863 864 865 private class Handler implements ItemListener, MouseListener, 866 MouseMotionListener, MouseWheelListener, 867 PropertyChangeListener, Serializable { 868 // 869 // MouseListener 870 // NOTE: this is added to both the JList and JComboBox 871 // 872 public void mouseClicked(MouseEvent e) { 873 } 874 875 public void mousePressed(MouseEvent e) { 876 if (e.getSource() == list) { 877 return; 878 } 879 if (!SwingUtilities.isLeftMouseButton(e) || !comboBox.isEnabled()) 880 return; 881 882 if ( comboBox.isEditable() ) { 883 Component comp = comboBox.getEditor().getEditorComponent(); 884 if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) { 885 comp.requestFocus(); 886 } 887 } 888 else if (comboBox.isRequestFocusEnabled()) { 889 comboBox.requestFocus(); 890 } 891 togglePopup(); 892 } 893 894 public void mouseReleased(MouseEvent e) { 895 if (e.getSource() == list) { 896 if (list.getModel().getSize() > 0) { 897 // JList mouse listener 898 if (comboBox.getSelectedIndex() == list.getSelectedIndex()) { 899 comboBox.getEditor().setItem(list.getSelectedValue()); 900 } 901 comboBox.setSelectedIndex(list.getSelectedIndex()); 902 } 903 comboBox.setPopupVisible(false); 904 // workaround for cancelling an edited item (bug 4530953) 905 if (comboBox.isEditable() && comboBox.getEditor() != null) { 906 comboBox.configureEditor(comboBox.getEditor(), 907 comboBox.getSelectedItem()); 908 } 909 return; 910 } 911 // JComboBox mouse listener 912 Component source = (Component)e.getSource(); 913 Dimension size = source.getSize(); 914 Rectangle bounds = new Rectangle( 0, 0, size.width, size.height); 915 if ( !bounds.contains( e.getPoint() ) ) { 916 MouseEvent newEvent = convertMouseEvent( e ); 917 Point location = newEvent.getPoint(); 918 Rectangle r = new Rectangle(); 919 list.computeVisibleRect( r ); 920 if ( r.contains( location ) ) { 921 if (comboBox.getSelectedIndex() == list.getSelectedIndex()) { 922 comboBox.getEditor().setItem(list.getSelectedValue()); 923 } 924 comboBox.setSelectedIndex(list.getSelectedIndex()); 925 } 926 comboBox.setPopupVisible(false); 927 } 928 hasEntered = false; 929 stopAutoScrolling(); 930 } 931 932 public void mouseEntered(MouseEvent e) { 933 } 934 935 public void mouseExited(MouseEvent e) { 936 } 937 938 // 939 // MouseMotionListener: 940 // NOTE: this is added to both the List and ComboBox 941 // 942 public void mouseMoved(MouseEvent anEvent) { 943 if (anEvent.getSource() == list) { 944 Point location = anEvent.getPoint(); 945 Rectangle r = new Rectangle(); 946 list.computeVisibleRect( r ); 947 if ( r.contains( location ) ) { 948 updateListBoxSelectionForEvent( anEvent, false ); 949 } 950 } 951 } 952 953 public void mouseDragged( MouseEvent e ) { 954 if (e.getSource() == list) { 955 return; 956 } 957 if ( isVisible() ) { 958 MouseEvent newEvent = convertMouseEvent( e ); 959 Rectangle r = new Rectangle(); 960 list.computeVisibleRect( r ); 961 962 if ( newEvent.getPoint().y >= r.y && newEvent.getPoint().y <= r.y + r.height - 1 ) { 963 hasEntered = true; 964 if ( isAutoScrolling ) { 965 stopAutoScrolling(); 966 } 967 Point location = newEvent.getPoint(); 968 if ( r.contains( location ) ) { 969 updateListBoxSelectionForEvent( newEvent, false ); 970 } 971 } 972 else { 973 if ( hasEntered ) { 974 int directionToScroll = newEvent.getPoint().y < r.y ? SCROLL_UP : SCROLL_DOWN; 975 if ( isAutoScrolling && scrollDirection != directionToScroll ) { 976 stopAutoScrolling(); 977 startAutoScrolling( directionToScroll ); 978 } 979 else if ( !isAutoScrolling ) { 980 startAutoScrolling( directionToScroll ); 981 } 982 } 983 else { 984 if ( e.getPoint().y < 0 ) { 985 hasEntered = true; 986 startAutoScrolling( SCROLL_UP ); 987 } 988 } 989 } 990 } 991 } 992 993 // 994 // PropertyChangeListener 995 // 996 public void propertyChange(PropertyChangeEvent e) { 997 @SuppressWarnings("unchecked") 998 JComboBox<Object> comboBox = (JComboBox)e.getSource(); 999 String propertyName = e.getPropertyName(); 1000 1001 if ( propertyName == "model" ) { 1002 @SuppressWarnings("unchecked") 1003 ComboBoxModel<Object> oldModel = (ComboBoxModel)e.getOldValue(); 1004 @SuppressWarnings("unchecked") 1005 ComboBoxModel<Object> newModel = (ComboBoxModel)e.getNewValue(); 1006 uninstallComboBoxModelListeners(oldModel); 1007 installComboBoxModelListeners(newModel); 1008 1009 list.setModel(newModel); 1010 1011 if ( isVisible() ) { 1012 hide(); 1013 } 1014 } 1015 else if ( propertyName == "renderer" ) { 1016 list.setCellRenderer( comboBox.getRenderer() ); 1017 if ( isVisible() ) { 1018 hide(); 1019 } 1020 } 1021 else if (propertyName == "componentOrientation") { 1022 // Pass along the new component orientation 1023 // to the list and the scroller 1024 1025 ComponentOrientation o =(ComponentOrientation)e.getNewValue(); 1026 1027 JList<?> list = getList(); 1028 if (list!=null && list.getComponentOrientation()!=o) { 1029 list.setComponentOrientation(o); 1030 } 1031 1032 if (scroller!=null && scroller.getComponentOrientation()!=o) { 1033 scroller.setComponentOrientation(o); 1034 } 1035 1036 if (o!=getComponentOrientation()) { 1037 setComponentOrientation(o); 1038 } 1039 } 1040 else if (propertyName == "lightWeightPopupEnabled") { 1041 setLightWeightPopupEnabled(comboBox.isLightWeightPopupEnabled()); 1042 } 1043 } 1044 1045 // 1046 // ItemListener 1047 // 1048 public void itemStateChanged( ItemEvent e ) { 1049 if (e.getStateChange() == ItemEvent.SELECTED) { 1050 @SuppressWarnings("unchecked") 1051 JComboBox<Object> comboBox = (JComboBox)e.getSource(); 1052 setListSelection(comboBox.getSelectedIndex()); 1053 } else { 1054 setListSelection(-1); 1055 } 1056 } 1057 1058 // 1059 // MouseWheelListener 1060 // 1061 public void mouseWheelMoved(MouseWheelEvent e) { 1062 e.consume(); 1063 } 1064 } 1065 1066 // 1067 // end Event Listeners 1068 //================================================================= 1069 1070 1071 /** 1072 * Overridden to unconditionally return false. 1073 */ 1074 @SuppressWarnings("deprecation") 1075 public boolean isFocusTraversable() { 1076 return false; 1077 } 1078 1079 //=================================================================== 1080 // begin Autoscroll methods 1081 // 1082 1083 /** 1084 * This protected method is implementation specific and should be private. 1085 * do not call or override. 1086 * 1087 * @param direction the direction of scrolling 1088 */ 1089 protected void startAutoScrolling( int direction ) { 1090 // XXX - should be a private method within InvocationMouseMotionHandler 1091 // if possible. 1092 if ( isAutoScrolling ) { 1093 autoscrollTimer.stop(); 1094 } 1095 1096 isAutoScrolling = true; 1097 1098 if ( direction == SCROLL_UP ) { 1099 scrollDirection = SCROLL_UP; 1100 Point convertedPoint = SwingUtilities.convertPoint( scroller, new Point( 1, 1 ), list ); 1101 int top = list.locationToIndex( convertedPoint ); 1102 list.setSelectedIndex( top ); 1103 1104 autoscrollTimer = new Timer( 100, new AutoScrollActionHandler( 1105 SCROLL_UP) ); 1106 } 1107 else if ( direction == SCROLL_DOWN ) { 1108 scrollDirection = SCROLL_DOWN; 1109 Dimension size = scroller.getSize(); 1110 Point convertedPoint = SwingUtilities.convertPoint( scroller, 1111 new Point( 1, (size.height - 1) - 2 ), 1112 list ); 1113 int bottom = list.locationToIndex( convertedPoint ); 1114 list.setSelectedIndex( bottom ); 1115 1116 autoscrollTimer = new Timer(100, new AutoScrollActionHandler( 1117 SCROLL_DOWN)); 1118 } 1119 autoscrollTimer.start(); 1120 } 1121 1122 /** 1123 * This protected method is implementation specific and should be private. 1124 * do not call or override. 1125 */ 1126 protected void stopAutoScrolling() { 1127 isAutoScrolling = false; 1128 1129 if ( autoscrollTimer != null ) { 1130 autoscrollTimer.stop(); 1131 autoscrollTimer = null; 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 autoScrollUp() { 1140 int index = list.getSelectedIndex(); 1141 if ( index > 0 ) { 1142 list.setSelectedIndex( index - 1 ); 1143 list.ensureIndexIsVisible( index - 1 ); 1144 } 1145 } 1146 1147 /** 1148 * This protected method is implementation specific and should be private. 1149 * do not call or override. 1150 */ 1151 protected void autoScrollDown() { 1152 int index = list.getSelectedIndex(); 1153 int lastItem = list.getModel().getSize() - 1; 1154 if ( index < lastItem ) { 1155 list.setSelectedIndex( index + 1 ); 1156 list.ensureIndexIsVisible( index + 1 ); 1157 } 1158 } 1159 1160 // 1161 // end Autoscroll methods 1162 //================================================================= 1163 1164 1165 //=================================================================== 1166 // begin Utility methods 1167 // 1168 1169 /** 1170 * Gets the AccessibleContext associated with this BasicComboPopup. 1171 * The AccessibleContext will have its parent set to the ComboBox. 1172 * 1173 * @return an AccessibleContext for the BasicComboPopup 1174 * @since 1.5 1175 */ 1176 public AccessibleContext getAccessibleContext() { 1177 AccessibleContext context = super.getAccessibleContext(); 1178 context.setAccessibleParent(comboBox); 1179 return context; 1180 } 1181 1182 1183 /** 1184 * This is a utility method that helps event handlers figure out where to 1185 * send the focus when the popup is brought up. The standard implementation 1186 * delegates the focus to the editor (if the combo box is editable) or to 1187 * the JComboBox if it is not editable. 1188 * 1189 * @param e a mouse event 1190 */ 1191 protected void delegateFocus( MouseEvent e ) { 1192 if ( comboBox.isEditable() ) { 1193 Component comp = comboBox.getEditor().getEditorComponent(); 1194 if ((!(comp instanceof JComponent)) || ((JComponent)comp).isRequestFocusEnabled()) { 1195 comp.requestFocus(); 1196 } 1197 } 1198 else if (comboBox.isRequestFocusEnabled()) { 1199 comboBox.requestFocus(); 1200 } 1201 } 1202 1203 /** 1204 * Makes the popup visible if it is hidden and makes it hidden if it is 1205 * visible. 1206 */ 1207 protected void togglePopup() { 1208 if ( isVisible() ) { 1209 hide(); 1210 } 1211 else { 1212 show(); 1213 } 1214 } 1215 1216 /** 1217 * Sets the list selection index to the selectedIndex. This 1218 * method is used to synchronize the list selection with the 1219 * combo box selection. 1220 * 1221 * @param selectedIndex the index to set the list 1222 */ 1223 private void setListSelection(int selectedIndex) { 1224 if ( selectedIndex == -1 ) { 1225 list.clearSelection(); 1226 } 1227 else { 1228 list.setSelectedIndex( selectedIndex ); 1229 list.ensureIndexIsVisible( selectedIndex ); 1230 } 1231 } 1232 1233 /** 1234 * Converts mouse event. 1235 * 1236 * @param e a mouse event 1237 * @return converted mouse event 1238 */ 1239 protected MouseEvent convertMouseEvent( MouseEvent e ) { 1240 Point convertedPoint = SwingUtilities.convertPoint( (Component)e.getSource(), 1241 e.getPoint(), list ); 1242 @SuppressWarnings("deprecation") 1243 MouseEvent newEvent = new MouseEvent( (Component)e.getSource(), 1244 e.getID(), 1245 e.getWhen(), 1246 e.getModifiers(), 1247 convertedPoint.x, 1248 convertedPoint.y, 1249 e.getXOnScreen(), 1250 e.getYOnScreen(), 1251 e.getClickCount(), 1252 e.isPopupTrigger(), 1253 MouseEvent.NOBUTTON ); 1254 return newEvent; 1255 } 1256 1257 1258 /** 1259 * Retrieves the height of the popup based on the current 1260 * ListCellRenderer and the maximum row count. 1261 * 1262 * @param maxRowCount the row count 1263 * @return the height of the popup 1264 */ 1265 protected int getPopupHeightForRowCount(int maxRowCount) { 1266 // Set the cached value of the minimum row count 1267 int minRowCount = Math.min( maxRowCount, comboBox.getItemCount() ); 1268 int height = 0; 1269 ListCellRenderer<Object> renderer = list.getCellRenderer(); 1270 Object value = null; 1271 1272 for ( int i = 0; i < minRowCount; ++i ) { 1273 value = list.getModel().getElementAt( i ); 1274 Component c = renderer.getListCellRendererComponent( list, value, i, false, false ); 1275 height += c.getPreferredSize().height; 1276 } 1277 1278 if (height == 0) { 1279 height = comboBox.getHeight(); 1280 } 1281 1282 Border border = scroller.getViewportBorder(); 1283 if (border != null) { 1284 Insets insets = border.getBorderInsets(null); 1285 height += insets.top + insets.bottom; 1286 } 1287 1288 border = scroller.getBorder(); 1289 if (border != null) { 1290 Insets insets = border.getBorderInsets(null); 1291 height += insets.top + insets.bottom; 1292 } 1293 1294 return height; 1295 } 1296 1297 /** 1298 * Calculate the placement and size of the popup portion of the combo box based 1299 * on the combo box location and the enclosing screen bounds. If 1300 * no transformations are required, then the returned rectangle will 1301 * have the same values as the parameters. 1302 * 1303 * @param px starting x location 1304 * @param py starting y location 1305 * @param pw starting width 1306 * @param ph starting height 1307 * @return a rectangle which represents the placement and size of the popup 1308 */ 1309 protected Rectangle computePopupBounds(int px,int py,int pw,int ph) { 1310 Toolkit toolkit = Toolkit.getDefaultToolkit(); 1311 Rectangle screenBounds; 1312 1313 // Calculate the desktop dimensions relative to the combo box. 1314 GraphicsConfiguration gc = comboBox.getGraphicsConfiguration(); 1315 Point p = new Point(); 1316 SwingUtilities.convertPointFromScreen(p, comboBox); 1317 if (gc != null) { 1318 Insets screenInsets = toolkit.getScreenInsets(gc); 1319 screenBounds = gc.getBounds(); 1320 screenBounds.width -= (screenInsets.left + screenInsets.right); 1321 screenBounds.height -= (screenInsets.top + screenInsets.bottom); 1322 screenBounds.x += (p.x + screenInsets.left); 1323 screenBounds.y += (p.y + screenInsets.top); 1324 } 1325 else { 1326 screenBounds = new Rectangle(p, toolkit.getScreenSize()); 1327 } 1328 int borderHeight = 0; 1329 Border popupBorder = getBorder(); 1330 if (popupBorder != null) { 1331 Insets borderInsets = popupBorder.getBorderInsets(this); 1332 borderHeight = borderInsets.top + borderInsets.bottom; 1333 screenBounds.width -= (borderInsets.left + borderInsets.right); 1334 screenBounds.height -= borderHeight; 1335 } 1336 Rectangle rect = new Rectangle(px, py, pw, ph); 1337 if (py + ph > screenBounds.y + screenBounds.height) { 1338 if (ph <= -screenBounds.y - borderHeight) { 1339 // popup goes above 1340 rect.y = -ph - borderHeight; 1341 } else { 1342 // a full screen height popup 1343 rect.y = screenBounds.y + Math.max(0, (screenBounds.height - ph) / 2 ); 1344 rect.height = Math.min(screenBounds.height, ph); 1345 } 1346 } 1347 return rect; 1348 } 1349 1350 /** 1351 * Calculates the upper left location of the Popup. 1352 */ 1353 private Point getPopupLocation() { 1354 Dimension popupSize = comboBox.getSize(); 1355 Insets insets = getInsets(); 1356 1357 // reduce the width of the scrollpane by the insets so that the popup 1358 // is the same width as the combo box. 1359 popupSize.setSize(popupSize.width - (insets.right + insets.left), 1360 getPopupHeightForRowCount( comboBox.getMaximumRowCount())); 1361 Rectangle popupBounds = computePopupBounds( 0, comboBox.getBounds().height, 1362 popupSize.width, popupSize.height); 1363 Dimension scrollSize = popupBounds.getSize(); 1364 Point popupLocation = popupBounds.getLocation(); 1365 1366 scroller.setMaximumSize( scrollSize ); 1367 scroller.setPreferredSize( scrollSize ); 1368 scroller.setMinimumSize( scrollSize ); 1369 1370 list.revalidate(); 1371 1372 return popupLocation; 1373 } 1374 1375 /** 1376 * A utility method used by the event listeners. Given a mouse event, it changes 1377 * the list selection to the list item below the mouse. 1378 * 1379 * @param anEvent a mouse event 1380 * @param shouldScroll if {@code true} list should be scrolled. 1381 */ 1382 protected void updateListBoxSelectionForEvent(MouseEvent anEvent,boolean shouldScroll) { 1383 // XXX - only seems to be called from this class. shouldScroll flag is 1384 // never true 1385 Point location = anEvent.getPoint(); 1386 if ( list == null ) 1387 return; 1388 int index = list.locationToIndex(location); 1389 if ( index == -1 ) { 1390 if ( location.y < 0 ) 1391 index = 0; 1392 else 1393 index = comboBox.getModel().getSize() - 1; 1394 } 1395 if ( list.getSelectedIndex() != index ) { 1396 list.setSelectedIndex(index); 1397 if ( shouldScroll ) 1398 list.ensureIndexIsVisible(index); 1399 } 1400 } 1401 1402 // 1403 // end Utility methods 1404 //================================================================= 1405 }