1: /* BasicComboBoxUI.java -- 2: Copyright (C) 2004, 2005, 2006, Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package javax.swing.plaf.basic; 40: 41: import java.awt.Color; 42: import java.awt.Component; 43: import java.awt.Container; 44: import java.awt.Dimension; 45: import java.awt.Font; 46: import java.awt.Graphics; 47: import java.awt.Insets; 48: import java.awt.LayoutManager; 49: import java.awt.Rectangle; 50: import java.awt.event.FocusEvent; 51: import java.awt.event.FocusListener; 52: import java.awt.event.ItemEvent; 53: import java.awt.event.ItemListener; 54: import java.awt.event.KeyAdapter; 55: import java.awt.event.KeyEvent; 56: import java.awt.event.KeyListener; 57: import java.awt.event.MouseListener; 58: import java.awt.event.MouseMotionListener; 59: import java.beans.PropertyChangeEvent; 60: import java.beans.PropertyChangeListener; 61: 62: import javax.accessibility.Accessible; 63: import javax.accessibility.AccessibleContext; 64: import javax.swing.CellRendererPane; 65: import javax.swing.ComboBoxEditor; 66: import javax.swing.ComboBoxModel; 67: import javax.swing.DefaultListCellRenderer; 68: import javax.swing.InputMap; 69: import javax.swing.JButton; 70: import javax.swing.JComboBox; 71: import javax.swing.JComponent; 72: import javax.swing.JList; 73: import javax.swing.ListCellRenderer; 74: import javax.swing.LookAndFeel; 75: import javax.swing.SwingUtilities; 76: import javax.swing.UIManager; 77: import javax.swing.event.ListDataEvent; 78: import javax.swing.event.ListDataListener; 79: import javax.swing.plaf.ComboBoxUI; 80: import javax.swing.plaf.ComponentUI; 81: import javax.swing.plaf.UIResource; 82: 83: /** 84: * A UI delegate for the {@link JComboBox} component. 85: * 86: * @author Olga Rodimina 87: * @author Robert Schuster 88: */ 89: public class BasicComboBoxUI extends ComboBoxUI 90: { 91: /** 92: * The arrow button that is displayed in the right side of JComboBox. This 93: * button is used to hide and show combo box's list of items. 94: */ 95: protected JButton arrowButton; 96: 97: /** 98: * The combo box represented by this UI delegate. 99: */ 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: }