blob: 1beac98f6feebd4299b085647d16ecd75323a37f [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.view.client;
import com.google.gwt.view.client.SelectionModel.AbstractSelectionModel;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A simple selection model that allows multiple items to be selected.
*
* @param <T> the data type of the items
*/
public class MultiSelectionModel<T> extends AbstractSelectionModel<T>
implements SetSelectionModel<T> {
/**
* Stores an item and its pending selection state.
*
* @param <T> the data type of the item
*/
static class SelectionChange<T> {
private final T item;
private final boolean isSelected;
SelectionChange(T item, boolean isSelected) {
this.item = item;
this.isSelected = isSelected;
}
public T getItem() {
return item;
}
public boolean isSelected() {
return isSelected;
}
}
// Ensure one value per key
final Map<Object, T> selectedSet;
/**
* A map of keys to the item and its pending selection state.
*/
private final Map<Object, SelectionChange<T>> selectionChanges;
/**
* Constructs a MultiSelectionModel without a key provider.
*/
public MultiSelectionModel() {
this(null);
}
/**
* Constructs a MultiSelectionModel with the given key provider.
*
* @param keyProvider an instance of ProvidesKey<T>, or null if the item
* should act as its own key
*/
public MultiSelectionModel(ProvidesKey<T> keyProvider) {
this(keyProvider, new HashMap<Object, T>(), new HashMap<Object, SelectionChange<T>>());
}
/**
* Construct a MultiSelectionModel with the given key provider and
* implementations of selectedSet and selectionChanges. Different
* implementations allow for enforcing order on selection.
*
* @param keyProvider an instance of ProvidesKey<T>, or null if the item
* should act as its own key
* @param selectedSet an instance of Map
* @param selectionChanges an instance of Map
*/
MultiSelectionModel(ProvidesKey<T> keyProvider, Map<Object, T> selectedSet,
Map<Object, SelectionChange<T>> selectionChanges) {
super(keyProvider);
this.selectedSet = selectedSet;
this.selectionChanges = selectionChanges;
}
/**
* Deselect all selected values.
*/
@Override
public void clear() {
// Clear the current list of pending changes.
selectionChanges.clear();
/*
* Add a pending change to deselect each key that is currently selected. We
* cannot just clear the selected set, because then we would not know which
* keys were selected before we cleared, which we need to know to determine
* if we should fire an event.
*/
for (T value : selectedSet.values()) {
selectionChanges.put(getKey(value), new SelectionChange<T>(value, false));
}
scheduleSelectionChangeEvent();
}
/**
* Get the set of selected items as a copy. If multiple selected items share
* the same key, only the last selected item is included in the set.
*
* @return the set of selected items
*/
@Override
public Set<T> getSelectedSet() {
resolveChanges();
return new HashSet<T>(selectedSet.values());
}
@Override
public boolean isSelected(T item) {
resolveChanges();
return selectedSet.containsKey(getKey(item));
}
@Override
public void setSelected(T item, boolean selected) {
selectionChanges.put(getKey(item), new SelectionChange<T>(item, selected));
scheduleSelectionChangeEvent();
}
@Override
protected void fireSelectionChangeEvent() {
if (isEventScheduled()) {
setEventCancelled(true);
}
resolveChanges();
}
void resolveChanges() {
if (selectionChanges.isEmpty()) {
return;
}
boolean changed = false;
for (Map.Entry<Object, SelectionChange<T>> entry : selectionChanges.entrySet()) {
Object key = entry.getKey();
SelectionChange<T> value = entry.getValue();
boolean selected = value.isSelected;
T oldValue = selectedSet.get(key);
if (selected) {
selectedSet.put(key, value.item);
Object oldKey = getKey(oldValue);
if (!changed) {
changed = (oldKey == null) ? (key != null) : !oldKey.equals(key);
}
} else {
if (oldValue != null) {
selectedSet.remove(key);
changed = true;
}
}
}
selectionChanges.clear();
// Fire a selection change event.
if (changed) {
SelectionChangeEvent.fire(this);
}
}
}