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