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