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