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 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.Graphics;
  47: import java.awt.Insets;
  48: import java.awt.LayoutManager;
  49: import java.awt.Rectangle;
  50: import java.awt.event.FocusEvent;
  51: import java.awt.event.FocusListener;
  52: import java.awt.event.ItemEvent;
  53: import java.awt.event.ItemListener;
  54: import java.awt.event.KeyAdapter;
  55: import java.awt.event.KeyEvent;
  56: import java.awt.event.KeyListener;
  57: import java.awt.event.MouseListener;
  58: import java.awt.event.MouseMotionListener;
  59: import java.beans.PropertyChangeEvent;
  60: import java.beans.PropertyChangeListener;
  61: 
  62: import javax.accessibility.Accessible;
  63: import javax.accessibility.AccessibleContext;
  64: import javax.swing.CellRendererPane;
  65: import javax.swing.ComboBoxEditor;
  66: import javax.swing.ComboBoxModel;
  67: import javax.swing.DefaultListCellRenderer;
  68: import javax.swing.InputMap;
  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:    * List used when rendering selected item of the combo box. The selection
 133:    * and foreground colors for combo box renderer are configured from this
 134:    * list.
 135:    */
 136:   protected JList listBox;
 137: 
 138:   /**
 139:    * ListDataListener listening to JComboBox model
 140:    */
 141:   protected ListDataListener listDataListener;
 142: 
 143:   /**
 144:    * Popup list containing the combo box's menu items.
 145:    */
 146:   protected ComboPopup popup;
 147:   
 148:   protected KeyListener popupKeyListener;
 149:   
 150:   protected MouseListener popupMouseListener;
 151:   
 152:   protected MouseMotionListener popupMouseMotionListener;
 153: 
 154:   /**
 155:    * Listener listening to changes in the bound properties of JComboBox
 156:    */
 157:   protected PropertyChangeListener propertyChangeListener;
 158: 
 159:   /* Size of the largest item in the comboBox
 160:    * This is package-private to avoid an accessor method.
 161:    */
 162:   Dimension displaySize = new Dimension();
 163: 
 164:   /**
 165:    * Used to render the combo box values.
 166:    */
 167:   protected CellRendererPane currentValuePane;
 168: 
 169:   /**
 170:    * The current minimum size if isMinimumSizeDirty is false.
 171:    * Setup by getMinimumSize() and invalidated by the various listeners.
 172:    */
 173:   protected Dimension cachedMinimumSize;
 174: 
 175:   /**
 176:    * Indicates whether or not the cachedMinimumSize field is valid or not.
 177:    */
 178:   protected boolean isMinimumSizeDirty = true;
 179: 
 180:   /**
 181:    * Creates a new <code>BasicComboBoxUI</code> object.
 182:    */
 183:   public BasicComboBoxUI()
 184:   {
 185:     currentValuePane = new CellRendererPane();
 186:     cachedMinimumSize = new Dimension();
 187:   }
 188: 
 189:   /**
 190:    * A factory method to create a UI delegate for the given 
 191:    * {@link JComponent}, which should be a {@link JComboBox}.
 192:    *
 193:    * @param c The {@link JComponent} a UI is being created for.
 194:    *
 195:    * @return A UI delegate for the {@link JComponent}.
 196:    */
 197:   public static ComponentUI createUI(JComponent c)
 198:   {
 199:     return new BasicComboBoxUI();
 200:   }
 201: 
 202:   /**
 203:    * Installs the UI for the given {@link JComponent}.
 204:    *
 205:    * @param c  the JComponent to install a UI for.
 206:    * 
 207:    * @see #uninstallUI(JComponent)
 208:    */
 209:   public void installUI(JComponent c)
 210:   {
 211:     super.installUI(c);
 212: 
 213:     if (c instanceof JComboBox)
 214:       {
 215:         isMinimumSizeDirty = true;
 216:         comboBox = (JComboBox) c;
 217:         installDefaults();
 218:         popup = createPopup();
 219:         listBox = popup.getList();
 220: 
 221:         // Set editor and renderer for the combo box. Editor is used
 222:         // only if combo box becomes editable, otherwise renderer is used
 223:         // to paint the selected item; combobox is not editable by default.
 224:         ListCellRenderer renderer = comboBox.getRenderer();
 225:         if (renderer == null || renderer instanceof UIResource)
 226:           comboBox.setRenderer(createRenderer());
 227: 
 228:         ComboBoxEditor currentEditor = comboBox.getEditor();
 229:         if (currentEditor == null || currentEditor instanceof UIResource)
 230:           {
 231:             currentEditor = createEditor();
 232:             comboBox.setEditor(currentEditor);
 233:           } 
 234: 
 235:         installComponents();
 236:         installListeners();
 237:         comboBox.setLayout(createLayoutManager());
 238:         comboBox.setFocusable(true);
 239:         installKeyboardActions();
 240:         comboBox.putClientProperty(BasicLookAndFeel.DONT_CANCEL_POPUP,
 241:                                    Boolean.TRUE);
 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:     comboBox.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 and install arrow button
 474:     arrowButton = createArrowButton();
 475:     comboBox.add(arrowButton);
 476:     if (arrowButton != null)
 477:       configureArrowButton();
 478: 
 479:     if (comboBox.isEditable())
 480:       addEditor();
 481: 
 482:     comboBox.add(currentValuePane);
 483:   }
 484: 
 485:   /**
 486:    * Uninstalls components from this {@link JComboBox}.
 487:    * 
 488:    * @see #installComponents()
 489:    */
 490:   protected void uninstallComponents()
 491:   {
 492:     // Unconfigure arrow button.
 493:     if (arrowButton != null)
 494:       {
 495:         unconfigureArrowButton();
 496:       }
 497: 
 498:     // Unconfigure editor.
 499:     if (editor != null)
 500:       {
 501:         unconfigureEditor();
 502:       }
 503: 
 504:     comboBox.removeAll();
 505:     arrowButton = null;
 506:   }
 507: 
 508:   /**
 509:    * Adds the current editor to the combo box.
 510:    */
 511:   public void addEditor()
 512:   {
 513:     removeEditor();
 514:     editor = comboBox.getEditor().getEditorComponent();
 515:     if (editor != null)
 516:       {
 517:         configureEditor();
 518:         comboBox.add(editor);
 519:       }
 520:   }
 521: 
 522:   /**
 523:    * Removes the current editor from the combo box.
 524:    */
 525:   public void removeEditor()
 526:   {
 527:     if (editor != null)
 528:       {
 529:         unconfigureEditor();
 530:         comboBox.remove(editor);
 531:       }
 532:   }
 533: 
 534:   /**
 535:    * Configures the editor for this combo box.
 536:    */
 537:   protected void configureEditor()
 538:   {
 539:     editor.setFont(comboBox.getFont());
 540:     if (popupKeyListener != null)
 541:       editor.addKeyListener(popupKeyListener);
 542:     if (keyListener != null)
 543:       editor.addKeyListener(keyListener);
 544:     comboBox.configureEditor(comboBox.getEditor(),
 545:                              comboBox.getSelectedItem());
 546:   }
 547: 
 548:   /**
 549:    * Unconfigures the editor for this combo box. 
 550:    */
 551:   protected void unconfigureEditor()
 552:   {
 553:     if (popupKeyListener != null)
 554:       editor.removeKeyListener(popupKeyListener);
 555:     if (keyListener != null)
 556:       editor.removeKeyListener(keyListener);
 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:         arrowButton.addMouseListener(popup.getMouseListener());
 571:         arrowButton.addMouseMotionListener(popup.getMouseMotionListener());
 572:         
 573:         // Mark the button as not closing the popup, we handle this ourselves.
 574:         arrowButton.putClientProperty(BasicLookAndFeel.DONT_CANCEL_POPUP,
 575:                                       Boolean.TRUE);
 576:       }
 577:   }
 578: 
 579:   /**
 580:    * Unconfigures the arrow button.
 581:    * 
 582:    * @see #configureArrowButton()
 583:    *
 584:    * @specnote The specification says this method is implementation specific
 585:    *           and should not be used or overridden.
 586:    */
 587:   public void unconfigureArrowButton()
 588:   {
 589:     if (arrowButton != null)
 590:       {
 591:         if (popupMouseListener != null)
 592:           arrowButton.removeMouseListener(popupMouseListener);
 593:         if (popupMouseMotionListener != null)
 594:           arrowButton.removeMouseMotionListener(popupMouseMotionListener);
 595:       }
 596:   }
 597: 
 598:   /**
 599:    * Creates an arrow button for this {@link JComboBox}.  The arrow button is
 600:    * displayed at the right end of the combo box and is used to display/hide
 601:    * the drop down list of items.
 602:    *
 603:    * @return A new button.
 604:    */
 605:   protected JButton createArrowButton()
 606:   {
 607:     return new BasicArrowButton(BasicArrowButton.SOUTH);
 608:   }
 609: 
 610:   /**
 611:    * Returns <code>true</code> if the popup is visible, and <code>false</code>
 612:    * otherwise.
 613:    *
 614:    * @param c The JComboBox to check
 615:    *
 616:    * @return <code>true</code> if popup part of the JComboBox is visible and 
 617:    *         <code>false</code> otherwise.
 618:    */
 619:   public boolean isPopupVisible(JComboBox c)
 620:   {
 621:     return popup.isVisible();
 622:   }
 623: 
 624:   /**
 625:    * Displays/hides the {@link JComboBox}'s list of items on the screen.
 626:    *
 627:    * @param c The combo box, for which list of items should be
 628:    *        displayed/hidden
 629:    * @param v true if show popup part of the jcomboBox and false to hide.
 630:    */
 631:   public void setPopupVisible(JComboBox c, boolean v)
 632:   {
 633:     if (v)
 634:       popup.show();
 635:     else
 636:       popup.hide();
 637:   }
 638: 
 639:   /**
 640:    * JComboBox is focus traversable if it is editable and not otherwise.
 641:    *
 642:    * @param c combo box for which to check whether it is focus traversable
 643:    *
 644:    * @return true if focus tranversable and false otherwise
 645:    */
 646:   public boolean isFocusTraversable(JComboBox c)
 647:   {
 648:     if (!comboBox.isEditable())
 649:       return true;
 650: 
 651:     return false;
 652:   }
 653: 
 654:   /**
 655:    * Paints given menu item using specified graphics context
 656:    *
 657:    * @param g The graphics context used to paint this combo box
 658:    * @param c comboBox which needs to be painted.
 659:    */
 660:   public void paint(Graphics g, JComponent c)
 661:   {
 662:     hasFocus = comboBox.hasFocus();
 663:     if (! comboBox.isEditable())
 664:       {
 665:         Rectangle rect = rectangleForCurrentValue();
 666:         paintCurrentValueBackground(g, rect, hasFocus);
 667:         paintCurrentValue(g, rect, hasFocus);
 668:       }
 669:   }
 670: 
 671:   /**
 672:    * Returns preferred size for the combo box.
 673:    *
 674:    * @param c comboBox for which to get preferred size
 675:    *
 676:    * @return The preferred size for the given combo box
 677:    */
 678:   public Dimension getPreferredSize(JComponent c)
 679:   {
 680:     return getMinimumSize(c);
 681:   }
 682: 
 683:   /**
 684:    * Returns the minimum size for this {@link JComboBox} for this
 685:    * look and feel. Also makes sure cachedMinimimSize is setup correctly.
 686:    *
 687:    * @param c The {@link JComponent} to find the minimum size for.
 688:    *
 689:    * @return The dimensions of the minimum size.
 690:    */
 691:   public Dimension getMinimumSize(JComponent c)
 692:   {
 693:     if (isMinimumSizeDirty)
 694:       {
 695:         Insets i = getInsets();
 696:         Dimension d = getDisplaySize();
 697:         d.width += i.left + i.right + d.height;
 698:         cachedMinimumSize = new Dimension(d.width, d.height + i.top + i.bottom);
 699:         isMinimumSizeDirty = false;
 700:       }
 701:     return new Dimension(cachedMinimumSize);
 702:   }
 703: 
 704:   /**
 705:    * Returns the maximum size for this {@link JComboBox} for this
 706:    * look and feel.
 707:    *
 708:    * @param c The {@link JComponent} to find the maximum size for
 709:    *
 710:    * @return The maximum size (<code>Dimension(32767, 32767)</code>).
 711:    */
 712:   public Dimension getMaximumSize(JComponent c)
 713:   {
 714:     return new Dimension(32767, 32767);
 715:   }
 716: 
 717:   /**
 718:    * Returns the number of accessible children of the combobox.
 719:    *
 720:    * @param c the component (combobox) to check, ignored
 721:    *
 722:    * @return the number of accessible children of the combobox
 723:    */
 724:   public int getAccessibleChildrenCount(JComponent c)
 725:   {
 726:     int count = 1;
 727:     if (comboBox.isEditable())
 728:       count = 2;
 729:     return count;
 730:   }
 731: 
 732:   /**
 733:    * Returns the accessible child with the specified index.
 734:    *
 735:    * @param c the component, this is ignored
 736:    * @param i the index of the accessible child to return
 737:    */
 738:   public Accessible getAccessibleChild(JComponent c, int i)
 739:   {
 740:     Accessible child = null;
 741:     switch (i)
 742:     {
 743:       case 0: // The popup.
 744:         if (popup instanceof Accessible)
 745:           {
 746:             AccessibleContext ctx = ((Accessible) popup).getAccessibleContext();
 747:             ctx.setAccessibleParent(comboBox);
 748:             child = (Accessible) popup;
 749:           }
 750:         break;
 751:       case 1: // The editor, if any.
 752:         if (comboBox.isEditable() && editor instanceof Accessible)
 753:           {
 754:             AccessibleContext ctx =
 755:               ((Accessible) editor).getAccessibleContext();
 756:             ctx.setAccessibleParent(comboBox);
 757:             child = (Accessible) editor;
 758:           }
 759:         break;
 760:     }
 761:     return child;
 762:   }
 763: 
 764:   /**
 765:    * Returns true if the specified key is a navigation key and false otherwise
 766:    *
 767:    * @param keyCode a key for which to check whether it is navigation key or
 768:    *        not.
 769:    *
 770:    * @return true if the specified key is a navigation key and false otherwis
 771:    */
 772:   protected boolean isNavigationKey(int keyCode)
 773:   {
 774:     return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN
 775:            || keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT
 776:            || keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_ESCAPE
 777:            || keyCode == KeyEvent.VK_TAB;
 778:   }
 779: 
 780:   /**
 781:    * Selects next possible item relative to the current selection
 782:    * to be next selected item in the combo box.
 783:    */
 784:   protected void selectNextPossibleValue()
 785:   {
 786:     int index = comboBox.getSelectedIndex();
 787:     if (index != comboBox.getItemCount() - 1)
 788:       comboBox.setSelectedIndex(index + 1);
 789:   }
 790: 
 791:   /**
 792:    * Selects previous item relative to current selection to be
 793:    * next selected item.
 794:    */
 795:   protected void selectPreviousPossibleValue()
 796:   {
 797:     int index = comboBox.getSelectedIndex();
 798:     if (index > 0)
 799:       comboBox.setSelectedIndex(index - 1);
 800:   }
 801: 
 802:   /**
 803:    * Displays combo box popup if the popup is not currently shown
 804:    * on the screen and hides it if it is currently shown
 805:    */
 806:   protected void toggleOpenClose()
 807:   {
 808:     setPopupVisible(comboBox, ! isPopupVisible(comboBox));
 809:   }
 810: 
 811:   /**
 812:    * Returns the bounds in which comboBox's selected item will be
 813:    * displayed.
 814:    *
 815:    * @return rectangle bounds in which comboBox's selected Item will be
 816:    *         displayed
 817:    */
 818:   protected Rectangle rectangleForCurrentValue()
 819:   {
 820:     int w = comboBox.getWidth();
 821:     int h = comboBox.getHeight();
 822:     Insets i = comboBox.getInsets();
 823:     int arrowSize = h - (i.top + i.bottom);
 824:     if (arrowButton != null)
 825:       arrowSize = arrowButton.getWidth();
 826:     return new Rectangle(i.left, i.top, w - (i.left + i.right + arrowSize),
 827:                          h - (i.top + i.left));
 828:   }
 829: 
 830:   /**
 831:    * Returns the insets of the current border.
 832:    *
 833:    * @return Insets representing space between combo box and its border
 834:    */
 835:   protected Insets getInsets()
 836:   {
 837:     return comboBox.getInsets();
 838:   }
 839: 
 840:   /**
 841:    * Paints currently selected value in the main part of the combo
 842:    * box (part without popup).
 843:    *
 844:    * @param g graphics context
 845:    * @param bounds Rectangle representing the size of the area in which
 846:    *        selected item should be drawn
 847:    * @param hasFocus true if combo box has focus and false otherwise
 848:    */
 849:   public void paintCurrentValue(Graphics g, Rectangle bounds, boolean hasFocus)
 850:   {
 851:     /* Gets the component to be drawn for the current value.
 852:      * If there is currently no selected item we will take an empty
 853:      * String as replacement.
 854:      */
 855:     ListCellRenderer renderer = comboBox.getRenderer();
 856:     if (comboBox.getSelectedIndex() != -1)
 857:       {
 858:         Component comp;
 859:         if (hasFocus && ! isPopupVisible(comboBox))
 860:           {
 861:             comp = renderer.getListCellRendererComponent(listBox,
 862:                 comboBox.getSelectedItem(), -1, true, false);
 863:           }
 864:         else
 865:           {
 866:             comp = renderer.getListCellRendererComponent(listBox,
 867:                 comboBox.getSelectedItem(), -1, false, false);
 868:             Color bg = UIManager.getColor("ComboBox.disabledForeground");
 869:             comp.setBackground(bg);
 870:           }
 871:         comp.setFont(comboBox.getFont());
 872:         if (hasFocus && ! isPopupVisible(comboBox))
 873:           {
 874:             comp.setForeground(listBox.getSelectionForeground());
 875:             comp.setBackground(listBox.getSelectionBackground());
 876:           }
 877:         else if (comboBox.isEnabled())
 878:           {
 879:             comp.setForeground(comboBox.getForeground());
 880:             comp.setBackground(comboBox.getBackground());
 881:           }
 882:         else
 883:           {
 884:             Color fg = UIManager.getColor("ComboBox.disabledForeground");
 885:             comp.setForeground(fg);
 886:             Color bg = UIManager.getColor("ComboBox.disabledBackground");
 887:             comp.setBackground(bg);
 888:           }
 889:         currentValuePane.paintComponent(g, comp, comboBox, bounds.x, bounds.y,
 890:                                         bounds.width, bounds.height);
 891:       }
 892:   }
 893: 
 894:   /**
 895:    * Paints the background of part of the combo box, where currently
 896:    * selected value is displayed. If the combo box has focus this method
 897:    * should also paint focus rectangle around the combo box.
 898:    *
 899:    * @param g graphics context
 900:    * @param bounds Rectangle representing the size of the largest item  in the
 901:    *        comboBox
 902:    * @param hasFocus true if combo box has fox and false otherwise
 903:    */
 904:   public void paintCurrentValueBackground(Graphics g, Rectangle bounds,
 905:                                           boolean hasFocus)
 906:   {
 907:     Color saved = g.getColor();
 908:     if (comboBox.isEnabled())
 909:       g.setColor(UIManager.getColor("UIManager.background"));
 910:     else
 911:       g.setColor(UIManager.getColor("UIManager.disabledBackground"));
 912:     g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
 913:     g.setColor(saved);
 914:   }
 915: 
 916:   private static final ListCellRenderer DEFAULT_RENDERER
 917:     = new DefaultListCellRenderer();
 918: 
 919:   /**
 920:    * Returns the default size for the display area of a combo box that does 
 921:    * not contain any elements.  This method returns the width and height of
 922:    * a single space in the current font, plus a margin of 1 pixel. 
 923:    *
 924:    * @return The default display size.
 925:    * 
 926:    * @see #getDisplaySize()
 927:    */
 928:   protected Dimension getDefaultSize()
 929:   {
 930:     Component comp = DEFAULT_RENDERER.getListCellRendererComponent(listBox,
 931:         " ", -1, false, false);
 932:     currentValuePane.add(comp);
 933:     comp.setFont(comboBox.getFont());
 934:     Dimension d = comp.getPreferredSize();
 935:     currentValuePane.remove(comp);
 936:     return d;
 937:   }
 938: 
 939:   /**
 940:    * Returns the size of the display area for the combo box. This size will be 
 941:    * the size of the combo box, not including the arrowButton.
 942:    *
 943:    * @return The size of the display area for the combo box.
 944:    */
 945:   protected Dimension getDisplaySize()
 946:   {
 947:     Dimension dim = new Dimension();
 948:     ListCellRenderer renderer = comboBox.getRenderer();
 949:     if (renderer == null)
 950:       {
 951:         renderer = DEFAULT_RENDERER;
 952:       }
 953:     
 954:     Object prototype = comboBox.getPrototypeDisplayValue();
 955:     if (prototype != null)
 956:       {
 957:         Component comp = renderer.getListCellRendererComponent(listBox, 
 958:             prototype, -1, false, false);
 959:         currentValuePane.add(comp);
 960:         comp.setFont(comboBox.getFont());
 961:         Dimension renderSize = comp.getPreferredSize();
 962:         currentValuePane.remove(comp);
 963:         dim.height = renderSize.height;
 964:         dim.width = renderSize.width;
 965:       }
 966:     else
 967:       {
 968:         ComboBoxModel model = comboBox.getModel();
 969:         int size = model.getSize();
 970:         if (size > 0)
 971:           {
 972:             for (int i = 0; i < size; ++i)
 973:               {
 974:                 Component comp = renderer.getListCellRendererComponent(listBox, 
 975:                     model.getElementAt(i), -1, false, false);
 976:                 currentValuePane.add(comp);
 977:                 comp.setFont(comboBox.getFont());
 978:                 Dimension renderSize = comp.getPreferredSize();
 979:                 currentValuePane.remove(comp);
 980:                 dim.width = Math.max(dim.width, renderSize.width);
 981:                 dim.height = Math.max(dim.height, renderSize.height);
 982:               }
 983:           }
 984:         else
 985:           {
 986:             dim = getDefaultSize();
 987:             if (comboBox.isEditable())
 988:               dim.width = 100;
 989:           }
 990:       }
 991:     if (comboBox.isEditable())
 992:       {
 993:         Dimension editSize = editor.getPreferredSize();
 994:         dim.width = Math.max(dim.width, editSize.width);
 995:         dim.height = Math.max(dim.height, editSize.height);
 996:       }
 997:     displaySize.setSize(dim.width, dim.height);
 998:     return dim;
 999:   }
