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