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