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