• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
getAddAllButton()23         public HasClickHandlers getAddAllButton();
getAddButton()24         public HasClickHandlers getAddButton();
getRemoveButton()25         public HasClickHandlers getRemoveButton();
getRemoveAllButton()26         public HasClickHandlers getRemoveAllButton();
getMoveUpButton()27         public HasClickHandlers getMoveUpButton();
getMoveDownButton()28         public HasClickHandlers getMoveDownButton();
getAvailableList()29         public SimplifiedList getAvailableList();
getSelectedList()30         public SimplifiedList getSelectedList();
31         // ListBoxes don't support DoubleClickEvents themselves, so the display needs to handle them
addDoubleClickHandler(DoubleClickHandler handler)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 {
getSingleSelector()39         public SimplifiedList getSingleSelector();
getToggleMultipleLink()40         public ToggleControl getToggleMultipleLink();
setDoubleListVisible(boolean doubleListVisible)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          */
onRemoveGeneratedItem(Item generatedItem)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 
Item(String name, String value)59         private Item(String name, String value) {
60             this.name = name;
61             this.value = value;
62         }
63 
createItem(String name, String value)64         public static Item createItem(String name, String value) {
65             return new Item(name, value);
66         }
67 
createGeneratedItem(String name, String value)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 
compareTo(Item item)74         public int compareTo(Item item) {
75             return name.compareTo(item.name);
76         }
77 
78         @Override
equals(Object obj)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
hashCode()88         public int hashCode() {
89             return name.hashCode();
90         }
91 
92         @Override
toString()93         public String toString() {
94             return "Item<" + name + ", " + value + ">";
95         }
96 
isSelected()97         private boolean isSelected() {
98             if (isGeneratedItem) {
99                 return true;
100             }
101             return selected;
102         }
103 
setSelected(boolean selected)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
getSingleSelector()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
getToggleMultipleLink()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