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

ca.odell.glazedlists.ListSelection Maven / Gradle / Ivy

/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists;

import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventAssembler;
import ca.odell.glazedlists.event.ListEventListener;
import ca.odell.glazedlists.impl.adt.Barcode;
import ca.odell.glazedlists.impl.adt.BarcodeIterator;
import ca.odell.glazedlists.matchers.Matcher;

import java.util.*;

/**
 * A class to provide index-based selection features. This class maintains two
 * lists derived from a single {@link EventList}:
 * 
    *
  • {@link #getSelected() Selected} - an {@link EventList} that contains only the selected values.
  • *
  • {@link #getDeselected() Deselected} - an {@link EventList} that contains only the deselected values.
  • *
* *

This design is intended to allow the sharing of selection logic between * all supported GUI toolkits as well as being available for use in non-GUI * applications and for index-based filtering. * *

Warning: This class is * thread ready but not thread safe. See {@link EventList} for an example * of thread safe code. * * @author Kevin Maltby */ public class ListSelection implements ListEventListener { /** * A selection mode where at most one element may be selected at one time. * For convenience, this value equals {@link javax.swing.ListSelectionModel#SINGLE_SELECTION}. */ public static final int SINGLE_SELECTION = 0; /** * A selection mode where at most one range of elements may be selected at one time. * For convenience, this value equals {@link javax.swing.ListSelectionModel#SINGLE_INTERVAL_SELECTION}. */ public static final int SINGLE_INTERVAL_SELECTION = 1; /** * A selection mode where any element may be selected and elements added * adjacent to selected elements are selected. For convenience, this value * equals {@link javax.swing.ListSelectionModel#MULTIPLE_INTERVAL_SELECTION}. */ public static final int MULTIPLE_INTERVAL_SELECTION = 2; /** * A selection mode where any element may be selected and freshly added elements * are always deselected. No equivalent policy exists in * {@link javax.swing.ListSelectionModel} */ public static final int MULTIPLE_INTERVAL_SELECTION_DEFENSIVE = 103; /** the current selection colour */ private static final Object SELECTED = Barcode.BLACK; /** the current deselection colour */ private static final Object DESELECTED = Barcode.WHITE; /** the source list */ private final EventList source; /** the selected view */ private SelectedList selectedList; /** the deselected view */ private DeselectedList deselectedList; /** the toggling selected view */ private SelectionToggleList selectedToggleList; /** the toggling deselected view */ private DeselectionToggleList deselectedToggleList; /** the selection state */ private Barcode barcode = new Barcode(); /** the lead selection index */ private int leadSelectionIndex = -1; /** the anchor selection index */ private int anchorSelectionIndex = -1; /** the selection mode defines characteristics of the selection */ private int selectionMode = MULTIPLE_INTERVAL_SELECTION_DEFENSIVE; /** the Matchers, if any, which decide whether a source element can be selected or not */ private final Collection> validSelectionMatchers = new ArrayList>(); /** * the registered SelectionListeners; also used as a mutex to ensure we * build each of the following only once: * *

    *
  • {@link #selectedList}
  • *
  • {@link #deselectedList}
  • *
  • {@link #selectedToggleList}
  • *
  • {@link #deselectedToggleList}
  • *
*/ private final List selectionListeners = new ArrayList(1); /** * Creates a new ListSelection that listens to changes on the given source. * When using this constructor, all elements are deselected by default. */ public ListSelection(EventList source) { this.source = source; barcode.add(0, DESELECTED, source.size()); source.addListEventListener(this); } /** * Creates a new ListSelection that listens to changes on the given source * and initializes selection with the given array of indices. */ public ListSelection(EventList source, int[] initialSelection) { this(source); select(initialSelection); } /** * Handle changes to the source list by adjusting our selection state and * the contents of the selected and deselected lists. */ public void listChanged(ListEvent listChanges) { // keep track of what used to be selected final int minSelectionIndexBefore = getMinSelectionIndex(); final int maxSelectionIndexBefore = getMaxSelectionIndex(); boolean selectionChanged = false; // handle reordering events if(listChanges.isReordering()) { // prepare for the reordering event beginSelected(); int[] sourceReorderMap = listChanges.getReorderMap(); int[] selectReorderMap = new int[barcode.colourSize(SELECTED)]; int[] deselectReorderMap = new int[barcode.colourSize(DESELECTED)]; // adjust the flaglist & construct a reorder map to propagate Barcode previousBarcode = barcode; barcode = new Barcode(); for(int c = 0; c < sourceReorderMap.length; c++) { Object flag = previousBarcode.get(sourceReorderMap[c]); boolean wasSelected = (flag != DESELECTED); barcode.add(c, flag, 1); if(wasSelected) { int previousIndex = previousBarcode.getColourIndex(sourceReorderMap[c], SELECTED); int currentIndex = barcode.getColourIndex(c, SELECTED); selectReorderMap[currentIndex] = previousIndex; } else { int previousIndex = previousBarcode.getColourIndex(sourceReorderMap[c], DESELECTED); int currentIndex = barcode.getColourIndex(c, DESELECTED); deselectReorderMap[currentIndex] = previousIndex; } } // adjust other internal state anchorSelectionIndex = -1; leadSelectionIndex = -1; // fire the reorder on the selected list addSelectedReorder(selectReorderMap); commitSelected(); // fire the reorder on the deselected list beginDeselected(); addDeselectedReorder(deselectReorderMap); commitDeselected(); // all reordering events are assumed to have changed the selection selectionChanged = true; // handle non-reordering events } else { // prepare a sequence of changes beginAll(); // for all changes update the barcode while(listChanges.next()) { int index = listChanges.getIndex(); int changeType = listChanges.getType(); // learn about what it was int previousSelectionIndex = barcode.getColourIndex(index, SELECTED); boolean previouslySelected = previousSelectionIndex != -1; // when an element is deleted, blow it away if(changeType == ListEvent.DELETE) { // if the change occurred anywhere before the maxSelectionIndexBefore // then it effects the current selection and we must fire a ListSelectionEvent if (index <= maxSelectionIndexBefore) selectionChanged = true; // delete selected values if(previouslySelected) { barcode.remove(index, 1); addSelectedDelete(previousSelectionIndex, listChanges.getOldValue()); // delete deselected values } else { int deselectedIndex = barcode.getColourIndex(index, DESELECTED); addDeselectedDelete(deselectedIndex, listChanges.getOldValue()); barcode.remove(index, 1); } // when an element is inserted, it is selected if its index was selected } else if(changeType == ListEvent.INSERT) { // if the change occurred anywhere before the maxSelectionIndexBefore // then it effects the current selection and we must fire a ListSelectionEvent if (index <= maxSelectionIndexBefore) selectionChanged = true; // when selected, decide based on selection mode if(previouslySelected) { // select the inserted for single interval and multiple interval selection if(selectionMode == SINGLE_INTERVAL_SELECTION || selectionMode == MULTIPLE_INTERVAL_SELECTION) { barcode.add(index, SELECTED, 1); addSelectedInsert(previousSelectionIndex, listChanges.getNewValue()); // do not select the inserted for single selection and defensive selection } else { barcode.add(index, DESELECTED, 1); int deselectedIndex = barcode.getColourIndex(index, DESELECTED); addDeselectedInsert(deselectedIndex, listChanges.getNewValue()); } // add a deselected value } else { barcode.add(index, DESELECTED, 1); int deselectedIndex = barcode.getColourIndex(index, DESELECTED); addDeselectedInsert(deselectedIndex, listChanges.getNewValue()); } // when an element is changed, assume selection stays the same } else if(changeType == ListEvent.UPDATE) { // update a selected value if(previouslySelected) { addSelectedUpdate(previousSelectionIndex, listChanges.getOldValue(), listChanges.getNewValue()); // update a deselected value } else { int deselectedIndex = barcode.getColourIndex(index, DESELECTED); addDeselectedUpdate(deselectedIndex, listChanges.getOldValue(), listChanges.getNewValue()); } } // adjust other internal state anchorSelectionIndex = adjustIndex(anchorSelectionIndex, changeType, index); leadSelectionIndex = adjustIndex(leadSelectionIndex, changeType, index); } commitAll(); } // notify listeners of the selection change if(minSelectionIndexBefore != -1 && maxSelectionIndexBefore != -1 && selectionChanged) { final int minSelectionIndexAfter = getMinSelectionIndex(); final int maxSelectionIndexAfter = getMaxSelectionIndex(); int changeStart = minSelectionIndexBefore; int changeFinish = maxSelectionIndexBefore; if(minSelectionIndexAfter != -1 && minSelectionIndexAfter < changeStart) changeStart = minSelectionIndexAfter; if(maxSelectionIndexAfter != -1 && maxSelectionIndexAfter > changeFinish) changeFinish = maxSelectionIndexAfter; fireSelectionChanged(changeStart, changeFinish); } } /** * Add a matcher which decides when source elements are valid for selection. * * @param validSelectionMatcher returns true if a source elements * can be selected; false otherwise */ public void addValidSelectionMatcher(Matcher validSelectionMatcher) { validSelectionMatchers.add(validSelectionMatcher); // walk through the existing selections deselecting anything that is no longer selectable for (int i = getMinSelectionIndex(), n = getMaxSelectionIndex(); i <= n; i++) if (isSelected(i) && !isSelectable(i)) deselect(i); } /** * Remove a matcher which decides when source elements are valid for selection. * * @param validSelectionMatcher returns true if a source elements * can be selected; false otherwise */ public void removeValidSelectionMatcher(Matcher validSelectionMatcher) { validSelectionMatchers.remove(validSelectionMatcher); } /** * Adjusts the specified index to the specified change. This is used to adjust * the anchor and lead selection indices when list changes occur. */ private int adjustIndex(int indexBefore, int changeType, int changeIndex) { if(indexBefore == -1) return -1; if(changeType == ListEvent.DELETE) { if(changeIndex < indexBefore) return indexBefore-1; else if(changeIndex == indexBefore) return -1; else return indexBefore; } else if(changeType == ListEvent.UPDATE) { return indexBefore; } else if(changeType == ListEvent.INSERT) { if(changeIndex <= indexBefore) return indexBefore+1; else return indexBefore; } else { throw new IllegalStateException(); } } /** * Gets an {@link EventList} that contains only selected values and modifies * the source list on mutation. * * Adding and removing items from this list performs the same operation on * the source list. */ public EventList getSelected() { synchronized (selectionListeners) { if(selectedList == null){ selectedList = new SelectedList(source); source.getPublisher().setRelatedListener(selectedList, this); } } return selectedList; } /** * Gets an {@link EventList} that contains only selected values and modifies * the selection state on mutation. * *

Adding an item to this list selects it and removing an item deselects it. * If an item not in the source list is added an * {@link IllegalArgumentException} is thrown. This list does not support * the {@link List#set set} method. */ public EventList getTogglingSelected() { synchronized (selectionListeners) { if(selectedToggleList == null) { selectedToggleList = new SelectionToggleList(source); source.getPublisher().setRelatedListener(selectedToggleList, this); } } return selectedToggleList; } /** * Gets an {@link EventList} that contains only deselected values add * modifies the source list on mutation. * * Adding and removing items from this list performs the same operation on * the source list. */ public EventList getDeselected() { synchronized (selectionListeners) { if(deselectedList == null) { deselectedList = new DeselectedList(source); source.getPublisher().setRelatedListener(deselectedList, this); } } return deselectedList; } /** * Gets an {@link EventList} that contains only deselected values and * modifies the selection state on mutation. * *

Adding an item to this list deselects it and removing an item selects it. * If an item not in the source list is added an * {@link IllegalArgumentException} is thrown. This list does not support * the {@link List#set set} method. */ public EventList getTogglingDeselected() { synchronized (selectionListeners) { if(deselectedToggleList == null) { deselectedToggleList = new DeselectionToggleList(source); source.getPublisher().setRelatedListener(deselectedToggleList, this); } } return deselectedToggleList; } /** * Get the {@link EventList} that selection is being managed for. */ public EventList getSource() { return source; } /** * Inverts the current selection. */ public void invertSelection() { // Clear the anchor and lead anchorSelectionIndex = -1; leadSelectionIndex = -1; // Update the selected list to reflect the selection inversion beginAll(); for(BarcodeIterator i = barcode.iterator(); i.hasNext(); ) { Object color = i.next(); E value = source.get(i.getIndex()); int originalIndex = i.getColourIndex(color); if(color == SELECTED) { i.set(DESELECTED); int newIndex = i.getColourIndex(DESELECTED); addDeselectEvent(originalIndex, newIndex, value); } else { i.set(SELECTED); int newIndex = i.getColourIndex(SELECTED); addSelectEvent(newIndex, originalIndex, value); } } commitAll(); // notify selection listeners that selection has been inverted fireSelectionChanged(0, source.size() - 1); } /** * Returns whether or not the item with the given source index * is selected. */ public boolean isSelected(int sourceIndex) { if(sourceIndex < 0 || sourceIndex >= source.size()) { return false; } return barcode.getColourIndex(sourceIndex, SELECTED) != -1; } /** * Deselects the element at the given index. */ public void deselect(int index) { deselect(index, index); } /** * Deselects all of the elements within the given range. */ public void deselect(int start, int end) { // fast fail if the range is invalid if(start == -1 || end == -1) { return; // use single value deselection in single selection mode } else if(selectionMode == SINGLE_SELECTION) { int selectedIndex = getMaxSelectionIndex(); // only change selection if you have to if(selectedIndex >= start && selectedIndex <= end) { deselectAll(); } return; // adjust the range to prevent the creation of multiple intervals } else if(selectionMode == SINGLE_INTERVAL_SELECTION && start > getMinSelectionIndex()) { end = Math.max(end, getMaxSelectionIndex()); } // record the original anchor and lead if they are about to change final int oldAnchor = anchorSelectionIndex == start ? -1 : anchorSelectionIndex; final int oldLead = leadSelectionIndex == end ? -1 : leadSelectionIndex; // update anchor and lead anchorSelectionIndex = start; leadSelectionIndex = end; // alter selection accordingly setSubRangeOfRange(false, start, end, -1, -1, oldLead, oldAnchor); } /** * Deselects all of the elements in the given array of indices. The * array must contain indices in sorted, ascending order. */ public void deselect(int[] indices) { // keep track of the range of values that were affected int firstAffectedIndex = -1; int lastAffectedIndex = -1; // iterate through the barcode updating the selected list as you go beginAll(); int currentIndex = 0; for(BarcodeIterator i = barcode.iterator(); i.hasNext() && currentIndex != indices.length;) { Object value = i.next(); if(i.getIndex() == indices[currentIndex]) { // selection changed if(value == SELECTED) { if(firstAffectedIndex == -1) firstAffectedIndex = i.getIndex(); lastAffectedIndex = i.getIndex(); addDeselectEvent(i); } currentIndex++; } } commitAll(); // notify listeners of selection change if(firstAffectedIndex > -1) fireSelectionChanged(firstAffectedIndex, lastAffectedIndex); } /** * Deselect all elements. */ public void deselectAll() { setAllColor(DESELECTED); } /** * @param color either selected or deselected. */ private void setAllColor(Object color) { // if there is nothing selected, we're done Object oppositeColor = (color == SELECTED) ? DESELECTED : SELECTED; if(barcode.colourSize(oppositeColor) == 0) return; // keep track of the range of values that were affected int firstAffectedIndex = -1; int lastAffectedIndex = -1; // prepare the change events beginAll(); for(BarcodeIterator i = barcode.iterator(); i.hasNextColour(oppositeColor);) { i.nextColour(oppositeColor); int index = i.getIndex(); E value = source.get(index); if(color == SELECTED) { addDeselectedDelete(0, value); addSelectedInsert(index, value); } else { addSelectedDelete(0, value); addDeselectedInsert(index, value); } if(firstAffectedIndex == -1) firstAffectedIndex = index; lastAffectedIndex = index; } // reset barcode state barcode.clear(); barcode.add(0, color, source.size()); // fire events commitAll(); fireSelectionChanged(firstAffectedIndex, lastAffectedIndex); } /** * Selects the element at the given index. */ public void select(int index) { select(index, index); } /** * Selects all of the elements within the given range. */ public void select(int start, int end) { // fast fail if the range is an no-op if(start == -1 || end == -1) { return; // use single value deselection in single selection mode } else if(selectionMode == SINGLE_SELECTION) { setSelection(start); return; // adjust the range to prevent the creation of multiple intervals } else if(selectionMode == SINGLE_INTERVAL_SELECTION) { // test if the current and new selection overlap boolean overlap = false; int minSelectedIndex = getMinSelectionIndex(); int maxSelectedIndex = getMaxSelectionIndex(); if(minSelectedIndex - 1 <= start && start <= maxSelectedIndex + 1) overlap = true; if(minSelectedIndex - 1 <= end && end <= maxSelectedIndex + 1) overlap = true; if(!overlap) { setSelection(start, end); return; } } // record the original anchor and lead if they are about to change final int oldAnchor = anchorSelectionIndex == start ? -1 : anchorSelectionIndex; final int oldLead = leadSelectionIndex == end ? -1 : leadSelectionIndex; // update anchor and lead anchorSelectionIndex = start; leadSelectionIndex = end; // alter selection accordingly setSubRangeOfRange(true, start, end, -1, -1, oldLead, oldAnchor); } /** * Selects all of the elements in the given array of indices. The * array must contain indices in sorted, ascending order. */ public void select(int[] indices) { // keep track of the range of values that were affected int firstAffectedIndex = -1; int lastAffectedIndex = -1; // iterate through the barcode updating the selected list as you go beginAll(); int currentIndex = 0; for(BarcodeIterator i = barcode.iterator(); i.hasNext() && currentIndex != indices.length;) { Object value = i.next(); if(i.getIndex() == indices[currentIndex]) { // selection changed if(value != SELECTED) { if(firstAffectedIndex == -1) firstAffectedIndex = i.getIndex(); lastAffectedIndex = i.getIndex(); addSelectEvent(i); } currentIndex++; } } commitAll(); // notify listeners of selection change if(firstAffectedIndex > -1) fireSelectionChanged(firstAffectedIndex, lastAffectedIndex); } /** * Select the specified element, if it exists. * * @return the index of the newly selected element, or -1 if no * element was found. */ public int select(E value) { int index = source.indexOf(value); if(index != -1) select(index); return index; } /** * Select all of the specified values. * * @return true if the selection changed as a result of the call. */ public boolean select(Collection values) { // This implementation leaves a lot to be desired. It's inefficient and // awkward. If possible, we should clean up the entire SelectionList so // we don't need to worry as much about the deselection list. // 1. Convert our Collection of values into a SortedSet of indices SortedSet indicesToSelect = new TreeSet(); for(Iterator v = values.iterator(); v.hasNext(); ) { E value = v.next(); int index = source.indexOf(value); if(index == -1) continue; indicesToSelect.add(new Integer(index)); } if(indicesToSelect.isEmpty()) return false; // 2. convert the sorted set of Integers into an int[] int[] indicesToSelectAsInts = new int[indicesToSelect.size()]; int arrayIndex = 0; for(Iterator i = indicesToSelect.iterator(); i.hasNext(); ) { Integer selectIndex = i.next(); indicesToSelectAsInts[arrayIndex] = selectIndex.intValue(); arrayIndex++; } // 3. Delegate to the other method, and return true if the selection grew int selectionSizeBefore = getSelected().size(); select(indicesToSelectAsInts); int selectionSizeAfter = getSelected().size(); return selectionSizeAfter > selectionSizeBefore; } /** * Selects all elements. */ public void selectAll() { setAllColor(SELECTED); } /** * Sets the selection to be only the element at the given index. If the * given index is -1, the selection will be cleared. */ public void setSelection(int index) { setSelection(index, index); } /** * Sets the selection to be only elements within the given range. If the * endpoints of the range are -1, the selection will be cleared. */ public void setSelection(int start, int end) { // a range including -1 implies a deselectAll() if(start == -1 || end == -1) { deselectAll(); return; } else if(selectionMode == SINGLE_SELECTION) { end = start; } // record the original anchor and lead if they are about to change final int oldAnchor = anchorSelectionIndex == start ? -1 : anchorSelectionIndex; final int oldLead = leadSelectionIndex == end ? -1 : leadSelectionIndex; // update anchor and lead anchorSelectionIndex = start; leadSelectionIndex = end; // alter selection accordingly setSubRangeOfRange(true, start, end, getMinSelectionIndex(), getMaxSelectionIndex(), oldLead, oldAnchor); } /** * Sets the selection to be only the element in the given array of indices. * Unlike {@link #setSelection(int)} and {@link #setSelection(int,int)}, * providing a value of -1 is an error. The array must contain indices in * sorted, ascending order. */ public void setSelection(int[] indices) { // fast fail is the selection is empty if(indices.length == 0) { deselectAll(); return; } // keep track of the range of values that were affected int firstAffectedIndex = -1; int lastAffectedIndex = -1; // iterate through the barcode updating the selected list as you go beginAll(); int currentIndex = 0; for(BarcodeIterator i = barcode.iterator(); i.hasNext();) { Object value = i.next(); // this element should be selected if(i.getIndex() == indices[currentIndex]) { // selection changed if(value != SELECTED) { if(firstAffectedIndex == -1) firstAffectedIndex = i.getIndex(); lastAffectedIndex = i.getIndex(); addSelectEvent(i); } // look at the next value if(currentIndex < indices.length - 1) currentIndex++; // element was selected and isn't within the new selection } else if(value == SELECTED) { if(firstAffectedIndex == -1) firstAffectedIndex = i.getIndex(); lastAffectedIndex = i.getIndex(); addDeselectEvent(i); } } commitAll(); // notify listeners of selection change if(firstAffectedIndex > -1) fireSelectionChanged(firstAffectedIndex, lastAffectedIndex); } /** * Return the anchor of the current selection. */ public int getAnchorSelectionIndex() { return anchorSelectionIndex; } /** * Set the anchor selection index. */ public void setAnchorSelectionIndex(int anchorSelectionIndex) { // record the original anchor and lead if they are about to change final int oldAnchor = this.anchorSelectionIndex == anchorSelectionIndex ? -1 : anchorSelectionIndex; // update anchor this.anchorSelectionIndex = anchorSelectionIndex; // a value of -1 clears selection if(anchorSelectionIndex == -1 || leadSelectionIndex == -1) { deselectAll(); // only the anchor should be selected } else if(selectionMode == SINGLE_SELECTION) { setSubRangeOfRange(true, anchorSelectionIndex, anchorSelectionIndex, getMinSelectionIndex(), getMaxSelectionIndex(), -1, oldAnchor); // select the interval between anchor and lead } else if(selectionMode == SINGLE_INTERVAL_SELECTION) { setSubRangeOfRange(true, anchorSelectionIndex, leadSelectionIndex, getMinSelectionIndex(), getMaxSelectionIndex(), -1, oldAnchor); // select the interval between anchor and lead without deselecting anything } else { setSubRangeOfRange(true, anchorSelectionIndex, leadSelectionIndex, -1, -1, -1, oldAnchor); } } /** * Return the lead of the current selection. */ public int getLeadSelectionIndex() { return leadSelectionIndex; } /** * Set the lead selection index. */ public void setLeadSelectionIndex(int leadSelectionIndex) { // record the original anchor and lead if they are about to change final int oldLead = this.leadSelectionIndex == leadSelectionIndex ? -1 : leadSelectionIndex; // update lead int originalLeadIndex = this.leadSelectionIndex; this.leadSelectionIndex = leadSelectionIndex; // a value of -1 clears selection if(leadSelectionIndex == -1 || anchorSelectionIndex == -1) { deselectAll(); // select only the lead } else if(selectionMode == SINGLE_SELECTION) { setSubRangeOfRange(true, leadSelectionIndex, leadSelectionIndex, getMinSelectionIndex(), getMaxSelectionIndex(), oldLead, -1); // select the interval between anchor and lead } else if(selectionMode == SINGLE_INTERVAL_SELECTION) { setSubRangeOfRange(true, anchorSelectionIndex, leadSelectionIndex, getMinSelectionIndex(), getMaxSelectionIndex(), oldLead, -1); // select the interval between anchor and lead deselecting as necessary } else { setSubRangeOfRange(true, anchorSelectionIndex, leadSelectionIndex, anchorSelectionIndex, originalLeadIndex, oldLead, -1); } } private void addSelectedReorder(int[] selectReorderMap) { if(selectedList != null) selectedList.updates().reorder(selectReorderMap); if(selectedToggleList != null) selectedToggleList.updates().reorder(selectReorderMap); } private void addDeselectedReorder(int[] deselectReorderMap) { if(deselectedList != null) deselectedList.updates().reorder(deselectReorderMap); if(deselectedToggleList != null) deselectedToggleList.updates().reorder(deselectReorderMap); } private void addSelectEvent(BarcodeIterator i) { E value = source.get(i.getIndex()); int deselectedIndex = i.getColourIndex(DESELECTED); int selectedIndex = i.set(SELECTED); addSelectEvent(selectedIndex, deselectedIndex, value); } private void addSelectEvent(int selectIndex, int deselectIndex, E value) { addDeselectedDelete(deselectIndex, value); addSelectedInsert(selectIndex, value); } private void addDeselectEvent(BarcodeIterator i) { E value = source.get(i.getIndex()); int selectedIndex = i.getColourIndex(SELECTED); int deselectedIndex = i.set(DESELECTED); addDeselectEvent(selectedIndex, deselectedIndex, value); } private void addDeselectEvent(int selectIndex, int deselectIndex, E value) { addSelectedDelete(selectIndex, value); addDeselectedInsert(deselectIndex, value); } private void addSelectedInsert(int index, E newValue){ if(selectedList != null) selectedList.updates().elementInserted(index, newValue); if(selectedToggleList != null) selectedToggleList.updates().elementInserted(index, newValue); } private void addSelectedUpdate(int index, E oldValue, E newValue){ if(selectedList != null) selectedList.updates().elementUpdated(index, oldValue, newValue); if(selectedToggleList != null) selectedToggleList.updates().elementUpdated(index, oldValue, newValue); } private void addSelectedDelete(int index, E oldValue){ if(selectedList != null) selectedList.updates().elementDeleted(index, oldValue); if(selectedToggleList != null) selectedToggleList.updates().elementDeleted(index, oldValue); } private void addDeselectedInsert(int index, E value){ if(deselectedList != null) deselectedList.updates().elementInserted(index, value); if(deselectedToggleList != null) deselectedToggleList.updates().elementInserted(index, value); } private void addDeselectedDelete(int index, E oldValue){ if(deselectedList != null) deselectedList.updates().elementDeleted(index, oldValue); if(deselectedToggleList != null) deselectedToggleList.updates().elementDeleted(index, oldValue); } private void addDeselectedUpdate(int index, E oldValue, E newValue) { if(deselectedList != null) deselectedList.updates().elementUpdated(index, oldValue, newValue); if(deselectedToggleList != null) deselectedToggleList.updates().elementUpdated(index, oldValue, newValue); } private void beginAll() { beginSelected(); beginDeselected(); } private void commitAll() { commitSelected(); commitDeselected(); } private void beginSelected() { if(selectedList != null) { selectedList.updates().beginEvent(); } if(selectedToggleList != null) { selectedToggleList.updates().beginEvent(); } } private void commitSelected() { if(selectedList != null) { selectedList.updates().commitEvent(); } if(selectedToggleList != null) { selectedToggleList.updates().commitEvent(); } } private void beginDeselected() { if(deselectedList != null) deselectedList.updates().beginEvent(); if(deselectedToggleList != null) deselectedToggleList.updates().beginEvent(); } private void commitDeselected() { if(deselectedList != null) deselectedList.updates().commitEvent(); if(deselectedToggleList != null) deselectedToggleList.updates().commitEvent(); } /** * Set the selection mode. */ public void setSelectionMode(int selectionMode) { this.selectionMode = selectionMode; setSelection(getMinSelectionIndex(), getMaxSelectionIndex()); } /** * Returns the current selection mode. */ public int getSelectionMode() { return selectionMode; } /** * Returns the first selected index or -1 if nothing is selected. */ public int getMinSelectionIndex() { if(barcode.colourSize(SELECTED) == 0) return -1; return barcode.getIndex(0, SELECTED); } /** * Returns the last selected index or -1 if nothing is selected. */ public int getMaxSelectionIndex() { if(barcode.colourSize(SELECTED) == 0) return -1; return barcode.getIndex(barcode.colourSize(SELECTED) - 1, SELECTED); } /** * Walks through the union of the specified ranges. All values in the * first range are given the specified selection value. All values in * the second range but not in the first range are given the opposite * selection value. In effect, the first range and the intersection of * the two ranges are given the specified value. The parts that are * in the second range but not in the first range (ie. everything else) * is given the opposite selection value. * * @param select true to set values in the first range as selected and * the other values in the second range as not selected. false to * set values in the first range as not selected and the other * values in the second range as selected. * @param changeIndex0 one end of the first range, inclusive. * @param changeIndex1 the opposite end of the first range, inclusive. * This may be lower than changeIndex0. * @param invertIndex0 one end of the second range, inclusive. To * specify an empty second range, specify -1 for this value. * @param invertIndex1 the opposite end of the second range, inclusive. * This may be lower than invertIndex0. To specify an empty second * range, specify -1 for this value. */ private void setSubRangeOfRange(boolean select, int changeIndex0, int changeIndex1, int invertIndex0, int invertIndex1, int oldLead, int oldAnchor) { // verify that the first range is legitimate if(changeIndex0 >= source.size() || changeIndex1 >= source.size() || ((changeIndex0 == -1 || changeIndex1 == -1) && changeIndex0 != changeIndex1)) { throw new IndexOutOfBoundsException("Invalid range for selection: " + changeIndex0 + "-" + changeIndex1 + ", list size is " + source.size()); } // verify that the second range is legitimate if(invertIndex0 >= source.size() || invertIndex1 >= source.size() || ((invertIndex0 == -1 || invertIndex1 == -1) && invertIndex0 != invertIndex1)) { throw new IndexOutOfBoundsException("Invalid range for invert selection: " + invertIndex0 + "-" + invertIndex1 + ", list size is " + source.size()); } // when the first range is empty if(changeIndex0 == -1 && changeIndex1 == -1) { // if the second range is empty, we're done if(invertIndex0 == -1 && invertIndex1 == -1) return; // otherwise set the first range to the second range and invert the goal changeIndex0 = invertIndex0; changeIndex1 = invertIndex1; select = !select; } // when the second range is empty if(invertIndex0 == -1 && invertIndex1 == -1) { // make this a subset of the first index which behaves the same as empty invertIndex0 = changeIndex0; invertIndex1 = changeIndex1; } // now get the change interval and invert interval int minChangeIndex = Math.min(changeIndex0, changeIndex1); int maxChangeIndex = Math.max(changeIndex0, changeIndex1); int minInvertIndex = Math.min(invertIndex0, invertIndex1); int maxInvertIndex = Math.max(invertIndex0, invertIndex1); // get the union of the two ranges int minUnionIndex = Math.min(minChangeIndex, minInvertIndex); int maxUnionIndex = Math.max(maxChangeIndex, maxInvertIndex); int minChangedIndex = maxUnionIndex + 1; int maxChangedIndex = minUnionIndex - 1; beginAll(); // walk through the effected range updating selection for(int i = minUnionIndex; i <= maxUnionIndex; i++) { int selectionIndex = barcode.getColourIndex(i, SELECTED); boolean selectedBefore = (selectionIndex != -1); boolean inChangeRange = (i >= minChangeIndex && i <= maxChangeIndex); boolean selectedAfter = (inChangeRange == select) && isSelectable(i); // when there's a change if(selectedBefore != selectedAfter) { E value = source.get(i); // update change range if(i < minChangedIndex) minChangedIndex = i; if(i > maxChangedIndex) maxChangedIndex = i; // it is being deselected if(selectedBefore) { barcode.set(i, DESELECTED, 1); addDeselectEvent(selectionIndex, i - selectionIndex, value); // it is being selected } else { barcode.set(i, SELECTED, 1); int newSelectionIndex = barcode.getColourIndex(i, SELECTED); addSelectEvent(newSelectionIndex, i - newSelectionIndex, value); } } } commitAll(); // consider the original lead/anchor indexes, if any, when firing the "selection changed" event // (this forces a redraw of the old lead/anchor rows when the lead/anchor changes) if (oldLead != -1) { minChangedIndex = Math.min(minChangedIndex, oldLead); maxChangedIndex = Math.max(maxChangedIndex, oldLead); } if (oldAnchor != -1) { minChangedIndex = Math.min(minChangedIndex, oldAnchor); maxChangedIndex = Math.max(maxChangedIndex, oldAnchor); } // notify selection listeners if(minChangedIndex <= maxChangedIndex) fireSelectionChanged(minChangedIndex, maxChangedIndex); } /** * Checks the {@link #validSelectionMatchers} to determine if the value at * the given index is allowed to be selected. * * @param index the index to check * @return true if the index can be selected; false * otherwise */ private boolean isSelectable(int index) { // no matchers implies the index is selectable if (validSelectionMatchers.isEmpty()) return true; // otherwise fetch the row object and validate it against all matchers final E rowObject = source.get(index); for (Iterator> iterator = validSelectionMatchers.iterator(); iterator.hasNext();) if (!iterator.next().matches(rowObject)) return false; return true; } /** * Register a {@link ca.odell.glazedlists.ListSelection.Listener Listener} * that will be notified when selection is changed. */ public void addSelectionListener(Listener selectionListener) { selectionListeners.add(selectionListener); } /** * Remove a {@link ca.odell.glazedlists.ListSelection.Listener Listener} * so that it will no longer be notified when selection changes. */ public void removeSelectionListener(Listener selectionListener) { selectionListeners.remove(selectionListener); } /** * Fire changes in selection to all registered listeners. */ private void fireSelectionChanged(int start, int end) { // notify all for(Iterator i = selectionListeners.iterator(); i.hasNext(); ) { i.next().selectionChanged(start, end); } } /** * Disposes of this ListSelection freeing up it's resources for * garbage collection. It is an error to use a ListSelection after * dispose() has been called. */ public void dispose() { source.removeListEventListener(this); selectionListeners.clear(); // detach the publisher dependencies if(selectedList != null) source.getPublisher().clearRelatedListener(selectedList, this); if(deselectedList != null) source.getPublisher().clearRelatedListener(deselectedList, this); if(selectedToggleList != null) source.getPublisher().clearRelatedListener(selectedToggleList, this); if(deselectedToggleList != null) source.getPublisher().clearRelatedListener(deselectedToggleList, this); } /** * A generic interface to respond to changes in selection that doesn't * require including a particular GUI toolkit. */ public interface Listener { /** * Notifies this SelectionListener of a change in selection. * * @param changeStart The first zero-relative index affected by a change in selection. * @param changeEnd The last zero-relative index affected by a change in selection. */ public void selectionChanged(int changeStart, int changeEnd); } /** * The {@link EventList} that contains only values that are currently * selected. */ private class SelectedList extends TransformedList { /** * Creates an {@link EventList} that provides a view of the * selected items in a ListSelection. */ SelectedList(EventList source) { super(source); } /** {@inheritDoc} */ @Override public int size() { return barcode.colourSize(SELECTED); } /** {@inheritDoc} */ @Override protected int getSourceIndex(int mutationIndex) { return barcode.getIndex(mutationIndex, SELECTED); } /** {@inheritDoc} */ @Override public void listChanged(ListEvent listChanges) { // Do nothing as all state changes are handled in ListSelection.listChanged() } /** * This allows access to the EventAssembler for this list. */ public ListEventAssembler updates() { return updates; } /** {@inheritDoc} */ @Override protected boolean isWritable() { return true; } /** * A no-op dispose method to prevent the user from shooting themselves * in the foot. To dispose a {@link ListSelection}, call * {@link ListSelection#dispose()} on that class directly. */ @Override public void dispose() { // Do Nothing } } /** * A SelectedList that mutates the selection instead of the underlying list. */ private class SelectionToggleList extends SelectedList{ SelectionToggleList(EventList source) { super(source); } /** @throws UnsupportedOperationException unconditionally */ @Override public E set(int index, E item){ throw new UnsupportedOperationException("Toggling lists don't support setting items"); } /** * Select the specified value in the source list, regardless of its * index. If the given item is found in the source list, it is selected. * * @throws IllegalArgumentException if the element isn't found */ @Override public void add(int index, E item) { index = source.indexOf(item); if(index != -1) { select(index); } else { throw new IllegalArgumentException("Added item " + item + " must be in source list"); } } /** * Deselect the specified index. */ @Override public E remove(int index){ if(index < 0 || index >= size()) throw new IndexOutOfBoundsException("Cannot remove at " + index + " on list of size " + size()); int sourceIndex = getSourceIndex(index); deselect(sourceIndex); return source.get(sourceIndex); } } /** * The {@link EventList} that contains only values that are not currently * selected. */ private class DeselectedList extends TransformedList { /** * Creates an {@link EventList} that provides a view of the * deselected items in a ListSelection. */ DeselectedList(EventList source) { super(source); } /** {@inheritDoc} */ @Override public int size() { return barcode.colourSize(DESELECTED); } /** {@inheritDoc} */ @Override protected int getSourceIndex(int mutationIndex) { return barcode.getIndex(mutationIndex, DESELECTED); } /** {@inheritDoc} */ @Override public void listChanged(ListEvent listChanges) { // Do nothing as all state changes are handled in ListSelection.listChanged() } /** * This allows access to the EventAssembler for this list. */ public ListEventAssembler updates() { return updates; } /** {@inheritDoc} */ @Override protected boolean isWritable() { return true; } /** * A no-op dispose method to prevent the user from shooting themselves * in the foot. To dispose a {@link ListSelection}, call * {@link ListSelection#dispose()} on that class directly. */ @Override public void dispose() { // Do Nothing } } /** * A DeselectedList that mutates the selection instead of the underlying list. */ private class DeselectionToggleList extends DeselectedList{ DeselectionToggleList(EventList source) { super(source); } /** @throws UnsupportedOperationException unconditionally */ @Override public E set(int index, E item){ throw new UnsupportedOperationException("Toggling lists don't support setting items"); } /** * Deselect the specified value. * * @throws IllegalArgumentException if the element isn't found */ @Override public void add(int index, E item) { index = source.indexOf(item); if(index != -1) { deselect(index); } else { throw new IllegalArgumentException("Added item " + item + " must be in source list"); } } /** * Select the specified index. */ @Override public E remove(int index){ if(index < 0 || index >= size()) throw new IndexOutOfBoundsException("Cannot remove at " + index + " on list of size " + size()); int sourceIndex = getSourceIndex(index); select(sourceIndex); return source.get(sourceIndex); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy