OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / JComboBox.java
1 /* JComboBox.java --
2    Copyright (C) 2002, 2004, 2005, 2006,  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.swing;
40
41 import java.awt.ItemSelectable;
42 import java.awt.event.ActionEvent;
43 import java.awt.event.ActionListener;
44 import java.awt.event.ItemEvent;
45 import java.awt.event.ItemListener;
46 import java.awt.event.KeyEvent;
47 import java.beans.PropertyChangeEvent;
48 import java.beans.PropertyChangeListener;
49 import java.util.Vector;
50
51 import javax.accessibility.Accessible;
52 import javax.accessibility.AccessibleAction;
53 import javax.accessibility.AccessibleContext;
54 import javax.accessibility.AccessibleRole;
55 import javax.accessibility.AccessibleSelection;
56 import javax.swing.event.ListDataEvent;
57 import javax.swing.event.ListDataListener;
58 import javax.swing.event.PopupMenuEvent;
59 import javax.swing.event.PopupMenuListener;
60 import javax.swing.plaf.ComboBoxUI;
61 import javax.swing.plaf.ComponentUI;
62 import javax.swing.plaf.basic.ComboPopup;
63
64 /**
65  * A component that allows a user to select any item in its list and
66  * displays the selected item to the user. JComboBox also can show/hide a
67  * popup menu containing its list of item whenever the mouse is pressed
68  * over it.
69  *
70  * @author Andrew Selkirk
71  * @author Olga Rodimina
72  * @author Robert Schuster
73  */
74 public class JComboBox extends JComponent implements ItemSelectable,
75                                                      ListDataListener,
76                                                      ActionListener,
77                                                      Accessible
78 {
79
80   private static final long serialVersionUID = 5654585963292734470L;
81
82   /**
83    * Classes implementing this interface are
84    * responsible for matching key characters typed by the user with combo
85    * box's items.
86    */
87   public static interface KeySelectionManager
88   {
89     int selectionForKey(char aKey, ComboBoxModel aModel);
90   }
91
92   /**
93    * Maximum number of rows that should be visible by default  in the
94    * JComboBox's popup
95    */
96   private static final int DEFAULT_MAXIMUM_ROW_COUNT = 8;
97
98   /**
99    * 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 }