package org.ovirt.engine.ui.common.widget.table;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gwt.user.cellview.client.AbstractHasData;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SelectionModel.AbstractSelectionModel;
/**
* Selection model that allows multiple items to be selected, while preserving selection order.
*
* @param T Table row data type.
* @see com.google.gwt.view.client.MultiSelectionModel
*/
public class OrderedMultiSelectionModel<T> extends AbstractSelectionModel<T> {
// Selected items mapped by their keys
private final Map<Object, T> selectedSet = new LinkedHashMap<>();
// Selection changes to be resolved
private final Map<T, Boolean> selectionChanges = new LinkedHashMap<>();
private boolean multiSelectEnabled;
private boolean multiRangeSelectEnabled;
private AbstractHasData<T> dataDisplay;
private int lastSelectedRow = -1;
private int originSelectedRow = -1;
private final Set<Integer> disabledRows = new HashSet<>();
public OrderedMultiSelectionModel() {
super(null);
}
public OrderedMultiSelectionModel(ProvidesKey<T> keyProvider) {
super(keyProvider);
}
/**
* Turns multiple selection feature on or off.
*/
public void setMultiSelectEnabled(boolean multiSelectEnabled) {
this.multiSelectEnabled = multiSelectEnabled;
}
/**
* Turns multiple 'range' selection feature on or off.
*/
public void setMultiRangeSelectEnabled(boolean multiRangeSelectEnabled) {
this.multiRangeSelectEnabled = multiRangeSelectEnabled;
}
/**
* Deselects all selected values.
*/
public void clear() {
clearSelection();
scheduleSelectionChangeEvent();
}
void clearSelection() {
selectionChanges.clear();
for (T value : selectedSet.values()) {
selectionChanges.put(value, false);
}
}
/**
* Returns the list of selected items as a copy.
*/
public List<T> getSelectedList() {
resolveChanges();
return new ArrayList<>(selectedSet.values());
}
@Override
public boolean isSelected(T object) {
resolveChanges();
return selectedSet.containsKey(getKey(object));
}
@Override
public void setSelected(T object, boolean selected) {
if (multiSelectEnabled) {
selectionChanges.put(object, !isSelected(object));
originSelectedRow = -1;
} else if (multiRangeSelectEnabled) {
selectRange(object);
} else {
clearSelection();
selectionChanges.put(object, selected);
originSelectedRow = -1;
}
// Save last selected row index
lastSelectedRow = getRowIndexByObject(object);
scheduleSelectionChangeEvent();
}
@Override
protected void fireSelectionChangeEvent() {
if (isEventScheduled()) {
setEventCancelled(true);
}
resolveChanges();
}
void resolveChanges() {
Set<Object> selectedKeys = selectedSet.keySet();
List<Object> visibleKeys = new ArrayList<>();
for (T visible : dataDisplay.getVisibleItems()) {
visibleKeys.add(getKey(visible));
}
if (!visibleKeys.containsAll(selectedKeys)) {
for (Map.Entry<Object, T> selectedEntry : selectedSet.entrySet()) {
if (!visibleKeys.contains(selectedEntry.getKey())) {
selectionChanges.put(selectedEntry.getValue(), false);
}
}
}
if (selectionChanges.isEmpty()) {
return;
}
boolean changed = false;
for (Map.Entry<T, Boolean> entry : selectionChanges.entrySet()) {
T object = entry.getKey();
boolean selected = entry.getValue();
Object key = getKey(object);
T oldValue = selectedSet.get(key);
if (selected) {
if (oldValue == null || !oldValue.equals(object)) {
selectedSet.put(getKey(object), object);
changed = true;
}
} else {
if (oldValue != null) {
selectedSet.remove(key);
changed = true;
}
}
}
selectionChanges.clear();
if (changed) {
SelectionChangeEvent.fire(this);
}
}
// Select a range of multiple rows
private void selectRange(T object) {
// Select multiple selection origin row
if (originSelectedRow == -1) {
originSelectedRow = lastSelectedRow;
}
// Get start/end rows
int selectedRow = getRowIndexByObject(object);
int startRow = originSelectedRow < selectedRow ? originSelectedRow : selectedRow;
int endRow = originSelectedRow > selectedRow ? originSelectedRow : selectedRow;
int lastIndex = dataDisplay.getVisibleItems().size() - 1;
//Adjust the end row in cases where multiple items were deleted
endRow = endRow > lastIndex ? lastIndex : endRow;
// Clear current selection and select row in range
clearSelection();
for (int row = startRow; row <= endRow; row++) {
selectionChanges.put(dataDisplay.getVisibleItems().get(row), true);
}
}
// Get row's index by a specified row object
private int getRowIndexByObject(T object) {
for (int row = 0; row < dataDisplay.getRowCount(); row++) {
if (dataDisplay.getVisibleItems().get(row).equals(object)) {
return row;
}
}
return -1;
}
// Select a row with regarding a specified shift
private void selectRow(int shift) {
if (selectedSet.isEmpty() || dataDisplay == null) {
return;
}
int shiftSelectedRow = lastSelectedRow + shift;
int nextRow =
shiftSelectedRow >= 0 ?
shiftSelectedRow % dataDisplay.getRowCount() : shiftSelectedRow + dataDisplay.getRowCount();
if (disabledRows.contains(nextRow)) {
selectRow(shift > 0 ? shift + 1 : shift - 1);
return;
}
setSelected(dataDisplay.getVisibleItems().get(nextRow), true);
}
public void setDisabledRows(int... disabledRows) {
if (disabledRows != null) {
for (int i : disabledRows) {
this.disabledRows.add(i);
}
}
}
public int getLastSelectedRow() {
return lastSelectedRow;
}
public void selectNext() {
selectRow(1);
}
public void selectPrev() {
selectRow(-1);
}
public void selectAllPrev() {
selectRange(firstItem());
scheduleSelectionChangeEvent();
}
public void selectAllNext() {
selectRange(lastItem());
scheduleSelectionChangeEvent();
}
public void selectAll() {
clearSelection();
setMultiRangeSelectEnabled(false);
setMultiSelectEnabled(false);
setSelected(firstItem(), true);
selectRange(lastItem());
scheduleSelectionChangeEvent();
}
private T firstItem() {
return dataDisplay.getVisibleItems().get(0);
}
private T lastItem() {
return dataDisplay.getVisibleItems().get(dataDisplay.getVisibleItems().size() - 1);
}
public void setDataDisplay(AbstractHasData<T> dataDisplay) {
this.dataDisplay = dataDisplay;
}
}