    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    }