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