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}