/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.libraries.designtime.swing;
import javax.swing.*;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import java.util.ArrayList;
/**
* The KeyedComboBox model allows to define an internal key (the data element) for every entry in the model.
* <p/>
* This class is usefull in all cases, where the public text differs from the internal view on the data. A separation
* between presentation data and processing data is a prequesite for localizing combobox entries. This model does not
* allow selected elements, which are not in the list of valid elements.
*
* @author Thomas Morgner
*/
public class KeyedComboBoxModel<K, V> implements ComboBoxModel {
/**
* The internal data carrier to map keys to values and vice versa.
*/
private static class ComboBoxItemPair<K, V> {
/**
* The key.
*/
private K key;
/**
* The value for the key.
*/
private V value;
/**
* Creates a new item pair for the given key and value. The value can be changed later, if needed.
*
* @param key the key
* @param value the value
*/
private ComboBoxItemPair( final K key, final V value ) {
this.key = key;
this.value = value;
}
/**
* Returns the key.
*
* @return the key.
*/
public K getKey() {
return key;
}
/**
* Returns the value.
*
* @return the value for this key.
*/
public V getValue() {
return value;
}
/**
* Redefines the value stored for that key.
*
* @param value the new value.
*/
public void setValue( final V value ) {
this.value = value;
}
}
private int selectedItemIndex;
private V selectedItemValue;
private ArrayList<ComboBoxItemPair<K, V>> data;
private ArrayList<ListDataListener> listdatalistener;
private transient ListDataListener[] tempListeners;
private boolean allowOtherValue;
/**
* Creates a new keyed combobox model.
*/
public KeyedComboBoxModel() {
data = new ArrayList<ComboBoxItemPair<K, V>>();
listdatalistener = new ArrayList<ListDataListener>();
selectedItemIndex = -1;
}
/**
* Creates a new keyed combobox model for the given keys and values. Keys and values must have the same number of
* items.
*
* @param keys the keys
* @param values the values
*/
public KeyedComboBoxModel( final K[] keys, final V[] values ) {
this();
setData( keys, values );
}
/**
* Replaces the data in this combobox model. The number of keys must be equals to the number of values.
*
* @param keys the keys
* @param values the values
*/
public void setData( final K[] keys, final V[] values ) {
if ( values.length != keys.length ) {
throw new IllegalArgumentException( "Values and text must have the same length." );
}
data.clear();
data.ensureCapacity( keys.length );
for ( int i = 0; i < values.length; i++ ) {
add( keys[ i ], values[ i ] );
}
selectedItemIndex = -1;
final ListDataEvent evt = new ListDataEvent
( this, ListDataEvent.CONTENTS_CHANGED, 0, data.size() - 1 );
fireListDataEvent( evt );
}
/**
* Notifies all registered list data listener of the given event.
*
* @param evt the event.
*/
protected synchronized void fireListDataEvent( final ListDataEvent evt ) {
if ( tempListeners == null ) {
tempListeners = listdatalistener.toArray
( new ListDataListener[ listdatalistener.size() ] );
}
final ListDataListener[] listeners = tempListeners;
for ( int i = 0; i < listeners.length; i++ ) {
final ListDataListener l = listeners[ i ];
l.contentsChanged( evt );
}
}
/**
* Returns the selected item.
*
* @return The selected item or <code>null</code> if there is no selection
*/
public V getSelectedItem() {
return selectedItemValue;
}
/**
* Defines the selected key. If the object is not in the list of values, no item gets selected.
*
* @param anItem the new selected item.
*/
public void setSelectedKey( final K anItem ) {
final int oldSelectedItem = this.selectedItemIndex;
final int newSelectedItem = findDataElementIndex( anItem );
if ( newSelectedItem == -1 ) {
selectedItemIndex = -1;
selectedItemValue = null;
} else {
selectedItemIndex = newSelectedItem;
selectedItemValue = getElementAt( selectedItemIndex );
}
if ( oldSelectedItem != this.selectedItemIndex ) {
fireListDataEvent( new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, -1, -1 ) );
}
}
/**
* Set the selected item. The implementation of this method should notify all registered
* <code>ListDataListener</code>s that the contents have changed.
*
* @param anItem the list object to select or <code>null</code> to clear the selection
*/
public final void setSelectedItem( final Object anItem ) {
//noinspection unchecked
setSelectedValue( (V) anItem );
}
public void setSelectedValue( final V anItem ) {
final int oldSelectedItem = this.selectedItemIndex;
final int newSelectedItem = findElementIndex( anItem );
if ( newSelectedItem == -1 ) {
if ( isAllowOtherValue() ) {
selectedItemIndex = -1;
selectedItemValue = anItem;
} else {
selectedItemIndex = -1;
selectedItemValue = null;
}
} else {
selectedItemIndex = newSelectedItem;
selectedItemValue = getElementAt( selectedItemIndex );
}
if ( oldSelectedItem != this.selectedItemIndex ) {
fireListDataEvent( new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, -1, -1 ) );
}
}
private boolean isAllowOtherValue() {
return allowOtherValue;
}
public void setAllowOtherValue( final boolean allowOtherValue ) {
this.allowOtherValue = allowOtherValue;
}
/**
* Adds a listener to the list that's notified each time a change to the data model occurs.
*
* @param l the <code>ListDataListener</code> to be added
*/
public synchronized void addListDataListener( final ListDataListener l ) {
if ( l == null ) {
throw new NullPointerException();
}
listdatalistener.add( l );
tempListeners = null;
}
/**
* Returns the value at the specified index.
*
* @param index the requested index
* @return the value at <code>index</code>
*/
public V getElementAt( final int index ) {
if ( index == -1 || index >= data.size() ) {
return null;
}
final ComboBoxItemPair<K, V> datacon = data.get( index );
if ( datacon == null ) {
return null;
}
return datacon.getValue();
}
/**
* Returns the key from the given index.
*
* @param index the index of the key.
* @return the the key at the specified index.
*/
public K getKeyAt( final int index ) {
if ( index >= data.size() ) {
return null;
}
if ( index < 0 ) {
return null;
}
final ComboBoxItemPair<K, V> datacon = data.get( index );
if ( datacon == null ) {
return null;
}
return datacon.getKey();
}
/**
* Returns the selected data element or null if none is set.
*
* @return the selected data element.
*/
public K getSelectedKey() {
return getKeyAt( selectedItemIndex );
}
/**
* Returns the length of the list.
*
* @return the length of the list
*/
public int getSize() {
return data.size();
}
/**
* Removes a listener from the list that's notified each time a change to the data model occurs.
*
* @param l the <code>ListDataListener</code> to be removed
*/
public void removeListDataListener( final ListDataListener l ) {
listdatalistener.remove( l );
tempListeners = null;
}
/**
* Searches an element by its key value. This method is called from setSelectedKey(..).
*
* @param anItem the item
* @return the index of the item or -1 if not found.
*/
private int findDataElementIndex( final K anItem ) {
for ( int i = 0; i < data.size(); i++ ) {
final ComboBoxItemPair<K, V> datacon = data.get( i );
final K key = datacon.getKey();
if ( anItem == key ) {
return i;
}
if ( anItem != null && anItem.equals( key ) ) {
return i;
}
}
return -1;
}
/**
* Tries to find the index of element with the given value. This method is called by the setSelectedItem method and
* returns the first occurence of the element.
*
* @param anItem the key for the element to be searched.
* @return the index of the key, or -1 if not found.
*/
public int findElementIndex( final V anItem ) {
for ( int i = 0; i < data.size(); i++ ) {
final ComboBoxItemPair<K, V> datacon = data.get( i );
final Object value = datacon.getValue();
if ( anItem == value ) {
return i;
}
if ( anItem != null && anItem.equals( value ) ) {
return i;
}
}
return -1;
}
/**
* Removes an entry from the model.
*
* @param key the key
*/
public void removeDataElement( final K key ) {
final int idx = findDataElementIndex( key );
if ( idx == -1 ) {
return;
}
data.remove( idx );
final ListDataEvent evt = new ListDataEvent
( this, ListDataEvent.INTERVAL_REMOVED, idx, idx );
fireListDataEvent( evt );
}
/**
* Adds a new entry to the model.
*
* @param key the key
* @param cbitem the display value.
*/
public void add( final K key, final V cbitem ) {
final ComboBoxItemPair<K, V> con = new ComboBoxItemPair<K, V>( key, cbitem );
data.add( con );
final ListDataEvent evt = new ListDataEvent
( this, ListDataEvent.INTERVAL_ADDED, data.size() - 2, data.size() - 2 );
fireListDataEvent( evt );
}
public void update( final int index, final K key, final V cbitem ) {
final ComboBoxItemPair<K, V> con = new ComboBoxItemPair<K, V>( key, cbitem );
data.set( index, con );
final ListDataEvent evt = new ListDataEvent
( this, ListDataEvent.CONTENTS_CHANGED, index, index );
fireListDataEvent( evt );
}
/**
* Removes all entries from the model.
*/
public void clear() {
final int size = getSize();
data.clear();
fireListDataEvent( new ListDataEvent( this, ListDataEvent.INTERVAL_REMOVED, 0, size - 1 ) );
selectedItemIndex = -1;
selectedItemValue = null;
fireListDataEvent( new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, -1, -1 ) );
}
public int getSelectedItemIndex() {
return selectedItemIndex;
}
public void remove( final int index ) {
data.remove( index );
final ListDataEvent evt = new ListDataEvent( this, ListDataEvent.INTERVAL_REMOVED, index, index );
fireListDataEvent( evt );
}
}