Home | History | Annotate | Download | only in ui
      1 package autotest.common.ui;
      2 
      3 import com.google.gwt.event.dom.client.ChangeEvent;
      4 import com.google.gwt.event.dom.client.ChangeHandler;
      5 import com.google.gwt.event.dom.client.ClickEvent;
      6 import com.google.gwt.event.dom.client.ClickHandler;
      7 import com.google.gwt.event.dom.client.DoubleClickEvent;
      8 import com.google.gwt.event.dom.client.DoubleClickHandler;
      9 import com.google.gwt.event.dom.client.HasClickHandlers;
     10 import com.google.gwt.event.shared.GwtEvent;
     11 import com.google.gwt.event.shared.HandlerRegistration;
     12 
     13 import java.util.ArrayList;
     14 import java.util.Collections;
     15 import java.util.HashSet;
     16 import java.util.List;
     17 import java.util.Set;
     18 
     19 
     20 public class MultiListSelectPresenter implements ClickHandler, DoubleClickHandler, ChangeHandler {
     21     /* Simple display showing two list boxes, one of available items and one of selected items */
     22     public interface DoubleListDisplay {
     23         public HasClickHandlers getAddAllButton();
     24         public HasClickHandlers getAddButton();
     25         public HasClickHandlers getRemoveButton();
     26         public HasClickHandlers getRemoveAllButton();
     27         public HasClickHandlers getMoveUpButton();
     28         public HasClickHandlers getMoveDownButton();
     29         public SimplifiedList getAvailableList();
     30         public SimplifiedList getSelectedList();
     31         // ListBoxes don't support DoubleClickEvents themselves, so the display needs to handle them
     32         public HandlerRegistration addDoubleClickHandler(DoubleClickHandler handler);
     33     }
     34 
     35     /* Optional additional display allowing toggle between a simple ListBox and a
     36      * DoubleListSelector
     37      */
     38     public interface ToggleDisplay {
     39         public SimplifiedList getSingleSelector();
     40         public ToggleControl getToggleMultipleLink();
     41         public void setDoubleListVisible(boolean doubleListVisible);
     42     }
     43 
     44     public interface GeneratorHandler {
     45         /**
     46          * The given generated Item was just deselected; handle any necessary cleanup.
     47          */
     48         public void onRemoveGeneratedItem(Item generatedItem);
     49     }
     50 
     51     public static class Item implements Comparable<Item> {
     52         public String name;
     53         public String value;
     54         // a generated item is destroyed when deselected.
     55         public boolean isGeneratedItem;
     56 
     57         private boolean selected;
     58 
     59         private Item(String name, String value) {
     60             this.name = name;
     61             this.value = value;
     62         }
     63 
     64         public static Item createItem(String name, String value) {
     65             return new Item(name, value);
     66         }
     67 
     68         public static Item createGeneratedItem(String name, String value) {
     69             Item item = new Item(name, value);
     70             item.isGeneratedItem = true;
     71             return item;
     72         }
     73 
     74         public int compareTo(Item item) {
     75             return name.compareTo(item.name);
     76         }
     77 
     78         @Override
     79         public boolean equals(Object obj) {
     80             if (!(obj instanceof Item)) {
     81                 return false;
     82             }
     83             Item other = (Item) obj;
     84             return name.equals(other.name);
     85         }
     86 
     87         @Override
     88         public int hashCode() {
     89             return name.hashCode();
     90         }
     91 
     92         @Override
     93         public String toString() {
     94             return "Item<" + name + ", " + value + ">";
     95         }
     96 
     97         private boolean isSelected() {
     98             if (isGeneratedItem) {
     99                 return true;
    100             }
    101             return selected;
    102         }
    103 
    104         private void setSelected(boolean selected) {
    105             assert !isGeneratedItem;
    106             this.selected = selected;
    107         }
    108     }
    109 
    110     /**
    111      * Null object to support displays that don't do toggling.
    112      */
    113     private static class NullToggleDisplay implements ToggleDisplay {
    114         @Override
    115         public SimplifiedList getSingleSelector() {
    116             return new SimplifiedList() {
    117                 @Override
    118                 public void addItem(String name, String value) {
    119                     return;
    120                 }
    121 
    122                 @Override
    123                 public void clear() {
    124                     return;
    125                 }
    126 
    127                 @Override
    128                 public String getSelectedName() {
    129                     return "";
    130                 }
    131 
    132                 @Override
    133                 public void selectByName(String name) {
    134                     return;
    135                 }
    136 
    137                 @Override
    138                 public HandlerRegistration addChangeHandler(ChangeHandler handler) {
    139                     throw new UnsupportedOperationException();
    140                 }
    141 
    142                 @Override
    143                 public void setEnabled(boolean enabled) {
    144                     throw new UnsupportedOperationException();
    145                 }
    146             };
    147         }
    148 
    149         @Override
    150         public ToggleControl getToggleMultipleLink() {
    151             return new ToggleControl() {
    152                 @Override
    153                 public HandlerRegistration addClickHandler(ClickHandler handler) {
    154                     throw new UnsupportedOperationException();
    155                 }
    156 
    157                 @Override
    158                 public void fireEvent(GwtEvent<?> event) {
    159                     throw new UnsupportedOperationException();
    160                 }
    161 
    162                 @Override
    163                 public boolean isActive() {
    164                     return true;
    165                 }
    166 
    167                 @Override
    168                 public void setActive(boolean active) {
    169                     return;
    170                 }
    171             };
    172         }
    173 
    174         @Override
    175         public void setDoubleListVisible(boolean doubleListVisible) {
    176             return;
    177         }
    178     }
    179 
    180     private List<Item> items = new ArrayList<Item>();
    181     // need a second list to track ordering
    182     private List<Item> selectedItems = new ArrayList<Item>();
    183     private DoubleListDisplay display;
    184     private ToggleDisplay toggleDisplay = new NullToggleDisplay();
    185     private GeneratorHandler generatorHandler;
    186 
    187     public void setGeneratorHandler(GeneratorHandler handler) {
    188         this.generatorHandler = handler;
    189     }
    190 
    191     public void bindDisplay(DoubleListDisplay display) {
    192         this.display = display;
    193         display.getAddAllButton().addClickHandler(this);
    194         display.getAddButton().addClickHandler(this);
    195         display.getRemoveButton().addClickHandler(this);
    196         display.getRemoveAllButton().addClickHandler(this);
    197         display.getMoveUpButton().addClickHandler(this);
    198         display.getMoveDownButton().addClickHandler(this);
    199         display.addDoubleClickHandler(this);
    200     }
    201 
    202     public void bindToggleDisplay(ToggleDisplay toggleDisplay) {
    203         this.toggleDisplay = toggleDisplay;
    204         toggleDisplay.getSingleSelector().addChangeHandler(this);
    205         toggleDisplay.getToggleMultipleLink().addClickHandler(this);
    206         toggleDisplay.getToggleMultipleLink().setActive(false);
    207     }
    208 
    209     private boolean verifyConsistency() {
    210         // check consistency of selectedItems
    211         for (Item item : items) {
    212             if (item.isSelected() && !selectedItems.contains(item)) {
    213                 throw new RuntimeException("selectedItems is inconsistent, missing: "
    214                                            + item.toString());
    215             }
    216         }
    217         return true;
    218     }
    219 
    220     public void addItem(Item item) {
    221         if (item.isGeneratedItem && isItemPresent(item)) {
    222             return;
    223         }
    224         items.add(item);
    225         Collections.sort(items);
    226         if (item.isSelected()) {
    227             selectedItems.add(item);
    228         }
    229         assert verifyConsistency();
    230         refresh();
    231     }
    232 
    233     private boolean isItemPresent(Item item) {
    234         return Collections.binarySearch(items, item) >= 0;
    235     }
    236 
    237     private void removeItem(Item item) {
    238         items.remove(item);
    239         if (item.isSelected()) {
    240             selectedItems.remove(item);
    241         }
    242         assert verifyConsistency();
    243         refresh();
    244     }
    245 
    246     public void clearItems() {
    247         for (Item item : new ArrayList<Item>(items)) {
    248             removeItem(item);
    249         }
    250     }
    251 
    252     private void refreshSingleSelector() {
    253         SimplifiedList selector = toggleDisplay.getSingleSelector();
    254 
    255         if (!selectedItems.isEmpty()) {
    256             assert selectedItems.size() == 1;
    257         }
    258 
    259         selector.clear();
    260         for (Item item : items) {
    261             selector.addItem(item.name, item.value);
    262             if (item.isSelected()) {
    263                 selector.selectByName(item.name);
    264             }
    265         }
    266     }
    267 
    268     private void refreshMultipleSelector() {
    269         display.getAvailableList().clear();
    270         for (Item item : items) {
    271             if (!item.isSelected()) {
    272                 display.getAvailableList().addItem(item.name, item.value);
    273             }
    274         }
    275 
    276         display.getSelectedList().clear();
    277         for (Item item : selectedItems) {
    278             display.getSelectedList().addItem(item.name, item.value);
    279         }
    280     }
    281 
    282     private void refresh() {
    283         if (selectedItems.size() > 1) {
    284             switchToMultiple();
    285         }
    286         if (isMultipleSelectActive()) {
    287             refreshMultipleSelector();
    288         } else {
    289             // single selector always needs something selected
    290             if (selectedItems.size() == 0 && !items.isEmpty()) {
    291                 selectItem(items.get(0));
    292             }
    293             refreshSingleSelector();
    294         }
    295     }
    296 
    297     private void selectItem(Item item) {
    298         item.setSelected(true);
    299         selectedItems.add(item);
    300         assert verifyConsistency();
    301     }
    302 
    303     public void selectItemByName(String name) {
    304         selectItem(getItemByName(name));
    305         refresh();
    306     }
    307 
    308     /**
    309      * Set the set of selected items by specifying item names.  All names must exist in the set of
    310      * header fields.
    311      */
    312     public void setSelectedItemsByName(List<String> names) {
    313         for (String itemName : names) {
    314             Item item = getItemByName(itemName);
    315             if (!item.isSelected()) {
    316                 selectItem(item);
    317             }
    318         }
    319 
    320         Set<String> selectedNames = new HashSet<String>(names);
    321         for (Item item : getItemsCopy()) {
    322             if (item.isSelected() && !selectedNames.contains(item.name)) {
    323                 deselectItem(item);
    324             }
    325         }
    326 
    327         if (selectedItems.size() < 2) {
    328             switchToSingle();
    329         }
    330         refresh();
    331     }
    332 
    333     /**
    334      * Set the set of selected items, silently dropping any that don't exist in the header field
    335      * list.
    336      */
    337     public void restoreSelectedItems(List<Item> items) {
    338         List<String> currentItems = new ArrayList<String>();
    339         for (Item item : items) {
    340             if (hasItemName(item.name)) {
    341                 currentItems.add(item.name);
    342             }
    343         }
    344         setSelectedItemsByName(currentItems);
    345     }
    346 
    347     private void deselectItem(Item item) {
    348         if (item.isGeneratedItem) {
    349             removeItem(item);
    350             generatorHandler.onRemoveGeneratedItem(item);
    351         } else {
    352             item.setSelected(false);
    353             selectedItems.remove(item);
    354         }
    355         assert verifyConsistency();
    356     }
    357 
    358     public List<Item> getSelectedItems() {
    359         return new ArrayList<Item>(selectedItems);
    360     }
    361 
    362     private boolean isMultipleSelectActive() {
    363         return toggleDisplay.getToggleMultipleLink().isActive();
    364     }
    365 
    366     private void switchToSingle() {
    367         // reduce selection to the first selected item
    368         while (selectedItems.size() > 1) {
    369             deselectItem(selectedItems.get(1));
    370         }
    371 
    372         toggleDisplay.setDoubleListVisible(false);
    373         toggleDisplay.getToggleMultipleLink().setActive(false);
    374     }
    375 
    376     private void switchToMultiple() {
    377         toggleDisplay.setDoubleListVisible(true);
    378         toggleDisplay.getToggleMultipleLink().setActive(true);
    379     }
    380 
    381     private Item getItemByName(String name) {
    382         Item item = findItem(name);
    383         if (item != null) {
    384             return item;
    385         }
    386         throw new IllegalArgumentException("Item '" + name + "' does not exist in " + items);
    387     }
    388 
    389     private Item findItem(String name) {
    390         for (Item item : items) {
    391             if (item.name.equals(name)) {
    392                 return item;
    393             }
    394         }
    395         return null;
    396     }
    397 
    398     public boolean hasItemName(String name) {
    399         return findItem(name) != null;
    400     }
    401 
    402     @Override
    403     public void onClick(ClickEvent event) {
    404         boolean isItemSelectedOnLeft = display.getAvailableList().getSelectedName() != null;
    405         boolean isItemSelectedOnRight = display.getSelectedList().getSelectedName() != null;
    406         Object source = event.getSource();
    407         if (source == display.getAddAllButton()) {
    408             addAll();
    409         } else if (source == display.getAddButton() && isItemSelectedOnLeft) {
    410             doSelect();
    411         } else if (source == display.getRemoveButton() && isItemSelectedOnRight) {
    412             doDeselect();
    413         } else if (source == display.getRemoveAllButton()) {
    414             deselectAll();
    415         } else if ((source == display.getMoveUpButton() || source == display.getMoveDownButton())
    416                    && isItemSelectedOnRight) {
    417             reorderItem(source == display.getMoveUpButton());
    418             return; // don't refresh again or we'll mess up the user's selection
    419         } else if (source == toggleDisplay.getToggleMultipleLink()) {
    420             if (toggleDisplay.getToggleMultipleLink().isActive()) {
    421                 switchToMultiple();
    422             } else {
    423                 switchToSingle();
    424             }
    425         } else {
    426             throw new RuntimeException("Unexpected ClickEvent from " + event.getSource());
    427         }
    428 
    429         refresh();
    430     }
    431 
    432     @Override
    433     public void onDoubleClick(DoubleClickEvent event) {
    434         Object source = event.getSource();
    435         if (source == display.getAvailableList()) {
    436             doSelect();
    437         } else if (source == display.getSelectedList()) {
    438             doDeselect();
    439         } else {
    440             // ignore double-clicks on other widgets
    441             return;
    442         }
    443 
    444         refresh();
    445     }
    446 
    447     @Override
    448     public void onChange(ChangeEvent event) {
    449         assert toggleDisplay != null;
    450         SimplifiedList selector = toggleDisplay.getSingleSelector();
    451         assert event.getSource() == selector;
    452         // events should only come from the single selector when it's active
    453         assert !toggleDisplay.getToggleMultipleLink().isActive();
    454 
    455         for (Item item : getItemsCopy()) {
    456             if (item.isSelected()) {
    457                 deselectItem(item);
    458             } else if (item.name.equals(selector.getSelectedName())) {
    459                 selectItem(item);
    460             }
    461         }
    462 
    463         refresh();
    464     }
    465 
    466     /**
    467      * Selecting or deselecting items can add or remove items (due to generators), so sometimes we
    468      * need to iterate over a copy.
    469      */
    470     private Iterable<Item> getItemsCopy() {
    471         return new ArrayList<Item>(items);
    472     }
    473 
    474     private void doSelect() {
    475         selectItem(getItemByName(display.getAvailableList().getSelectedName()));
    476     }
    477 
    478     private void doDeselect() {
    479         deselectItem(getItemByName(display.getSelectedList().getSelectedName()));
    480     }
    481 
    482     private void addAll() {
    483         for (Item item : items) {
    484             if (!item.isSelected()) {
    485                 selectItem(item);
    486             }
    487         }
    488     }
    489 
    490     public void deselectAll() {
    491         for (Item item : getItemsCopy()) {
    492             if (item.isSelected()) {
    493                 deselectItem(item);
    494             }
    495         }
    496     }
    497 
    498     private void reorderItem(boolean moveUp) {
    499         Item item = getItemByName(display.getSelectedList().getSelectedName());
    500         int positionDelta = moveUp ? -1 : 1;
    501         int newPosition = selectedItems.indexOf(item) + positionDelta;
    502         newPosition = Math.max(0, Math.min(selectedItems.size() - 1, newPosition));
    503         selectedItems.remove(item);
    504         selectedItems.add(newPosition, item);
    505         refresh();
    506         display.getSelectedList().selectByName(item.name);
    507     }
    508 }
    509