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