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.5 2005/10/18 13:18:34 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) for every 051 * entry in the model. 052 * <p/> 053 * This class is usefull in all cases, where the public text differs from the internal 054 * view on the data. A separation between presentation data and processing data is a 055 * prequesite for localizing combobox entries. This model does not allow selected 056 * elements, which are not in the list of valid elements. 057 * 058 * @author Thomas Morgner 059 */ 060 public class KeyedComboBoxModel implements ComboBoxModel { 061 062 /** 063 * The internal data carrier to map keys to values and vice versa. 064 */ 065 private static class ComboBoxItemPair { 066 /** The key. */ 067 private Object key; 068 /** The value for the key. */ 069 private Object value; 070 071 /** 072 * Creates a new item pair for the given key and value. The value 073 * can be changed later, if needed. 074 * 075 * @param key the key 076 * @param value the value 077 */ 078 public ComboBoxItemPair(final Object key, final Object value) { 079 this.key = key; 080 this.value = value; 081 } 082 083 /** 084 * Returns the key. 085 * @return the key. 086 */ 087 public Object getKey() { 088 return key; 089 } 090 091 /** 092 * Returns the value. 093 * @return the value for this key. 094 */ 095 public Object getValue() { 096 return value; 097 } 098 099 /** 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 }