Source for javax.swing.plaf.basic.BasicComboBoxUI

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