Source for javax.swing.plaf.basic.BasicComboBoxUI

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