    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      /**
    065       * The internal data carrier to map keys to values and vice versa.
    066       */
    067      private static class ComboBoxItemPair
    068      {
    069        /**
    070         * The key.
    071         */
    072        private Object key;
    073        /**
    074         * The value for the key.
    075         */
    076        private Object value;
    077
    078        /**
    079         * Creates a new item pair for the given key and value. The value can be
    080         * changed later, if needed.
    081         *
    082         * @param key   the key
    083         * @param value the value
    084         */
    085        public ComboBoxItemPair(final Object key, final Object value)
    086        {
    087          this.key = key;
    088          this.value = value;
    089        }
    090
    091        /**
    092         * Returns the key.
    093         *
    094         * @return the key.
    095         */
    096        public Object getKey()
    097        {
    098          return key;
    099        }
    100
    101        /**
    102         * Returns the value.
    103         *
    104         * @return the value for this key.
    105         */
    106        public Object getValue()
    107        {
    108          return value;
    109        }
    110
    111        /**
    112         * Redefines the value stored for that key.
    113         *
    114         * @param value the new value.
    115         */
    116        public void setValue(final Object value)
    117        {
    118          this.value = value;
    119        }
    120      }
    121
    122      /**
    123       * The index of the selected item.
    124       */
    125      private int selectedItemIndex;
    126      private Object selectedItemValue;
    127      /**
    128       * The data (contains ComboBoxItemPairs).
    129       */
    130      private ArrayList data;
    131      /**
    132       * The listeners.
    133       */
    134      private ArrayList listdatalistener;
    135      /**
    136       * The cached listeners as array.
    137       */
    138      private transient ListDataListener[] tempListeners;
    139      private boolean allowOtherValue;
    140
    141      /**
    142       * Creates a new keyed combobox model.
    143       */
    144      public KeyedComboBoxModel()
    145      {
    146        data = new ArrayList();
    147        listdatalistener = new ArrayList();
    148      }
    149
    150      /**
    151       * Creates a new keyed combobox model for the given keys and values. Keys
    152       * and values must have the same number of items.
    153       *
    154       * @param keys   the keys
    155       * @param values the values
    156       */
    157      public KeyedComboBoxModel(final Object[] keys, final Object[] values)
    158      {
    159        this();
    160        setData(keys, values);
    161      }
    162
    163      /**
    164       * Replaces the data in this combobox model. The number of keys must be
    165       * equals to the number of values.
    166       *
    167       * @param keys   the keys
    168       * @param values the values
    169       */
    170      public void setData(final Object[] keys, final Object[] values)
    171      {
    172        if (values.length != keys.length)
    173        {
    174          throw new IllegalArgumentException("Values and text must have the same length.");
    175        }
    176
    177        data.clear();
    178        data.ensureCapacity(keys.length);
    179
    180        for (int i = 0; i < values.length; i++)
    181        {
    182          add(keys[i], values[i]);
    183        }
    184
    185        selectedItemIndex = -1;
    186        final ListDataEvent evt = new ListDataEvent
    187            (this, ListDataEvent.CONTENTS_CHANGED, 0, data.size() - 1);
    188        fireListDataEvent(evt);
    189      }
    190
    191      /**
    192       * Notifies all registered list data listener of the given event.
    193       *
    194       * @param evt the event.
    195       */
    196      protected synchronized void fireListDataEvent(final ListDataEvent evt)
    197      {
    198        if (tempListeners == null)
    199        {
    200          tempListeners = (ListDataListener[]) listdatalistener.toArray
    201              (new ListDataListener[listdatalistener.size()]);
    202        }
    203        for (int i = 0; i < tempListeners.length; i++)
    204        {
    205          final ListDataListener l = tempListeners[i];
    206          l.contentsChanged(evt);
    207        }
    208      }
    209
    210      /**
    211       * Returns the selected item.
    212       *
    213       * @return The selected item or <code>null</code> if there is no selection
    214       */
    215      public Object getSelectedItem()
    216      {
    217        return selectedItemValue;
    218      }
    219
    220      /**
    221       * Defines the selected key. If the object is not in the list of values, no
    222       * item gets selected.
    223       *
    224       * @param anItem the new selected item.
    225       */
    226      public void setSelectedKey(final Object anItem)
    227      {
    228        if (anItem == null)
    229        {
    230          selectedItemIndex = -1;
    231          selectedItemValue = null;
    232        }
    233        else
    234        {
    235          final int newSelectedItem = findDataElementIndex(anItem);
    236          if (newSelectedItem == -1)
    237          {
    238            selectedItemIndex = -1;
    239            selectedItemValue = null;
    240          }
    241          else
    242          {
    243            selectedItemIndex = newSelectedItem;
    244            selectedItemValue = getElementAt(selectedItemIndex);
    245          }
    246        }
    247        fireListDataEvent(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, -1, -1));
    248      }
    249
    250      /**
    251       * Set the selected item. The implementation of this  method should notify
    252       * all registered <code>ListDataListener</code>s that the contents have
    253       * changed.
    254       *
    255       * @param anItem the list object to select or <code>null</code> to clear the
    256       *               selection
    257       */
    258      public void setSelectedItem(final Object anItem)
    259      {
    260        if (anItem == null)
    261        {
    262          selectedItemIndex = -1;
    263          selectedItemValue = null;
    264        }
    265        else
    266        {
    267          final int newSelectedItem = findElementIndex(anItem);
    268          if (newSelectedItem == -1)
    269          {
    270            if (isAllowOtherValue())
    271            {
    272              selectedItemIndex = -1;
    273              selectedItemValue = anItem;
    274            }
    275            else
    276            {
    277              selectedItemIndex = -1;
    278              selectedItemValue = null;
    279            }
    280          }
    281          else
    282          {
    283            selectedItemIndex = newSelectedItem;
    284            selectedItemValue = getElementAt(selectedItemIndex);
    285          }
    286        }
    287        fireListDataEvent(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, -1, -1));
    288      }
    289
    290      private boolean isAllowOtherValue()
    291      {
    292        return allowOtherValue;
    293      }
    294
    295      public void setAllowOtherValue(final boolean allowOtherValue)
    296      {
    297        this.allowOtherValue = allowOtherValue;
    298      }
    299
    300      /**
    301       * Adds a listener to the list that's notified each time a change to the data
    302       * model occurs.
    303       *
    304       * @param l the <code>ListDataListener</code> to be added
    305       */
    306      public synchronized void addListDataListener(final ListDataListener l)
    307      {
    308        listdatalistener.add(l);
    309        tempListeners = null;
    310      }
    311
    312      /**
    313       * Returns the value at the specified index.
    314       *
    315       * @param index the requested index
    316       * @return the value at <code>index</code>
    317       */
    318      public Object getElementAt(final int index)
    319      {
    320        if (index >= data.size())
    321        {
    322          return null;
    323        }
    324
    325        final ComboBoxItemPair datacon = (ComboBoxItemPair) data.get(index);
    326        if (datacon == null)
    327        {
    328          return null;
    329        }
    330        return datacon.getValue();
    331      }
    332
    333      /**
    334       * Returns the key from the given index.
    335       *
    336       * @param index the index of the key.
    337       * @return the the key at the specified index.
    338       */
    339      public Object getKeyAt(final int index)
    340      {
    341        if (index >= data.size())
    342        {
    343          return null;
    344        }
    345
    346        if (index < 0)
    347        {
    348          return null;
    349        }
    350
    351        final ComboBoxItemPair datacon = (ComboBoxItemPair) data.get(index);
    352        if (datacon == null)
    353        {
    354          return null;
    355        }
    356        return datacon.getKey();
    357      }
    358
    359      /**
    360       * Returns the selected data element or null if none is set.
    361       *
    362       * @return the selected data element.
    363       */
    364      public Object getSelectedKey()
    365      {
    366        return getKeyAt(selectedItemIndex);
    367      }
    368
    369      /**
    370       * Returns the length of the list.
    371       *
    372       * @return the length of the list
    373       */
    374      public int getSize()
    375      {
    376        return data.size();
    377      }
    378
    379      /**
    380       * Removes a listener from the list that's notified each time a change to
    381       * the data model occurs.
    382       *
    383       * @param l the <code>ListDataListener</code> to be removed
    384       */
    385      public void removeListDataListener(final ListDataListener l)
    386      {
    387        listdatalistener.remove(l);
    388        tempListeners = null;
    389      }
    390
    391      /**
    392       * Searches an element by its data value. This method is called by the
    393       * setSelectedItem method and returns the first occurence of the element.
    394       *
    395       * @param anItem the item
    396       * @return the index of the item or -1 if not found.
    397       */
    398      private int findDataElementIndex(final Object anItem)
    399      {
    400        if (anItem == null)
    401        {
    402          throw new NullPointerException("Item to find must not be null");
    403        }
    404
    405        for (int i = 0; i < data.size(); i++)
    406        {
    407          final ComboBoxItemPair datacon = (ComboBoxItemPair) data.get(i);
    408          if (anItem.equals(datacon.getKey()))
    409          {
    410            return i;
    411          }
    412        }
    413        return -1;
    414      }
    415
    416      /**
    417       * Tries to find the index of element with the given key. The key must not
    418       * be null.
    419       *
    420       * @param key the key for the element to be searched.
    421       * @return the index of the key, or -1 if not found.
    422       */
    423      public int findElementIndex(final Object key)
    424      {
    425        if (key == null)
    426        {
    427          throw new NullPointerException("Item to find must not be null");
    428        }
    429
    430        for (int i = 0; i < data.size(); i++)
    431        {
    432          final ComboBoxItemPair datacon = (ComboBoxItemPair) data.get(i);
    433          if (key.equals(datacon.getValue()))
    434          {
    435            return i;
    436          }
    437        }
    438        return -1;
    439      }
    440
    441      /**
    442       * Removes an entry from the model.
    443       *
    444       * @param key the key
    445       */
    446      public void removeDataElement(final Object key)
    447      {
    448        final int idx = findDataElementIndex(key);
    449        if (idx == -1)
    450        {
    451          return;
    452        }
    453
    454        data.remove(idx);
    455        final ListDataEvent evt = new ListDataEvent
    456            (this, ListDataEvent.INTERVAL_REMOVED, idx, idx);
    457        fireListDataEvent(evt);
    458      }
    459
    460      /**
    461       * Adds a new entry to the model.
    462       *
    463       * @param key    the key
    464       * @param cbitem the display value.
    465       */
    466      public void add(final Object key, final Object cbitem)
    467      {
    468        final ComboBoxItemPair con = new ComboBoxItemPair(key, cbitem);
    469        data.add(con);
    470        final ListDataEvent evt = new ListDataEvent
    471            (this, ListDataEvent.INTERVAL_ADDED, data.size() - 2, data.size() - 2);
    472        fireListDataEvent(evt);
    473      }
    474
    475      /**
    476       * Removes all entries from the model.
    477       */
    478      public void clear()
    479      {
    480        final int size = getSize();
    481        data.clear();
    482        final ListDataEvent evt = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, 0, size - 1);
    483        fireListDataEvent(evt);
    484      }
    485
    486    }