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

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

There is a newer version: 1.9.1
Show newest version
/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists;

// core glazed lists
import ca.odell.glazedlists.event.*;
// access to the volatile implementation classes
import ca.odell.glazedlists.impl.adt.*;
// to store event info for forwarding on the deselected EventList
import java.util.*;

/**
 * An {@link EventList} to provide index-based selection features.  This
 * {@link EventList} does not perform a transformation on the source, but
 * instead provides two additional {@link EventList}s:
 * 
    *
  • {@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 * both of our 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 { /** * 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 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; /** observe the source list */ private final SourceListener sourceListener = new SourceListener(); /** 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 current selection colour */ private Object selected = Barcode.BLACK; /** the current deselection colour */ private Object deselected = Barcode.WHITE; /** the registered SelectionListeners */ private 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(sourceListener); } /** * 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. */ private class SourceListener implements ListEventListener { /** {@inheritDoc} */ public void listChanged(ListEvent listChanges) { // keep track of what used to be selected int minSelectionIndexBefore = getMinSelectionIndex(); int maxSelectionIndexBefore = getMaxSelectionIndex(); // 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(); // 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) { // delete selected values if(previouslySelected) { barcode.remove(index, 1); addSelectedChange(ListEvent.DELETE, previousSelectionIndex, previousSelectionIndex); // delete deselected values } else { int deselectedIndex = barcode.getColourIndex(index, deselected); addDeselectedChange(ListEvent.DELETE, deselectedIndex, deselectedIndex); barcode.remove(index, 1); } // when an element is inserted, it is selected if its index was selected } else if(changeType == ListEvent.INSERT) { // 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); addSelectedChange(ListEvent.INSERT, previousSelectionIndex, previousSelectionIndex); // do not select the inserted for single selection and defensive selection } else { barcode.add(index, deselected, 1); int deselectedIndex = barcode.getColourIndex(index, deselected); addDeselectedChange(ListEvent.INSERT, deselectedIndex, deselectedIndex); } // add a deselected value } else { barcode.add(index, deselected, 1); int deselectedIndex = barcode.getColourIndex(index, deselected); addDeselectedChange(ListEvent.INSERT, deselectedIndex, deselectedIndex); } // when an element is changed, assume selection stays the same } else if(changeType == ListEvent.UPDATE) { // update a selected value if(previouslySelected) { addSelectedChange(ListEvent.UPDATE, previousSelectionIndex, previousSelectionIndex); // update a deselected value } else { int deselectedIndex = barcode.getColourIndex(index, deselected); addDeselectedChange(ListEvent.UPDATE, deselectedIndex, deselectedIndex); } } // adjust other internal state anchorSelectionIndex = adjustIndex(anchorSelectionIndex, changeType, index); leadSelectionIndex = adjustIndex(leadSelectionIndex, changeType, index); } commitAll(); } // notify listeners of selection change if(minSelectionIndexBefore != -1 && maxSelectionIndexBefore != -1) { int minSelectionIndexAfter = getMinSelectionIndex(); 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); } } } /** * 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() { source.getReadWriteLock().writeLock().lock(); try { if(selectedList == null){ selectedList = new SelectedList(source); source.getPublisher().setRelatedListener(selectedList, sourceListener); } return selectedList; } finally { source.getReadWriteLock().writeLock().unlock(); } } /** * 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() { source.getReadWriteLock().writeLock().lock(); try { if(selectedToggleList == null){ selectedToggleList = new SelectionToggleList(source); source.getPublisher().setRelatedListener(selectedToggleList, sourceListener); } return selectedToggleList; } finally { source.getReadWriteLock().writeLock().unlock(); } } /** * 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() { source.getReadWriteLock().writeLock().lock(); try { if(deselectedList == null){ deselectedList = new DeselectedList(source); source.getPublisher().setRelatedListener(deselectedList, sourceListener); } return deselectedList; } finally { source.getReadWriteLock().writeLock().unlock(); } } /** * 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() { source.getReadWriteLock().writeLock().lock(); try { if(deselectedToggleList == null) { deselectedToggleList = new DeselectionToggleList(source); source.getPublisher().setRelatedListener(deselectedToggleList, sourceListener); } return deselectedToggleList; } finally { source.getReadWriteLock().writeLock().unlock(); } } /** * Get the {@link EventList} that selection is being managed for. */ public EventList getSource() { return source; } /** * Inverts the current selection. */ public void invertSelection() { // Switch what colour is considered to be selected if(selected == Barcode.BLACK) { selected = Barcode.WHITE; deselected = Barcode.BLACK; } else { selected = Barcode.BLACK; deselected = Barcode.WHITE; } // Clear the anchor and lead anchorSelectionIndex = -1; leadSelectionIndex = -1; // Update the selected list to reflect the selection inversion beginAll(); addSelectedChange(ListEvent.DELETE, 0, barcode.colourSize(deselected) - 1); addSelectedChange(ListEvent.INSERT, 0, barcode.colourSize(selected) - 1); // Update the deselected list to reflect the selection inversion addDeselectedChange(ListEvent.DELETE, 0, barcode.colourSize(selected) - 1); addDeselectedChange(ListEvent.INSERT, 0, barcode.colourSize(deselected) - 1); 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()); } // update anchor and lead anchorSelectionIndex = start; leadSelectionIndex = end; // alter selection accordingly setSubRangeOfRange(false, start, end, -1, -1); } /** * 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); } /** * Deselects all elements. */ public void deselectAll() { // keep track of how many selected elements there were int selectionChangeSize = barcode.colourSize(selected); // fast fail if there is no change to make if(selectionChangeSize == 0) return; // keep track of the range of values that were affected int firstAffectedIndex = -1; int lastAffectedIndex = -1; // update the deselected list while processing the change beginDeselected(); for(BarcodeIterator i = barcode.iterator(); i.hasNextColour(selected);) { i.nextColour(selected); if(firstAffectedIndex == -1) firstAffectedIndex = i.getIndex(); lastAffectedIndex = i.getIndex(); addDeselectedChange(ListEvent.INSERT, lastAffectedIndex, lastAffectedIndex); } // bulk update the barcode to be entirely deselected barcode.clear(); barcode.add(0, deselected, source.size()); commitDeselected(); beginSelected(); addSelectedChange(ListEvent.DELETE, 0, selectionChangeSize - 1); commitSelected(); // notify listeners of selection change 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; } } // update anchor and lead anchorSelectionIndex = start; leadSelectionIndex = end; // alter selection accordingly setSubRangeOfRange(true, start, end, -1, -1); } /** * 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); } private void addSelectEvent(BarcodeIterator i) { int deselectedIndex = i.getColourIndex(deselected); addSelectEvent(i.set(selected), deselectedIndex); } private void addSelectEvent(int selectIndex, int deselectIndex){ addDeselectedChange(ListEvent.DELETE, deselectIndex, deselectIndex); addSelectedChange(ListEvent.INSERT, selectIndex, selectIndex); } /** * 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() { // keep track of how many deselected elements there were int deselectionChangeSize = barcode.colourSize(deselected); // fast fail if there is nothing to change if(deselectionChangeSize == 0) return; // keep track of the range of values that were affected int firstAffectedIndex = -1; int lastAffectedIndex = -1; // update the selected list while processing the change beginSelected(); for(BarcodeIterator i = barcode.iterator(); i.hasNextColour(deselected);) { i.nextColour(deselected); if(firstAffectedIndex == -1) firstAffectedIndex = i.getIndex(); lastAffectedIndex = i.getIndex(); addSelectedChange(ListEvent.INSERT, lastAffectedIndex, lastAffectedIndex); } // bulk update the barcode to be entirely selected barcode.clear(); barcode.add(0, selected, source.size()); commitSelected(); // update the deselected list beginDeselected(); addDeselectedChange(ListEvent.DELETE, 0, deselectionChangeSize - 1); commitDeselected(); // notify listeners of selection change fireSelectionChanged(firstAffectedIndex, lastAffectedIndex); } /** * 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; } // update anchor and lead anchorSelectionIndex = start; leadSelectionIndex = end; // alter selection accordingly setSubRangeOfRange(true, start, end, getMinSelectionIndex(), getMaxSelectionIndex()); } /** * 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) { // 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()); //setSelection(anchorSelectionIndex); // select the interval between anchor and lead } else if(selectionMode == SINGLE_INTERVAL_SELECTION) { setSubRangeOfRange(true, anchorSelectionIndex, leadSelectionIndex, getMinSelectionIndex(), getMaxSelectionIndex()); //setSelection(anchorSelectionIndex, leadSelectionIndex); // select the interval between anchor and lead without deselecting anything } else { setSubRangeOfRange(true, anchorSelectionIndex, leadSelectionIndex, -1, -1); //select(anchorSelectionIndex, leadSelectionIndex); } } /** * Return the lead of the current selection. */ public int getLeadSelectionIndex() { return leadSelectionIndex; } /** * Set the lead selection index. */ public void setLeadSelectionIndex(int 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()); //setSelection(leadSelectionIndex); // select the interval between anchor and lead } else if(selectionMode == SINGLE_INTERVAL_SELECTION) { setSubRangeOfRange(true, anchorSelectionIndex, leadSelectionIndex, getMinSelectionIndex(), getMaxSelectionIndex()); //setSelection(anchorSelectionIndex, leadSelectionIndex); // select the interval between anchor and lead deselecting as necessary } else { setSubRangeOfRange(true, anchorSelectionIndex, leadSelectionIndex, anchorSelectionIndex, originalLeadIndex); } } 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 addDeselectEvent(BarcodeIterator i) { addDeselectEvent(i.getColourIndex(selected), i.set(deselected)); } private void addDeselectEvent(int selectIndex, int deselectIndex) { addSelectedChange(ListEvent.DELETE, selectIndex, selectIndex); addDeselectedChange(ListEvent.INSERT, deselectIndex, deselectIndex); } private void addSelectedChange(int type, int start, int end){ if(selectedList != null) selectedList.updates().addChange(type, start, end); if(selectedToggleList != null) selectedToggleList.updates().addChange(type, start, end); } private void addDeselectedChange(int type, int start, int end){ if(deselectedList != null) deselectedList.updates().addChange(type, start, end); if(deselectedToggleList != null) deselectedToggleList.updates().addChange(type, start, end); } 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) { // 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 affect 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); // when there's a change if(selectedBefore != selectedAfter) { // 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); // it is being selected } else { barcode.set(i, selected, 1); int newSelectionIndex = barcode.getColourIndex(i, selected); addSelectEvent(newSelectionIndex, i - newSelectionIndex); } } } commitAll(); // notify selection listeners if(minChangedIndex <= maxChangedIndex) fireSelectionChanged(minChangedIndex, maxChangedIndex); } /** * 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(); ) { Listener listener = i.next(); listener.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(sourceListener); selectionListeners.clear(); // detach the publisher dependencies if(selectedList != null) source.getPublisher().clearRelatedListener(selectedList, sourceListener); if(deselectedList != null) source.getPublisher().clearRelatedListener(deselectedList, sourceListener); if(selectedToggleList != null) source.getPublisher().clearRelatedListener(selectedToggleList, sourceListener); if(deselectedToggleList != null) source.getPublisher().clearRelatedListener(deselectedToggleList, sourceListener); } /** * 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} */ public int size() { return barcode.colourSize(selected); } /** {@inheritDoc} */ protected int getSourceIndex(int mutationIndex) { return barcode.getIndex(mutationIndex, selected); } /** {@inheritDoc} */ 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} */ 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. */ 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 */ 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 */ 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. */ 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} */ public int size() { return barcode.colourSize(deselected); } /** {@inheritDoc} */ protected int getSourceIndex(int mutationIndex) { return barcode.getIndex(mutationIndex, deselected); } /** {@inheritDoc} */ 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} */ 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. */ 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 */ 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 */ 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. */ 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