Source for javax.swing.plaf.basic.BasicComboBoxUI

   1: /* BasicComboBoxUI.java --
   2:    Copyright (C) 2004, 2005, 2006,  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.plaf.basic;
  40: 
  41: import java.awt.Color;
  42: import java.awt.Component;
  43: import java.awt.Container;
  44: import java.awt.Dimension;
  45: import java.awt.Font;
  46: import java.awt.Graphics;
  47: import java.awt.Insets;
  48: import java.awt.LayoutManager;
  49: import java.awt.Rectangle;
  50: import java.awt.event.FocusEvent;
  51: import java.awt.event.FocusListener;
  52: import java.awt.event.ItemEvent;
  53: import java.awt.event.ItemListener;
  54: import java.awt.event.KeyAdapter;
  55: import java.awt.event.KeyEvent;
  56: import java.awt.event.KeyListener;
  57: import java.awt.event.MouseListener;
  58: import java.awt.event.MouseMotionListener;
  59: import java.beans.PropertyChangeEvent;
  60: import java.beans.PropertyChangeListener;
  61: 
  62: import javax.accessibility.Accessible;
  63: import javax.accessibility.AccessibleContext;
  64: import javax.swing.CellRendererPane;
  65: import javax.swing.ComboBoxEditor;
  66: import javax.swing.ComboBoxModel;
  67: import javax.swing.DefaultListCellRenderer;
  68: import javax.swing.InputMap;
  69: import javax.swing.JButton;
  70: import javax.swing.JComboBox;
  71: import javax.swing.JComponent;
  72: import javax.swing.JList;
  73: import javax.swing.ListCellRenderer;
  74: import javax.swing.LookAndFeel;
  75: import javax.swing.SwingUtilities;
  76: import javax.swing.UIManager;
  77: import javax.swing.event.ListDataEvent;
  78: import javax.swing.event.ListDataListener;
  79: import javax.swing.plaf.ComboBoxUI;
  80: import javax.swing.plaf.ComponentUI;
  81: import javax.swing.plaf.UIResource;
  82: 
  83: /**
  84:  * A UI delegate for the {@link JComboBox} component.
  85:  *
  86:  * @author Olga Rodimina
  87:  * @author Robert Schuster
  88:  */
  89: public class BasicComboBoxUI extends ComboBoxUI
  90: {
  91:   /**
  92:    * The arrow button that is displayed in the right side of JComboBox. This
  93:    * button is used to hide and show combo box's list of items.
  94:    */
  95:   protected JButton arrowButton;
  96: 
  97:   /**
  98:    * The combo box represented by this UI delegate.
  99:    */
 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: 
 219:         // Set editor and renderer for the combo box. Editor is used
 220:         // only if combo box becomes editable, otherwise renderer is used
 221:         // to paint the selected item; combobox is not editable by default.
 222:         ListCellRenderer renderer = comboBox.getRenderer();
 223:         if (renderer == null || renderer instanceof UIResource)
 224:           comboBox.setRenderer(createRenderer());
 225: 
 226:         ComboBoxEditor currentEditor = comboBox.getEditor();
 227:         if (currentEditor == null || currentEditor instanceof UIResource)
 228:           {
 229:             currentEditor = createEditor();
 230:             comboBox.setEditor(currentEditor);
 231:           } 
 232:         editor = currentEditor.getEditorComponent();
 233: 
 234:         installComponents();
 235:         installListeners();
 236:         if (arrowButton != null)
 237:           configureArrowButton();
 238:         if (editor != null)
 239:           configureEditor();
 240:         comboBox.setLayout(createLayoutManager());
 241:         comboBox.setFocusable(true);
 242:         installKeyboardActions();
 243:         comboBox.putClientProperty(BasicLookAndFeel.DONT_CANCEL_POPUP,
 244:                                    Boolean.TRUE);
 245:       }
 246:   }
 247: 
 248:   /**
 249:    * Uninstalls the UI for the given {@link JComponent}.
 250:    *
 251:    * @param c The JComponent that is having this UI removed.
 252:    * 
 253:    * @see #installUI(JComponent)
 254:    */
 255:   public void uninstallUI(JComponent c)
 256:   {
 257:     setPopupVisible(comboBox, false);
 258:     popup.uninstallingUI();
 259:     uninstallKeyboardActions();
 260:     comboBox.setLayout(null);
 261:     uninstallComponents();
 262:     uninstallListeners();
 263:     uninstallDefaults();
 264:     comboBox = null;
 265:   }
 266: 
 267:   /**
 268:    * Installs the defaults that are defined in the {@link BasicLookAndFeel} 
 269:    * for this {@link JComboBox}.
 270:    * 
 271:    * @see #uninstallDefaults()
 272:    */
 273:   protected void installDefaults()
 274:   {
 275:     LookAndFeel.installColorsAndFont(comboBox, "ComboBox.background",
 276:                                      "ComboBox.foreground", "ComboBox.font");
 277:     LookAndFeel.installBorder(comboBox, "ComboBox.border");
 278:   }
 279: 
 280:   /**
 281:    * Creates and installs the listeners for this UI.
 282:    * 
 283:    * @see #uninstallListeners()
 284:    */
 285:   protected void installListeners()
 286:   {
 287:     // install combo box's listeners
 288:     propertyChangeListener = createPropertyChangeListener();
 289:     comboBox.addPropertyChangeListener(propertyChangeListener);
 290: 
 291:     focusListener = createFocusListener();
 292:     comboBox.addFocusListener(focusListener);
 293: 
 294:     itemListener = createItemListener();
 295:     comboBox.addItemListener(itemListener);
 296: 
 297:     keyListener = createKeyListener();
 298:     comboBox.addKeyListener(keyListener);
 299: 
 300:     // install listeners that listen to combo box model
 301:     listDataListener = createListDataListener();
 302:     comboBox.getModel().addListDataListener(listDataListener);
 303: 
 304:     // Install mouse and key listeners from the popup.
 305:     popupMouseListener = popup.getMouseListener();
 306:     comboBox.addMouseListener(popupMouseListener);
 307: 
 308:     popupMouseMotionListener = popup.getMouseMotionListener();
 309:     comboBox.addMouseMotionListener(popupMouseMotionListener);
 310: 
 311:     popupKeyListener = popup.getKeyListener();
 312:     comboBox.addKeyListener(popupKeyListener);
 313:   }
 314: 
 315:   /**
 316:    * Uninstalls the defaults and sets any objects created during
 317:    * install to <code>null</code>.
 318:    * 
 319:    * @see #installDefaults()
 320:    */
 321:   protected void uninstallDefaults()
 322:   {
 323:     if (comboBox.getFont() instanceof UIResource)
 324:       comboBox.setFont(null);
 325: 
 326:     if (comboBox.getForeground() instanceof UIResource)
 327:       comboBox.setForeground(null);
 328:     
 329:     if (comboBox.getBackground() instanceof UIResource)
 330:       comboBox.setBackground(null);
 331: 
 332:     LookAndFeel.uninstallBorder(comboBox);
 333:   }
 334: 
 335:   /**
 336:    * Detaches all the listeners we attached in {@link #installListeners}.
 337:    * 
 338:    * @see #installListeners()
 339:    */
 340:   protected void uninstallListeners()
 341:   {
 342:     comboBox.removePropertyChangeListener(propertyChangeListener);
 343:     propertyChangeListener = null;
 344: 
 345:     comboBox.removeFocusListener(focusListener);
 346:     listBox.removeFocusListener(focusListener);
 347:     focusListener = null;
 348: 
 349:     comboBox.removeItemListener(itemListener);
 350:     itemListener = null;
 351: 
 352:     comboBox.removeKeyListener(keyListener);
 353:     keyListener = null;
 354: 
 355:     comboBox.getModel().removeListDataListener(listDataListener);
 356:     listDataListener = null;
 357: 
 358:     if (popupMouseListener != null)
 359:       comboBox.removeMouseListener(popupMouseListener);
 360:     popupMouseListener = null;
 361: 
 362:     if (popupMouseMotionListener != null)
 363:       comboBox.removeMouseMotionListener(popupMouseMotionListener);
 364:     popupMouseMotionListener = null;
 365: 
 366:     if (popupKeyListener != null)
 367:       comboBox.removeKeyListener(popupKeyListener);
 368:     popupKeyListener = null;
 369:   }
 370: 
 371:   /**
 372:    * Creates the popup that will contain list of combo box's items.
 373:    *
 374:    * @return popup containing list of combo box's items
 375:    */
 376:   protected ComboPopup createPopup()
 377:   {
 378:     return new BasicComboPopup(comboBox);
 379:   }
 380: 
 381:   /**
 382:    * Creates a {@link KeyListener} to listen to key events.
 383:    *
 384:    * @return KeyListener that listens to key events.
 385:    */
 386:   protected KeyListener createKeyListener()
 387:   {
 388:     return new KeyHandler();
 389:   }
 390: 
 391:   /**
 392:    * Creates the {@link FocusListener} that will listen to changes in this
 393:    * JComboBox's focus.
 394:    *
 395:    * @return the FocusListener.
 396:    */
 397:   protected FocusListener createFocusListener()
 398:   {
 399:     return new FocusHandler();
 400:   }
 401: 
 402:   /**
 403:    * Creates a {@link ListDataListener} to listen to the combo box's data model.
 404:    *
 405:    * @return The new listener.
 406:    */
 407:   protected ListDataListener createListDataListener()
 408:   {
 409:     return new ListDataHandler();
 410:   }
 411: 
 412:   /**
 413:    * Creates an {@link ItemListener} that will listen to the changes in
 414:    * the JComboBox's selection.
 415:    *
 416:    * @return The ItemListener
 417:    */
 418:   protected ItemListener createItemListener()
 419:   {
 420:     return new ItemHandler();
 421:   }
 422: 
 423:   /**
 424:    * Creates a {@link PropertyChangeListener} to listen to the changes in
 425:    * the JComboBox's bound properties.
 426:    *
 427:    * @return The PropertyChangeListener
 428:    */
 429:   protected PropertyChangeListener createPropertyChangeListener()
 430:   {
 431:     return new PropertyChangeHandler();
 432:   }
 433: 
 434:   /**
 435:    * Creates and returns a layout manager for the combo box.  Subclasses can 
 436:    * override this method to provide a different layout.
 437:    *
 438:    * @return a layout manager for the combo box.
 439:    */
 440:   protected LayoutManager createLayoutManager()
 441:   {
 442:     return new ComboBoxLayoutManager();
 443:   }
 444: 
 445:   /**
 446:    * Creates a component that will be responsible for rendering the
 447:    * selected component in the combo box.
 448:    *
 449:    * @return A renderer for the combo box.
 450:    */
 451:   protected ListCellRenderer createRenderer()
 452:   {
 453:     return new BasicComboBoxRenderer.UIResource();
 454:   }
 455: 
 456:   /**
 457:    * Creates the component that will be responsible for displaying/editing
 458:    * the selected item in the combo box. This editor is used only when combo 
 459:    * box is editable.
 460:    *
 461:    * @return A new component that will be responsible for displaying/editing
 462:    *         the selected item in the combo box.
 463:    */
 464:   protected ComboBoxEditor createEditor()
 465:   {
 466:     return new BasicComboBoxEditor.UIResource();
 467:   }
 468: 
 469:   /**
 470:    * Installs the components for this JComboBox. ArrowButton, main
 471:    * part of combo box (upper part) and popup list of items are created and
 472:    * configured here.
 473:    */
 474:   protected void installComponents()
 475:   {
 476:     // create drop down list of items
 477:     popup = createPopup();
 478:     listBox = popup.getList();
 479: 
 480:     // create and install arrow button
 481:     arrowButton = createArrowButton();
 482:     comboBox.add(arrowButton);
 483: 
 484:     if (comboBox.isEditable())
 485:       addEditor();
 486: 
 487:     comboBox.add(currentValuePane);
 488:   }
 489: 
 490:   /**
 491:    * Uninstalls components from this {@link JComboBox}.
 492:    * 
 493:    * @see #installComponents()
 494:    */
 495:   protected void uninstallComponents()
 496:   {
 497:     // uninstall arrow button
 498:     unconfigureArrowButton();
 499:     comboBox.remove(arrowButton);
 500:     arrowButton = null;
 501: 
 502:     popup = null;
 503: 
 504:     if (comboBox.getRenderer() instanceof UIResource)
 505:       comboBox.setRenderer(null);
 506: 
 507:     // if the editor is not an instanceof UIResource, it was not set by the
 508:     // UI delegate, so don't clear it...
 509:     ComboBoxEditor currentEditor = comboBox.getEditor();
 510:     if (currentEditor instanceof UIResource)
 511:       {
 512:         comboBox.setEditor(null);
 513:         editor = null;
 514:       }
 515:   }
 516: 
 517:   /**
 518:    * Adds the current editor to the combo box.
 519:    */
 520:   public void addEditor()
 521:   {
 522:     removeEditor();
 523:     editor = comboBox.getEditor().getEditorComponent();
 524:     comboBox.add(editor);
 525:   }
 526: 
 527:   /**
 528:    * Removes the current editor from the combo box.
 529:    */
 530:   public void removeEditor()
 531:   {
 532:     if (editor != null)
 533:       {
 534:         unconfigureEditor();
 535:         comboBox.remove(editor);
 536:       }
 537:   }
 538: 
 539:   /**
 540:    * Configures the editor for this combo box.
 541:    */
 542:   protected void configureEditor()
 543:   {
 544:     editor.setFont(comboBox.getFont());
 545:     if (popupKeyListener != null)
 546:       editor.addKeyListener(popupKeyListener);
 547:     if (keyListener != null)
 548:       editor.addKeyListener(keyListener);
 549:     comboBox.configureEditor(comboBox.getEditor(),
 550:                              comboBox.getSelectedItem());
 551:   }
 552: 
 553:   /**
 554:    * Unconfigures the editor for this combo box. 
 555:    */
 556:   protected void unconfigureEditor()
 557:   {
 558:     if (popupKeyListener != null)
 559:       editor.removeKeyListener(popupKeyListener);
 560:     if (keyListener != null)
 561:       editor.removeKeyListener(keyListener);
 562:   }
 563: 
 564:   /**
 565:    * Configures the arrow button.
 566:    * 
 567:    * @see #configureArrowButton()
 568:    */
 569:   public void configureArrowButton()
 570:   {
 571:     if (arrowButton != null)
 572:       {
 573:         arrowButton.setEnabled(comboBox.isEnabled());
 574:         arrowButton.setFocusable(false);
 575:         if (popupMouseListener != null)
 576:           arrowButton.addMouseListener(popupMouseListener);
 577:         if (popupMouseMotionListener != null)
 578:           arrowButton.addMouseMotionListener(popupMouseMotionListener);
 579:         
 580:         // Mark the button as not closing the popup, we handle this ourselves.
 581:         arrowButton.putClientProperty(BasicLookAndFeel.DONT_CANCEL_POPUP,
 582:                                       Boolean.TRUE);
 583:       }
 584:   }
 585: 
 586:   /**
 587:    * Unconfigures the arrow button.
 588:    * 
 589:    * @see #configureArrowButton()
 590:    *
 591:    * @specnote The specification says this method is implementation specific
 592:    *           and should not be used or overridden.
 593:    */
 594:   public void unconfigureArrowButton()
 595:   {
 596:     if (arrowButton != null)
 597:       {
 598:         if (popupMouseListener != null)
 599:           arrowButton.removeMouseListener(popupMouseListener);
 600:         if (popupMouseMotionListener != null)
 601:           arrowButton.removeMouseMotionListener(popupMouseMotionListener);
 602:       }
 603:   }
 604: 
 605:   /**
 606:    * Creates an arrow button for this {@link JComboBox}.  The arrow button is
 607:    * displayed at the right end of the combo box and is used to display/hide
 608:    * the drop down list of items.
 609:    *
 610:    * @return A new button.
 611:    */
 612:   protected JButton createArrowButton()
 613:   {
 614:     return new BasicArrowButton(BasicArrowButton.SOUTH);
 615:   }
 616: 
 617:   /**
 618:    * Returns <code>true</code> if the popup is visible, and <code>false</code>
 619:    * otherwise.
 620:    *
 621:    * @param c The JComboBox to check
 622:    *
 623:    * @return <code>true</code> if popup part of the JComboBox is visible and 
 624:    *         <code>false</code> otherwise.
 625:    */
 626:   public boolean isPopupVisible(JComboBox c)
 627:   {
 628:     return popup.isVisible();
 629:   }
 630: 
 631:   /**
 632:    * Displays/hides the {@link JComboBox}'s list of items on the screen.
 633:    *
 634:    * @param c The combo box, for which list of items should be
 635:    *        displayed/hidden
 636:    * @param v true if show popup part of the jcomboBox and false to hide.
 637:    */
 638:   public void setPopupVisible(JComboBox c, boolean v)
 639:   {
 640:     if (v)
 641:       popup.show();
 642:     else
 643:       popup.hide();
 644:   }
 645: 
 646:   /**
 647:    * JComboBox is focus traversable if it is editable and not otherwise.
 648:    *
 649:    * @param c combo box for which to check whether it is focus traversable
 650:    *
 651:    * @return true if focus tranversable and false otherwise
 652:    */
 653:   public boolean isFocusTraversable(JComboBox c)
 654:   {
 655:     if (!comboBox.isEditable())
 656:       return true;
 657: 
 658:     return false;
 659:   }
 660: 
 661:   /**
 662:    * Paints given menu item using specified graphics context
 663:    *
 664:    * @param g The graphics context used to paint this combo box
 665:    * @param c comboBox which needs to be painted.
 666:    */
 667:   public void paint(Graphics g, JComponent c)
 668:   {
 669:     hasFocus = comboBox.hasFocus();
 670:     if (! comboBox.isEditable())
 671:       {
 672:         Rectangle rect = rectangleForCurrentValue();
 673:         paintCurrentValueBackground(g, rect, hasFocus);
 674:         paintCurrentValue(g, rect, hasFocus);
 675:       }
 676:   }
 677: 
 678:   /**
 679:    * Returns preferred size for the combo box.
 680:    *
 681:    * @param c comboBox for which to get preferred size
 682:    *
 683:    * @return The preferred size for the given combo box
 684:    */
 685:   public Dimension getPreferredSize(JComponent c)
 686:   {
 687:     return getMinimumSize(c);
 688:   }
 689: 
 690:   /**
 691:    * Returns the minimum size for this {@link JComboBox} for this
 692:    * look and feel. Also makes sure cachedMinimimSize is setup correctly.
 693:    *
 694:    * @param c The {@link JComponent} to find the minimum size for.
 695:    *
 696:    * @return The dimensions of the minimum size.
 697:    */
 698:   public Dimension getMinimumSize(JComponent c)
 699:   {
 700:     if (isMinimumSizeDirty)
 701:       {
 702:         Insets i = getInsets();
 703:         Dimension d = getDisplaySize();
 704:         d.width += i.left + i.right + d.height;
 705:         cachedMinimumSize = new Dimension(d.width, d.height + i.top + i.bottom);
 706:         isMinimumSizeDirty = false;
 707:       }
 708:     return new Dimension(cachedMinimumSize);
 709:   }
 710: 
 711:   /**
 712:    * Returns the maximum size for this {@link JComboBox} for this
 713:    * look and feel.
 714:    *
 715:    * @param c The {@link JComponent} to find the maximum size for
 716:    *
 717:    * @return The maximum size (<code>Dimension(32767, 32767)</code>).
 718:    */
 719:   public Dimension getMaximumSize(JComponent c)
 720:   {
 721:     return new Dimension(32767, 32767);
 722:   }
 723: 
 724:   /**
 725:    * Returns the number of accessible children of the combobox.
 726:    *
 727:    * @param c the component (combobox) to check, ignored
 728:    *
 729:    * @return the number of accessible children of the combobox
 730:    */
 731:   public int getAccessibleChildrenCount(JComponent c)
 732:   {
 733:     int count = 1;
 734:     if (comboBox.isEditable())
 735:       count = 2;
 736:     return count;
 737:   }
 738: 
 739:   /**
 740:    * Returns the accessible child with the specified index.
 741:    *
 742:    * @param c the component, this is ignored
 743:    * @param i the index of the accessible child to return
 744:    */
 745:   public Accessible getAccessibleChild(JComponent c, int i)
 746:   {
 747:     Accessible child = null;
 748:     switch (i)
 749:     {
 750:       case 0: // The popup.
 751:         if (popup instanceof Accessible)
 752:           {
 753:             AccessibleContext ctx = ((Accessible) popup).getAccessibleContext();
 754:             ctx.setAccessibleParent(comboBox);
 755:             child = (Accessible) popup;
 756:           }
 757:         break;
 758:       case 1: // The editor, if any.
 759:         if (comboBox.isEditable() && editor instanceof Accessible)
 760:           {
 761:             AccessibleContext ctx =
 762:               ((Accessible) editor).getAccessibleContext();
 763:             ctx.setAccessibleParent(comboBox);
 764:             child = (Accessible) editor;
 765:           }
 766:         break;
 767:     }
 768:     return child;
 769:   }
 770: 
 771:   /**
 772:    * Returns true if the specified key is a navigation key and false otherwise
 773:    *
 774:    * @param keyCode a key for which to check whether it is navigation key or
 775:    *        not.
 776:    *
 777:    * @return true if the specified key is a navigation key and false otherwis
 778:    */
 779:   protected boolean isNavigationKey(int keyCode)
 780:   {
 781:     return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN
 782:            || keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT
 783:            || keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_ESCAPE
 784:            || keyCode == KeyEvent.VK_TAB;
 785:   }
 786: 
 787:   /**
 788:    * Selects next possible item relative to the current selection
 789:    * to be next selected item in the combo box.
 790:    */
 791:   protected void selectNextPossibleValue()
 792:   {
 793:     int index = comboBox.getSelectedIndex();
 794:     if (index != comboBox.getItemCount() - 1)
 795:       comboBox.setSelectedIndex(index + 1);
 796:   }
 797: 
 798:   /**
 799:    * Selects previous item relative to current selection to be
 800:    * next selected item.
 801:    */
 802:   protected void selectPreviousPossibleValue()
 803:   {
 804:     int index = comboBox.getSelectedIndex();
 805:     if (index > 0)
 806:       comboBox.setSelectedIndex(index - 1);
 807:   }
 808: 
 809:   /**
 810:    * Displays combo box popup if the popup is not currently shown
 811:    * on the screen and hides it if it is currently shown
 812:    */
 813:   protected void toggleOpenClose()
 814:   {
 815:     setPopupVisible(comboBox, ! isPopupVisible(comboBox));
 816:   }
 817: 
 818:   /**
 819:    * Returns the bounds in which comboBox's selected item will be
 820:    * displayed.
 821:    *
 822:    * @return rectangle bounds in which comboBox's selected Item will be
 823:    *         displayed
 824:    */
 825:   protected Rectangle rectangleForCurrentValue()
 826:   {
 827:     int w = comboBox.getWidth();
 828:     int h = comboBox.getHeight();
 829:     Insets i = comboBox.getInsets();
 830:     int arrowSize = h - (i.top + i.bottom);
 831:     if (arrowButton != null)
 832:       arrowSize = arrowButton.getWidth();
 833:     return new Rectangle(i.left, i.top, w - (i.left + i.right + arrowSize),
 834:                          h - (i.top + i.left));
 835:   }
 836: 
 837:   /**
 838:    * Returns the insets of the current border.
 839:    *
 840:    * @return Insets representing space between combo box and its border
 841:    */
 842:   protected Insets getInsets()
 843:   {
 844:     return comboBox.getInsets();
 845:   }
 846: 
 847:   /**
 848:    * Paints currently selected value in the main part of the combo
 849:    * box (part without popup).
 850:    *
 851:    * @param g graphics context
 852:    * @param bounds Rectangle representing the size of the area in which
 853:    *        selected item should be drawn
 854:    * @param hasFocus true if combo box has focus and false otherwise
 855:    */
 856:   public void paintCurrentValue(Graphics g, Rectangle bounds, boolean hasFocus)
 857:   {
 858:     Object currentValue = comboBox.getSelectedItem();
 859:     boolean isPressed = arrowButton.getModel().isPressed();
 860: 
 861:     /* Gets the component to be drawn for the current value.
 862:      * If there is currently no selected item we will take an empty
 863:      * String as replacement.
 864:      */
 865:     ListCellRenderer renderer = comboBox.getRenderer();
 866:     if (comboBox.getSelectedIndex() != -1)
 867:       {
 868:         Component comp;
 869:         if (hasFocus && ! isPopupVisible(comboBox))
 870:           {
 871:             comp = renderer.getListCellRendererComponent(listBox,
 872:                 comboBox.getSelectedItem(), -1, true, false);
 873:           }
 874:         else
 875:           {
 876:             comp = renderer.getListCellRendererComponent(listBox,
 877:                 comboBox.getSelectedItem(), -1, false, false);
 878:             Color bg = UIManager.getColor("ComboBox.disabledForeground");
 879:             comp.setBackground(bg);
 880:           }
 881:         comp.setFont(comboBox.getFont());
 882:         if (hasFocus && ! isPopupVisible(comboBox))
 883:           {
 884:             comp.setForeground(listBox.getSelectionForeground());
 885:             comp.setBackground(listBox.getSelectionBackground());
 886:           }
 887:         else if (comboBox.isEnabled())
 888:           {
 889:             comp.setForeground(comboBox.getForeground());
 890:             comp.setBackground(comboBox.getBackground());
 891:           }
 892:         else
 893:           {
 894:             Color fg = UIManager.getColor("ComboBox.disabledForeground");
 895:             comp.setForeground(fg);
 896:             Color bg = UIManager.getColor("ComboBox.disabledBackground");
 897:             comp.setBackground(bg);
 898:           }
 899:         currentValuePane.paintComponent(g, comp, comboBox, bounds.x, bounds.y,
 900:                                         bounds.width, bounds.height);
 901:       }
 902:   }
 903: 
 904:   /**
 905:    * Paints the background of part of the combo box, where currently
 906:    * selected value is displayed. If the combo box has focus this method
 907:    * should also paint focus rectangle around the combo box.
 908:    *
 909:    * @param g graphics context
 910:    * @param bounds Rectangle representing the size of the largest item  in the
 911:    *        comboBox
 912:    * @param hasFocus true if combo box has fox and false otherwise
 913:    */
 914:   public void paintCurrentValueBackground(Graphics g, Rectangle bounds,
 915:                                           boolean hasFocus)
 916:   {
 917:     Color saved = g.getColor();
 918:     if (comboBox.isEnabled())
 919:       g.setColor(UIManager.getColor("UIManager.background"));
 920:     else
 921:       g.setColor(UIManager.getColor("UIManager.disabledBackground"));
 922:     g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
 923:     g.setColor(saved);
 924:   }
 925: 
 926:   private static final ListCellRenderer DEFAULT_RENDERER
 927:     = new DefaultListCellRenderer();
 928: 
 929:   /**
 930:    * Returns the default size for the display area of a combo box that does 
 931:    * not contain any elements.  This method returns the width and height of
 932:    * a single space in the current font, plus a margin of 1 pixel. 
 933:    *
 934:    * @return The default display size.
 935:    * 
 936:    * @see #getDisplaySize()
 937:    */
 938:   protected Dimension getDefaultSize()
 939:   {
 940:     Component comp = DEFAULT_RENDERER.getListCellRendererComponent(listBox,
 941:         " ", -1, false, false);
 942:     currentValuePane.add(comp);
 943:     comp.setFont(comboBox.getFont());
 944:     Dimension d = comp.getPreferredSize();
 945:     currentValuePane.remove(comp);
 946:     return d;
 947:   }
 948: 
 949:   /**
 950:    * Returns the size of the display area for the combo box. This size will be 
 951:    * the size of the combo box, not including the arrowButton.
 952:    *
 953:    * @return The size of the display area for the combo box.
 954:    */
 955:   protected Dimension getDisplaySize()
 956:   {
 957:     Dimension dim = new Dimension();
 958:     ListCellRenderer renderer = comboBox.getRenderer();
 959:     if (renderer == null)
 960:       {
 961:         renderer = DEFAULT_RENDERER;
 962:       }
 963:     
 964:     Object prototype = comboBox.getPrototypeDisplayValue();
 965:     if (prototype != null)
 966:       {
 967:         Component comp = renderer.getListCellRendererComponent(listBox, 
 968:             prototype, -1, false, false);
 969:         currentValuePane.add(comp);
 970:         comp.setFont(comboBox.getFont());
 971:         Dimension renderSize = comp.getPreferredSize();
 972:         currentValuePane.remove(comp);
 973:         dim.height = renderSize.height;
 974:         dim.width = renderSize.width;
 975:       }
 976:     else
 977:       {
 978:         ComboBoxModel model = comboBox.getModel();
 979:         int size = model.getSize();
 980:         if (size > 0)
 981:           {
 982:             for (int i = 0; i < size; ++i)
 983:               {
 984:                 Component comp = renderer.getListCellRendererComponent(listBox, 
 985:                     model.getElementAt(i), -1, false, false);
 986:                 currentValuePane.add(comp);
 987:                 comp.setFont(comboBox.getFont());
 988:                 Dimension renderSize = comp.getPreferredSize();
 989:                 currentValuePane.remove(comp);
 990:                 dim.width = Math.max(dim.width, renderSize.width);
 991:                 dim.height = Math.max(dim.height, renderSize.height);
 992:               }
 993:           }
 994:         else
 995:           {
 996:             dim = getDefaultSize();
 997:             if (comboBox.isEditable())
 998:               dim.width = 100;
 999:           }
1000:       }
1001:     if (comboBox.isEditable())
1002:       {
1003:         Dimension editSize = editor.getPreferredSize();
1004:         dim.width = Math.max(dim.width, editSize.width);
1005:         dim.height = Math.max(dim.height, editSize.height);
1006:       }
1007:     displaySize.setSize(dim.width, dim.height);
1008:     return dim;
1009:   }
1010: 
1011:   /**
1012:    * Installs the keyboard actions for the {@link JComboBox} as specified
1013:    * by the look and feel.
1014:    */
1015:   protected void installKeyboardActions()
1016:   {
1017:     SwingUtilities.replaceUIInputMap(comboBox,
1018:         JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
1019:         (InputMap) UIManager.get("ComboBox.ancestorInputMap"));
1020:     // Install any action maps here.
1021:   }
1022:   
1023:   /**
1024:    * Uninstalls the keyboard actions for the {@link JComboBox} there were
1025:    * installed by in {@link #installListeners}.
1026:    */
1027:   protected void uninstallKeyboardActions()
1028:   {
1029:     SwingUtilities.replaceUIInputMap(comboBox,
1030:         JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
1031:     // Uninstall any action maps here.
1032:   }
1033: 
1034:   /**
1035:    * A {@link LayoutManager} used to position the sub-components of the
1036:    * {@link JComboBox}.
1037:    * 
1038:    * @see BasicComboBoxUI#createLayoutManager()
1039:    */
1040:   public class ComboBoxLayoutManager implements LayoutManager
1041:   {
1042:     /**
1043:      * Creates a new ComboBoxLayoutManager object.
1044:      */
1045:     public ComboBoxLayoutManager()
1046:     {
1047:       // Nothing to do here.
1048:     }
1049: 
1050:     /**
1051:      * Adds a component to the layout.  This method does nothing, since the
1052:      * layout manager doesn't need to track the components.
1053:      * 
1054:      * @param name  the name to associate the component with (ignored).
1055:      * @param comp  the component (ignored).
1056:      */
1057:     public void addLayoutComponent(String name, Component comp)
1058:     {
1059:       // Do nothing
1060:     }
1061: 
1062:     /**
1063:      * Removes a component from the layout.  This method does nothing, since
1064:      * the layout manager doesn't need to track the components.
1065:      * 
1066:      * @param comp  the component.
1067:      */
1068:     public void removeLayoutComponent(Component comp)
1069:     {
1070:       // Do nothing
1071:     }
1072: 
1073:     /**
1074:      * Returns preferred layout size of the JComboBox.
1075:      *
1076:      * @param parent  the Container for which the preferred size should be 
1077:      *                calculated.
1078:      *
1079:      * @return The preferred size for the given container
1080:      */
1081:     public Dimension preferredLayoutSize(Container parent)
1082:     {
1083:       return parent.getPreferredSize();
1084:     }
1085: 
1086:     /**
1087:      * Returns the minimum layout size.
1088:      * 
1089:      * @param parent  the container.
1090:      * 
1091:      * @return The minimum size.
1092:      */
1093:     public Dimension minimumLayoutSize(Container parent)
1094:     {
1095:       return parent.getMinimumSize();
1096:     }
1097: 
1098:     /**
1099:      * Arranges the components in the container.  It puts arrow
1100:      * button right end part of the comboBox. If the comboBox is editable
1101:      * then editor is placed to the left of arrow  button, starting from the
1102:      * beginning.
1103:      *
1104:      * @param parent Container that should be layed out.
1105:      */
1106:     public void layoutContainer(Container parent)
1107:     {
1108:       // Position editor component to the left of arrow button if combo box is 
1109:       // editable
1110:       Insets i = getInsets();
1111:       int arrowSize = comboBox.getHeight() - (i.top + i.bottom);
1112:       int editorWidth = comboBox.getBounds().width - arrowSize;
1113: 
1114:       if (arrowButton != null)
1115:         arrowButton.setBounds(comboBox.getWidth() - (i.right + arrowSize),
1116:                               i.top, arrowSize, arrowSize);
1117:       if (editor != null)
1118:         editor.setBounds(rectangleForCurrentValue());
1119:     }
1120:   }
1121: 
1122:   /**
1123:    * Handles focus changes occuring in the combo box. This class is
1124:    * responsible for repainting combo box whenever focus is gained or lost
1125:    * and also for hiding popup list of items whenever combo box loses its
1126:    * focus.
1127:    */
1128:   public class FocusHandler extends Object implements FocusListener
1129:   {
1130:     /**
1131:      * Creates a new FocusHandler object.
1132:      */
1133:     public FocusHandler()
1134:     {
1135:       // Nothing to do here.
1136:     }
1137: 
1138:     /**
1139:      * Invoked when combo box gains focus. It repaints main
1140:      * part of combo box accordingly.
1141:      *
1142:      * @param e the FocusEvent
1143:      */
1144:     public void focusGained(FocusEvent e)
1145:     {
1146:       hasFocus = true;
1147:       comboBox.repaint();
1148:     }
1149: 
1150:     /**
1151:      * Invoked when the combo box loses focus.  It repaints the main part
1152:      * of the combo box accordingly and hides the popup list of items.
1153:      *
1154:      * @param e the FocusEvent
1155:      */
1156:     public void focusLost(FocusEvent e)
1157:     {
1158:       hasFocus = false;
1159:       if (! e.isTemporary() && comboBox.isLightWeightPopupEnabled())
1160:         setPopupVisible(comboBox, false);
1161:       comboBox.repaint();
1162:     }
1163:   }
1164: 
1165:   /**
1166:    * Handles {@link ItemEvent}s fired by the {@link JComboBox} when its 
1167:    * selected item changes.
1168:    */
1169:   public class ItemHandler extends Object implements ItemListener
1170:   {
1171:     /**
1172:      * Creates a new ItemHandler object.
1173:      */
1174:     public ItemHandler()
1175:     {
1176:       // Nothing to do here.
1177:     }
1178: 
1179:     /**
1180:      * Invoked when selected item becomes deselected or when
1181:      * new item becomes selected.
1182:      *
1183:      * @param e the ItemEvent representing item's state change.
1184:      */
1185:     public void itemStateChanged(ItemEvent e)
1186:     {
1187:       ComboBoxModel model = comboBox.getModel();
1188:       Object v = model.getSelectedItem();
1189:       if (editor != null)
1190:         comboBox.configureEditor(comboBox.getEditor(), v);
1191:       comboBox.repaint();
1192:     }
1193:   }
1194: 
1195:   /**
1196:    * KeyHandler handles key events occuring while JComboBox has focus.
1197:    */
1198:   public class KeyHandler extends KeyAdapter
1199:   {
1200:     public KeyHandler()
1201:     {
1202:       // Nothing to do here.
1203:     }
1204: 
1205:     /**
1206:      * Invoked whenever key is pressed while JComboBox is in focus.
1207:      */
1208:     public void keyPressed(KeyEvent e)
1209:     {
1210:       if (comboBox.getModel().getSize() != 0 && comboBox.isEnabled())
1211:         {
1212:           if (! isNavigationKey(e.getKeyCode()))
1213:             {
1214:               if (! comboBox.isEditable())
1215:                 if (comboBox.selectWithKeyChar(e.getKeyChar()))
1216:                   e.consume();
1217:             }
1218:           else
1219:             {
1220:               if (e.getKeyCode() == KeyEvent.VK_UP && comboBox.isPopupVisible())
1221:                 selectPreviousPossibleValue();
1222:               else if (e.getKeyCode() == KeyEvent.VK_DOWN)
1223:                 {
1224:                   if (comboBox.isPopupVisible())
1225:                     selectNextPossibleValue();
1226:                   else
1227:                     comboBox.showPopup();
1228:                 }
1229:               else if (e.getKeyCode() == KeyEvent.VK_ENTER
1230:                        || e.getKeyCode() == KeyEvent.VK_ESCAPE)
1231:                 popup.hide();
1232:             }
1233:         }
1234:     }
1235:   }
1236: 
1237:   /**
1238:    * Handles the changes occurring in the JComboBox's data model.
1239:    */
1240:   public class ListDataHandler extends Object implements ListDataListener
1241:   {
1242:     /**
1243:      * Creates a new ListDataHandler object.
1244:      */
1245:     public ListDataHandler()
1246:     {
1247:       // Nothing to do here.
1248:     }
1249: 
1250:     /**
1251:      * Invoked if the content's of JComboBox's data model are changed.
1252:      *
1253:      * @param e ListDataEvent describing the change.
1254:      */
1255:     public void contentsChanged(ListDataEvent e)
1256:     {
1257:       if (e.getIndex0() != -1 || e.getIndex1() != -1)
1258:         {
1259:           isMinimumSizeDirty = true;
1260:           comboBox.revalidate();
1261:         }
1262:       if (editor != null)
1263:         comboBox.configureEditor(comboBox.getEditor(),
1264:             comboBox.getSelectedItem());
1265:       comboBox.repaint();
1266:     }
1267: 
1268:     /**
1269:      * Invoked when items are added to the JComboBox's data model.
1270:      *
1271:      * @param e ListDataEvent describing the change.
1272:      */
1273:     public void intervalAdded(ListDataEvent e)
1274:     {
1275:       int start = e.getIndex0();
1276:       int end = e.getIndex1();
1277:       if (start == 0 && comboBox.getItemCount() - (end - start + 1) == 0)
1278:         contentsChanged(e);
1279:       else if (start != -1  || end != -1)
1280:         {
1281:           ListCellRenderer renderer = comboBox.getRenderer();
1282:           ComboBoxModel model = comboBox.getModel();
1283:           int w = displaySize.width;
1284:           int h = displaySize.height;
1285:           // TODO: Optimize using prototype here.
1286:           for (int i = start; i <= end; ++i)
1287:             {
1288:               Component comp = renderer.getListCellRendererComponent(listBox,
1289:                   model.getElementAt(i), -1, false, false);
1290:               currentValuePane.add(comp);
1291:               comp.setFont(comboBox.getFont());
1292:               Dimension dim = comp.getPreferredSize();
1293:               w = Math.max(w, dim.width);
1294:               h = Math.max(h, dim.height);
1295:               currentValuePane.remove(comp);
1296:             }
1297:           if (displaySize.width < w || displaySize.height < h)
1298:             {
1299:               if (displaySize.width < w)
1300:                 displaySize.width = w;
1301:               if (displaySize.height < h)
1302:                 displaySize.height = h;
1303:               comboBox.revalidate();
1304:               if (editor != null)
1305:                 {
1306:                   comboBox.configureEditor(comboBox.getEditor(),
1307:                                            comboBox.getSelectedItem());
1308:                 }
1309:             }
1310:         }
1311:       
1312:     }
1313: 
1314:     /**
1315:      * Invoked when items are removed from the JComboBox's
1316:      * data model.
1317:      *
1318:      * @param e ListDataEvent describing the change.
1319:      */
1320:     public void intervalRemoved(ListDataEvent e)
1321:     {
1322:       contentsChanged(e);
1323:     }
1324:   }
1325: 
1326:   /**
1327:    * Handles {@link PropertyChangeEvent}s fired by the {@link JComboBox}.
1328:    */
1329:   public class PropertyChangeHandler extends Object
1330:     implements PropertyChangeListener
1331:   {
1332:     /**
1333:      * Creates a new instance.
1334:      */
1335:     public PropertyChangeHandler()
1336:     {
1337:       // Nothing to do here.
1338:     }
1339: 
1340:     /**
1341:      * Invoked whenever bound property of JComboBox changes.
1342:      * 
1343:      * @param e  the event.
1344:      */
1345:     public void propertyChange(PropertyChangeEvent e)
1346:     {
1347:       // Lets assume every change invalidates the minimumsize.
1348:       isMinimumSizeDirty = true;
1349: 
1350:       if (e.getPropertyName().equals("enabled"))
1351:         {
1352:           arrowButton.setEnabled(comboBox.isEnabled());
1353: 
1354:           if (comboBox.isEditable())
1355:             comboBox.getEditor().getEditorComponent().setEnabled(
1356:                 comboBox.isEnabled());
1357:         }
1358:       else if (e.getPropertyName().equals("editable"))
1359:         {
1360:           if (comboBox.isEditable())
1361:             {
1362:               configureEditor();
1363:               addEditor();
1364:             }
1365:           else
1366:             {
1367:               unconfigureEditor();
1368:               removeEditor();
1369:             }
1370: 
1371:           comboBox.revalidate();
1372:           comboBox.repaint();
1373:         }
1374:       else if (e.getPropertyName().equals("dataModel"))
1375:         {
1376:           // remove ListDataListener from old model and add it to new model
1377:           ComboBoxModel oldModel = (ComboBoxModel) e.getOldValue();
1378:           if (oldModel != null)
1379:             oldModel.removeListDataListener(listDataListener);
1380: 
1381:           if ((ComboBoxModel) e.getNewValue() != null)
1382:             comboBox.getModel().addListDataListener(listDataListener);
1383:         }
1384:       else if (e.getPropertyName().equals("font"))
1385:         {
1386:           Font font = (Font) e.getNewValue();
1387:           editor.setFont(font);
1388:           listBox.setFont(font);
1389:           arrowButton.setFont(font);
1390:           comboBox.revalidate();
1391:           comboBox.repaint();
1392:         }
1393: 
1394:       // FIXME: Need to handle changes in other bound properties.       
1395:     }
1396:   }
1397: }