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      */
 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
 113     public void installUI(JComponent c) {
 114         buttonHandler = new ButtonHandler();
 115         super.installUI(c);
 116     }
 117 
 118     @Override
 119     protected void installDefaults() {
 120         updateStyle(comboBox);
 121     }
 122 
 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         context.dispose();
 148 
 149         if(listBox != null) {
 150             SynthLookAndFeel.updateStyles(listBox);
 151         }
 152     }
 153 
 154     /**
 155      * {@inheritDoc}
 156      */
 157     @Override
 158     protected void installListeners() {
 159         comboBox.addPropertyChangeListener(this);
 160         comboBox.addMouseListener(buttonHandler);
 161         editorFocusHandler = new EditorFocusHandler(comboBox);
 162         super.installListeners();
 163     }
 164 
 165     /**
 166      * {@inheritDoc}
 167      */
 168     @Override
 169     public void uninstallUI(JComponent c) {
 170         if (popup instanceof SynthComboPopup) {
 171             ((SynthComboPopup)popup).removePopupMenuListener(buttonHandler);
 172         }
 173         super.uninstallUI(c);
 174         buttonHandler = null;
 175     }
 176 
 177     /**
 178      * {@inheritDoc}
 179      */
 180     @Override
 181     protected void uninstallDefaults() {
 182         SynthContext context = getContext(comboBox, ENABLED);
 183 
 184         style.uninstallDefaults(context);
 185         context.dispose();
 186         style = null;
 187     }
 188 
 189     /**
 190      * {@inheritDoc}
 191      */
 192     @Override
 193     protected void uninstallListeners() {
 194         editorFocusHandler.unregister();
 195         comboBox.removePropertyChangeListener(this);
 196         comboBox.removeMouseListener(buttonHandler);
 197         buttonHandler.pressed = false;
 198         buttonHandler.over = false;
 199         super.uninstallListeners();
 200     }
 201 
 202     /**
 203      * {@inheritDoc}
 204      */
 205     @Override
 206     public SynthContext getContext(JComponent c) {
 207         return getContext(c, getComponentState(c));
 208     }
 209 
 210     private SynthContext getContext(JComponent c, int state) {
 211         return SynthContext.getContext(SynthContext.class, c,
 212                     SynthLookAndFeel.getRegion(c), style, state);
 213     }
 214 
 215     private int getComponentState(JComponent c) {
 216         // currently we have a broken situation where if a developer
 217         // takes the border from a JComboBox and sets it on a JTextField
 218         // then the codepath will eventually lead back to this method
 219         // but pass in a JTextField instead of JComboBox! In case this
 220         // happens, we just return the normal synth state for the component
 221         // instead of doing anything special
 222         if (!(c instanceof JComboBox)) return SynthLookAndFeel.getComponentState(c);
 223 
 224         JComboBox box = (JComboBox)c;
 225         if (shouldActLikeButton()) {
 226             int state = ENABLED;
 227             if ((!c.isEnabled())) {
 228                 state = DISABLED;
 229             }
 230             if (buttonHandler.isPressed()) {
 231                 state |= PRESSED;
 232             }
 233             if (buttonHandler.isRollover()) {
 234                 state |= MOUSE_OVER;
 235             }
 236             if (box.isFocusOwner()) {
 237                 state |= FOCUSED;
 238             }
 239             return state;
 240         } else {
 241             // for editable combos the editor component has the focus not the
 242             // combo box its self, so we should make the combo paint focused
 243             // when its editor has focus
 244             int basicState = SynthLookAndFeel.getComponentState(c);
 245             if (box.isEditable() &&
 246                      box.getEditor().getEditorComponent().isFocusOwner()) {
 247                 basicState |= FOCUSED;
 248             }
 249             return basicState;
 250         }
 251     }
 252 
 253     /**
 254      * {@inheritDoc}
 255      */
 256     @Override
 257     protected ComboPopup createPopup() {
 258         SynthComboPopup p = new SynthComboPopup(comboBox);
 259         p.addPopupMenuListener(buttonHandler);
 260         return p;
 261     }
 262 
 263     /**
 264      * {@inheritDoc}
 265      */
 266     @Override
 267     protected ListCellRenderer createRenderer() {
 268         return new SynthComboBoxRenderer();
 269     }
 270 
 271     /**
 272      * {@inheritDoc}
 273      */
 274     @Override
 275     protected ComboBoxEditor createEditor() {
 276         return new SynthComboBoxEditor();
 277     }
 278 
 279     //
 280     // end UI Initialization
 281     //======================
 282 
 283     /**
 284      * {@inheritDoc}
 285      */
 286     @Override
 287     public void propertyChange(PropertyChangeEvent e) {
 288         if (SynthLookAndFeel.shouldUpdateStyle(e)) {
 289             updateStyle(comboBox);
 290         }
 291     }
 292 
 293     /**
 294      * {@inheritDoc}
 295      */
 296     @Override
 297     protected JButton createArrowButton() {
 298         SynthArrowButton button = new SynthArrowButton(SwingConstants.SOUTH);
 299         button.setName("ComboBox.arrowButton");
 300         button.setModel(buttonHandler);
 301         return button;
 302     }
 303 
 304     //=================================
 305     // begin ComponentUI Implementation
 306 
 307     /**
 308      * Notifies this UI delegate to repaint the specified component.
 309      * This method paints the component background, then calls
 310      * the {@link #paint(SynthContext,Graphics)} method.
 311      *
 312      * <p>In general, this method does not need to be overridden by subclasses.
 313      * All Look and Feel rendering code should reside in the {@code paint} method.
 314      *
 315      * @param g the {@code Graphics} object used for painting
 316      * @param c the component being painted
 317      * @see #paint(SynthContext,Graphics)
 318      */
 319     @Override
 320     public void update(Graphics g, JComponent c) {
 321         SynthContext context = getContext(c);
 322 
 323         SynthLookAndFeel.update(context, g);
 324         context.getPainter().paintComboBoxBackground(context, g, 0, 0,
 325                                                   c.getWidth(), c.getHeight());
 326         paint(context, g);
 327         context.dispose();
 328     }
 329 
 330     /**
 331      * Paints the specified component according to the Look and Feel.
 332      * <p>This method is not used by Synth Look and Feel.
 333      * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
 334      *
 335      * @param g the {@code Graphics} object used for painting
 336      * @param c the component being painted
 337      * @see #paint(SynthContext,Graphics)
 338      */
 339     @Override
 340     public void paint(Graphics g, JComponent c) {
 341         SynthContext context = getContext(c);
 342 
 343         paint(context, g);
 344         context.dispose();
 345     }
 346 
 347     /**
 348      * Paints the specified component.
 349      *
 350      * @param context context for the component being painted
 351      * @param g the {@code Graphics} object used for painting
 352      * @see #update(Graphics,JComponent)
 353      */
 354     protected void paint(SynthContext context, Graphics g) {
 355         hasFocus = comboBox.hasFocus();
 356         if ( !comboBox.isEditable() ) {
 357             Rectangle r = rectangleForCurrentValue();
 358             paintCurrentValue(g,r,hasFocus);
 359         }
 360     }
 361 
 362     /**
 363      * {@inheritDoc}
 364      */
 365     @Override
 366     public void paintBorder(SynthContext context, Graphics g, int x,
 367                             int y, int w, int h) {
 368         context.getPainter().paintComboBoxBorder(context, g, x, y, w, h);
 369     }
 370 
 371     /**
 372      * Paints the currently selected item.
 373      */
 374     @Override
 375     public void paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus) {
 376         ListCellRenderer renderer = comboBox.getRenderer();
 377         Component c;
 378 
 379         c = renderer.getListCellRendererComponent(
 380                 listBox, comboBox.getSelectedItem(), -1, false, false );
 381 
 382         // Fix for 4238829: should lay out the JPanel.
 383         boolean shouldValidate = false;
 384         if (c instanceof JPanel)  {
 385             shouldValidate = true;
 386         }
 387 
 388         if (c instanceof UIResource) {
 389             c.setName("ComboBox.renderer");
 390         }
 391 
 392         boolean force = forceOpaque && c instanceof JComponent;
 393         if (force) {
 394             ((JComponent)c).setOpaque(false);
 395         }
 396 
 397         int x = bounds.x, y = bounds.y, w = bounds.width, h = bounds.height;
 398         if (padding != null) {
 399             x = bounds.x + padding.left;
 400             y = bounds.y + padding.top;
 401             w = bounds.width - (padding.left + padding.right);
 402             h = bounds.height - (padding.top + padding.bottom);
 403         }
 404 
 405         currentValuePane.paintComponent(g, c, comboBox, x, y, w, h, shouldValidate);
 406 
 407         if (force) {
 408             ((JComponent)c).setOpaque(true);
 409         }
 410     }
 411 
 412     /**
 413      * @return true if this combo box should act as one big button. Typically
 414      * only happens when buttonWhenNotEditable is true, and comboBox.isEditable
 415      * is false.
 416      */
 417     private boolean shouldActLikeButton() {
 418         return buttonWhenNotEditable && !comboBox.isEditable();
 419     }
 420 
 421     /**
 422      * Returns the default size of an empty display area of the combo box using
 423      * the current renderer and font.
 424      *
 425      * This method was overridden to use SynthComboBoxRenderer instead of
 426      * DefaultListCellRenderer as the default renderer when calculating the
 427      * size of the combo box. This is used in the case of the combo not having
 428      * any data.
 429      *
 430      * @return the size of an empty display area
 431      * @see #getDisplaySize
 432      */
 433     @Override
 434     protected Dimension getDefaultSize() {
 435         SynthComboBoxRenderer r = new SynthComboBoxRenderer();
 436         Dimension d = getSizeForComponent(r.getListCellRendererComponent(listBox, " ", -1, false, false));
 437         return new Dimension(d.width, d.height);
 438     }
 439 
 440     /**
 441      * From BasicComboBoxRenderer v 1.18.
 442      *
 443      * Be aware that SynthFileChooserUIImpl relies on the fact that the default
 444      * renderer installed on a Synth combo box is a JLabel. If this is changed,
 445      * then an assert will fail in SynthFileChooserUIImpl
 446      */
 447     @SuppressWarnings("serial") // Superclass is not serializable across versions
 448     private class SynthComboBoxRenderer extends JLabel implements ListCellRenderer<Object>, UIResource {
 449         public SynthComboBoxRenderer() {
 450             super();
 451             setText(" ");
 452         }
 453 
 454         @Override
 455         public String getName() {
 456             // SynthComboBoxRenderer should have installed Name while constructor is working.
 457             // The setName invocation in the SynthComboBoxRenderer() constructor doesn't work
 458             // because of the opaque property is installed in the constructor based on the
 459             // component name (see GTKStyle.isOpaque())
 460             String name = super.getName();
 461 
 462             return name == null ? "ComboBox.renderer" : name;
 463         }
 464 
 465         @Override
 466         public Component getListCellRendererComponent(JList<?> list, Object value,
 467                          int index, boolean isSelected, boolean cellHasFocus) {
 468             setName("ComboBox.listRenderer");
 469             SynthLookAndFeel.resetSelectedUI();
 470             if (isSelected) {
 471                 setBackground(list.getSelectionBackground());
 472                 setForeground(list.getSelectionForeground());
 473                 if (!useListColors) {
 474                     SynthLookAndFeel.setSelectedUI(
 475                          (SynthLabelUI)SynthLookAndFeel.getUIOfType(getUI(),
 476                          SynthLabelUI.class), isSelected, cellHasFocus,
 477                          list.isEnabled(), false);
 478                 }
 479             } else {
 480                 setBackground(list.getBackground());
 481                 setForeground(list.getForeground());
 482             }
 483 
 484             setFont(list.getFont());
 485 
 486             if (value instanceof Icon) {
 487                 setIcon((Icon)value);
 488                 setText("");
 489             } else {
 490                 String text = (value == null) ? " " : value.toString();
 491 
 492                 if ("".equals(text)) {
 493                     text = " ";
 494                 }
 495                 setText(text);
 496             }
 497 
 498             // The renderer component should inherit the enabled and
 499             // orientation state of its parent combobox.  This is
 500             // especially needed for GTK comboboxes, where the
 501             // ListCellRenderer's state determines the visual state
 502             // of the combobox.
 503             if (comboBox != null){
 504                 setEnabled(comboBox.isEnabled());
 505                 setComponentOrientation(comboBox.getComponentOrientation());
 506             }
 507 
 508             return this;
 509         }
 510 
 511         @Override
 512         public void paint(Graphics g) {
 513             super.paint(g);
 514             SynthLookAndFeel.resetSelectedUI();
 515         }
 516     }
 517 
 518 
 519     private static class SynthComboBoxEditor
 520             extends BasicComboBoxEditor.UIResource {
 521 
 522         @Override public JTextField createEditorComponent() {
 523             JTextField f = new JTextField("", 9);
 524             f.setName("ComboBox.textField");
 525             return f;
 526         }
 527     }
 528 
 529 
 530     /**
 531      * Handles all the logic for treating the combo as a button when it is
 532      * not editable, and when shouldActLikeButton() is true. This class is a
 533      * special ButtonModel, and installed on the arrowButton when appropriate.
 534      * It also is installed as a mouse listener and mouse motion listener on
 535      * the combo box. In this way, the state between the button and combo
 536      * are in sync. Whenever one is "over" both are. Whenever one is pressed,
 537      * both are.
 538      */
 539     @SuppressWarnings("serial") // Superclass is not serializable across versions
 540     private final class ButtonHandler extends DefaultButtonModel
 541             implements MouseListener, PopupMenuListener {
 542         /**
 543          * Indicates that the mouse is over the combo or the arrow button.
 544          * This field only has meaning if buttonWhenNotEnabled is true.
 545          */
 546         private boolean over;
 547         /**
 548          * Indicates that the combo or arrow button has been pressed. This
 549          * field only has meaning if buttonWhenNotEnabled is true.
 550          */
 551         private boolean pressed;
 552 
 553         //------------------------------------------------------------------
 554         // State Methods
 555         //------------------------------------------------------------------
 556 
 557         /**
 558          * <p>Updates the internal "pressed" state. If shouldActLikeButton()
 559          * is true, and if this method call will change the internal state,
 560          * then the combo and button will be repainted.</p>
 561          *
 562          * <p>Note that this method is called either when a press event
 563          * occurs on the combo box, or on the arrow button.</p>
 564          */
 565         private void updatePressed(boolean p) {
 566             this.pressed = p && isEnabled();
 567             if (shouldActLikeButton()) {
 568                 comboBox.repaint();
 569             }
 570         }
 571 
 572         /**
 573          * <p>Updates the internal "over" state. If shouldActLikeButton()
 574          * is true, and if this method call will change the internal state,
 575          * then the combo and button will be repainted.</p>
 576          *
 577          * <p>Note that this method is called either when a mouseover/mouseoff event
 578          * occurs on the combo box, or on the arrow button.</p>
 579          */
 580         private void updateOver(boolean o) {
 581             boolean old = isRollover();
 582             this.over = o && isEnabled();
 583             boolean newo = isRollover();
 584             if (shouldActLikeButton() && old != newo) {
 585                 comboBox.repaint();
 586             }
 587         }
 588 
 589         //------------------------------------------------------------------
 590         // DefaultButtonModel Methods
 591         //------------------------------------------------------------------
 592 
 593         /**
 594          * @inheritDoc
 595          *
 596          * Ensures that isPressed() will return true if the combo is pressed,
 597          * or the arrowButton is pressed, <em>or</em> if the combo popup is
 598          * visible. This is the case because a combo box looks pressed when
 599          * the popup is visible, and so should the arrow button.
 600          */
 601         @Override
 602         public boolean isPressed() {
 603             boolean b = shouldActLikeButton() ? pressed : super.isPressed();
 604             return b || (pressedWhenPopupVisible && comboBox.isPopupVisible());
 605         }
 606 
 607         /**
 608          * @inheritDoc
 609          *
 610          * Ensures that the armed state is in sync with the pressed state
 611          * if shouldActLikeButton is true. Without this method, the arrow
 612          * button will not look pressed when the popup is open, regardless
 613          * of the result of isPressed() alone.
 614          */
 615         @Override
 616         public boolean isArmed() {
 617             boolean b = shouldActLikeButton() ||
 618                         (pressedWhenPopupVisible && comboBox.isPopupVisible());
 619             return b ? isPressed() : super.isArmed();
 620         }
 621 
 622         /**
 623          * @inheritDoc
 624          *
 625          * Ensures that isRollover() will return true if the combo is
 626          * rolled over, or the arrowButton is rolled over.
 627          */
 628         @Override
 629         public boolean isRollover() {
 630             return shouldActLikeButton() ? over : super.isRollover();
 631         }
 632 
 633         /**
 634          * @inheritDoc
 635          *
 636          * Forwards pressed states to the internal "pressed" field
 637          */
 638         @Override
 639         public void setPressed(boolean b) {
 640             super.setPressed(b);
 641             updatePressed(b);
 642         }
 643 
 644         /**
 645          * @inheritDoc
 646          *
 647          * Forwards rollover states to the internal "over" field
 648          */
 649         @Override
 650         public void setRollover(boolean b) {
 651             super.setRollover(b);
 652             updateOver(b);
 653         }
 654 
 655         //------------------------------------------------------------------
 656         // MouseListener/MouseMotionListener Methods
 657         //------------------------------------------------------------------
 658 
 659         @Override
 660         public void mouseEntered(MouseEvent mouseEvent) {
 661             updateOver(true);
 662         }
 663 
 664         @Override
 665         public void mouseExited(MouseEvent mouseEvent) {
 666             updateOver(false);
 667         }
 668 
 669         @Override
 670         public void mousePressed(MouseEvent mouseEvent) {
 671             updatePressed(true);
 672         }
 673 
 674         @Override
 675         public void mouseReleased(MouseEvent mouseEvent) {
 676             updatePressed(false);
 677         }
 678 
 679         @Override
 680         public void mouseClicked(MouseEvent e) {}
 681 
 682         //------------------------------------------------------------------
 683         // PopupMenuListener Methods
 684         //------------------------------------------------------------------
 685 
 686         /**
 687          * @inheritDoc
 688          *
 689          * Ensures that the combo box is repainted when the popup is closed.
 690          * This avoids a bug where clicking off the combo wasn't causing a repaint,
 691          * and thus the combo box still looked pressed even when it was not.
 692          *
 693          * This bug was only noticed when acting as a button, but may be generally
 694          * present. If so, remove the if() block
 695          */
 696         @Override
 697         public void popupMenuCanceled(PopupMenuEvent e) {
 698             if (shouldActLikeButton() || pressedWhenPopupVisible) {
 699                 comboBox.repaint();
 700             }
 701         }
 702 
 703         @Override
 704         public void popupMenuWillBecomeVisible(PopupMenuEvent e) {}
 705         @Override
 706         public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}
 707     }
 708 
 709     /**
 710      * Handler for repainting combo when editor component gains/looses focus
 711      */
 712     private static class EditorFocusHandler implements FocusListener,
 713             PropertyChangeListener {
 714         private JComboBox comboBox;
 715         private ComboBoxEditor editor = null;
 716         private Component editorComponent = null;
 717 
 718         private EditorFocusHandler(JComboBox comboBox) {
 719             this.comboBox = comboBox;
 720             editor = comboBox.getEditor();
 721             if (editor != null){
 722                 editorComponent = editor.getEditorComponent();
 723                 if (editorComponent != null){
 724                     editorComponent.addFocusListener(this);
 725                 }
 726             }
 727             comboBox.addPropertyChangeListener("editor",this);
 728         }
 729 
 730         public void unregister(){
 731             comboBox.removePropertyChangeListener(this);
 732             if (editorComponent!=null){
 733                 editorComponent.removeFocusListener(this);
 734             }
 735         }
 736 
 737         /** Invoked when a component gains the keyboard focus. */
 738         public void focusGained(FocusEvent e) {
 739             // repaint whole combo on focus gain
 740             comboBox.repaint();
 741         }
 742 
 743         /** Invoked when a component loses the keyboard focus. */
 744         public void focusLost(FocusEvent e) {
 745             // repaint whole combo on focus loss
 746             comboBox.repaint();
 747         }
 748 
 749         /**
 750          * Called when the combos editor changes
 751          *
 752          * @param evt A PropertyChangeEvent object describing the event source and
 753          *            the property that has changed.
 754          */
 755         public void propertyChange(PropertyChangeEvent evt) {
 756             ComboBoxEditor newEditor = comboBox.getEditor();
 757             if (editor != newEditor){
 758                 if (editorComponent!=null){
 759                     editorComponent.removeFocusListener(this);
 760                 }
 761                 editor = newEditor;
 762                 if (editor != null){
 763                     editorComponent = editor.getEditorComponent();
 764                     if (editorComponent != null){
 765                         editorComponent.addFocusListener(this);
 766                     }
 767                 }
 768             }
 769         }
 770     }
 771 }