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