    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.7 2007/10/19 13:05:51 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
    204        final ListDataListener[] listeners = tempListeners;
    205        for (int i = 0; i < listeners.length; i++)
    206        {
    207          final ListDataListener l = listeners[i];
    208          l.contentsChanged(evt);
    209        }
    210      }
    211
    212      /**
    213       * Returns the selected item.
    214       *
    215       * @return The selected item or <code>null</code> if there is no selection
    216       */
    217      public Object getSelectedItem()
    218      {
    219        return selectedItemValue;
    220      }
    221
    222      /**
    223       * Defines the selected key. If the object is not in the list of values, no
    224       * item gets selected.
    225       *
    226       * @param anItem the new selected item.
    227       */
    228      public void setSelectedKey(final Object anItem)
    229      {
    230        if (anItem == null)
    231        {
    232          selectedItemIndex = -1;
    233          selectedItemValue = null;
    234        }
    235        else
    236        {
    237          final int newSelectedItem = findDataElementIndex(anItem);
    238          if (newSelectedItem == -1)
    239          {
    240            selectedItemIndex = -1;
    241            selectedItemValue = null;
    242          }
    243          else
    244          {
    245            selectedItemIndex = newSelectedItem;
    246            selectedItemValue = getElementAt(selectedItemIndex);
    247          }
    248        }
    249        fireListDataEvent(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, -1, -1));
    250      }
    251
    252      /**
    253       * Set the selected item. The implementation of this  method should notify
    254       * all registered <code>ListDataListener</code>s that the contents have
    255       * changed.
    256       *
    257       * @param anItem the list object to select or <code>null</code> to clear the
    258       *               selection
    259       */
    260      public void setSelectedItem(final Object anItem)
    261      {
    262        if (anItem == null)
    263        {
    264          selectedItemIndex = -1;
    265          selectedItemValue = null;
    266        }
    267        else
    268        {
    269          final int newSelectedItem = findElementIndex(anItem);
    270          if (newSelectedItem == -1)
    271          {
    272            if (isAllowOtherValue())
    273            {
    274              selectedItemIndex = -1;
    275              selectedItemValue = anItem;
    276            }
    277            else
    278            {
    279              selectedItemIndex = -1;
    280              selectedItemValue = null;
    281            }
    282          }
    283          else
    284          {
    285            selectedItemIndex = newSelectedItem;
    286            selectedItemValue = getElementAt(selectedItemIndex);
    287          }
    288        }
    289        fireListDataEvent(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, -1, -1));
    290      }
    291
    292      private boolean isAllowOtherValue()
    293      {
    294        return allowOtherValue;
    295      }
    296
    297      public void setAllowOtherValue(final boolean allowOtherValue)
    298      {
    299        this.allowOtherValue = allowOtherValue;
    300      }
    301
    302      /**
    303       * Adds a listener to the list that's notified each time a change to the data
    304       * model occurs.
    305       *
    306       * @param l the <code>ListDataListener</code> to be added
    307       */
    308      public synchronized void addListDataListener(final ListDataListener l)
    309      {
    310        if (l == null)
    311        {
    312          throw new NullPointerException();
    313        }
    314        listdatalistener.add(l);
    315        tempListeners = null;
    316      }
    317
    318      /**
    319       * Returns the value at the specified index.
    320       *
    321       * @param index the requested index
    322       * @return the value at <code>index</code>
    323       */
    324      public Object getElementAt(final int index)
    325      {
    326        if (index >= data.size())
    327        {
    328          return null;
    329        }
    330
    331        final ComboBoxItemPair datacon = (ComboBoxItemPair) data.get(index);
    332        if (datacon == null)
    333        {
    334          return null;
    335        }
    336        return datacon.getValue();
    337      }
    338
    339      /**
    340       * Returns the key from the given index.
    341       *
    342       * @param index the index of the key.
    343       * @return the the key at the specified index.
    344       */
    345      public Object getKeyAt(final int index)
    346      {
    347        if (index >= data.size())
    348        {
    349          return null;
    350        }
    351
    352        if (index < 0)
    353        {
    354          return null;
    355        }
    356
    357        final ComboBoxItemPair datacon = (ComboBoxItemPair) data.get(index);
    358        if (datacon == null)
    359        {
    360          return null;
    361        }
    362        return datacon.getKey();
    363      }
    364
    365      /**
    366       * Returns the selected data element or null if none is set.
    367       *
    368       * @return the selected data element.
    369       */
    370      public Object getSelectedKey()
    371      {
    372        return getKeyAt(selectedItemIndex);
    373      }
    374
    375      /**
    376       * Returns the length of the list.
    377       *
    378       * @return the length of the list
    379       */
    380      public int getSize()
    381      {
    382        return data.size();
    383      }
    384
    385      /**
    386       * Removes a listener from the list that's notified each time a change to
    387       * the data model occurs.
    388       *
    389       * @param l the <code>ListDataListener</code> to be removed
    390       */
    391      public void removeListDataListener(final ListDataListener l)
    392      {
    393        listdatalistener.remove(l);
    394        tempListeners = null;
    395      }
    396
    397      /**
    398       * Searches an element by its data value. This method is called by the
    399       * setSelectedItem method and returns the first occurence of the element.
    400       *
    401       * @param anItem the item
    402       * @return the index of the item or -1 if not found.
    403       */
    404      private int findDataElementIndex(final Object anItem)
    405      {
    406        if (anItem == null)
    407        {
    408          throw new NullPointerException("Item to find must not be null");
    409        }
    410
    411        for (int i = 0; i < data.size(); i++)
    412        {
    413          final ComboBoxItemPair datacon = (ComboBoxItemPair) data.get(i);
    414          if (anItem.equals(datacon.getKey()))
    415          {
    416            return i;
    417          }
    418        }
    419        return -1;
    420      }
    421
    422      /**
    423       * Tries to find the index of element with the given key. The key must not
    424       * be null.
    425       *
    426       * @param key the key for the element to be searched.
    427       * @return the index of the key, or -1 if not found.
    428       */
    429      public int findElementIndex(final Object key)
    430      {
    431        if (key == null)
    432        {
    433          throw new NullPointerException("Item to find must not be null");
    434        }
    435
    436        for (int i = 0; i < data.size(); i++)
    437        {
    438          final ComboBoxItemPair datacon = (ComboBoxItemPair) data.get(i);
    439          if (key.equals(datacon.getValue()))
    440          {
    441            return i;
    442          }
    443        }
    444        return -1;
    445      }
    446
    447      /**
    448       * Removes an entry from the model.
    449       *
    450       * @param key the key
    451       */
    452      public void removeDataElement(final Object key)
    453      {
    454        final int idx = findDataElementIndex(key);
    455        if (idx == -1)
    456        {
    457          return;
    458        }
    459
    460        data.remove(idx);
    461        final ListDataEvent evt = new ListDataEvent
    462            (this, ListDataEvent.INTERVAL_REMOVED, idx, idx);
    463        fireListDataEvent(evt);
    464      }
    465
    466      /**
    467       * Adds a new entry to the model.
    468       *
    469       * @param key    the key
    470       * @param cbitem the display value.
    471       */
    472      public void add(final Object key, final Object cbitem)
    473      {
    474        final ComboBoxItemPair con = new ComboBoxItemPair(key, cbitem);
    475        data.add(con);
    476        final ListDataEvent evt = new ListDataEvent
    477            (this, ListDataEvent.INTERVAL_ADDED, data.size() - 2, data.size() - 2);
    478        fireListDataEvent(evt);
    479      }
    480
    481      /**
    482       * Removes all entries from the model.
    483       */
    484      public void clear()
    485      {
    486        final int size = getSize();
    487        data.clear();
    488        final ListDataEvent evt = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, 0, size - 1);
    489        fireListDataEvent(evt);
    490      }
    491
    492    }