/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.arakhne.afc.ui.selection ; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeSet; import org.arakhne.afc.util.ListenerCollection; /** Abstracxt implementation of a selection manager. * * @param <OBJ> is the type of the objects inside this manager. * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @deprecated see JavaFX API */ @Deprecated public abstract class SelectionManager<OBJ extends Selectable> implements Set<OBJ> { private final ListenerCollection<SelectionListener> listeners = new ListenerCollection<>(); private final Class<OBJ> elementType; /** Create a new SelectionManager. * * @param elementType is the type of the elements inside this selection manager. */ public SelectionManager(Class<OBJ> elementType) { this.elementType = elementType; } /** Update the system's selection. */ protected void updateSystemSelection() { // } /** Reset any internal bufferized value. */ protected void resetInternalBuffers() { // } /** Add the given object inside the inner storage data structure. * * @param object * @return <code>true</code> if the object was added; <code>false</code> * if not added. */ protected abstract boolean addInStorage(OBJ object); /** Remove the given object from the inner storage data structure. * * @param object * @return <code>true</code> if the object was removed; <code>false</code> * if not removed. */ protected abstract boolean removeFromStorage(OBJ object); /** Clear the storage * * @return the removed objects. */ protected abstract Collection<OBJ> clearStorage(); /** Replies an iterator on the storage. * * @return the iterator. */ protected abstract Iterator<OBJ> getIteratorOnStorage(); /** Invoked when the given object was removed from the inner storage. * * @param object */ protected void onRemovedObject(OBJ object) { // } /** Invoked when the given object was added into the inner storage. * * @param object */ protected void onAddedObject(OBJ object) { // } /** Add selection listener. * * @param listener */ public final void addSelectionListener(SelectionListener listener) { this.listeners.add(SelectionListener.class, listener); } /** Remove selection listener. * * @param listener */ public final void removeSelectionListener(SelectionListener listener) { this.listeners.remove(SelectionListener.class, listener); } /** Notifies the listeners about the selection of a selectable object. * * @param selectableObject is the selected object. * @param isAdjusting indicates if the event to fire is the last inside * a sequence of events. If <code>true</code> the event to fire must * be followed by other selection events that are produces by the * same action on the selection manager. If <code>false</code>, there * is no following selection event for the same action on the selection * manager. */ protected final void fireSelected(OBJ selectableObject, boolean isAdjusting) { SelectionEvent event = new SelectionEvent(this, selectableObject, false, isAdjusting); for(SelectionListener listener : this.listeners.getListeners(SelectionListener.class)) { listener.selectionChanged(event); } } /** Notifies the listeners about the deselection of a selectable object. * * @param selectableObject is the unselected object. * @param isAdjusting indicates if the event to fire is the last inside * a sequence of events. If <code>true</code> the event to fire must * be followed by other selection events that are produces by the * same action on the selection manager. If <code>false</code>, there * is no following selection event for the same action on the selection * manager. */ protected final void fireUnselected(OBJ selectableObject, boolean isAdjusting) { SelectionEvent event = new SelectionEvent(this, selectableObject, true, isAdjusting); for(SelectionListener listener : this.listeners.getListeners(SelectionListener.class)) { listener.selectionChanged(event); } } /** Toggle the selection of figures. * * @param selectableObject are the figures to toggle. * @return <code>true</code> if the selection of a selectable object * has changed; <code>false</code> if no object has changed * of selection state. */ @SuppressWarnings("unchecked") public final boolean toggle(OBJ... selectableObject) { return toggle(Arrays.asList(selectableObject)); } /** Toggle the selection of figures. * * @param selectableObjects are the figures to toggle. * @return <code>true</code> if the selection of a selectable object * has changed; <code>false</code> if no object has changed * of selection state. */ public synchronized final boolean toggle(Collection<? extends OBJ> selectableObjects) { boolean changed = false; OBJ selected = null; OBJ unselected = null; for(OBJ f : selectableObjects) { if (removeFromStorage(f)) { if (selected!=null) { fireSelected(selected, true); selected = null; } else if (unselected!=null) { fireUnselected(unselected, true); unselected = null; } onRemovedObject(f); changed = true; resetInternalBuffers(); unselected = f; } else if (f.isSelectable() && addInStorage(f)) { if (selected!=null) { fireSelected(selected, true); selected = null; } else if (unselected!=null) { fireUnselected(unselected, true); unselected = null; } onAddedObject(f); changed = true; resetInternalBuffers(); selected = f; } } if (selected!=null) { fireSelected(selected, false); } else if (unselected!=null) { fireUnselected(unselected, false); } if (changed) { updateSystemSelection(); } return changed; } /** * {@inheritDoc} */ @Override public synchronized final boolean add(OBJ e) { if (e.isSelectable() && addInStorage(e)) { onAddedObject(e); resetInternalBuffers(); fireSelected(e, false); updateSystemSelection(); return true; } return false; } /** * {@inheritDoc} */ @Override public synchronized final boolean remove(Object o) { if (o!=null && this.elementType.isInstance(o)) { OBJ obj = this.elementType.cast(o); if (removeFromStorage(obj)) { onRemovedObject(obj); resetInternalBuffers(); fireUnselected(obj, false); updateSystemSelection(); return true; } } return false; } /** * {@inheritDoc} */ @Override public synchronized final boolean addAll(Collection<? extends OBJ> c) { boolean changed = false; if (c!=null) { OBJ selected = null; for(OBJ obj : c) { if (obj.isSelectable() && addInStorage(obj)) { if (selected!=null) { fireSelected(selected, true); } onAddedObject(obj); resetInternalBuffers(); changed = true; selected = obj; } } if (selected!=null) { fireSelected(selected, false); } } if (changed) { updateSystemSelection(); } return changed; } /** * {@inheritDoc} */ @Override public synchronized final boolean retainAll(Collection<?> c) { boolean changed = false; if (c==null || c.isEmpty()) { changed = !isEmpty(); clear(); } else { OBJ unselected = null; Iterator<OBJ> iterator = getIteratorOnStorage(); OBJ selObject; while (iterator.hasNext()) { selObject = iterator.next(); if (!c.contains(selObject)) { if (unselected!=null) { fireUnselected(unselected, true); } iterator.remove(); onRemovedObject(selObject); changed = true; resetInternalBuffers(); unselected = selObject; } } if (unselected!=null) { fireUnselected(unselected, false); } } if (changed) { updateSystemSelection(); } return changed; } /** * {@inheritDoc} */ @Override public synchronized final boolean removeAll(Collection<?> c) { boolean changed = false; if (c!=null) { OBJ unselected = null; for(Object o : c) { if (o!=null && this.elementType.isInstance(o)) { OBJ obj = this.elementType.cast(o); if (removeFromStorage(obj)) { if (unselected!=null) { fireUnselected(unselected, true); } onRemovedObject(obj); resetInternalBuffers(); changed = true; unselected = obj; } } } if (unselected!=null) { fireUnselected(unselected, false); } } if (changed) { updateSystemSelection(); } return changed; } /** * Select the specified selectable object and deselect all the previously * selected objects. * * @param e is the selectable object to select. * @return <code>true</code> if the selection has changed; * otherwise <code>false</code>. */ @SuppressWarnings("unchecked") public final boolean setSelection(OBJ... e) { return setSelection(Arrays.asList(e)); } /** * Select the specified selectable object and deselect all the previously * selected objects. * * @param e is the selectable object to select. * @return <code>true</code> if the selection has changed; * otherwise <code>false</code>. */ public synchronized final boolean setSelection(Collection<? extends OBJ> e) { boolean changed = false; Set<OBJ> alreadySelected = new TreeSet<>(); OBJ unselected = null; OBJ selected = null; Iterator<OBJ> iterator = getIteratorOnStorage(); OBJ selObject; while (iterator.hasNext()) { selObject = iterator.next(); if (!e.contains(selObject)) { if (unselected!=null) { fireUnselected(unselected, true); } iterator.remove(); onRemovedObject(selObject); changed = true; resetInternalBuffers(); unselected = selObject; } else { alreadySelected.add(selObject); } } for(OBJ fig : e) { if (fig.isSelectable() && !alreadySelected.contains(fig) && addInStorage(fig)) { if (selected!=null) { fireSelected(selected, true); } else if (unselected!=null) { fireUnselected(unselected, true); unselected = null; } onAddedObject(fig); changed = true; resetInternalBuffers(); selected = fig; } } if (selected!=null) { fireSelected(selected, false); } else if (unselected!=null) { fireUnselected(unselected, false); } if (changed) { updateSystemSelection(); } return changed; } /** * {@inheritDoc} */ @Override public synchronized final Iterator<OBJ> iterator() { return new SelectionIterator(getIteratorOnStorage()); } /** * {@inheritDoc} */ @Override public synchronized final void clear() { Collection<OBJ> elements = clearStorage(); if (!elements.isEmpty()) { resetInternalBuffers(); updateSystemSelection(); Iterator<OBJ> iterator = elements.iterator(); OBJ obj; while (iterator.hasNext()) { obj = iterator.next(); onRemovedObject(obj); resetInternalBuffers(); fireUnselected(obj, iterator.hasNext()); } } } /** * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class SelectionIterator implements Iterator<OBJ> { private final Iterator<OBJ> iterator; private OBJ lastObject = null; public SelectionIterator(Iterator<OBJ> iterator) { this.iterator = iterator; } /** * {@inheritDoc} */ @Override public boolean hasNext() { synchronized(SelectionManager.this) { return this.iterator.hasNext(); } } /** * {@inheritDoc} */ @Override public OBJ next() { synchronized(SelectionManager.this) { this.lastObject = this.iterator.next(); } return this.lastObject; } /** * {@inheritDoc} */ @Override public void remove() { OBJ obj = this.lastObject; this.lastObject = null; if (obj==null) throw new NoSuchElementException(); synchronized(SelectionManager.this) { this.iterator.remove(); } onRemovedObject(obj); resetInternalBuffers(); fireUnselected(obj, false); updateSystemSelection(); } } // class SelectionIterator }