001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package javax.swing;
019:
020: import java.awt.ItemSelectable;
021: import java.awt.event.ActionEvent;
022: import java.awt.event.ActionListener;
023: import java.awt.event.ItemEvent;
024: import java.awt.event.ItemListener;
025: import java.awt.event.KeyEvent;
026: import java.beans.PropertyChangeEvent;
027: import java.beans.PropertyChangeListener;
028: import java.util.Vector;
029: import javax.accessibility.Accessible;
030: import javax.accessibility.AccessibleAction;
031: import javax.accessibility.AccessibleContext;
032: import javax.accessibility.AccessibleRole;
033: import javax.accessibility.AccessibleSelection;
034: import javax.accessibility.AccessibleStateSet;
035: import javax.swing.event.AncestorEvent;
036: import javax.swing.event.AncestorListener;
037: import javax.swing.event.ListDataEvent;
038: import javax.swing.event.ListDataListener;
039: import javax.swing.event.PopupMenuEvent;
040: import javax.swing.event.PopupMenuListener;
041: import javax.swing.plaf.ComboBoxUI;
042: import org.apache.harmony.luni.util.NotImplementedException;
043: import org.apache.harmony.x.swing.internal.nls.Messages;
044: import org.apache.harmony.x.swing.StringConstants;
045:
046: /**
047: * <p>
048: * <i>JComboBox</i>
049: * </p>
050: * <h3>Implementation Notes:</h3>
051: * <ul>
052: * <li>The <code>serialVersionUID</code> fields are explicitly declared as a performance
053: * optimization, not as a guarantee of serialization compatibility.</li>
054: * </ul>
055: */
056: public class JComboBox extends JComponent implements ItemSelectable,
057: ListDataListener, ActionListener, Accessible {
058: private static final long serialVersionUID = 4884562788864849284L;
059:
060: protected class AccessibleJComboBox extends AccessibleJComponent
061: implements AccessibleAction, AccessibleSelection {
062: private static final long serialVersionUID = 1L;
063:
064: public AccessibleJComboBox() {
065: }
066:
067: @Override
068: public int getAccessibleChildrenCount()
069: throws NotImplementedException {
070: throw new NotImplementedException();
071: }
072:
073: @Override
074: public Accessible getAccessibleChild(int i)
075: throws NotImplementedException {
076: throw new NotImplementedException();
077: }
078:
079: @Override
080: public AccessibleRole getAccessibleRole() {
081: return AccessibleRole.COMBO_BOX;
082: }
083:
084: @Override
085: public AccessibleStateSet getAccessibleStateSet()
086: throws NotImplementedException {
087: throw new NotImplementedException();
088: }
089:
090: @Override
091: public AccessibleAction getAccessibleAction()
092: throws NotImplementedException {
093: throw new NotImplementedException();
094: }
095:
096: public String getAccessibleActionDescription(int i)
097: throws NotImplementedException {
098: throw new NotImplementedException();
099: }
100:
101: public int getAccessibleActionCount()
102: throws NotImplementedException {
103: throw new NotImplementedException();
104: }
105:
106: public boolean doAccessibleAction(int i)
107: throws NotImplementedException {
108: throw new NotImplementedException();
109: }
110:
111: @Override
112: public AccessibleSelection getAccessibleSelection()
113: throws NotImplementedException {
114: throw new NotImplementedException();
115: }
116:
117: public int getAccessibleSelectionCount()
118: throws NotImplementedException {
119: throw new NotImplementedException();
120: }
121:
122: public Accessible getAccessibleSelection(int i)
123: throws NotImplementedException {
124: throw new NotImplementedException();
125: }
126:
127: public boolean isAccessibleChildSelected(int i)
128: throws NotImplementedException {
129: throw new NotImplementedException();
130: }
131:
132: public void addAccessibleSelection(int i)
133: throws NotImplementedException {
134: throw new NotImplementedException();
135: }
136:
137: public void removeAccessibleSelection(int i)
138: throws NotImplementedException {
139: throw new NotImplementedException();
140: }
141:
142: public void clearAccessibleSelection()
143: throws NotImplementedException {
144: throw new NotImplementedException();
145: }
146:
147: public void selectAllAccessibleSelection()
148: throws NotImplementedException {
149: throw new NotImplementedException();
150: }
151: }
152:
153: public static interface KeySelectionManager {
154: int selectionForKey(char key, ComboBoxModel model);
155: }
156:
157: private class DefaultKeySelectionManager implements
158: KeySelectionManager {
159: public int selectionForKey(char keyChar, ComboBoxModel model) {
160: int selectedIndex = getIndex(model.getSelectedItem(), model);
161: for (int i = selectedIndex + 1; i < model.getSize(); i++) {
162: String item = model.getElementAt(i).toString();
163: if (itemStartsWith(item, keyChar)) {
164: return i;
165: }
166: }
167: for (int i = 0; i <= selectedIndex; i++) {
168: String item = model.getElementAt(i).toString();
169: if (itemStartsWith(item, keyChar)) {
170: return i;
171: }
172: }
173: return -1;
174: }
175:
176: private boolean itemStartsWith(String item, char keyChar) {
177: return Character.toUpperCase(keyChar) == Character
178: .toUpperCase(item.charAt(0));
179: }
180: }
181:
182: private class ActionPropertyChangeListener implements
183: PropertyChangeListener {
184: public void propertyChange(PropertyChangeEvent event) {
185: Action action = (Action) event.getSource();
186: if (action != null) {
187: String propertyName = event.getPropertyName();
188: if (Action.SHORT_DESCRIPTION.equals(propertyName)) {
189: setToolTipText((String) event.getNewValue());
190: } else if (StringConstants.ENABLED_PROPERTY_CHANGED
191: .equals(propertyName)) {
192: setEnabled(((Boolean) event.getNewValue())
193: .booleanValue());
194: } else if (Action.ACTION_COMMAND_KEY
195: .equals(propertyName)) {
196: setActionCommand((String) action
197: .getValue(Action.ACTION_COMMAND_KEY));
198: }
199: }
200: }
201: }
202:
203: private static final String UI_CLASS_ID = "ComboBoxUI";
204:
205: private static final String MAXIMUM_ROW_COUNT_PROPERTY_CHANGED = "maximumRowCount";
206:
207: private static final String PROTOTYPE_DISPLAY_VALUE_PROPERTY_CHANGED = "prototypeDisplayValue";
208:
209: protected String actionCommand = "comboBoxChanged";
210:
211: protected ComboBoxModel dataModel;
212:
213: protected ComboBoxEditor editor;
214:
215: protected boolean isEditable;
216:
217: protected KeySelectionManager keySelectionManager;
218:
219: protected boolean lightWeightPopupEnabled = true;
220:
221: protected int maximumRowCount = 8;
222:
223: protected ListCellRenderer renderer;
224:
225: protected Object selectedItemReminder;
226:
227: private Object prototypeDisplayValue;
228:
229: private Action action;
230:
231: private PropertyChangeListener actionPropertyChangeListener;
232:
233: public JComboBox() {
234: this (new DefaultComboBoxModel());
235: }
236:
237: public JComboBox(Object[] items) {
238: this (new DefaultComboBoxModel(items));
239: }
240:
241: public JComboBox(Vector<?> items) {
242: this (new DefaultComboBoxModel(items));
243: }
244:
245: public JComboBox(ComboBoxModel model) {
246: dataModel = model;
247: dataModel.addListDataListener(this );
248: installAncestorListener();
249: updateUI();
250: }
251:
252: public void setUI(ComboBoxUI ui) {
253: super .setUI(ui);
254: }
255:
256: @Override
257: public void updateUI() {
258: setUI((ComboBoxUI) UIManager.getUI(this ));
259: }
260:
261: @Override
262: public String getUIClassID() {
263: return UI_CLASS_ID;
264: }
265:
266: public ComboBoxUI getUI() {
267: return (ComboBoxUI) ui;
268: }
269:
270: public void setModel(ComboBoxModel model) {
271: if (dataModel != model) {
272: ComboBoxModel oldModel = dataModel;
273: if (oldModel != null) {
274: oldModel.removeListDataListener(this );
275: }
276: dataModel = model;
277: dataModel.addListDataListener(this );
278: firePropertyChange(StringConstants.MODEL_PROPERTY_CHANGED,
279: oldModel, model);
280: }
281: }
282:
283: public ComboBoxModel getModel() {
284: return dataModel;
285: }
286:
287: public void setLightWeightPopupEnabled(boolean isEnabled) {
288: if (lightWeightPopupEnabled != isEnabled) {
289: lightWeightPopupEnabled = isEnabled;
290: firePropertyChange(
291: StringConstants.LIGHTWEIGHT_POPUP_ENABLED_PROPERTY_CHANGED,
292: !isEnabled, isEnabled);
293: }
294: }
295:
296: public boolean isLightWeightPopupEnabled() {
297: return lightWeightPopupEnabled;
298: }
299:
300: public void setEditable(boolean isEditable) {
301: if (this .isEditable != isEditable) {
302: this .isEditable = isEditable;
303: firePropertyChange(
304: StringConstants.EDITABLE_PROPERTY_CHANGED,
305: !isEditable, isEditable);
306: }
307: }
308:
309: public boolean isEditable() {
310: return isEditable;
311: }
312:
313: public void setMaximumRowCount(int count) {
314: LookAndFeel.markPropertyNotInstallable(this , "maximumRowCount");
315: if (maximumRowCount != count) {
316: int oldValue = maximumRowCount;
317: maximumRowCount = count;
318: firePropertyChange(MAXIMUM_ROW_COUNT_PROPERTY_CHANGED,
319: oldValue, count);
320: }
321: }
322:
323: public int getMaximumRowCount() {
324: return maximumRowCount;
325: }
326:
327: public void setRenderer(ListCellRenderer renderer) {
328: if (this .renderer != renderer) {
329: ListCellRenderer oldValue = this .renderer;
330: this .renderer = renderer;
331: firePropertyChange(
332: StringConstants.RENDERER_PROPERTY_CHANGED,
333: oldValue, renderer);
334: }
335: }
336:
337: public ListCellRenderer getRenderer() {
338: return renderer;
339: }
340:
341: public void setEditor(ComboBoxEditor editor) {
342: if (this .editor != editor) {
343: ComboBoxEditor oldValue = this .editor;
344: if (oldValue != null) {
345: oldValue.removeActionListener(this );
346: }
347: this .editor = editor;
348: if (this .editor != null) {
349: this .editor.addActionListener(this );
350: }
351: firePropertyChange(StringConstants.EDITOR_PROPERTY_CHANGED,
352: oldValue, editor);
353: }
354: }
355:
356: public ComboBoxEditor getEditor() {
357: return editor;
358: }
359:
360: public void setSelectedItem(Object element) {
361: selectedItemReminder = dataModel.getSelectedItem();
362: if (isEditable || getIndex(element) != -1 || element == null) {
363: if (element != getSelectedItem() || element != null
364: && !element.equals(getSelectedItem())) {
365: dataModel.setSelectedItem(element);
366: } else if (isEditable && element != null
367: && !element.equals(getEditor().getItem())) {
368: getEditor().setItem(element);
369: } else {
370: // fire action event even if selection is not changed
371: fireActionEvent();
372: }
373: }
374: }
375:
376: public Object getSelectedItem() {
377: return dataModel.getSelectedItem();
378: }
379:
380: public void setSelectedIndex(int index) {
381: if (index < -1 || index >= dataModel.getSize()) {
382: throw new IllegalArgumentException(Messages
383: .getString("swing.0C")); //$NON-NLS-1$
384: }
385: if (index == -1) {
386: setSelectedItem(null);
387: } else {
388: setSelectedItem(dataModel.getElementAt(index));
389: }
390: }
391:
392: public Object[] getSelectedObjects() {
393: if (getSelectedItem() != null) {
394: return new Object[] { getSelectedItem() };
395: }
396: return new Object[0];
397: }
398:
399: public int getSelectedIndex() {
400: return getIndex(getSelectedItem());
401: }
402:
403: public Object getPrototypeDisplayValue() {
404: return prototypeDisplayValue;
405: }
406:
407: public void setPrototypeDisplayValue(Object prototypeDisplayValue) {
408: if (this .prototypeDisplayValue != prototypeDisplayValue) {
409: Object oldValue = this .prototypeDisplayValue;
410: this .prototypeDisplayValue = prototypeDisplayValue;
411: firePropertyChange(
412: PROTOTYPE_DISPLAY_VALUE_PROPERTY_CHANGED, oldValue,
413: prototypeDisplayValue);
414: }
415: }
416:
417: public void addItem(Object element) {
418: selectedItemReminder = getSelectedItem();
419: if (dataModel instanceof MutableComboBoxModel) {
420: ((MutableComboBoxModel) dataModel).addElement(element);
421: } else {
422: throw new RuntimeException(Messages.getString("swing.0B")); //$NON-NLS-1$
423: }
424: }
425:
426: public void insertItemAt(Object element, int index) {
427: selectedItemReminder = getSelectedItem();
428: if (dataModel instanceof MutableComboBoxModel) {
429: ((MutableComboBoxModel) dataModel).insertElementAt(element,
430: index);
431: } else {
432: throw new RuntimeException(Messages.getString("swing.0B")); //$NON-NLS-1$
433: }
434: }
435:
436: public void removeItem(Object element) {
437: selectedItemReminder = getSelectedItem();
438: if (dataModel instanceof MutableComboBoxModel) {
439: ((MutableComboBoxModel) dataModel).removeElement(element);
440: } else {
441: throw new RuntimeException(Messages.getString("swing.0B")); //$NON-NLS-1$
442: }
443: }
444:
445: public void removeItemAt(int index) {
446: selectedItemReminder = getSelectedItem();
447: if (dataModel instanceof MutableComboBoxModel) {
448: ((MutableComboBoxModel) dataModel).removeElementAt(index);
449: } else {
450: throw new RuntimeException(Messages.getString("swing.0B")); //$NON-NLS-1$
451: }
452: }
453:
454: public void removeAllItems() {
455: selectedItemReminder = getSelectedItem();
456: if (dataModel instanceof MutableComboBoxModel) {
457: MutableComboBoxModel model = (MutableComboBoxModel) dataModel;
458: while (model.getSize() > 0) {
459: model.removeElementAt(0);
460: }
461: } else {
462: throw new RuntimeException(Messages.getString("swing.0B")); //$NON-NLS-1$
463: }
464: }
465:
466: public int getItemCount() {
467: return dataModel.getSize();
468: }
469:
470: public Object getItemAt(int index) {
471: if (index < 0 || index >= dataModel.getSize()) {
472: return null;
473: }
474: return dataModel.getElementAt(index);
475: }
476:
477: public void addItemListener(ItemListener l) {
478: listenerList.add(ItemListener.class, l);
479: }
480:
481: public void removeItemListener(ItemListener l) {
482: listenerList.remove(ItemListener.class, l);
483: }
484:
485: public ItemListener[] getItemListeners() {
486: return listenerList.getListeners(ItemListener.class);
487: }
488:
489: public void addActionListener(ActionListener l) {
490: listenerList.add(ActionListener.class, l);
491: }
492:
493: public void removeActionListener(ActionListener l) {
494: listenerList.remove(ActionListener.class, l);
495: }
496:
497: public ActionListener[] getActionListeners() {
498: return listenerList.getListeners(ActionListener.class);
499: }
500:
501: public void addPopupMenuListener(PopupMenuListener l) {
502: listenerList.add(PopupMenuListener.class, l);
503: }
504:
505: public void removePopupMenuListener(PopupMenuListener l) {
506: listenerList.remove(PopupMenuListener.class, l);
507: }
508:
509: public PopupMenuListener[] getPopupMenuListeners() {
510: return listenerList.getListeners(PopupMenuListener.class);
511: }
512:
513: public void firePopupMenuWillBecomeVisible() {
514: PopupMenuEvent event = new PopupMenuEvent(this );
515: PopupMenuListener[] listeners = getPopupMenuListeners();
516: for (int i = 0; i < listeners.length; i++) {
517: listeners[i].popupMenuWillBecomeVisible(event);
518: }
519: }
520:
521: public void firePopupMenuWillBecomeInvisible() {
522: PopupMenuEvent event = new PopupMenuEvent(this );
523: PopupMenuListener[] listeners = getPopupMenuListeners();
524: for (int i = 0; i < listeners.length; i++) {
525: listeners[i].popupMenuWillBecomeInvisible(event);
526: }
527: }
528:
529: public void firePopupMenuCanceled() {
530: PopupMenuEvent event = new PopupMenuEvent(this );
531: PopupMenuListener[] listeners = getPopupMenuListeners();
532: for (int i = 0; i < listeners.length; i++) {
533: listeners[i].popupMenuCanceled(event);
534: }
535: }
536:
537: public void setActionCommand(String command) {
538: actionCommand = command;
539: }
540:
541: public String getActionCommand() {
542: return actionCommand;
543: }
544:
545: public void showPopup() {
546: setPopupVisible(true);
547: }
548:
549: public void hidePopup() {
550: setPopupVisible(false);
551: }
552:
553: public void setPopupVisible(boolean isVisible) {
554: getUI().setPopupVisible(this , isVisible);
555: }
556:
557: public boolean isPopupVisible() {
558: return getUI().isPopupVisible(this );
559: }
560:
561: public void setAction(Action action) {
562: if (this .action == action) {
563: return;
564: }
565: Action oldValue = this .action;
566: if (oldValue != null) {
567: if (hasListener(ActionListener.class, oldValue)) {
568: removeActionListener(oldValue);
569: }
570: if (actionPropertyChangeListener != null) {
571: oldValue
572: .removePropertyChangeListener(actionPropertyChangeListener);
573: }
574: }
575: this .action = action;
576: if (action != null) {
577: if (!hasListener(ActionListener.class, action)) {
578: addActionListener(action);
579: }
580: actionPropertyChangeListener = createActionPropertyChangeListener(this .action);
581: this .action
582: .addPropertyChangeListener(actionPropertyChangeListener);
583: }
584: firePropertyChange(StringConstants.ACTION_PROPERTY_CHANGED,
585: oldValue, action);
586: configurePropertiesFromAction(action);
587: }
588:
589: public Action getAction() {
590: return action;
591: }
592:
593: protected void configurePropertiesFromAction(Action action) {
594: if (action != null) {
595: setToolTipText((String) action
596: .getValue(Action.SHORT_DESCRIPTION));
597: setEnabled(action.isEnabled());
598: setActionCommand((String) action
599: .getValue(Action.ACTION_COMMAND_KEY));
600: } else {
601: setActionCommand(null);
602: setEnabled(true);
603: setToolTipText(null);
604: }
605: }
606:
607: public void actionPerformed(ActionEvent e) {
608: setSelectedItem(editor.getItem());
609: }
610:
611: public void contentsChanged(ListDataEvent e) {
612: selectedItemChanged();
613: fireActionEvent();
614: }
615:
616: public void intervalAdded(ListDataEvent e) {
617: }
618:
619: public void intervalRemoved(ListDataEvent e) {
620: }
621:
622: @Override
623: public void setEnabled(boolean isEnabled) {
624: super .setEnabled(isEnabled);
625: if (action != null) {
626: action.setEnabled(isEnabled);
627: }
628: }
629:
630: public void configureEditor(ComboBoxEditor editor, Object item) {
631: editor.setItem(item);
632: }
633:
634: @Override
635: public void processKeyEvent(KeyEvent e) {
636: if (e.getKeyCode() == KeyEvent.VK_TAB) {
637: hidePopup();
638: }
639: super .processKeyEvent(e);
640: }
641:
642: public boolean selectWithKeyChar(char keyChar) {
643: if (keySelectionManager == null) {
644: keySelectionManager = createDefaultKeySelectionManager();
645: }
646: int index = keySelectionManager.selectionForKey(keyChar,
647: getModel());
648: if (index != -1) {
649: setSelectedIndex(index);
650: return true;
651: }
652: return false;
653: }
654:
655: public void setKeySelectionManager(KeySelectionManager manager) {
656: keySelectionManager = manager;
657: }
658:
659: public KeySelectionManager getKeySelectionManager() {
660: return keySelectionManager;
661: }
662:
663: @Override
664: public AccessibleContext getAccessibleContext() {
665: if (accessibleContext == null) {
666: accessibleContext = new AccessibleJComboBox();
667: }
668: return accessibleContext;
669: }
670:
671: protected void installAncestorListener() {
672: addAncestorListener(new AncestorListener() {
673: public void ancestorAdded(AncestorEvent e) {
674: }
675:
676: public void ancestorMoved(AncestorEvent e) {
677: }
678:
679: public void ancestorRemoved(AncestorEvent e) {
680: hidePopup();
681: }
682: });
683: }
684:
685: protected PropertyChangeListener createActionPropertyChangeListener(
686: Action a) {
687: return new ActionPropertyChangeListener();
688: }
689:
690: protected void fireItemStateChanged(ItemEvent e) {
691: ItemListener[] listeners = getItemListeners();
692: for (int i = 0; i < listeners.length; i++) {
693: listeners[i].itemStateChanged(e);
694: }
695: }
696:
697: protected void fireActionEvent() {
698: ActionEvent event = new ActionEvent(this ,
699: ActionEvent.ACTION_PERFORMED, actionCommand);
700: ActionListener[] listeners = getActionListeners();
701: for (int i = 0; i < listeners.length; i++) {
702: listeners[i].actionPerformed(event);
703: }
704: }
705:
706: protected void selectedItemChanged() {
707: if (selectedItemReminder != null) {
708: ItemEvent itemEvent = new ItemEvent(JComboBox.this ,
709: ItemEvent.ITEM_STATE_CHANGED, selectedItemReminder,
710: ItemEvent.DESELECTED);
711: fireItemStateChanged(itemEvent);
712: }
713: Object newSelection = dataModel.getSelectedItem();
714: if (isEditable || getIndex(newSelection) != -1) {
715: ItemEvent itemEvent = new ItemEvent(JComboBox.this ,
716: ItemEvent.ITEM_STATE_CHANGED, newSelection,
717: ItemEvent.SELECTED);
718: fireItemStateChanged(itemEvent);
719: }
720: }
721:
722: protected KeySelectionManager createDefaultKeySelectionManager() {
723: return new DefaultKeySelectionManager();
724: }
725:
726: private int getIndex(Object element) {
727: return getIndex(element, dataModel);
728: }
729:
730: private int getIndex(Object element, ComboBoxModel model) {
731: if (element == null) {
732: return -1;
733: }
734: for (int i = 0; i < model.getSize(); i++) {
735: if (element.equals(model.getElementAt(i))) {
736: return i;
737: }
738: }
739: return -1;
740: }
741: }
|