blob: 4b9d9235dff8bf67813e225042c76db9a5be489d [file] [log] [blame]
package autotest.common.ui;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class MultiListSelectPresenter implements ClickHandler, DoubleClickHandler, ChangeHandler {
/* Simple display showing two list boxes, one of available items and one of selected items */
public interface DoubleListDisplay {
public HasClickHandlers getAddAllButton();
public HasClickHandlers getAddButton();
public HasClickHandlers getRemoveButton();
public HasClickHandlers getRemoveAllButton();
public HasClickHandlers getMoveUpButton();
public HasClickHandlers getMoveDownButton();
public SimplifiedList getAvailableList();
public SimplifiedList getSelectedList();
// ListBoxes don't support DoubleClickEvents themselves, so the display needs to handle them
public HandlerRegistration addDoubleClickHandler(DoubleClickHandler handler);
}
/* Optional additional display allowing toggle between a simple ListBox and a
* DoubleListSelector
*/
public interface ToggleDisplay {
public SimplifiedList getSingleSelector();
public ToggleControl getToggleMultipleLink();
public void setDoubleListVisible(boolean doubleListVisible);
}
public interface GeneratorHandler {
/**
* The given generated Item was just deselected; handle any necessary cleanup.
*/
public void onRemoveGeneratedItem(Item generatedItem);
}
public static class Item implements Comparable<Item> {
public String name;
public String value;
// a generated item is destroyed when deselected.
public boolean isGeneratedItem;
private boolean selected;
private Item(String name, String value) {
this.name = name;
this.value = value;
}
public static Item createItem(String name, String value) {
return new Item(name, value);
}
public static Item createGeneratedItem(String name, String value) {
Item item = new Item(name, value);
item.isGeneratedItem = true;
return item;
}
public int compareTo(Item item) {
return name.compareTo(item.name);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Item)) {
return false;
}
Item other = (Item) obj;
return name.equals(other.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return "Item<" + name + ", " + value + ">";
}
private boolean isSelected() {
if (isGeneratedItem) {
return true;
}
return selected;
}
private void setSelected(boolean selected) {
assert !isGeneratedItem;
this.selected = selected;
}
}
/**
* Null object to support displays that don't do toggling.
*/
private static class NullToggleDisplay implements ToggleDisplay {
@Override
public SimplifiedList getSingleSelector() {
return new SimplifiedList() {
@Override
public void addItem(String name, String value) {
return;
}
@Override
public void clear() {
return;
}
@Override
public String getSelectedName() {
return "";
}
@Override
public void selectByName(String name) {
return;
}
@Override
public HandlerRegistration addChangeHandler(ChangeHandler handler) {
throw new UnsupportedOperationException();
}
@Override
public void setEnabled(boolean enabled) {
throw new UnsupportedOperationException();
}
};
}
@Override
public ToggleControl getToggleMultipleLink() {
return new ToggleControl() {
@Override
public HandlerRegistration addClickHandler(ClickHandler handler) {
throw new UnsupportedOperationException();
}
@Override
public void fireEvent(GwtEvent<?> event) {
throw new UnsupportedOperationException();
}
@Override
public boolean isActive() {
return true;
}
@Override
public void setActive(boolean active) {
return;
}
};
}
@Override
public void setDoubleListVisible(boolean doubleListVisible) {
return;
}
}
private List<Item> items = new ArrayList<Item>();
// need a second list to track ordering
private List<Item> selectedItems = new ArrayList<Item>();
private DoubleListDisplay display;
private ToggleDisplay toggleDisplay = new NullToggleDisplay();
private GeneratorHandler generatorHandler;
public void setGeneratorHandler(GeneratorHandler handler) {
this.generatorHandler = handler;
}
public void bindDisplay(DoubleListDisplay display) {
this.display = display;
display.getAddAllButton().addClickHandler(this);
display.getAddButton().addClickHandler(this);
display.getRemoveButton().addClickHandler(this);
display.getRemoveAllButton().addClickHandler(this);
display.getMoveUpButton().addClickHandler(this);
display.getMoveDownButton().addClickHandler(this);
display.addDoubleClickHandler(this);
}
public void bindToggleDisplay(ToggleDisplay toggleDisplay) {
this.toggleDisplay = toggleDisplay;
toggleDisplay.getSingleSelector().addChangeHandler(this);
toggleDisplay.getToggleMultipleLink().addClickHandler(this);
toggleDisplay.getToggleMultipleLink().setActive(false);
}
private boolean verifyConsistency() {
// check consistency of selectedItems
for (Item item : items) {
if (item.isSelected() && !selectedItems.contains(item)) {
throw new RuntimeException("selectedItems is inconsistent, missing: "
+ item.toString());
}
}
return true;
}
public void addItem(Item item) {
if (item.isGeneratedItem && isItemPresent(item)) {
return;
}
items.add(item);
Collections.sort(items);
if (item.isSelected()) {
selectedItems.add(item);
}
assert verifyConsistency();
refresh();
}
private boolean isItemPresent(Item item) {
return Collections.binarySearch(items, item) >= 0;
}
private void removeItem(Item item) {
items.remove(item);
if (item.isSelected()) {
selectedItems.remove(item);
}
assert verifyConsistency();
refresh();
}
public void clearItems() {
for (Item item : new ArrayList<Item>(items)) {
removeItem(item);
}
}
private void refreshSingleSelector() {
SimplifiedList selector = toggleDisplay.getSingleSelector();
if (!selectedItems.isEmpty()) {
assert selectedItems.size() == 1;
}
selector.clear();
for (Item item : items) {
selector.addItem(item.name, item.value);
if (item.isSelected()) {
selector.selectByName(item.name);
}
}
}
private void refreshMultipleSelector() {
display.getAvailableList().clear();
for (Item item : items) {
if (!item.isSelected()) {
display.getAvailableList().addItem(item.name, item.value);
}
}
display.getSelectedList().clear();
for (Item item : selectedItems) {
display.getSelectedList().addItem(item.name, item.value);
}
}
private void refresh() {
if (selectedItems.size() > 1) {
switchToMultiple();
}
if (isMultipleSelectActive()) {
refreshMultipleSelector();
} else {
// single selector always needs something selected
if (selectedItems.size() == 0 && !items.isEmpty()) {
selectItem(items.get(0));
}
refreshSingleSelector();
}
}
private void selectItem(Item item) {
item.setSelected(true);
selectedItems.add(item);
assert verifyConsistency();
}
public void selectItemByName(String name) {
selectItem(getItemByName(name));
refresh();
}
/**
* Set the set of selected items by specifying item names. All names must exist in the set of
* header fields.
*/
public void setSelectedItemsByName(List<String> names) {
for (String itemName : names) {
Item item = getItemByName(itemName);
if (!item.isSelected()) {
selectItem(item);
}
}
Set<String> selectedNames = new HashSet<String>(names);
for (Item item : getItemsCopy()) {
if (item.isSelected() && !selectedNames.contains(item.name)) {
deselectItem(item);
}
}
if (selectedItems.size() < 2) {
switchToSingle();
}
refresh();
}
/**
* Set the set of selected items, silently dropping any that don't exist in the header field
* list.
*/
public void restoreSelectedItems(List<Item> items) {
List<String> currentItems = new ArrayList<String>();
for (Item item : items) {
if (hasItemName(item.name)) {
currentItems.add(item.name);
}
}
setSelectedItemsByName(currentItems);
}
private void deselectItem(Item item) {
if (item.isGeneratedItem) {
removeItem(item);
generatorHandler.onRemoveGeneratedItem(item);
} else {
item.setSelected(false);
selectedItems.remove(item);
}
assert verifyConsistency();
}
public List<Item> getSelectedItems() {
return new ArrayList<Item>(selectedItems);
}
private boolean isMultipleSelectActive() {
return toggleDisplay.getToggleMultipleLink().isActive();
}
private void switchToSingle() {
// reduce selection to the first selected item
while (selectedItems.size() > 1) {
deselectItem(selectedItems.get(1));
}
toggleDisplay.setDoubleListVisible(false);
toggleDisplay.getToggleMultipleLink().setActive(false);
}
private void switchToMultiple() {
toggleDisplay.setDoubleListVisible(true);
toggleDisplay.getToggleMultipleLink().setActive(true);
}
private Item getItemByName(String name) {
Item item = findItem(name);
if (item != null) {
return item;
}
throw new IllegalArgumentException("Item '" + name + "' does not exist in " + items);
}
private Item findItem(String name) {
for (Item item : items) {
if (item.name.equals(name)) {
return item;
}
}
return null;
}
public boolean hasItemName(String name) {
return findItem(name) != null;
}
@Override
public void onClick(ClickEvent event) {
boolean isItemSelectedOnLeft = display.getAvailableList().getSelectedName() != null;
boolean isItemSelectedOnRight = display.getSelectedList().getSelectedName() != null;
Object source = event.getSource();
if (source == display.getAddAllButton()) {
addAll();
} else if (source == display.getAddButton() && isItemSelectedOnLeft) {
doSelect();
} else if (source == display.getRemoveButton() && isItemSelectedOnRight) {
doDeselect();
} else if (source == display.getRemoveAllButton()) {
deselectAll();
} else if ((source == display.getMoveUpButton() || source == display.getMoveDownButton())
&& isItemSelectedOnRight) {
reorderItem(source == display.getMoveUpButton());
return; // don't refresh again or we'll mess up the user's selection
} else if (source == toggleDisplay.getToggleMultipleLink()) {
if (toggleDisplay.getToggleMultipleLink().isActive()) {
switchToMultiple();
} else {
switchToSingle();
}
} else {
throw new RuntimeException("Unexpected ClickEvent from " + event.getSource());
}
refresh();
}
@Override
public void onDoubleClick(DoubleClickEvent event) {
Object source = event.getSource();
if (source == display.getAvailableList()) {
doSelect();
} else if (source == display.getSelectedList()) {
doDeselect();
} else {
// ignore double-clicks on other widgets
return;
}
refresh();
}
@Override
public void onChange(ChangeEvent event) {
assert toggleDisplay != null;
SimplifiedList selector = toggleDisplay.getSingleSelector();
assert event.getSource() == selector;
// events should only come from the single selector when it's active
assert !toggleDisplay.getToggleMultipleLink().isActive();
for (Item item : getItemsCopy()) {
if (item.isSelected()) {
deselectItem(item);
} else if (item.name.equals(selector.getSelectedName())) {
selectItem(item);
}
}
refresh();
}
/**
* Selecting or deselecting items can add or remove items (due to generators), so sometimes we
* need to iterate over a copy.
*/
private Iterable<Item> getItemsCopy() {
return new ArrayList<Item>(items);
}
private void doSelect() {
selectItem(getItemByName(display.getAvailableList().getSelectedName()));
}
private void doDeselect() {
deselectItem(getItemByName(display.getSelectedList().getSelectedName()));
}
private void addAll() {
for (Item item : items) {
if (!item.isSelected()) {
selectItem(item);
}
}
}
public void deselectAll() {
for (Item item : getItemsCopy()) {
if (item.isSelected()) {
deselectItem(item);
}
}
}
private void reorderItem(boolean moveUp) {
Item item = getItemByName(display.getSelectedList().getSelectedName());
int positionDelta = moveUp ? -1 : 1;
int newPosition = selectedItems.indexOf(item) + positionDelta;
newPosition = Math.max(0, Math.min(selectedItems.size() - 1, newPosition));
selectedItems.remove(item);
selectedItems.add(newPosition, item);
refresh();
display.getSelectedList().selectByName(item.name);
}
}