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.8 2008/09/10 09:26:11 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)
  51:  * for every entry in the model.
  52:  * <p/>
  53:  * This class is usefull in all cases, where the public text differs from the
  54:  * internal view on the data. A separation between presentation data and
  55:  * processing data is a prequesite for localizing combobox entries. This model
  56:  * does not allow selected elements, which are not in the list of valid
  57:  * elements.
  58:  *
  59:  * @author Thomas Morgner
  60:  */
  61: public class KeyedComboBoxModel implements ComboBoxModel
  62: {
  63: 
  64:   /**
  65:    * The internal data carrier to map keys to values and vice versa.
  66:    */
  67:   private static class ComboBoxItemPair
  68:   {
  69:     /**
  70:      * The key.
  71:      */
  72:     private Object key;
  73:     /**
  74:      * The value for the key.
  75:      */
  76:     private Object value;
  77: 
  78:     /**
  79:      * Creates a new item pair for the given key and value. The value can be
  80:      * changed later, if needed.
  81:      *
  82:      * @param key   the key
  83:      * @param value the value
  84:      */
  85:     public ComboBoxItemPair(final Object key, final Object value)
  86:     {
  87:       this.key = key;
  88:       this.value = value;
  89:     }
  90: 
  91:     /**
  92:      * Returns the key.
  93:      *
  94:      * @return the key.
  95:      */
  96:     public Object getKey()
  97:     {
  98:       return this.key;
  99:     }
 100: 
 101:     /**
 102:      * Returns the value.
 103:      *
 104:      * @return the value for this key.
 105:      */
 106:     public Object getValue()
 107:     {
 108:       return this.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:     this.data = new ArrayList();
 147:     this.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:     this.data.clear();
 178:     this.data.ensureCapacity(keys.length);
 179: 
 180:     for (int i = 0; i < values.length; i++)
 181:     {
 182:       add(keys[i], values[i]);
 183:     }
 184: 
 185:     this.selectedItemIndex = -1;
 186:     final ListDataEvent evt = new ListDataEvent
 187:         (this, ListDataEvent.CONTENTS_CHANGED, 0, this.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 (this.tempListeners == null)
 199:     {
 200:         this.tempListeners = (ListDataListener[]) this.listdatalistener.toArray
 201:           (new ListDataListener[this.listdatalistener.size()]);
 202:     }
 203: 
 204:     final ListDataListener[] listeners = this.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 this.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:         this.selectedItemIndex = -1;
 233:         this.selectedItemValue = null;
 234:     }
 235:     else
 236:     {
 237:       final int newSelectedItem = findDataElementIndex(anItem);
 238:       if (newSelectedItem == -1)
 239:       {
 240:           this.selectedItemIndex = -1;
 241:           this.selectedItemValue = null;
 242:       }
 243:       else
 244:       {
 245:           this.selectedItemIndex = newSelectedItem;
 246:           this.selectedItemValue = getElementAt(this.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:         this.selectedItemIndex = -1;
 265:         this.selectedItemValue = null;
 266:     }
 267:     else
 268:     {
 269:       final int newSelectedItem = findElementIndex(anItem);
 270:       if (newSelectedItem == -1)
 271:       {
 272:         if (isAllowOtherValue())
 273:         {
 274:             this.selectedItemIndex = -1;
 275:             this.selectedItemValue = anItem;
 276:         }
 277:         else
 278:         {
 279:             this.selectedItemIndex = -1;
 280:           this.selectedItemValue = null;
 281:         }
 282:       }
 283:       else
 284:       {
 285:           this.selectedItemIndex = newSelectedItem;
 286:           this.selectedItemValue = getElementAt(this.selectedItemIndex);
 287:       }
 288:     }
 289:     fireListDataEvent(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, -1, -1));
 290:   }
 291: 
 292:   private boolean isAllowOtherValue()
 293:   {
 294:     return this.allowOtherValue;
 295:   }
 296: 
 297:   /**
 298:    * @param allowOtherValue
 299:    */
 300:   public void setAllowOtherValue(final boolean allowOtherValue)
 301:   {
 302:     this.allowOtherValue = allowOtherValue;
 303:   }
 304: 
 305:   /**
 306:    * Adds a listener to the list that's notified each time a change to the data
 307:    * model occurs.
 308:    *
 309:    * @param l the <code>ListDataListener</code> to be added
 310:    */
 311:   public synchronized void addListDataListener(final ListDataListener l)
 312:   {
 313:     if (l == null)
 314:     {
 315:       throw new NullPointerException();
 316:     }
 317:     this.listdatalistener.add(l);
 318:     this.tempListeners = null;
 319:   }
 320: 
 321:   /**
 322:    * Returns the value at the specified index.
 323:    *
 324:    * @param index the requested index
 325:    * @return the value at <code>index</code>
 326:    */
 327:   public Object getElementAt(final int index)
 328:   {
 329:     if (index >= this.data.size())
 330:     {
 331:       return null;
 332:     }
 333: 
 334:     final ComboBoxItemPair datacon = (ComboBoxItemPair) this.data.get(index);
 335:     if (datacon == null)
 336:     {
 337:       return null;
 338:     }
 339:     return datacon.getValue();
 340:   }
 341: 
 342:   /**
 343:    * Returns the key from the given index.
 344:    *
 345:    * @param index the index of the key.
 346:    * @return the the key at the specified index.
 347:    */
 348:   public Object getKeyAt(final int index)
 349:   {
 350:     if (index >= this.data.size())
 351:     {
 352:       return null;
 353:     }
 354: 
 355:     if (index < 0)
 356:     {
 357:       return null;
 358:     }
 359: 
 360:     final ComboBoxItemPair datacon = (ComboBoxItemPair) this.data.get(index);
 361:     if (datacon == null)
 362:     {
 363:       return null;
 364:     }
 365:     return datacon.getKey();
 366:   }
 367: 
 368:   /**
 369:    * Returns the selected data element or null if none is set.
 370:    *
 371:    * @return the selected data element.
 372:    */
 373:   public Object getSelectedKey()
 374:   {
 375:     return getKeyAt(this.selectedItemIndex);
 376:   }
 377: 
 378:   /**
 379:    * Returns the length of the list.
 380:    *
 381:    * @return the length of the list
 382:    */
 383:   public int getSize()
 384:   {
 385:     return this.data.size();
 386:   }
 387: 
 388:   /**
 389:    * Removes a listener from the list that's notified each time a change to
 390:    * the data model occurs.
 391:    *
 392:    * @param l the <code>ListDataListener</code> to be removed
 393:    */
 394:   public void removeListDataListener(final ListDataListener l)
 395:   {
 396:       this.listdatalistener.remove(l);
 397:       this.tempListeners = null;
 398:   }
 399: 
 400:   /**
 401:    * Searches an element by its data value. This method is called by the
 402:    * setSelectedItem method and returns the first occurence of the element.
 403:    *
 404:    * @param anItem the item
 405:    * @return the index of the item or -1 if not found.
 406:    */
 407:   private int findDataElementIndex(final Object anItem)
 408:   {
 409:     if (anItem == null)
 410:     {
 411:       throw new NullPointerException("Item to find must not be null");
 412:     }
 413: 
 414:     for (int i = 0; i < this.data.size(); i++)
 415:     {
 416:       final ComboBoxItemPair datacon = (ComboBoxItemPair) this.data.get(i);
 417:       if (anItem.equals(datacon.getKey()))
 418:       {
 419:         return i;
 420:       }
 421:     }
 422:     return -1;
 423:   }
 424: 
 425:   /**
 426:    * Tries to find the index of element with the given key. The key must not
 427:    * be null.
 428:    *
 429:    * @param key the key for the element to be searched.
 430:    * @return the index of the key, or -1 if not found.
 431:    */
 432:   public int findElementIndex(final Object key)
 433:   {
 434:     if (key == null)
 435:     {
 436:       throw new NullPointerException("Item to find must not be null");
 437:     }
 438: 
 439:     for (int i = 0; i < this.data.size(); i++)
 440:     {
 441:       final ComboBoxItemPair datacon = (ComboBoxItemPair) this.data.get(i);
 442:       if (key.equals(datacon.getValue()))
 443:       {
 444:         return i;
 445:       }
 446:     }
 447:     return -1;
 448:   }
 449: 
 450:   /**
 451:    * Removes an entry from the model.
 452:    *
 453:    * @param key the key
 454:    */
 455:   public void removeDataElement(final Object key)
 456:   {
 457:     final int idx = findDataElementIndex(key);
 458:     if (idx == -1)
 459:     {
 460:       return;
 461:     }
 462: 
 463:     this.data.remove(idx);
 464:     final ListDataEvent evt = new ListDataEvent
 465:         (this, ListDataEvent.INTERVAL_REMOVED, idx, idx);
 466:     fireListDataEvent(evt);
 467:   }
 468: 
 469:   /**
 470:    * Adds a new entry to the model.
 471:    *
 472:    * @param key    the key
 473:    * @param cbitem the display value.
 474:    */
 475:   public void add(final Object key, final Object cbitem)
 476:   {
 477:     final ComboBoxItemPair con = new ComboBoxItemPair(key, cbitem);
 478:     this.data.add(con);
 479:     final ListDataEvent evt = new ListDataEvent
 480:         (this, ListDataEvent.INTERVAL_ADDED, this.data.size() - 2, this.data.size() - 2);
 481:     fireListDataEvent(evt);
 482:   }
 483: 
 484:   /**
 485:    * Removes all entries from the model.
 486:    */
 487:   public void clear()
 488:   {
 489:     final int size = getSize();
 490:     this.data.clear();
 491:     final ListDataEvent evt = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, 0, size - 1);
 492:     fireListDataEvent(evt);
 493:   }
 494: 
 495: }