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