001    /* JComboBox.java --
002       Copyright (C) 2002, 2004, 2005, 2006,  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;
040    
041    import gnu.java.lang.CPStringBuilder;
042    
043    import java.awt.ItemSelectable;
044    import java.awt.event.ActionEvent;
045    import java.awt.event.ActionListener;
046    import java.awt.event.ItemEvent;
047    import java.awt.event.ItemListener;
048    import java.awt.event.KeyEvent;
049    import java.beans.PropertyChangeEvent;
050    import java.beans.PropertyChangeListener;
051    import java.util.Vector;
052    
053    import javax.accessibility.Accessible;
054    import javax.accessibility.AccessibleAction;
055    import javax.accessibility.AccessibleContext;
056    import javax.accessibility.AccessibleRole;
057    import javax.accessibility.AccessibleSelection;
058    import javax.swing.event.ListDataEvent;
059    import javax.swing.event.ListDataListener;
060    import javax.swing.event.PopupMenuEvent;
061    import javax.swing.event.PopupMenuListener;
062    import javax.swing.plaf.ComboBoxUI;
063    import javax.swing.plaf.ComponentUI;
064    import javax.swing.plaf.basic.ComboPopup;
065    
066    /**
067     * A component that allows a user to select any item in its list and
068     * displays the selected item to the user. JComboBox also can show/hide a
069     * popup menu containing its list of item whenever the mouse is pressed
070     * over it.
071     *
072     * @author Andrew Selkirk
073     * @author Olga Rodimina
074     * @author Robert Schuster
075     */
076    public class JComboBox extends JComponent implements ItemSelectable,
077                                                         ListDataListener,
078                                                         ActionListener,
079                                                         Accessible
080    {
081    
082      private static final long serialVersionUID = 5654585963292734470L;
083    
084      /**
085       * Classes implementing this interface are
086       * responsible for matching key characters typed by the user with combo
087       * box's items.
088       */
089      public static interface KeySelectionManager
090      {
091        int selectionForKey(char aKey, ComboBoxModel aModel);
092      }
093    
094      /**
095       * Maximum number of rows that should be visible by default  in the
096       * JComboBox's popup
097       */
098      private static final int DEFAULT_MAXIMUM_ROW_COUNT = 8;
099    
100      /**
101       * Data model used by JComboBox to keep track of its list data and currently
102       * selected element in the list.
103       */
104      protected ComboBoxModel dataModel;
105    
106      /**
107       * Renderer renders(paints) every object in the combo box list in its
108       * associated list cell. This ListCellRenderer is used only when  this
109       * JComboBox is uneditable.
110       */
111      protected ListCellRenderer renderer;
112    
113      /**
114       * Editor that is responsible for editing an object in a combo box list.
115       */
116      protected ComboBoxEditor editor;
117    
118      /**
119       * Number of rows that will be visible in the JComboBox's popup.
120       */
121      protected int maximumRowCount;
122    
123      /**
124       * This field indicates if textfield of this JComboBox is editable or not.
125       */
126      protected boolean isEditable;
127    
128      /**
129       * This field is reference to the current selection of the combo box.
130       */
131      protected Object selectedItemReminder;
132    
133      /**
134       * keySelectionManager
135       */
136      protected KeySelectionManager keySelectionManager;
137    
138      /**
139       * This actionCommand is used in ActionEvent that is fired to JComboBox's
140       * ActionListeneres.
141       */
142      protected String actionCommand;
143    
144      /**
145       * This property indicates if heavyweight popup or lightweight popup will be
146       * used to diplay JComboBox's elements.
147       */
148      protected boolean lightWeightPopupEnabled;
149    
150      /**
151       * The action taken when new item is selected in the JComboBox
152       */
153      private Action action;
154    
155      /**
156       * since 1.4  If this field is set then comboBox's display area for the
157       * selected item  will be set by default to this value.
158       */
159      private Object prototypeDisplayValue;
160    
161      /**
162       * Constructs JComboBox object with specified data model for it.
163       * <p>Note that the JComboBox will not change the value that
164       * is preselected by your ComboBoxModel implementation.</p>
165       *
166       * @param model Data model that will be used by this JComboBox to keep track
167       *        of its list of items.
168       */
169      public JComboBox(ComboBoxModel model)
170      {
171        setEditable(false);
172        setEnabled(true);
173        setMaximumRowCount(DEFAULT_MAXIMUM_ROW_COUNT);
174        setModel(model);
175        setActionCommand("comboBoxChanged");
176    
177        lightWeightPopupEnabled = true;
178        isEditable = false;
179    
180        updateUI();
181      }
182    
183      /**
184       * Constructs JComboBox with specified list of items.
185       *
186       * @param itemArray array containing list of items for this JComboBox
187       */
188      public JComboBox(Object[] itemArray)
189      {
190        this(new DefaultComboBoxModel(itemArray));
191        
192        if (itemArray.length > 0) 
193          setSelectedIndex(0);
194      }
195    
196      /**
197       * Constructs JComboBox object with specified list of items.
198       *
199       * @param itemVector vector containing list of items for this JComboBox.
200       */
201      public JComboBox(Vector<?> itemVector)
202      {
203        this(new DefaultComboBoxModel(itemVector));
204    
205        if (itemVector.size() > 0)
206          setSelectedIndex(0);
207      }
208    
209      /**
210       * Constructor. Creates new empty JComboBox. ComboBox's data model is set to
211       * DefaultComboBoxModel.
212       */
213      public JComboBox()
214      {
215        this(new DefaultComboBoxModel());
216      }
217    
218      /**
219       * This method returns true JComboBox is editable and false otherwise
220       *
221       * @return boolean true if JComboBox is editable and false otherwise
222       */
223      public boolean isEditable()
224      {
225        return isEditable;
226      }
227    
228      /*
229       * This method adds ancestor listener to this JComboBox.
230       */
231      protected void installAncestorListener()
232      {
233        /* FIXME: Need to implement.
234         *
235         * Need to add ancestor listener to this JComboBox. This listener
236         * should close combo box's popup list of items whenever it
237         * receives an AncestorEvent.
238         */
239      }
240    
241      /**
242       * Set the "UI" property of the combo box, which is a look and feel class
243       * responsible for handling comboBox's input events and painting it.
244       *
245       * @param ui The new "UI" property
246       */
247      public void setUI(ComboBoxUI ui)
248      {
249        super.setUI(ui);
250      }
251    
252      /**
253       * This method sets this comboBox's UI to the UIManager's default for the
254       * current look and feel.
255       */
256      public void updateUI()
257      {
258        setUI((ComboBoxUI) UIManager.getUI(this));
259      }
260    
261      /**
262       * This method returns the String identifier for the UI class to the used
263       * with the JComboBox.
264       *
265       * @return The String identifier for the UI class.
266       */
267      public String getUIClassID()
268      {
269        return "ComboBoxUI";
270      }
271    
272      /**
273       * This method returns the UI used to display the JComboBox.
274       *
275       * @return The UI used to display the JComboBox.
276       */
277      public ComboBoxUI getUI()
278      {
279        return (ComboBoxUI) ui;
280      }
281    
282      /**
283       * Set the data model for this JComboBox. This un-registers all  listeners
284       * associated with the current model, and re-registers them with the new
285       * model.
286       *
287       * @param newDataModel The new data model for this JComboBox
288       */
289      public void setModel(ComboBoxModel newDataModel)
290      {
291        // dataModel is null if it this method is called from inside the constructors.
292        if (dataModel != null)
293          {
294            // Prevents unneccessary updates.
295            if (dataModel == newDataModel)
296              return;
297    
298            // Removes itself (as DataListener) from the to-be-replaced model.
299            dataModel.removeListDataListener(this);
300          }
301        
302        /* Adds itself as a DataListener to the new model.
303         * It is intentioned that this operation will fail with a NullPointerException if the
304         * caller delivered a null argument.
305         */
306        newDataModel.addListDataListener(this);
307    
308        // Stores old data model for event notification.
309        ComboBoxModel oldDataModel = dataModel;
310        dataModel = newDataModel;
311        selectedItemReminder = newDataModel.getSelectedItem();
312        
313        // Notifies the listeners of the model change.
314        firePropertyChange("model", oldDataModel, dataModel);
315      }
316    
317      /**
318       * This method returns data model for this comboBox.
319       *
320       * @return ComboBoxModel containing items for this combo box.
321       */
322      public ComboBoxModel getModel()
323      {
324        return dataModel;
325      }
326    
327      /**
328       * This method sets JComboBox's popup to be either lightweight or
329       * heavyweight. If 'enabled' is true then lightweight popup is used and
330       * heavyweight otherwise. By default lightweight popup is used to display
331       * this JComboBox's elements.
332       *
333       * @param enabled indicates if lightweight popup or heavyweight popup should
334       *        be used to display JComboBox's elements.
335       */
336      public void setLightWeightPopupEnabled(boolean enabled)
337      {
338        lightWeightPopupEnabled = enabled;
339      }
340    
341      /**
342       * This method returns whether popup menu that is used to display list of
343       * combo box's item is lightWeight or not.
344       *
345       * @return boolean true if popup menu is lightweight and false otherwise.
346       */
347      public boolean isLightWeightPopupEnabled()
348      {
349        return lightWeightPopupEnabled;
350      }
351    
352      /**
353       * This method sets editability of the combo box. If combo box  is editable
354       * the user can choose component from the combo box list by typing
355       * component's name in the editor(JTextfield by default).  Otherwise if not
356       * editable, the user should use the list to choose   the component. This
357       * method fires PropertyChangeEvents to JComboBox's registered
358       * PropertyChangeListeners to indicate that 'editable' property of the
359       * JComboBox has changed.
360       *
361       * @param editable indicates if the JComboBox's textfield should be editable
362       *        or not.
363       */
364      public void setEditable(boolean editable)
365      {
366        if (isEditable != editable)
367          {
368            isEditable = editable;
369            firePropertyChange("editable", !isEditable, isEditable);
370          }
371      }
372    
373      /**
374       * Sets number of rows that should be visible in this JComboBox's popup. If
375       * this JComboBox's popup has more elements that maximum number or rows
376       * then popup will have a scroll pane to allow users to view other
377       * elements.
378       *
379       * @param rowCount number of rows that will be visible in JComboBox's popup.
380       */
381      public void setMaximumRowCount(int rowCount)
382      {
383        if (maximumRowCount != rowCount)
384          {
385            int oldMaximumRowCount = maximumRowCount;
386            maximumRowCount = rowCount;
387            firePropertyChange("maximumRowCount", oldMaximumRowCount,
388                               maximumRowCount);
389          }
390      }
391    
392      /**
393       * This method returns number of rows visible in the JComboBox's list of
394       * items.
395       *
396       * @return int maximun number of visible rows in the JComboBox's list.
397       */
398      public int getMaximumRowCount()
399      {
400        return maximumRowCount;
401      }
402    
403      /**
404       * This method sets cell renderer for this JComboBox that will be used to
405       * paint combo box's items. The Renderer should only be used only when
406       * JComboBox is not editable.  In the case when JComboBox is editable  the
407       * editor must be used.  This method also fires PropertyChangeEvent when
408       * cellRendered for this JComboBox has changed.
409       *
410       * @param aRenderer cell renderer that will be used by this JComboBox to
411       *        paint its elements.
412       */
413      public void setRenderer(ListCellRenderer aRenderer)
414      {
415        if (renderer != aRenderer)
416          {
417            ListCellRenderer oldRenderer = renderer;
418            renderer = aRenderer;
419            firePropertyChange("renderer", oldRenderer, renderer);
420          }
421      }
422    
423      /**
424       * This method returns renderer responsible for rendering selected item in
425       * the combo box
426       *
427       * @return ListCellRenderer
428       */
429      public ListCellRenderer getRenderer()
430      {
431        return renderer;
432      }
433    
434      /**
435       * Sets editor for this JComboBox
436       *
437       * @param newEditor ComboBoxEditor for this JComboBox. This method fires
438       *        PropertyChangeEvent when 'editor' property is changed.
439       */
440      public void setEditor(ComboBoxEditor newEditor)
441      {
442        if (editor == newEditor)
443          return;
444    
445        if (editor != null)
446          editor.removeActionListener(this);
447    
448        ComboBoxEditor oldEditor = editor;
449        editor = newEditor;
450    
451        if (editor != null)
452          editor.addActionListener(this);
453    
454        firePropertyChange("editor", oldEditor, editor);
455      }
456    
457      /**
458       * Returns editor component that is responsible for displaying/editing
459       * selected item in the combo box.
460       *
461       * @return ComboBoxEditor
462       */
463      public ComboBoxEditor getEditor()
464      {
465        return editor;
466      }
467    
468      /**
469       * Forces combo box to select given item
470       *
471       * @param item element in the combo box to select.
472       */
473      public void setSelectedItem(Object item)
474      {
475        dataModel.setSelectedItem(item);
476        fireActionEvent();
477      }
478    
479      /**
480       * Returns currently selected item in the combo box.
481       * The result may be <code>null</code> to indicate that nothing is
482       * currently selected.
483       *
484       * @return element that is currently selected in this combo box.
485       */
486      public Object getSelectedItem()
487      {
488        return dataModel.getSelectedItem();
489      }
490    
491      /**
492       * Forces JComboBox to select component located in the given index in the
493       * combo box.
494       * <p>If the index is below -1 or exceeds the upper bound an
495       * <code>IllegalArgumentException</code> is thrown.<p/>
496       * <p>If the index is -1 then no item gets selected.</p>
497       *
498       * @param index index specifying location of the component that  should be
499       *        selected.
500       */
501      public void setSelectedIndex(int index)
502      {
503            if (index < -1 || index >= dataModel.getSize())
504          // Fails because index is out of bounds.
505          throw new IllegalArgumentException("illegal index: " + index);
506        else
507           // Selects the item at the given index or clears the selection if the
508           // index value is -1.
509          setSelectedItem((index == -1) ? null : dataModel.getElementAt(index));
510      }
511    
512      /**
513       * Returns index of the item that is currently selected in the combo box. If
514       * no item is currently selected, then -1 is returned.
515       * <p>
516       * Note: For performance reasons you should minimize invocation of this
517       * method. If the data model is not an instance of
518       * <code>DefaultComboBoxModel</code> the complexity is O(n) where n is the
519       * number of elements in the combo box.
520       * </p>
521       * 
522       * @return int Index specifying location of the currently selected item in the
523       *         combo box or -1 if nothing is selected in the combo box.
524       */
525      public int getSelectedIndex()
526      {
527        Object selectedItem = getSelectedItem();
528    
529        if (selectedItem != null)
530          {
531            if (dataModel instanceof DefaultComboBoxModel)
532              // Uses special method of DefaultComboBoxModel to retrieve the index.
533              return ((DefaultComboBoxModel) dataModel).getIndexOf(selectedItem);
534            else
535              {
536                // Iterates over all items to retrieve the index.
537                int size = dataModel.getSize();
538    
539                for (int i = 0; i < size; i++)
540                  {
541                    Object o = dataModel.getElementAt(i);
542    
543                    // XXX: Is special handling of ComparableS neccessary?
544                    if ((selectedItem != null) ? selectedItem.equals(o) : o == null)
545                      return i;
546                  }
547              }
548          }
549    
550        // returns that no item is currently selected
551        return -1;
552      }
553    
554      /**
555       * Returns an object that is used as the display value when calculating the 
556       * preferred size for the combo box.  This value is, of course, never 
557       * displayed anywhere.
558       * 
559       * @return The prototype display value (possibly <code>null</code>).
560       * 
561       * @since 1.4
562       * @see #setPrototypeDisplayValue(Object)
563       */
564      public Object getPrototypeDisplayValue()
565      {
566        return prototypeDisplayValue;
567      }
568    
569      /**
570       * Sets the object that is assumed to be the displayed item when calculating
571       * the preferred size for the combo box.  A {@link PropertyChangeEvent} (with
572       * the name <code>prototypeDisplayValue</code>) is sent to all registered 
573       * listeners. 
574       * 
575       * @param value  the new value (<code>null</code> permitted).
576       * 
577       * @since 1.4
578       * @see #getPrototypeDisplayValue()
579       */
580      public void setPrototypeDisplayValue(Object value)
581      {
582        Object oldValue = prototypeDisplayValue;
583        prototypeDisplayValue = value;
584        firePropertyChange("prototypeDisplayValue", oldValue, value);
585      }
586    
587      /**
588       * This method adds given element to this JComboBox.
589       * <p>A <code>RuntimeException</code> is thrown if the data model is not
590       * an instance of {@link MutableComboBoxModel}.</p>
591       *
592       * @param element element to add
593       */
594      public void addItem(Object element)
595      {
596            if (dataModel instanceof MutableComboBoxModel)
597          ((MutableComboBoxModel) dataModel).addElement(element);
598        else
599          throw new RuntimeException("Unable to add the item because the data "
600                                     + "model it is not an instance of "
601                                     + "MutableComboBoxModel.");
602      }
603    
604      /**
605       * Inserts given element at the specified index to this JComboBox.
606       * <p>A <code>RuntimeException</code> is thrown if the data model is not
607       * an instance of {@link MutableComboBoxModel}.</p>
608       *
609       * @param element element to insert
610       * @param index position where to insert the element
611       */
612      public void insertItemAt(Object element, int index)
613      {
614            if (dataModel instanceof MutableComboBoxModel)
615          ((MutableComboBoxModel) dataModel).insertElementAt(element, index);
616        else
617          throw new RuntimeException("Unable to insert the item because the data "
618                                     + "model it is not an instance of "
619                                     + "MutableComboBoxModel.");
620      }
621    
622      /**
623       * This method removes given element from this JComboBox.
624       * <p>A <code>RuntimeException</code> is thrown if the data model is not
625       * an instance of {@link MutableComboBoxModel}.</p>
626       *
627       * @param element element to remove
628       */
629      public void removeItem(Object element)
630      {
631            if (dataModel instanceof MutableComboBoxModel)
632          ((MutableComboBoxModel) dataModel).removeElement(element);
633        else
634          throw new RuntimeException("Unable to remove the item because the data "
635                                     + "model it is not an instance of "
636                                     + "MutableComboBoxModel.");
637      }
638    
639      /**
640       * This method remove element location in the specified index in the
641       * JComboBox.
642       * <p>A <code>RuntimeException</code> is thrown if the data model is not
643       * an instance of {@link MutableComboBoxModel}.</p>
644       *
645       * @param index index specifying position of the element to remove
646       */
647      public void removeItemAt(int index)
648      {
649        if (dataModel instanceof MutableComboBoxModel)
650          ((MutableComboBoxModel) dataModel).removeElementAt(index);
651        else
652          throw new RuntimeException("Unable to remove the item because the data "
653                                     + "model it is not an instance of "
654                                     + "MutableComboBoxModel.");
655      }
656    
657      /**
658       * This method removes all elements from this JComboBox.
659       * <p>
660       * A <code>RuntimeException</code> is thrown if the data model is not an
661       * instance of {@link MutableComboBoxModel}.
662       * </p>
663       */
664      public void removeAllItems()
665      {
666        if (dataModel instanceof DefaultComboBoxModel)
667          // Uses special method if we have a DefaultComboBoxModel.
668          ((DefaultComboBoxModel) dataModel).removeAllElements();
669        else if (dataModel instanceof MutableComboBoxModel)
670          {
671            // Iterates over all items and removes each.
672            MutableComboBoxModel mcbm = (MutableComboBoxModel) dataModel;
673    
674             // We intentionally remove the items backwards to support models which
675             // shift their content to the beginning (e.g. linked lists)
676            for (int i = mcbm.getSize() - 1; i >= 0; i--)
677              mcbm.removeElementAt(i);
678          }
679        else
680          throw new RuntimeException("Unable to remove the items because the data "
681                                     + "model it is not an instance of "
682                                     + "MutableComboBoxModel.");
683      }
684    
685      /**
686       * This method displays popup with list of combo box's items on the screen
687       */
688      public void showPopup()
689      {
690        setPopupVisible(true);
691      }
692    
693      /**
694       * This method hides popup containing list of combo box's items
695       */
696      public void hidePopup()
697      {
698        setPopupVisible(false);
699      }
700    
701      /**
702       * This method either displayes or hides the popup containing  list of combo
703       * box's items.
704       *
705       * @param visible show popup if 'visible' is true and hide it otherwise
706       */
707      public void setPopupVisible(boolean visible)
708      {
709        getUI().setPopupVisible(this, visible);
710      }
711    
712      /**
713       * Checks if popup is currently visible on the screen.
714       *
715       * @return boolean true if popup is visible and false otherwise
716       */
717      public boolean isPopupVisible()
718      {
719        return getUI().isPopupVisible(this);
720      }
721    
722      /**
723       * This method sets actionCommand to the specified string. ActionEvent fired
724       * to this JComboBox  registered ActionListeners will contain this
725       * actionCommand.
726       *
727       * @param aCommand new action command for the JComboBox's ActionEvent
728       */
729      public void setActionCommand(String aCommand)
730      {
731        actionCommand = aCommand;
732      }
733    
734      /**
735       * Returns actionCommand associated with the ActionEvent fired by the
736       * JComboBox to its registered ActionListeners.
737       *
738       * @return String actionCommand for the ActionEvent
739       */
740      public String getActionCommand()
741      {
742        return actionCommand;
743      }
744    
745      /**
746       * setAction
747       *
748       * @param a action to set
749       */
750      public void setAction(Action a)
751      {
752        Action old = action;
753        action = a;
754        configurePropertiesFromAction(action);
755        if (action != null)
756          // FIXME: remove from old action and add to new action 
757          // PropertyChangeListener to listen to changes in the action
758          addActionListener(action);
759      }
760    
761      /**
762       * This method returns Action that is invoked when selected item is changed
763       * in the JComboBox.
764       *
765       * @return Action
766       */
767      public Action getAction()
768      {
769        return action;
770      }
771    
772      /**
773       * Configure properties of the JComboBox by reading properties of specified
774       * action. This method always sets the comboBox's "enabled" property to the
775       * value of the Action's "enabled" property.
776       *
777       * @param a An Action to configure the combo box from
778       */
779      protected void configurePropertiesFromAction(Action a)
780      {
781        if (a == null)
782          {
783            setEnabled(true);
784            setToolTipText(null);
785          }
786        else
787          {
788            setEnabled(a.isEnabled());
789            setToolTipText((String) (a.getValue(Action.SHORT_DESCRIPTION)));
790          }
791      }
792    
793      /**
794       * Creates PropertyChangeListener to listen for the changes in comboBox's
795       * action properties.
796       *
797       * @param action action to listen to for property changes
798       *
799       * @return a PropertyChangeListener that listens to changes in
800       *         action properties.
801       */
802      protected PropertyChangeListener createActionPropertyChangeListener(Action action)
803      {
804        return new PropertyChangeListener()
805          {
806            public void propertyChange(PropertyChangeEvent e)
807            {
808              Action act = (Action) (e.getSource());
809              configurePropertiesFromAction(act);
810            }
811          };
812      }
813    
814      /**
815       * This method fires ItemEvent to this JComboBox's registered ItemListeners.
816       * This method is invoked when currently selected item in this combo box
817       * has changed.
818       *
819       * @param e the ItemEvent describing the change in the combo box's
820       *        selection.
821       */
822      protected void fireItemStateChanged(ItemEvent e)
823      {
824        ItemListener[] ll = getItemListeners();
825    
826        for (int i = 0; i < ll.length; i++)
827          ll[i].itemStateChanged(e);
828      }
829    
830      /**
831       * This method fires ActionEvent to this JComboBox's registered
832       * ActionListeners. This method is invoked when user explicitly changes
833       * currently selected item.
834       */
835      protected void fireActionEvent()
836      {
837        ActionListener[] ll = getActionListeners();
838    
839        for (int i = 0; i < ll.length; i++)
840          ll[i].actionPerformed(new ActionEvent(this,
841                                                ActionEvent.ACTION_PERFORMED,
842                                                actionCommand));
843      }
844    
845      /**
846       * Fires a popupMenuCanceled() event to all <code>PopupMenuListeners</code>.
847       *
848       * Note: This method is intended for use by plaf classes only.
849       */
850      public void firePopupMenuCanceled()
851      {
852        PopupMenuListener[] listeners = getPopupMenuListeners();
853        PopupMenuEvent e = new PopupMenuEvent(this);
854        for (int i = 0; i < listeners.length; i++)
855          listeners[i].popupMenuCanceled(e);
856      }
857    
858      /**
859       * Fires a popupMenuWillBecomeInvisible() event to all 
860       * <code>PopupMenuListeners</code>.
861       *
862       * Note: This method is intended for use by plaf classes only.
863       */
864      public void firePopupMenuWillBecomeInvisible()
865      {
866        PopupMenuListener[] listeners = getPopupMenuListeners();
867        PopupMenuEvent e = new PopupMenuEvent(this);
868        for (int i = 0; i < listeners.length; i++)
869          listeners[i].popupMenuWillBecomeInvisible(e);
870      }
871    
872      /**
873       * Fires a popupMenuWillBecomeVisible() event to all 
874       * <code>PopupMenuListeners</code>.
875       *
876       * Note: This method is intended for use by plaf classes only.
877       */
878      public void firePopupMenuWillBecomeVisible()
879      {
880        PopupMenuListener[] listeners = getPopupMenuListeners();
881        PopupMenuEvent e = new PopupMenuEvent(this);
882        for (int i = 0; i < listeners.length; i++)
883          listeners[i].popupMenuWillBecomeVisible(e);
884      }
885    
886      /**
887       * This method is invoked whenever selected item changes in the combo box's
888       * data model. It fires ItemEvent and ActionEvent to all registered
889       * ComboBox's ItemListeners and ActionListeners respectively, indicating
890       * the change.
891       */
892      protected void selectedItemChanged()
893      {
894        // Fire ItemEvent to indicated that previously selected item is now
895        // deselected        
896        if (selectedItemReminder != null)
897          fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED,
898                                             selectedItemReminder,
899                                             ItemEvent.DESELECTED));
900    
901        // Fire ItemEvent to indicate that new item is selected    
902        Object newSelection = getSelectedItem();
903        if (newSelection != null)
904          fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED,
905                                             newSelection, ItemEvent.SELECTED));
906    
907        // Fire Action Event to JComboBox's registered listeners                                                                     
908        fireActionEvent();
909    
910        selectedItemReminder = newSelection;
911      }
912    
913      /**
914       * Returns Object array of size 1 containing currently selected element in
915       * the JComboBox.
916       *
917       * @return Object[] Object array of size 1 containing currently selected
918       *         element in the JComboBox.
919       */
920      public Object[] getSelectedObjects()
921      {
922        return new Object[] { getSelectedItem() };
923      }
924    
925      /**
926       * This method handles actionEvents fired by the ComboBoxEditor. It changes
927       * this JComboBox's selection to the new value currently in the editor and
928       * hides list of combo box items.
929       *
930       * @param e the ActionEvent
931       */
932      public void actionPerformed(ActionEvent e)
933      {
934        setSelectedItem(getEditor().getItem());
935        setPopupVisible(false);
936      }
937    
938      /**
939       * This method selects item in this combo box that matches specified
940       * specified keyChar and returns true if such item is found. Otherwise
941       * false is returned.
942       *
943       * @param keyChar character indicating which item in the combo box should be
944       *        selected.
945       *
946       * @return boolean true if item corresponding to the specified keyChar
947       *         exists in the combo box. Otherwise false is returned.
948       */
949      public boolean selectWithKeyChar(char keyChar)
950      {
951        if (keySelectionManager == null)
952          {
953            keySelectionManager = createDefaultKeySelectionManager();
954          }
955    
956        int index = keySelectionManager.selectionForKey(keyChar, getModel());
957        boolean retVal = false;
958        if (index >= 0)
959          {
960            setSelectedIndex(index);
961            retVal = true;
962          }
963        return retVal;
964      }
965    
966      /**
967       * The part of implementation of ListDataListener interface. This method is
968       * invoked when some items where added to the JComboBox's data model.
969       *
970       * @param event ListDataEvent describing the change
971       */
972      public void intervalAdded(ListDataEvent event)
973      {
974        // FIXME: Need to implement
975        repaint();
976      }
977    
978      /**
979       * The part of implementation of ListDataListener interface. This method is
980       * invoked when some items where removed from the JComboBox's data model.
981       *
982       * @param event ListDataEvent describing the change.
983       */
984      public void intervalRemoved(ListDataEvent event)
985      {
986        // FIXME: Need to implement
987        repaint();
988      }
989    
990      /**
991       * The part of implementation of ListDataListener interface. This method is
992       * invoked when contents of the JComboBox's  data model changed.
993       *
994       * @param event ListDataEvent describing the change
995       */
996      public void contentsChanged(ListDataEvent event)
997      {
998        // if first and last index of the given ListDataEvent are both -1,
999        // then it indicates that selected item in the combo box data model
1000        // have changed. 
1001        if (event.getIndex0() == -1 && event.getIndex1() == -1)
1002          selectedItemChanged();
1003      }
1004    
1005      /**
1006       * This method disables or enables JComboBox. If the JComboBox is enabled,
1007       * then user is able to make item choice, otherwise if JComboBox is
1008       * disabled then user is not able to make a selection.
1009       *
1010       * @param enabled if 'enabled' is true then enable JComboBox and disable it
1011       */
1012      public void setEnabled(boolean enabled)
1013      {
1014        boolean oldEnabled = super.isEnabled();
1015        if (enabled != oldEnabled)
1016          {
1017            super.setEnabled(enabled);
1018            firePropertyChange("enabled", oldEnabled, enabled);
1019          }
1020      }
1021    
1022      /**
1023       * This method initializes specified ComboBoxEditor to display given item.
1024       *
1025       * @param anEditor ComboBoxEditor to initialize
1026       * @param anItem Item that should displayed in the specified editor
1027       */
1028      public void configureEditor(ComboBoxEditor anEditor, Object anItem)
1029      {
1030        anEditor.setItem(anItem);
1031      }
1032    
1033      /**
1034       * This method is fired whenever a key is pressed with the combo box
1035       * in focus
1036       *
1037       * @param e The KeyEvent indicating which key was pressed.
1038       */
1039      public void processKeyEvent(KeyEvent e)
1040      {
1041        if (e.getKeyCode() == KeyEvent.VK_TAB)
1042          setPopupVisible(false);
1043        else
1044          super.processKeyEvent(e);
1045      }
1046    
1047      /**
1048       * setKeySelectionManager
1049       *
1050       * @param aManager
1051       */
1052      public void setKeySelectionManager(KeySelectionManager aManager)
1053      {
1054        keySelectionManager = aManager;
1055      }
1056    
1057      /**
1058       * getKeySelectionManager
1059       *
1060       * @return JComboBox.KeySelectionManager
1061       */
1062      public KeySelectionManager getKeySelectionManager()
1063      {
1064        return keySelectionManager;
1065      }
1066    
1067      /**
1068       * This method returns number of elements in this JComboBox
1069       *
1070       * @return int number of elements in this JComboBox
1071       */
1072      public int getItemCount()
1073      {
1074        return dataModel.getSize();
1075      }
1076    
1077      /**
1078       * Returns elements located in the combo box at the given index.
1079       *
1080       * @param index index specifying location of the component to  return.
1081       *
1082       * @return component in the combo box that is located in  the given index.
1083       */
1084      public Object getItemAt(int index)
1085      {
1086        return dataModel.getElementAt(index);
1087      }
1088    
1089      /**
1090       * createDefaultKeySelectionManager
1091       *
1092       * @return KeySelectionManager
1093       */
1094      protected KeySelectionManager createDefaultKeySelectionManager()
1095      {
1096        return new DefaultKeySelectionManager();
1097      }
1098    
1099      /**
1100       * Returns an implementation-dependent string describing the attributes of
1101       * this <code>JComboBox</code>.
1102       *
1103       * @return A string describing the attributes of this <code>JComboBox</code>
1104       *         (never <code>null</code>).
1105       */
1106      protected String paramString()
1107      {
1108        String superParamStr = super.paramString();
1109        CPStringBuilder sb = new CPStringBuilder();
1110        sb.append(",isEditable=").append(isEditable());
1111        sb.append(",lightWeightPopupEnabled=").append(isLightWeightPopupEnabled());
1112        sb.append(",maximumRowCount=").append(getMaximumRowCount());
1113        
1114        sb.append(",selectedItemReminder=");
1115        if (selectedItemReminder != null)
1116          sb.append(selectedItemReminder);
1117        return superParamStr + sb.toString();
1118      }
1119    
1120      /**
1121       * Returns the object that provides accessibility features for this
1122       * <code>JComboBox</code> component.
1123       *
1124       * @return The accessible context (an instance of 
1125       *         {@link AccessibleJComboBox}).
1126       */
1127      public AccessibleContext getAccessibleContext()
1128      {
1129        if (accessibleContext == null)
1130          accessibleContext = new AccessibleJComboBox();
1131    
1132        return accessibleContext;
1133      }
1134    
1135      /**
1136       * This methods adds specified ActionListener to this JComboBox.
1137       *
1138       * @param listener to add
1139       */
1140      public void addActionListener(ActionListener listener)
1141      {
1142        listenerList.add(ActionListener.class, listener);
1143      }
1144    
1145      /**
1146       * This method removes specified ActionListener from this JComboBox.
1147       *
1148       * @param listener ActionListener
1149       */
1150      public void removeActionListener(ActionListener listener)
1151      {
1152        listenerList.remove(ActionListener.class, listener);
1153      }
1154    
1155      /**
1156       * This method returns array of ActionListeners that are registered with
1157       * this JComboBox.
1158       *
1159       * @since 1.4
1160       */
1161      public ActionListener[] getActionListeners()
1162      {
1163        return (ActionListener[]) getListeners(ActionListener.class);
1164      }
1165    
1166      /**
1167       * This method registers given ItemListener with this JComboBox
1168       *
1169       * @param listener to remove
1170       */
1171      public void addItemListener(ItemListener listener)
1172      {
1173        listenerList.add(ItemListener.class, listener);
1174      }
1175    
1176      /**
1177       * This method unregisters given ItemListener from this JComboBox
1178       *
1179       * @param listener to remove
1180       */
1181      public void removeItemListener(ItemListener listener)
1182      {
1183        listenerList.remove(ItemListener.class, listener);
1184      }
1185    
1186      /**
1187       * This method returns array of ItemListeners that are registered with this
1188       * JComboBox.
1189       *
1190       * @since 1.4
1191       */
1192      public ItemListener[] getItemListeners()
1193      {
1194        return (ItemListener[]) getListeners(ItemListener.class);
1195      }
1196    
1197      /**
1198       * Adds PopupMenuListener to combo box to listen to the events fired by the
1199       * combo box's popup menu containing its list of items
1200       *
1201       * @param listener to add
1202       */
1203      public void addPopupMenuListener(PopupMenuListener listener)
1204      {
1205        listenerList.add(PopupMenuListener.class, listener);
1206      }
1207    
1208      /**
1209       * Removes PopupMenuListener to combo box to listen to the events fired by
1210       * the combo box's popup menu containing its list of items
1211       *
1212       * @param listener to add
1213       */
1214      public void removePopupMenuListener(PopupMenuListener listener)
1215      {
1216        listenerList.remove(PopupMenuListener.class, listener);
1217      }
1218    
1219      /**
1220       * Returns array of PopupMenuListeners that are registered with  combo box.
1221       */
1222      public PopupMenuListener[] getPopupMenuListeners()
1223      {
1224        return (PopupMenuListener[]) getListeners(PopupMenuListener.class);
1225      }
1226    
1227      /**
1228       * Accessibility support for <code>JComboBox</code>.
1229       */
1230      protected class AccessibleJComboBox extends AccessibleJComponent
1231        implements AccessibleAction, AccessibleSelection
1232      {
1233        private static final long serialVersionUID = 8217828307256675666L;
1234    
1235        /**
1236         * @specnote This constructor was protected in 1.4, but made public
1237         * in 1.5.
1238         */
1239        public AccessibleJComboBox()
1240        {
1241          // Nothing to do here.
1242        }
1243    
1244        /**
1245         * Returns the number of accessible children of this object. The
1246         * implementation of AccessibleJComboBox delegates this call to the UI
1247         * of the associated JComboBox.
1248         *
1249         * @return the number of accessible children of this object
1250         *
1251         * @see ComponentUI#getAccessibleChildrenCount(JComponent)
1252         */
1253        public int getAccessibleChildrenCount()
1254        {
1255          ComponentUI ui = getUI();
1256          int count;
1257          if (ui != null)
1258            count = ui.getAccessibleChildrenCount(JComboBox.this);
1259          else
1260            count = super.getAccessibleChildrenCount();
1261          return count;
1262        }
1263    
1264        /**
1265         * Returns the number of accessible children of this object. The
1266         * implementation of AccessibleJComboBox delegates this call to the UI
1267         * of the associated JComboBox.
1268         *
1269         * @param index the index of the accessible child to fetch
1270         *
1271         * @return the number of accessible children of this object
1272         *
1273         * @see ComponentUI#getAccessibleChild(JComponent, int)
1274         */
1275        public Accessible getAccessibleChild(int index)
1276        {
1277          ComponentUI ui = getUI();
1278          Accessible child = null;
1279          if (ui != null)
1280            child = ui.getAccessibleChild(JComboBox.this, index);
1281          else
1282            child = super.getAccessibleChild(index);
1283          return child;
1284        }
1285    
1286        /**
1287         * Returns the AccessibleSelection object associated with this object.
1288         * AccessibleJComboBoxes handle their selection themselves, so this
1289         * always returns <code>this</code>.
1290         *
1291         * @return the AccessibleSelection object associated with this object
1292         */
1293        public AccessibleSelection getAccessibleSelection()
1294        {
1295          return this;
1296        }
1297    
1298        /**
1299         * Returns the accessible selection from this AccssibleJComboBox.
1300         *
1301         * @param index the index of the selected child to fetch
1302         *
1303         * @return the accessible selection from this AccssibleJComboBox
1304         */
1305        public Accessible getAccessibleSelection(int index)
1306        {
1307          // Get hold of the actual popup.
1308          Accessible popup = getUI().getAccessibleChild(JComboBox.this, 0);
1309          Accessible selected = null;
1310          if (popup != null && popup instanceof ComboPopup)
1311            {
1312              ComboPopup cPopup = (ComboPopup) popup;
1313              // Query the list for the currently selected child.
1314              JList l = cPopup.getList();
1315              AccessibleContext listCtx = l.getAccessibleContext();
1316              if (listCtx != null)
1317                {
1318                  AccessibleSelection s = listCtx.getAccessibleSelection();
1319                  if (s != null)
1320                    {
1321                      selected = s.getAccessibleSelection(index);
1322                    }
1323                }
1324            }
1325          return selected;
1326        }
1327    
1328        /**
1329         * Returns <code>true</code> if the accessible child with the specified
1330         * <code>index</code> is selected, <code>false</code> otherwise.
1331         *
1332         * @param index the index of the accessible child
1333         *
1334         * @return <code>true</code> if the accessible child with the specified
1335         *         <code>index</code> is selected, <code>false</code> otherwise
1336         */
1337        public boolean isAccessibleChildSelected(int index)
1338        {
1339          return getSelectedIndex() == index;
1340        }
1341    
1342        /**
1343         * Returns the accessible role for the <code>JComboBox</code> component.
1344         *
1345         * @return {@link AccessibleRole#COMBO_BOX}.
1346         */
1347        public AccessibleRole getAccessibleRole()
1348        {
1349          return AccessibleRole.COMBO_BOX;
1350        }
1351    
1352        /**
1353         * Returns the accessible action associated to this accessible object.
1354         * AccessibleJComboBox implements its own AccessibleAction, so this
1355         * method returns <code>this</code>.
1356         *
1357         * @return the accessible action associated to this accessible object
1358         */
1359        public AccessibleAction getAccessibleAction()
1360        {
1361          return this;
1362        }
1363    
1364        /**
1365         * Returns the description of the specified action. AccessibleJComboBox
1366         * implements 1 action (toggle the popup menu) and thus returns
1367         * <code>UIManager.getString("ComboBox.togglePopupText")</code>
1368         *
1369         * @param actionIndex the index of the action for which to return the
1370         *        description
1371         *
1372         * @return the description of the specified action
1373         */
1374        public String getAccessibleActionDescription(int actionIndex)
1375        {
1376          return UIManager.getString("ComboBox.togglePopupText");
1377        }
1378    
1379        /**
1380         * Returns the number of accessible actions that can be performed by
1381         * this object. AccessibleJComboBox implement s one accessible action
1382         * (toggle the popup menu), so this method always returns <code>1</code>.
1383         *
1384         * @return the number of accessible actions that can be performed by
1385         *         this object
1386         */
1387        public int getAccessibleActionCount()
1388        {
1389          return 1;
1390        }
1391    
1392        /**
1393         * Performs the accessible action with the specified index.
1394         * AccessibleJComboBox has 1 accessible action
1395         * (<code>actionIndex == 0</code>), which is to toggle the
1396         * popup menu. All other action indices have no effect and return
1397         * <code<>false</code>.
1398         *
1399         * @param actionIndex the index of the action to perform
1400         *
1401         * @return <code>true</code> if the action has been performed,
1402         *         <code>false</code> otherwise
1403         */
1404        public boolean doAccessibleAction(int actionIndex)
1405        {
1406          boolean actionPerformed = false;
1407          if (actionIndex == 0)
1408            {
1409              setPopupVisible(! isPopupVisible());
1410              actionPerformed = true;
1411            }
1412          return actionPerformed;
1413        }
1414    
1415        /**
1416         * Returns the number of selected accessible children of this object. This
1417         * returns <code>1</code> if the combobox has a selected entry,
1418         * <code>0</code> otherwise.
1419         *
1420         * @return the number of selected accessible children of this object
1421         */
1422        public int getAccessibleSelectionCount()
1423        {
1424          Object sel = getSelectedItem();
1425          int count = 0;
1426          if (sel != null)
1427            count = 1;
1428          return count;
1429        }
1430    
1431        /**
1432         * Sets the current selection to the specified <code>index</code>.
1433         *
1434         * @param index the index to set as selection
1435         */
1436        public void addAccessibleSelection(int index)
1437        {
1438          setSelectedIndex(index);
1439        }
1440    
1441        /**
1442         * Removes the specified index from the current selection.
1443         *
1444         * @param index the index to remove from the selection
1445         */
1446        public void removeAccessibleSelection(int index)
1447        {
1448          if (getSelectedIndex() == index)
1449            clearAccessibleSelection();
1450        }
1451    
1452        /**
1453         * Clears the current selection.
1454         */
1455        public void clearAccessibleSelection()
1456        {
1457          setSelectedIndex(-1);
1458        }
1459    
1460        /**
1461         * Multiple selection is not supported by AccessibleJComboBox, so this
1462         * does nothing.
1463         */
1464        public void selectAllAccessibleSelection()
1465        {
1466          // Nothing to do here.
1467        }
1468      }
1469      
1470      private class DefaultKeySelectionManager
1471          implements KeySelectionManager
1472      {
1473    
1474        public int selectionForKey(char aKey, ComboBoxModel aModel)
1475        {
1476          int selectedIndex = getSelectedIndex();
1477    
1478          // Start at currently selected item and iterate to end of list
1479          for (int i = selectedIndex + 1; i < aModel.getSize(); i++)
1480            {
1481              String nextItem = aModel.getElementAt(i).toString();
1482    
1483              if (nextItem.charAt(0) == aKey)
1484                return i;
1485            }
1486    
1487          // Wrap to start of list if no match yet
1488          for (int i = 0; i <= selectedIndex; i++)
1489            {
1490              String nextItem = aModel.getElementAt(i).toString();
1491    
1492              if (nextItem.charAt(0) == aKey)
1493                return i;
1494            }
1495    
1496          return - 1;
1497        }
1498      }
1499    }