001/* BasicComboBoxUI.java -- 002 Copyright (C) 2004, 2005, 2006, Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing.plaf.basic; 040 041import java.awt.Color; 042import java.awt.Component; 043import java.awt.Container; 044import java.awt.Dimension; 045import java.awt.Font; 046import java.awt.Graphics; 047import java.awt.Insets; 048import java.awt.LayoutManager; 049import java.awt.Rectangle; 050import java.awt.event.FocusEvent; 051import java.awt.event.FocusListener; 052import java.awt.event.ItemEvent; 053import java.awt.event.ItemListener; 054import java.awt.event.KeyAdapter; 055import java.awt.event.KeyEvent; 056import java.awt.event.KeyListener; 057import java.awt.event.MouseListener; 058import java.awt.event.MouseMotionListener; 059import java.beans.PropertyChangeEvent; 060import java.beans.PropertyChangeListener; 061 062import javax.accessibility.Accessible; 063import javax.accessibility.AccessibleContext; 064import javax.swing.CellRendererPane; 065import javax.swing.ComboBoxEditor; 066import javax.swing.ComboBoxModel; 067import javax.swing.DefaultListCellRenderer; 068import javax.swing.InputMap; 069import javax.swing.JButton; 070import javax.swing.JComboBox; 071import javax.swing.JComponent; 072import javax.swing.JList; 073import javax.swing.ListCellRenderer; 074import javax.swing.LookAndFeel; 075import javax.swing.SwingUtilities; 076import javax.swing.UIManager; 077import javax.swing.event.ListDataEvent; 078import javax.swing.event.ListDataListener; 079import javax.swing.plaf.ComboBoxUI; 080import javax.swing.plaf.ComponentUI; 081import javax.swing.plaf.UIResource; 082 083/** 084 * A UI delegate for the {@link JComboBox} component. 085 * 086 * @author Olga Rodimina 087 * @author Robert Schuster 088 */ 089public class BasicComboBoxUI extends ComboBoxUI 090{ 091 /** 092 * The arrow button that is displayed in the right side of JComboBox. This 093 * button is used to hide and show combo box's list of items. 094 */ 095 protected JButton arrowButton; 096 097 /** 098 * The combo box represented by this UI delegate. 099 */ 100 protected JComboBox comboBox; 101 102 /** 103 * The component that is responsible for displaying/editing the selected 104 * item of the combo box. 105 * 106 * @see BasicComboBoxEditor#getEditorComponent() 107 */ 108 protected Component editor; 109 110 /** 111 * A listener listening to focus events occurring in the {@link JComboBox}. 112 */ 113 protected FocusListener focusListener; 114 115 /** 116 * A flag indicating whether JComboBox currently has the focus. 117 */ 118 protected boolean hasFocus; 119 120 /** 121 * A listener listening to item events fired by the {@link JComboBox}. 122 */ 123 protected ItemListener itemListener; 124 125 /** 126 * A listener listening to key events that occur while {@link JComboBox} has 127 * the focus. 128 */ 129 protected KeyListener keyListener; 130 131 /** 132 * List used when rendering selected item of the combo box. The selection 133 * and foreground colors for combo box renderer are configured from this 134 * list. 135 */ 136 protected JList listBox; 137 138 /** 139 * ListDataListener listening to JComboBox model 140 */ 141 protected ListDataListener listDataListener; 142 143 /** 144 * Popup list containing the combo box's menu items. 145 */ 146 protected ComboPopup popup; 147 148 protected KeyListener popupKeyListener; 149 150 protected MouseListener popupMouseListener; 151 152 protected MouseMotionListener popupMouseMotionListener; 153 154 /** 155 * Listener listening to changes in the bound properties of JComboBox 156 */ 157 protected PropertyChangeListener propertyChangeListener; 158 159 /* Size of the largest item in the comboBox 160 * This is package-private to avoid an accessor method. 161 */ 162 Dimension displaySize = new Dimension(); 163 164 /** 165 * Used to render the combo box values. 166 */ 167 protected CellRendererPane currentValuePane; 168 169 /** 170 * The current minimum size if isMinimumSizeDirty is false. 171 * Setup by getMinimumSize() and invalidated by the various listeners. 172 */ 173 protected Dimension cachedMinimumSize; 174 175 /** 176 * Indicates whether or not the cachedMinimumSize field is valid or not. 177 */ 178 protected boolean isMinimumSizeDirty = true; 179 180 /** 181 * Creates a new <code>BasicComboBoxUI</code> object. 182 */ 183 public BasicComboBoxUI() 184 { 185 currentValuePane = new CellRendererPane(); 186 cachedMinimumSize = new Dimension(); 187 } 188 189 /** 190 * A factory method to create a UI delegate for the given 191 * {@link JComponent}, which should be a {@link JComboBox}. 192 * 193 * @param c The {@link JComponent} a UI is being created for. 194 * 195 * @return A UI delegate for the {@link JComponent}. 196 */ 197 public static ComponentUI createUI(JComponent c) 198 { 199 return new BasicComboBoxUI(); 200 } 201 202 /** 203 * Installs the UI for the given {@link JComponent}. 204 * 205 * @param c the JComponent to install a UI for. 206 * 207 * @see #uninstallUI(JComponent) 208 */ 209 public void installUI(JComponent c) 210 { 211 super.installUI(c); 212 213 if (c instanceof JComboBox) 214 { 215 isMinimumSizeDirty = true; 216 comboBox = (JComboBox) c; 217 installDefaults(); 218 popup = createPopup(); 219 listBox = popup.getList(); 220 221 // Set editor and renderer for the combo box. Editor is used 222 // only if combo box becomes editable, otherwise renderer is used 223 // to paint the selected item; combobox is not editable by default. 224 ListCellRenderer renderer = comboBox.getRenderer(); 225 if (renderer == null || renderer instanceof UIResource) 226 comboBox.setRenderer(createRenderer()); 227 228 ComboBoxEditor currentEditor = comboBox.getEditor(); 229 if (currentEditor == null || currentEditor instanceof UIResource) 230 { 231 currentEditor = createEditor(); 232 comboBox.setEditor(currentEditor); 233 } 234 235 installComponents(); 236 installListeners(); 237 comboBox.setLayout(createLayoutManager()); 238 comboBox.setFocusable(true); 239 installKeyboardActions(); 240 comboBox.putClientProperty(BasicLookAndFeel.DONT_CANCEL_POPUP, 241 Boolean.TRUE); 242 } 243 } 244 245 /** 246 * Uninstalls the UI for the given {@link JComponent}. 247 * 248 * @param c The JComponent that is having this UI removed. 249 * 250 * @see #installUI(JComponent) 251 */ 252 public void uninstallUI(JComponent c) 253 { 254 setPopupVisible(comboBox, false); 255 popup.uninstallingUI(); 256 uninstallKeyboardActions(); 257 comboBox.setLayout(null); 258 uninstallComponents(); 259 uninstallListeners(); 260 uninstallDefaults(); 261 comboBox = null; 262 } 263 264 /** 265 * Installs the defaults that are defined in the {@link BasicLookAndFeel} 266 * for this {@link JComboBox}. 267 * 268 * @see #uninstallDefaults() 269 */ 270 protected void installDefaults() 271 { 272 LookAndFeel.installColorsAndFont(comboBox, "ComboBox.background", 273 "ComboBox.foreground", "ComboBox.font"); 274 LookAndFeel.installBorder(comboBox, "ComboBox.border"); 275 } 276 277 /** 278 * Creates and installs the listeners for this UI. 279 * 280 * @see #uninstallListeners() 281 */ 282 protected void installListeners() 283 { 284 // install combo box's listeners 285 propertyChangeListener = createPropertyChangeListener(); 286 comboBox.addPropertyChangeListener(propertyChangeListener); 287 288 focusListener = createFocusListener(); 289 comboBox.addFocusListener(focusListener); 290 291 itemListener = createItemListener(); 292 comboBox.addItemListener(itemListener); 293 294 keyListener = createKeyListener(); 295 comboBox.addKeyListener(keyListener); 296 297 // install listeners that listen to combo box model 298 listDataListener = createListDataListener(); 299 comboBox.getModel().addListDataListener(listDataListener); 300 301 // Install mouse and key listeners from the popup. 302 popupMouseListener = popup.getMouseListener(); 303 comboBox.addMouseListener(popupMouseListener); 304 305 popupMouseMotionListener = popup.getMouseMotionListener(); 306 comboBox.addMouseMotionListener(popupMouseMotionListener); 307 308 popupKeyListener = popup.getKeyListener(); 309 comboBox.addKeyListener(popupKeyListener); 310 } 311 312 /** 313 * Uninstalls the defaults and sets any objects created during 314 * install to <code>null</code>. 315 * 316 * @see #installDefaults() 317 */ 318 protected void uninstallDefaults() 319 { 320 if (comboBox.getFont() instanceof UIResource) 321 comboBox.setFont(null); 322 323 if (comboBox.getForeground() instanceof UIResource) 324 comboBox.setForeground(null); 325 326 if (comboBox.getBackground() instanceof UIResource) 327 comboBox.setBackground(null); 328 329 LookAndFeel.uninstallBorder(comboBox); 330 } 331 332 /** 333 * Detaches all the listeners we attached in {@link #installListeners}. 334 * 335 * @see #installListeners() 336 */ 337 protected void uninstallListeners() 338 { 339 comboBox.removePropertyChangeListener(propertyChangeListener); 340 propertyChangeListener = null; 341 342 comboBox.removeFocusListener(focusListener); 343 listBox.removeFocusListener(focusListener); 344 focusListener = null; 345 346 comboBox.removeItemListener(itemListener); 347 itemListener = null; 348 349 comboBox.removeKeyListener(keyListener); 350 keyListener = null; 351 352 comboBox.getModel().removeListDataListener(listDataListener); 353 listDataListener = null; 354 355 if (popupMouseListener != null) 356 comboBox.removeMouseListener(popupMouseListener); 357 popupMouseListener = null; 358 359 if (popupMouseMotionListener != null) 360 comboBox.removeMouseMotionListener(popupMouseMotionListener); 361 popupMouseMotionListener = null; 362 363 if (popupKeyListener != null) 364 comboBox.removeKeyListener(popupKeyListener); 365 popupKeyListener = null; 366 } 367 368 /** 369 * Creates the popup that will contain list of combo box's items. 370 * 371 * @return popup containing list of combo box's items 372 */ 373 protected ComboPopup createPopup() 374 { 375 return new BasicComboPopup(comboBox); 376 } 377 378 /** 379 * Creates a {@link KeyListener} to listen to key events. 380 * 381 * @return KeyListener that listens to key events. 382 */ 383 protected KeyListener createKeyListener() 384 { 385 return new KeyHandler(); 386 } 387 388 /** 389 * Creates the {@link FocusListener} that will listen to changes in this 390 * JComboBox's focus. 391 * 392 * @return the FocusListener. 393 */ 394 protected FocusListener createFocusListener() 395 { 396 return new FocusHandler(); 397 } 398 399 /** 400 * Creates a {@link ListDataListener} to listen to the combo box's data model. 401 * 402 * @return The new listener. 403 */ 404 protected ListDataListener createListDataListener() 405 { 406 return new ListDataHandler(); 407 } 408 409 /** 410 * Creates an {@link ItemListener} that will listen to the changes in 411 * the JComboBox's selection. 412 * 413 * @return The ItemListener 414 */ 415 protected ItemListener createItemListener() 416 { 417 return new ItemHandler(); 418 } 419 420 /** 421 * Creates a {@link PropertyChangeListener} to listen to the changes in 422 * the JComboBox's bound properties. 423 * 424 * @return The PropertyChangeListener 425 */ 426 protected PropertyChangeListener createPropertyChangeListener() 427 { 428 return new PropertyChangeHandler(); 429 } 430 431 /** 432 * Creates and returns a layout manager for the combo box. Subclasses can 433 * override this method to provide a different layout. 434 * 435 * @return a layout manager for the combo box. 436 */ 437 protected LayoutManager createLayoutManager() 438 { 439 return new ComboBoxLayoutManager(); 440 } 441 442 /** 443 * Creates a component that will be responsible for rendering the 444 * selected component in the combo box. 445 * 446 * @return A renderer for the combo box. 447 */ 448 protected ListCellRenderer createRenderer() 449 { 450 return new BasicComboBoxRenderer.UIResource(); 451 } 452 453 /** 454 * Creates the component that will be responsible for displaying/editing 455 * the selected item in the combo box. This editor is used only when combo 456 * box is editable. 457 * 458 * @return A new component that will be responsible for displaying/editing 459 * the selected item in the combo box. 460 */ 461 protected ComboBoxEditor createEditor() 462 { 463 return new BasicComboBoxEditor.UIResource(); 464 } 465 466 /** 467 * Installs the components for this JComboBox. ArrowButton, main 468 * part of combo box (upper part) and popup list of items are created and 469 * configured here. 470 */ 471 protected void installComponents() 472 { 473 // create and install arrow button 474 arrowButton = createArrowButton(); 475 comboBox.add(arrowButton); 476 if (arrowButton != null) 477 configureArrowButton(); 478 479 if (comboBox.isEditable()) 480 addEditor(); 481 482 comboBox.add(currentValuePane); 483 } 484 485 /** 486 * Uninstalls components from this {@link JComboBox}. 487 * 488 * @see #installComponents() 489 */ 490 protected void uninstallComponents() 491 { 492 // Unconfigure arrow button. 493 if (arrowButton != null) 494 { 495 unconfigureArrowButton(); 496 } 497 498 // Unconfigure editor. 499 if (editor != null) 500 { 501 unconfigureEditor(); 502 } 503 504 comboBox.removeAll(); 505 arrowButton = null; 506 } 507 508 /** 509 * Adds the current editor to the combo box. 510 */ 511 public void addEditor() 512 { 513 removeEditor(); 514 editor = comboBox.getEditor().getEditorComponent(); 515 if (editor != null) 516 { 517 configureEditor(); 518 comboBox.add(editor); 519 } 520 } 521 522 /** 523 * Removes the current editor from the combo box. 524 */ 525 public void removeEditor() 526 { 527 if (editor != null) 528 { 529 unconfigureEditor(); 530 comboBox.remove(editor); 531 } 532 } 533 534 /** 535 * Configures the editor for this combo box. 536 */ 537 protected void configureEditor() 538 { 539 editor.setFont(comboBox.getFont()); 540 if (popupKeyListener != null) 541 editor.addKeyListener(popupKeyListener); 542 if (keyListener != null) 543 editor.addKeyListener(keyListener); 544 comboBox.configureEditor(comboBox.getEditor(), 545 comboBox.getSelectedItem()); 546 } 547 548 /** 549 * Unconfigures the editor for this combo box. 550 */ 551 protected void unconfigureEditor() 552 { 553 if (popupKeyListener != null) 554 editor.removeKeyListener(popupKeyListener); 555 if (keyListener != null) 556 editor.removeKeyListener(keyListener); 557 } 558 559 /** 560 * Configures the arrow button. 561 * 562 * @see #configureArrowButton() 563 */ 564 public void configureArrowButton() 565 { 566 if (arrowButton != null) 567 { 568 arrowButton.setEnabled(comboBox.isEnabled()); 569 arrowButton.setFocusable(false); 570 arrowButton.addMouseListener(popup.getMouseListener()); 571 arrowButton.addMouseMotionListener(popup.getMouseMotionListener()); 572 573 // Mark the button as not closing the popup, we handle this ourselves. 574 arrowButton.putClientProperty(BasicLookAndFeel.DONT_CANCEL_POPUP, 575 Boolean.TRUE); 576 } 577 } 578 579 /** 580 * Unconfigures the arrow button. 581 * 582 * @see #configureArrowButton() 583 * 584 * @specnote The specification says this method is implementation specific 585 * and should not be used or overridden. 586 */ 587 public void unconfigureArrowButton() 588 { 589 if (arrowButton != null) 590 { 591 if (popupMouseListener != null) 592 arrowButton.removeMouseListener(popupMouseListener); 593 if (popupMouseMotionListener != null) 594 arrowButton.removeMouseMotionListener(popupMouseMotionListener); 595 } 596 } 597 598 /** 599 * Creates an arrow button for this {@link JComboBox}. The arrow button is 600 * displayed at the right end of the combo box and is used to display/hide 601 * the drop down list of items. 602 * 603 * @return A new button. 604 */ 605 protected JButton createArrowButton() 606 { 607 return new BasicArrowButton(BasicArrowButton.SOUTH); 608 } 609 610 /** 611 * Returns <code>true</code> if the popup is visible, and <code>false</code> 612 * otherwise. 613 * 614 * @param c The JComboBox to check 615 * 616 * @return <code>true</code> if popup part of the JComboBox is visible and 617 * <code>false</code> otherwise. 618 */ 619 public boolean isPopupVisible(JComboBox c) 620 { 621 return popup.isVisible(); 622 } 623 624 /** 625 * Displays/hides the {@link JComboBox}'s list of items on the screen. 626 * 627 * @param c The combo box, for which list of items should be 628 * displayed/hidden 629 * @param v true if show popup part of the jcomboBox and false to hide. 630 */ 631 public void setPopupVisible(JComboBox c, boolean v) 632 { 633 if (v) 634 popup.show(); 635 else 636 popup.hide(); 637 } 638 639 /** 640 * JComboBox is focus traversable if it is editable and not otherwise. 641 * 642 * @param c combo box for which to check whether it is focus traversable 643 * 644 * @return true if focus tranversable and false otherwise 645 */ 646 public boolean isFocusTraversable(JComboBox c) 647 { 648 if (!comboBox.isEditable()) 649 return true; 650 651 return false; 652 } 653 654 /** 655 * Paints given menu item using specified graphics context 656 * 657 * @param g The graphics context used to paint this combo box 658 * @param c comboBox which needs to be painted. 659 */ 660 public void paint(Graphics g, JComponent c) 661 { 662 hasFocus = comboBox.hasFocus(); 663 if (! comboBox.isEditable()) 664 { 665 Rectangle rect = rectangleForCurrentValue(); 666 paintCurrentValueBackground(g, rect, hasFocus); 667 paintCurrentValue(g, rect, hasFocus); 668 } 669 } 670 671 /** 672 * Returns preferred size for the combo box. 673 * 674 * @param c comboBox for which to get preferred size 675 * 676 * @return The preferred size for the given combo box 677 */ 678 public Dimension getPreferredSize(JComponent c) 679 { 680 return getMinimumSize(c); 681 } 682 683 /** 684 * Returns the minimum size for this {@link JComboBox} for this 685 * look and feel. Also makes sure cachedMinimimSize is setup correctly. 686 * 687 * @param c The {@link JComponent} to find the minimum size for. 688 * 689 * @return The dimensions of the minimum size. 690 */ 691 public Dimension getMinimumSize(JComponent c) 692 { 693 if (isMinimumSizeDirty) 694 { 695 Insets i = getInsets(); 696 Dimension d = getDisplaySize(); 697 d.width += i.left + i.right + d.height; 698 cachedMinimumSize = new Dimension(d.width, d.height + i.top + i.bottom); 699 isMinimumSizeDirty = false; 700 } 701 return new Dimension(cachedMinimumSize); 702 } 703 704 /** 705 * Returns the maximum size for this {@link JComboBox} for this 706 * look and feel. 707 * 708 * @param c The {@link JComponent} to find the maximum size for 709 * 710 * @return The maximum size (<code>Dimension(32767, 32767)</code>). 711 */ 712 public Dimension getMaximumSize(JComponent c) 713 { 714 return new Dimension(32767, 32767); 715 } 716 717 /** 718 * Returns the number of accessible children of the combobox. 719 * 720 * @param c the component (combobox) to check, ignored 721 * 722 * @return the number of accessible children of the combobox 723 */ 724 public int getAccessibleChildrenCount(JComponent c) 725 { 726 int count = 1; 727 if (comboBox.isEditable()) 728 count = 2; 729 return count; 730 } 731 732 /** 733 * Returns the accessible child with the specified index. 734 * 735 * @param c the component, this is ignored 736 * @param i the index of the accessible child to return 737 */ 738 public Accessible getAccessibleChild(JComponent c, int i) 739 { 740 Accessible child = null; 741 switch (i) 742 { 743 case 0: // The popup. 744 if (popup instanceof Accessible) 745 { 746 AccessibleContext ctx = ((Accessible) popup).getAccessibleContext(); 747 ctx.setAccessibleParent(comboBox); 748 child = (Accessible) popup; 749 } 750 break; 751 case 1: // The editor, if any. 752 if (comboBox.isEditable() && editor instanceof Accessible) 753 { 754 AccessibleContext ctx = 755 ((Accessible) editor).getAccessibleContext(); 756 ctx.setAccessibleParent(comboBox); 757 child = (Accessible) editor; 758 } 759 break; 760 } 761 return child; 762 } 763 764 /** 765 * Returns true if the specified key is a navigation key and false otherwise 766 * 767 * @param keyCode a key for which to check whether it is navigation key or 768 * not. 769 * 770 * @return true if the specified key is a navigation key and false otherwis 771 */ 772 protected boolean isNavigationKey(int keyCode) 773 { 774 return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN 775 || keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT 776 || keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_ESCAPE 777 || keyCode == KeyEvent.VK_TAB; 778 } 779 780 /** 781 * Selects next possible item relative to the current selection 782 * to be next selected item in the combo box. 783 */ 784 protected void selectNextPossibleValue() 785 { 786 int index = comboBox.getSelectedIndex(); 787 if (index != comboBox.getItemCount() - 1) 788 comboBox.setSelectedIndex(index + 1); 789 } 790 791 /** 792 * Selects previous item relative to current selection to be 793 * next selected item. 794 */ 795 protected void selectPreviousPossibleValue() 796 { 797 int index = comboBox.getSelectedIndex(); 798 if (index > 0) 799 comboBox.setSelectedIndex(index - 1); 800 } 801 802 /** 803 * Displays combo box popup if the popup is not currently shown 804 * on the screen and hides it if it is currently shown 805 */ 806 protected void toggleOpenClose() 807 { 808 setPopupVisible(comboBox, ! isPopupVisible(comboBox)); 809 } 810 811 /** 812 * Returns the bounds in which comboBox's selected item will be 813 * displayed. 814 * 815 * @return rectangle bounds in which comboBox's selected Item will be 816 * displayed 817 */ 818 protected Rectangle rectangleForCurrentValue() 819 { 820 int w = comboBox.getWidth(); 821 int h = comboBox.getHeight(); 822 Insets i = comboBox.getInsets(); 823 int arrowSize = h - (i.top + i.bottom); 824 if (arrowButton != null) 825 arrowSize = arrowButton.getWidth(); 826 return new Rectangle(i.left, i.top, w - (i.left + i.right + arrowSize), 827 h - (i.top + i.left)); 828 } 829 830 /** 831 * Returns the insets of the current border. 832 * 833 * @return Insets representing space between combo box and its border 834 */ 835 protected Insets getInsets() 836 { 837 return comboBox.getInsets(); 838 } 839 840 /** 841 * Paints currently selected value in the main part of the combo 842 * box (part without popup). 843 * 844 * @param g graphics context 845 * @param bounds Rectangle representing the size of the area in which 846 * selected item should be drawn 847 * @param hasFocus true if combo box has focus and false otherwise 848 */ 849 public void paintCurrentValue(Graphics g, Rectangle bounds, boolean hasFocus) 850 { 851 /* Gets the component to be drawn for the current value. 852 * If there is currently no selected item we will take an empty 853 * String as replacement. 854 */ 855 ListCellRenderer renderer = comboBox.getRenderer(); 856 if (comboBox.getSelectedIndex() != -1) 857 { 858 Component comp; 859 if (hasFocus && ! isPopupVisible(comboBox)) 860 { 861 comp = renderer.getListCellRendererComponent(listBox, 862 comboBox.getSelectedItem(), -1, true, false); 863 } 864 else 865 { 866 comp = renderer.getListCellRendererComponent(listBox, 867 comboBox.getSelectedItem(), -1, false, false); 868 Color bg = UIManager.getColor("ComboBox.disabledForeground"); 869 comp.setBackground(bg); 870 } 871 comp.setFont(comboBox.getFont()); 872 if (hasFocus && ! isPopupVisible(comboBox)) 873 { 874 comp.setForeground(listBox.getSelectionForeground()); 875 comp.setBackground(listBox.getSelectionBackground()); 876 } 877 else if (comboBox.isEnabled()) 878 { 879 comp.setForeground(comboBox.getForeground()); 880 comp.setBackground(comboBox.getBackground()); 881 } 882 else 883 { 884 Color fg = UIManager.getColor("ComboBox.disabledForeground"); 885 comp.setForeground(fg); 886 Color bg = UIManager.getColor("ComboBox.disabledBackground"); 887 comp.setBackground(bg); 888 } 889 currentValuePane.paintComponent(g, comp, comboBox, bounds.x, bounds.y, 890 bounds.width, bounds.height); 891 } 892 } 893 894 /** 895 * Paints the background of part of the combo box, where currently 896 * selected value is displayed. If the combo box has focus this method 897 * should also paint focus rectangle around the combo box. 898 * 899 * @param g graphics context 900 * @param bounds Rectangle representing the size of the largest item in the 901 * comboBox 902 * @param hasFocus true if combo box has fox and false otherwise 903 */ 904 public void paintCurrentValueBackground(Graphics g, Rectangle bounds, 905 boolean hasFocus) 906 { 907 Color saved = g.getColor(); 908 if (comboBox.isEnabled()) 909 g.setColor(UIManager.getColor("UIManager.background")); 910 else 911 g.setColor(UIManager.getColor("UIManager.disabledBackground")); 912 g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); 913 g.setColor(saved); 914 } 915 916 private static final ListCellRenderer DEFAULT_RENDERER 917 = new DefaultListCellRenderer(); 918 919 /** 920 * Returns the default size for the display area of a combo box that does 921 * not contain any elements. This method returns the width and height of 922 * a single space in the current font, plus a margin of 1 pixel. 923 * 924 * @return The default display size. 925 * 926 * @see #getDisplaySize() 927 */ 928 protected Dimension getDefaultSize() 929 { 930 Component comp = DEFAULT_RENDERER.getListCellRendererComponent(listBox, 931 " ", -1, false, false); 932 currentValuePane.add(comp); 933 comp.setFont(comboBox.getFont()); 934 Dimension d = comp.getPreferredSize(); 935 currentValuePane.remove(comp); 936 return d; 937 } 938 939 /** 940 * Returns the size of the display area for the combo box. This size will be 941 * the size of the combo box, not including the arrowButton. 942 * 943 * @return The size of the display area for the combo box. 944 */ 945 protected Dimension getDisplaySize() 946 { 947 Dimension dim = new Dimension(); 948 ListCellRenderer renderer = comboBox.getRenderer(); 949 if (renderer == null) 950 { 951 renderer = DEFAULT_RENDERER; 952 } 953 954 Object prototype = comboBox.getPrototypeDisplayValue(); 955 if (prototype != null) 956 { 957 Component comp = renderer.getListCellRendererComponent(listBox, 958 prototype, -1, false, false); 959 currentValuePane.add(comp); 960 comp.setFont(comboBox.getFont()); 961 Dimension renderSize = comp.getPreferredSize(); 962 currentValuePane.remove(comp); 963 dim.height = renderSize.height; 964 dim.width = renderSize.width; 965 } 966 else 967 { 968 ComboBoxModel model = comboBox.getModel(); 969 int size = model.getSize(); 970 if (size > 0) 971 { 972 for (int i = 0; i < size; ++i) 973 { 974 Component comp = renderer.getListCellRendererComponent(listBox, 975 model.getElementAt(i), -1, false, false); 976 currentValuePane.add(comp); 977 comp.setFont(comboBox.getFont()); 978 Dimension renderSize = comp.getPreferredSize(); 979 currentValuePane.remove(comp); 980 dim.width = Math.max(dim.width, renderSize.width); 981 dim.height = Math.max(dim.height, renderSize.height); 982 } 983 } 984 else 985 { 986 dim = getDefaultSize(); 987 if (comboBox.isEditable()) 988 dim.width = 100; 989 } 990 } 991 if (comboBox.isEditable()) 992 { 993 Dimension editSize = editor.getPreferredSize(); 994 dim.width = Math.max(dim.width, editSize.width); 995 dim.height = Math.max(dim.height, editSize.height); 996 } 997 displaySize.setSize(dim.width, dim.height); 998 return dim; 999 } 1000 1001 /** 1002 * Installs the keyboard actions for the {@link JComboBox} as specified 1003 * by the look and feel. 1004 */ 1005 protected void installKeyboardActions() 1006 { 1007 SwingUtilities.replaceUIInputMap(comboBox, 1008 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, 1009 (InputMap) UIManager.get("ComboBox.ancestorInputMap")); 1010 // Install any action maps here. 1011 } 1012 1013 /** 1014 * Uninstalls the keyboard actions for the {@link JComboBox} there were 1015 * installed by in {@link #installListeners}. 1016 */ 1017 protected void uninstallKeyboardActions() 1018 { 1019 SwingUtilities.replaceUIInputMap(comboBox, 1020 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); 1021 // Uninstall any action maps here. 1022 } 1023 1024 /** 1025 * A {@link LayoutManager} used to position the sub-components of the 1026 * {@link JComboBox}. 1027 * 1028 * @see BasicComboBoxUI#createLayoutManager() 1029 */ 1030 public class ComboBoxLayoutManager implements LayoutManager 1031 { 1032 /** 1033 * Creates a new ComboBoxLayoutManager object. 1034 */ 1035 public ComboBoxLayoutManager() 1036 { 1037 // Nothing to do here. 1038 } 1039 1040 /** 1041 * Adds a component to the layout. This method does nothing, since the 1042 * layout manager doesn't need to track the components. 1043 * 1044 * @param name the name to associate the component with (ignored). 1045 * @param comp the component (ignored). 1046 */ 1047 public void addLayoutComponent(String name, Component comp) 1048 { 1049 // Do nothing 1050 } 1051 1052 /** 1053 * Removes a component from the layout. This method does nothing, since 1054 * the layout manager doesn't need to track the components. 1055 * 1056 * @param comp the component. 1057 */ 1058 public void removeLayoutComponent(Component comp) 1059 { 1060 // Do nothing 1061 } 1062 1063 /** 1064 * Returns preferred layout size of the JComboBox. 1065 * 1066 * @param parent the Container for which the preferred size should be 1067 * calculated. 1068 * 1069 * @return The preferred size for the given container 1070 */ 1071 public Dimension preferredLayoutSize(Container parent) 1072 { 1073 return parent.getPreferredSize(); 1074 } 1075 1076 /** 1077 * Returns the minimum layout size. 1078 * 1079 * @param parent the container. 1080 * 1081 * @return The minimum size. 1082 */ 1083 public Dimension minimumLayoutSize(Container parent) 1084 { 1085 return parent.getMinimumSize(); 1086 } 1087 1088 /** 1089 * Arranges the components in the container. It puts arrow 1090 * button right end part of the comboBox. If the comboBox is editable 1091 * then editor is placed to the left of arrow button, starting from the 1092 * beginning. 1093 * 1094 * @param parent Container that should be layed out. 1095 */ 1096 public void layoutContainer(Container parent) 1097 { 1098 // Position editor component to the left of arrow button if combo box is 1099 // editable 1100 Insets i = getInsets(); 1101 int arrowSize = comboBox.getHeight() - (i.top + i.bottom); 1102 1103 if (arrowButton != null) 1104 arrowButton.setBounds(comboBox.getWidth() - (i.right + arrowSize), 1105 i.top, arrowSize, arrowSize); 1106 if (editor != null) 1107 editor.setBounds(rectangleForCurrentValue()); 1108 } 1109 } 1110 1111 /** 1112 * Handles focus changes occuring in the combo box. This class is 1113 * responsible for repainting combo box whenever focus is gained or lost 1114 * and also for hiding popup list of items whenever combo box loses its 1115 * focus. 1116 */ 1117 public class FocusHandler extends Object implements FocusListener 1118 { 1119 /** 1120 * Creates a new FocusHandler object. 1121 */ 1122 public FocusHandler() 1123 { 1124 // Nothing to do here. 1125 } 1126 1127 /** 1128 * Invoked when combo box gains focus. It repaints main 1129 * part of combo box accordingly. 1130 * 1131 * @param e the FocusEvent 1132 */ 1133 public void focusGained(FocusEvent e) 1134 { 1135 hasFocus = true; 1136 comboBox.repaint(); 1137 } 1138 1139 /** 1140 * Invoked when the combo box loses focus. It repaints the main part 1141 * of the combo box accordingly and hides the popup list of items. 1142 * 1143 * @param e the FocusEvent 1144 */ 1145 public void focusLost(FocusEvent e) 1146 { 1147 hasFocus = false; 1148 if (! e.isTemporary() && comboBox.isLightWeightPopupEnabled()) 1149 setPopupVisible(comboBox, false); 1150 comboBox.repaint(); 1151 } 1152 } 1153 1154 /** 1155 * Handles {@link ItemEvent}s fired by the {@link JComboBox} when its 1156 * selected item changes. 1157 */ 1158 public class ItemHandler extends Object implements ItemListener 1159 { 1160 /** 1161 * Creates a new ItemHandler object. 1162 */ 1163 public ItemHandler() 1164 { 1165 // Nothing to do here. 1166 } 1167 1168 /** 1169 * Invoked when selected item becomes deselected or when 1170 * new item becomes selected. 1171 * 1172 * @param e the ItemEvent representing item's state change. 1173 */ 1174 public void itemStateChanged(ItemEvent e) 1175 { 1176 ComboBoxModel model = comboBox.getModel(); 1177 Object v = model.getSelectedItem(); 1178 if (editor != null) 1179 comboBox.configureEditor(comboBox.getEditor(), v); 1180 comboBox.repaint(); 1181 } 1182 } 1183 1184 /** 1185 * KeyHandler handles key events occuring while JComboBox has focus. 1186 */ 1187 public class KeyHandler extends KeyAdapter 1188 { 1189 public KeyHandler() 1190 { 1191 // Nothing to do here. 1192 } 1193 1194 /** 1195 * Invoked whenever key is pressed while JComboBox is in focus. 1196 */ 1197 public void keyPressed(KeyEvent e) 1198 { 1199 if (comboBox.getModel().getSize() != 0 && comboBox.isEnabled()) 1200 { 1201 if (! isNavigationKey(e.getKeyCode())) 1202 { 1203 if (! comboBox.isEditable()) 1204 if (comboBox.selectWithKeyChar(e.getKeyChar())) 1205 e.consume(); 1206 } 1207 else 1208 { 1209 if (e.getKeyCode() == KeyEvent.VK_UP && comboBox.isPopupVisible()) 1210 selectPreviousPossibleValue(); 1211 else if (e.getKeyCode() == KeyEvent.VK_DOWN) 1212 { 1213 if (comboBox.isPopupVisible()) 1214 selectNextPossibleValue(); 1215 else 1216 comboBox.showPopup(); 1217 } 1218 else if (e.getKeyCode() == KeyEvent.VK_ENTER 1219 || e.getKeyCode() == KeyEvent.VK_ESCAPE) 1220 popup.hide(); 1221 } 1222 } 1223 } 1224 } 1225 1226 /** 1227 * Handles the changes occurring in the JComboBox's data model. 1228 */ 1229 public class ListDataHandler extends Object implements ListDataListener 1230 { 1231 /** 1232 * Creates a new ListDataHandler object. 1233 */ 1234 public ListDataHandler() 1235 { 1236 // Nothing to do here. 1237 } 1238 1239 /** 1240 * Invoked if the content's of JComboBox's data model are changed. 1241 * 1242 * @param e ListDataEvent describing the change. 1243 */ 1244 public void contentsChanged(ListDataEvent e) 1245 { 1246 if (e.getIndex0() != -1 || e.getIndex1() != -1) 1247 { 1248 isMinimumSizeDirty = true; 1249 comboBox.revalidate(); 1250 } 1251 if (editor != null) 1252 comboBox.configureEditor(comboBox.getEditor(), 1253 comboBox.getSelectedItem()); 1254 comboBox.repaint(); 1255 } 1256 1257 /** 1258 * Invoked when items are added to the JComboBox's data model. 1259 * 1260 * @param e ListDataEvent describing the change. 1261 */ 1262 public void intervalAdded(ListDataEvent e) 1263 { 1264 int start = e.getIndex0(); 1265 int end = e.getIndex1(); 1266 if (start == 0 && comboBox.getItemCount() - (end - start + 1) == 0) 1267 contentsChanged(e); 1268 else if (start != -1 || end != -1) 1269 { 1270 ListCellRenderer renderer = comboBox.getRenderer(); 1271 ComboBoxModel model = comboBox.getModel(); 1272 int w = displaySize.width; 1273 int h = displaySize.height; 1274 // TODO: Optimize using prototype here. 1275 for (int i = start; i <= end; ++i) 1276 { 1277 Component comp = renderer.getListCellRendererComponent(listBox, 1278 model.getElementAt(i), -1, false, false); 1279 currentValuePane.add(comp); 1280 comp.setFont(comboBox.getFont()); 1281 Dimension dim = comp.getPreferredSize(); 1282 w = Math.max(w, dim.width); 1283 h = Math.max(h, dim.height); 1284 currentValuePane.remove(comp); 1285 } 1286 if (displaySize.width < w || displaySize.height < h) 1287 { 1288 if (displaySize.width < w) 1289 displaySize.width = w; 1290 if (displaySize.height < h) 1291 displaySize.height = h; 1292 comboBox.revalidate(); 1293 if (editor != null) 1294 { 1295 comboBox.configureEditor(comboBox.getEditor(), 1296 comboBox.getSelectedItem()); 1297 } 1298 } 1299 } 1300 1301 } 1302 1303 /** 1304 * Invoked when items are removed from the JComboBox's 1305 * data model. 1306 * 1307 * @param e ListDataEvent describing the change. 1308 */ 1309 public void intervalRemoved(ListDataEvent e) 1310 { 1311 contentsChanged(e); 1312 } 1313 } 1314 1315 /** 1316 * Handles {@link PropertyChangeEvent}s fired by the {@link JComboBox}. 1317 */ 1318 public class PropertyChangeHandler extends Object 1319 implements PropertyChangeListener 1320 { 1321 /** 1322 * Creates a new instance. 1323 */ 1324 public PropertyChangeHandler() 1325 { 1326 // Nothing to do here. 1327 } 1328 1329 /** 1330 * Invoked whenever bound property of JComboBox changes. 1331 * 1332 * @param e the event. 1333 */ 1334 public void propertyChange(PropertyChangeEvent e) 1335 { 1336 // Lets assume every change invalidates the minimumsize. 1337 String propName = e.getPropertyName(); 1338 if (propName.equals("enabled")) 1339 { 1340 boolean enabled = comboBox.isEnabled(); 1341 if (editor != null) 1342 editor.setEnabled(enabled); 1343 if (arrowButton != null) 1344 arrowButton.setEnabled(enabled); 1345 1346 comboBox.repaint(); 1347 } 1348 else if (propName.equals("editor") && comboBox.isEditable()) 1349 { 1350 addEditor(); 1351 comboBox.revalidate(); 1352 } 1353 else if (e.getPropertyName().equals("editable")) 1354 { 1355 if (comboBox.isEditable()) 1356 { 1357 addEditor(); 1358 } 1359 else 1360 { 1361 removeEditor(); 1362 } 1363 1364 comboBox.revalidate(); 1365 } 1366 else if (propName.equals("model")) 1367 { 1368 // remove ListDataListener from old model and add it to new model 1369 ComboBoxModel oldModel = (ComboBoxModel) e.getOldValue(); 1370 if (oldModel != null && listDataListener != null) 1371 oldModel.removeListDataListener(listDataListener); 1372 1373 ComboBoxModel newModel = (ComboBoxModel) e.getNewValue(); 1374 if (newModel != null && listDataListener != null) 1375 comboBox.getModel().addListDataListener(listDataListener); 1376 1377 if (editor != null) 1378 { 1379 comboBox.configureEditor(comboBox.getEditor(), 1380 comboBox.getSelectedItem()); 1381 } 1382 isMinimumSizeDirty = true; 1383 comboBox.revalidate(); 1384 comboBox.repaint(); 1385 } 1386 else if (propName.equals("font")) 1387 { 1388 Font font = (Font) e.getNewValue(); 1389 if (editor != null) 1390 { 1391 editor.setFont(font); 1392 } 1393 listBox.setFont(font); 1394 isMinimumSizeDirty = true; 1395 comboBox.revalidate(); 1396 } 1397 else if (propName.equals("prototypeDisplayValue")) 1398 { 1399 isMinimumSizeDirty = true; 1400 comboBox.revalidate(); 1401 } 1402 else if (propName.equals("renderer")) 1403 { 1404 isMinimumSizeDirty = true; 1405 comboBox.revalidate(); 1406 } 1407 // FIXME: Need to handle changes in other bound properties. 1408 } 1409 } 1410}