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

source.ca.odell.glazedlists.swing.EventSelectionModel 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.swing;

import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.ListSelection;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.matchers.Matcher;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.util.ArrayList;
import java.util.List;

/**
 * An {@link EventSelectionModel} is a class that performs two simulaneous
 * services. It is a {@link ListSelectionModel} to provide selection tracking for a
 * {@link JTable}. It is also a {@link EventList} that contains the table's selection.
 *
 * 

As elements are selected or deselected, the {@link EventList} aspect of this * {@link EventSelectionModel} changes. Changes to that {@link List} will change the * source {@link EventList}. To modify only the selection, use the * {@link ListSelectionModel}'s methods. * *

Alongside MULTIPLE_INTERVAL_SELECTION, this {@link ListSelectionModel} * supports an additional selection mode. * MULTIPLE_INTERVAL_SELECTION_DEFENSIVE is a new selection mode. * It is identical to MULTIPLE_INTERVAL_SELECTION in every way but * one. When a row is inserted immediately before a selected row in the * MULTIPLE_INTERVAL_SELECTION mode, it becomes selected. But in * the MULTIPLE_INTERVAL_SELECTION_DEFENSIVE mode, it does not * become selected. To set this mode, use * {@link #setSelectionMode(int) setSelectionMode(ListSelection.MULTIPLE_INTERVAL_SELECTION_DEFENSIVE)}. * * @see Bug 39 * @see Bug 61 * @see Bug 76 * @see Bug 108 * @see Bug 110 * @see Bug 112 * @see Bug 222 * * @author Jesse Wilson */ public final class EventSelectionModel implements ListSelectionModel { /** the event lists that provide an event list view of the selection */ private ListSelection listSelection; /** the proxy moves events to the Swing Event Dispatch thread */ private TransformedList swingThreadSource; /** whether the user can modify the selection */ private boolean enabled = true; /** listeners to notify when the selection changes */ private final List listeners = new ArrayList(); /** whether there are a series of changes on the way */ private boolean valueIsAdjusting = false; private int fullChangeStart = -1; private int fullChangeFinish = -1; /** * Creates a new selection model that also presents a list of the selection. * * The {@link EventSelectionModel} listens to this {@link EventList} in order * to adjust selection when the {@link EventList} is modified. For example, * when an element is added to the {@link EventList}, this may offset the * selection of the following elements. * * @param source the {@link EventList} whose selection will be managed. This should * be the same {@link EventList} passed to the constructor of your * {@link EventTableModel} or {@link EventListModel}. */ public EventSelectionModel(EventList source) { // lock the source list for reading since we want to prevent writes // from occurring until we fully initialize this EventSelectionModel source.getReadWriteLock().readLock().lock(); try { swingThreadSource = GlazedListsSwing.swingThreadProxyList(source); // build a list for reading the selection this.listSelection = new ListSelection(swingThreadSource); this.listSelection.addSelectionListener(new SwingSelectionListener()); } finally { source.getReadWriteLock().readLock().unlock(); } } /** * Gets the event list that always contains the current selection. * * @deprecated As of 2005/02/18, the naming of this method became * ambiguous. Please use {@link #getSelected()}. */ public EventList getEventList() { return getSelected(); } /** * 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() { swingThreadSource.getReadWriteLock().readLock().lock(); try { return listSelection.getSelected(); } finally { swingThreadSource.getReadWriteLock().readLock().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. */ public EventList getTogglingSelected() { swingThreadSource.getReadWriteLock().readLock().lock(); try { return listSelection.getTogglingSelected(); } finally { swingThreadSource.getReadWriteLock().readLock().unlock(); } } /** * Gets an {@link EventList} that contains only deselected 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 getDeselected() { swingThreadSource.getReadWriteLock().readLock().lock(); try { return listSelection.getDeselected(); } finally { swingThreadSource.getReadWriteLock().readLock().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 */ public EventList getTogglingDeselected() { swingThreadSource.getReadWriteLock().readLock().lock(); try { return listSelection.getTogglingDeselected(); } finally { swingThreadSource.getReadWriteLock().readLock().unlock(); } } /** * Gets the selection model that provides selection management for a table. * * @deprecated As of 2004/11/26, the {@link EventSelectionModel} implements * {@link ListSelectionModel} directly. */ public ListSelectionModel getListSelectionModel() { return this; } /** * Set the EventSelectionModel as editable or not. This means that the user cannot * manipulate the selection by clicking. The selection can still be changed as * the source list changes. * *

Note that this will also disable the selection from being modified * programatically. Therefore you must use setEnabled(true) to * modify the selection in code. */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /** * Returns whether the EventSelectionModel is editable or not. */ public boolean getEnabled() { return enabled; } /** * Listens to selection changes on the {@link ListSelection} and fires * {@link ListSelectionEvent}s to registered listeners. */ private class SwingSelectionListener implements ListSelection.Listener { /** {@inheritDoc} */ public void selectionChanged(int changeStart, int changeEnd) { fireSelectionChanged(changeStart, changeEnd); } } /** * Notify listeners that the selection has changed. * *

This notifies all listeners with the same immutable * ListSelectionEvent. */ private void fireSelectionChanged(int changeStart, int changeFinish) { // if this is a change in a series, save the bounds of this change if(valueIsAdjusting) { if(fullChangeStart == -1 || changeStart < fullChangeStart) fullChangeStart = changeStart; if(fullChangeFinish == -1 || changeFinish > fullChangeFinish) fullChangeFinish = changeFinish; } // fire the change final ListSelectionEvent event = new ListSelectionEvent(this, changeStart, changeFinish, valueIsAdjusting); for (int i = 0, n = listeners.size(); i < n; i++) listeners.get(i).valueChanged(event); } /** * Inverts the current selection. */ public void invertSelection() { swingThreadSource.getReadWriteLock().writeLock().lock(); try { listSelection.invertSelection(); } finally { swingThreadSource.getReadWriteLock().writeLock().unlock(); } } /** * Change the selection to be between index0 and index1 inclusive. * *

First this calculates the smallest range where changes occur. This * includes the union of the selection range before and the selection * range specified. It then walks through the change and sets each * index as selected or not based on whether the index is in the * new range. Finally it fires events to both the listening lists and * selection listeners about what changes happened. * *

If the selection does not change, this will not fire any events. */ public void setSelectionInterval(int index0, int index1) { if(!enabled) return; swingThreadSource.getReadWriteLock().writeLock().lock(); try { listSelection.setSelection(index0, index1); } finally { swingThreadSource.getReadWriteLock().writeLock().unlock(); } } /** * Change the selection to be the set union of the current selection and the indices between index0 and index1 inclusive */ public void addSelectionInterval(int index0, int index1) { if(!enabled) return; swingThreadSource.getReadWriteLock().writeLock().lock(); try { listSelection.select(index0, index1); } finally { swingThreadSource.getReadWriteLock().writeLock().unlock(); } } /** * Change the selection to be the set difference of the current selection and the indices between index0 and index1 inclusive. */ public void removeSelectionInterval(int index0, int index1) { if(!enabled) return; if(index0 == 0 && index1 == 0 && swingThreadSource.isEmpty()) return; // hack for Java 5 compatibility swingThreadSource.getReadWriteLock().writeLock().lock(); try { listSelection.deselect(index0, index1); } finally { swingThreadSource.getReadWriteLock().writeLock().unlock(); } } /** * Returns true if the specified index is selected. If the specified * index has not been seen before, this will return false. This is * in the case where the table painting and selection have fallen * out of sync. Usually in this case there is an update event waiting * in the event queue that notifies this model of the change * in table size. */ public boolean isSelectedIndex(int index) { return (listSelection.isSelected(index)); } /** * Return the first index argument from the most recent call to setSelectionInterval(), addSelectionInterval() or removeSelectionInterval(). */ public int getAnchorSelectionIndex() { return listSelection.getAnchorSelectionIndex(); } /** * Set the anchor selection index. */ public void setAnchorSelectionIndex(int anchorSelectionIndex) { if(!enabled) return; swingThreadSource.getReadWriteLock().writeLock().lock(); try { listSelection.setAnchorSelectionIndex(anchorSelectionIndex); } finally { swingThreadSource.getReadWriteLock().writeLock().unlock(); } } /** * Return the second index argument from the most recent call to setSelectionInterval(), addSelectionInterval() or removeSelectionInterval(). */ public int getLeadSelectionIndex() { return listSelection.getLeadSelectionIndex(); } /** * Set the lead selection index. */ public void setLeadSelectionIndex(int leadSelectionIndex) { if(!enabled) return; swingThreadSource.getReadWriteLock().writeLock().lock(); try { listSelection.setLeadSelectionIndex(leadSelectionIndex); } finally { swingThreadSource.getReadWriteLock().writeLock().unlock(); } } /** * Gets the index of the first selected element. */ public int getMinSelectionIndex() { return listSelection.getMinSelectionIndex(); } /** * Gets the index of the last selected element. */ public int getMaxSelectionIndex() { return listSelection.getMaxSelectionIndex(); } /** * Change the selection to the empty set. */ public void clearSelection() { if(!enabled) return; swingThreadSource.getReadWriteLock().writeLock().lock(); try { listSelection.deselectAll(); } finally { swingThreadSource.getReadWriteLock().writeLock().unlock(); } } /** * Returns true if no indices are selected. */ public boolean isSelectionEmpty() { swingThreadSource.getReadWriteLock().readLock().lock(); try { return listSelection.getSelected().isEmpty(); } finally { swingThreadSource.getReadWriteLock().readLock().unlock(); } } /** * Insert length indices beginning before/after index. */ public void insertIndexInterval(int index, int length, boolean before) { // these changes are handled by the ListSelection } /** * Remove the indices in the interval index0,index1 (inclusive) from the selection model. */ public void removeIndexInterval(int index0, int index1) { // these changes are handled by the ListSelection } /** * This property is true if upcoming changes to the value of the model should be considered a single event. */ public void setValueIsAdjusting(boolean valueIsAdjusting) { this.valueIsAdjusting = valueIsAdjusting; // fire one extra change containing all changes in this set if(!valueIsAdjusting) { if(fullChangeStart != -1 && fullChangeFinish != -1) { swingThreadSource.getReadWriteLock().writeLock().lock(); try { fireSelectionChanged(fullChangeStart, fullChangeFinish); fullChangeStart = -1; fullChangeFinish = -1; } finally { swingThreadSource.getReadWriteLock().writeLock().unlock(); } } } } /** * Returns true if the value is undergoing a series of changes. */ public boolean getValueIsAdjusting() { return valueIsAdjusting; } /** * Set the selection mode. */ public void setSelectionMode(int selectionMode) { swingThreadSource.getReadWriteLock().writeLock().lock(); try { listSelection.setSelectionMode(selectionMode); } finally { swingThreadSource.getReadWriteLock().writeLock().unlock(); } } /** * Returns the current selection mode. */ public int getSelectionMode() { return listSelection.getSelectionMode(); } /** * Add a matcher which decides when source elements are valid for selection. * * @param validSelectionMatcher returns true if a source element * can be selected; false otherwise */ public void addValidSelectionMatcher(Matcher validSelectionMatcher) { listSelection.addValidSelectionMatcher(validSelectionMatcher); } /** * Remove a matcher which decides when source elements are valid for selection. * * @param validSelectionMatcher returns true if a source element * can be selected; false otherwise */ public void removeValidSelectionMatcher(Matcher validSelectionMatcher) { listSelection.removeValidSelectionMatcher(validSelectionMatcher); } /** * Add a listener to the list that's notified each time a change to * the selection occurs. * * Note that the change events fired by this class may include rows * that have been removed from the table. For this reason it is * advised not to for() through the changed range without * also verifying that each row is still in the table. */ public void addListSelectionListener(ListSelectionListener listener) { listeners.add(listener); } /** * Remove a listener from the list that's notified each time a change to the selection occurs. */ public void removeListSelectionListener(ListSelectionListener listener) { listeners.remove(listener); } /** * Releases the resources consumed by this {@link EventSelectionModel} so that it * may eventually be garbage collected. * *

An {@link EventSelectionModel} will be garbage collected without a call to * {@link #dispose()}, but not before its source {@link EventList} is garbage * collected. By calling {@link #dispose()}, you allow the {@link EventSelectionModel} * to be garbage collected before its source {@link EventList}. This is * necessary for situations where an {@link EventSelectionModel} is short-lived but * its source {@link EventList} is long-lived. * *

Warning: It is an error * to call any method on a {@link EventSelectionModel} after it has been disposed. */ public void dispose() { listSelection.dispose(); swingThreadSource.dispose(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy