Source for javax.swing.plaf.basic.BasicComboPopup

   1: /* BasicComboPopup.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.Dimension;
  44: import java.awt.Insets;
  45: import java.awt.Point;
  46: import java.awt.Rectangle;
  47: import java.awt.event.ItemEvent;
  48: import java.awt.event.ItemListener;
  49: import java.awt.event.KeyAdapter;
  50: import java.awt.event.KeyEvent;
  51: import java.awt.event.KeyListener;
  52: import java.awt.event.MouseAdapter;
  53: import java.awt.event.MouseEvent;
  54: import java.awt.event.MouseListener;
  55: import java.awt.event.MouseMotionAdapter;
  56: import java.awt.event.MouseMotionListener;
  57: import java.beans.PropertyChangeEvent;
  58: import java.beans.PropertyChangeListener;
  59: 
  60: import javax.swing.BorderFactory;
  61: import javax.swing.ComboBoxModel;
  62: import javax.swing.JComboBox;
  63: import javax.swing.JList;
  64: import javax.swing.JPopupMenu;
  65: import javax.swing.JScrollBar;
  66: import javax.swing.JScrollPane;
  67: import javax.swing.ListCellRenderer;
  68: import javax.swing.ListSelectionModel;
  69: import javax.swing.MenuSelectionManager;
  70: import javax.swing.SwingConstants;
  71: import javax.swing.SwingUtilities;
  72: import javax.swing.Timer;
  73: import javax.swing.UIManager;
  74: import javax.swing.event.ListDataEvent;
  75: import javax.swing.event.ListDataListener;
  76: import javax.swing.event.ListSelectionEvent;
  77: import javax.swing.event.ListSelectionListener;
  78: import javax.swing.event.PopupMenuEvent;
  79: import javax.swing.event.PopupMenuListener;
  80: 
  81: /**
  82:  * UI Delegate for ComboPopup
  83:  *
  84:  * @author Olga Rodimina
  85:  */
  86: public class BasicComboPopup extends JPopupMenu implements ComboPopup
  87: {
  88:   /* Timer for autoscrolling */
  89:   protected Timer autoscrollTimer;
  90: 
  91:   /** ComboBox associated with this popup */
  92:   protected JComboBox comboBox;
  93: 
  94:   /** FIXME: Need to document */
  95:   protected boolean hasEntered;
  96: 
  97:   /**
  98:    * Indicates whether the scroll bar located in popup menu with comboBox's
  99:    * list of items is currently autoscrolling. This happens when mouse event
 100:    * originated in the combo box and is dragged outside of its bounds
 101:    */
 102:   protected boolean isAutoScrolling;
 103: 
 104:   /** ItemListener listening to the selection changes in the combo box */
 105:   protected ItemListener itemListener;
 106: 
 107:   /** This listener is not used */
 108:   protected KeyListener keyListener;
 109: 
 110:   /** JList which is used to display item is the combo box */
 111:   protected JList list;
 112: 
 113:   /** This listener is not used */
 114:   protected ListDataListener listDataListener;
 115: 
 116:   /**
 117:    * MouseListener listening to mouse events occuring in the  combo box's
 118:    * list.
 119:    */
 120:   protected MouseListener listMouseListener;
 121: 
 122:   /**
 123:    * MouseMotionListener listening to mouse motion events occuring  in the
 124:    * combo box's list
 125:    */
 126:   protected MouseMotionListener listMouseMotionListener;
 127: 
 128:   /** This listener is not used */
 129:   protected ListSelectionListener listSelectionListener;
 130: 
 131:   /** MouseListener listening to mouse events occuring in the combo box */
 132:   protected MouseListener mouseListener;
 133: 
 134:   /**
 135:    * MouseMotionListener listening to mouse motion events occuring in the
 136:    * combo box
 137:    */
 138:   protected MouseMotionListener mouseMotionListener;
 139: 
 140:   /**
 141:    * PropertyChangeListener listening to changes occuring in the bound
 142:    * properties of the combo box
 143:    */
 144:   protected PropertyChangeListener propertyChangeListener;
 145: 
 146:   /** direction for scrolling down list of combo box's items */
 147:   protected static final int SCROLL_DOWN = 1;
 148: 
 149:   /** direction for scrolling up list of combo box's items */
 150:   protected static final int SCROLL_UP = 0;
 151: 
 152:   /** Indicates auto scrolling direction */
 153:   protected int scrollDirection;
 154: 
 155:   /** JScrollPane that contains list portion of the combo box */
 156:   protected JScrollPane scroller;
 157: 
 158:   /** This field is not used */
 159:   protected boolean valueIsAdjusting;
 160: 
 161:   /**
 162:    * Creates a new BasicComboPopup object.
 163:    *
 164:    * @param comboBox the combo box with which this popup should be associated
 165:    */
 166:   public BasicComboPopup(JComboBox comboBox)
 167:   {
 168:     this.comboBox = comboBox;
 169:     mouseListener = createMouseListener();
 170:     mouseMotionListener = createMouseMotionListener();
 171:     keyListener = createKeyListener();
 172: 
 173:     list = createList();
 174:     configureList();
 175:     scroller = createScroller();
 176:     configureScroller();
 177:     configurePopup();
 178:     installComboBoxListeners();
 179:     installKeyboardActions();
 180:   }
 181: 
 182:   /**
 183:    * This method displays drow down list of combo box items on the screen.
 184:    */
 185:   public void show()
 186:   {
 187:     Dimension size = comboBox.getSize();
 188:     size.height = getPopupHeightForRowCount(comboBox.getMaximumRowCount());
 189:     Insets i = getInsets();
 190:     size.width -= i.left + i.right;
 191:     Rectangle bounds = computePopupBounds(0, comboBox.getBounds().height,
 192:                                           size.width, size.height);
 193: 
 194:     scroller.setMaximumSize(bounds.getSize());
 195:     scroller.setPreferredSize(bounds.getSize());
 196:     scroller.setMinimumSize(bounds.getSize());
 197:     list.invalidate();
 198: 
 199:     syncListSelection();
 200: 
 201:     list.ensureIndexIsVisible(list.getSelectedIndex());
 202:     setLightWeightPopupEnabled(comboBox.isLightWeightPopupEnabled());
 203:     show(comboBox, bounds.x, bounds.y);
 204:   }
 205: 
 206:   /**
 207:    * This method hides drop down list of items
 208:    */
 209:   public void hide()
 210:   {
 211:     MenuSelectionManager menuSelectionManager =
 212:       MenuSelectionManager.defaultManager();
 213:     javax.swing.MenuElement[] menuElements =
 214:       menuSelectionManager.getSelectedPath();
 215:     for (int i = 0; i < menuElements.length; i++)
 216:       {
 217:         if (menuElements[i] == this)
 218:           {
 219:             menuSelectionManager.clearSelectedPath();
 220:             break;
 221:           }
 222:       }
 223:     comboBox.repaint();
 224:   }
 225: 
 226:   /**
 227:    * Return list cointaining JComboBox's items
 228:    *
 229:    * @return list cointaining JComboBox's items
 230:    */
 231:   public JList getList()
 232:   {
 233:     return list;
 234:   }
 235: 
 236:   /**
 237:    * Returns MouseListener that is listening to mouse events occuring in the
 238:    * combo box.
 239:    *
 240:    * @return MouseListener
 241:    */
 242:   public MouseListener getMouseListener()
 243:   {
 244:     return mouseListener;
 245:   }
 246: 
 247:   /**
 248:    * Returns MouseMotionListener that is listening to mouse  motion events
 249:    * occuring in the combo box.
 250:    *
 251:    * @return MouseMotionListener
 252:    */
 253:   public MouseMotionListener getMouseMotionListener()
 254:   {
 255:     return mouseMotionListener;
 256:   }
 257: 
 258:   /**
 259:    * Returns KeyListener listening to key events occuring in the combo box.
 260:    * This method returns null because KeyHandler is not longer used.
 261:    *
 262:    * @return KeyListener
 263:    */
 264:   public KeyListener getKeyListener()
 265:   {
 266:     return keyListener;
 267:   }
 268: 
 269:   /**
 270:    * This method uninstalls the UI for the  given JComponent.
 271:    */
 272:   public void uninstallingUI()
 273:   {
 274:     uninstallComboBoxModelListeners(comboBox.getModel());
 275:     uninstallListeners();
 276:     uninstallKeyboardActions();
 277:   }
 278: 
 279:   /**
 280:    * This method uninstalls listeners that were listening to changes occuring
 281:    * in the comb box's data model
 282:    *
 283:    * @param model data model for the combo box from which to uninstall
 284:    *        listeners
 285:    */
 286:   protected void uninstallComboBoxModelListeners(ComboBoxModel model)
 287:   {
 288:     model.removeListDataListener(listDataListener);
 289:   }
 290: 
 291:   /**
 292:    * This method uninstalls keyboard actions installed by the UI.
 293:    */
 294:   protected void uninstallKeyboardActions()
 295:   {
 296:     // Nothing to do here.
 297:   }
 298: 
 299:   /**
 300:    * This method fires PopupMenuEvent indicating that combo box's popup list
 301:    * of items will become visible
 302:    */
 303:   protected void firePopupMenuWillBecomeVisible()
 304:   {
 305:     PopupMenuListener[] ll = comboBox.getPopupMenuListeners();
 306: 
 307:     for (int i = 0; i < ll.length; i++)
 308:       ll[i].popupMenuWillBecomeVisible(new PopupMenuEvent(comboBox));
 309:   }
 310: 
 311:   /**
 312:    * This method fires PopupMenuEvent indicating that combo box's popup list
 313:    * of items will become invisible.
 314:    */
 315:   protected void firePopupMenuWillBecomeInvisible()
 316:   {
 317:     PopupMenuListener[] ll = comboBox.getPopupMenuListeners();
 318: 
 319:     for (int i = 0; i < ll.length; i++)
 320:       ll[i].popupMenuWillBecomeInvisible(new PopupMenuEvent(comboBox));
 321:   }
 322: 
 323:   /**
 324:    * This method fires PopupMenuEvent indicating that combo box's popup list
 325:    * of items was closed without selection.
 326:    */
 327:   protected void firePopupMenuCanceled()
 328:   {
 329:     PopupMenuListener[] ll = comboBox.getPopupMenuListeners();
 330: 
 331:     for (int i = 0; i < ll.length; i++)
 332:       ll[i].popupMenuCanceled(new PopupMenuEvent(comboBox));
 333:   }
 334: 
 335:   /**
 336:    * Creates MouseListener to listen to mouse events occuring in the combo
 337:    * box. Note that this listener doesn't listen to mouse events occuring in
 338:    * the popup portion of the combo box, it only listens to main combo box
 339:    * part.
 340:    *
 341:    * @return new MouseMotionListener that listens to mouse events occuring in
 342:    *         the combo box
 343:    */
 344:   protected MouseListener createMouseListener()
 345:   {
 346:     return new InvocationMouseHandler();
 347:   }
 348: 
 349:   /**
 350:    * Create Mouse listener that listens to mouse dragging events occuring in
 351:    * the combo box. This listener is responsible for changing the selection
 352:    * in the combo box list to the component over which mouse is being
 353:    * currently dragged
 354:    *
 355:    * @return new MouseMotionListener that listens to mouse dragging events
 356:    *         occuring in the combo box
 357:    */
 358:   protected MouseMotionListener createMouseMotionListener()
 359:   {
 360:     return new InvocationMouseMotionHandler();
 361:   }
 362: 
 363:   /**
 364:    * KeyListener created in this method is not used anymore.
 365:    *
 366:    * @return KeyListener that does nothing
 367:    */
 368:   protected KeyListener createKeyListener()
 369:   {
 370:     return new InvocationKeyHandler();
 371:   }
 372: 
 373:   /**
 374:    * ListSelectionListener created in this method is not used anymore
 375:    *
 376:    * @return ListSelectionListener that does nothing
 377:    */
 378:   protected ListSelectionListener createListSelectionListener()
 379:   {
 380:     return new ListSelectionHandler();
 381:   }
 382: 
 383:   /**
 384:    * Creates ListDataListener. This method returns null, because
 385:    * ListDataHandler class is obsolete and is no longer used.
 386:    *
 387:    * @return null
 388:    */
 389:   protected ListDataListener createListDataListener()
 390:   {
 391:     return null;
 392:   }
 393: 
 394:   /**
 395:    * This method creates ListMouseListener to listen to mouse events occuring
 396:    * in the combo box's item list.
 397:    *
 398:    * @return MouseListener to listen to mouse events occuring in the combo
 399:    *         box's items list.
 400:    */
 401:   protected MouseListener createListMouseListener()
 402:   {
 403:     return new ListMouseHandler();
 404:   }
 405: 
 406:   /**
 407:    * Creates ListMouseMotionlistener to listen to mouse motion events occuring
 408:    * in the combo box's list. This listener is responsible for highlighting
 409:    * items in the list when mouse is moved over them.
 410:    *
 411:    * @return MouseMotionListener that handles mouse motion events occuring in
 412:    *         the list of the combo box.
 413:    */
 414:   protected MouseMotionListener createListMouseMotionListener()
 415:   {
 416:     return new ListMouseMotionHandler();
 417:   }
 418: 
 419:   /**
 420:    * Creates PropertyChangeListener to handle changes in the JComboBox's bound
 421:    * properties.
 422:    *
 423:    * @return PropertyChangeListener to handle changes in the JComboBox's bound
 424:    *         properties.
 425:    */
 426:   protected PropertyChangeListener createPropertyChangeListener()
 427:   {
 428:     return new PropertyChangeHandler();
 429:   }
 430: 
 431:   /**
 432:    * Creates new ItemListener that will listen to ItemEvents occuring in the
 433:    * combo box.
 434:    *
 435:    * @return ItemListener to listen to ItemEvents occuring in the combo box.
 436:    */
 437:   protected ItemListener createItemListener()
 438:   {
 439:     return new ItemHandler();
 440:   }
 441: 
 442:   /**
 443:    * Creates JList that will be used to display items in the combo box.
 444:    *
 445:    * @return JList that will be used to display items in the combo box.
 446:    */
 447:   protected JList createList()
 448:   {
 449:     JList l = new JList(comboBox.getModel());
 450:     return l;
 451:   }
 452: 
 453:   /**
 454:    * This method configures the list of comboBox's items by setting  default
 455:    * properties and installing listeners.
 456:    */
 457:   protected void configureList()
 458:   {
 459:     list.setFont(comboBox.getFont());
 460:     list.setForeground(comboBox.getForeground());
 461:     list.setBackground(comboBox.getBackground());
 462:     Color sfg = UIManager.getColor("ComboBox.selectionForeground");
 463:     list.setSelectionForeground(sfg);
 464:     Color sbg = UIManager.getColor("ComboBox.selectionBackground");
 465:     list.setSelectionBackground(sbg);
 466:     list.setBorder(null);
 467:     list.setCellRenderer(comboBox.getRenderer());
 468:     list.setFocusable(false);
 469:     syncListSelection();
 470:     list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
 471:     installListListeners();
 472:   }
 473: 
 474:   /**
 475:    * This method installs list listeners.
 476:    */
 477:   protected void installListListeners()
 478:   {
 479:     // mouse listener listening to mouse events occuring in the 
 480:     // combo box's list of items.
 481:     listMouseListener = createListMouseListener();
 482:     list.addMouseListener(listMouseListener);
 483: 
 484:     // mouse listener listening to mouse motion events occuring in the
 485:     // combo box's list of items
 486:     listMouseMotionListener = createListMouseMotionListener();
 487:     list.addMouseMotionListener(listMouseMotionListener);
 488: 
 489:     listSelectionListener = createListSelectionListener();
 490:     list.addListSelectionListener(listSelectionListener);
 491:   }
 492: 
 493:   /**
 494:    * This method creates scroll pane that will contain the list of comboBox's
 495:    * items inside of it.
 496:    *
 497:    * @return JScrollPane
 498:    */
 499:   protected JScrollPane createScroller()
 500:   {
 501:     return new JScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
 502:                            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
 503:   }
 504: 
 505:   /**
 506:    * This method configures scroll pane to contain list of comboBox's  items
 507:    */
 508:   protected void configureScroller()
 509:   {
 510:     scroller.setBorder(null);
 511:     scroller.setFocusable(false);
 512:     scroller.getVerticalScrollBar().setFocusable(false);
 513:   }
 514: 
 515:   /**
 516:    * This method configures popup menu that will be used to display Scrollpane
 517:    * with list of items inside of it.
 518:    */
 519:   protected void configurePopup()
 520:   {
 521:     setBorderPainted(true);
 522:     setBorder(BorderFactory.createLineBorder(Color.BLACK));
 523:     setOpaque(false);
 524:     add(scroller);
 525:     setFocusable(false);
 526:   }
 527: 
 528:   /*
 529:    * This method installs listeners that will listen to changes occuring
 530:    * in the combo box.
 531:    */
 532:   protected void installComboBoxListeners()
 533:   {
 534:     // item listener listenening to selection events in the combo box
 535:     itemListener = createItemListener();
 536:     comboBox.addItemListener(itemListener);
 537: 
 538:     propertyChangeListener = createPropertyChangeListener();
 539:     comboBox.addPropertyChangeListener(propertyChangeListener);
 540: 
 541:     installComboBoxModelListeners(comboBox.getModel());
 542:   }
 543: 
 544:   /**
 545:    * This method installs listeners that will listen to changes occuring in
 546:    * the comb box's data model
 547:    *
 548:    * @param model data model for the combo box for which to install listeners
 549:    */
 550:   protected void installComboBoxModelListeners(ComboBoxModel model)
 551:   {
 552:     // list data listener to listen for ListDataEvents in combo box.
 553:     // This listener is now obsolete and nothing is done here
 554:     listDataListener = createListDataListener();
 555:     comboBox.getModel().addListDataListener(listDataListener);
 556:   }
 557: 
 558:   /**
 559:    * Installs the keyboard actions.
 560:    */
 561:   protected void installKeyboardActions()
 562:   {
 563:     // Nothing to do here
 564:   }
 565: 
 566:   /**
 567:    * This method always returns false to indicate that  items in the combo box
 568:    * list are not focus traversable.
 569:    *
 570:    * @return false
 571:    */
 572:   public boolean isFocusTraversable()
 573:   {
 574:     return false;
 575:   }
 576: 
 577:   /**
 578:    * This method start scrolling combo box's list of items  either up or down
 579:    * depending on the specified 'direction'
 580:    *
 581:    * @param direction of the scrolling.
 582:    */
 583:   protected void startAutoScrolling(int direction)
 584:   {
 585:     // FIXME: add timer
 586:     isAutoScrolling = true;
 587: 
 588:     if (direction == SCROLL_UP)
 589:       autoScrollUp();
 590:     else
 591:       autoScrollDown();
 592:   }
 593: 
 594:   /**
 595:    * This method stops scrolling the combo box's list of items
 596:    */
 597:   protected void stopAutoScrolling()
 598:   {
 599:     // FIXME: add timer
 600:     isAutoScrolling = false;
 601:   }
 602: 
 603:   /**
 604:    * This method scrolls up list of combo box's items up and highlights that
 605:    * just became visible.
 606:    */
 607:   protected void autoScrollUp()
 608:   {
 609:     // scroll up the scroll bar to make the item above visible    
 610:     JScrollBar scrollbar = scroller.getVerticalScrollBar();
 611:     int scrollToNext = list.getScrollableUnitIncrement(super.getBounds(),
 612:                                                        SwingConstants.VERTICAL,
 613:                                                        SCROLL_UP);
 614: 
 615:     scrollbar.setValue(scrollbar.getValue() - scrollToNext);
 616: 
 617:     // If we haven't reached the begging of the combo box's list of items, 
 618:     // then highlight next element above currently highlighted element    
 619:     if (list.getSelectedIndex() != 0)
 620:       list.setSelectedIndex(list.getSelectedIndex() - 1);
 621:   }
 622: 
 623:   /**
 624:    * This method scrolls down list of combo box's and highlights item in the
 625:    * list that just became visible.
 626:    */
 627:   protected void autoScrollDown()
 628:   {
 629:     // scroll scrollbar down to make next item visible    
 630:     JScrollBar scrollbar = scroller.getVerticalScrollBar();
 631:     int scrollToNext = list.getScrollableUnitIncrement(super.getBounds(),
 632:                                                        SwingConstants.VERTICAL,
 633:                                                        SCROLL_DOWN);
 634:     scrollbar.setValue(scrollbar.getValue() + scrollToNext);
 635: 
 636:     // If we haven't reached the end of the combo box's list of items
 637:     // then highlight next element below currently highlighted element
 638:     if (list.getSelectedIndex() + 1 != comboBox.getItemCount())
 639:       list.setSelectedIndex(list.getSelectedIndex() + 1);
 640:   }
 641: 
 642:   /**
 643:    * This method helps to delegate focus to the right component in the
 644:    * JComboBox. If the comboBox is editable then focus is sent to
 645:    * ComboBoxEditor, otherwise it is delegated to JComboBox.
 646:    *
 647:    * @param e MouseEvent
 648:    */
 649:   protected void delegateFocus(MouseEvent e)
 650:   {
 651:     if (comboBox.isEditable())
 652:       comboBox.getEditor().getEditorComponent().requestFocus();
 653:     else
 654:       comboBox.requestFocus();
 655:   }
 656: 
 657:   /**
 658:    * This method displays combo box popup if the popup is  not currently shown
 659:    * on the screen and hides it if it is  currently visible
 660:    */
 661:   protected void togglePopup()
 662:   {
 663:     if (isVisible())
 664:       hide();
 665:     else
 666:       show();
 667:   }
 668: 
 669:   /**
 670:    * DOCUMENT ME!
 671:    *
 672:    * @param e DOCUMENT ME!
 673:    *
 674:    * @return DOCUMENT ME!
 675:    */
 676:   protected MouseEvent convertMouseEvent(MouseEvent e)
 677:   {
 678:     Point point = SwingUtilities.convertPoint((Component) e.getSource(),
 679:                                               e.getPoint(), list);
 680:     MouseEvent newEvent = new MouseEvent((Component) e.getSource(),
 681:                                         e.getID(), e.getWhen(),
 682:                                         e.getModifiers(), point.x, point.y,
 683:                                         e.getModifiers(),
 684:                                         e.isPopupTrigger());
 685:     return newEvent;
 686:   }
 687: 
 688:   /**
 689:    * Returns required height of the popup such that number of items visible in
 690:    * it are equal to the maximum row count.  By default
 691:    * comboBox.maximumRowCount=8
 692:    *
 693:    * @param maxRowCount number of maximum visible rows in the  combo box's
 694:    *        popup list of items
 695:    *
 696:    * @return height of the popup required to fit number of items  equal to
 697:    *         JComboBox.maximumRowCount.
 698:    */
 699:   protected int getPopupHeightForRowCount(int maxRowCount)
 700:   {
 701:     int totalHeight = 0;
 702:     ListCellRenderer rend = list.getCellRenderer();
 703: 
 704:     if (comboBox.getItemCount() < maxRowCount)
 705:       maxRowCount = comboBox.getItemCount();
 706: 
 707:     for (int i = 0; i < maxRowCount; i++)
 708:       {
 709:     Component comp = rend.getListCellRendererComponent(list,
 710:                                                        comboBox.getModel()
 711:                                                                .getElementAt(i),
 712:                                                        -1, false, false);
 713:     Dimension dim = comp.getPreferredSize();
 714:     totalHeight += dim.height;
 715:       }
 716: 
 717:     return totalHeight == 0 ? 100 : totalHeight;
 718:   }
 719: 
 720:   /**
 721:    * DOCUMENT ME!
 722:    *
 723:    * @param px DOCUMENT ME!
 724:    * @param py DOCUMENT ME!
 725:    * @param pw DOCUMENT ME!
 726:    * @param ph DOCUMENT ME!
 727:    *
 728:    * @return DOCUMENT ME!
 729:    */
 730:   protected Rectangle computePopupBounds(int px, int py, int pw, int ph)
 731:   {
 732:     return new Rectangle(px, py, pw, ph);
 733:   }
 734: 
 735:   /**
 736:    * This method changes the selection in the list to the item over which  the
 737:    * mouse is currently located.
 738:    *
 739:    * @param anEvent MouseEvent
 740:    * @param shouldScroll DOCUMENT ME!
 741:    */
 742:   protected void updateListBoxSelectionForEvent(MouseEvent anEvent,
 743:                                                 boolean shouldScroll)
 744:   {
 745:     Point point = anEvent.getPoint();
 746:     if (list != null)
 747:       {
 748:         int index = list.locationToIndex(point);
 749:         if (index == -1)
 750:           {
 751:             if (point.y < 0)
 752:               index = 0;
 753:             else
 754:               index = comboBox.getModel().getSize() - 1;
 755:           }
 756:         if (list.getSelectedIndex() != index)
 757:           {
 758:             list.setSelectedIndex(index);
 759:             if (shouldScroll)
 760:               list.ensureIndexIsVisible(index);
 761:           }
 762:       }
 763:   }
 764: 
 765:   /**
 766:    * InvocationMouseHandler is a listener that listens to mouse events
 767:    * occuring in the combo box. Note that this listener doesn't listen to
 768:    * mouse events occuring in the popup portion of the combo box, it only
 769:    * listens to main combo box part(area that displays selected item).  This
 770:    * listener is responsible for showing and hiding popup portion  of the
 771:    * combo box.
 772:    */
 773:   protected class InvocationMouseHandler extends MouseAdapter
 774:   {
 775:     /**
 776:      * Creates a new InvocationMouseHandler object.
 777:      */
 778:     protected InvocationMouseHandler()
 779:     {
 780:       // Nothing to do here.
 781:     }
 782: 
 783:     /**
 784:      * This method is invoked whenever mouse is being pressed over the main
 785:      * part of the combo box. This method will show popup if  the popup is
 786:      * not shown on the screen right now, and it will hide popup otherwise.
 787:      *
 788:      * @param e MouseEvent that should be handled
 789:      */
 790:     public void mousePressed(MouseEvent e)
 791:     {
 792:       if (SwingUtilities.isLeftMouseButton(e) && comboBox.isEnabled())
 793:         {
 794:           delegateFocus(e);
 795:           togglePopup();
 796:         }
 797:     }
 798: 
 799:     /**
 800:      * This method is invoked whenever mouse event was originated in the combo
 801:      * box and released either in the combBox list of items or in the combo
 802:      * box itself.
 803:      *
 804:      * @param e MouseEvent that should be handled
 805:      */
 806:     public void mouseReleased(MouseEvent e)
 807:     {
 808:       Component component = (Component) e.getSource();
 809:       Dimension size = component.getSize();
 810:       Rectangle bounds = new Rectangle(0, 0, size.width - 1, size.height - 1);
 811:       // If mouse was released inside the bounds of combo box then do nothing,
 812:       // Otherwise if mouse was released inside the list of combo box items
 813:       // then change selection and close popup
 814:       if (! bounds.contains(e.getPoint()))
 815:         {
 816:           MouseEvent convEvent = convertMouseEvent(e);
 817:           Point point = convEvent.getPoint();
 818:           Rectangle visRect = new Rectangle();
 819:           list.computeVisibleRect(visRect);
 820:           if (visRect.contains(point))
 821:             {
 822:               updateListBoxSelectionForEvent(convEvent, false);
 823:               comboBox.setSelectedIndex(list.getSelectedIndex());
 824:             }
 825:           hide();
 826:         }
 827:       hasEntered = false;
 828:       stopAutoScrolling();
 829:     }
 830:   }
 831: 
 832:   /**
 833:    * InvocationMouseMotionListener is a mouse listener that listens to mouse
 834:    * dragging events occuring in the combo box.
 835:    */
 836:   protected class InvocationMouseMotionHandler extends MouseMotionAdapter
 837:   {
 838:     /**
 839:      * Creates a new InvocationMouseMotionHandler object.
 840:      */
 841:     protected InvocationMouseMotionHandler()
 842:     {
 843:       // Nothing to do here.
 844:     }
 845: 
 846:     /**
 847:      * This method is responsible for highlighting item in the drop down list
 848:      * over which the mouse is currently being dragged.
 849:      */
 850:     public void mouseDragged(MouseEvent e)
 851:     {
 852:       if (isVisible())
 853:         {
 854:           MouseEvent convEvent = convertMouseEvent(e);
 855:           Rectangle visRect = new Rectangle();
 856:           list.computeVisibleRect(visRect);
 857:           if (convEvent.getPoint().y >= visRect.y
 858:               && (convEvent.getPoint().y <= visRect.y + visRect.height - 1))
 859:             {
 860:               hasEntered = true;
 861:               if (isAutoScrolling)
 862:                 stopAutoScrolling();
 863:               Point point = convEvent.getPoint();
 864:               if (visRect.contains(point))
 865:                 {
 866:                   valueIsAdjusting = true;
 867:                   updateListBoxSelectionForEvent(convEvent, false);
 868:                   valueIsAdjusting = false;
 869:                 }
 870:             }
 871:           else if (hasEntered)
 872:             {
 873:               int dir = convEvent.getPoint().y < visRect.y ? SCROLL_UP
 874:                                                            : SCROLL_DOWN;
 875:               if (isAutoScrolling && scrollDirection != dir)
 876:                 {
 877:                   stopAutoScrolling();
 878:                   startAutoScrolling(dir);
 879:                 }
 880:               else if (!isAutoScrolling)
 881:                 startAutoScrolling(dir);
 882:             }
 883:           else if (e.getPoint().y < 0)
 884:             {
 885:               hasEntered = true;
 886:               startAutoScrolling(SCROLL_UP);
 887:             }
 888:         }
 889:     }
 890:   }
 891: 
 892:   /**
 893:    * ItemHandler is an item listener that listens to selection events occuring
 894:    * in the combo box. FIXME: should specify here what it does when item is
 895:    * selected or deselected in the combo box list.
 896:    */
 897:   protected class ItemHandler extends Object implements ItemListener
 898:   {
 899:     /**
 900:      * Creates a new ItemHandler object.
 901:      */
 902:     protected ItemHandler()
 903:     {
 904:       // Nothing to do here.
 905:     }
 906: 
 907:     /**
 908:      * This method responds to the selection events occuring in the combo box.
 909:      *
 910:      * @param e ItemEvent specifying the combo box's selection
 911:      */
 912:     public void itemStateChanged(ItemEvent e)
 913:     {
 914:       if (e.getStateChange() == ItemEvent.SELECTED && ! valueIsAdjusting)
 915:         {
 916:           valueIsAdjusting = true;
 917:           syncListSelection();
 918:           valueIsAdjusting = false;
 919:           list.ensureIndexIsVisible(comboBox.getSelectedIndex());
 920:         }
 921:     }
 922:   }
 923: 
 924:   /**
 925:    * ListMouseHandler is a listener that listens to mouse events occuring in
 926:    * the combo box's list of items. This class is responsible for hiding
 927:    * popup portion of the combo box if the mouse is released inside the combo
 928:    * box's list.
 929:    */
 930:   protected class ListMouseHandler extends MouseAdapter
 931:   {
 932:     protected ListMouseHandler()
 933:     {
 934:       // Nothing to do here.
 935:     }
 936: 
 937:     public void mousePressed(MouseEvent e)
 938:     {
 939:       // Nothing to do here.
 940:     }
 941: 
 942:     public void mouseReleased(MouseEvent anEvent)
 943:     {
 944:       comboBox.setSelectedIndex(list.getSelectedIndex());
 945:       hide();
 946:     }
 947:   }
 948: 
 949:   /**
 950:    * ListMouseMotionHandler listens to mouse motion events occuring in the
 951:    * combo box's list. This class is responsible for highlighting items in
 952:    * the list when mouse is moved over them
 953:    */
 954:   protected class ListMouseMotionHandler extends MouseMotionAdapter
 955:   {
 956:     protected ListMouseMotionHandler()
 957:     {
 958:       // Nothing to do here.
 959:     }
 960: 
 961:     public void mouseMoved(MouseEvent anEvent)
 962:     {
 963:       Point point = anEvent.getPoint();
 964:       Rectangle visRect = new Rectangle();
 965:       list.computeVisibleRect(visRect);
 966:       if (visRect.contains(point))
 967:         {
 968:           valueIsAdjusting = true;
 969:           updateListBoxSelectionForEvent(anEvent, false);
 970:           valueIsAdjusting = false;
 971:         }
 972:     }
 973:   }
 974: 
 975:   /**
 976:    * This class listens to changes occuring in the bound properties of the
 977:    * combo box
 978:    */
 979:   protected class PropertyChangeHandler extends Object
 980:     implements PropertyChangeListener
 981:   {
 982:     protected PropertyChangeHandler()
 983:     {
 984:       // Nothing to do here.
 985:     }
 986: 
 987:     public void propertyChange(PropertyChangeEvent e)
 988:     {
 989:       if (e.getPropertyName().equals("renderer"))
 990:         {
 991:       list.setCellRenderer(comboBox.getRenderer());
 992:       if (isVisible())
 993:         hide();
 994:         }
 995:       if (e.getPropertyName().equals("model"))
 996:         {
 997:           ComboBoxModel oldModel = (ComboBoxModel) e.getOldValue();
 998:           uninstallComboBoxModelListeners(oldModel);
 999:           ComboBoxModel newModel = (ComboBoxModel) e.getNewValue();
1000:           list.setModel(newModel);
1001:           installComboBoxModelListeners(newModel);
1002:           if (comboBox.getItemCount() > 0)
1003:             comboBox.setSelectedIndex(0);
1004:           if (isVisible())
1005:             hide();
1006:         }
1007:     }
1008:   }
1009: 
1010:   // ------ private helper methods --------------------
1011: 
1012:   /**
1013:    * This method uninstalls listeners installed by the UI
1014:    */
1015:   private void uninstallListeners()
1016:   {
1017:     uninstallComboBoxListeners();
1018:     uninstallComboBoxModelListeners(comboBox.getModel());
1019:   }
1020: 
1021:   /**
1022:    * This method uninstalls Listeners registered with combo boxes list of
1023:    * items
1024:    */
1025:   private void uninstallListListeners()
1026:   {
1027:     list.removeMouseListener(listMouseListener);
1028:     listMouseListener = null;
1029: 
1030:     list.removeMouseMotionListener(listMouseMotionListener);
1031:     listMouseMotionListener = null;
1032:   }
1033: 
1034:   /**
1035:    * This method uninstalls listeners listening to combo box  associated with
1036:    * this popup menu
1037:    */
1038:   private void uninstallComboBoxListeners()
1039:   {
1040:     comboBox.removeItemListener(itemListener);
1041:     itemListener = null;
1042: 
1043:     comboBox.removePropertyChangeListener(propertyChangeListener);
1044:     propertyChangeListener = null;
1045:   }
1046: 
1047:   void syncListSelection()
1048:   {
1049:     int index = comboBox.getSelectedIndex();
1050:     if (index == -1)
1051:       list.clearSelection();
1052:     else
1053:       list.setSelectedIndex(index);
1054:   }
1055: 
1056:   // --------------------------------------------------------------------
1057:   //  The following classes are here only for backwards API compatibility
1058:   //  They aren't used.
1059:   // --------------------------------------------------------------------
1060: 
1061:   /**
1062:    * This class is not used any more.
1063:    */
1064:   public class ListDataHandler extends Object implements ListDataListener
1065:   {
1066:     public ListDataHandler()
1067:     {
1068:       // Nothing to do here.
1069:     }
1070: 
1071:     public void contentsChanged(ListDataEvent e)
1072:     {
1073:       // Nothing to do here.
1074:     }
1075: 
1076:     public void intervalAdded(ListDataEvent e)
1077:     {
1078:       // Nothing to do here.
1079:     }
1080: 
1081:     public void intervalRemoved(ListDataEvent e)
1082:     {
1083:       // Nothing to do here.
1084:     }
1085:   }
1086: 
1087:   /**
1088:    * This class is not used anymore
1089:    */
1090:   protected class ListSelectionHandler extends Object
1091:     implements ListSelectionListener
1092:   {
1093:     protected ListSelectionHandler()
1094:     {
1095:       // Nothing to do here.
1096:     }
1097: 
1098:     public void valueChanged(ListSelectionEvent e)
1099:     {
1100:       // Nothing to do here.
1101:     }
1102:   }
1103: 
1104:   /**
1105:    * This class is not used anymore
1106:    */
1107:   public class InvocationKeyHandler extends KeyAdapter
1108:   {
1109:     public InvocationKeyHandler()
1110:     {
1111:       // Nothing to do here.
1112:     }
1113: 
1114:     public void keyReleased(KeyEvent e)
1115:     {
1116:       // Nothing to do here.
1117:     }
1118:   }
1119: }