Home | History | Annotate | Download | only in utils
      1 
      2 package com.badlogic.gdx.scenes.scene2d.utils;
      3 
      4 import com.badlogic.gdx.scenes.scene2d.Actor;
      5 import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener.ChangeEvent;
      6 import com.badlogic.gdx.utils.Array;
      7 import com.badlogic.gdx.utils.OrderedSet;
      8 import com.badlogic.gdx.utils.Pools;
      9 
     10 import java.util.Iterator;
     11 
     12 /** Manages selected objects. Optionally fires a {@link ChangeEvent} on an actor. Selection changes can be vetoed via
     13  * {@link ChangeEvent#cancel()}.
     14  * @author Nathan Sweet */
     15 public class Selection<T> implements Disableable, Iterable<T> {
     16 	private Actor actor;
     17 	final OrderedSet<T> selected = new OrderedSet();
     18 	private final OrderedSet<T> old = new OrderedSet();
     19 	boolean isDisabled;
     20 	private boolean toggle;
     21 	boolean multiple;
     22 	boolean required;
     23 	private boolean programmaticChangeEvents = true;
     24 	T lastSelected;
     25 
     26 	/** @param actor An actor to fire {@link ChangeEvent} on when the selection changes, or null. */
     27 	public void setActor (Actor actor) {
     28 		this.actor = actor;
     29 	}
     30 
     31 	/** Selects or deselects the specified item based on how the selection is configured, whether ctrl is currently pressed, etc.
     32 	 * This is typically invoked by user interaction. */
     33 	public void choose (T item) {
     34 		if (item == null) throw new IllegalArgumentException("item cannot be null.");
     35 		if (isDisabled) return;
     36 		snapshot();
     37 		try {
     38 			if ((toggle || (!required && selected.size == 1) || UIUtils.ctrl()) && selected.contains(item)) {
     39 				if (required && selected.size == 1) return;
     40 				selected.remove(item);
     41 				lastSelected = null;
     42 			} else {
     43 				boolean modified = false;
     44 				if (!multiple || (!toggle && !UIUtils.ctrl())) {
     45 					if (selected.size == 1 && selected.contains(item)) return;
     46 					modified = selected.size > 0;
     47 					selected.clear();
     48 				}
     49 				if (!selected.add(item) && !modified) return;
     50 				lastSelected = item;
     51 			}
     52 			if (fireChangeEvent()) revert();
     53 		} finally {
     54 			cleanup();
     55 		}
     56 	}
     57 
     58 	public boolean hasItems () {
     59 		return selected.size > 0;
     60 	}
     61 
     62 	public boolean isEmpty () {
     63 		return selected.size == 0;
     64 	}
     65 
     66 	public int size () {
     67 		return selected.size;
     68 	}
     69 
     70 	public OrderedSet<T> items () {
     71 		return selected;
     72 	}
     73 
     74 	/** Returns the first selected item, or null. */
     75 	public T first () {
     76 		return selected.size == 0 ? null : selected.first();
     77 	}
     78 
     79 	void snapshot () {
     80 		old.clear();
     81 		old.addAll(selected);
     82 	}
     83 
     84 	void revert () {
     85 		selected.clear();
     86 		selected.addAll(old);
     87 	}
     88 
     89 	void cleanup () {
     90 		old.clear(32);
     91 	}
     92 
     93 	/** Sets the selection to only the specified item. */
     94 	public void set (T item) {
     95 		if (item == null) throw new IllegalArgumentException("item cannot be null.");
     96 		if (selected.size == 1 && selected.first() == item) return;
     97 		snapshot();
     98 		selected.clear();
     99 		selected.add(item);
    100 		if (programmaticChangeEvents && fireChangeEvent())
    101 			revert();
    102 		else
    103 			lastSelected = item;
    104 		cleanup();
    105 	}
    106 
    107 	public void setAll (Array<T> items) {
    108 		boolean added = false;
    109 		snapshot();
    110 		selected.clear();
    111 		for (int i = 0, n = items.size; i < n; i++) {
    112 			T item = items.get(i);
    113 			if (item == null) throw new IllegalArgumentException("item cannot be null.");
    114 			if (selected.add(item)) added = true;
    115 		}
    116 		if (added && programmaticChangeEvents && fireChangeEvent())
    117 			revert();
    118 		else
    119 			lastSelected = items.peek();
    120 		cleanup();
    121 	}
    122 
    123 	/** Adds the item to the selection. */
    124 	public void add (T item) {
    125 		if (item == null) throw new IllegalArgumentException("item cannot be null.");
    126 		if (!selected.add(item)) return;
    127 		if (programmaticChangeEvents && fireChangeEvent())
    128 			selected.remove(item);
    129 		else
    130 			lastSelected = item;
    131 	}
    132 
    133 	public void addAll (Array<T> items) {
    134 		boolean added = false;
    135 		snapshot();
    136 		for (int i = 0, n = items.size; i < n; i++) {
    137 			T item = items.get(i);
    138 			if (item == null) throw new IllegalArgumentException("item cannot be null.");
    139 			if (selected.add(item)) added = true;
    140 		}
    141 		if (added && programmaticChangeEvents && fireChangeEvent())
    142 			revert();
    143 		else
    144 			lastSelected = items.peek();
    145 		cleanup();
    146 	}
    147 
    148 	public void remove (T item) {
    149 		if (item == null) throw new IllegalArgumentException("item cannot be null.");
    150 		if (!selected.remove(item)) return;
    151 		if (programmaticChangeEvents && fireChangeEvent())
    152 			selected.add(item);
    153 		else
    154 			lastSelected = null;
    155 	}
    156 
    157 	public void removeAll (Array<T> items) {
    158 		boolean removed = false;
    159 		snapshot();
    160 		for (int i = 0, n = items.size; i < n; i++) {
    161 			T item = items.get(i);
    162 			if (item == null) throw new IllegalArgumentException("item cannot be null.");
    163 			if (selected.remove(item)) removed = true;
    164 		}
    165 		if (removed && programmaticChangeEvents && fireChangeEvent())
    166 			revert();
    167 		else
    168 			lastSelected = null;
    169 		cleanup();
    170 	}
    171 
    172 	public void clear () {
    173 		if (selected.size == 0) return;
    174 		snapshot();
    175 		selected.clear();
    176 		if (programmaticChangeEvents && fireChangeEvent())
    177 			revert();
    178 		else
    179 			lastSelected = null;
    180 		cleanup();
    181 	}
    182 
    183 	/** Fires a change event on the selection's actor, if any. Called internally when the selection changes, depending on
    184 	 * {@link #setProgrammaticChangeEvents(boolean)}.
    185 	 * @return true if the change should be undone. */
    186 	public boolean fireChangeEvent () {
    187 		if (actor == null) return false;
    188 		ChangeEvent changeEvent = Pools.obtain(ChangeEvent.class);
    189 		try {
    190 			return actor.fire(changeEvent);
    191 		} finally {
    192 			Pools.free(changeEvent);
    193 		}
    194 	}
    195 
    196 	public boolean contains (T item) {
    197 		if (item == null) return false;
    198 		return selected.contains(item);
    199 	}
    200 
    201 	/** Makes a best effort to return the last item selected, else returns an arbitrary item or null if the selection is empty. */
    202 	public T getLastSelected () {
    203 		if (lastSelected != null) {
    204 			return lastSelected;
    205 		} else if (selected.size > 0) {
    206 			return selected.first();
    207 		}
    208 		return null;
    209 	}
    210 
    211 	public Iterator<T> iterator () {
    212 		return selected.iterator();
    213 	}
    214 
    215 	public Array<T> toArray () {
    216 		return selected.iterator().toArray();
    217 	}
    218 
    219 	public Array<T> toArray (Array<T> array) {
    220 		return selected.iterator().toArray(array);
    221 	}
    222 
    223 	/** If true, prevents {@link #choose(Object)} from changing the selection. Default is false. */
    224 	public void setDisabled (boolean isDisabled) {
    225 		this.isDisabled = isDisabled;
    226 	}
    227 
    228 	public boolean isDisabled () {
    229 		return isDisabled;
    230 	}
    231 
    232 	public boolean getToggle () {
    233 		return toggle;
    234 	}
    235 
    236 	/** If true, prevents {@link #choose(Object)} from clearing the selection. Default is false. */
    237 	public void setToggle (boolean toggle) {
    238 		this.toggle = toggle;
    239 	}
    240 
    241 	public boolean getMultiple () {
    242 		return multiple;
    243 	}
    244 
    245 	/** If true, allows {@link #choose(Object)} to select multiple items. Default is false. */
    246 	public void setMultiple (boolean multiple) {
    247 		this.multiple = multiple;
    248 	}
    249 
    250 	public boolean getRequired () {
    251 		return required;
    252 	}
    253 
    254 	/** If true, prevents {@link #choose(Object)} from selecting none. Default is false. */
    255 	public void setRequired (boolean required) {
    256 		this.required = required;
    257 	}
    258 
    259 	/** If false, only {@link #choose(Object)} will fire a change event. Default is true. */
    260 	public void setProgrammaticChangeEvents (boolean programmaticChangeEvents) {
    261 		this.programmaticChangeEvents = programmaticChangeEvents;
    262 	}
    263 
    264 	public String toString () {
    265 		return selected.toString();
    266 	}
    267 }
    268