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.8 2008/09/10 09:26:11 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) 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 this.key; 099 } 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 }