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