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 }