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