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