Source for javax.swing.plaf.basic.BasicComboBoxUI

   1: /* BasicComboBoxUI.java --
   2:    Copyright (C) 2004, 2005  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.FontMetrics;
  47: import java.awt.Graphics;
  48: import java.awt.Insets;
  49: import java.awt.LayoutManager;
  50: import java.awt.Rectangle;
  51: import java.awt.event.FocusEvent;
  52: import java.awt.event.FocusListener;
  53: import java.awt.event.ItemEvent;
  54: import java.awt.event.ItemListener;
  55: import java.awt.event.KeyAdapter;
  56: import java.awt.event.KeyEvent;
  57: import java.awt.event.KeyListener;
  58: import java.awt.event.MouseAdapter;
  59: import java.awt.event.MouseEvent;
  60: import java.awt.event.MouseListener;
  61: import java.awt.event.MouseMotionListener;
  62: import java.beans.PropertyChangeEvent;
  63: import java.beans.PropertyChangeListener;
  64: 
  65: import javax.accessibility.Accessible;
  66: import javax.swing.CellRendererPane;
  67: import javax.swing.ComboBoxEditor;
  68: import javax.swing.ComboBoxModel;
  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:    * A listener listening to mouse events occuring in the {@link JComboBox}.
 133:    */
 134:   private MouseListener mouseListener;
 135: 
 136:   /**
 137:    * List used when rendering selected item of the combo box. The selection
 138:    * and foreground colors for combo box renderer are configured from this
 139:    * list.
 140:    */
 141:   protected JList listBox;
 142: 
 143:   /**
 144:    * ListDataListener listening to JComboBox model
 145:    */
 146:   protected ListDataListener listDataListener;
 147: 
 148:   /**
 149:    * Popup list containing the combo box's menu items.
 150:    */
 151:   protected ComboPopup popup;
 152:   
 153:   protected KeyListener popupKeyListener;
 154:   
 155:   protected MouseListener popupMouseListener;
 156:   
 157:   protected MouseMotionListener popupMouseMotionListener;
 158: 
 159:   /**
 160:    * Listener listening to changes in the bound properties of JComboBox
 161:    */
 162:   protected PropertyChangeListener propertyChangeListener;
 163: 
 164:   /** 
 165:    * The button background. 
 166:    * @see #installDefaults()
 167:    */
 168:   private Color buttonBackground;
 169:   
 170:   /** 
 171:    * The button shadow. 
 172:    * @see #installDefaults()
 173:    */
 174:   private Color buttonShadow;
 175:   
 176:   /**
 177:    * The button dark shadow.
 178:    * @see #installDefaults()
 179:    */
 180:   private Color buttonDarkShadow;
 181: 
 182:   /**
 183:    * The button highlight.
 184:    * @see #installDefaults()
 185:    */
 186:   private Color buttonHighlight;
 187: 
 188:   /* Size of the largest item in the comboBox
 189:    * This is package-private to avoid an accessor method.
 190:    */
 191:   Dimension displaySize;
 192: 
 193:   // FIXME: This field isn't used anywhere at this moment.
 194:   protected CellRendererPane currentValuePane;
 195: 
 196:   /**
 197:    * The current minimum size if isMinimumSizeDirty is false.
 198:    * Setup by getMinimumSize() and invalidated by the various listeners.
 199:    */
 200:   protected Dimension cachedMinimumSize;
 201: 
 202:   /**
 203:    * Indicates whether or not the cachedMinimumSize field is valid or not.
 204:    */
 205:   protected boolean isMinimumSizeDirty = true;
 206: 
 207:   /**
 208:    * Creates a new <code>BasicComboBoxUI</code> object.
 209:    */
 210:   public BasicComboBoxUI()
 211:   {
 212:     // Nothing to do here.
 213:   }
 214: 
 215:   /**
 216:    * A factory method to create a UI delegate for the given 
 217:    * {@link JComponent}, which should be a {@link JComboBox}.
 218:    *
 219:    * @param c The {@link JComponent} a UI is being created for.
 220:    *
 221:    * @return A UI delegate for the {@link JComponent}.
 222:    */
 223:   public static ComponentUI createUI(JComponent c)
 224:   {
 225:     return new BasicComboBoxUI();
 226:   }
 227: 
 228:   /**
 229:    * Installs the UI for the given {@link JComponent}.
 230:    *
 231:    * @param c  the JComponent to install a UI for.
 232:    * 
 233:    * @see #uninstallUI(JComponent)
 234:    */
 235:   public void installUI(JComponent c)
 236:   {
 237:     super.installUI(c);
 238: 
 239:     if (c instanceof JComboBox)
 240:       {
 241:         comboBox = (JComboBox) c;
 242:         comboBox.setOpaque(true);
 243:         comboBox.setLayout(createLayoutManager());
 244:         installDefaults();
 245:         installComponents();
 246:         installListeners();
 247:         installKeyboardActions();
 248:       }
 249:   }
 250: 
 251:   /**
 252:    * Uninstalls the UI for the given {@link JComponent}.
 253:    *
 254:    * @param c The JComponent that is having this UI removed.
 255:    * 
 256:    * @see #installUI(JComponent)
 257:    */
 258:   public void uninstallUI(JComponent c)
 259:   {
 260:     uninstallKeyboardActions();
 261:     uninstallListeners();
 262:     uninstallComponents();
 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:     
 278:     // fetch the button color scheme
 279:     buttonBackground = UIManager.getColor("ComboBox.buttonBackground");
 280:     buttonShadow = UIManager.getColor("ComboBox.buttonShadow");
 281:     buttonDarkShadow = UIManager.getColor("ComboBox.buttonDarkShadow");
 282:     buttonHighlight = UIManager.getColor("ComboBox.buttonHighlight");
 283:   }
 284: 
 285:   /**
 286:    * Creates and installs the listeners for this UI.
 287:    * 
 288:    * @see #uninstallListeners()
 289:    */
 290:   protected void installListeners()
 291:   {
 292:     // install combo box's listeners
 293:     propertyChangeListener = createPropertyChangeListener();
 294:     comboBox.addPropertyChangeListener(propertyChangeListener);
 295: 
 296:     focusListener = createFocusListener();
 297:     editor.addFocusListener(focusListener);
 298: 
 299:     itemListener = createItemListener();
 300:     comboBox.addItemListener(itemListener);
 301: 
 302:     keyListener = createKeyListener();
 303:     comboBox.addKeyListener(keyListener);
 304: 
 305:     mouseListener = createMouseListener();
 306:     arrowButton.addMouseListener(mouseListener);
 307: 
 308:     // install listeners that listen to combo box model
 309:     listDataListener = createListDataListener();
 310:     comboBox.getModel().addListDataListener(listDataListener);
 311:   }
 312: 
 313:   /**
 314:    * Uninstalls the defaults and sets any objects created during
 315:    * install to <code>null</code>.
 316:    * 
 317:    * @see #installDefaults()
 318:    */
 319:   protected void uninstallDefaults()
 320:   {
 321:     if (comboBox.getFont() instanceof UIResource)
 322:       comboBox.setFont(null);
 323: 
 324:     if (comboBox.getForeground() instanceof UIResource)
 325:       comboBox.setForeground(null);
 326:     
 327:     if (comboBox.getBackground() instanceof UIResource)
 328:       comboBox.setBackground(null);
 329: 
 330:     buttonBackground = null;
 331:     buttonShadow = null;
 332:     buttonDarkShadow = null;
 333:     buttonHighlight = null;
 334:   }
 335: 
 336:   /**
 337:    * Detaches all the listeners we attached in {@link #installListeners}.
 338:    * 
 339:    * @see #installListeners()
 340:    */
 341:   protected void uninstallListeners()
 342:   {
 343:     comboBox.removePropertyChangeListener(propertyChangeListener);
 344:     propertyChangeListener = null;
 345: 
 346:     comboBox.removeFocusListener(focusListener);
 347:     listBox.removeFocusListener(focusListener);
 348:     focusListener = null;
 349: 
 350:     comboBox.removeItemListener(itemListener);
 351:     itemListener = null;
 352: 
 353:     comboBox.removeKeyListener(keyListener);
 354:     keyListener = null;
 355: 
 356:     arrowButton.removeMouseListener(mouseListener);
 357:     mouseListener = null;
 358: 
 359:     comboBox.getModel().removeListDataListener(listDataListener);
 360:     listDataListener = null;
 361:   }
 362: 
 363:   /**
 364:    * Creates the popup that will contain list of combo box's items.
 365:    *
 366:    * @return popup containing list of combo box's items
 367:    */
 368:   protected ComboPopup createPopup()
 369:   {
 370:     return new BasicComboPopup(comboBox);
 371:   }
 372: 
 373:   /**
 374:    * Creates a {@link KeyListener} to listen to key events.
 375:    *
 376:    * @return KeyListener that listens to key events.
 377:    */
 378:   protected KeyListener createKeyListener()
 379:   {
 380:     return new KeyHandler();
 381:   }
 382: 
 383:   /**
 384:    * Creates a {@link MouseListener} that will listen to mouse events occurring
 385:    * in the combo box.
 386:    *
 387:    * @return the MouseListener
 388:    */
 389:   private MouseListener createMouseListener()
 390:   {
 391:     return new MouseHandler();
 392:   }
 393: 
 394:   /**
 395:    * Creates the {@link FocusListener} that will listen to changes in this
 396:    * JComboBox's focus.
 397:    *
 398:    * @return the FocusListener.
 399:    */
 400:   protected FocusListener createFocusListener()
 401:   {
 402:     return new FocusHandler();
 403:   }
 404: 
 405:   /**
 406:    * Creates a {@link ListDataListener} to listen to the combo box's data model.
 407:    *
 408:    * @return The new listener.
 409:    */
 410:   protected ListDataListener createListDataListener()
 411:   {
 412:     return new ListDataHandler();
 413:   }
 414: 
 415:   /**
 416:    * Creates an {@link ItemListener} that will listen to the changes in
 417:    * the JComboBox's selection.
 418:    *
 419:    * @return The ItemListener
 420:    */
 421:   protected ItemListener createItemListener()
 422:   {
 423:     return new ItemHandler();
 424:   }
 425: 
 426:   /**
 427:    * Creates a {@link PropertyChangeListener} to listen to the changes in
 428:    * the JComboBox's bound properties.
 429:    *
 430:    * @return The PropertyChangeListener
 431:    */
 432:   protected PropertyChangeListener createPropertyChangeListener()
 433:   {
 434:     return new PropertyChangeHandler();
 435:   }
 436: 
 437:   /**
 438:    * Creates and returns a layout manager for the combo box.  Subclasses can 
 439:    * override this method to provide a different layout.
 440:    *
 441:    * @return a layout manager for the combo box.
 442:    */
 443:   protected LayoutManager createLayoutManager()
 444:   {
 445:     return new ComboBoxLayoutManager();
 446:   }
 447: 
 448:   /**
 449:    * Creates a component that will be responsible for rendering the
 450:    * selected component in the combo box.
 451:    *
 452:    * @return A renderer for the combo box.
 453:    */
 454:   protected ListCellRenderer createRenderer()
 455:   {
 456:     return new BasicComboBoxRenderer();
 457:   }
 458: 
 459:   /**
 460:    * Creates the component that will be responsible for displaying/editing
 461:    * the selected item in the combo box. This editor is used only when combo 
 462:    * box is editable.
 463:    *
 464:    * @return A new component that will be responsible for displaying/editing
 465:    *         the selected item in the combo box.
 466:    */
 467:   protected ComboBoxEditor createEditor()
 468:   {
 469:     return new BasicComboBoxEditor.UIResource();
 470:   }
 471: 
 472:   /**
 473:    * Installs the components for this JComboBox. ArrowButton, main
 474:    * part of combo box (upper part) and popup list of items are created and
 475:    * configured here.
 476:    */
 477:   protected void installComponents()
 478:   {
 479:     // create drop down list of items
 480:     popup = createPopup();
 481:     listBox = popup.getList();
 482: 
 483:     // set editor and renderer for the combo box. Editor is used
 484:     // only if combo box becomes editable, otherwise renderer is used
 485:     // to paint the selected item; combobox is not editable by default. 
 486:     comboBox.setRenderer(createRenderer());
 487: 
 488:     // create and install arrow button
 489:     arrowButton = createArrowButton();
 490:     configureArrowButton();
 491:     comboBox.add(arrowButton);
 492: 
 493:     ComboBoxEditor currentEditor = comboBox.getEditor();
 494:     if (currentEditor == null || currentEditor instanceof UIResource)
 495:       {
 496:         currentEditor = createEditor();
 497:         comboBox.setEditor(currentEditor);
 498:       } 
 499:     editor = currentEditor.getEditorComponent();
 500: 
 501:     comboBox.revalidate();
 502:   }
 503: 
 504:   /**
 505:    * Uninstalls components from this {@link JComboBox}.
 506:    * 
 507:    * @see #installComponents()
 508:    */
 509:   protected void uninstallComponents()
 510:   {
 511:     // uninstall arrow button
 512:     unconfigureArrowButton();
 513:     comboBox.remove(arrowButton);
 514:     arrowButton = null;
 515: 
 516:     listBox = null;
 517:     popup = null;
 518: 
 519:     comboBox.setRenderer(null);
 520: 
 521:     // if the editor is not an instanceof UIResource, it was not set by the
 522:     // UI delegate, so don't clear it...
 523:     ComboBoxEditor currentEditor = comboBox.getEditor();
 524:     if (currentEditor instanceof UIResource)
 525:       {
 526:         comboBox.setEditor(null);
 527:         editor = null;
 528:       }
 529:   }
 530: 
 531:   /**
 532:    * Adds the current editor to the combo box.
 533:    */
 534:   public void addEditor()
 535:   {
 536:     comboBox.add(editor);
 537:   }
 538: 
 539:   /**
 540:    * Removes the current editor from the combo box.
 541:    */
 542:   public void removeEditor()
 543:   {
 544:     comboBox.remove(editor);
 545:   }
 546: 
 547:   /**
 548:    * Configures the editor for this combo box.
 549:    */
 550:   protected void configureEditor()
 551:   {
 552:     editor.setFont(comboBox.getFont());
 553:     comboBox.getEditor().setItem(comboBox.getSelectedItem());
 554:     // FIXME: Need to implement. Set font and add listeners.
 555:   }
 556: 
 557:   /**
 558:    * Unconfigures the editor for this combo nox.  This method is not implemented.
 559:    */
 560:   protected void unconfigureEditor()
 561:   {
 562:     // FIXME: Need to implement    
 563:   }
 564: 
 565:   /**
 566:    * Configures the arrow button.
 567:    * 
 568:    * @see #configureArrowButton()
 569:    */
 570:   public void configureArrowButton()
 571:   {
 572:     arrowButton.setEnabled(comboBox.isEnabled());
 573:     arrowButton.setFont(comboBox.getFont());
 574:     arrowButton.setFocusable(false);
 575:   }
 576: 
 577:   /**
 578:    * Unconfigures the arrow button.
 579:    * 
 580:    * @see #configureArrowButton()
 581:    *
 582:    * @specnote The specification says this method is implementation specific
 583:    *           and should not be used or overridden.
 584:    */
 585:   public void unconfigureArrowButton()
 586:   {
 587:     // Nothing to do here yet.
 588:   }
 589: 
 590:   /**
 591:    * Creates an arrow button for this {@link JComboBox}.  The arrow button is
 592:    * displayed at the right end of the combo box and is used to display/hide
 593:    * the drop down list of items.
 594:    *
 595:    * @return A new button.
 596:    */
 597:   protected JButton createArrowButton()
 598:   {
 599:     return new BasicArrowButton(BasicArrowButton.SOUTH, buttonBackground, 
 600:             buttonShadow, buttonDarkShadow, buttonHighlight);
 601:   }
 602: 
 603:   /**
 604:    * Returns <code>true</code> if the popup is visible, and <code>false</code>
 605:    * otherwise.
 606:    *
 607:    * @param c The JComboBox to check
 608:    *
 609:    * @return <code>true</code> if popup part of the JComboBox is visible and 
 610:    *         <code>false</code> otherwise.
 611:    */
 612:   public boolean isPopupVisible(JComboBox c)
 613:   {
 614:     return popup.isVisible();
 615:   }
 616: 
 617:   /**
 618:    * Displays/hides the {@link JComboBox}'s list of items on the screen.
 619:    *
 620:    * @param c The combo box, for which list of items should be
 621:    *        displayed/hidden
 622:    * @param v true if show popup part of the jcomboBox and false to hide.
 623:    */
 624:   public void setPopupVisible(JComboBox c, boolean v)
 625:   {
 626:     if (v)
 627:       popup.show();
 628:     else
 629:       popup.hide();
 630: 
 631:     if (comboBox.isEditable())
 632:       editor.requestFocus();
 633:     else
 634:       comboBox.requestFocus();
 635:   }
 636: 
 637:   /**
 638:    * JComboBox is focus traversable if it is editable and not otherwise.
 639:    *
 640:    * @param c combo box for which to check whether it is focus traversable
 641:    *
 642:    * @return true if focus tranversable and false otherwise
 643:    */
 644:   public boolean isFocusTraversable(JComboBox c)
 645:   {
 646:     if (!comboBox.isEditable())
 647:       return true;
 648: 
 649:     return false;
 650:   }
 651: 
 652:   /**
 653:    * Paints given menu item using specified graphics context
 654:    *
 655:    * @param g The graphics context used to paint this combo box
 656:    * @param c comboBox which needs to be painted.
 657:    */
 658:   public void paint(Graphics g, JComponent c)
 659:   {
 660:     Rectangle rect = rectangleForCurrentValue();
 661:     paintCurrentValueBackground(g, rect, hasFocus);
 662:     paintCurrentValue(g, rect, hasFocus);
 663:   }
 664: 
 665:   /**
 666:    * Returns preferred size for the combo box.
 667:    *
 668:    * @param c comboBox for which to get preferred size
 669:    *
 670:    * @return The preferred size for the given combo box
 671:    */
 672:   public Dimension getPreferredSize(JComponent c)
 673:   {
 674:     // note:  overriding getMinimumSize() (for example in the MetalComboBoxUI 
 675:     // class) affects the getPreferredSize() result, so it seems logical that
 676:     // this method is implemented by delegating to the getMinimumSize() method
 677:     return getMinimumSize(c);
 678:   }
 679: 
 680:   /**
 681:    * Returns the minimum size for this {@link JComboBox} for this
 682:    * look and feel. Also makes sure cachedMinimimSize is setup correctly.
 683:    *
 684:    * @param c The {@link JComponent} to find the minimum size for.
 685:    *
 686:    * @return The dimensions of the minimum size.
 687:    */
 688:   public Dimension getMinimumSize(JComponent c)
 689:   {
 690:     if (isMinimumSizeDirty)
 691:       {
 692:     Dimension d = getDisplaySize();
 693:     int arrowButtonWidth = d.height;
 694:     cachedMinimumSize = new Dimension(d.width + arrowButtonWidth,
 695:                       d.height);
 696:     isMinimumSizeDirty = false;
 697:       }
 698:     return new Dimension(cachedMinimumSize);
 699:   }
 700: 
 701:   /** The value returned by the getMaximumSize() method. */
 702:   private static final Dimension MAXIMUM_SIZE = new Dimension(32767, 32767);
 703:   
 704:   /**
 705:    * Returns the maximum size for this {@link JComboBox} for this
 706:    * look and feel.
 707:    *
 708:    * @param c The {@link JComponent} to find the maximum size for
 709:    *
 710:    * @return The maximum size (<code>Dimension(32767, 32767)</code>).
 711:    */
 712:   public Dimension getMaximumSize(JComponent c)
 713:   {
 714:     return MAXIMUM_SIZE;
 715:   }
 716: 
 717:   public int getAccessibleChildrenCount(JComponent c)
 718:   {
 719:     // FIXME: Need to implement
 720:     return 0;
 721:   }
 722: 
 723:   public Accessible getAccessibleChild(JComponent c, int i)
 724:   {
 725:     // FIXME: Need to implement
 726:     return null;
 727:   }
 728: 
 729:   /**
 730:    * Returns true if the specified key is a navigation key and false otherwise
 731:    *
 732:    * @param keyCode a key for which to check whether it is navigation key or
 733:    *        not.
 734:    *
 735:    * @return true if the specified key is a navigation key and false otherwis
 736:    */
 737:   protected boolean isNavigationKey(int keyCode)
 738:   {
 739:     return false;
 740:   }
 741: 
 742:   /**
 743:    * Selects next possible item relative to the current selection
 744:    * to be next selected item in the combo box.
 745:    */
 746:   protected void selectNextPossibleValue()
 747:   {
 748:     int index = comboBox.getSelectedIndex();
 749:     if (index != comboBox.getItemCount() - 1)
 750:       comboBox.setSelectedIndex(index + 1);
 751:   }
 752: 
 753:   /**
 754:    * Selects previous item relative to current selection to be
 755:    * next selected item.
 756:    */
 757:   protected void selectPreviousPossibleValue()
 758:   {
 759:     int index = comboBox.getSelectedIndex();
 760:     if (index != 0)
 761:       comboBox.setSelectedIndex(index - 1);
 762:   }
 763: 
 764:   /**
 765:    * Displays combo box popup if the popup is not currently shown
 766:    * on the screen and hides it if it is currently shown
 767:    */
 768:   protected void toggleOpenClose()
 769:   {
 770:     setPopupVisible(comboBox, ! isPopupVisible(comboBox));
 771:   }
 772: 
 773:   /**
 774:    * Returns the bounds in which comboBox's selected item will be
 775:    * displayed.
 776:    *
 777:    * @return rectangle bounds in which comboBox's selected Item will be
 778:    *         displayed
 779:    */
 780:   protected Rectangle rectangleForCurrentValue()
 781:   {
 782:     Rectangle cbBounds = SwingUtilities.getLocalBounds(comboBox);
 783:     Rectangle abBounds = arrowButton.getBounds();   
 784:     Rectangle rectForCurrentValue = new Rectangle(cbBounds.x, cbBounds.y,
 785:       cbBounds.width - abBounds.width, cbBounds.height);
 786:     return rectForCurrentValue;
 787:   }
 788: 
 789:   /**
 790:    * Returns the insets of the current border.
 791:    *
 792:    * @return Insets representing space between combo box and its border
 793:    */
 794:   protected Insets getInsets()
 795:   {
 796:     return new Insets(0, 0, 0, 0);
 797:   }
 798: 
 799:   /**
 800:    * Paints currently selected value in the main part of the combo
 801:    * box (part without popup).
 802:    *
 803:    * @param g graphics context
 804:    * @param bounds Rectangle representing the size of the area in which
 805:    *        selected item should be drawn
 806:    * @param hasFocus true if combo box has focus and false otherwise
 807:    */
 808:   public void paintCurrentValue(Graphics g, Rectangle bounds, boolean hasFocus)
 809:   {
 810:     if (! comboBox.isEditable())
 811:       {
 812:     Object currentValue = comboBox.getSelectedItem();
 813:     boolean isPressed = arrowButton.getModel().isPressed();
 814: 
 815:     /* Gets the component to be drawn for the current value.
 816:      * If there is currently no selected item we will take an empty
 817:      * String as replacement.
 818:      */
 819:         Component comp = comboBox.getRenderer().getListCellRendererComponent(
 820:                 listBox, (currentValue != null ? currentValue : ""), -1,
 821:                 isPressed, hasFocus);
 822:         if (! comboBox.isEnabled())
 823:           {
 824:             comp.setBackground(UIManager.getColor(
 825:                                                "ComboBox.disabledBackground"));
 826:             comp.setForeground(UIManager.getColor(
 827:                                                "ComboBox.disabledForeground"));
 828:             comp.setEnabled(false);
 829:           }
 830:         comp.setBounds(0, 0, bounds.width, bounds.height);
 831:         comp.setFont(comboBox.getFont());
 832:         comp.paint(g);
 833:         
 834:         comboBox.revalidate();
 835:       }
 836:     else
 837:       comboBox.getEditor().setItem(comboBox.getSelectedItem());
 838:   }
 839: 
 840:   /**
 841:    * Paints the background of part of the combo box, where currently
 842:    * selected value is displayed. If the combo box has focus this method
 843:    * should also paint focus rectangle around the combo box.
 844:    *
 845:    * @param g graphics context
 846:    * @param bounds Rectangle representing the size of the largest item  in the
 847:    *        comboBox
 848:    * @param hasFocus true if combo box has fox and false otherwise
 849:    */
 850:   public void paintCurrentValueBackground(Graphics g, Rectangle bounds,
 851:                                           boolean hasFocus)
 852:   {
 853:     // background is painted by renderer, so it seems that nothing
 854:     // should be done here.
 855:   }
 856: 
 857:   /**
 858:    * Returns the default size for the display area of a combo box that does 
 859:    * not contain any elements.  This method returns the width and height of
 860:    * a single space in the current font, plus a margin of 1 pixel. 
 861:    *
 862:    * @return The default display size.
 863:    * 
 864:    * @see #getDisplaySize()
 865:    */
 866:   protected Dimension getDefaultSize()
 867:   {
 868:     // There is nothing in the spec to say how this method should be
 869:     // implemented...so I've done some guessing, written some Mauve tests,
 870:     // and written something that gives dimensions that are close to the 
 871:     // reference implementation.
 872:     FontMetrics fm = comboBox.getFontMetrics(comboBox.getFont());
 873:     int w = fm.charWidth(' ') + 2;
 874:     int h = fm.getHeight() + 2;
 875:     return new Dimension(w, h);
 876:   }
 877: 
 878:   /**
 879:    * Returns the size of the display area for the combo box. This size will be 
 880:    * the size of the combo box, not including the arrowButton.
 881:    *
 882:    * @return The size of the display area for the combo box.
 883:    */
 884:   protected Dimension getDisplaySize()
 885:   {
 886:     if (!comboBox.isEditable()) 
 887:       {
 888:         Object prototype = comboBox.getPrototypeDisplayValue();
 889:         if (prototype != null)
 890:           {
 891:             // calculate result based on prototype
 892:             ListCellRenderer renderer = comboBox.getRenderer();
 893:             Component comp = renderer.getListCellRendererComponent(listBox, 
 894:                 prototype, -1, false, false);
 895:             Dimension compSize = comp.getPreferredSize();
 896:             compSize.width += 2;  // add 1 pixel margin around area
 897:             compSize.height += 2;
 898:             return compSize;
 899:           }
 900:         else
 901:           {
 902:             ComboBoxModel model = comboBox.getModel();
 903:             int numItems = model.getSize();
 904: 
 905:             // if combo box doesn't have any items then simply
 906:             // return its default size
 907:             if (numItems == 0)
 908:               {
 909:                 displaySize = getDefaultSize();
 910:                 return displaySize;
 911:               }
 912: 
 913:             Dimension size = new Dimension(0, 0);
 914: 
 915:             // ComboBox's display size should be equal to the 
 916:             // size of the largest item in the combo box. 
 917:             ListCellRenderer renderer = comboBox.getRenderer();
 918: 
 919:             for (int i = 0; i < numItems; i++)
 920:               {
 921:                 Object item = model.getElementAt(i);
 922:                 Component comp = renderer.getListCellRendererComponent(listBox, 
 923:                     item, -1, false, false);
 924: 
 925:                 Dimension compSize = comp.getPreferredSize();
 926:                 if (compSize.width + 2 > size.width)
 927:                   size.width = compSize.width + 2;
 928:                 if (compSize.height + 2 > size.height)
 929:                   size.height = compSize.height + 2;
 930:               }
 931:             displaySize = size;
 932:             return displaySize;
 933:           }
 934:       }
 935:     else // an editable combo,  
 936:       {
 937:         Component comp = comboBox.getEditor().getEditorComponent();
 938:         Dimension prefSize = comp.getPreferredSize();
 939:         int width = prefSize.width;
 940:         int height = prefSize.height + 2;
 941:         Object prototype = comboBox.getPrototypeDisplayValue();
 942:         if (prototype != null)
 943:           {
 944:             FontMetrics fm = comboBox.getFontMetrics(comboBox.getFont());
 945:             width = Math.max(width, fm.stringWidth(prototype.toString()) + 2);
 946:           }
 947:         displaySize = new Dimension(width, height);
 948:         return displaySize;
 949:       }
 950:   }
 951: 
 952:   /**
 953:    * Installs the keyboard actions for the {@link JComboBox} as specified
 954:    * by the look and feel.
 955:    */
 956:   protected void installKeyboardActions()
 957:   {
 958:     // FIXME: Need to implement.
 959:   }
 960: 
 961:   /**
 962:    * Uninstalls the keyboard actions for the {@link JComboBox} there were
 963:    * installed by in {@link #installListeners}.
 964:    */
 965:   protected void uninstallKeyboardActions()
 966:   {
 967:     // FIXME: Need to implement.
 968:   }
 969: 
 970:   /**
 971:    * A {@link LayoutManager} used to position the sub-components of the
 972:    * {@link JComboBox}.
 973:    * 
 974:    * @see BasicComboBoxUI#createLayoutManager()
 975:    */
 976:   public class ComboBoxLayoutManager implements LayoutManager
 977:   {
 978:     /**
 979:      * Creates a new ComboBoxLayoutManager object.
 980:      */
 981:     public ComboBoxLayoutManager()
 982:     {
 983:       // Nothing to do here.
 984:     }
 985: 
 986:     /**
 987:      * Adds a component to the layout.  This method does nothing, since the
 988:      * layout manager doesn't need to track the components.
 989:      * 
 990:      * @param name  the name to associate the component with (ignored).
 991:      * @param comp  the component (ignored).
 992:      */
 993:     public void addLayoutComponent(String name, Component comp)
 994:     {
 995:       // Do nothing
 996:     }
 997: 
 998:     /**
 999:      * Removes a component from the layout.  This method does nothing, since
1000:      * the layout manager doesn't need to track the components.
1001:      * 
1002:      * @param comp  the component.
1003:      */
1004:     public void removeLayoutComponent(Component comp)
1005:     {
1006:       // Do nothing
1007:     }
1008: 
1009:     /**
1010:      * Returns preferred layout size of the JComboBox.
1011:      *
1012:      * @param parent  the Container for which the preferred size should be 
1013:      *                calculated.
1014:      *
1015:      * @return The preferred size for the given container
1016:      */
1017:     public Dimension preferredLayoutSize(Container parent)
1018:     {
1019:       return getPreferredSize((JComponent) parent);
1020:     }
1021: 
1022:     /**
1023:      * Returns the minimum layout size.
1024:      * 
1025:      * @param parent  the container.
1026:      * 
1027:      * @return The minimum size.
1028:      */
1029:     public Dimension minimumLayoutSize(Container parent)
1030:     {
1031:       return preferredLayoutSize(parent);
1032:     }
1033: 
1034:     /**
1035:      * Arranges the components in the container.  It puts arrow
1036:      * button right end part of the comboBox. If the comboBox is editable
1037:      * then editor is placed to the left of arrow  button, starting from the
1038:      * beginning.
1039:      *
1040:      * @param parent Container that should be layed out.
1041:      */
1042:     public void layoutContainer(Container parent)
1043:     {
1044:       // Position editor component to the left of arrow button if combo box is 
1045:       // editable
1046:       int arrowSize = comboBox.getHeight();
1047:       int editorWidth = comboBox.getBounds().width - arrowSize;
1048: 
1049:       if (comboBox.isEditable())
1050:         editor.setBounds(0, 0, editorWidth, comboBox.getBounds().height);
1051:       
1052:       arrowButton.setBounds(editorWidth, 0, arrowSize, arrowSize);
1053:       comboBox.revalidate();
1054:     }
1055:   }
1056: 
1057:   /**
1058:    * Handles focus changes occuring in the combo box. This class is
1059:    * responsible for repainting combo box whenever focus is gained or lost
1060:    * and also for hiding popup list of items whenever combo box loses its
1061:    * focus.
1062:    */
1063:   public class FocusHandler extends Object implements FocusListener
1064:   {
1065:     /**
1066:      * Creates a new FocusHandler object.
1067:      */
1068:     public FocusHandler()
1069:     {
1070:       // Nothing to do here.
1071:     }
1072: 
1073:     /**
1074:      * Invoked when combo box gains focus. It repaints main
1075:      * part of combo box accordingly.
1076:      *
1077:      * @param e the FocusEvent
1078:      */
1079:     public void focusGained(FocusEvent e)
1080:     {
1081:       // Lets assume every change invalidates the minimumsize.
1082:       isMinimumSizeDirty = true;
1083: 
1084:       hasFocus = true;
1085:       comboBox.repaint();
1086:     }
1087: 
1088:     /**
1089:      * Invoked when the combo box loses focus.  It repaints the main part
1090:      * of the combo box accordingly and hides the popup list of items.
1091:      *
1092:      * @param e the FocusEvent
1093:      */
1094:     public void focusLost(FocusEvent e)
1095:     {
1096:       // Lets assume every change invalidates the minimumsize.
1097:       isMinimumSizeDirty = true;
1098: 
1099:       hasFocus = false;
1100:       setPopupVisible(comboBox, false);
1101:       comboBox.repaint();
1102:     }
1103:   }
1104: 
1105:   /**
1106:    * Handles {@link ItemEvent}s fired by the {@link JComboBox} when its 
1107:    * selected item changes.
1108:    */
1109:   public class ItemHandler extends Object implements ItemListener
1110:   {
1111:     /**
1112:      * Creates a new ItemHandler object.
1113:      */
1114:     public ItemHandler()
1115:     {
1116:       // Nothing to do here.
1117:     }
1118: 
1119:     /**
1120:      * Invoked when selected item becomes deselected or when
1121:      * new item becomes selected.
1122:      *
1123:      * @param e the ItemEvent representing item's state change.
1124:      */
1125:     public void itemStateChanged(ItemEvent e)
1126:     {
1127:       // Lets assume every change invalidates the minimumsize.
1128:       isMinimumSizeDirty = true;
1129: 
1130:       if (e.getStateChange() == ItemEvent.SELECTED && comboBox.isEditable())
1131:         comboBox.getEditor().setItem(e.getItem());
1132:       comboBox.repaint();
1133:     }
1134:   }
1135: 
1136:   /**
1137:    * KeyHandler handles key events occuring while JComboBox has focus.
1138:    */
1139:   public class KeyHandler extends KeyAdapter
1140:   {
1141:     public KeyHandler()
1142:     {
1143:       // Nothing to do here.
1144:     }
1145: 
1146:     /**
1147:      * Invoked whenever key is pressed while JComboBox is in focus.
1148:      */
1149:     public void keyPressed(KeyEvent e)
1150:     {
1151:       // FIXME: This method calls JComboBox.selectWithKeyChar if the key that was 
1152:       // pressed is not a navigation key. 
1153:     }
1154:   }
1155: 
1156:   /**
1157:    * Handles the changes occurring in the JComboBox's data model.
1158:    */
1159:   public class ListDataHandler extends Object implements ListDataListener
1160:   {
1161:     /**
1162:      * Creates a new ListDataHandler object.
1163:      */
1164:     public ListDataHandler()
1165:     {
1166:       // Nothing to do here.
1167:     }
1168: 
1169:     /**
1170:      * Invoked if the content's of JComboBox's data model are changed.
1171:      *
1172:      * @param e ListDataEvent describing the change.
1173:      */
1174:     public void contentsChanged(ListDataEvent e)
1175:     {
1176:       // if the item is selected or deselected
1177: 
1178:       // Lets assume every change invalidates the minimumsize.
1179:       isMinimumSizeDirty = true;
1180:     }
1181: 
1182:     /**
1183:      * Invoked when items are added to the JComboBox's data model.
1184:      *
1185:      * @param e ListDataEvent describing the change.
1186:      */
1187:     public void intervalAdded(ListDataEvent e)
1188:     {
1189:       // Lets assume every change invalidates the minimumsize.
1190:       isMinimumSizeDirty = true;
1191: 
1192:       ComboBoxModel model = comboBox.getModel();
1193:       ListCellRenderer renderer = comboBox.getRenderer();
1194: 
1195:       if (displaySize == null)
1196:         displaySize = getDisplaySize();
1197:       if (displaySize.width < getDefaultSize().width)
1198:         displaySize.width = getDefaultSize().width;
1199:       if (displaySize.height < getDefaultSize().height)
1200:         displaySize.height = getDefaultSize().height;
1201: 
1202:       comboBox.repaint();
1203:     }
1204: 
1205:     /**
1206:      * Invoked when items are removed from the JComboBox's
1207:      * data model.
1208:      *
1209:      * @param e ListDataEvent describing the change.
1210:      */
1211:     public void intervalRemoved(ListDataEvent e)
1212:     {
1213:       // Lets assume every change invalidates the minimumsize.
1214:       isMinimumSizeDirty = true;
1215: 
1216:       // recalculate display size of the JComboBox.
1217:       displaySize = getDisplaySize();
1218:       comboBox.repaint();
1219:     }
1220:   }
1221: 
1222:   /**
1223:    * Handles {@link PropertyChangeEvent}s fired by the {@link JComboBox}.
1224:    */
1225:   public class PropertyChangeHandler extends Object
1226:     implements PropertyChangeListener
1227:   {
1228:     /**
1229:      * Creates a new instance.
1230:      */
1231:     public PropertyChangeHandler()
1232:     {
1233:       // Nothing to do here.
1234:     }
1235: 
1236:     /**
1237:      * Invoked whenever bound property of JComboBox changes.
1238:      * 
1239:      * @param e  the event.
1240:      */
1241:     public void propertyChange(PropertyChangeEvent e)
1242:     {
1243:       // Lets assume every change invalidates the minimumsize.
1244:       isMinimumSizeDirty = true;
1245: 
1246:       if (e.getPropertyName().equals("enabled"))
1247:         {
1248:       arrowButton.setEnabled(comboBox.isEnabled());
1249: 
1250:       if (comboBox.isEditable())
1251:         comboBox.getEditor().getEditorComponent().setEnabled(comboBox
1252:                                                              .isEnabled());
1253:         }
1254:       else if (e.getPropertyName().equals("editable"))
1255:         {
1256:       if (comboBox.isEditable())
1257:         {
1258:           configureEditor();
1259:           addEditor();
1260:         }
1261:       else
1262:         {
1263:           unconfigureEditor();
1264:           removeEditor();
1265:         }
1266: 
1267:       comboBox.revalidate();
1268:       comboBox.repaint();
1269:         }
1270:       else if (e.getPropertyName().equals("dataModel"))
1271:         {
1272:       // remove ListDataListener from old model and add it to new model
1273:       ComboBoxModel oldModel = (ComboBoxModel) e.getOldValue();
1274:       if (oldModel != null)
1275:         oldModel.removeListDataListener(listDataListener);
1276: 
1277:       if ((ComboBoxModel) e.getNewValue() != null)
1278:         comboBox.getModel().addListDataListener(listDataListener);
1279:         }
1280:       else if (e.getPropertyName().equals("font"))
1281:         {
1282:           Font font = (Font) e.getNewValue();
1283:           editor.setFont(font);
1284:           listBox.setFont(font);
1285:           arrowButton.setFont(font);
1286:           comboBox.revalidate();
1287:           comboBox.repaint();
1288:         }
1289: 
1290:       // FIXME: Need to handle changes in other bound properties.    
1291:     }
1292:   }
1293: 
1294:   /**
1295:    * A handler for mouse events occurring in the combo box.  An instance of 
1296:    * this class is returned by the <code>createMouseListener()</code> method.
1297:    */
1298:   private class MouseHandler extends MouseAdapter
1299:   {
1300:     /**
1301:      * Invoked when mouse is pressed over the combo box. It toggles the 
1302:      * visibility of the popup list.
1303:      *
1304:      * @param e  the event
1305:      */
1306:     public void mousePressed(MouseEvent e)
1307:     {
1308:       if (comboBox.isEnabled())
1309:         toggleOpenClose();
1310:     }
1311:   }
1312: }