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.5 2005/10/18 13:18:34 mungady 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) for every
051     * entry in the model.
052     * <p/>
053     * This class is usefull in all cases, where the public text differs from the internal
054     * view on the data. A separation between presentation data and processing data is a
055     * prequesite for localizing combobox entries. This model does not allow selected
056     * elements, which are not in the list of valid elements.
057     *
058     * @author Thomas Morgner
059     */
060    public class KeyedComboBoxModel implements ComboBoxModel {
061    
062        /**
063         * The internal data carrier to map keys to values and vice versa.
064         */
065        private static class ComboBoxItemPair {
066            /** The key. */
067            private Object key;
068            /** The value for the key. */
069            private Object value;
070    
071            /**
072             * Creates a new item pair for the given key and value. The value
073             * can be changed later, if needed.
074             *
075             * @param key the key
076             * @param value the value
077             */
078            public ComboBoxItemPair(final Object key, final Object value) {
079                this.key = key;
080                this.value = value;
081            }
082    
083            /**
084             * Returns the key.
085             * @return the key.
086             */
087            public Object getKey() {
088                return key;
089            }
090    
091            /**
092             * Returns the value.
093             * @return the value for this key.
094             */
095            public Object getValue() {
096                return value;
097            }
098    
099            /**
100             * Redefines the value stored for that key.
101             * @param value the new value.
102             */
103            public void setValue(final Object value) {
104                this.value = value;
105            }
106        }
107    
108        /** The index of the selected item. */
109        private int selectedItem;
110        /** The data (contains ComboBoxItemPairs). */
111        private ArrayList data;
112        /** The listeners. */
113        private ArrayList listdatalistener;
114        /** The cached listeners as array. */
115        private transient ListDataListener[] tempListeners;
116    
117        /**
118         * Creates a new keyed combobox model.
119         */
120        public KeyedComboBoxModel() {
121            data = new ArrayList();
122            listdatalistener = new ArrayList();
123        }
124    
125        /**
126         * Creates a new keyed combobox model for the given keys and values.
127         * Keys and values must have the same number of items.
128         *
129         * @param keys the keys
130         * @param values the values
131         */
132        public KeyedComboBoxModel(final Object[] keys, final Object[] values) {
133            this();
134            setData(keys, values);
135        }
136    
137        /**
138         * Replaces the data in this combobox model. The number of keys must be equals to the
139         * number of values.
140         *
141         * @param keys the keys
142         * @param values the values
143         */
144        public void setData(final Object[] keys, final Object[] values) {
145            if (values.length != keys.length) {
146                throw new IllegalArgumentException("Values and text must have the same length.");
147            }
148    
149            data.clear();
150            data.ensureCapacity(keys.length);
151    
152            for (int i = 0; i < values.length; i++) {
153                add(keys[i], values[i]);
154            }
155    
156            selectedItem = -1;
157            final ListDataEvent evt = new ListDataEvent
158                (this, ListDataEvent.CONTENTS_CHANGED, 0, data.size() - 1);
159            fireListDataEvent(evt);
160        }
161    
162        /**
163         * Notifies all registered list data listener of the given event.
164         *
165         * @param evt the event.
166         */
167        protected synchronized void fireListDataEvent(final ListDataEvent evt) {
168            if (tempListeners == null) {
169                tempListeners = (ListDataListener[]) listdatalistener.toArray
170                    (new ListDataListener[listdatalistener.size()]);
171            }
172            for (int i = 0; i < tempListeners.length; i++) {
173                final ListDataListener l = tempListeners[i];
174                l.contentsChanged(evt);
175            }
176        }
177    
178        /**
179         * Returns the selected item.
180         *
181         * @return The selected item or <code>null</code> if there is no selection
182         */
183        public Object getSelectedItem() {
184            if (selectedItem >= data.size()) {
185                return null;
186            }
187    
188            if (selectedItem < 0) {
189                return null;
190            }
191    
192            final ComboBoxItemPair item = (ComboBoxItemPair) data.get(selectedItem);
193            return item.getValue();
194        }
195    
196        /**
197         * Defines the selected key. If the object is not in the list of values, no item
198         * gets selected.
199         *
200         * @param anItem the new selected item.
201         */
202        public void setSelectedKey(final Object anItem) {
203            if (anItem == null) {
204                selectedItem = -1;
205            }
206            else {
207                final int newSelectedItem = findDataElementIndex(anItem);
208                selectedItem = newSelectedItem;
209            }
210            fireListDataEvent(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, -1, -1));
211        }
212    
213        /**
214         * Set the selected item. The implementation of this  method should notify all
215         * registered <code>ListDataListener</code>s that the contents have changed.
216         *
217         * @param anItem the list object to select or <code>null</code> to clear the selection
218         */
219        public void setSelectedItem(final Object anItem) {
220            if (anItem == null) {
221                selectedItem = -1;
222            }
223            else {
224                final int newSelectedItem = findElementIndex(anItem);
225                selectedItem = newSelectedItem;
226            }
227            fireListDataEvent(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, -1, -1));
228        }
229    
230        /**
231         * Adds a listener to the list that's notified each time a change to the data model
232         * occurs.
233         *
234         * @param l the <code>ListDataListener</code> to be added
235         */
236        public synchronized void addListDataListener(final ListDataListener l) {
237            listdatalistener.add(l);
238            tempListeners = null;
239        }
240    
241        /**
242         * Returns the value at the specified index.
243         *
244         * @param index the requested index
245         * @return the value at <code>index</code>
246         */
247        public Object getElementAt(final int index) {
248            if (index >= data.size()) {
249                return null;
250            }
251    
252            final ComboBoxItemPair datacon = (ComboBoxItemPair) data.get(index);
253            if (datacon == null) {
254                return null;
255            }
256            return datacon.getValue();
257        }
258    
259        /**
260         * Returns the key from the given index.
261         *
262         * @param index the index of the key.
263         * @return the the key at the specified index.
264         */
265        public Object getKeyAt(final int index) {
266            if (index >= data.size()) {
267                return null;
268            }
269    
270            if (index < 0) {
271                return null;
272            }
273    
274            final ComboBoxItemPair datacon = (ComboBoxItemPair) data.get(index);
275            if (datacon == null) {
276                return null;
277            }
278            return datacon.getKey();
279        }
280    
281        /**
282         * Returns the selected data element or null if none is set.
283         *
284         * @return the selected data element.
285         */
286        public Object getSelectedKey() {
287            return getKeyAt(selectedItem);
288        }
289    
290        /**
291         * Returns the length of the list.
292         *
293         * @return the length of the list
294         */
295        public int getSize() {
296            return data.size();
297        }
298    
299        /**
300         * Removes a listener from the list that's notified each time a change to the data model
301         * occurs.
302         *
303         * @param l the <code>ListDataListener</code> to be removed
304         */
305        public void removeListDataListener(final ListDataListener l) {
306            listdatalistener.remove(l);
307            tempListeners = null;
308        }
309    
310        /**
311         * Searches an element by its data value. This method is called by the setSelectedItem
312         * method and returns the first occurence of the element.
313         *
314         * @param anItem the item
315         * @return the index of the item or -1 if not found.
316         */
317        private int findDataElementIndex(final Object anItem) {
318            if (anItem == null) {
319                throw new NullPointerException("Item to find must not be null");
320            }
321    
322            for (int i = 0; i < data.size(); i++) {
323                final ComboBoxItemPair datacon = (ComboBoxItemPair) data.get(i);
324                if (anItem.equals(datacon.getKey())) {
325                    return i;
326                }
327            }
328            return -1;
329        }
330    
331        /**
332         * Tries to find the index of element with the given key. The key must not be null.
333         *
334         * @param key the key for the element to be searched.
335         * @return the index of the key, or -1 if not found.
336         */
337        public int findElementIndex(final Object key) {
338            if (key == null) {
339                throw new NullPointerException("Item to find must not be null");
340            }
341    
342            for (int i = 0; i < data.size(); i++) {
343                final ComboBoxItemPair datacon = (ComboBoxItemPair) data.get(i);
344                if (key.equals(datacon.getValue())) {
345                    return i;
346                }
347            }
348            return -1;
349        }
350    
351        /**
352         * Removes an entry from the model.
353         *
354         * @param key the key
355         */
356        public void removeDataElement(final Object key) {
357            final int idx = findDataElementIndex(key);
358            if (idx == -1) {
359                return;
360            }
361    
362            data.remove(idx);
363            final ListDataEvent evt = new ListDataEvent
364                (this, ListDataEvent.INTERVAL_REMOVED, idx, idx);
365            fireListDataEvent(evt);
366        }
367    
368        /**
369         * Adds a new entry to the model.
370         *
371         * @param key    the key
372         * @param cbitem the display value.
373         */
374        public void add(final Object key, final Object cbitem) {
375            final ComboBoxItemPair con = new ComboBoxItemPair(key, cbitem);
376            data.add(con);
377            final ListDataEvent evt = new ListDataEvent
378                (this, ListDataEvent.INTERVAL_ADDED, data.size() - 2, data.size() - 2);
379            fireListDataEvent(evt);
380        }
381    
382        /**
383         * Removes all entries from the model.
384         */
385        public void clear() {
386            final int size = getSize();
387            data.clear();
388            final ListDataEvent evt = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, 0, size - 1);
389            fireListDataEvent(evt);
390        }
391    
392    }