001/* BasicComboPopup.java --
002   Copyright (C) 2004, 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.plaf.basic;
040
041import java.awt.Color;
042import java.awt.Component;
043import java.awt.Dimension;
044import java.awt.Insets;
045import java.awt.Point;
046import java.awt.Rectangle;
047import java.awt.event.ItemEvent;
048import java.awt.event.ItemListener;
049import java.awt.event.KeyAdapter;
050import java.awt.event.KeyEvent;
051import java.awt.event.KeyListener;
052import java.awt.event.MouseAdapter;
053import java.awt.event.MouseEvent;
054import java.awt.event.MouseListener;
055import java.awt.event.MouseMotionAdapter;
056import java.awt.event.MouseMotionListener;
057import java.beans.PropertyChangeEvent;
058import java.beans.PropertyChangeListener;
059
060import javax.swing.BorderFactory;
061import javax.swing.ComboBoxModel;
062import javax.swing.JComboBox;
063import javax.swing.JList;
064import javax.swing.JPopupMenu;
065import javax.swing.JScrollBar;
066import javax.swing.JScrollPane;
067import javax.swing.ListCellRenderer;
068import javax.swing.ListSelectionModel;
069import javax.swing.MenuSelectionManager;
070import javax.swing.SwingConstants;
071import javax.swing.SwingUtilities;
072import javax.swing.Timer;
073import javax.swing.UIManager;
074import javax.swing.event.ListDataEvent;
075import javax.swing.event.ListDataListener;
076import javax.swing.event.ListSelectionEvent;
077import javax.swing.event.ListSelectionListener;
078import javax.swing.event.PopupMenuEvent;
079import javax.swing.event.PopupMenuListener;
080
081/**
082 * UI Delegate for ComboPopup
083 *
084 * @author Olga Rodimina
085 */
086public class BasicComboPopup extends JPopupMenu implements ComboPopup
087{
088  /* Timer for autoscrolling */
089  protected Timer autoscrollTimer;
090
091  /** ComboBox associated with this popup */
092  protected JComboBox comboBox;
093
094  /** FIXME: Need to document */
095  protected boolean hasEntered;
096
097  /**
098   * Indicates whether the scroll bar located in popup menu with comboBox's
099   * 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}