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