Source for org.jfree.ui.KeyedComboBoxModel

   1: /* ========================================================================
   2:  * JCommon : a free general purpose class library for the Java(tm) platform
   3:  * ========================================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  * 
   7:  * Project Info:  http://www.jfree.org/jcommon/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ------------------
  28:  * KeyedComboBoxModel.java
  29:  * ------------------
  30:  * (C) Copyright 2004, by Thomas Morgner and Contributors.
  31:  *
  32:  * Original Author:  Thomas Morgner;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * $Id: KeyedComboBoxModel.java,v 1.5 2005/10/18 13:18:34 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 07-Jun-2004 : Added JCommon header (DG);
  40:  *
  41:  */
  42: package org.jfree.ui;
  43: 
  44: import java.util.ArrayList;
  45: import javax.swing.ComboBoxModel;
  46: import javax.swing.event.ListDataEvent;
  47: import javax.swing.event.ListDataListener;
  48: 
  49: /**
  50:  * The KeyedComboBox model allows to define an internal key (the data element) for every
  51:  * entry in the model.
  52:  * <p/>
  53:  * This class is usefull in all cases, where the public text differs from the internal
  54:  * view on the data. A separation between presentation data and processing data is a
  55:  * prequesite for localizing combobox entries. This model does not allow selected
  56:  * elements, which are not in the list of valid elements.
  57:  *
  58:  * @author Thomas Morgner
  59:  */
  60: public class KeyedComboBoxModel implements ComboBoxModel {
  61: 
  62:     /**
  63:      * The internal data carrier to map keys to values and vice versa.
  64:      */
  65:     private static class ComboBoxItemPair {
  66:         /** The key. */
  67:         private Object key;
  68:         /** The value for the key. */
  69:         private Object value;
  70: 
  71:         /**
  72:          * Creates a new item pair for the given key and value. The value
  73:          * can be changed later, if needed.
  74:          *
  75:          * @param key the key
  76:          * @param value the value
  77:          */
  78:         public ComboBoxItemPair(final Object key, final Object value) {
  79:             this.key = key;
  80:             this.value = value;
  81:         }
  82: 
  83:         /**
  84:          * Returns the key.
  85:          * @return the key.
  86:          */
  87:         public Object getKey() {
  88:             return key;
  89:         }
  90: 
  91:         /**
  92:          * Returns the value.
  93:          * @return the value for this key.
  94:          */
  95:         public Object getValue() {
  96:             return value;
  97:         }
  98: 
  99:         /**
 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: }