001: /* ========================================================================
002: * JCommon : a free general purpose class library for the Java(tm) platform
003: * ========================================================================
004: *
005: * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jcommon/index.html
008: *
009: * This library is free software; you can redistribute it and/or modify it
010: * under the terms of the GNU Lesser General Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but
015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017: * License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022: * USA.
023: *
024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025: * in the United States and other countries.]
026: *
027: * ------------------
028: * KeyedComboBoxModel.java
029: * ------------------
030: * (C) Copyright 2004, by Thomas Morgner and Contributors.
031: *
032: * Original Author: Thomas Morgner;
033: * Contributor(s): David Gilbert (for Object Refinery Limited);
034: *
035: * $Id: KeyedComboBoxModel.java,v 1.6 2006/12/03 15:33:33 taqua Exp $
036: *
037: * Changes
038: * -------
039: * 07-Jun-2004 : Added JCommon header (DG);
040: *
041: */
042: package org.jfree.ui;
043:
044: import java.util.ArrayList;
045: import javax.swing.ComboBoxModel;
046: import javax.swing.event.ListDataEvent;
047: import javax.swing.event.ListDataListener;
048:
049: /**
050: * The KeyedComboBox model allows to define an internal key (the data element)
051: * for every entry in the model.
052: * <p/>
053: * This class is usefull in all cases, where the public text differs from the
054: * internal view on the data. A separation between presentation data and
055: * processing data is a prequesite for localizing combobox entries. This model
056: * does not allow selected elements, which are not in the list of valid
057: * elements.
058: *
059: * @author Thomas Morgner
060: */
061: public class KeyedComboBoxModel implements ComboBoxModel {
062:
063: /**
064: * The internal data carrier to map keys to values and vice versa.
065: */
066: private static class ComboBoxItemPair {
067: /**
068: * The key.
069: */
070: private Object key;
071: /**
072: * The value for the key.
073: */
074: private Object value;
075:
076: /**
077: * Creates a new item pair for the given key and value. The value can be
078: * changed later, if needed.
079: *
080: * @param key the key
081: * @param value the value
082: */
083: public ComboBoxItemPair(final Object key, final Object value) {
084: this .key = key;
085: this .value = value;
086: }
087:
088: /**
089: * Returns the key.
090: *
091: * @return the key.
092: */
093: public Object getKey() {
094: return key;
095: }
096:
097: /**
098: * Returns the value.
099: *
100: * @return the value for this key.
101: */
102: public Object getValue() {
103: return value;
104: }
105:
106: /**
107: * Redefines the value stored for that key.
108: *
109: * @param value the new value.
110: */
111: public void setValue(final Object value) {
112: this .value = value;
113: }
114: }
115:
116: /**
117: * The index of the selected item.
118: */
119: private int selectedItemIndex;
120: private Object selectedItemValue;
121: /**
122: * The data (contains ComboBoxItemPairs).
123: */
124: private ArrayList data;
125: /**
126: * The listeners.
127: */
128: private ArrayList listdatalistener;
129: /**
130: * The cached listeners as array.
131: */
132: private transient ListDataListener[] tempListeners;
133: private boolean allowOtherValue;
134:
135: /**
136: * Creates a new keyed combobox model.
137: */
138: public KeyedComboBoxModel() {
139: data = new ArrayList();
140: listdatalistener = new ArrayList();
141: }
142:
143: /**
144: * Creates a new keyed combobox model for the given keys and values. Keys
145: * and values must have the same number of items.
146: *
147: * @param keys the keys
148: * @param values the values
149: */
150: public KeyedComboBoxModel(final Object[] keys, final Object[] values) {
151: this ();
152: setData(keys, values);
153: }
154:
155: /**
156: * Replaces the data in this combobox model. The number of keys must be
157: * equals to the number of values.
158: *
159: * @param keys the keys
160: * @param values the values
161: */
162: public void setData(final Object[] keys, final Object[] values) {
163: if (values.length != keys.length) {
164: throw new IllegalArgumentException(
165: "Values and text must have the same length.");
166: }
167:
168: data.clear();
169: data.ensureCapacity(keys.length);
170:
171: for (int i = 0; i < values.length; i++) {
172: add(keys[i], values[i]);
173: }
174:
175: selectedItemIndex = -1;
176: final ListDataEvent evt = new ListDataEvent(this ,
177: ListDataEvent.CONTENTS_CHANGED, 0, data.size() - 1);
178: fireListDataEvent(evt);
179: }
180:
181: /**
182: * Notifies all registered list data listener of the given event.
183: *
184: * @param evt the event.
185: */
186: protected synchronized void fireListDataEvent(
187: final ListDataEvent evt) {
188: if (tempListeners == null) {
189: tempListeners = (ListDataListener[]) listdatalistener
190: .toArray(new ListDataListener[listdatalistener
191: .size()]);
192: }
193: for (int i = 0; i < tempListeners.length; i++) {
194: final ListDataListener l = tempListeners[i];
195: l.contentsChanged(evt);
196: }
197: }
198:
199: /**
200: * Returns the selected item.
201: *
202: * @return The selected item or <code>null</code> if there is no selection
203: */
204: public Object getSelectedItem() {
205: return selectedItemValue;
206: }
207:
208: /**
209: * Defines the selected key. If the object is not in the list of values, no
210: * item gets selected.
211: *
212: * @param anItem the new selected item.
213: */
214: public void setSelectedKey(final Object anItem) {
215: if (anItem == null) {
216: selectedItemIndex = -1;
217: selectedItemValue = null;
218: } else {
219: final int newSelectedItem = findDataElementIndex(anItem);
220: if (newSelectedItem == -1) {
221: selectedItemIndex = -1;
222: selectedItemValue = null;
223: } else {
224: selectedItemIndex = newSelectedItem;
225: selectedItemValue = getElementAt(selectedItemIndex);
226: }
227: }
228: fireListDataEvent(new ListDataEvent(this ,
229: ListDataEvent.CONTENTS_CHANGED, -1, -1));
230: }
231:
232: /**
233: * Set the selected item. The implementation of this method should notify
234: * all registered <code>ListDataListener</code>s that the contents have
235: * changed.
236: *
237: * @param anItem the list object to select or <code>null</code> to clear the
238: * selection
239: */
240: public void setSelectedItem(final Object anItem) {
241: if (anItem == null) {
242: selectedItemIndex = -1;
243: selectedItemValue = null;
244: } else {
245: final int newSelectedItem = findElementIndex(anItem);
246: if (newSelectedItem == -1) {
247: if (isAllowOtherValue()) {
248: selectedItemIndex = -1;
249: selectedItemValue = anItem;
250: } else {
251: selectedItemIndex = -1;
252: selectedItemValue = null;
253: }
254: } else {
255: selectedItemIndex = newSelectedItem;
256: selectedItemValue = getElementAt(selectedItemIndex);
257: }
258: }
259: fireListDataEvent(new ListDataEvent(this ,
260: ListDataEvent.CONTENTS_CHANGED, -1, -1));
261: }
262:
263: private boolean isAllowOtherValue() {
264: return allowOtherValue;
265: }
266:
267: public void setAllowOtherValue(final boolean allowOtherValue) {
268: this .allowOtherValue = allowOtherValue;
269: }
270:
271: /**
272: * Adds a listener to the list that's notified each time a change to the data
273: * model occurs.
274: *
275: * @param l the <code>ListDataListener</code> to be added
276: */
277: public synchronized void addListDataListener(
278: final ListDataListener l) {
279: listdatalistener.add(l);
280: tempListeners = null;
281: }
282:
283: /**
284: * Returns the value at the specified index.
285: *
286: * @param index the requested index
287: * @return the value at <code>index</code>
288: */
289: public Object getElementAt(final int index) {
290: if (index >= data.size()) {
291: return null;
292: }
293:
294: final ComboBoxItemPair datacon = (ComboBoxItemPair) data
295: .get(index);
296: if (datacon == null) {
297: return null;
298: }
299: return datacon.getValue();
300: }
301:
302: /**
303: * Returns the key from the given index.
304: *
305: * @param index the index of the key.
306: * @return the the key at the specified index.
307: */
308: public Object getKeyAt(final int index) {
309: if (index >= data.size()) {
310: return null;
311: }
312:
313: if (index < 0) {
314: return null;
315: }
316:
317: final ComboBoxItemPair datacon = (ComboBoxItemPair) data
318: .get(index);
319: if (datacon == null) {
320: return null;
321: }
322: return datacon.getKey();
323: }
324:
325: /**
326: * Returns the selected data element or null if none is set.
327: *
328: * @return the selected data element.
329: */
330: public Object getSelectedKey() {
331: return getKeyAt(selectedItemIndex);
332: }
333:
334: /**
335: * Returns the length of the list.
336: *
337: * @return the length of the list
338: */
339: public int getSize() {
340: return data.size();
341: }
342:
343: /**
344: * Removes a listener from the list that's notified each time a change to
345: * the data model occurs.
346: *
347: * @param l the <code>ListDataListener</code> to be removed
348: */
349: public void removeListDataListener(final ListDataListener l) {
350: listdatalistener.remove(l);
351: tempListeners = null;
352: }
353:
354: /**
355: * Searches an element by its data value. This method is called by the
356: * setSelectedItem method and returns the first occurence of the element.
357: *
358: * @param anItem the item
359: * @return the index of the item or -1 if not found.
360: */
361: private int findDataElementIndex(final Object anItem) {
362: if (anItem == null) {
363: throw new NullPointerException(
364: "Item to find must not be null");
365: }
366:
367: for (int i = 0; i < data.size(); i++) {
368: final ComboBoxItemPair datacon = (ComboBoxItemPair) data
369: .get(i);
370: if (anItem.equals(datacon.getKey())) {
371: return i;
372: }
373: }
374: return -1;
375: }
376:
377: /**
378: * Tries to find the index of element with the given key. The key must not
379: * be null.
380: *
381: * @param key the key for the element to be searched.
382: * @return the index of the key, or -1 if not found.
383: */
384: public int findElementIndex(final Object key) {
385: if (key == null) {
386: throw new NullPointerException(
387: "Item to find must not be null");
388: }
389:
390: for (int i = 0; i < data.size(); i++) {
391: final ComboBoxItemPair datacon = (ComboBoxItemPair) data
392: .get(i);
393: if (key.equals(datacon.getValue())) {
394: return i;
395: }
396: }
397: return -1;
398: }
399:
400: /**
401: * Removes an entry from the model.
402: *
403: * @param key the key
404: */
405: public void removeDataElement(final Object key) {
406: final int idx = findDataElementIndex(key);
407: if (idx == -1) {
408: return;
409: }
410:
411: data.remove(idx);
412: final ListDataEvent evt = new ListDataEvent(this ,
413: ListDataEvent.INTERVAL_REMOVED, idx, idx);
414: fireListDataEvent(evt);
415: }
416:
417: /**
418: * Adds a new entry to the model.
419: *
420: * @param key the key
421: * @param cbitem the display value.
422: */
423: public void add(final Object key, final Object cbitem) {
424: final ComboBoxItemPair con = new ComboBoxItemPair(key, cbitem);
425: data.add(con);
426: final ListDataEvent evt = new ListDataEvent(this ,
427: ListDataEvent.INTERVAL_ADDED, data.size() - 2, data
428: .size() - 2);
429: fireListDataEvent(evt);
430: }
431:
432: /**
433: * Removes all entries from the model.
434: */
435: public void clear() {
436: final int size = getSize();
437: data.clear();
438: final ListDataEvent evt = new ListDataEvent(this ,
439: ListDataEvent.INTERVAL_REMOVED, 0, size - 1);
440: fireListDataEvent(evt);
441: }
442:
443: }
|