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