All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.cuf.model.SelectionInList Maven / Gradle / Ivy

The newest version!
package net.sf.cuf.model;

import java.util.Comparator;
import java.util.List;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * A SelectionInList holds both the data as well as the selection
 * as a ValueModel.
 * A SelectionInList differs from an IndexedAdapter in the following
 * points:
    *
  • the users of an IndexedAdapter doesn't know that a list is used * at all, they are usally interrested in the aspect of one entry * in the list *
  • the SelectionInList holds both the data as well as the selection *
* Typical users of a SelectionInList are adapters for comboboxes, * radiobuttons, ... . This users are interested in the list as a whole, * as well as in the selection index. * @param the Type inside the list we monitor the selection */ public class SelectionInList extends AbstractValueModel> implements ValueModel> , ChangeListener, ExternalUpdate, IndexProvider { /* * TODO changes that should be made here corresponding to the new MultiSelectionInList: * - Introduce a value model that holds the selected object of the list * and that is modifiable within the values of the base list and null. * - Change the IndexedAdapter to reflect that change. * - If the List is given explicitly in a constructor instead of a ValueModel, * internally create a value model and put the value in it (simplifies the code). */ /** null or value model holding our our list */ private ValueModel> mListHolder; /** null or mListHolder if the value model provides an initial index */ private IndexProvider mIndexProvider; /** null or mListHolder if the value model supports and external update */ private ExternalUpdate mExternalUpdate; /** our list, may be null */ private List mList; /** value model holding either NO_SELECTION or the selection in our list * as an Integer, never null, value never null */ private ValueModel mSelectionIndexHolder; /** checks if the index is not NO_SELECTION, that means that the index is * inside our list, never null, value either Boolean.TRUE or Boolean.FALSE */ private IndexValidHolder mIndexInList; /** * If true, we try to keep the selected object stable when * the list changes, if false, a change in the list * will always result in clearing the selection. * Default value is false. *

* A value of true means that we search in the new list for an object * that is equal to the previously selected object * using the {@link #mSelectionComparator}. * If we find one, we adjust the selection index to match * the new position. If we don't find one, we set the * selection index to {@link #NO_SELECTION}. *

* Note that in case of value true * and unless there is no previous selection, * a change in the list will result in a notification * on the {@link #mSelectionIndexHolder}, even if the actual index doesn't * change. Furthermore, if the object is found, * a notification will be fired with the new index * which causes in an {@link IndexedAdapter} to fire a notification * event even though its object may have stayed the same. */ private boolean mKeepSelection; /** * the comparator used to find the previously selected object * in a new list - see {@link #mKeepSelection}. * This value may be null, which means that {@link Object#equals(Object)} * should be used. */ private Comparator mSelectionComparator; /** no selection as an Integer object */ public static final Integer NO_SELECTION= IndexProvider.NO_SELECTION; /** * Creates a new SelectionInList that does not select an entry in the list. * @param pListHolder the list we monitor the selection is the value of pListHolder * @throws IllegalArgumentException if pListHolder is null or pListHolder.getValue() * is not null and not a List */ public SelectionInList(final ValueModel> pListHolder) { this(pListHolder, IndexProvider.NO_SELECTION); } /** * Creates a new SelectionInList that does not select an entry in the list. * @param pListHolder the list we monitor the selection is the value of pListHolder * @param pIndex NO_SELECTION or the initial index in the list * @throws IllegalArgumentException if pListHolder is null or the value if * pListHolder is neither null nor a List or pIndex is out of range */ public SelectionInList(final ValueModel> pListHolder, final int pIndex) { super(); if (pListHolder==null) { throw new IllegalArgumentException("list holder must not be null"); } init(pListHolder.getValue(), pListHolder, pIndex); } /** * Creates a new SelectionInList that does not select an entry in the list. * @param pList the list we are indexing into, may be null */ public SelectionInList(final List pList) { this(pList, IndexProvider.NO_SELECTION); } /** * Creates a new SelectionInList. * @param pList the list we are indexing into, may be null * @param pIndex NO_SELECTION or the initial index in the list * @throws IllegalArgumentException if pIndex is out of range */ public SelectionInList(final List pList, final int pIndex) { super(); init(pList, null, pIndex); } /** * Handle common constructor stuff. * @param pList list we are indexing into, may be null * @param pListHolder ValueModel of pList, may be null * @param pIndex NO_SELECTION or the initial index in pList * @throws IllegalArgumentException if pIndex is out of range */ private void init(final List pList, final ValueModel> pListHolder, int pIndex) { mList = pList; mListHolder = pListHolder; setInSetValue(false, false); if (mListHolder instanceof IndexProvider) { mIndexProvider= (IndexProvider) mListHolder; pIndex= mIndexProvider.getIndex(); } else { mIndexProvider= null; } if (mListHolder instanceof ExternalUpdate) { mExternalUpdate= (ExternalUpdate)mListHolder; } mIndexInList = new IndexValidHolder(pIndex); mSelectionIndexHolder= new SelectionIndexHolder(pIndex); if (mListHolder!=null) { mListHolder.addChangeListener(this); } } /** * Returns always true * @return true */ public boolean isEditable() { // TODO: this is a bug. // When a list holder is used, the value should // depend on the mListHolder.isEditable() return true; } /** * Cleanup all resources: disconnect from any input sources (like * other ValueModel's ...), and remove all listeners. */ public void dispose() { super.dispose(); if (mListHolder!=null && !mListHolder.isDisposed()) { mListHolder.removeChangeListener(this); } mSelectionIndexHolder.dispose(); mIndexInList.dispose(); } /** * Small helper to check the index. * @param pIndex index to check * @param pList List for the index, may be null */ private static void checkIndex(final int pIndex, final List pList) { int maxSize= IndexProvider.NO_SELECTION; if (pList!=null) { maxSize = pList.size()-1; } if ((pIndexmaxSize)) { throw new IllegalArgumentException("index out of range, got "+ pIndex+", but list size is "+ (maxSize+1)); } } /** * defines the behavior of the selection when the * list changes. * @param pKeepSelection if true, we try to keep the selected * object, if false, the selection is cleared. * @see #mKeepSelection */ public void setKeepSelection(final boolean pKeepSelection) { mKeepSelection = pKeepSelection; } /** * @return true if we try to keep the selected object when the list * changes, false if the selection will be cleared * @see #mKeepSelection */ public boolean isKeepSelection() { return mKeepSelection; } /** * @return the comparator used to find the object if {@link #mKeepSelection} is true * @see #mKeepSelection */ public Comparator getSelectionComparator() { return mSelectionComparator; } /** * sets the comparator to use to find the object if {@link #mKeepSelection} is true * @param pSelectionComparator the new comparator, may be null. * If null, {@link Object#equals(Object)} will be used. * @see #mKeepSelection */ public void setSelectionComparator(final Comparator pSelectionComparator) { mSelectionComparator = pSelectionComparator; } /** * Set a new value, this will fire a ChangeEvent if the new value * is different from the old value. The new value must be a List * or null, if we have a list holder we set the list to * the value holder. * @param pValue the new list (null is o.k.) * @param pIsSetForced true if a forced setValue should be done * @throws IllegalArgumentException if pValue is neither a List nor null */ public void setValue(final List pValue, final boolean pIsSetForced) { checkDisposed(); if (isInSetValue()) { return; } if ((pValue!=null) && !(pValue instanceof List)) { throw new IllegalArgumentException("value must be a List or null, not " + "a "+pValue.getClass().getName()); } int oldSelectionIndex = mSelectionIndexHolder.intValue(); T oldSelectedObject = null; if (oldSelectionIndex>=0 && mList!=null && oldSelectionIndex=0) { newIndex = findIndexInList(mList, oldSelectedObject); forced = true; } mSelectionIndexHolder.setValue( newIndex, forced); // this must happen after the index has been updated, otherwise // we have a index that might not be inside the table fireStateChanged(); } finally { setInSetValue(false, false); } } /** * Finds the first index in the list that matches the given object. * To compare the object, the {@link #mSelectionComparator} * is used if it is given with the object as the first parameter * and the list entry as the second parameter, otherwise {@link Object#equals(Object)} * is used. null is only matched by null. * If the object is not found in the list, -1 will be returned. * * @param pList the list to search through, may be null * @param pObject the object to look for, may be null * @return the index of the first matching object in the list or -1 if none could not be found */ private int findIndexInList( final List pList, final T pObject) { if (pList==null) { return -1; } else if (mSelectionComparator==null) { // we can use default method return pList.indexOf( pObject); } else { for (int i = 0; i < pList.size(); i++) { T objInList = pList.get(i); if (pObject==null) { if (objInList==null) { return i; } } else if (objInList!=null) { if (mSelectionComparator.compare(pObject, objInList)==0) { return i; } } } return -1; } } /** * Get the current list. * @return null or our list */ public List getValue() { try { checkDisposed(); } catch (IllegalStateException e) { // we consider this as an valid action, otherwise we would force // to disconnect all cascading value models, returning null in a // "shutdown" scenaria is slightly more gracefull than throwing an // exception return null; } return mList; } /** * Return the index of the currently selected item in our list. * @return NO_SELECTION or our index */ public int getIndex() { checkDisposed(); return mSelectionIndexHolder.intValue(); } /** * Add an item to the underlying list at the current position or at the * end of the list if there is currently no selection. * @param pItem the item to add */ public void addItem(final T pItem) { checkDisposed(); int index= mSelectionIndexHolder.intValue(); if (index== NO_SELECTION) { addItem(mList.size(), pItem); } else { addItem(index, pItem); } } /** * Add an item to the underlying list at the given position. If the List is * inside a ValueModel, re-set the list to that value model. The current index * is updated to match pIndex before firing a state-change. * @param pIndex the index to add the item * @param pItem the item to add */ public void addItem(final int pIndex, final T pItem) { checkDisposed(); if (isInSetValue()) { throw new IllegalStateException("addItem called during setValue, index= "+ pIndex+", new item= "+pItem); } setInSetValue(true, false); try { mList.add(pIndex, pItem); if (mExternalUpdate!=null) { mExternalUpdate.signalExternalUpdate(); } // do not change the order of the following lines, otherwise // we have a index that might not be inside the table mSelectionIndexHolder.setValueForced(pIndex); fireStateChanged(); } finally { setInSetValue(false, false); } } /** * Remove the item at the handed position. * If the current index would no longer be valid (when removing the last * item or a list), the current index is set first to the list size - 1 or * to NO_SELECTION before firing a state-change. * @param pIndex NO_SELECTION or 0..list size-1 * @return null or the object at that list position */ public Object removeItem(final int pIndex) { checkDisposed(); if (isInSetValue()) { throw new IllegalStateException("removeItem called during setValue, index= "+pIndex); } if (pIndex== NO_SELECTION) { return null; } Object back= null; setInSetValue(true, false); try { back= mList.remove(pIndex); if (mExternalUpdate!=null) { mExternalUpdate.signalExternalUpdate(); } int index= pIndex; if (pIndex==mList.size()) index= (mList.size()-1); // do not change the order of the following lines, otherwise // we have a index that might not be inside the table mSelectionIndexHolder.setValue(new Integer(index)); fireStateChanged(); } finally { setInSetValue(false, false); } return back; } /** * Removes the currently selecte item, or does nothing if no item * is selected. * @return null or the object at the removed position */ public Object removeItem() { return removeItem(selectionHolder().intValue()); } /** * Invoked when the value model holding our list changed its state. * * @param pEvent a ChangeEvent object, not used */ public void stateChanged(final ChangeEvent pEvent) { checkDisposed(); // we ignore state changes when we just changed the list if (isInSetValue()) { return; } // remember the current selection int oldSelectionIndex = mSelectionIndexHolder.intValue(); T oldSelectedObject = null; if (oldSelectionIndex>=0 && mList!=null && oldSelectionIndex value= mListHolder.getValue(); int index= IndexProvider.NO_SELECTION; if (mIndexProvider!=null) { index= mIndexProvider.getIndex(); } checkIndex(index, value); // first remember the list (so that it is available during the callbacks), // then update the index, then notify our listeners mList= value; if (mKeepSelection && oldSelectionIndex>=0) { mSelectionIndexHolder.setValueForced(findIndexInList(mList, oldSelectedObject)); } else { mSelectionIndexHolder.setValue(new Integer(index)); } fireStateChanged(); } /** * The provided ValueModel can be used to monitor if this IndexedAdapter * holds a "valid" value (index is not NO_SELECTION). * The ValueModel is read-only, and contains either Boolean.TRUE or Boolean.FALSE. * @return a ValueModel with a Boolean value */ public ValueModel isIndexInList() { return mIndexInList; } /** * The provided ValueModel can be used to monitor and change * the selection in our list. * The value of the ValueModel is and an Integer (never null) holding * the index or NO_SELECTION if there is no valid index. * @return a ValueModel with a Integer value */ public ValueModel selectionHolder() { checkDisposed(); return mSelectionIndexHolder; } /** * Provides the ValueModel of the ListHolder for our List. * * @return ValueModel holding our list, may be null */ public ValueModel> listHolder() { return mListHolder; } /** * helper class that holds the index of the selection and * checks if the value in setValue() is valid. */ private class SelectionIndexHolder extends ValueHolder { /** * Creates a new SelectionIndexHolder with the given index. * @param pIndex the initial index. */ SelectionIndexHolder(final int pIndex) { setValue(new Integer(pIndex)); } /** * Set a new value, this will fire a ChangeEvent if the new value * is different from the old value. * @param pValue the new Integer value (null is not o.k.) * @param pIsSetForced true if a forced setValue should be done * @throws IllegalArgumentException if pValue is not an Integer or * the integer is not inside the list */ public void setValue(final Integer pValue, final boolean pIsSetForced) { checkDisposed(); if (pValue == null) { throw new IllegalArgumentException("selection must not be null"); } int index= pValue; checkIndex(index, mList); super.setValue(pValue,pIsSetForced); mIndexInList.setIndex(index); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy