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