/*******************************************************************************
* Copyright (c) 2007, 2010 Association for Decentralized Information Management
* in Industry THTH ry.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VTT Technical Research Centre of Finland - initial API and implementation
*******************************************************************************/
package org.simantics.g2d.diagram.participant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
import org.simantics.g2d.diagram.DiagramHints;
import org.simantics.g2d.diagram.IDiagram;
import org.simantics.g2d.element.IElement;
import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
import org.simantics.scenegraph.g2d.events.command.CommandEvent;
import org.simantics.scenegraph.g2d.events.command.Commands;
import org.simantics.utils.DataContainer;
import org.simantics.utils.datastructures.collections.CollectionUtils;
import org.simantics.utils.datastructures.hints.IHintContext.Key;
import org.simantics.utils.datastructures.hints.IHintContext.MouseSpecificKeyOf;
import org.simantics.utils.threads.ThreadUtils;
/**
* Selection is a canvas utility used for managing selected elements.
*
* There are multiple selection context. They are differentiated with selectionId.
* Multiple selections are used in multi-user environments (such as multi touch screen).
* In normal diagram setup SELECTION0 is the default selection.
*
* @author Toni Kalajainen
*/
public class Selection extends AbstractCanvasParticipant {
/** Key for the most common Selection0 */
public static final SelectionHintKey SELECTION0 = new SelectionHintKey(0);
private static final Set NO_SELECTION = Collections
.unmodifiableSet(new HashSet(0));
public static SelectionHintKey getKeyForSelectionId(int selectionId) {
if (selectionId == 0)
return SELECTION0;
return new SelectionHintKey(selectionId);
}
@EventHandler(priority = 0)
public boolean handleCommand(CommandEvent e)
{
if (e.command.equals( Commands.SELECT_ALL )) {
IDiagram d = getHint( DiagramHints.KEY_DIAGRAM );
if (d==null) return true;
addAll(0, d.getElements());
return true;
}
if (e.command.equals( Commands.INVERT_SELECTION )) {
IDiagram d = getHint( DiagramHints.KEY_DIAGRAM );
if (d==null) return true;
Set current = getSelection(0);
Set inverted = new HashSet(d.getElements());
inverted.removeAll(current);
setSelection(0, inverted);
return true;
}
return false;
}
/**
* Get selection
*
* @param selectionId
* selectionId
* @return returns a set of pickables
*/
public Set getSelection(int selectionId) {
Key key = getKeyForSelectionId(selectionId);
Set selection = getHint(key);
if (selection == null)
return NO_SELECTION;
return new HashSet(selection);
}
/**
* Get all selections of all selection ids
* @return all selections
*/
public Set getAllSelections() {
Set result = new HashSet();
for (Entry entry : getContext()
.getHintStack().getHintsOfClass(SelectionHintKey.class)
.entrySet()) {
@SuppressWarnings("unchecked")
Set set = (Set) entry.getValue();
if (set == null || set.isEmpty())
continue;
result.addAll(set);
}
return result;
}
/**
* Get selections by selection id
*
* @return map of selection ids and selections
*/
@SuppressWarnings("unchecked")
public Map> getSelections() {
Map> result = new HashMap>();
for (Entry entry : getContext()
.getHintStack().getHintsOfClass(SelectionHintKey.class)
.entrySet()) {
Set set = (Set) entry.getValue();
if (set == null || set.isEmpty())
continue;
result.put(entry.getKey().mouseId, set);
}
return result;
}
public int[] getSelectionIds() {
Map map = getContext().getHintStack()
.getHintsOfClass(SelectionHintKey.class);
int result[] = new int[map.size()];
int i = 0;
for (SelectionHintKey key : map.keySet())
result[i++] = key.mouseId;
return result;
}
public boolean setSelection(final int selectionId,
final Collection _selection) {
final DataContainer result = new DataContainer(false);
ThreadUtils.syncExec(getThread(), new Runnable() {
@Override
public void run() {
Collection selection = _selection;
Key key = getKeyForSelectionId(selectionId);
if (selection == null || selection.isEmpty())
selection = NO_SELECTION;
Set oldSelection = getHint(key);
if (oldSelection == null)
oldSelection = NO_SELECTION;
if (oldSelection.equals(selection))
return;
if (selection == NO_SELECTION) {
removeHint(key);
result.set(true);
}
Set newSelection = Collections
.unmodifiableSet(new HashSet(selection));
setHint(key, newSelection);
result.set(true);
}
});
return result.get();
}
public boolean setSelection(int selectionId, IElement selection) {
ArrayList list = new ArrayList(1);
list.add(selection);
return setSelection(selectionId, list);
}
/**
* Add item to selection
*
* @param pickable
*/
public boolean add(final int selectionId, final IElement pickable) {
final DataContainer result = new DataContainer(false);
ThreadUtils.syncExec(getThread(), new Runnable() {
@Override
public void run() {
Key key = getKeyForSelectionId(selectionId);
Set oldSelection = getSelection(selectionId);
if (oldSelection.contains(pickable))
return;
Set newSelection = new HashSet(
oldSelection);
newSelection.add(pickable);
newSelection = Collections.unmodifiableSet(newSelection);
setHint(key, newSelection);
result.set(true);
}
});
return result.get();
}
/**
* Add items to mouse0's selection
*
* @param pickables
* @return
*/
public boolean addAll(final int selectionId,
final Collection pickables) {
final DataContainer result = new DataContainer(false);
ThreadUtils.syncExec(getThread(), new Runnable() {
@Override
public void run() {
Key key = getKeyForSelectionId(selectionId);
Set selection = getSelection(selectionId);
if (selection.containsAll(pickables))
return;
Set newSelection = new HashSet(selection);
newSelection.addAll(pickables);
newSelection = Collections.unmodifiableSet(newSelection);
setHint(key, newSelection);
result.set(true);
}
});
return result.get();
}
/**
* Remove an item from mouse0's selection
*
* @param pickable
* @return
*/
public boolean remove(final int selectionId, final IElement pickable) {
final DataContainer result = new DataContainer(false);
ThreadUtils.syncExec(getThread(), new Runnable() {
@Override
public void run() {
Key key = getKeyForSelectionId(selectionId);
Set oldSelection = getSelection(selectionId);
if (!oldSelection.contains(pickable))
return;
Set newSelection = new HashSet(
oldSelection);
newSelection.remove(pickable);
newSelection = Collections.unmodifiableSet(newSelection);
setHint(key, newSelection);
result.set(true);
}
});
return result.get();
}
/**
* Returns true if two collections have a common object
*
* @param x
* @param y
* @return
*/
public static boolean containsAny(Collection> x, Collection> y) {
for (Object o : x)
if (y.contains(o))
return true;
return false;
}
/**
* Remove items from mouse0's selection
*
* @param pickables
* @return
*/
public boolean removeAll(final int selectionId,
final Collection pickables) {
final DataContainer result = new DataContainer(false);
ThreadUtils.syncExec(getThread(), new Runnable() {
@Override
public void run() {
Key key = getKeyForSelectionId(selectionId);
Set oldSelection = getSelection(selectionId);
if (!containsAny(oldSelection, pickables))
return;
Set newSelection = new HashSet(
oldSelection);
newSelection.removeAll(pickables);
newSelection = Collections.unmodifiableSet(newSelection);
setHint(key, newSelection);
result.set(true);
}
});
return result.get();
}
/**
* Retain items in mouse0's selection
*
* @param pickable
* @return
*/
public boolean retainAll(final int selectionId,
final Collection pickable) {
final DataContainer result = new DataContainer(false);
ThreadUtils.syncExec(getThread(), new Runnable() {
@Override
public void run() {
Key key = getKeyForSelectionId(selectionId);
Set oldSelection = getSelection(selectionId);
Set newSelection = new HashSet(
oldSelection);
newSelection.retainAll(pickable);
if (oldSelection.equals(newSelection))
return;
newSelection = Collections.unmodifiableSet(newSelection);
setHint(key, newSelection);
result.set(true);
}
});
return result.get();
}
public boolean contains(int selectionId, IElement pickable) {
Set oldSelection = getSelection(selectionId);
return oldSelection.contains(pickable);
}
public synchronized void toggle(final int selectionId, final Set toggleSet) {
ThreadUtils.syncExec(getThread(), new Runnable() {
@Override
public void run() {
Key key = getKeyForSelectionId(selectionId);
Set oldSelection = getSelection(selectionId);
Set newSelection = new HashSet(oldSelection);
CollectionUtils.toggle(newSelection, toggleSet);
newSelection = Collections.unmodifiableSet(newSelection);
setHint(key, newSelection);
}
});
}
/**
* Toggle an item in a selection.
*/
public synchronized void toggle(int selectionId, IElement pickable) {
Key key = getKeyForSelectionId(selectionId);
Set oldSelection = getSelection(selectionId);
Set newSelection = new HashSet(oldSelection);
if (oldSelection.contains(pickable))
newSelection.remove(pickable);
else
newSelection.add(pickable);
newSelection = Collections.unmodifiableSet(newSelection);
setHint(key, newSelection);
}
/**
* Clear selection
*/
public void clear(int selectionId) {
Key key = getKeyForSelectionId(selectionId);
removeHint(key);
}
/** Class for mouse specific hint keys */
public static class SelectionHintKey extends MouseSpecificKeyOf {
public SelectionHintKey(int mouseId) {
super(mouseId, Set.class);
}
}
}