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