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