001    /* BasicComboBoxUI.java --
002       Copyright (C) 2004, 2005, 2006,  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.plaf.basic;
040    
041    import java.awt.Color;
042    import java.awt.Component;
043    import java.awt.Container;
044    import java.awt.Dimension;
045    import java.awt.Font;
046    import java.awt.Graphics;
047    import java.awt.Insets;
048    import java.awt.LayoutManager;
049    import java.awt.Rectangle;
050    import java.awt.event.FocusEvent;
051    import java.awt.event.FocusListener;
052    import java.awt.event.ItemEvent;
053    import java.awt.event.ItemListener;
054    import java.awt.event.KeyAdapter;
055    import java.awt.event.KeyEvent;
056    import java.awt.event.KeyListener;
057    import java.awt.event.MouseListener;
058    import java.awt.event.MouseMotionListener;
059    import java.beans.PropertyChangeEvent;
060    import java.beans.PropertyChangeListener;
061    
062    import javax.accessibility.Accessible;
063    import javax.accessibility.AccessibleContext;
064    import javax.swing.CellRendererPane;
065    import javax.swing.ComboBoxEditor;
066    import javax.swing.ComboBoxModel;
067    import javax.swing.DefaultListCellRenderer;
068    import javax.swing.InputMap;
069    import javax.swing.JButton;
070    import javax.swing.JComboBox;
071    import javax.swing.JComponent;
072    import javax.swing.JList;
073    import javax.swing.ListCellRenderer;
074    import javax.swing.LookAndFeel;
075    import javax.swing.SwingUtilities;
076    import javax.swing.UIManager;
077    import javax.swing.event.ListDataEvent;
078    import javax.swing.event.ListDataListener;
079    import javax.swing.plaf.ComboBoxUI;
080    import javax.swing.plaf.ComponentUI;
081    import 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     */
089    public 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    }