1000: 
1001:   /**
1002:    * Installs the keyboard actions for the {@link JComboBox} as specified
1003:    * by the look and feel.
1004:    */
1005:   protected void installKeyboardActions()
1006:   {
1007:     SwingUtilities.replaceUIInputMap(comboBox,
1008:         JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
1009:         (InputMap) UIManager.get("ComboBox.ancestorInputMap"));
1010:     // Install any action maps here.
1011:   }
1012:   
1013:   /**
1014:    * Uninstalls the keyboard actions for the {@link JComboBox} there were
1015:    * installed by in {@link #installListeners}.
1016:    */
1017:   protected void uninstallKeyboardActions()
1018:   {
1019:     SwingUtilities.replaceUIInputMap(comboBox,
1020:         JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
1021:     // Uninstall any action maps here.
1022:   }
1023: 
1024:   /**
1025:    * A {@link LayoutManager} used to position the sub-components of the
1026:    * {@link JComboBox}.
1027:    * 
1028:    * @see BasicComboBoxUI#createLayoutManager()
1029:    */
1030:   public class ComboBoxLayoutManager implements LayoutManager
1031:   {
1032:     /**
1033:      * Creates a new ComboBoxLayoutManager object.
1034:      */
1035:     public ComboBoxLayoutManager()
1036:     {
1037:       // Nothing to do here.
1038:     }
1039: 
1040:     /**
1041:      * Adds a component to the layout.  This method does nothing, since the
1042:      * layout manager doesn't need to track the components.
1043:      * 
1044:      * @param name  the name to associate the component with (ignored).
1045:      * @param comp  the component (ignored).
1046:      */
1047:     public void addLayoutComponent(String name, Component comp)
1048:     {
1049:       // Do nothing
1050:     }
1051: 
1052:     /**
1053:      * Removes a component from the layout.  This method does nothing, since
1054:      * the layout manager doesn't need to track the components.
1055:      * 
1056:      * @param comp  the component.
1057:      */
1058:     public void removeLayoutComponent(Component comp)
1059:     {
1060:       // Do nothing
1061:     }
1062: 
1063:     /**
1064:      * Returns preferred layout size of the JComboBox.
1065:      *
1066:      * @param parent  the Container for which the preferred size should be 
1067:      *                calculated.
1068:      *
1069:      * @return The preferred size for the given container
1070:      */
1071:     public Dimension preferredLayoutSize(Container parent)
1072:     {
1073:       return parent.getPreferredSize();
1074:     }
1075: 
1076:     /**
1077:      * Returns the minimum layout size.
1078:      * 
1079:      * @param parent  the container.
1080:      * 
1081:      * @return The minimum size.
1082:      */
1083:     public Dimension minimumLayoutSize(Container parent)
1084:     {
1085:       return parent.getMinimumSize();
1086:     }
1087: 
1088:     /**
1089:      * Arranges the components in the container.  It puts arrow
1090:      * button right end part of the comboBox. If the comboBox is editable
1091:      * then editor is placed to the left of arrow  button, starting from the
1092:      * beginning.
1093:      *
1094:      * @param parent Container that should be layed out.
1095:      */
1096:     public void layoutContainer(Container parent)
1097:     {
1098:       // Position editor component to the left of arrow button if combo box is 
1099:       // editable
1100:       Insets i = getInsets();
1101:       int arrowSize = comboBox.getHeight() - (i.top + i.bottom);
1102: 
1103:       if (arrowButton != null)
1104:         arrowButton.setBounds(comboBox.getWidth() - (i.right + arrowSize),
1105:                               i.top, arrowSize, arrowSize);
1106:       if (editor != null)
1107:         editor.setBounds(rectangleForCurrentValue());
1108:     }
1109:   }
1110: 
1111:   /**
1112:    * Handles focus changes occuring in the combo box. This class is
1113:    * responsible for repainting combo box whenever focus is gained or lost
1114:    * and also for hiding popup list of items whenever combo box loses its
1115:    * focus.
1116:    */
1117:   public class FocusHandler extends Object implements FocusListener
1118:   {
1119:     /**
1120:      * Creates a new FocusHandler object.
1121:      */
1122:     public FocusHandler()
1123:     {
1124:       // Nothing to do here.
1125:     }
1126: 
1127:     /**
1128:      * Invoked when combo box gains focus. It repaints main
1129:      * part of combo box accordingly.
1130:      *
1131:      * @param e the FocusEvent
1132:      */
1133:     public void focusGained(FocusEvent e)
1134:     {
1135:       hasFocus = true;
1136:       comboBox.repaint();
1137:     }
1138: 
1139:     /**
1140:      * Invoked when the combo box loses focus.  It repaints the main part
1141:      * of the combo box accordingly and hides the popup list of items.
1142:      *
1143:      * @param e the FocusEvent
1144:      */
1145:     public void focusLost(FocusEvent e)
1146:     {
1147:       hasFocus = false;
1148:       if (! e.isTemporary() && comboBox.isLightWeightPopupEnabled())
1149:         setPopupVisible(comboBox, false);
1150:       comboBox.repaint();
1151:     }
1152:   }
1153: 
1154:   /**
1155:    * Handles {@link ItemEvent}s fired by the {@link JComboBox} when its 
1156:    * selected item changes.
1157:    */
1158:   public class ItemHandler extends Object implements ItemListener
1159:   {
1160:     /**
1161:      * Creates a new ItemHandler object.
1162:      */
1163:     public ItemHandler()
1164:     {
1165:       // Nothing to do here.
1166:     }
1167: 
1168:     /**
1169:      * Invoked when selected item becomes deselected or when
1170:      * new item becomes selected.
1171:      *
1172:      * @param e the ItemEvent representing item's state change.
1173:      */
1174:     public void itemStateChanged(ItemEvent e)
1175:     {
1176:       ComboBoxModel model = comboBox.getModel();
1177:       Object v = model.getSelectedItem();
1178:       if (editor != null)
1179:         comboBox.configureEditor(comboBox.getEditor(), v);
1180:       comboBox.repaint();
1181:     }
1182:   }
1183: 
1184:   /**
1185:    * KeyHandler handles key events occuring while JComboBox has focus.
1186:    */
1187:   public class KeyHandler extends KeyAdapter
1188:   {
1189:     public KeyHandler()
1190:     {
1191:       // Nothing to do here.
1192:     }
1193: 
1194:     /**
1195:      * Invoked whenever key is pressed while JComboBox is in focus.
1196:      */
1197:     public void keyPressed(KeyEvent e)
1198:     {
1199:       if (comboBox.getModel().getSize() != 0 && comboBox.isEnabled())
1200:         {
1201:           if (! isNavigationKey(e.getKeyCode()))
1202:             {
1203:               if (! comboBox.isEditable())
1204:                 if (comboBox.selectWithKeyChar(e.getKeyChar()))
1205:                   e.consume();
1206:             }
1207:           else
1208:             {
1209:               if (e.getKeyCode() == KeyEvent.VK_UP && comboBox.isPopupVisible())
1210:                 selectPreviousPossibleValue();
1211:               else if (e.getKeyCode() == KeyEvent.VK_DOWN)
1212:                 {
1213:                   if (comboBox.isPopupVisible())
1214:                     selectNextPossibleValue();
1215:                   else
1216:                     comboBox.showPopup();
1217:                 }
1218:               else if (e.getKeyCode() == KeyEvent.VK_ENTER
1219:                        || e.getKeyCode() == KeyEvent.VK_ESCAPE)
1220:                 popup.hide();
1221:             }
1222:         }
1223:     }
1224:   }
1225: 
1226:   /**
1227:    * Handles the changes occurring in the JComboBox's data model.
1228:    */
1229:   public class ListDataHandler extends Object implements ListDataListener
1230:   {
1231:     /**
1232:      * Creates a new ListDataHandler object.
1233:      */
1234:     public ListDataHandler()
1235:     {
1236:       // Nothing to do here.
1237:     }
1238: 
1239:     /**
1240:      * Invoked if the content's of JComboBox's data model are changed.
1241:      *
1242:      * @param e ListDataEvent describing the change.
1243:      */
1244:     public void contentsChanged(ListDataEvent e)
1245:     {
1246:       if (e.getIndex0() != -1 || e.getIndex1() != -1)
1247:         {
1248:           isMinimumSizeDirty = true;
1249:           comboBox.revalidate();
1250:         }
1251:       if (editor != null)
1252:         comboBox.configureEditor(comboBox.getEditor(),
1253:             comboBox.getSelectedItem());
1254:       comboBox.repaint();
1255:     }
1256: 
1257:     /**
1258:      * Invoked when items are added to the JComboBox's data model.
1259:      *
1260:      * @param e ListDataEvent describing the change.
1261:      */
1262:     public void intervalAdded(ListDataEvent e)
1263:     {
1264:       int start = e.getIndex0();
1265:       int end = e.getIndex1();
1266:       if (start == 0 && comboBox.getItemCount() - (end - start + 1) == 0)
1267:         contentsChanged(e);
1268:       else if (start != -1  || end != -1)
1269:         {
1270:           ListCellRenderer renderer = comboBox.getRenderer();
1271:           ComboBoxModel model = comboBox.getModel();
1272:           int w = displaySize.width;
1273:           int h = displaySize.height;
1274:           // TODO: Optimize using prototype here.
1275:           for (int i = start; i <= end; ++i)
1276:             {
1277:               Component comp = renderer.getListCellRendererComponent(listBox,
1278:                   model.getElementAt(i), -1, false, false);
1279:               currentValuePane.add(comp);
1280:               comp.setFont(comboBox.getFont());
1281:               Dimension dim = comp.getPreferredSize();
1282:               w = Math.max(w, dim.width);
1283:               h = Math.max(h, dim.height);
1284:               currentValuePane.remove(comp);
1285:             }
1286:           if (displaySize.width < w || displaySize.height < h)
1287:             {
1288:               if (displaySize.width < w)
1289:                 displaySize.width = w;
1290:               if (displaySize.height < h)
1291:                 displaySize.height = h;
1292:               comboBox.revalidate();
1293:               if (editor != null)
1294:                 {
1295:                   comboBox.configureEditor(comboBox.getEditor(),
1296:                                            comboBox.getSelectedItem());
1297:                 }
1298:             }
1299:         }
1300:       
1301:     }
1302: 
1303:     /**
1304:      * Invoked when items are removed from the JComboBox's
1305:      * data model.
1306:      *
1307:      * @param e ListDataEvent describing the change.
1308:      */
1309:     public void intervalRemoved(ListDataEvent e)
1310:     {
1311:       contentsChanged(e);
1312:     }
1313:   }
1314: 
1315:   /**
1316:    * Handles {@link PropertyChangeEvent}s fired by the {@link JComboBox}.
1317:    */
1318:   public class PropertyChangeHandler extends Object
1319:     implements PropertyChangeListener
1320:   {
1321:     /**
1322:      * Creates a new instance.
1323:      */
1324:     public PropertyChangeHandler()
1325:     {
1326:       // Nothing to do here.
1327:     }
1328: 
1329:     /**
1330:      * Invoked whenever bound property of JComboBox changes.
1331:      * 
1332:      * @param e  the event.
1333:      */
1334:     public void propertyChange(PropertyChangeEvent e)
1335:     {
1336:       // Lets assume every change invalidates the minimumsize.
1337:       String propName = e.getPropertyName();
1338:       if (propName.equals("enabled"))
1339:         {
1340:           boolean enabled = comboBox.isEnabled();
1341:           if (editor != null)
1342:             editor.setEnabled(enabled);
1343:           if (arrowButton != null)
1344:             arrowButton.setEnabled(enabled);
1345: 
1346:           comboBox.repaint();
1347:         }
1348:       else if (propName.equals("editor") && comboBox.isEditable())
1349:         {
1350:           addEditor();
1351:           comboBox.revalidate();
1352:         }
1353:       else if (e.getPropertyName().equals("editable"))
1354:         {
1355:           if (comboBox.isEditable())
1356:             {
1357:               addEditor();
1358:             }
1359:           else
1360:             {
1361:               removeEditor();
1362:             }
1363: 
1364:           comboBox.revalidate();
1365:         }
1366:       else if (propName.equals("model"))
1367:         {
1368:           // remove ListDataListener from old model and add it to new model
1369:           ComboBoxModel oldModel = (ComboBoxModel) e.getOldValue();
1370:           if (oldModel != null && listDataListener != null)
1371:             oldModel.removeListDataListener(listDataListener);
1372: 
1373:           ComboBoxModel newModel = (ComboBoxModel) e.getNewValue();
1374:           if (newModel != null && listDataListener != null)
1375:             comboBox.getModel().addListDataListener(listDataListener);
1376: 
1377:           if (editor != null)
1378:             {
1379:               comboBox.configureEditor(comboBox.getEditor(),
1380:                                        comboBox.getSelectedItem());
1381:             }
1382:           isMinimumSizeDirty = true;
1383:           comboBox.revalidate();
1384:           comboBox.repaint();
1385:         }
1386:       else if (propName.equals("font"))
1387:         {
1388:           Font font = (Font) e.getNewValue();
1389:           if (editor != null)
1390:             {
1391:               editor.setFont(font);
1392:             }
1393:           listBox.setFont(font);
1394:           isMinimumSizeDirty = true;
1395:           comboBox.revalidate();
1396:         }
1397:       else if (propName.equals("prototypeDisplayValue"))
1398:         {
1399:           isMinimumSizeDirty = true;
1400:           comboBox.revalidate();
1401:         }
1402:       else if (propName.equals("renderer"))
1403:         {
1404:           isMinimumSizeDirty = true;
1405:           comboBox.revalidate();
1406:         }
1407:       // FIXME: Need to handle changes in other bound properties.       
1408:     }
1409:   }
1410: }