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