1 /* 2 * Copyright (c) 1997, 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 java.awt.*; 29 import java.awt.event.*; 30 import javax.swing.*; 31 import javax.accessibility.*; 32 import javax.swing.plaf.*; 33 import javax.swing.text.*; 34 import javax.swing.event.*; 35 import java.beans.PropertyChangeListener; 36 import java.beans.PropertyChangeEvent; 37 import sun.awt.AppContext; 38 import sun.swing.DefaultLookup; 39 import sun.swing.UIAction; 40 41 /** 42 * Basic UI implementation for JComboBox. 43 * <p> 44 * The combo box is a compound component which means that it is an aggregate of 45 * many simpler components. This class creates and manages the listeners 46 * on the combo box and the combo box model. These listeners update the user 47 * interface in response to changes in the properties and state of the combo box. 48 * <p> 49 * All event handling is handled by listener classes created with the 50 * {@code createxxxListener()} methods and internal classes. 51 * You can change the behavior of this class by overriding the 52 * {@code createxxxListener()} methods and supplying your own 53 * event listeners or subclassing from the ones supplied in this class. 54 * <p> 55 * For adding specific actions, 56 * overide {@code installKeyboardActions} to add actions in response to 57 * KeyStroke bindings. See the article <a href="http://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html">How to Use Key Bindings</a> 58 * 59 * @author Arnaud Weber 60 * @author Tom Santos 61 * @author Mark Davidson 62 */ 63 public class BasicComboBoxUI extends ComboBoxUI { 64 65 /** 66 * The instance of {@code JComboBox}. 67 */ 68 protected JComboBox<Object> comboBox; 69 /** 70 * This protected field is implementation specific. Do not access directly 71 * or override. 72 */ 73 protected boolean hasFocus = false; 74 75 // Control the selection behavior of the JComboBox when it is used 76 // in the JTable DefaultCellEditor. 77 private boolean isTableCellEditor = false; 78 private static final String IS_TABLE_CELL_EDITOR = "JComboBox.isTableCellEditor"; 79 80 /** 81 * This list is for drawing the current item in the combo box. 82 */ 83 protected JList<Object> listBox; 84 85 /** 86 * Used to render the currently selected item in the combo box. 87 * It doesn't have anything to do with the popup's rendering. 88 */ 89 protected CellRendererPane currentValuePane = new CellRendererPane(); 90 91 /** 92 * The implementation of {@code ComboPopup} that is used to show the popup. 93 */ 94 protected ComboPopup popup; 95 96 /** 97 * The Component that the @{code ComboBoxEditor} uses for editing. 98 */ 99 protected Component editor; 100 101 /** 102 * The arrow button that invokes the popup. 103 */ 104 protected JButton arrowButton; 105 106 // Listeners that are attached to the JComboBox 107 /** 108 * This protected field is implementation specific. Do not access directly 109 * or override. Override the listener construction method instead. 110 * 111 * @see #createKeyListener 112 */ 113 protected KeyListener keyListener; 114 /** 115 * This protected field is implementation specific. Do not access directly 116 * or override. Override the listener construction method instead. 117 * 118 * @see #createFocusListener 119 */ 120 protected FocusListener focusListener; 121 /** 122 * This protected field is implementation specific. Do not access directly 123 * or override. Override the listener construction method instead. 124 * 125 * @see #createPropertyChangeListener 126 */ 127 protected PropertyChangeListener propertyChangeListener; 128 129 /** 130 * This protected field is implementation specific. Do not access directly 131 * or override. Override the listener construction method instead. 132 * 133 * @see #createItemListener 134 */ 135 protected ItemListener itemListener; 136 137 // Listeners that the ComboPopup produces. 138 /** 139 * The {@code MouseListener} listens to events. 140 */ 141 protected MouseListener popupMouseListener; 142 143 /** 144 * The {@code MouseMotionListener} listens to events. 145 */ 146 protected MouseMotionListener popupMouseMotionListener; 147 148 /** 149 * The {@code KeyListener} listens to events. 150 */ 151 protected KeyListener popupKeyListener; 152 153 private MouseWheelListener mouseWheelListener; 154 155 // This is used for knowing when to cache the minimum preferred size. 156 // If the data in the list changes, the cached value get marked for recalc. 157 // Added to the current JComboBox model 158 /** 159 * This protected field is implementation specific. Do not access directly 160 * or override. Override the listener construction method instead. 161 * 162 * @see #createListDataListener 163 */ 164 protected ListDataListener listDataListener; 165 166 /** 167 * Implements all the Listeners needed by this class, all existing 168 * listeners redirect to it. 169 */ 170 private Handler handler; 171 172 /** 173 * The time factor to treate the series of typed alphanumeric key 174 * as prefix for first letter navigation. 175 */ 176 private long timeFactor = 1000L; 177 178 /** 179 * This is tricky, this variables is needed for DefaultKeySelectionManager 180 * to take into account time factor. 181 */ 182 private long lastTime = 0L; 183 private long time = 0L; 184 185 /** 186 * The default key selection manager 187 */ 188 JComboBox.KeySelectionManager keySelectionManager; 189 190 /** 191 * The flag for recalculating the minimum preferred size. 192 */ 193 protected boolean isMinimumSizeDirty = true; 194 195 /** 196 * The cached minimum preferred size. 197 */ 198 protected Dimension cachedMinimumSize = new Dimension( 0, 0 ); 199 200 // Flag for calculating the display size 201 private boolean isDisplaySizeDirty = true; 202 203 // Cached the size that the display needs to render the largest item 204 private Dimension cachedDisplaySize = new Dimension( 0, 0 ); 205 206 // Key used for lookup of the DefaultListCellRenderer in the AppContext. 207 private static final Object COMBO_UI_LIST_CELL_RENDERER_KEY = 208 new StringBuffer("DefaultListCellRendererKey"); 209 210 static final StringBuffer HIDE_POPUP_KEY 211 = new StringBuffer("HidePopupKey"); 212 213 /** 214 * Whether or not all cells have the same baseline. 215 */ 216 private boolean sameBaseline; 217 218 /** 219 * Indicates whether or not the combo box button should be square. 220 * If square, then the width and height are equal, and are both set to 221 * the height of the combo minus appropriate insets. 222 * 223 * @since 1.7 224 */ 225 protected boolean squareButton = true; 226 227 /** 228 * If specified, these insets act as padding around the cell renderer when 229 * laying out and painting the "selected" item in the combo box. These 230 * insets add to those specified by the cell renderer. 231 * 232 * @since 1.7 233 */ 234 protected Insets padding; 235 236 // Used for calculating the default size. 237 private static ListCellRenderer<Object> getDefaultListCellRenderer() { 238 @SuppressWarnings("unchecked") 239 ListCellRenderer<Object> renderer = (ListCellRenderer)AppContext. 240 getAppContext().get(COMBO_UI_LIST_CELL_RENDERER_KEY); 241 242 if (renderer == null) { 243 renderer = new DefaultListCellRenderer(); 244 AppContext.getAppContext().put(COMBO_UI_LIST_CELL_RENDERER_KEY, 245 new DefaultListCellRenderer()); 246 } 247 return renderer; 248 } 249 250 /** 251 * Populates ComboBox's actions. 252 */ 253 static void loadActionMap(LazyActionMap map) { 254 map.put(new Actions(Actions.HIDE)); 255 map.put(new Actions(Actions.PAGE_DOWN)); 256 map.put(new Actions(Actions.PAGE_UP)); 257 map.put(new Actions(Actions.HOME)); 258 map.put(new Actions(Actions.END)); 259 map.put(new Actions(Actions.DOWN)); 260 map.put(new Actions(Actions.DOWN_2)); 261 map.put(new Actions(Actions.TOGGLE)); 262 map.put(new Actions(Actions.TOGGLE_2)); 263 map.put(new Actions(Actions.UP)); 264 map.put(new Actions(Actions.UP_2)); 265 map.put(new Actions(Actions.ENTER)); 266 } 267 268 //======================== 269 // begin UI Initialization 270 // 271 272 /** 273 * Constructs a new instance of {@code BasicComboBoxUI}. 274 * 275 * @param c a component 276 * @return a new instance of {@code BasicComboBoxUI} 277 */ 278 public static ComponentUI createUI(JComponent c) { 279 return new BasicComboBoxUI(); 280 } 281 282 @Override 283 public void installUI( JComponent c ) { 284 isMinimumSizeDirty = true; 285 286 @SuppressWarnings("unchecked") 287 JComboBox<Object> tmp = (JComboBox)c; 288 comboBox = tmp; 289 installDefaults(); 290 popup = createPopup(); 291 listBox = popup.getList(); 292 293 // Is this combo box a cell editor? 294 Boolean inTable = (Boolean)c.getClientProperty(IS_TABLE_CELL_EDITOR ); 295 if (inTable != null) { 296 isTableCellEditor = inTable.equals(Boolean.TRUE) ? true : false; 297 } 298 299 if ( comboBox.getRenderer() == null || comboBox.getRenderer() instanceof UIResource ) { 300 comboBox.setRenderer( createRenderer() ); 301 } 302 303 if ( comboBox.getEditor() == null || comboBox.getEditor() instanceof UIResource ) { 304 comboBox.setEditor( createEditor() ); 305 } 306 307 installListeners(); 308 installComponents(); 309 310 comboBox.setLayout( createLayoutManager() ); 311 312 comboBox.setRequestFocusEnabled( true ); 313 314 installKeyboardActions(); 315 316 comboBox.putClientProperty("doNotCancelPopup", HIDE_POPUP_KEY); 317 318 if (keySelectionManager == null || keySelectionManager instanceof UIResource) { 319 keySelectionManager = new DefaultKeySelectionManager(); 320 } 321 comboBox.setKeySelectionManager(keySelectionManager); 322 } 323 324 @Override 325 public void uninstallUI( JComponent c ) { 326 setPopupVisible( comboBox, false); 327 popup.uninstallingUI(); 328 329 uninstallKeyboardActions(); 330 331 comboBox.setLayout( null ); 332 333 uninstallComponents(); 334 uninstallListeners(); 335 uninstallDefaults(); 336 337 if ( comboBox.getRenderer() == null || comboBox.getRenderer() instanceof UIResource ) { 338 comboBox.setRenderer( null ); 339 } 340 341 ComboBoxEditor comboBoxEditor = comboBox.getEditor(); 342 if (comboBoxEditor instanceof UIResource ) { 343 if (comboBoxEditor.getEditorComponent().hasFocus()) { 344 // Leave focus in JComboBox. 345 comboBox.requestFocusInWindow(); 346 } 347 comboBox.setEditor( null ); 348 } 349 350 if (keySelectionManager instanceof UIResource) { 351 comboBox.setKeySelectionManager(null); 352 } 353 354 handler = null; 355 keyListener = null; 356 focusListener = null; 357 listDataListener = null; 358 propertyChangeListener = null; 359 popup = null; 360 listBox = null; 361 comboBox = null; 362 } 363 364 /** 365 * Installs the default colors, default font, default renderer, and default 366 * editor into the JComboBox. 367 */ 368 protected void installDefaults() { 369 LookAndFeel.installColorsAndFont( comboBox, 370 "ComboBox.background", 371 "ComboBox.foreground", 372 "ComboBox.font" ); 373 LookAndFeel.installBorder( comboBox, "ComboBox.border" ); 374 LookAndFeel.installProperty( comboBox, "opaque", Boolean.TRUE); 375 376 Long l = (Long)UIManager.get("ComboBox.timeFactor"); 377 timeFactor = l == null ? 1000L : l.longValue(); 378 379 //NOTE: this needs to default to true if not specified 380 Boolean b = (Boolean)UIManager.get("ComboBox.squareButton"); 381 squareButton = b == null ? true : b; 382 383 padding = UIManager.getInsets("ComboBox.padding"); 384 } 385 386 /** 387 * Creates and installs listeners for the combo box and its model. 388 * This method is called when the UI is installed. 389 */ 390 protected void installListeners() { 391 if ( (itemListener = createItemListener()) != null) { 392 comboBox.addItemListener( itemListener ); 393 } 394 if ( (propertyChangeListener = createPropertyChangeListener()) != null ) { 395 comboBox.addPropertyChangeListener( propertyChangeListener ); 396 } 397 if ( (keyListener = createKeyListener()) != null ) { 398 comboBox.addKeyListener( keyListener ); 399 } 400 if ( (focusListener = createFocusListener()) != null ) { 401 comboBox.addFocusListener( focusListener ); 402 } 403 if ((popupMouseListener = popup.getMouseListener()) != null) { 404 comboBox.addMouseListener( popupMouseListener ); 405 } 406 if ((popupMouseMotionListener = popup.getMouseMotionListener()) != null) { 407 comboBox.addMouseMotionListener( popupMouseMotionListener ); 408 } 409 if ((popupKeyListener = popup.getKeyListener()) != null) { 410 comboBox.addKeyListener(popupKeyListener); 411 } 412 413 if ( comboBox.getModel() != null ) { 414 if ( (listDataListener = createListDataListener()) != null ) { 415 comboBox.getModel().addListDataListener( listDataListener ); 416 } 417 } 418 419 if ((mouseWheelListener = createMouseWheelListener()) != null) { 420 comboBox.addMouseWheelListener(mouseWheelListener); 421 } 422 } 423 424 /** 425 * Uninstalls the default colors, default font, default renderer, 426 * and default editor from the combo box. 427 */ 428 protected void uninstallDefaults() { 429 LookAndFeel.installColorsAndFont( comboBox, 430 "ComboBox.background", 431 "ComboBox.foreground", 432 "ComboBox.font" ); 433 LookAndFeel.uninstallBorder( comboBox ); 434 } 435 436 /** 437 * Removes the installed listeners from the combo box and its model. 438 * The number and types of listeners removed and in this method should be 439 * the same that was added in {@code installListeners} 440 */ 441 protected void uninstallListeners() { 442 if ( keyListener != null ) { 443 comboBox.removeKeyListener( keyListener ); 444 } 445 if ( itemListener != null) { 446 comboBox.removeItemListener( itemListener ); 447 } 448 if ( propertyChangeListener != null ) { 449 comboBox.removePropertyChangeListener( propertyChangeListener ); 450 } 451 if ( focusListener != null) { 452 comboBox.removeFocusListener( focusListener ); 453 } 454 if ( popupMouseListener != null) { 455 comboBox.removeMouseListener( popupMouseListener ); 456 } 457 if ( popupMouseMotionListener != null) { 458 comboBox.removeMouseMotionListener( popupMouseMotionListener ); 459 } 460 if (popupKeyListener != null) { 461 comboBox.removeKeyListener(popupKeyListener); 462 } 463 if ( comboBox.getModel() != null ) { 464 if ( listDataListener != null ) { 465 comboBox.getModel().removeListDataListener( listDataListener ); 466 } 467 } 468 if (mouseWheelListener != null) { 469 comboBox.removeMouseWheelListener(mouseWheelListener); 470 } 471 } 472 473 /** 474 * Creates the popup portion of the combo box. 475 * 476 * @return an instance of {@code ComboPopup} 477 * @see ComboPopup 478 */ 479 protected ComboPopup createPopup() { 480 return new BasicComboPopup( comboBox ); 481 } 482 483 /** 484 * Creates a {@code KeyListener} which will be added to the 485 * combo box. If this method returns null then it will not be added 486 * to the combo box. 487 * 488 * @return an instance {@code KeyListener} or null 489 */ 490 protected KeyListener createKeyListener() { 491 return getHandler(); 492 } 493 494 /** 495 * Creates a {@code FocusListener} which will be added to the combo box. 496 * If this method returns null then it will not be added to the combo box. 497 * 498 * @return an instance of a {@code FocusListener} or null 499 */ 500 protected FocusListener createFocusListener() { 501 return getHandler(); 502 } 503 504 /** 505 * Creates a list data listener which will be added to the 506 * {@code ComboBoxModel}. If this method returns null then 507 * it will not be added to the combo box model. 508 * 509 * @return an instance of a {@code ListDataListener} or null 510 */ 511 protected ListDataListener createListDataListener() { 512 return getHandler(); 513 } 514 515 /** 516 * Creates an {@code ItemListener} which will be added to the 517 * combo box. If this method returns null then it will not 518 * be added to the combo box. 519 * <p> 520 * Subclasses may override this method to return instances of their own 521 * ItemEvent handlers. 522 * 523 * @return an instance of an {@code ItemListener} or null 524 */ 525 protected ItemListener createItemListener() { 526 return null; 527 } 528 529 /** 530 * Creates a {@code PropertyChangeListener} which will be added to 531 * the combo box. If this method returns null then it will not 532 * be added to the combo box. 533 * 534 * @return an instance of a {@code PropertyChangeListener} or null 535 */ 536 protected PropertyChangeListener createPropertyChangeListener() { 537 return getHandler(); 538 } 539 540 /** 541 * Creates a layout manager for managing the components which make up the 542 * combo box. 543 * 544 * @return an instance of a layout manager 545 */ 546 protected LayoutManager createLayoutManager() { 547 return getHandler(); 548 } 549 550 /** 551 * Creates the default renderer that will be used in a non-editiable combo 552 * box. A default renderer will used only if a renderer has not been 553 * explicitly set with {@code setRenderer}. 554 * 555 * @return a {@code ListCellRender} used for the combo box 556 * @see javax.swing.JComboBox#setRenderer 557 */ 558 protected ListCellRenderer<Object> createRenderer() { 559 return new BasicComboBoxRenderer.UIResource(); 560 } 561 562 /** 563 * Creates the default editor that will be used in editable combo boxes. 564 * A default editor will be used only if an editor has not been 565 * explicitly set with {@code setEditor}. 566 * 567 * @return a {@code ComboBoxEditor} used for the combo box 568 * @see javax.swing.JComboBox#setEditor 569 */ 570 protected ComboBoxEditor createEditor() { 571 return new BasicComboBoxEditor.UIResource(); 572 } 573 574 /** 575 * Returns the shared listener. 576 */ 577 private Handler getHandler() { 578 if (handler == null) { 579 handler = new Handler(); 580 } 581 return handler; 582 } 583 584 private MouseWheelListener createMouseWheelListener() { 585 return getHandler(); 586 } 587 588 // 589 // end UI Initialization 590 //====================== 591 592 593 //====================== 594 // begin Inner classes 595 // 596 597 /** 598 * This listener checks to see if the key event isn't a navigation key. If 599 * it finds a key event that wasn't a navigation key it dispatches it to 600 * JComboBox.selectWithKeyChar() so that it can do type-ahead. 601 * 602 * This public inner class should be treated as protected. 603 * Instantiate it only within subclasses of 604 * {@code BasicComboBoxUI}. 605 */ 606 public class KeyHandler extends KeyAdapter { 607 @Override 608 public void keyPressed( KeyEvent e ) { 609 getHandler().keyPressed(e); 610 } 611 } 612 613 /** 614 * This listener hides the popup when the focus is lost. It also repaints 615 * when focus is gained or lost. 616 * 617 * This public inner class should be treated as protected. 618 * Instantiate it only within subclasses of 619 * {@code BasicComboBoxUI}. 620 */ 621 public class FocusHandler implements FocusListener { 622 public void focusGained( FocusEvent e ) { 623 getHandler().focusGained(e); 624 } 625 626 public void focusLost( FocusEvent e ) { 627 getHandler().focusLost(e); 628 } 629 } 630 631 /** 632 * This listener watches for changes in the 633 * {@code ComboBoxModel}. 634 * <p> 635 * This public inner class should be treated as protected. 636 * Instantiate it only within subclasses of 637 * {@code BasicComboBoxUI}. 638 * 639 * @see #createListDataListener 640 */ 641 public class ListDataHandler implements ListDataListener { 642 public void contentsChanged( ListDataEvent e ) { 643 getHandler().contentsChanged(e); 644 } 645 646 public void intervalAdded( ListDataEvent e ) { 647 getHandler().intervalAdded(e); 648 } 649 650 public void intervalRemoved( ListDataEvent e ) { 651 getHandler().intervalRemoved(e); 652 } 653 } 654 655 /** 656 * This listener watches for changes to the selection in the 657 * combo box. 658 * <p> 659 * This public inner class should be treated as protected. 660 * Instantiate it only within subclasses of 661 * {@code BasicComboBoxUI}. 662 * 663 * @see #createItemListener 664 */ 665 public class ItemHandler implements ItemListener { 666 // This class used to implement behavior which is now redundant. 667 public void itemStateChanged(ItemEvent e) {} 668 } 669 670 /** 671 * This listener watches for bound properties that have changed in the 672 * combo box. 673 * <p> 674 * Subclasses which wish to listen to combo box property changes should 675 * call the superclass methods to ensure that the combo box ui correctly 676 * handles property changes. 677 * <p> 678 * This public inner class should be treated as protected. 679 * Instantiate it only within subclasses of 680 * {@code BasicComboBoxUI}. 681 * 682 * @see #createPropertyChangeListener 683 */ 684 public class PropertyChangeHandler implements PropertyChangeListener { 685 public void propertyChange(PropertyChangeEvent e) { 686 getHandler().propertyChange(e); 687 } 688 } 689 690 691 // Syncronizes the ToolTip text for the components within the combo box to be the 692 // same value as the combo box ToolTip text. 693 private void updateToolTipTextForChildren() { 694 Component[] children = comboBox.getComponents(); 695 for ( int i = 0; i < children.length; ++i ) { 696 if ( children[i] instanceof JComponent ) { 697 ((JComponent)children[i]).setToolTipText( comboBox.getToolTipText() ); 698 } 699 } 700 } 701 702 /** 703 * This layout manager handles the 'standard' layout of combo boxes. It puts 704 * the arrow button to the right and the editor to the left. If there is no 705 * editor it still keeps the arrow button to the right. 706 * 707 * This public inner class should be treated as protected. 708 * Instantiate it only within subclasses of 709 * {@code BasicComboBoxUI}. 710 */ 711 public class ComboBoxLayoutManager implements LayoutManager { 712 public void addLayoutComponent(String name, Component comp) {} 713 714 public void removeLayoutComponent(Component comp) {} 715 716 public Dimension preferredLayoutSize(Container parent) { 717 return getHandler().preferredLayoutSize(parent); 718 } 719 720 public Dimension minimumLayoutSize(Container parent) { 721 return getHandler().minimumLayoutSize(parent); 722 } 723 724 public void layoutContainer(Container parent) { 725 getHandler().layoutContainer(parent); 726 } 727 } 728 729 // 730 // end Inner classes 731 //==================== 732 733 734 //=============================== 735 // begin Sub-Component Management 736 // 737 738 /** 739 * Creates and initializes the components which make up the 740 * aggregate combo box. This method is called as part of the UI 741 * installation process. 742 */ 743 protected void installComponents() { 744 arrowButton = createArrowButton(); 745 746 if (arrowButton != null) { 747 comboBox.add(arrowButton); 748 configureArrowButton(); 749 } 750 751 if ( comboBox.isEditable() ) { 752 addEditor(); 753 } 754 755 comboBox.add( currentValuePane ); 756 } 757 758 /** 759 * The aggregate components which comprise the combo box are 760 * unregistered and uninitialized. This method is called as part of the 761 * UI uninstallation process. 762 */ 763 protected void uninstallComponents() { 764 if ( arrowButton != null ) { 765 unconfigureArrowButton(); 766 } 767 if ( editor != null ) { 768 unconfigureEditor(); 769 } 770 comboBox.removeAll(); // Just to be safe. 771 arrowButton = null; 772 } 773 774 /** 775 * This public method is implementation specific and should be private. 776 * do not call or override. To implement a specific editor create a 777 * custom {@code ComboBoxEditor} 778 * 779 * @see #createEditor 780 * @see javax.swing.JComboBox#setEditor 781 * @see javax.swing.ComboBoxEditor 782 */ 783 public void addEditor() { 784 removeEditor(); 785 editor = comboBox.getEditor().getEditorComponent(); 786 if ( editor != null ) { 787 configureEditor(); 788 comboBox.add(editor); 789 if(comboBox.isFocusOwner()) { 790 // Switch focus to the editor component 791 editor.requestFocusInWindow(); 792 } 793 } 794 } 795 796 /** 797 * This public method is implementation specific and should be private. 798 * do not call or override. 799 * 800 * @see #addEditor 801 */ 802 public void removeEditor() { 803 if ( editor != null ) { 804 unconfigureEditor(); 805 comboBox.remove( editor ); 806 editor = null; 807 } 808 } 809 810 /** 811 * This protected method is implementation specific and should be private. 812 * do not call or override. 813 * 814 * @see #addEditor 815 */ 816 protected void configureEditor() { 817 // Should be in the same state as the combobox 818 editor.setEnabled(comboBox.isEnabled()); 819 820 editor.setFocusable(comboBox.isFocusable()); 821 822 editor.setFont( comboBox.getFont() ); 823 824 if (focusListener != null) { 825 editor.addFocusListener(focusListener); 826 } 827 828 editor.addFocusListener( getHandler() ); 829 830 comboBox.getEditor().addActionListener(getHandler()); 831 832 if(editor instanceof JComponent) { 833 ((JComponent)editor).putClientProperty("doNotCancelPopup", 834 HIDE_POPUP_KEY); 835 ((JComponent)editor).setInheritsPopupMenu(true); 836 } 837 838 comboBox.configureEditor(comboBox.getEditor(),comboBox.getSelectedItem()); 839 840 editor.addPropertyChangeListener(propertyChangeListener); 841 } 842 843 /** 844 * This protected method is implementation specific and should be private. 845 * Do not call or override. 846 * 847 * @see #addEditor 848 */ 849 protected void unconfigureEditor() { 850 if (focusListener != null) { 851 editor.removeFocusListener(focusListener); 852 } 853 854 editor.removePropertyChangeListener(propertyChangeListener); 855 editor.removeFocusListener(getHandler()); 856 comboBox.getEditor().removeActionListener(getHandler()); 857 } 858 859 /** 860 * This public method is implementation specific and should be private. Do 861 * not call or override. 862 * 863 * @see #createArrowButton 864 */ 865 public void configureArrowButton() { 866 if ( arrowButton != null ) { 867 arrowButton.setEnabled( comboBox.isEnabled() ); 868 arrowButton.setFocusable(comboBox.isFocusable()); 869 arrowButton.setRequestFocusEnabled(false); 870 arrowButton.addMouseListener( popup.getMouseListener() ); 871 arrowButton.addMouseMotionListener( popup.getMouseMotionListener() ); 872 arrowButton.resetKeyboardActions(); 873 arrowButton.putClientProperty("doNotCancelPopup", HIDE_POPUP_KEY); 874 arrowButton.setInheritsPopupMenu(true); 875 } 876 } 877 878 /** 879 * This public method is implementation specific and should be private. Do 880 * not call or override. 881 * 882 * @see #createArrowButton 883 */ 884 public void unconfigureArrowButton() { 885 if ( arrowButton != null ) { 886 arrowButton.removeMouseListener( popup.getMouseListener() ); 887 arrowButton.removeMouseMotionListener( popup.getMouseMotionListener() ); 888 } 889 } 890 891 /** 892 * Creates a button which will be used as the control to show or hide 893 * the popup portion of the combo box. 894 * 895 * @return a button which represents the popup control 896 */ 897 protected JButton createArrowButton() { 898 JButton button = new BasicArrowButton(BasicArrowButton.SOUTH, 899 UIManager.getColor("ComboBox.buttonBackground"), 900 UIManager.getColor("ComboBox.buttonShadow"), 901 UIManager.getColor("ComboBox.buttonDarkShadow"), 902 UIManager.getColor("ComboBox.buttonHighlight")); 903 button.setName("ComboBox.arrowButton"); 904 return button; 905 } 906 907 // 908 // end Sub-Component Management 909 //=============================== 910 911 912 //================================ 913 // begin ComboBoxUI Implementation 914 // 915 916 /** 917 * Tells if the popup is visible or not. 918 */ 919 public boolean isPopupVisible( JComboBox<?> c ) { 920 return popup.isVisible(); 921 } 922 923 /** 924 * Hides the popup. 925 */ 926 public void setPopupVisible( JComboBox<?> c, boolean v ) { 927 if ( v ) { 928 popup.show(); 929 } else { 930 popup.hide(); 931 } 932 } 933 934 /** 935 * Determines if the JComboBox is focus traversable. If the JComboBox is editable 936 * this returns false, otherwise it returns true. 937 */ 938 public boolean isFocusTraversable( JComboBox<?> c ) { 939 return !comboBox.isEditable(); 940 } 941 942 // 943 // end ComboBoxUI Implementation 944 //============================== 945 946 947 //================================= 948 // begin ComponentUI Implementation 949 @Override 950 public void paint( Graphics g, JComponent c ) { 951 hasFocus = comboBox.hasFocus(); 952 if ( !comboBox.isEditable() ) { 953 Rectangle r = rectangleForCurrentValue(); 954 paintCurrentValueBackground(g,r,hasFocus); 955 paintCurrentValue(g,r,hasFocus); 956 } 957 } 958 959 @Override 960 public Dimension getPreferredSize( JComponent c ) { 961 return getMinimumSize(c); 962 } 963 964 /** 965 * The minimum size is the size of the display area plus insets plus the button. 966 */ 967 @Override 968 public Dimension getMinimumSize( JComponent c ) { 969 if ( !isMinimumSizeDirty ) { 970 return new Dimension(cachedMinimumSize); 971 } 972 Dimension size = getDisplaySize(); 973 Insets insets = getInsets(); 974 //calculate the width and height of the button 975 int buttonHeight = size.height; 976 int buttonWidth = squareButton ? buttonHeight : arrowButton.getPreferredSize().width; 977 //adjust the size based on the button width 978 size.height += insets.top + insets.bottom; 979 size.width += insets.left + insets.right + buttonWidth; 980 981 cachedMinimumSize.setSize( size.width, size.height ); 982 isMinimumSizeDirty = false; 983 984 return new Dimension(size); 985 } 986 987 @Override 988 public Dimension getMaximumSize( JComponent c ) { 989 return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE); 990 } 991 992 /** 993 * Returns the baseline. 994 * 995 * @throws NullPointerException {@inheritDoc} 996 * @throws IllegalArgumentException {@inheritDoc} 997 * @see javax.swing.JComponent#getBaseline(int, int) 998 * @since 1.6 999 */ 1000 @Override 1001 public int getBaseline(JComponent c, int width, int height) { 1002 super.getBaseline(c, width, height); 1003 int baseline = -1; 1004 // force sameBaseline to be updated. 1005 getDisplaySize(); 1006 if (sameBaseline) { 1007 Insets insets = c.getInsets(); 1008 height = Math.max(height - insets.top - insets.bottom, 0); 1009 if (!comboBox.isEditable()) { 1010 ListCellRenderer<Object> renderer = comboBox.getRenderer(); 1011 if (renderer == null) { 1012 renderer = new DefaultListCellRenderer(); 1013 } 1014 Object value = null; 1015 Object prototypeValue = comboBox.getPrototypeDisplayValue(); 1016 if (prototypeValue != null) { 1017 value = prototypeValue; 1018 } 1019 else if (comboBox.getModel().getSize() > 0) { 1020 // Note, we're assuming the baseline is the same for all 1021 // cells, if not, this needs to loop through all. 1022 value = comboBox.getModel().getElementAt(0); 1023 } 1024 Component component = renderer. 1025 getListCellRendererComponent(listBox, value, -1, 1026 false, false); 1027 if (component instanceof JLabel) { 1028 JLabel label = (JLabel) component; 1029 String text = label.getText(); 1030 if ((text == null) || text.isEmpty()) { 1031 label.setText(" "); 1032 } 1033 } 1034 if (component instanceof JComponent) { 1035 component.setFont(comboBox.getFont()); 1036 } 1037 baseline = component.getBaseline(width, height); 1038 } 1039 else { 1040 baseline = editor.getBaseline(width, height); 1041 } 1042 if (baseline > 0) { 1043 baseline += insets.top; 1044 } 1045 } 1046 return baseline; 1047 } 1048 1049 /** 1050 * Returns an enum indicating how the baseline of the component 1051 * changes as the size changes. 1052 * 1053 * @throws NullPointerException {@inheritDoc} 1054 * @see javax.swing.JComponent#getBaseline(int, int) 1055 * @since 1.6 1056 */ 1057 @Override 1058 public Component.BaselineResizeBehavior getBaselineResizeBehavior( 1059 JComponent c) { 1060 super.getBaselineResizeBehavior(c); 1061 // Force sameBaseline to be updated. 1062 getDisplaySize(); 1063 if (comboBox.isEditable()) { 1064 return editor.getBaselineResizeBehavior(); 1065 } 1066 else if (sameBaseline) { 1067 ListCellRenderer<Object> renderer = comboBox.getRenderer(); 1068 if (renderer == null) { 1069 renderer = new DefaultListCellRenderer(); 1070 } 1071 Object value = null; 1072 Object prototypeValue = comboBox.getPrototypeDisplayValue(); 1073 if (prototypeValue != null) { 1074 value = prototypeValue; 1075 } 1076 else if (comboBox.getModel().getSize() > 0) { 1077 // Note, we're assuming the baseline is the same for all 1078 // cells, if not, this needs to loop through all. 1079 value = comboBox.getModel().getElementAt(0); 1080 } 1081 if (value != null) { 1082 Component component = renderer. 1083 getListCellRendererComponent(listBox, value, -1, 1084 false, false); 1085 return component.getBaselineResizeBehavior(); 1086 } 1087 } 1088 return Component.BaselineResizeBehavior.OTHER; 1089 } 1090 1091 // This is currently hacky... 1092 @Override 1093 public int getAccessibleChildrenCount(JComponent c) { 1094 if ( comboBox.isEditable() ) { 1095 return 2; 1096 } 1097 else { 1098 return 1; 1099 } 1100 } 1101 1102 // This is currently hacky... 1103 @Override 1104 public Accessible getAccessibleChild(JComponent c, int i) { 1105 // 0 = the popup 1106 // 1 = the editor 1107 switch ( i ) { 1108 case 0: 1109 if ( popup instanceof Accessible ) { 1110 AccessibleContext ac = ((Accessible) popup).getAccessibleContext(); 1111 ac.setAccessibleParent(comboBox); 1112 return(Accessible) popup; 1113 } 1114 break; 1115 case 1: 1116 if ( comboBox.isEditable() 1117 && (editor instanceof Accessible) ) { 1118 AccessibleContext ac = ((Accessible) editor).getAccessibleContext(); 1119 ac.setAccessibleParent(comboBox); 1120 return(Accessible) editor; 1121 } 1122 break; 1123 } 1124 return null; 1125 } 1126 1127 // 1128 // end ComponentUI Implementation 1129 //=============================== 1130 1131 1132 //====================== 1133 // begin Utility Methods 1134 // 1135 1136 /** 1137 * Returns whether or not the supplied keyCode maps to a key that is used for 1138 * navigation. This is used for optimizing key input by only passing non- 1139 * navigation keys to the type-ahead mechanism. Subclasses should override this 1140 * if they change the navigation keys. 1141 * 1142 * @param keyCode a key code 1143 * @return {@code true} if the supplied {@code keyCode} maps to a navigation key 1144 */ 1145 protected boolean isNavigationKey( int keyCode ) { 1146 return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN || 1147 keyCode == KeyEvent.VK_KP_UP || keyCode == KeyEvent.VK_KP_DOWN; 1148 } 1149 1150 private boolean isNavigationKey(int keyCode, int modifiers) { 1151 InputMap inputMap = comboBox.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 1152 KeyStroke key = KeyStroke.getKeyStroke(keyCode, modifiers); 1153 1154 if (inputMap != null && inputMap.get(key) != null) { 1155 return true; 1156 } 1157 return false; 1158 } 1159 1160 /** 1161 * Selects the next item in the list. It won't change the selection if the 1162 * currently selected item is already the last item. 1163 */ 1164 protected void selectNextPossibleValue() { 1165 int si; 1166 1167 if ( comboBox.isPopupVisible() ) { 1168 si = listBox.getSelectedIndex(); 1169 } 1170 else { 1171 si = comboBox.getSelectedIndex(); 1172 } 1173 1174 if ( si < comboBox.getModel().getSize() - 1 ) { 1175 listBox.setSelectedIndex( si + 1 ); 1176 listBox.ensureIndexIsVisible( si + 1 ); 1177 if ( !isTableCellEditor ) { 1178 if (!(UIManager.getBoolean("ComboBox.noActionOnKeyNavigation") && comboBox.isPopupVisible())) { 1179 comboBox.setSelectedIndex(si+1); 1180 } 1181 } 1182 comboBox.repaint(); 1183 } 1184 } 1185 1186 /** 1187 * Selects the previous item in the list. It won't change the selection if the 1188 * currently selected item is already the first item. 1189 */ 1190 protected void selectPreviousPossibleValue() { 1191 int si; 1192 1193 if ( comboBox.isPopupVisible() ) { 1194 si = listBox.getSelectedIndex(); 1195 } 1196 else { 1197 si = comboBox.getSelectedIndex(); 1198 } 1199 1200 if ( si > 0 ) { 1201 listBox.setSelectedIndex( si - 1 ); 1202 listBox.ensureIndexIsVisible( si - 1 ); 1203 if ( !isTableCellEditor ) { 1204 if (!(UIManager.getBoolean("ComboBox.noActionOnKeyNavigation") && comboBox.isPopupVisible())) { 1205 comboBox.setSelectedIndex(si-1); 1206 } 1207 } 1208 comboBox.repaint(); 1209 } 1210 } 1211 1212 /** 1213 * Hides the popup if it is showing and shows the popup if it is hidden. 1214 */ 1215 protected void toggleOpenClose() { 1216 setPopupVisible(comboBox, !isPopupVisible(comboBox)); 1217 } 1218 1219 /** 1220 * Returns the area that is reserved for drawing the currently selected item. 1221 * 1222 * @return the area that is reserved for drawing the currently selected item 1223 */ 1224 protected Rectangle rectangleForCurrentValue() { 1225 int width = comboBox.getWidth(); 1226 int height = comboBox.getHeight(); 1227 Insets insets = getInsets(); 1228 int buttonSize = height - (insets.top + insets.bottom); 1229 if ( arrowButton != null ) { 1230 buttonSize = arrowButton.getWidth(); 1231 } 1232 if(BasicGraphicsUtils.isLeftToRight(comboBox)) { 1233 return new Rectangle(insets.left, insets.top, 1234 width - (insets.left + insets.right + buttonSize), 1235 height - (insets.top + insets.bottom)); 1236 } 1237 else { 1238 return new Rectangle(insets.left + buttonSize, insets.top, 1239 width - (insets.left + insets.right + buttonSize), 1240 height - (insets.top + insets.bottom)); 1241 } 1242 } 1243 1244 /** 1245 * Gets the insets from the JComboBox. 1246 * 1247 * @return the insets 1248 */ 1249 protected Insets getInsets() { 1250 return comboBox.getInsets(); 1251 } 1252 1253 // 1254 // end Utility Methods 1255 //==================== 1256 1257 1258 //=============================== 1259 // begin Painting Utility Methods 1260 // 1261 1262 /** 1263 * Paints the currently selected item. 1264 * 1265 * @param g an instance of {@code Graphics} 1266 * @param bounds a bounding rectangle to render to 1267 * @param hasFocus is focused 1268 */ 1269 public void paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus) { 1270 ListCellRenderer<Object> renderer = comboBox.getRenderer(); 1271 Component c; 1272 1273 if ( hasFocus && !isPopupVisible(comboBox) ) { 1274 c = renderer.getListCellRendererComponent( listBox, 1275 comboBox.getSelectedItem(), 1276 -1, 1277 true, 1278 false ); 1279 } 1280 else { 1281 c = renderer.getListCellRendererComponent( listBox, 1282 comboBox.getSelectedItem(), 1283 -1, 1284 false, 1285 false ); 1286 c.setBackground(UIManager.getColor("ComboBox.background")); 1287 } 1288 c.setFont(comboBox.getFont()); 1289 if ( hasFocus && !isPopupVisible(comboBox) ) { 1290 c.setForeground(listBox.getSelectionForeground()); 1291 c.setBackground(listBox.getSelectionBackground()); 1292 } 1293 else { 1294 if ( comboBox.isEnabled() ) { 1295 c.setForeground(comboBox.getForeground()); 1296 c.setBackground(comboBox.getBackground()); 1297 } 1298 else { 1299 c.setForeground(DefaultLookup.getColor( 1300 comboBox, this, "ComboBox.disabledForeground", null)); 1301 c.setBackground(DefaultLookup.getColor( 1302 comboBox, this, "ComboBox.disabledBackground", null)); 1303 } 1304 } 1305 1306 // Fix for 4238829: should lay out the JPanel. 1307 boolean shouldValidate = false; 1308 if (c instanceof JPanel) { 1309 shouldValidate = true; 1310 } 1311 1312 int x = bounds.x, y = bounds.y, w = bounds.width, h = bounds.height; 1313 if (padding != null) { 1314 x = bounds.x + padding.left; 1315 y = bounds.y + padding.top; 1316 w = bounds.width - (padding.left + padding.right); 1317 h = bounds.height - (padding.top + padding.bottom); 1318 } 1319 1320 currentValuePane.paintComponent(g,c,comboBox,x,y,w,h,shouldValidate); 1321 } 1322 1323 /** 1324 * Paints the background of the currently selected item. 1325 * 1326 * @param g an instance of {@code Graphics} 1327 * @param bounds a bounding rectangle to render to 1328 * @param hasFocus is focused 1329 */ 1330 public void paintCurrentValueBackground(Graphics g,Rectangle bounds,boolean hasFocus) { 1331 Color t = g.getColor(); 1332 if ( comboBox.isEnabled() ) 1333 g.setColor(DefaultLookup.getColor(comboBox, this, 1334 "ComboBox.background", null)); 1335 else 1336 g.setColor(DefaultLookup.getColor(comboBox, this, 1337 "ComboBox.disabledBackground", null)); 1338 g.fillRect(bounds.x,bounds.y,bounds.width,bounds.height); 1339 g.setColor(t); 1340 } 1341 1342 /** 1343 * Repaint the currently selected item. 1344 */ 1345 void repaintCurrentValue() { 1346 Rectangle r = rectangleForCurrentValue(); 1347 comboBox.repaint(r.x,r.y,r.width,r.height); 1348 } 1349 1350 // 1351 // end Painting Utility Methods 1352 //============================= 1353 1354 1355 //=============================== 1356 // begin Size Utility Methods 1357 // 1358 1359 /** 1360 * Return the default size of an empty display area of the combo box using 1361 * the current renderer and font. 1362 * 1363 * @return the size of an empty display area 1364 * @see #getDisplaySize 1365 */ 1366 protected Dimension getDefaultSize() { 1367 // Calculates the height and width using the default text renderer 1368 Dimension d = getSizeForComponent(getDefaultListCellRenderer().getListCellRendererComponent(listBox, " ", -1, false, false)); 1369 1370 return new Dimension(d.width, d.height); 1371 } 1372 1373 /** 1374 * Returns the calculated size of the display area. The display area is the 1375 * portion of the combo box in which the selected item is displayed. This 1376 * method will use the prototype display value if it has been set. 1377 * <p> 1378 * For combo boxes with a non trivial number of items, it is recommended to 1379 * use a prototype display value to significantly speed up the display 1380 * size calculation. 1381 * 1382 * @return the size of the display area calculated from the combo box items 1383 * @see javax.swing.JComboBox#setPrototypeDisplayValue 1384 */ 1385 protected Dimension getDisplaySize() { 1386 if (!isDisplaySizeDirty) { 1387 return new Dimension(cachedDisplaySize); 1388 } 1389 Dimension result = new Dimension(); 1390 1391 ListCellRenderer<Object> renderer = comboBox.getRenderer(); 1392 if (renderer == null) { 1393 renderer = new DefaultListCellRenderer(); 1394 } 1395 1396 sameBaseline = true; 1397 1398 Object prototypeValue = comboBox.getPrototypeDisplayValue(); 1399 if (prototypeValue != null) { 1400 // Calculates the dimension based on the prototype value 1401 result = getSizeForComponent(renderer.getListCellRendererComponent(listBox, 1402 prototypeValue, 1403 -1, false, false)); 1404 } else { 1405 // Calculate the dimension by iterating over all the elements in the combo 1406 // box list. 1407 ComboBoxModel<Object> model = comboBox.getModel(); 1408 int modelSize = model.getSize(); 1409 int baseline = -1; 1410 Dimension d; 1411 1412 Component cpn; 1413 1414 if (modelSize > 0 ) { 1415 for (int i = 0; i < modelSize ; i++ ) { 1416 // Calculates the maximum height and width based on the largest 1417 // element 1418 Object value = model.getElementAt(i); 1419 Component c = renderer.getListCellRendererComponent( 1420 listBox, value, -1, false, false); 1421 d = getSizeForComponent(c); 1422 if (sameBaseline && value != null && 1423 (!(value instanceof String) || !"".equals(value))) { 1424 int newBaseline = c.getBaseline(d.width, d.height); 1425 if (newBaseline == -1) { 1426 sameBaseline = false; 1427 } 1428 else if (baseline == -1) { 1429 baseline = newBaseline; 1430 } 1431 else if (baseline != newBaseline) { 1432 sameBaseline = false; 1433 } 1434 } 1435 result.width = Math.max(result.width,d.width); 1436 result.height = Math.max(result.height,d.height); 1437 } 1438 } else { 1439 result = getDefaultSize(); 1440 if (comboBox.isEditable()) { 1441 result.width = 100; 1442 } 1443 } 1444 } 1445 1446 if ( comboBox.isEditable() ) { 1447 Dimension d = editor.getPreferredSize(); 1448 result.width = Math.max(result.width,d.width); 1449 result.height = Math.max(result.height,d.height); 1450 } 1451 1452 // calculate in the padding 1453 if (padding != null) { 1454 result.width += padding.left + padding.right; 1455 result.height += padding.top + padding.bottom; 1456 } 1457 1458 // Set the cached value 1459 cachedDisplaySize.setSize(result.width, result.height); 1460 isDisplaySizeDirty = false; 1461 1462 return result; 1463 } 1464 1465 /** 1466 * Returns the size a component would have if used as a cell renderer. 1467 * 1468 * @param comp a {@code Component} to check 1469 * @return size of the component 1470 * @since 1.7 1471 */ 1472 protected Dimension getSizeForComponent(Component comp) { 1473 // This has been refactored out in hopes that it may be investigated and 1474 // simplified for the next major release. adding/removing 1475 // the component to the currentValuePane and changing the font may be 1476 // redundant operations. 1477 currentValuePane.add(comp); 1478 comp.setFont(comboBox.getFont()); 1479 Dimension d = comp.getPreferredSize(); 1480 currentValuePane.remove(comp); 1481 return d; 1482 } 1483 1484 1485 // 1486 // end Size Utility Methods 1487 //============================= 1488 1489 1490 //================================= 1491 // begin Keyboard Action Management 1492 // 1493 1494 /** 1495 * Adds keyboard actions to the JComboBox. Actions on enter and esc are already 1496 * supplied. Add more actions as you need them. 1497 */ 1498 protected void installKeyboardActions() { 1499 InputMap km = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 1500 SwingUtilities.replaceUIInputMap(comboBox, JComponent. 1501 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km); 1502 1503 1504 LazyActionMap.installLazyActionMap(comboBox, BasicComboBoxUI.class, 1505 "ComboBox.actionMap"); 1506 } 1507 1508 InputMap getInputMap(int condition) { 1509 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) { 1510 return (InputMap)DefaultLookup.get(comboBox, this, 1511 "ComboBox.ancestorInputMap"); 1512 } 1513 return null; 1514 } 1515 1516 boolean isTableCellEditor() { 1517 return isTableCellEditor; 1518 } 1519 1520 /** 1521 * Removes the focus InputMap and ActionMap. 1522 */ 1523 protected void uninstallKeyboardActions() { 1524 SwingUtilities.replaceUIInputMap(comboBox, JComponent. 1525 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); 1526 SwingUtilities.replaceUIActionMap(comboBox, null); 1527 } 1528 1529 1530 // 1531 // Actions 1532 // 1533 private static class Actions extends UIAction { 1534 private static final String HIDE = "hidePopup"; 1535 private static final String DOWN = "selectNext"; 1536 private static final String DOWN_2 = "selectNext2"; 1537 private static final String TOGGLE = "togglePopup"; 1538 private static final String TOGGLE_2 = "spacePopup"; 1539 private static final String UP = "selectPrevious"; 1540 private static final String UP_2 = "selectPrevious2"; 1541 private static final String ENTER = "enterPressed"; 1542 private static final String PAGE_DOWN = "pageDownPassThrough"; 1543 private static final String PAGE_UP = "pageUpPassThrough"; 1544 private static final String HOME = "homePassThrough"; 1545 private static final String END = "endPassThrough"; 1546 1547 Actions(String name) { 1548 super(name); 1549 } 1550 1551 public void actionPerformed( ActionEvent e ) { 1552 String key = getName(); 1553 @SuppressWarnings("unchecked") 1554 JComboBox<Object> comboBox = (JComboBox)e.getSource(); 1555 BasicComboBoxUI ui = (BasicComboBoxUI)BasicLookAndFeel.getUIOfType( 1556 comboBox.getUI(), BasicComboBoxUI.class); 1557 if (key == HIDE) { 1558 comboBox.firePopupMenuCanceled(); 1559 comboBox.setPopupVisible(false); 1560 } 1561 else if (key == PAGE_DOWN || key == PAGE_UP || 1562 key == HOME || key == END) { 1563 int index = getNextIndex(comboBox, key); 1564 if (index >= 0 && index < comboBox.getItemCount()) { 1565 if (UIManager.getBoolean("ComboBox.noActionOnKeyNavigation") && comboBox.isPopupVisible()) { 1566 ui.listBox.setSelectedIndex(index); 1567 ui.listBox.ensureIndexIsVisible(index); 1568 comboBox.repaint(); 1569 } else { 1570 comboBox.setSelectedIndex(index); 1571 } 1572 } 1573 } 1574 else if (key == DOWN) { 1575 if (comboBox.isShowing() ) { 1576 if ( comboBox.isPopupVisible() ) { 1577 if (ui != null) { 1578 ui.selectNextPossibleValue(); 1579 } 1580 } else { 1581 comboBox.setPopupVisible(true); 1582 } 1583 } 1584 } 1585 else if (key == DOWN_2) { 1586 // Special case in which pressing the arrow keys will not 1587 // make the popup appear - except for editable combo boxes 1588 // and combo boxes inside a table. 1589 if (comboBox.isShowing() ) { 1590 if ( (comboBox.isEditable() || 1591 (ui != null && ui.isTableCellEditor())) 1592 && !comboBox.isPopupVisible() ) { 1593 comboBox.setPopupVisible(true); 1594 } else { 1595 if (ui != null) { 1596 ui.selectNextPossibleValue(); 1597 } 1598 } 1599 } 1600 } 1601 else if (key == TOGGLE || key == TOGGLE_2) { 1602 if (ui != null && (key == TOGGLE || !comboBox.isEditable())) { 1603 if ( ui.isTableCellEditor() ) { 1604 // Forces the selection of the list item if the 1605 // combo box is in a JTable. 1606 comboBox.setSelectedIndex(ui.popup.getList(). 1607 getSelectedIndex()); 1608 } 1609 else { 1610 comboBox.setPopupVisible(!comboBox.isPopupVisible()); 1611 } 1612 } 1613 } 1614 else if (key == UP) { 1615 if (ui != null) { 1616 if (ui.isPopupVisible(comboBox)) { 1617 ui.selectPreviousPossibleValue(); 1618 } 1619 else if (DefaultLookup.getBoolean(comboBox, ui, 1620 "ComboBox.showPopupOnNavigation", false)) { 1621 ui.setPopupVisible(comboBox, true); 1622 } 1623 } 1624 } 1625 else if (key == UP_2) { 1626 // Special case in which pressing the arrow keys will not 1627 // make the popup appear - except for editable combo boxes. 1628 if (comboBox.isShowing() && ui != null) { 1629 if ( comboBox.isEditable() && !comboBox.isPopupVisible()) { 1630 comboBox.setPopupVisible(true); 1631 } else { 1632 ui.selectPreviousPossibleValue(); 1633 } 1634 } 1635 } 1636 1637 else if (key == ENTER) { 1638 if (comboBox.isPopupVisible()) { 1639 // If ComboBox.noActionOnKeyNavigation is set, 1640 // forse selection of list item 1641 if (UIManager.getBoolean("ComboBox.noActionOnKeyNavigation")) { 1642 Object listItem = ui.popup.getList().getSelectedValue(); 1643 if (listItem != null) { 1644 comboBox.getEditor().setItem(listItem); 1645 comboBox.setSelectedItem(listItem); 1646 } 1647 comboBox.setPopupVisible(false); 1648 } else { 1649 // Forces the selection of the list item 1650 boolean isEnterSelectablePopup = 1651 UIManager.getBoolean("ComboBox.isEnterSelectablePopup"); 1652 if (!comboBox.isEditable() || isEnterSelectablePopup 1653 || ui.isTableCellEditor) { 1654 Object listItem = ui.popup.getList().getSelectedValue(); 1655 if (listItem != null) { 1656 // Use the selected value from popup 1657 // to set the selected item in combo box, 1658 // but ensure before that JComboBox.actionPerformed() 1659 // won't use editor's value to set the selected item 1660 comboBox.getEditor().setItem(listItem); 1661 comboBox.setSelectedItem(listItem); 1662 } 1663 } 1664 comboBox.setPopupVisible(false); 1665 } 1666 } 1667 else { 1668 // Hide combo box if it is a table cell editor 1669 if (ui.isTableCellEditor && !comboBox.isEditable()) { 1670 comboBox.setSelectedItem(comboBox.getSelectedItem()); 1671 } 1672 // Call the default button binding. 1673 // This is a pretty messy way of passing an event through 1674 // to the root pane. 1675 JRootPane root = SwingUtilities.getRootPane(comboBox); 1676 if (root != null) { 1677 InputMap im = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 1678 ActionMap am = root.getActionMap(); 1679 if (im != null && am != null) { 1680 Object obj = im.get(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0)); 1681 if (obj != null) { 1682 Action action = am.get(obj); 1683 if (action != null) { 1684 action.actionPerformed(new ActionEvent( 1685 root, e.getID(), e.getActionCommand(), 1686 e.getWhen(), e.getModifiers())); 1687 } 1688 } 1689 } 1690 } 1691 } 1692 } 1693 } 1694 1695 private int getNextIndex(JComboBox<?> comboBox, String key) { 1696 int listHeight = comboBox.getMaximumRowCount(); 1697 1698 int selectedIndex = comboBox.getSelectedIndex(); 1699 if (UIManager.getBoolean("ComboBox.noActionOnKeyNavigation") 1700 && (comboBox.getUI() instanceof BasicComboBoxUI)) { 1701 selectedIndex = ((BasicComboBoxUI) comboBox.getUI()).listBox.getSelectedIndex(); 1702 } 1703 1704 if (key == PAGE_UP) { 1705 int index = selectedIndex - listHeight; 1706 return (index < 0 ? 0: index); 1707 } 1708 else if (key == PAGE_DOWN) { 1709 int index = selectedIndex + listHeight; 1710 int max = comboBox.getItemCount(); 1711 return (index < max ? index: max-1); 1712 } 1713 else if (key == HOME) { 1714 return 0; 1715 } 1716 else if (key == END) { 1717 return comboBox.getItemCount() - 1; 1718 } 1719 return comboBox.getSelectedIndex(); 1720 } 1721 1722 public boolean isEnabled(Object c) { 1723 if (getName() == HIDE) { 1724 return (c != null && ((JComboBox)c).isPopupVisible()); 1725 } 1726 return true; 1727 } 1728 } 1729 // 1730 // end Keyboard Action Management 1731 //=============================== 1732 1733 1734 // 1735 // Shared Handler, implements all listeners 1736 // 1737 private class Handler implements ActionListener, FocusListener, 1738 KeyListener, LayoutManager, 1739 ListDataListener, PropertyChangeListener, 1740 MouseWheelListener { 1741 // 1742 // PropertyChangeListener 1743 // 1744 public void propertyChange(PropertyChangeEvent e) { 1745 String propertyName = e.getPropertyName(); 1746 if (e.getSource() == editor){ 1747 // If the border of the editor changes then this can effect 1748 // the size of the editor which can cause the combo's size to 1749 // become invalid so we need to clear size caches 1750 if ("border".equals(propertyName)){ 1751 isMinimumSizeDirty = true; 1752 isDisplaySizeDirty = true; 1753 comboBox.revalidate(); 1754 } 1755 } else { 1756 @SuppressWarnings("unchecked") 1757 JComboBox<?> comboBox = (JComboBox)e.getSource(); 1758 if ( propertyName == "model" ) { 1759 @SuppressWarnings("unchecked") 1760 ComboBoxModel<?> newModel = (ComboBoxModel)e.getNewValue(); 1761 @SuppressWarnings("unchecked") 1762 ComboBoxModel<?> oldModel = (ComboBoxModel)e.getOldValue(); 1763 1764 if ( oldModel != null && listDataListener != null ) { 1765 oldModel.removeListDataListener( listDataListener ); 1766 } 1767 1768 if ( newModel != null && listDataListener != null ) { 1769 newModel.addListDataListener( listDataListener ); 1770 } 1771 1772 if ( editor != null ) { 1773 comboBox.configureEditor( comboBox.getEditor(), comboBox.getSelectedItem() ); 1774 } 1775 isMinimumSizeDirty = true; 1776 isDisplaySizeDirty = true; 1777 comboBox.revalidate(); 1778 comboBox.repaint(); 1779 } 1780 else if ( propertyName == "editor" && comboBox.isEditable() ) { 1781 addEditor(); 1782 comboBox.revalidate(); 1783 } 1784 else if ( propertyName == "editable" ) { 1785 if ( comboBox.isEditable() ) { 1786 comboBox.setRequestFocusEnabled( false ); 1787 addEditor(); 1788 } else { 1789 comboBox.setRequestFocusEnabled( true ); 1790 removeEditor(); 1791 } 1792 updateToolTipTextForChildren(); 1793 comboBox.revalidate(); 1794 } 1795 else if ( propertyName == "enabled" ) { 1796 boolean enabled = comboBox.isEnabled(); 1797 if ( editor != null ) 1798 editor.setEnabled(enabled); 1799 if ( arrowButton != null ) 1800 arrowButton.setEnabled(enabled); 1801 comboBox.repaint(); 1802 } 1803 else if ( propertyName == "focusable" ) { 1804 boolean focusable = comboBox.isFocusable(); 1805 if ( editor != null ) 1806 editor.setFocusable(focusable); 1807 if ( arrowButton != null ) 1808 arrowButton.setFocusable(focusable); 1809 comboBox.repaint(); 1810 } 1811 else if ( propertyName == "maximumRowCount" ) { 1812 if ( isPopupVisible( comboBox ) ) { 1813 setPopupVisible(comboBox, false); 1814 setPopupVisible(comboBox, true); 1815 } 1816 } 1817 else if ( propertyName == "font" ) { 1818 listBox.setFont( comboBox.getFont() ); 1819 if ( editor != null ) { 1820 editor.setFont( comboBox.getFont() ); 1821 } 1822 isMinimumSizeDirty = true; 1823 isDisplaySizeDirty = true; 1824 comboBox.validate(); 1825 } 1826 else if ( propertyName == JComponent.TOOL_TIP_TEXT_KEY ) { 1827 updateToolTipTextForChildren(); 1828 } 1829 else if ( propertyName == BasicComboBoxUI.IS_TABLE_CELL_EDITOR ) { 1830 Boolean inTable = (Boolean)e.getNewValue(); 1831 isTableCellEditor = inTable.equals(Boolean.TRUE) ? true : false; 1832 } 1833 else if (propertyName == "prototypeDisplayValue") { 1834 isMinimumSizeDirty = true; 1835 isDisplaySizeDirty = true; 1836 comboBox.revalidate(); 1837 } 1838 else if (propertyName == "renderer") { 1839 isMinimumSizeDirty = true; 1840 isDisplaySizeDirty = true; 1841 comboBox.revalidate(); 1842 } 1843 } 1844 } 1845 1846 1847 // 1848 // KeyListener 1849 // 1850 1851 // This listener checks to see if the key event isn't a navigation 1852 // key. If it finds a key event that wasn't a navigation key it 1853 // dispatches it to JComboBox.selectWithKeyChar() so that it can do 1854 // type-ahead. 1855 public void keyPressed( KeyEvent e ) { 1856 if ( isNavigationKey(e.getKeyCode(), e.getModifiers()) ) { 1857 lastTime = 0L; 1858 } else if ( comboBox.isEnabled() && comboBox.getModel().getSize()!=0 && 1859 isTypeAheadKey( e ) && e.getKeyChar() != KeyEvent.CHAR_UNDEFINED) { 1860 time = e.getWhen(); 1861 if ( comboBox.selectWithKeyChar(e.getKeyChar()) ) { 1862 e.consume(); 1863 } 1864 } 1865 } 1866 1867 public void keyTyped(KeyEvent e) { 1868 } 1869 1870 public void keyReleased(KeyEvent e) { 1871 } 1872 1873 private boolean isTypeAheadKey( KeyEvent e ) { 1874 return !e.isAltDown() && !BasicGraphicsUtils.isMenuShortcutKeyDown(e); 1875 } 1876 1877 // 1878 // FocusListener 1879 // 1880 // NOTE: The class is added to both the Editor and ComboBox. 1881 // The combo box listener hides the popup when the focus is lost. 1882 // It also repaints when focus is gained or lost. 1883 1884 public void focusGained( FocusEvent e ) { 1885 ComboBoxEditor comboBoxEditor = comboBox.getEditor(); 1886 1887 if ( (comboBoxEditor != null) && 1888 (e.getSource() == comboBoxEditor.getEditorComponent()) ) { 1889 return; 1890 } 1891 hasFocus = true; 1892 comboBox.repaint(); 1893 1894 if (comboBox.isEditable() && editor != null) { 1895 editor.requestFocus(); 1896 } 1897 } 1898 1899 public void focusLost( FocusEvent e ) { 1900 ComboBoxEditor editor = comboBox.getEditor(); 1901 if ( (editor != null) && 1902 (e.getSource() == editor.getEditorComponent()) ) { 1903 Object item = editor.getItem(); 1904 1905 Object selectedItem = comboBox.getSelectedItem(); 1906 if (!e.isTemporary() && item != null && 1907 !item.equals((selectedItem == null) ? "" : selectedItem )) { 1908 comboBox.actionPerformed 1909 (new ActionEvent(editor, 0, "", 1910 EventQueue.getMostRecentEventTime(), 0)); 1911 } 1912 } 1913 1914 hasFocus = false; 1915 if (!e.isTemporary()) { 1916 setPopupVisible(comboBox, false); 1917 } 1918 comboBox.repaint(); 1919 } 1920 1921 // 1922 // ListDataListener 1923 // 1924 1925 // This listener watches for changes in the ComboBoxModel 1926 public void contentsChanged( ListDataEvent e ) { 1927 if ( !(e.getIndex0() == -1 && e.getIndex1() == -1) ) { 1928 isMinimumSizeDirty = true; 1929 comboBox.revalidate(); 1930 } 1931 1932 // set the editor with the selected item since this 1933 // is the event handler for a selected item change. 1934 if (comboBox.isEditable() && editor != null) { 1935 comboBox.configureEditor( comboBox.getEditor(), 1936 comboBox.getSelectedItem() ); 1937 } 1938 1939 isDisplaySizeDirty = true; 1940 comboBox.repaint(); 1941 } 1942 1943 public void intervalAdded( ListDataEvent e ) { 1944 contentsChanged( e ); 1945 } 1946 1947 public void intervalRemoved( ListDataEvent e ) { 1948 contentsChanged( e ); 1949 } 1950 1951 // 1952 // LayoutManager 1953 // 1954 1955 // This layout manager handles the 'standard' layout of combo boxes. 1956 // It puts the arrow button to the right and the editor to the left. 1957 // If there is no editor it still keeps the arrow button to the right. 1958 public void addLayoutComponent(String name, Component comp) {} 1959 1960 public void removeLayoutComponent(Component comp) {} 1961 1962 public Dimension preferredLayoutSize(Container parent) { 1963 return parent.getPreferredSize(); 1964 } 1965 1966 public Dimension minimumLayoutSize(Container parent) { 1967 return parent.getMinimumSize(); 1968 } 1969 1970 public void layoutContainer(Container parent) { 1971 @SuppressWarnings("unchecked") 1972 JComboBox<?> cb = (JComboBox)parent; 1973 int width = cb.getWidth(); 1974 int height = cb.getHeight(); 1975 1976 Insets insets = getInsets(); 1977 int buttonHeight = height - (insets.top + insets.bottom); 1978 int buttonWidth = buttonHeight; 1979 if (arrowButton != null) { 1980 Insets arrowInsets = arrowButton.getInsets(); 1981 buttonWidth = squareButton ? 1982 buttonHeight : 1983 arrowButton.getPreferredSize().width + arrowInsets.left + arrowInsets.right; 1984 } 1985 Rectangle cvb; 1986 1987 if (arrowButton != null) { 1988 if (BasicGraphicsUtils.isLeftToRight(cb)) { 1989 arrowButton.setBounds(width - (insets.right + buttonWidth), 1990 insets.top, buttonWidth, buttonHeight); 1991 } else { 1992 arrowButton.setBounds(insets.left, insets.top, 1993 buttonWidth, buttonHeight); 1994 } 1995 } 1996 if ( editor != null ) { 1997 cvb = rectangleForCurrentValue(); 1998 editor.setBounds(cvb); 1999 } 2000 } 2001 2002 // 2003 // ActionListener 2004 // 2005 // Fix for 4515752: Forward the Enter pressed on the 2006 // editable combo box to the default button 2007 2008 // Note: This could depend on event ordering. The first ActionEvent 2009 // from the editor may be handled by the JComboBox in which case, the 2010 // enterPressed action will always be invoked. 2011 public void actionPerformed(ActionEvent evt) { 2012 Object item = comboBox.getEditor().getItem(); 2013 if (item != null) { 2014 if (!comboBox.isPopupVisible() && !item.equals(comboBox.getSelectedItem())) { 2015 comboBox.setSelectedItem(comboBox.getEditor().getItem()); 2016 } 2017 ActionMap am = comboBox.getActionMap(); 2018 if (am != null) { 2019 Action action = am.get("enterPressed"); 2020 if (action != null) { 2021 action.actionPerformed(new ActionEvent(comboBox, evt.getID(), 2022 evt.getActionCommand(), 2023 evt.getModifiers())); 2024 } 2025 } 2026 } 2027 } 2028 2029 public void mouseWheelMoved(MouseWheelEvent e) { 2030 e.consume(); 2031 } 2032 } 2033 2034 class DefaultKeySelectionManager implements JComboBox.KeySelectionManager, UIResource { 2035 private String prefix = ""; 2036 private String typedString = ""; 2037 2038 public int selectionForKey(char aKey,ComboBoxModel<?> aModel) { 2039 if (lastTime == 0L) { 2040 prefix = ""; 2041 typedString = ""; 2042 } 2043 boolean startingFromSelection = true; 2044 2045 int startIndex = comboBox.getSelectedIndex(); 2046 if (time - lastTime < timeFactor) { 2047 typedString += aKey; 2048 if((prefix.length() == 1) && (aKey == prefix.charAt(0))) { 2049 // Subsequent same key presses move the keyboard focus to the next 2050 // object that starts with the same letter. 2051 startIndex++; 2052 } else { 2053 prefix = typedString; 2054 } 2055 } else { 2056 startIndex++; 2057 typedString = "" + aKey; 2058 prefix = typedString; 2059 } 2060 lastTime = time; 2061 2062 if (startIndex < 0 || startIndex >= aModel.getSize()) { 2063 startingFromSelection = false; 2064 startIndex = 0; 2065 } 2066 int index = listBox.getNextMatch(prefix, startIndex, 2067 Position.Bias.Forward); 2068 if (index < 0 && startingFromSelection) { // wrap 2069 index = listBox.getNextMatch(prefix, 0, 2070 Position.Bias.Forward); 2071 } 2072 return index; 2073 } 2074 } 2075 2076 }