001: /*
002: * Copyright 2000,2005 wingS development team.
003: *
004: * This file is part of wingS (http://wingsframework.org).
005: *
006: * wingS is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU Lesser General Public License
008: * as published by the Free Software Foundation; either version 2.1
009: * of the License, or (at your option) any later version.
010: *
011: * Please see COPYING for the complete licence.
012: */
013: package org.wings;
014:
015: import org.wings.plaf.ComboBoxCG;
016:
017: import javax.swing.*;
018: import javax.swing.event.EventListenerList;
019: import javax.swing.event.ListDataEvent;
020: import javax.swing.event.ListDataListener;
021: import java.awt.*;
022: import java.awt.event.ActionEvent;
023: import java.awt.event.ActionListener;
024: import java.awt.event.ItemEvent;
025: import java.awt.event.ItemListener;
026: import java.util.Vector;
027:
028: /**
029: * Combobox widget to be used inside {@link SForm} elements.
030: *
031: * @author Holger Engels
032: * @see javax.swing.ComboBoxModel
033: * @see SListCellRenderer
034: */
035: public class SComboBox extends SComponent implements
036: LowLevelEventListener, ListDataListener, ItemSelectable {
037: /**
038: * The model.
039: *
040: * @see javax.swing.ComboBoxModel
041: */
042: protected ComboBoxModel dataModel;
043:
044: /**
045: * The renderer used for cell rendering each cell.
046: *
047: * @see SListCellRenderer
048: */
049: protected SListCellRenderer renderer;
050:
051: /**
052: * how many rows are displayed in the popup window
053: */
054: protected int maximumRowCount = 8;
055:
056: /**
057: * action command to fire
058: */
059: protected String actionCommand = "comboBoxChanged";
060:
061: // do not initalize with null!
062: private final SCellRendererPane cellRendererPane = new SCellRendererPane();
063:
064: /**
065: * This protected field is implementation specific. Do not access directly
066: * or override.
067: */
068: protected Object selectedItemReminder;
069:
070: // Flag to ensure that infinite loops do not occur with ActionEvents.
071: private boolean firingActionEvent = false;
072:
073: // Flag to ensure the we don't get multiple ActionEvents on item selection.
074: private boolean selectingItem = false;
075:
076: private boolean delayEvent = false;
077: private boolean delayedEvent = false;
078:
079: /**
080: * Creates a SComboBox that takes its items from an existing ComboBoxModel.
081: *
082: * @param model the ComboBoxModel that provides the displayed list of items
083: */
084: public SComboBox(ComboBoxModel model) {
085: setModel(model);
086: }
087:
088: /**
089: * Creates a SComboBox that contains the elements in the specified array.
090: */
091: public SComboBox(final Object items[]) {
092: setModel(new DefaultComboBoxModel(items));
093: }
094:
095: /**
096: * Creates a SComboBox that contains the elements in the specified Vector.
097: */
098: public SComboBox(Vector items) {
099: setModel(new DefaultComboBoxModel(items));
100: }
101:
102: /**
103: * Creates a SComboBox with a default data model.
104: * The default data model is an empty list of objects.
105: * Use <code>addItem</code> to add items.
106: */
107: public SComboBox() {
108: setModel(new DefaultComboBoxModel());
109: }
110:
111: /**
112: * Sets the data model that the SComboBox uses to obtain the list of items.
113: *
114: * @param model the ComboBoxModel that provides the displayed list of items
115: */
116: public void setModel(ComboBoxModel model) {
117: if (isDifferent(dataModel, model)) {
118: if (dataModel != null)
119: dataModel.removeListDataListener(this );
120:
121: dataModel = model;
122:
123: dataModel.addListDataListener(this );
124:
125: // set the current selected item.
126: selectedItemReminder = dataModel.getSelectedItem();
127:
128: reload();
129: }
130: }
131:
132: /**
133: * Returns the data model currently used by the SComboBox.
134: *
135: * @return the ComboBoxModel that provides the displayed list of items
136: */
137: public final ComboBoxModel getModel() {
138: return dataModel;
139: }
140:
141: /**
142: * Sets the maximum number of rows the SComboBox displays.
143: * If shown as a formComponent, this value is used for the <em>size</em>-attribute.
144: *
145: * @param count <em>size</em>-attribute
146: */
147: public void setMaximumRowCount(int count) {
148: if (maximumRowCount != count) {
149: maximumRowCount = count;
150:
151: reload();
152: }
153: }
154:
155: /**
156: * Returns the <em>size</em>-attribute
157: *
158: * @return the value used for the <em>size</em>-attribute
159: */
160: public int getMaximumRowCount() {
161: return maximumRowCount;
162: }
163:
164: /**
165: * @param newRenderer the SListCellRenderer that displays the selected item.
166: */
167: public void setRenderer(SListCellRenderer newRenderer) {
168: if (isDifferent(renderer, newRenderer)) {
169: renderer = newRenderer;
170: reload();
171: }
172: }
173:
174: /**
175: * @return the ListCellRenderer that displays the selected item.
176: */
177: public SListCellRenderer getRenderer() {
178: return renderer;
179: }
180:
181: /**
182: * Sets the selected item in the SComboBox.
183: *
184: * @param object the list object to select
185: */
186: public void setSelectedItem(Object object) {
187: if (isDifferent(object, dataModel.getSelectedItem())) {
188: // Must toggle the state of this flag since this method
189: // call may result in ListDataEvents being fired.
190:
191: selectingItem = true;
192: dataModel.setSelectedItem(object);
193: selectingItem = false;
194:
195: if (!delayEvent) {
196: if (selectedItemReminder != dataModel.getSelectedItem()) {
197: // in case a users implementation of ComboBoxModel
198: // doesn't fire a ListDataEvent when the selection
199: // changes.
200: selectedItemChanged();
201: }
202: fireActionEvent();
203:
204: delayedEvent = false;
205: } else
206: delayedEvent = true;
207:
208: if (isUpdatePossible()
209: && SComboBox.class.isAssignableFrom(getClass()))
210: update(((ComboBoxCG) getCG()).getSelectionUpdate(this ,
211: getSelectedIndex()));
212: else
213: reload();
214: }
215: }
216:
217: /**
218: * Returns the currently selected item.
219: *
220: * @return the currently selected list object from the data model
221: */
222: public Object getSelectedItem() {
223: return dataModel.getSelectedItem();
224: }
225:
226: /**
227: * Selects the item at index <code>index</code>.
228: *
229: * @param index the item to be selected
230: */
231: public void setSelectedIndex(int index) {
232: int size = dataModel.getSize();
233:
234: if (index == -1)
235: setSelectedItem(null);
236: else if (index < -1 || index >= size)
237: throw new IllegalArgumentException("setSelectedIndex: "
238: + index + " out of bounds");
239: else
240: setSelectedItem(dataModel.getElementAt(index));
241: }
242:
243: /**
244: * Returns the index of the currently selected item in the list.
245: *
246: * @return the selected item in the list or -1 if no item is selected or if
247: * the currently selected item (text field) is not in the list
248: */
249: public int getSelectedIndex() {
250: Object selected = dataModel.getSelectedItem();
251:
252: if (selected == null)
253: return -1;
254:
255: int itemCount = getItemCount();
256: for (int i = 0; i < itemCount; i++) {
257: if (selected.equals(getItemAt(i))) {
258: return i;
259: }
260: }
261: return -1;
262: }
263:
264: /**
265: * Adds an item to the item list.
266: *
267: * @param object the Object to add to the list
268: */
269: public void addItem(Object object) {
270: checkMutableComboBoxModel();
271: ((MutableComboBoxModel) dataModel).addElement(object);
272: }
273:
274: /**
275: * Inserts an item into the item list at a given index.
276: *
277: * @param object the Object to add to the list
278: * @param index an int specifying the position at which to add the item
279: */
280: public void insertItemAt(Object object, int index) {
281: checkMutableComboBoxModel();
282: ((MutableComboBoxModel) dataModel).insertElementAt(object,
283: index);
284: }
285:
286: /**
287: * Removes an item from the item list.
288: * This method works only if the SComboBox uses the default data model.
289: * SComboBox uses the default data model when created with the empty constructor
290: * and no other model has been set.
291: *
292: * @param object the object to remove from the item list
293: */
294: public void removeItem(Object object) {
295: checkMutableComboBoxModel();
296: ((MutableComboBoxModel) dataModel).removeElement(object);
297: }
298:
299: /**
300: * Removes the item at <code>index</code>
301: *
302: * @param index an int specifying the idex of the item to remove, where 0
303: * indicates the first item in the list
304: */
305: public void removeItemAt(int index) {
306: checkMutableComboBoxModel();
307: ((MutableComboBoxModel) dataModel).removeElementAt(index);
308: }
309:
310: /**
311: * Removes all items from the item list.
312: */
313: public void removeAllItems() {
314: checkMutableComboBoxModel();
315: MutableComboBoxModel model = (MutableComboBoxModel) dataModel;
316: int size = model.getSize();
317:
318: if (model instanceof DefaultComboBoxModel) {
319: ((DefaultComboBoxModel) model).removeAllElements();
320: } else {
321: for (int i = 0; i < size; ++i) {
322: Object element = model.getElementAt(0);
323: model.removeElement(element);
324: }
325: }
326: }
327:
328: void checkMutableComboBoxModel() {
329: if (!(dataModel instanceof MutableComboBoxModel))
330: throw new RuntimeException(
331: "Cannot use this method with a non-Mutable data model.");
332: }
333:
334: /**
335: * Adds an ItemListener. The listener will receive an action event
336: * when the user changed the selection.
337: *
338: * @param listener the ItemListener that is to be notified
339: */
340: public void addItemListener(ItemListener listener) {
341: addEventListener(ItemListener.class, listener);
342: }
343:
344: /**
345: * Removes an ItemListener
346: *
347: * @param listener the ItemListener to remove
348: */
349: public void removeItemListener(ItemListener listener) {
350: removeEventListener(ItemListener.class, listener);
351: }
352:
353: /**
354: * Returns an array of all the ItemListeners added to this SComboBox
355: *
356: * @return all ItemListeners added or an empty array if there are no such listeners
357: */
358: public ItemListener[] getItemListeners() {
359: return (ItemListener[]) getListeners(ItemListener.class);
360: }
361:
362: /**
363: * Adds an ActionListener. The listener will receive an action event
364: * when the user changed the selection.
365: *
366: * @param listener the ActionListener that is to be notified
367: */
368: public void addActionListener(ActionListener listener) {
369: addEventListener(ActionListener.class, listener);
370: reload();
371: }
372:
373: /**
374: * Removes an ActionListener
375: *
376: * @param listener the ActionListener to remove
377: */
378: public void removeActionListener(ActionListener listener) {
379: removeEventListener(ActionListener.class, listener);
380: reload();
381: }
382:
383: /**
384: * Returns an array of all the ActionListeners added to this SComboBox
385: *
386: * @return all ActionListeners added or an empty array if there are no such listeners
387: */
388: public ActionListener[] getActionListeners() {
389: return (ActionListener[]) getListeners(ActionListener.class);
390: }
391:
392: /**
393: * Sets the action commnand that should be included in the event
394: * sent to action listeners.
395: *
396: * @param command a string containing the "command" that is sent
397: * to action listeners. The same listener can then
398: * do different things depending on the command it
399: * receives.
400: */
401: public void setActionCommand(String command) {
402: actionCommand = command;
403: }
404:
405: /**
406: * Returns the action commnand that is included in the event sent to
407: * action listeners.
408: *
409: * @return the string containing the "command" that is sent
410: * to action listeners.
411: */
412: public String getActionCommand() {
413: return actionCommand;
414: }
415:
416: /**
417: * Notify all listeners that have registered as ItemListeners.
418: *
419: * @see EventListenerList
420: */
421: protected void fireItemStateChanged(ItemEvent e) {
422: // Guaranteed to return a non-null array
423: Object[] listeners = getListenerList();
424: // Process the listeners last to first, notifying
425: // those that are interested in this event
426: for (int i = listeners.length - 2; i >= 0; i -= 2) {
427: if (listeners[i] == ItemListener.class) {
428: ((ItemListener) listeners[i + 1]).itemStateChanged(e);
429: }
430: }
431: }
432:
433: /**
434: * Notify all listeners that have registered as ActionListeners if the
435: * selected item has changed
436: *
437: * @see EventListenerList
438: */
439: protected void fireActionEvent() {
440: if (!firingActionEvent) {
441: // Set flag to ensure that an infinite loop is not created
442: firingActionEvent = true;
443:
444: ActionEvent e = null;
445:
446: // Guaranteed to return a non-null array
447: Object[] listeners = getListenerList();
448: // Process the listeners last to first, notifying
449: // those that are interested in this event
450: for (int i = listeners.length - 2; i >= 0; i -= 2) {
451: if (listeners[i] == ActionListener.class) {
452: if (e == null)
453: e = new ActionEvent(this ,
454: ActionEvent.ACTION_PERFORMED,
455: getActionCommand());
456: ((ActionListener) listeners[i + 1])
457: .actionPerformed(e);
458: }
459: }
460: firingActionEvent = false;
461: }
462: }
463:
464: /**
465: * Returns an array containing the selected item.
466: *
467: * @return an array of Objects containing the selected item
468: */
469: public Object[] getSelectedObjects() {
470: Object selectedObject = getSelectedItem();
471: if (selectedObject == null)
472: return new Object[0];
473: else
474: return new Object[] { selectedObject };
475: }
476:
477: /**
478: * This method is public as an implementation side effect.
479: * do not call or override.
480: *
481: * @see javax.swing.event.ListDataListener
482: */
483: public void contentsChanged(ListDataEvent e) {
484: if (isDifferent(selectedItemReminder, dataModel
485: .getSelectedItem())) {
486: if (!delayEvent) {
487: selectedItemChanged();
488: if (!selectingItem)
489: fireActionEvent();
490:
491: delayedEvent = false;
492: }
493:
494: if (e.getIndex0() != -1 && e.getIndex1() != -1)
495: reload();
496: }
497: }
498:
499: /**
500: * Invoked when items have been added to the internal data model.
501: * The "interval" includes the first and last values added.
502: *
503: * @see javax.swing.event.ListDataListener
504: */
505: public void intervalAdded(ListDataEvent e) {
506: reload();
507: }
508:
509: /**
510: * Invoked when values have been removed from the data model.
511: * The"interval" includes the first and last values removed.
512: *
513: * @see javax.swing.event.ListDataListener
514: */
515: public void intervalRemoved(ListDataEvent e) {
516: reload();
517: }
518:
519: /* Accessing the model */
520: /**
521: * Returns the number of items in the list.
522: *
523: * @return an int equal to the number of items in the list
524: */
525: public int getItemCount() {
526: return dataModel.getSize();
527: }
528:
529: /**
530: * Returns the list item at the specified index.
531: *
532: * @param index an int indicating the list position
533: * @return the Object at that list position
534: */
535: public Object getItemAt(int index) {
536: return dataModel.getElementAt(index);
537: }
538:
539: /**
540: * This protected method is implementation specific. Do not access directly
541: * or override.
542: */
543: protected void selectedItemChanged() {
544: if (selectedItemReminder != null) {
545: fireItemStateChanged(new ItemEvent(this ,
546: ItemEvent.ITEM_STATE_CHANGED, selectedItemReminder,
547: ItemEvent.DESELECTED));
548: }
549:
550: // set the new selected item.
551: selectedItemReminder = dataModel.getSelectedItem();
552:
553: if (selectedItemReminder != null) {
554: fireItemStateChanged(new ItemEvent(this ,
555: ItemEvent.ITEM_STATE_CHANGED, selectedItemReminder,
556: ItemEvent.SELECTED));
557: }
558: }
559:
560: public void processLowLevelEvent(String action, String[] values) {
561: processKeyEvents(values);
562: if (action.endsWith("_keystroke"))
563: return;
564:
565: delayEvent = true;
566:
567: int selectedIndex = -1;
568: // last will win!!
569: for (int i = 0; i < values.length; i++) {
570: try {
571: if (values[i].length() > 0)
572: selectedIndex = Integer.parseInt(values[i]);
573: } catch (Exception ex) {
574: // ignore, some illegal request.. (maybe log it)
575: }
576: }
577:
578: if (selectedIndex >= 0 && getSelectedIndex() != selectedIndex) {
579: setSelectedIndex(selectedIndex);
580: SForm.addArmedComponent(this );
581: }
582:
583: delayEvent = false;
584: }
585:
586: public void setParent(SContainer p) {
587: super .setParent(p);
588: if (getCellRendererPane() != null)
589: getCellRendererPane().setParent(p);
590: }
591:
592: protected void setParentFrame(SFrame f) {
593: super .setParentFrame(f);
594: if (getCellRendererPane() != null)
595: getCellRendererPane().setParentFrame(f);
596: }
597:
598: public void fireIntermediateEvents() {
599: }
600:
601: public void fireFinalEvents() {
602: super .fireFinalEvents();
603: if (delayedEvent) {
604: if (selectedItemReminder != dataModel.getSelectedItem()) {
605: // in case a users implementation of ComboBoxModel
606: // doesn't fire a ListDataEvent when the selection
607: // changes.
608: selectedItemChanged();
609: }
610: fireActionEvent();
611:
612: delayedEvent = false;
613: }
614: }
615:
616: /** @see LowLevelEventListener#isEpochCheckEnabled() */
617: private boolean epochCheckEnabled = true;
618:
619: /** @see LowLevelEventListener#isEpochCheckEnabled() */
620: public boolean isEpochCheckEnabled() {
621: return epochCheckEnabled;
622: }
623:
624: /** @see LowLevelEventListener#isEpochCheckEnabled() */
625: public void setEpochCheckEnabled(boolean epochCheckEnabled) {
626: this .epochCheckEnabled = epochCheckEnabled;
627: }
628:
629: public final SCellRendererPane getCellRendererPane() {
630: return cellRendererPane;
631: }
632:
633: public void setCG(ComboBoxCG cg) {
634: super .setCG(cg);
635: }
636:
637: public String getSelectionParameter(int index) {
638: return Integer.toString(index);
639: }
640: }
|