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