1 /* 2 * Copyright (c) 1997, 2008, 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 comboBox.add( arrowButton ); 696 697 if (arrowButton != null) { 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 if (value == null) { 975 value = " "; 976 } else if (value instanceof String && "".equals(value)) { 977 value = " "; 978 } 979 Component component = renderer. 980 getListCellRendererComponent(listBox, value, -1, 981 false, false); 982 if (component instanceof JComponent) { 983 component.setFont(comboBox.getFont()); 984 } 985 baseline = component.getBaseline(width, height); 986 } 987 else { 988 baseline = editor.getBaseline(width, height); 989 } 990 if (baseline > 0) { 991 baseline += insets.top; 992 } 993 } 994 return baseline; 995 } 996 997 /** 998 * Returns an enum indicating how the baseline of the component 999 * changes as the size changes. 1000 * 1001 * @throws NullPointerException {@inheritDoc} 1002 * @see javax.swing.JComponent#getBaseline(int, int) 1003 * @since 1.6 1004 */ 1005 @Override 1006 public Component.BaselineResizeBehavior getBaselineResizeBehavior( 1007 JComponent c) { 1008 super.getBaselineResizeBehavior(c); 1009 // Force sameBaseline to be updated. 1010 getDisplaySize(); 1011 if (comboBox.isEditable()) { 1012 return editor.getBaselineResizeBehavior(); 1013 } 1014 else if (sameBaseline) { 1015 ListCellRenderer renderer = comboBox.getRenderer(); 1016 if (renderer == null) { 1017 renderer = new DefaultListCellRenderer(); 1018 } 1019 Object value = null; 1020 Object prototypeValue = comboBox.getPrototypeDisplayValue(); 1021 if (prototypeValue != null) { 1022 value = prototypeValue; 1023 } 1024 else if (comboBox.getModel().getSize() > 0) { 1025 // Note, we're assuming the baseline is the same for all 1026 // cells, if not, this needs to loop through all. 1027 value = comboBox.getModel().getElementAt(0); 1028 } 1029 if (value != null) { 1030 Component component = renderer. 1031 getListCellRendererComponent(listBox, value, -1, 1032 false, false); 1033 return component.getBaselineResizeBehavior(); 1034 } 1035 } 1036 return Component.BaselineResizeBehavior.OTHER; 1037 } 1038 1039 // This is currently hacky... 1040 @Override 1041 public int getAccessibleChildrenCount(JComponent c) { 1042 if ( comboBox.isEditable() ) { 1043 return 2; 1044 } 1045 else { 1046 return 1; 1047 } 1048 } 1049 1050 // This is currently hacky... 1051 @Override 1052 public Accessible getAccessibleChild(JComponent c, int i) { 1053 // 0 = the popup 1054 // 1 = the editor 1055 switch ( i ) { 1056 case 0: 1057 if ( popup instanceof Accessible ) { 1058 AccessibleContext ac = ((Accessible) popup).getAccessibleContext(); 1059 ac.setAccessibleParent(comboBox); 1060 return(Accessible) popup; 1061 } 1062 break; 1063 case 1: 1064 if ( comboBox.isEditable() 1065 && (editor instanceof Accessible) ) { 1066 AccessibleContext ac = ((Accessible) editor).getAccessibleContext(); 1067 ac.setAccessibleParent(comboBox); 1068 return(Accessible) editor; 1069 } 1070 break; 1071 } 1072 return null; 1073 } 1074 1075 // 1076 // end ComponentUI Implementation 1077 //=============================== 1078 1079 1080 //====================== 1081 // begin Utility Methods 1082 // 1083 1084 /** 1085 * Returns whether or not the supplied keyCode maps to a key that is used for 1086 * navigation. This is used for optimizing key input by only passing non- 1087 * navigation keys to the type-ahead mechanism. Subclasses should override this 1088 * if they change the navigation keys. 1089 */ 1090 protected boolean isNavigationKey( int keyCode ) { 1091 return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN || 1092 keyCode == KeyEvent.VK_KP_UP || keyCode == KeyEvent.VK_KP_DOWN; 1093 } 1094 1095 private boolean isNavigationKey(int keyCode, int modifiers) { 1096 InputMap inputMap = comboBox.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 1097 KeyStroke key = KeyStroke.getKeyStroke(keyCode, modifiers); 1098 1099 if (inputMap != null && inputMap.get(key) != null) { 1100 return true; 1101 } 1102 return false; 1103 } 1104 1105 /** 1106 * Selects the next item in the list. It won't change the selection if the 1107 * currently selected item is already the last item. 1108 */ 1109 protected void selectNextPossibleValue() { 1110 int si; 1111 1112 if ( comboBox.isPopupVisible() ) { 1113 si = listBox.getSelectedIndex(); 1114 } 1115 else { 1116 si = comboBox.getSelectedIndex(); 1117 } 1118 1119 if ( si < comboBox.getModel().getSize() - 1 ) { 1120 listBox.setSelectedIndex( si + 1 ); 1121 listBox.ensureIndexIsVisible( si + 1 ); 1122 if ( !isTableCellEditor ) { 1123 comboBox.setSelectedIndex(si+1); 1124 } 1125 comboBox.repaint(); 1126 } 1127 } 1128 1129 /** 1130 * Selects the previous item in the list. It won't change the selection if the 1131 * currently selected item is already the first item. 1132 */ 1133 protected void selectPreviousPossibleValue() { 1134 int si; 1135 1136 if ( comboBox.isPopupVisible() ) { 1137 si = listBox.getSelectedIndex(); 1138 } 1139 else { 1140 si = comboBox.getSelectedIndex(); 1141 } 1142 1143 if ( si > 0 ) { 1144 listBox.setSelectedIndex( si - 1 ); 1145 listBox.ensureIndexIsVisible( si - 1 ); 1146 if ( !isTableCellEditor ) { 1147 comboBox.setSelectedIndex(si-1); 1148 } 1149 comboBox.repaint(); 1150 } 1151 } 1152 1153 /** 1154 * Hides the popup if it is showing and shows the popup if it is hidden. 1155 */ 1156 protected void toggleOpenClose() { 1157 setPopupVisible(comboBox, !isPopupVisible(comboBox)); 1158 } 1159 1160 /** 1161 * Returns the area that is reserved for drawing the currently selected item. 1162 */ 1163 protected Rectangle rectangleForCurrentValue() { 1164 int width = comboBox.getWidth(); 1165 int height = comboBox.getHeight(); 1166 Insets insets = getInsets(); 1167 int buttonSize = height - (insets.top + insets.bottom); 1168 if ( arrowButton != null ) { 1169 buttonSize = arrowButton.getWidth(); 1170 } 1171 if(BasicGraphicsUtils.isLeftToRight(comboBox)) { 1172 return new Rectangle(insets.left, insets.top, 1173 width - (insets.left + insets.right + buttonSize), 1174 height - (insets.top + insets.bottom)); 1175 } 1176 else { 1177 return new Rectangle(insets.left + buttonSize, insets.top, 1178 width - (insets.left + insets.right + buttonSize), 1179 height - (insets.top + insets.bottom)); 1180 } 1181 } 1182 1183 /** 1184 * Gets the insets from the JComboBox. 1185 */ 1186 protected Insets getInsets() { 1187 return comboBox.getInsets(); 1188 } 1189 1190 // 1191 // end Utility Methods 1192 //==================== 1193 1194 1195 //=============================== 1196 // begin Painting Utility Methods 1197 // 1198 1199 /** 1200 * Paints the currently selected item. 1201 */ 1202 public void paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus) { 1203 ListCellRenderer renderer = comboBox.getRenderer(); 1204 Component c; 1205 1206 if ( hasFocus && !isPopupVisible(comboBox) ) { 1207 c = renderer.getListCellRendererComponent( listBox, 1208 comboBox.getSelectedItem(), 1209 -1, 1210 true, 1211 false ); 1212 } 1213 else { 1214 c = renderer.getListCellRendererComponent( listBox, 1215 comboBox.getSelectedItem(), 1216 -1, 1217 false, 1218 false ); 1219 c.setBackground(UIManager.getColor("ComboBox.background")); 1220 } 1221 c.setFont(comboBox.getFont()); 1222 if ( hasFocus && !isPopupVisible(comboBox) ) { 1223 c.setForeground(listBox.getSelectionForeground()); 1224 c.setBackground(listBox.getSelectionBackground()); 1225 } 1226 else { 1227 if ( comboBox.isEnabled() ) { 1228 c.setForeground(comboBox.getForeground()); 1229 c.setBackground(comboBox.getBackground()); 1230 } 1231 else { 1232 c.setForeground(DefaultLookup.getColor( 1233 comboBox, this, "ComboBox.disabledForeground", null)); 1234 c.setBackground(DefaultLookup.getColor( 1235 comboBox, this, "ComboBox.disabledBackground", null)); 1236 } 1237 } 1238 1239 // Fix for 4238829: should lay out the JPanel. 1240 boolean shouldValidate = false; 1241 if (c instanceof JPanel) { 1242 shouldValidate = true; 1243 } 1244 1245 int x = bounds.x, y = bounds.y, w = bounds.width, h = bounds.height; 1246 if (padding != null) { 1247 x = bounds.x + padding.left; 1248 y = bounds.y + padding.top; 1249 w = bounds.width - (padding.left + padding.right); 1250 h = bounds.height - (padding.top + padding.bottom); 1251 } 1252 1253 currentValuePane.paintComponent(g,c,comboBox,x,y,w,h,shouldValidate); 1254 } 1255 1256 /** 1257 * Paints the background of the currently selected item. 1258 */ 1259 public void paintCurrentValueBackground(Graphics g,Rectangle bounds,boolean hasFocus) { 1260 Color t = g.getColor(); 1261 if ( comboBox.isEnabled() ) 1262 g.setColor(DefaultLookup.getColor(comboBox, this, 1263 "ComboBox.background", null)); 1264 else 1265 g.setColor(DefaultLookup.getColor(comboBox, this, 1266 "ComboBox.disabledBackground", null)); 1267 g.fillRect(bounds.x,bounds.y,bounds.width,bounds.height); 1268 g.setColor(t); 1269 } 1270 1271 /** 1272 * Repaint the currently selected item. 1273 */ 1274 void repaintCurrentValue() { 1275 Rectangle r = rectangleForCurrentValue(); 1276 comboBox.repaint(r.x,r.y,r.width,r.height); 1277 } 1278 1279 // 1280 // end Painting Utility Methods 1281 //============================= 1282 1283 1284 //=============================== 1285 // begin Size Utility Methods 1286 // 1287 1288 /** 1289 * Return the default size of an empty display area of the combo box using 1290 * the current renderer and font. 1291 * 1292 * @return the size of an empty display area 1293 * @see #getDisplaySize 1294 */ 1295 protected Dimension getDefaultSize() { 1296 // Calculates the height and width using the default text renderer 1297 Dimension d = getSizeForComponent(getDefaultListCellRenderer().getListCellRendererComponent(listBox, " ", -1, false, false)); 1298 1299 return new Dimension(d.width, d.height); 1300 } 1301 1302 /** 1303 * Returns the calculated size of the display area. The display area is the 1304 * portion of the combo box in which the selected item is displayed. This 1305 * method will use the prototype display value if it has been set. 1306 * <p> 1307 * For combo boxes with a non trivial number of items, it is recommended to 1308 * use a prototype display value to significantly speed up the display 1309 * size calculation. 1310 * 1311 * @return the size of the display area calculated from the combo box items 1312 * @see javax.swing.JComboBox#setPrototypeDisplayValue 1313 */ 1314 protected Dimension getDisplaySize() { 1315 if (!isDisplaySizeDirty) { 1316 return new Dimension(cachedDisplaySize); 1317 } 1318 Dimension result = new Dimension(); 1319 1320 ListCellRenderer renderer = comboBox.getRenderer(); 1321 if (renderer == null) { 1322 renderer = new DefaultListCellRenderer(); 1323 } 1324 1325 sameBaseline = true; 1326 1327 Object prototypeValue = comboBox.getPrototypeDisplayValue(); 1328 if (prototypeValue != null) { 1329 // Calculates the dimension based on the prototype value 1330 result = getSizeForComponent(renderer.getListCellRendererComponent(listBox, 1331 prototypeValue, 1332 -1, false, false)); 1333 } else { 1334 // Calculate the dimension by iterating over all the elements in the combo 1335 // box list. 1336 ComboBoxModel model = comboBox.getModel(); 1337 int modelSize = model.getSize(); 1338 int baseline = -1; 1339 Dimension d; 1340 1341 Component cpn; 1342 1343 if (modelSize > 0 ) { 1344 for (int i = 0; i < modelSize ; i++ ) { 1345 // Calculates the maximum height and width based on the largest 1346 // element 1347 Object value = model.getElementAt(i); 1348 Component c = renderer.getListCellRendererComponent( 1349 listBox, value, -1, false, false); 1350 d = getSizeForComponent(c); 1351 if (sameBaseline && value != null && 1352 (!(value instanceof String) || !"".equals(value))) { 1353 int newBaseline = c.getBaseline(d.width, d.height); 1354 if (newBaseline == -1) { 1355 sameBaseline = false; 1356 } 1357 else if (baseline == -1) { 1358 baseline = newBaseline; 1359 } 1360 else if (baseline != newBaseline) { 1361 sameBaseline = false; 1362 } 1363 } 1364 result.width = Math.max(result.width,d.width); 1365 result.height = Math.max(result.height,d.height); 1366 } 1367 } else { 1368 result = getDefaultSize(); 1369 if (comboBox.isEditable()) { 1370 result.width = 100; 1371 } 1372 } 1373 } 1374 1375 if ( comboBox.isEditable() ) { 1376 Dimension d = editor.getPreferredSize(); 1377 result.width = Math.max(result.width,d.width); 1378 result.height = Math.max(result.height,d.height); 1379 } 1380 1381 // calculate in the padding 1382 if (padding != null) { 1383 result.width += padding.left + padding.right; 1384 result.height += padding.top + padding.bottom; 1385 } 1386 1387 // Set the cached value 1388 cachedDisplaySize.setSize(result.width, result.height); 1389 isDisplaySizeDirty = false; 1390 1391 return result; 1392 } 1393 1394 /** 1395 * Returns the size a component would have if used as a cell renderer. 1396 * 1397 * @param comp a {@code Component} to check 1398 * @return size of the component 1399 * @since 1.7 1400 */ 1401 protected Dimension getSizeForComponent(Component comp) { 1402 // This has been refactored out in hopes that it may be investigated and 1403 // simplified for the next major release. adding/removing 1404 // the component to the currentValuePane and changing the font may be 1405 // redundant operations. 1406 currentValuePane.add(comp); 1407 comp.setFont(comboBox.getFont()); 1408 Dimension d = comp.getPreferredSize(); 1409 currentValuePane.remove(comp); 1410 return d; 1411 } 1412 1413 1414 // 1415 // end Size Utility Methods 1416 //============================= 1417 1418 1419 //================================= 1420 // begin Keyboard Action Management 1421 // 1422 1423 /** 1424 * Adds keyboard actions to the JComboBox. Actions on enter and esc are already 1425 * supplied. Add more actions as you need them. 1426 */ 1427 protected void installKeyboardActions() { 1428 InputMap km = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 1429 SwingUtilities.replaceUIInputMap(comboBox, JComponent. 1430 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km); 1431 1432 1433 LazyActionMap.installLazyActionMap(comboBox, BasicComboBoxUI.class, 1434 "ComboBox.actionMap"); 1435 } 1436 1437 InputMap getInputMap(int condition) { 1438 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) { 1439 return (InputMap)DefaultLookup.get(comboBox, this, 1440 "ComboBox.ancestorInputMap"); 1441 } 1442 return null; 1443 } 1444 1445 boolean isTableCellEditor() { 1446 return isTableCellEditor; 1447 } 1448 1449 /** 1450 * Removes the focus InputMap and ActionMap. 1451 */ 1452 protected void uninstallKeyboardActions() { 1453 SwingUtilities.replaceUIInputMap(comboBox, JComponent. 1454 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); 1455 SwingUtilities.replaceUIActionMap(comboBox, null); 1456 } 1457 1458 1459 // 1460 // Actions 1461 // 1462 private static class Actions extends UIAction { 1463 private static final String HIDE = "hidePopup"; 1464 private static final String DOWN = "selectNext"; 1465 private static final String DOWN_2 = "selectNext2"; 1466 private static final String TOGGLE = "togglePopup"; 1467 private static final String TOGGLE_2 = "spacePopup"; 1468 private static final String UP = "selectPrevious"; 1469 private static final String UP_2 = "selectPrevious2"; 1470 private static final String ENTER = "enterPressed"; 1471 private static final String PAGE_DOWN = "pageDownPassThrough"; 1472 private static final String PAGE_UP = "pageUpPassThrough"; 1473 private static final String HOME = "homePassThrough"; 1474 private static final String END = "endPassThrough"; 1475 1476 Actions(String name) { 1477 super(name); 1478 } 1479 1480 public void actionPerformed( ActionEvent e ) { 1481 String key = getName(); 1482 JComboBox comboBox = (JComboBox)e.getSource(); 1483 BasicComboBoxUI ui = (BasicComboBoxUI)BasicLookAndFeel.getUIOfType( 1484 comboBox.getUI(), BasicComboBoxUI.class); 1485 if (key == HIDE) { 1486 comboBox.firePopupMenuCanceled(); 1487 comboBox.setPopupVisible(false); 1488 } 1489 else if (key == PAGE_DOWN || key == PAGE_UP || 1490 key == HOME || key == END) { 1491 int index = getNextIndex(comboBox, key); 1492 if (index >= 0 && index < comboBox.getItemCount()) { 1493 comboBox.setSelectedIndex(index); 1494 } 1495 } 1496 else if (key == DOWN) { 1497 if (comboBox.isShowing() ) { 1498 if ( comboBox.isPopupVisible() ) { 1499 if (ui != null) { 1500 ui.selectNextPossibleValue(); 1501 } 1502 } else { 1503 comboBox.setPopupVisible(true); 1504 } 1505 } 1506 } 1507 else if (key == DOWN_2) { 1508 // Special case in which pressing the arrow keys will not 1509 // make the popup appear - except for editable combo boxes 1510 // and combo boxes inside a table. 1511 if (comboBox.isShowing() ) { 1512 if ( (comboBox.isEditable() || 1513 (ui != null && ui.isTableCellEditor())) 1514 && !comboBox.isPopupVisible() ) { 1515 comboBox.setPopupVisible(true); 1516 } else { 1517 if (ui != null) { 1518 ui.selectNextPossibleValue(); 1519 } 1520 } 1521 } 1522 } 1523 else if (key == TOGGLE || key == TOGGLE_2) { 1524 if (ui != null && (key == TOGGLE || !comboBox.isEditable())) { 1525 if ( ui.isTableCellEditor() ) { 1526 // Forces the selection of the list item if the 1527 // combo box is in a JTable. 1528 comboBox.setSelectedIndex(ui.popup.getList(). 1529 getSelectedIndex()); 1530 } 1531 else { 1532 comboBox.setPopupVisible(!comboBox.isPopupVisible()); 1533 } 1534 } 1535 } 1536 else if (key == UP) { 1537 if (ui != null) { 1538 if (ui.isPopupVisible(comboBox)) { 1539 ui.selectPreviousPossibleValue(); 1540 } 1541 else if (DefaultLookup.getBoolean(comboBox, ui, 1542 "ComboBox.showPopupOnNavigation", false)) { 1543 ui.setPopupVisible(comboBox, true); 1544 } 1545 } 1546 } 1547 else if (key == UP_2) { 1548 // Special case in which pressing the arrow keys will not 1549 // make the popup appear - except for editable combo boxes. 1550 if (comboBox.isShowing() && ui != null) { 1551 if ( comboBox.isEditable() && !comboBox.isPopupVisible()) { 1552 comboBox.setPopupVisible(true); 1553 } else { 1554 ui.selectPreviousPossibleValue(); 1555 } 1556 } 1557 } 1558 1559 else if (key == ENTER) { 1560 if (comboBox.isPopupVisible()) { 1561 // Forces the selection of the list item 1562 boolean isEnterSelectablePopup = 1563 UIManager.getBoolean("ComboBox.isEnterSelectablePopup"); 1564 if (!comboBox.isEditable() || isEnterSelectablePopup 1565 || ui.isTableCellEditor) { 1566 Object listItem = ui.popup.getList().getSelectedValue(); 1567 if (listItem != null) { 1568 // Use the selected value from popup 1569 // to set the selected item in combo box, 1570 // but ensure before that JComboBox.actionPerformed() 1571 // won't use editor's value to set the selected item 1572 comboBox.getEditor().setItem(listItem); 1573 comboBox.setSelectedItem(listItem); 1574 } 1575 } 1576 comboBox.setPopupVisible(false); 1577 } 1578 else { 1579 // Hide combo box if it is a table cell editor 1580 if (ui.isTableCellEditor && !comboBox.isEditable()) { 1581 comboBox.setSelectedItem(comboBox.getSelectedItem()); 1582 } 1583 // Call the default button binding. 1584 // This is a pretty messy way of passing an event through 1585 // to the root pane. 1586 JRootPane root = SwingUtilities.getRootPane(comboBox); 1587 if (root != null) { 1588 InputMap im = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 1589 ActionMap am = root.getActionMap(); 1590 if (im != null && am != null) { 1591 Object obj = im.get(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0)); 1592 if (obj != null) { 1593 Action action = am.get(obj); 1594 if (action != null) { 1595 action.actionPerformed(new ActionEvent( 1596 root, e.getID(), e.getActionCommand(), 1597 e.getWhen(), e.getModifiers())); 1598 } 1599 } 1600 } 1601 } 1602 } 1603 } 1604 } 1605 1606 private int getNextIndex(JComboBox comboBox, String key) { 1607 if (key == PAGE_UP) { 1608 int listHeight = comboBox.getMaximumRowCount(); 1609 int index = comboBox.getSelectedIndex() - listHeight; 1610 return (index < 0 ? 0: index); 1611 } 1612 else if (key == PAGE_DOWN) { 1613 int listHeight = comboBox.getMaximumRowCount(); 1614 int index = comboBox.getSelectedIndex() + listHeight; 1615 int max = comboBox.getItemCount(); 1616 return (index < max ? index: max-1); 1617 } 1618 else if (key == HOME) { 1619 return 0; 1620 } 1621 else if (key == END) { 1622 return comboBox.getItemCount() - 1; 1623 } 1624 return comboBox.getSelectedIndex(); 1625 } 1626 1627 public boolean isEnabled(Object c) { 1628 if (getName() == HIDE) { 1629 return (c != null && ((JComboBox)c).isPopupVisible()); 1630 } 1631 return true; 1632 } 1633 } 1634 // 1635 // end Keyboard Action Management 1636 //=============================== 1637 1638 1639 // 1640 // Shared Handler, implements all listeners 1641 // 1642 private class Handler implements ActionListener, FocusListener, 1643 KeyListener, LayoutManager, 1644 ListDataListener, PropertyChangeListener { 1645 // 1646 // PropertyChangeListener 1647 // 1648 public void propertyChange(PropertyChangeEvent e) { 1649 String propertyName = e.getPropertyName(); 1650 if (e.getSource() == editor){ 1651 // If the border of the editor changes then this can effect 1652 // the size of the editor which can cause the combo's size to 1653 // become invalid so we need to clear size caches 1654 if ("border".equals(propertyName)){ 1655 isMinimumSizeDirty = true; 1656 isDisplaySizeDirty = true; 1657 comboBox.revalidate(); 1658 } 1659 } else { 1660 JComboBox comboBox = (JComboBox)e.getSource(); 1661 if ( propertyName == "model" ) { 1662 ComboBoxModel newModel = (ComboBoxModel)e.getNewValue(); 1663 ComboBoxModel oldModel = (ComboBoxModel)e.getOldValue(); 1664 1665 if ( oldModel != null && listDataListener != null ) { 1666 oldModel.removeListDataListener( listDataListener ); 1667 } 1668 1669 if ( newModel != null && listDataListener != null ) { 1670 newModel.addListDataListener( listDataListener ); 1671 } 1672 1673 if ( editor != null ) { 1674 comboBox.configureEditor( comboBox.getEditor(), comboBox.getSelectedItem() ); 1675 } 1676 isMinimumSizeDirty = true; 1677 isDisplaySizeDirty = true; 1678 comboBox.revalidate(); 1679 comboBox.repaint(); 1680 } 1681 else if ( propertyName == "editor" && comboBox.isEditable() ) { 1682 addEditor(); 1683 comboBox.revalidate(); 1684 } 1685 else if ( propertyName == "editable" ) { 1686 if ( comboBox.isEditable() ) { 1687 comboBox.setRequestFocusEnabled( false ); 1688 addEditor(); 1689 } else { 1690 comboBox.setRequestFocusEnabled( true ); 1691 removeEditor(); 1692 } 1693 updateToolTipTextForChildren(); 1694 comboBox.revalidate(); 1695 } 1696 else if ( propertyName == "enabled" ) { 1697 boolean enabled = comboBox.isEnabled(); 1698 if ( editor != null ) 1699 editor.setEnabled(enabled); 1700 if ( arrowButton != null ) 1701 arrowButton.setEnabled(enabled); 1702 comboBox.repaint(); 1703 } 1704 else if ( propertyName == "focusable" ) { 1705 boolean focusable = comboBox.isFocusable(); 1706 if ( editor != null ) 1707 editor.setFocusable(focusable); 1708 if ( arrowButton != null ) 1709 arrowButton.setFocusable(focusable); 1710 comboBox.repaint(); 1711 } 1712 else if ( propertyName == "maximumRowCount" ) { 1713 if ( isPopupVisible( comboBox ) ) { 1714 setPopupVisible(comboBox, false); 1715 setPopupVisible(comboBox, true); 1716 } 1717 } 1718 else if ( propertyName == "font" ) { 1719 listBox.setFont( comboBox.getFont() ); 1720 if ( editor != null ) { 1721 editor.setFont( comboBox.getFont() ); 1722 } 1723 isMinimumSizeDirty = true; 1724 isDisplaySizeDirty = true; 1725 comboBox.validate(); 1726 } 1727 else if ( propertyName == JComponent.TOOL_TIP_TEXT_KEY ) { 1728 updateToolTipTextForChildren(); 1729 } 1730 else if ( propertyName == BasicComboBoxUI.IS_TABLE_CELL_EDITOR ) { 1731 Boolean inTable = (Boolean)e.getNewValue(); 1732 isTableCellEditor = inTable.equals(Boolean.TRUE) ? true : false; 1733 } 1734 else if (propertyName == "prototypeDisplayValue") { 1735 isMinimumSizeDirty = true; 1736 isDisplaySizeDirty = true; 1737 comboBox.revalidate(); 1738 } 1739 else if (propertyName == "renderer") { 1740 isMinimumSizeDirty = true; 1741 isDisplaySizeDirty = true; 1742 comboBox.revalidate(); 1743 } 1744 } 1745 } 1746 1747 1748 // 1749 // KeyListener 1750 // 1751 1752 // This listener checks to see if the key event isn't a navigation 1753 // key. If it finds a key event that wasn't a navigation key it 1754 // dispatches it to JComboBox.selectWithKeyChar() so that it can do 1755 // type-ahead. 1756 public void keyPressed( KeyEvent e ) { 1757 if ( isNavigationKey(e.getKeyCode(), e.getModifiers()) ) { 1758 lastTime = 0L; 1759 } else if ( comboBox.isEnabled() && comboBox.getModel().getSize()!=0 && 1760 isTypeAheadKey( e ) && e.getKeyChar() != KeyEvent.CHAR_UNDEFINED) { 1761 time = e.getWhen(); 1762 if ( comboBox.selectWithKeyChar(e.getKeyChar()) ) { 1763 e.consume(); 1764 } 1765 } 1766 } 1767 1768 public void keyTyped(KeyEvent e) { 1769 } 1770 1771 public void keyReleased(KeyEvent e) { 1772 } 1773 1774 private boolean isTypeAheadKey( KeyEvent e ) { 1775 return !e.isAltDown() && !BasicGraphicsUtils.isMenuShortcutKeyDown(e); 1776 } 1777 1778 // 1779 // FocusListener 1780 // 1781 // NOTE: The class is added to both the Editor and ComboBox. 1782 // The combo box listener hides the popup when the focus is lost. 1783 // It also repaints when focus is gained or lost. 1784 1785 public void focusGained( FocusEvent e ) { 1786 ComboBoxEditor comboBoxEditor = comboBox.getEditor(); 1787 1788 if ( (comboBoxEditor != null) && 1789 (e.getSource() == comboBoxEditor.getEditorComponent()) ) { 1790 return; 1791 } 1792 hasFocus = true; 1793 comboBox.repaint(); 1794 1795 if (comboBox.isEditable() && editor != null) { 1796 editor.requestFocus(); 1797 } 1798 } 1799 1800 public void focusLost( FocusEvent e ) { 1801 ComboBoxEditor editor = comboBox.getEditor(); 1802 if ( (editor != null) && 1803 (e.getSource() == editor.getEditorComponent()) ) { 1804 Object item = editor.getItem(); 1805 1806 Object selectedItem = comboBox.getSelectedItem(); 1807 if (!e.isTemporary() && item != null && 1808 !item.equals((selectedItem == null) ? "" : selectedItem )) { 1809 comboBox.actionPerformed 1810 (new ActionEvent(editor, 0, "", 1811 EventQueue.getMostRecentEventTime(), 0)); 1812 } 1813 } 1814 1815 hasFocus = false; 1816 if (!e.isTemporary()) { 1817 setPopupVisible(comboBox, false); 1818 } 1819 comboBox.repaint(); 1820 } 1821 1822 // 1823 // ListDataListener 1824 // 1825 1826 // This listener watches for changes in the ComboBoxModel 1827 public void contentsChanged( ListDataEvent e ) { 1828 if ( !(e.getIndex0() == -1 && e.getIndex1() == -1) ) { 1829 isMinimumSizeDirty = true; 1830 comboBox.revalidate(); 1831 } 1832 1833 // set the editor with the selected item since this 1834 // is the event handler for a selected item change. 1835 if (comboBox.isEditable() && editor != null) { 1836 comboBox.configureEditor( comboBox.getEditor(), 1837 comboBox.getSelectedItem() ); 1838 } 1839 1840 isDisplaySizeDirty = true; 1841 comboBox.repaint(); 1842 } 1843 1844 public void intervalAdded( ListDataEvent e ) { 1845 contentsChanged( e ); 1846 } 1847 1848 public void intervalRemoved( ListDataEvent e ) { 1849 contentsChanged( e ); 1850 } 1851 1852 // 1853 // LayoutManager 1854 // 1855 1856 // This layout manager handles the 'standard' layout of combo boxes. 1857 // It puts the arrow button to the right and the editor to the left. 1858 // If there is no editor it still keeps the arrow button to the right. 1859 public void addLayoutComponent(String name, Component comp) {} 1860 1861 public void removeLayoutComponent(Component comp) {} 1862 1863 public Dimension preferredLayoutSize(Container parent) { 1864 return parent.getPreferredSize(); 1865 } 1866 1867 public Dimension minimumLayoutSize(Container parent) { 1868 return parent.getMinimumSize(); 1869 } 1870 1871 public void layoutContainer(Container parent) { 1872 JComboBox cb = (JComboBox)parent; 1873 int width = cb.getWidth(); 1874 int height = cb.getHeight(); 1875 1876 Insets insets = getInsets(); 1877 int buttonHeight = height - (insets.top + insets.bottom); 1878 int buttonWidth = buttonHeight; 1879 if (arrowButton != null) { 1880 Insets arrowInsets = arrowButton.getInsets(); 1881 buttonWidth = squareButton ? 1882 buttonHeight : 1883 arrowButton.getPreferredSize().width + arrowInsets.left + arrowInsets.right; 1884 } 1885 Rectangle cvb; 1886 1887 if (arrowButton != null) { 1888 if (BasicGraphicsUtils.isLeftToRight(cb)) { 1889 arrowButton.setBounds(width - (insets.right + buttonWidth), 1890 insets.top, buttonWidth, buttonHeight); 1891 } else { 1892 arrowButton.setBounds(insets.left, insets.top, 1893 buttonWidth, buttonHeight); 1894 } 1895 } 1896 if ( editor != null ) { 1897 cvb = rectangleForCurrentValue(); 1898 editor.setBounds(cvb); 1899 } 1900 } 1901 1902 // 1903 // ActionListener 1904 // 1905 // Fix for 4515752: Forward the Enter pressed on the 1906 // editable combo box to the default button 1907 1908 // Note: This could depend on event ordering. The first ActionEvent 1909 // from the editor may be handled by the JComboBox in which case, the 1910 // enterPressed action will always be invoked. 1911 public void actionPerformed(ActionEvent evt) { 1912 Object item = comboBox.getEditor().getItem(); 1913 if (item != null) { 1914 if(!comboBox.isPopupVisible() && !item.equals(comboBox.getSelectedItem())) { 1915 comboBox.setSelectedItem(comboBox.getEditor().getItem()); 1916 } 1917 ActionMap am = comboBox.getActionMap(); 1918 if (am != null) { 1919 Action action = am.get("enterPressed"); 1920 if (action != null) { 1921 action.actionPerformed(new ActionEvent(comboBox, evt.getID(), 1922 evt.getActionCommand(), 1923 evt.getModifiers())); 1924 } 1925 } 1926 } 1927 } 1928 } 1929 1930 class DefaultKeySelectionManager implements JComboBox.KeySelectionManager, UIResource { 1931 private String prefix = ""; 1932 private String typedString = ""; 1933 1934 public int selectionForKey(char aKey,ComboBoxModel aModel) { 1935 if (lastTime == 0L) { 1936 prefix = ""; 1937 typedString = ""; 1938 } 1939 boolean startingFromSelection = true; 1940 1941 int startIndex = comboBox.getSelectedIndex(); 1942 if (time - lastTime < timeFactor) { 1943 typedString += aKey; 1944 if((prefix.length() == 1) && (aKey == prefix.charAt(0))) { 1945 // Subsequent same key presses move the keyboard focus to the next 1946 // object that starts with the same letter. 1947 startIndex++; 1948 } else { 1949 prefix = typedString; 1950 } 1951 } else { 1952 startIndex++; 1953 typedString = "" + aKey; 1954 prefix = typedString; 1955 } 1956 lastTime = time; 1957 1958 if (startIndex < 0 || startIndex >= aModel.getSize()) { 1959 startingFromSelection = false; 1960 startIndex = 0; 1961 } 1962 int index = listBox.getNextMatch(prefix, startIndex, 1963 Position.Bias.Forward); 1964 if (index < 0 && startingFromSelection) { // wrap 1965 index = listBox.getNextMatch(prefix, 0, 1966 Position.Bias.Forward); 1967 } 1968 return index; 1969 } 1970 } 1971 1972 }