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