Frames | No Frames |
1: /* BasicComboBoxUI.java -- 2: Copyright (C) 2004, 2005 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.FontMetrics; 47: import java.awt.Graphics; 48: import java.awt.Insets; 49: import java.awt.LayoutManager; 50: import java.awt.Rectangle; 51: import java.awt.event.FocusEvent; 52: import java.awt.event.FocusListener; 53: import java.awt.event.ItemEvent; 54: import java.awt.event.ItemListener; 55: import java.awt.event.KeyAdapter; 56: import java.awt.event.KeyEvent; 57: import java.awt.event.KeyListener; 58: import java.awt.event.MouseAdapter; 59: import java.awt.event.MouseEvent; 60: import java.awt.event.MouseListener; 61: import java.awt.event.MouseMotionListener; 62: import java.beans.PropertyChangeEvent; 63: import java.beans.PropertyChangeListener; 64: 65: import javax.accessibility.Accessible; 66: import javax.swing.CellRendererPane; 67: import javax.swing.ComboBoxEditor; 68: import javax.swing.ComboBoxModel; 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: * A listener listening to mouse events occuring in the {@link JComboBox}. 133: */ 134: private MouseListener mouseListener; 135: 136: /** 137: * List used when rendering selected item of the combo box. The selection 138: * and foreground colors for combo box renderer are configured from this 139: * list. 140: */ 141: protected JList listBox; 142: 143: /** 144: * ListDataListener listening to JComboBox model 145: */ 146: protected ListDataListener listDataListener; 147: 148: /** 149: * Popup list containing the combo box's menu items. 150: */ 151: protected ComboPopup popup; 152: 153: protected KeyListener popupKeyListener; 154: 155: protected MouseListener popupMouseListener; 156: 157: protected MouseMotionListener popupMouseMotionListener; 158: 159: /** 160: * Listener listening to changes in the bound properties of JComboBox 161: */ 162: protected PropertyChangeListener propertyChangeListener; 163: 164: /** 165: * The button background. 166: * @see #installDefaults() 167: */ 168: private Color buttonBackground; 169: 170: /** 171: * The button shadow. 172: * @see #installDefaults() 173: */ 174: private Color buttonShadow; 175: 176: /** 177: * The button dark shadow. 178: * @see #installDefaults() 179: */ 180: private Color buttonDarkShadow; 181: 182: /** 183: * The button highlight. 184: * @see #installDefaults() 185: */ 186: private Color buttonHighlight; 187: 188: /* Size of the largest item in the comboBox 189: * This is package-private to avoid an accessor method. 190: */ 191: Dimension displaySize; 192: 193: // FIXME: This fields aren't used anywhere at this moment. 194: protected Dimension cachedMinimumSize; 195: protected CellRendererPane currentValuePane; 196: protected boolean isMinimumSizeDirty; 197: 198: /** 199: * Creates a new <code>BasicComboBoxUI</code> object. 200: */ 201: public BasicComboBoxUI() 202: { 203: // Nothing to do here. 204: } 205: 206: /** 207: * A factory method to create a UI delegate for the given 208: * {@link JComponent}, which should be a {@link JComboBox}. 209: * 210: * @param c The {@link JComponent} a UI is being created for. 211: * 212: * @return A UI delegate for the {@link JComponent}. 213: */ 214: public static ComponentUI createUI(JComponent c) 215: { 216: return new BasicComboBoxUI(); 217: } 218: 219: /** 220: * Installs the UI for the given {@link JComponent}. 221: * 222: * @param c the JComponent to install a UI for. 223: * 224: * @see #uninstallUI(JComponent) 225: */ 226: public void installUI(JComponent c) 227: { 228: super.installUI(c); 229: 230: if (c instanceof JComboBox) 231: { 232: comboBox = (JComboBox) c; 233: comboBox.setOpaque(true); 234: comboBox.setLayout(createLayoutManager()); 235: installDefaults(); 236: installComponents(); 237: installListeners(); 238: installKeyboardActions(); 239: } 240: } 241: 242: /** 243: * Uninstalls the UI for the given {@link JComponent}. 244: * 245: * @param c The JComponent that is having this UI removed. 246: * 247: * @see #installUI(JComponent) 248: */ 249: public void uninstallUI(JComponent c) 250: { 251: uninstallKeyboardActions(); 252: uninstallListeners(); 253: uninstallComponents(); 254: uninstallDefaults(); 255: comboBox = null; 256: } 257: 258: /** 259: * Installs the defaults that are defined in the {@link BasicLookAndFeel} 260: * for this {@link JComboBox}. 261: * 262: * @see #uninstallDefaults() 263: */ 264: protected void installDefaults() 265: { 266: LookAndFeel.installColorsAndFont(comboBox, "ComboBox.background", 267: "ComboBox.foreground", "ComboBox.font"); 268: 269: // fetch the button color scheme 270: buttonBackground = UIManager.getColor("ComboBox.buttonBackground"); 271: buttonShadow = UIManager.getColor("ComboBox.buttonShadow"); 272: buttonDarkShadow = UIManager.getColor("ComboBox.buttonDarkShadow"); 273: buttonHighlight = UIManager.getColor("ComboBox.buttonHighlight"); 274: } 275: 276: /** 277: * Creates and installs the listeners for this UI. 278: * 279: * @see #uninstallListeners() 280: */ 281: protected void installListeners() 282: { 283: // install combo box's listeners 284: propertyChangeListener = createPropertyChangeListener(); 285: comboBox.addPropertyChangeListener(propertyChangeListener); 286: 287: focusListener = createFocusListener(); 288: comboBox.addFocusListener(focusListener); 289: listBox.addFocusListener(focusListener); 290: 291: itemListener = createItemListener(); 292: comboBox.addItemListener(itemListener); 293: 294: keyListener = createKeyListener(); 295: comboBox.addKeyListener(keyListener); 296: 297: mouseListener = createMouseListener(); 298: arrowButton.addMouseListener(mouseListener); 299: 300: // install listeners that listen to combo box model 301: listDataListener = createListDataListener(); 302: comboBox.getModel().addListDataListener(listDataListener); 303: } 304: 305: /** 306: * Uninstalls the defaults and sets any objects created during 307: * install to <code>null</code>. 308: * 309: * @see #installDefaults() 310: */ 311: protected void uninstallDefaults() 312: { 313: if (comboBox.getFont() instanceof UIResource) 314: comboBox.setFont(null); 315: 316: if (comboBox.getForeground() instanceof UIResource) 317: comboBox.setForeground(null); 318: 319: if (comboBox.getBackground() instanceof UIResource) 320: comboBox.setBackground(null); 321: 322: buttonBackground = null; 323: buttonShadow = null; 324: buttonDarkShadow = null; 325: buttonHighlight = null; 326: } 327: 328: /** 329: * Detaches all the listeners we attached in {@link #installListeners}. 330: * 331: * @see #installListeners() 332: */ 333: protected void uninstallListeners() 334: { 335: comboBox.removePropertyChangeListener(propertyChangeListener); 336: propertyChangeListener = null; 337: 338: comboBox.removeFocusListener(focusListener); 339: listBox.removeFocusListener(focusListener); 340: focusListener = null; 341: 342: comboBox.removeItemListener(itemListener); 343: itemListener = null; 344: 345: comboBox.removeKeyListener(keyListener); 346: keyListener = null; 347: 348: arrowButton.removeMouseListener(mouseListener); 349: mouseListener = null; 350: 351: comboBox.getModel().removeListDataListener(listDataListener); 352: listDataListener = null; 353: } 354: 355: /** 356: * Creates the popup that will contain list of combo box's items. 357: * 358: * @return popup containing list of combo box's items 359: */ 360: protected ComboPopup createPopup() 361: { 362: return new BasicComboPopup(comboBox); 363: } 364: 365: /** 366: * Creates a {@link KeyListener} to listen to key events. 367: * 368: * @return KeyListener that listens to key events. 369: */ 370: protected KeyListener createKeyListener() 371: { 372: return new KeyHandler(); 373: } 374: 375: /** 376: * Creates a {@link MouseListener} that will listen to mouse events occurring 377: * in the combo box. 378: * 379: * @return the MouseListener 380: */ 381: private MouseListener createMouseListener() 382: { 383: return new MouseHandler(); 384: } 385: 386: /** 387: * Creates the {@link FocusListener} that will listen to changes in this 388: * JComboBox's focus. 389: * 390: * @return the FocusListener. 391: */ 392: protected FocusListener createFocusListener() 393: { 394: return new FocusHandler(); 395: } 396: 397: /** 398: * Creates a {@link ListDataListener} to listen to the combo box's data model. 399: * 400: * @return The new listener. 401: */ 402: protected ListDataListener createListDataListener() 403: { 404: return new ListDataHandler(); 405: } 406: 407: /** 408: * Creates an {@link ItemListener} that will listen to the changes in 409: * the JComboBox's selection. 410: * 411: * @return The ItemListener 412: */ 413: protected ItemListener createItemListener() 414: { 415: return new ItemHandler(); 416: } 417: 418: /** 419: * Creates a {@link PropertyChangeListener} to listen to the changes in 420: * the JComboBox's bound properties. 421: * 422: * @return The PropertyChangeListener 423: */ 424: protected PropertyChangeListener createPropertyChangeListener() 425: { 426: return new PropertyChangeHandler(); 427: } 428: 429: /** 430: * Creates and returns a layout manager for the combo box. Subclasses can 431: * override this method to provide a different layout. 432: * 433: * @return a layout manager for the combo box. 434: */ 435: protected LayoutManager createLayoutManager() 436: { 437: return new ComboBoxLayoutManager(); 438: } 439: 440: /** 441: * Creates a component that will be responsible for rendering the 442: * selected component in the combo box. 443: * 444: * @return A renderer for the combo box. 445: */ 446: protected ListCellRenderer createRenderer() 447: { 448: return new BasicComboBoxRenderer(); 449: } 450: 451: /** 452: * Creates the component that will be responsible for displaying/editing 453: * the selected item in the combo box. This editor is used only when combo 454: * box is editable. 455: * 456: * @return A new component that will be responsible for displaying/editing 457: * the selected item in the combo box. 458: */ 459: protected ComboBoxEditor createEditor() 460: { 461: return new BasicComboBoxEditor.UIResource(); 462: } 463: 464: /** 465: * Installs the components for this JComboBox. ArrowButton, main 466: * part of combo box (upper part) and popup list of items are created and 467: * configured here. 468: */ 469: protected void installComponents() 470: { 471: // create drop down list of items 472: popup = createPopup(); 473: listBox = popup.getList(); 474: 475: // set editor and renderer for the combo box. Editor is used 476: // only if combo box becomes editable, otherwise renderer is used 477: // to paint the selected item; combobox is not editable by default. 478: comboBox.setRenderer(createRenderer()); 479: 480: // create and install arrow button 481: arrowButton = createArrowButton(); 482: configureArrowButton(); 483: comboBox.add(arrowButton); 484: 485: ComboBoxEditor currentEditor = comboBox.getEditor(); 486: if (currentEditor == null || currentEditor instanceof UIResource) 487: { 488: currentEditor = createEditor(); 489: comboBox.setEditor(currentEditor); 490: } 491: editor = currentEditor.getEditorComponent(); 492: 493: comboBox.revalidate(); 494: } 495: 496: /** 497: * Uninstalls components from this {@link JComboBox}. 498: * 499: * @see #installComponents() 500: */ 501: protected void uninstallComponents() 502: { 503: // uninstall arrow button 504: unconfigureArrowButton(); 505: comboBox.remove(arrowButton); 506: arrowButton = null; 507: 508: listBox = null; 509: popup = null; 510: 511: comboBox.setRenderer(null); 512: 513: // if the editor is not an instanceof UIResource, it was not set by the 514: // UI delegate, so don't clear it... 515: ComboBoxEditor currentEditor = comboBox.getEditor(); 516: if (currentEditor instanceof UIResource) 517: { 518: comboBox.setEditor(null); 519: editor = null; 520: } 521: } 522: 523: /** 524: * Adds the current editor to the combo box. 525: */ 526: public void addEditor() 527: { 528: comboBox.add(editor); 529: } 530: 531: /** 532: * Removes the current editor from the combo box. 533: */ 534: public void removeEditor() 535: { 536: comboBox.remove(editor); 537: } 538: 539: /** 540: * Configures the editor for this combo box. 541: */ 542: protected void configureEditor() 543: { 544: editor.setFont(comboBox.getFont()); 545: comboBox.getEditor().setItem(comboBox.getSelectedItem()); 546: // FIXME: Need to implement. Set font and add listeners. 547: } 548: 549: /** 550: * Unconfigures the editor for this combo nox. This method is not implemented. 551: */ 552: protected void unconfigureEditor() 553: { 554: // FIXME: Need to implement 555: } 556: 557: /** 558: * Configures the arrow button. 559: * 560: * @see #configureArrowButton() 561: */ 562: public void configureArrowButton() 563: { 564: arrowButton.setEnabled(comboBox.isEnabled()); 565: arrowButton.setFont(comboBox.getFont()); 566: } 567: 568: /** 569: * Unconfigures the arrow button. 570: * 571: * @see #configureArrowButton() 572: * 573: * @specnote The specification says this method is implementation specific 574: * and should not be used or overridden. 575: */ 576: public void unconfigureArrowButton() 577: { 578: // Nothing to do here yet. 579: } 580: 581: /** 582: * Creates an arrow button for this {@link JComboBox}. The arrow button is 583: * displayed at the right end of the combo box and is used to display/hide 584: * the drop down list of items. 585: * 586: * @return A new button. 587: */ 588: protected JButton createArrowButton() 589: { 590: return new BasicArrowButton(BasicArrowButton.SOUTH, buttonBackground, 591: buttonShadow, buttonDarkShadow, buttonHighlight); 592: } 593: 594: /** 595: * Returns <code>true</code> if the popup is visible, and <code>false</code> 596: * otherwise. 597: * 598: * @param c The JComboBox to check 599: * 600: * @return <code>true</code> if popup part of the JComboBox is visible and 601: * <code>false</code> otherwise. 602: */ 603: public boolean isPopupVisible(JComboBox c) 604: { 605: return popup.isVisible(); 606: } 607: 608: /** 609: * Displays/hides the {@link JComboBox}'s list of items on the screen. 610: * 611: * @param c The combo box, for which list of items should be 612: * displayed/hidden 613: * @param v true if show popup part of the jcomboBox and false to hide. 614: */ 615: public void setPopupVisible(JComboBox c, boolean v) 616: { 617: if (v) 618: { 619: popup.show(); 620: popup.getList().requestFocus(); 621: } 622: else 623: popup.hide(); 624: } 625: 626: /** 627: * JComboBox is focus traversable if it is editable and not otherwise. 628: * 629: * @param c combo box for which to check whether it is focus traversable 630: * 631: * @return true if focus tranversable and false otherwise 632: */ 633: public boolean isFocusTraversable(JComboBox c) 634: { 635: if (!comboBox.isEditable()) 636: return true; 637: 638: return false; 639: } 640: 641: /** 642: * Paints given menu item using specified graphics context 643: * 644: * @param g The graphics context used to paint this combo box 645: * @param c comboBox which needs to be painted. 646: */ 647: public void paint(Graphics g, JComponent c) 648: { 649: Rectangle rect = rectangleForCurrentValue(); 650: paintCurrentValueBackground(g, rect, hasFocus); 651: paintCurrentValue(g, rect, hasFocus); 652: } 653: 654: /** 655: * Returns preferred size for the combo box. 656: * 657: * @param c comboBox for which to get preferred size 658: * 659: * @return The preferred size for the given combo box 660: */ 661: public Dimension getPreferredSize(JComponent c) 662: { 663: // note: overriding getMinimumSize() (for example in the MetalComboBoxUI 664: // class) affects the getPreferredSize() result, so it seems logical that 665: // this method is implemented by delegating to the getMinimumSize() method 666: return getMinimumSize(c); 667: } 668: 669: /** 670: * Returns the minimum size for this {@link JComboBox} for this 671: * look and feel. 672: * 673: * @param c The {@link JComponent} to find the minimum size for. 674: * 675: * @return The dimensions of the minimum size. 676: */ 677: public Dimension getMinimumSize(JComponent c) 678: { 679: Dimension d = getDisplaySize(); 680: int arrowButtonWidth = d.height; 681: Dimension result = new Dimension(d.width + arrowButtonWidth, d.height); 682: return result; 683: } 684: 685: /** The value returned by the getMaximumSize() method. */ 686: private static final Dimension MAXIMUM_SIZE = new Dimension(32767, 32767); 687: 688: /** 689: * Returns the maximum size for this {@link JComboBox} for this 690: * look and feel. 691: * 692: * @param c The {@link JComponent} to find the maximum size for 693: * 694: * @return The maximum size (<code>Dimension(32767, 32767)</code>). 695: */ 696: public Dimension getMaximumSize(JComponent c) 697: { 698: return MAXIMUM_SIZE; 699: } 700: 701: public int getAccessibleChildrenCount(JComponent c) 702: { 703: // FIXME: Need to implement 704: return 0; 705: } 706: 707: public Accessible getAccessibleChild(JComponent c, int i) 708: { 709: // FIXME: Need to implement 710: return null; 711: } 712: 713: /** 714: * Returns true if the specified key is a navigation key and false otherwise 715: * 716: * @param keyCode a key for which to check whether it is navigation key or 717: * not. 718: * 719: * @return true if the specified key is a navigation key and false otherwis 720: */ 721: protected boolean isNavigationKey(int keyCode) 722: { 723: return false; 724: } 725: 726: /** 727: * Selects next possible item relative to the current selection 728: * to be next selected item in the combo box. 729: */ 730: protected void selectNextPossibleValue() 731: { 732: int index = comboBox.getSelectedIndex(); 733: if (index != comboBox.getItemCount() - 1) 734: comboBox.setSelectedIndex(index + 1); 735: } 736: 737: /** 738: * Selects previous item relative to current selection to be 739: * next selected item. 740: */ 741: protected void selectPreviousPossibleValue() 742: { 743: int index = comboBox.getSelectedIndex(); 744: if (index != 0) 745: comboBox.setSelectedIndex(index - 1); 746: } 747: 748: /** 749: * Displays combo box popup if the popup is not currently shown 750: * on the screen and hides it if it is currently shown 751: */ 752: protected void toggleOpenClose() 753: { 754: setPopupVisible(comboBox, ! isPopupVisible(comboBox)); 755: } 756: 757: /** 758: * Returns the bounds in which comboBox's selected item will be 759: * displayed. 760: * 761: * @return rectangle bounds in which comboBox's selected Item will be 762: * displayed 763: */ 764: protected Rectangle rectangleForCurrentValue() 765: { 766: Rectangle cbBounds = SwingUtilities.getLocalBounds(comboBox); 767: Rectangle abBounds = arrowButton.getBounds(); 768: Rectangle rectForCurrentValue = new Rectangle(cbBounds.x, cbBounds.y, 769: cbBounds.width - abBounds.width, cbBounds.height); 770: return rectForCurrentValue; 771: } 772: 773: /** 774: * Returns the insets of the current border. 775: * 776: * @return Insets representing space between combo box and its border 777: */ 778: protected Insets getInsets() 779: { 780: return new Insets(0, 0, 0, 0); 781: } 782: 783: /** 784: * Paints currently selected value in the main part of the combo 785: * box (part without popup). 786: * 787: * @param g graphics context 788: * @param bounds Rectangle representing the size of the area in which 789: * selected item should be drawn 790: * @param hasFocus true if combo box has focus and false otherwise 791: */ 792: public void paintCurrentValue(Graphics g, Rectangle bounds, boolean hasFocus) 793: { 794: if (! comboBox.isEditable()) 795: { 796: Object currentValue = comboBox.getSelectedItem(); 797: boolean isPressed = arrowButton.getModel().isPressed(); 798: 799: /* Gets the component to be drawn for the current value. 800: * If there is currently no selected item we will take an empty 801: * String as replacement. 802: */ 803: Component comp = comboBox.getRenderer().getListCellRendererComponent( 804: listBox, (currentValue != null ? currentValue : ""), -1, 805: isPressed, hasFocus); 806: if (! comboBox.isEnabled()) 807: { 808: comp.setBackground(UIManager.getColor( 809: "ComboBox.disabledBackground")); 810: comp.setForeground(UIManager.getColor( 811: "ComboBox.disabledForeground")); 812: comp.setEnabled(false); 813: } 814: comp.setBounds(0, 0, bounds.width, bounds.height); 815: comp.setFont(comboBox.getFont()); 816: comp.paint(g); 817: 818: comboBox.revalidate(); 819: } 820: else 821: comboBox.getEditor().setItem(comboBox.getSelectedItem()); 822: } 823: 824: /** 825: * Paints the background of part of the combo box, where currently 826: * selected value is displayed. If the combo box has focus this method 827: * should also paint focus rectangle around the combo box. 828: * 829: * @param g graphics context 830: * @param bounds Rectangle representing the size of the largest item in the 831: * comboBox 832: * @param hasFocus true if combo box has fox and false otherwise 833: */ 834: public void paintCurrentValueBackground(Graphics g, Rectangle bounds, 835: boolean hasFocus) 836: { 837: // background is painted by renderer, so it seems that nothing 838: // should be done here. 839: } 840: 841: /** 842: * Returns the default size for the display area of a combo box that does 843: * not contain any elements. This method returns the width and height of 844: * a single space in the current font, plus a margin of 1 pixel. 845: * 846: * @return The default display size. 847: * 848: * @see #getDisplaySize() 849: */ 850: protected Dimension getDefaultSize() 851: { 852: // There is nothing in the spec to say how this method should be 853: // implemented...so I've done some guessing, written some Mauve tests, 854: // and written something that gives dimensions that are close to the 855: // reference implementation. 856: FontMetrics fm = comboBox.getFontMetrics(comboBox.getFont()); 857: int w = fm.charWidth(' ') + 2; 858: int h = fm.getHeight() + 2; 859: return new Dimension(w, h); 860: } 861: 862: /** 863: * Returns the size of the display area for the combo box. This size will be 864: * the size of the combo box, not including the arrowButton. 865: * 866: * @return The size of the display area for the combo box. 867: */ 868: protected Dimension getDisplaySize() 869: { 870: if (!comboBox.isEditable()) 871: { 872: Object prototype = comboBox.getPrototypeDisplayValue(); 873: if (prototype != null) 874: { 875: // calculate result based on prototype 876: ListCellRenderer renderer = comboBox.getRenderer(); 877: Component comp = renderer.getListCellRendererComponent(listBox, 878: prototype, -1, false, false); 879: Dimension compSize = comp.getPreferredSize(); 880: compSize.width += 2; // add 1 pixel margin around area 881: compSize.height += 2; 882: return compSize; 883: } 884: else 885: { 886: ComboBoxModel model = comboBox.getModel(); 887: int numItems = model.getSize(); 888: 889: // if combo box doesn't have any items then simply 890: // return its default size 891: if (numItems == 0) 892: { 893: displaySize = getDefaultSize(); 894: return displaySize; 895: } 896: 897: Dimension size = new Dimension(0, 0); 898: 899: // ComboBox's display size should be equal to the 900: // size of the largest item in the combo box. 901: ListCellRenderer renderer = comboBox.getRenderer(); 902: 903: for (int i = 0; i < numItems; i++) 904: { 905: Object item = model.getElementAt(i); 906: Component comp = renderer.getListCellRendererComponent(listBox, 907: item, -1, false, false); 908: 909: Dimension compSize = comp.getPreferredSize(); 910: if (compSize.width + 2 > size.width) 911: size.width = compSize.width + 2; 912: if (compSize.height + 2 > size.height) 913: size.height = compSize.height + 2; 914: } 915: displaySize = size; 916: return displaySize; 917: } 918: } 919: else // an editable combo, 920: { 921: Component comp = comboBox.getEditor().getEditorComponent(); 922: Dimension prefSize = comp.getPreferredSize(); 923: int width = prefSize.width; 924: int height = prefSize.height + 2; 925: Object prototype = comboBox.getPrototypeDisplayValue(); 926: if (prototype != null) 927: { 928: FontMetrics fm = comboBox.getFontMetrics(comboBox.getFont()); 929: width = Math.max(width, fm.stringWidth(prototype.toString()) + 2); 930: } 931: displaySize = new Dimension(width, height); 932: return displaySize; 933: } 934: } 935: 936: /** 937: * Installs the keyboard actions for the {@link JComboBox} as specified 938: * by the look and feel. 939: */ 940: protected void installKeyboardActions() 941: { 942: // FIXME: Need to implement. 943: } 944: 945: /** 946: * Uninstalls the keyboard actions for the {@link JComboBox} there were 947: * installed by in {@link #installListeners}. 948: */ 949: protected void uninstallKeyboardActions() 950: { 951: // FIXME: Need to implement. 952: } 953: 954: /** 955: * A {@link LayoutManager} used to position the sub-components of the 956: * {@link JComboBox}. 957: * 958: * @see BasicComboBoxUI#createLayoutManager() 959: */ 960: public class ComboBoxLayoutManager implements LayoutManager 961: { 962: /** 963: * Creates a new ComboBoxLayoutManager object. 964: */ 965: public ComboBoxLayoutManager() 966: { 967: // Nothing to do here. 968: } 969: 970: /** 971: * Adds a component to the layout. This method does nothing, since the 972: * layout manager doesn't need to track the components. 973: * 974: * @param name the name to associate the component with (ignored). 975: * @param comp the component (ignored). 976: */ 977: public void addLayoutComponent(String name, Component comp) 978: { 979: // Do nothing 980: } 981: 982: /** 983: * Removes a component from the layout. This method does nothing, since 984: * the layout manager doesn't need to track the components. 985: * 986: * @param comp the component. 987: */ 988: public void removeLayoutComponent(Component comp) 989: { 990: // Do nothing 991: } 992: 993: /** 994: * Returns preferred layout size of the JComboBox. 995: * 996: * @param parent the Container for which the preferred size should be 997: * calculated. 998: * 999: * @return The preferred size for the given container 1000: */ 1001: public Dimension preferredLayoutSize(Container parent) 1002: { 1003: return getPreferredSize((JComponent) parent); 1004: } 1005: 1006: /** 1007: * Returns the minimum layout size. 1008: * 1009: * @param parent the container. 1010: * 1011: * @return The minimum size. 1012: */ 1013: public Dimension minimumLayoutSize(Container parent) 1014: { 1015: return preferredLayoutSize(parent); 1016: } 1017: 1018: /** 1019: * Arranges the components in the container. It puts arrow 1020: * button right end part of the comboBox. If the comboBox is editable 1021: * then editor is placed to the left of arrow button, starting from the 1022: * beginning. 1023: * 1024: * @param parent Container that should be layed out. 1025: */ 1026: public void layoutContainer(Container parent) 1027: { 1028: // Position editor component to the left of arrow button if combo box is 1029: // editable 1030: int arrowSize = comboBox.getHeight(); 1031: int editorWidth = comboBox.getBounds().width - arrowSize; 1032: 1033: if (comboBox.isEditable()) 1034: editor.setBounds(0, 0, editorWidth, comboBox.getBounds().height); 1035: 1036: arrowButton.setBounds(editorWidth, 0, arrowSize, arrowSize); 1037: comboBox.revalidate(); 1038: } 1039: } 1040: 1041: /** 1042: * Handles focus changes occuring in the combo box. This class is 1043: * responsible for repainting combo box whenever focus is gained or lost 1044: * and also for hiding popup list of items whenever combo box loses its 1045: * focus. 1046: */ 1047: public class FocusHandler extends Object implements FocusListener 1048: { 1049: /** 1050: * Creates a new FocusHandler object. 1051: */ 1052: public FocusHandler() 1053: { 1054: // Nothing to do here. 1055: } 1056: 1057: /** 1058: * Invoked when combo box gains focus. It repaints main 1059: * part of combo box accordingly. 1060: * 1061: * @param e the FocusEvent 1062: */ 1063: public void focusGained(FocusEvent e) 1064: { 1065: hasFocus = true; 1066: comboBox.repaint(); 1067: } 1068: 1069: /** 1070: * Invoked when the combo box loses focus. It repaints the main part 1071: * of the combo box accordingly and hides the popup list of items. 1072: * 1073: * @param e the FocusEvent 1074: */ 1075: public void focusLost(FocusEvent e) 1076: { 1077: hasFocus = false; 1078: setPopupVisible(comboBox, false); 1079: comboBox.repaint(); 1080: } 1081: } 1082: 1083: /** 1084: * Handles {@link ItemEvent}s fired by the {@link JComboBox} when its 1085: * selected item changes. 1086: */ 1087: public class ItemHandler extends Object implements ItemListener 1088: { 1089: /** 1090: * Creates a new ItemHandler object. 1091: */ 1092: public ItemHandler() 1093: { 1094: // Nothing to do here. 1095: } 1096: 1097: /** 1098: * Invoked when selected item becomes deselected or when 1099: * new item becomes selected. 1100: * 1101: * @param e the ItemEvent representing item's state change. 1102: */ 1103: public void itemStateChanged(ItemEvent e) 1104: { 1105: if (e.getStateChange() == ItemEvent.SELECTED && comboBox.isEditable()) 1106: comboBox.getEditor().setItem(e.getItem()); 1107: comboBox.repaint(); 1108: } 1109: } 1110: 1111: /** 1112: * KeyHandler handles key events occuring while JComboBox has focus. 1113: */ 1114: public class KeyHandler extends KeyAdapter 1115: { 1116: public KeyHandler() 1117: { 1118: // Nothing to do here. 1119: } 1120: 1121: /** 1122: * Invoked whenever key is pressed while JComboBox is in focus. 1123: */ 1124: public void keyPressed(KeyEvent e) 1125: { 1126: // FIXME: This method calls JComboBox.selectWithKeyChar if the key that was 1127: // pressed is not a navigation key. 1128: } 1129: } 1130: 1131: /** 1132: * Handles the changes occurring in the JComboBox's data model. 1133: */ 1134: public class ListDataHandler extends Object implements ListDataListener 1135: { 1136: /** 1137: * Creates a new ListDataHandler object. 1138: */ 1139: public ListDataHandler() 1140: { 1141: // Nothing to do here. 1142: } 1143: 1144: /** 1145: * Invoked if the content's of JComboBox's data model are changed. 1146: * 1147: * @param e ListDataEvent describing the change. 1148: */ 1149: public void contentsChanged(ListDataEvent e) 1150: { 1151: // if the item is selected or deselected 1152: } 1153: 1154: /** 1155: * Invoked when items are added to the JComboBox's data model. 1156: * 1157: * @param e ListDataEvent describing the change. 1158: */ 1159: public void intervalAdded(ListDataEvent e) 1160: { 1161: ComboBoxModel model = comboBox.getModel(); 1162: ListCellRenderer renderer = comboBox.getRenderer(); 1163: 1164: if (displaySize == null) 1165: displaySize = getDisplaySize(); 1166: if (displaySize.width < getDefaultSize().width) 1167: displaySize.width = getDefaultSize().width; 1168: if (displaySize.height < getDefaultSize().height) 1169: displaySize.height = getDefaultSize().height; 1170: 1171: comboBox.repaint(); 1172: } 1173: 1174: /** 1175: * Invoked when items are removed from the JComboBox's 1176: * data model. 1177: * 1178: * @param e ListDataEvent describing the change. 1179: */ 1180: public void intervalRemoved(ListDataEvent e) 1181: { 1182: // recalculate display size of the JComboBox. 1183: displaySize = getDisplaySize(); 1184: comboBox.repaint(); 1185: } 1186: } 1187: 1188: /** 1189: * Handles {@link PropertyChangeEvent}s fired by the {@link JComboBox}. 1190: */ 1191: public class PropertyChangeHandler extends Object 1192: implements PropertyChangeListener 1193: { 1194: /** 1195: * Creates a new instance. 1196: */ 1197: public PropertyChangeHandler() 1198: { 1199: // Nothing to do here. 1200: } 1201: 1202: /** 1203: * Invoked whenever bound property of JComboBox changes. 1204: * 1205: * @param e the event. 1206: */ 1207: public void propertyChange(PropertyChangeEvent e) 1208: { 1209: if (e.getPropertyName().equals("enabled")) 1210: { 1211: arrowButton.setEnabled(comboBox.isEnabled()); 1212: 1213: if (comboBox.isEditable()) 1214: comboBox.getEditor().getEditorComponent().setEnabled(comboBox 1215: .isEnabled()); 1216: } 1217: else if (e.getPropertyName().equals("editable")) 1218: { 1219: if (comboBox.isEditable()) 1220: { 1221: configureEditor(); 1222: addEditor(); 1223: } 1224: else 1225: { 1226: unconfigureEditor(); 1227: removeEditor(); 1228: } 1229: 1230: comboBox.revalidate(); 1231: comboBox.repaint(); 1232: } 1233: else if (e.getPropertyName().equals("dataModel")) 1234: { 1235: // remove ListDataListener from old model and add it to new model 1236: ComboBoxModel oldModel = (ComboBoxModel) e.getOldValue(); 1237: if (oldModel != null) 1238: oldModel.removeListDataListener(listDataListener); 1239: 1240: if ((ComboBoxModel) e.getNewValue() != null) 1241: comboBox.getModel().addListDataListener(listDataListener); 1242: } 1243: else if (e.getPropertyName().equals("font")) 1244: { 1245: Font font = (Font) e.getNewValue(); 1246: editor.setFont(font); 1247: listBox.setFont(font); 1248: arrowButton.setFont(font); 1249: comboBox.revalidate(); 1250: comboBox.repaint(); 1251: } 1252: 1253: // FIXME: Need to handle changes in other bound properties. 1254: } 1255: } 1256: 1257: /** 1258: * A handler for mouse events occurring in the combo box. An instance of 1259: * this class is returned by the <code>createMouseListener()</code> method. 1260: */ 1261: private class MouseHandler extends MouseAdapter 1262: { 1263: /** 1264: * Invoked when mouse is pressed over the combo box. It toggles the 1265: * visibility of the popup list. 1266: * 1267: * @param e the event 1268: */ 1269: public void mousePressed(MouseEvent e) 1270: { 1271: if (comboBox.isEnabled()) 1272: toggleOpenClose(); 1273: } 1274: } 1275: }