1 /*
2  * Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package javax.swing.plaf.synth;
27 
28 import java.awt.*;
29 import java.awt.event.*;
30 import javax.swing.*;
31 import javax.swing.plaf.*;
32 import javax.swing.event.*;
33 import javax.swing.plaf.basic.*;
34 import java.beans.PropertyChangeListener;
35 import java.beans.PropertyChangeEvent;
36 
37 /**
38  * Provides the Synth L&F UI delegate for
39  * {@link javax.swing.JComboBox}.
40  *
41  * @author Scott Violet
42  * @since 1.7
43  */
44 public class SynthComboBoxUI extends BasicComboBoxUI implements
45                               PropertyChangeListener, SynthUI {
46     private SynthStyle style;
47     private boolean useListColors;
48 
49     /**
50      * Used to adjust the location and size of the popup. Very useful for
51      * situations such as we find in Nimbus where part of the border is used
52      * to paint the focus. In such cases, the border is empty space, and not
53      * part of the "visual" border, and in these cases, you'd like the popup
54      * to be adjusted such that it looks as if it were next to the visual border.
55      * You may want to use negative insets to get the right look.
56      */
57     Insets popupInsets;
58 
59     /**
60      * This flag may be set via UIDefaults. By default, it is false, to
61      * preserve backwards compatibility. If true, then the combo will
62      * "act as a button" when it is not editable.
63      */
64     private boolean buttonWhenNotEditable;
65 
66     /**
67      * A flag to indicate that the combo box and combo box button should
68      * remain in the PRESSED state while the combo popup is visible.
69      */
70     private boolean pressedWhenPopupVisible;
71 
72     /**
73      * When buttonWhenNotEditable is true, this field is used to help make
74      * the combo box appear and function as a button when the combo box is
75      * not editable. In such a state, you can click anywhere on the button
76      * to get it to open the popup. Also, anywhere you hover over the combo
77      * will cause the entire combo to go into "rollover" state, and anywhere
78      * you press will go into "pressed" state. This also keeps in sync the
79      * state of the combo and the arrowButton.
80      */
81     private ButtonHandler buttonHandler;
82 
83     /**
84      * Handler for repainting combo when editor component gains/looses focus
85      */
86     private EditorFocusHandler editorFocusHandler;
87 
88     /**
89      * If true, then the cell renderer will be forced to be non-opaque when
90      * used for rendering the selected item in the combo box (not in the list),
91      * and forced to opaque after rendering the selected value.
92      */
93     private boolean forceOpaque = false;
94 
95     /**
96      * Creates a new UI object for the given component.
97      *
98      * @param c component to create UI object for
99      * @return the UI object
100      */
createUI(JComponent c)101     public static ComponentUI createUI(JComponent c) {
102         return new SynthComboBoxUI();
103     }
104 
105     /**
106      * {@inheritDoc}
107      *
108      * Overridden to ensure that ButtonHandler is created prior to any of
109      * the other installXXX methods, since several of them reference
110      * buttonHandler.
111      */
112     @Override
installUI(JComponent c)113     public void installUI(JComponent c) {
114         buttonHandler = new ButtonHandler();
115         super.installUI(c);
116     }
117 
118     @Override
installDefaults()119     protected void installDefaults() {
120         updateStyle(comboBox);
121     }
122 
updateStyle(JComboBox<?> comboBox)123     private void updateStyle(JComboBox<?> comboBox) {
124         SynthStyle oldStyle = style;
125         SynthContext context = getContext(comboBox, ENABLED);
126 
127         style = SynthLookAndFeel.updateStyle(context, this);
128         if (style != oldStyle) {
129             padding = (Insets) style.get(context, "ComboBox.padding");
130             popupInsets = (Insets)style.get(context, "ComboBox.popupInsets");
131             useListColors = style.getBoolean(context,
132                     "ComboBox.rendererUseListColors", true);
133             buttonWhenNotEditable = style.getBoolean(context,
134                     "ComboBox.buttonWhenNotEditable", false);
135             pressedWhenPopupVisible = style.getBoolean(context,
136                     "ComboBox.pressedWhenPopupVisible", false);
137             squareButton = style.getBoolean(context,
138                     "ComboBox.squareButton", true);
139 
140             if (oldStyle != null) {
141                 uninstallKeyboardActions();
142                 installKeyboardActions();
143             }
144             forceOpaque = style.getBoolean(context,
145                     "ComboBox.forceOpaque", false);
146         }
147 
148         if(listBox != null) {
149             SynthLookAndFeel.updateStyles(listBox);
150         }
151     }
152 
153     /**
154      * {@inheritDoc}
155      */
156     @Override
installListeners()157     protected void installListeners() {
158         comboBox.addPropertyChangeListener(this);
159         comboBox.addMouseListener(buttonHandler);
160         editorFocusHandler = new EditorFocusHandler(comboBox);
161         super.installListeners();
162     }
163 
164     /**
165      * {@inheritDoc}
166      */
167     @Override
uninstallUI(JComponent c)168     public void uninstallUI(JComponent c) {
169         if (popup instanceof SynthComboPopup) {
170             ((SynthComboPopup)popup).removePopupMenuListener(buttonHandler);
171         }
172         super.uninstallUI(c);
173         buttonHandler = null;
174     }
175 
176     /**
177      * {@inheritDoc}
178      */
179     @Override
uninstallDefaults()180     protected void uninstallDefaults() {
181         SynthContext context = getContext(comboBox, ENABLED);
182 
183         style.uninstallDefaults(context);
184         style = null;
185     }
186 
187     /**
188      * {@inheritDoc}
189      */
190     @Override
uninstallListeners()191     protected void uninstallListeners() {
192         editorFocusHandler.unregister();
193         comboBox.removePropertyChangeListener(this);
194         comboBox.removeMouseListener(buttonHandler);
195         buttonHandler.pressed = false;
196         buttonHandler.over = false;
197         super.uninstallListeners();
198     }
199 
200     /**
201      * {@inheritDoc}
202      */
203     @Override
getContext(JComponent c)204     public SynthContext getContext(JComponent c) {
205         return getContext(c, getComponentState(c));
206     }
207 
getContext(JComponent c, int state)208     private SynthContext getContext(JComponent c, int state) {
209         return SynthContext.getContext(c, style, state);
210     }
211 
getComponentState(JComponent c)212     private int getComponentState(JComponent c) {
213         // currently we have a broken situation where if a developer
214         // takes the border from a JComboBox and sets it on a JTextField
215         // then the codepath will eventually lead back to this method
216         // but pass in a JTextField instead of JComboBox! In case this
217         // happens, we just return the normal synth state for the component
218         // instead of doing anything special
219         if (!(c instanceof JComboBox)) return SynthLookAndFeel.getComponentState(c);
220 
221         JComboBox<?> box = (JComboBox)c;
222         if (shouldActLikeButton()) {
223             int state = ENABLED;
224             if ((!c.isEnabled())) {
225                 state = DISABLED;
226             }
227             if (buttonHandler.isPressed()) {
228                 state |= PRESSED;
229             }
230             if (buttonHandler.isRollover()) {
231                 state |= MOUSE_OVER;
232             }
233             if (box.isFocusOwner()) {
234                 state |= FOCUSED;
235             }
236             return state;
237         } else {
238             // for editable combos the editor component has the focus not the
239             // combo box its self, so we should make the combo paint focused
240             // when its editor has focus
241             int basicState = SynthLookAndFeel.getComponentState(c);
242             if (box.isEditable() &&
243                      box.getEditor().getEditorComponent().isFocusOwner()) {
244                 basicState |= FOCUSED;
245             }
246             return basicState;
247         }
248     }
249 
250     /**
251      * {@inheritDoc}
252      */
253     @Override
createPopup()254     protected ComboPopup createPopup() {
255         SynthComboPopup p = new SynthComboPopup(comboBox);
256         p.addPopupMenuListener(buttonHandler);
257         return p;
258     }
259 
260     /**
261      * {@inheritDoc}
262      */
263     @Override
createRenderer()264     protected ListCellRenderer<Object> createRenderer() {
265         return new SynthComboBoxRenderer();
266     }
267 
268     /**
269      * {@inheritDoc}
270      */
271     @Override
createEditor()272     protected ComboBoxEditor createEditor() {
273         return new SynthComboBoxEditor();
274     }
275 
276     //
277     // end UI Initialization
278     //======================
279 
280     /**
281      * {@inheritDoc}
282      */
283     @Override
propertyChange(PropertyChangeEvent e)284     public void propertyChange(PropertyChangeEvent e) {
285         if (SynthLookAndFeel.shouldUpdateStyle(e)) {
286             updateStyle(comboBox);
287         }
288     }
289 
290     /**
291      * {@inheritDoc}
292      */
293     @Override
createArrowButton()294     protected JButton createArrowButton() {
295         SynthArrowButton button = new SynthArrowButton(SwingConstants.SOUTH);
296         button.setName("ComboBox.arrowButton");
297         button.setModel(buttonHandler);
298         return button;
299     }
300 
301     //=================================
302     // begin ComponentUI Implementation
303 
304     /**
305      * Notifies this UI delegate to repaint the specified component.
306      * This method paints the component background, then calls
307      * the {@link #paint(SynthContext,Graphics)} method.
308      *
309      * <p>In general, this method does not need to be overridden by subclasses.
310      * All Look and Feel rendering code should reside in the {@code paint} method.
311      *
312      * @param g the {@code Graphics} object used for painting
313      * @param c the component being painted
314      * @see #paint(SynthContext,Graphics)
315      */
316     @Override
update(Graphics g, JComponent c)317     public void update(Graphics g, JComponent c) {
318         SynthContext context = getContext(c);
319 
320         SynthLookAndFeel.update(context, g);
321         context.getPainter().paintComboBoxBackground(context, g, 0, 0,
322                                                   c.getWidth(), c.getHeight());
323         paint(context, g);
324     }
325 
326     /**
327      * Paints the specified component according to the Look and Feel.
328      * <p>This method is not used by Synth Look and Feel.
329      * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
330      *
331      * @param g the {@code Graphics} object used for painting
332      * @param c the component being painted
333      * @see #paint(SynthContext,Graphics)
334      */
335     @Override
paint(Graphics g, JComponent c)336     public void paint(Graphics g, JComponent c) {
337         SynthContext context = getContext(c);
338 
339         paint(context, g);
340     }
341 
342     /**
343      * Paints the specified component.
344      *
345      * @param context context for the component being painted
346      * @param g the {@code Graphics} object used for painting
347      * @see #update(Graphics,JComponent)
348      */
paint(SynthContext context, Graphics g)349     protected void paint(SynthContext context, Graphics g) {
350         hasFocus = comboBox.hasFocus();
351         if ( !comboBox.isEditable() ) {
352             Rectangle r = rectangleForCurrentValue();
353             paintCurrentValue(g,r,hasFocus);
354         }
355     }
356 
357     /**
358      * {@inheritDoc}
359      */
360     @Override
paintBorder(SynthContext context, Graphics g, int x, int y, int w, int h)361     public void paintBorder(SynthContext context, Graphics g, int x,
362                             int y, int w, int h) {
363         context.getPainter().paintComboBoxBorder(context, g, x, y, w, h);
364     }
365 
366     /**
367      * Paints the currently selected item.
368      */
369     @Override
paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus)370     public void paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus) {
371         ListCellRenderer<Object> renderer = comboBox.getRenderer();
372         Component c;
373 
374         c = renderer.getListCellRendererComponent(
375                 listBox, comboBox.getSelectedItem(), -1, false, false );
376 
377         // Fix for 4238829: should lay out the JPanel.
378         boolean shouldValidate = false;
379         if (c instanceof JPanel)  {
380             shouldValidate = true;
381         }
382 
383         if (c instanceof UIResource) {
384             c.setName("ComboBox.renderer");
385         }
386 
387         boolean force = forceOpaque && c instanceof JComponent;
388         if (force) {
389             ((JComponent)c).setOpaque(false);
390         }
391 
392         int x = bounds.x, y = bounds.y, w = bounds.width, h = bounds.height;
393         if (padding != null) {
394             x = bounds.x + padding.left;
395             y = bounds.y + padding.top;
396             w = bounds.width - (padding.left + padding.right);
397             h = bounds.height - (padding.top + padding.bottom);
398         }
399 
400         currentValuePane.paintComponent(g, c, comboBox, x, y, w, h, shouldValidate);
401 
402         if (force) {
403             ((JComponent)c).setOpaque(true);
404         }
405     }
406 
407     /**
408      * @return true if this combo box should act as one big button. Typically
409      * only happens when buttonWhenNotEditable is true, and comboBox.isEditable
410      * is false.
411      */
shouldActLikeButton()412     private boolean shouldActLikeButton() {
413         return buttonWhenNotEditable && !comboBox.isEditable();
414     }
415 
416     /**
417      * Returns the default size of an empty display area of the combo box using
418      * the current renderer and font.
419      *
420      * This method was overridden to use SynthComboBoxRenderer instead of
421      * DefaultListCellRenderer as the default renderer when calculating the
422      * size of the combo box. This is used in the case of the combo not having
423      * any data.
424      *
425      * @return the size of an empty display area
426      * @see #getDisplaySize
427      */
428     @Override
getDefaultSize()429     protected Dimension getDefaultSize() {
430         SynthComboBoxRenderer r = new SynthComboBoxRenderer();
431         Dimension d = getSizeForComponent(r.getListCellRendererComponent(listBox, " ", -1, false, false));
432         return new Dimension(d.width, d.height);
433     }
434 
435     /**
436      * From BasicComboBoxRenderer v 1.18.
437      *
438      * Be aware that SynthFileChooserUIImpl relies on the fact that the default
439      * renderer installed on a Synth combo box is a JLabel. If this is changed,
440      * then an assert will fail in SynthFileChooserUIImpl
441      */
442     @SuppressWarnings("serial") // Superclass is not serializable across versions
443     private class SynthComboBoxRenderer extends JLabel implements ListCellRenderer<Object>, UIResource {
SynthComboBoxRenderer()444         public SynthComboBoxRenderer() {
445             super();
446             setText(" ");
447         }
448 
449         @Override
getName()450         public String getName() {
451             // SynthComboBoxRenderer should have installed Name while constructor is working.
452             // The setName invocation in the SynthComboBoxRenderer() constructor doesn't work
453             // because of the opaque property is installed in the constructor based on the
454             // component name (see GTKStyle.isOpaque())
455             String name = super.getName();
456 
457             return name == null ? "ComboBox.renderer" : name;
458         }
459 
460         @Override
getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus)461         public Component getListCellRendererComponent(JList<?> list, Object value,
462                          int index, boolean isSelected, boolean cellHasFocus) {
463             setName("ComboBox.listRenderer");
464             SynthLookAndFeel.resetSelectedUI();
465             if (isSelected) {
466                 setBackground(list.getSelectionBackground());
467                 setForeground(list.getSelectionForeground());
468                 if (!useListColors) {
469                     SynthLookAndFeel.setSelectedUI(
470                          (SynthLabelUI)SynthLookAndFeel.getUIOfType(getUI(),
471                          SynthLabelUI.class), isSelected, cellHasFocus,
472                          list.isEnabled(), false);
473                 }
474             } else {
475                 setBackground(list.getBackground());
476                 setForeground(list.getForeground());
477             }
478 
479             setFont(list.getFont());
480 
481             if (value instanceof Icon) {
482                 setIcon((Icon)value);
483                 setText("");
484             } else {
485                 String text = (value == null) ? " " : value.toString();
486 
487                 if ("".equals(text)) {
488                     text = " ";
489                 }
490                 setText(text);
491             }
492 
493             // The renderer component should inherit the enabled and
494             // orientation state of its parent combobox.  This is
495             // especially needed for GTK comboboxes, where the
496             // ListCellRenderer's state determines the visual state
497             // of the combobox.
498             if (comboBox != null){
499                 setEnabled(comboBox.isEnabled());
500                 setComponentOrientation(comboBox.getComponentOrientation());
501             }
502 
503             return this;
504         }
505 
506         @Override
paint(Graphics g)507         public void paint(Graphics g) {
508             super.paint(g);
509             SynthLookAndFeel.resetSelectedUI();
510         }
511     }
512 
513 
514     private static class SynthComboBoxEditor
515             extends BasicComboBoxEditor.UIResource {
516 
createEditorComponent()517         @Override public JTextField createEditorComponent() {
518             JTextField f = new JTextField("", 9);
519             f.setName("ComboBox.textField");
520             return f;
521         }
522     }
523 
524 
525     /**
526      * Handles all the logic for treating the combo as a button when it is
527      * not editable, and when shouldActLikeButton() is true. This class is a
528      * special ButtonModel, and installed on the arrowButton when appropriate.
529      * It also is installed as a mouse listener and mouse motion listener on
530      * the combo box. In this way, the state between the button and combo
531      * are in sync. Whenever one is "over" both are. Whenever one is pressed,
532      * both are.
533      */
534     @SuppressWarnings("serial") // Superclass is not serializable across versions
535     private final class ButtonHandler extends DefaultButtonModel
536             implements MouseListener, PopupMenuListener {
537         /**
538          * Indicates that the mouse is over the combo or the arrow button.
539          * This field only has meaning if buttonWhenNotEnabled is true.
540          */
541         private boolean over;
542         /**
543          * Indicates that the combo or arrow button has been pressed. This
544          * field only has meaning if buttonWhenNotEnabled is true.
545          */
546         private boolean pressed;
547 
548         //------------------------------------------------------------------
549         // State Methods
550         //------------------------------------------------------------------
551 
552         /**
553          * <p>Updates the internal "pressed" state. If shouldActLikeButton()
554          * is true, and if this method call will change the internal state,
555          * then the combo and button will be repainted.</p>
556          *
557          * <p>Note that this method is called either when a press event
558          * occurs on the combo box, or on the arrow button.</p>
559          */
updatePressed(boolean p)560         private void updatePressed(boolean p) {
561             this.pressed = p && isEnabled();
562             if (shouldActLikeButton()) {
563                 comboBox.repaint();
564             }
565         }
566 
567         /**
568          * <p>Updates the internal "over" state. If shouldActLikeButton()
569          * is true, and if this method call will change the internal state,
570          * then the combo and button will be repainted.</p>
571          *
572          * <p>Note that this method is called either when a mouseover/mouseoff event
573          * occurs on the combo box, or on the arrow button.</p>
574          */
updateOver(boolean o)575         private void updateOver(boolean o) {
576             boolean old = isRollover();
577             this.over = o && isEnabled();
578             boolean newo = isRollover();
579             if (shouldActLikeButton() && old != newo) {
580                 comboBox.repaint();
581             }
582         }
583 
584         //------------------------------------------------------------------
585         // DefaultButtonModel Methods
586         //------------------------------------------------------------------
587 
588         /**
589          * @inheritDoc
590          *
591          * Ensures that isPressed() will return true if the combo is pressed,
592          * or the arrowButton is pressed, <em>or</em> if the combo popup is
593          * visible. This is the case because a combo box looks pressed when
594          * the popup is visible, and so should the arrow button.
595          */
596         @Override
isPressed()597         public boolean isPressed() {
598             boolean b = shouldActLikeButton() ? pressed : super.isPressed();
599             return b || (pressedWhenPopupVisible && comboBox.isPopupVisible());
600         }
601 
602         /**
603          * @inheritDoc
604          *
605          * Ensures that the armed state is in sync with the pressed state
606          * if shouldActLikeButton is true. Without this method, the arrow
607          * button will not look pressed when the popup is open, regardless
608          * of the result of isPressed() alone.
609          */
610         @Override
isArmed()611         public boolean isArmed() {
612             boolean b = shouldActLikeButton() ||
613                         (pressedWhenPopupVisible && comboBox.isPopupVisible());
614             return b ? isPressed() : super.isArmed();
615         }
616 
617         /**
618          * @inheritDoc
619          *
620          * Ensures that isRollover() will return true if the combo is
621          * rolled over, or the arrowButton is rolled over.
622          */
623         @Override
isRollover()624         public boolean isRollover() {
625             return shouldActLikeButton() ? over : super.isRollover();
626         }
627 
628         /**
629          * @inheritDoc
630          *
631          * Forwards pressed states to the internal "pressed" field
632          */
633         @Override
setPressed(boolean b)634         public void setPressed(boolean b) {
635             super.setPressed(b);
636             updatePressed(b);
637         }
638 
639         /**
640          * @inheritDoc
641          *
642          * Forwards rollover states to the internal "over" field
643          */
644         @Override
setRollover(boolean b)645         public void setRollover(boolean b) {
646             super.setRollover(b);
647             updateOver(b);
648         }
649 
650         //------------------------------------------------------------------
651         // MouseListener/MouseMotionListener Methods
652         //------------------------------------------------------------------
653 
654         @Override
mouseEntered(MouseEvent mouseEvent)655         public void mouseEntered(MouseEvent mouseEvent) {
656             updateOver(true);
657         }
658 
659         @Override
mouseExited(MouseEvent mouseEvent)660         public void mouseExited(MouseEvent mouseEvent) {
661             updateOver(false);
662         }
663 
664         @Override
mousePressed(MouseEvent mouseEvent)665         public void mousePressed(MouseEvent mouseEvent) {
666             updatePressed(true);
667         }
668 
669         @Override
mouseReleased(MouseEvent mouseEvent)670         public void mouseReleased(MouseEvent mouseEvent) {
671             updatePressed(false);
672         }
673 
674         @Override
mouseClicked(MouseEvent e)675         public void mouseClicked(MouseEvent e) {}
676 
677         //------------------------------------------------------------------
678         // PopupMenuListener Methods
679         //------------------------------------------------------------------
680 
681         /**
682          * @inheritDoc
683          *
684          * Ensures that the combo box is repainted when the popup is closed.
685          * This avoids a bug where clicking off the combo wasn't causing a repaint,
686          * and thus the combo box still looked pressed even when it was not.
687          *
688          * This bug was only noticed when acting as a button, but may be generally
689          * present. If so, remove the if() block
690          */
691         @Override
popupMenuCanceled(PopupMenuEvent e)692         public void popupMenuCanceled(PopupMenuEvent e) {
693             if (shouldActLikeButton() || pressedWhenPopupVisible) {
694                 comboBox.repaint();
695             }
696         }
697 
698         @Override
popupMenuWillBecomeVisible(PopupMenuEvent e)699         public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}
700         @Override
popupMenuWillBecomeInvisible(PopupMenuEvent e)701         public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}
702     }
703 
704     /**
705      * Handler for repainting combo when editor component gains/looses focus
706      */
707     private static class EditorFocusHandler implements FocusListener,
708             PropertyChangeListener {
709         private JComboBox<?> comboBox;
710         private ComboBoxEditor editor = null;
711         private Component editorComponent = null;
712 
EditorFocusHandler(JComboBox<?> comboBox)713         private EditorFocusHandler(JComboBox<?> comboBox) {
714             this.comboBox = comboBox;
715             editor = comboBox.getEditor();
716             if (editor != null){
717                 editorComponent = editor.getEditorComponent();
718                 if (editorComponent != null){
719                     editorComponent.addFocusListener(this);
720                 }
721             }
722             comboBox.addPropertyChangeListener("editor",this);
723         }
724 
unregister()725         public void unregister(){
726             comboBox.removePropertyChangeListener(this);
727             if (editorComponent!=null){
728                 editorComponent.removeFocusListener(this);
729             }
730         }
731 
732         /** Invoked when a component gains the keyboard focus. */
focusGained(FocusEvent e)733         public void focusGained(FocusEvent e) {
734             // repaint whole combo on focus gain
735             comboBox.repaint();
736         }
737 
738         /** Invoked when a component loses the keyboard focus. */
focusLost(FocusEvent e)739         public void focusLost(FocusEvent e) {
740             // repaint whole combo on focus loss
741             comboBox.repaint();
742         }
743 
744         /**
745          * Called when the combos editor changes
746          *
747          * @param evt A PropertyChangeEvent object describing the event source and
748          *            the property that has changed.
749          */
propertyChange(PropertyChangeEvent evt)750         public void propertyChange(PropertyChangeEvent evt) {
751             ComboBoxEditor newEditor = comboBox.getEditor();
752             if (editor != newEditor){
753                 if (editorComponent!=null){
754                     editorComponent.removeFocusListener(this);
755                 }
756                 editor = newEditor;
757                 if (editor != null){
758                     editorComponent = editor.getEditorComponent();
759                     if (editorComponent != null){
760                         editorComponent.addFocusListener(this);
761                     }
762                 }
763             }
764         }
765     }
766 }
767