package org.iplantc.phyloviewer.client.events;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.iplantc.phyloviewer.client.tree.viewer.DetailView;
import org.iplantc.phyloviewer.shared.math.Box2D;
import org.iplantc.phyloviewer.shared.math.Vector2;
import org.iplantc.phyloviewer.shared.model.INode;
import org.iplantc.phyloviewer.shared.scene.Rectangle;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
public class SelectionMouseHandler extends BaseMouseHandler implements HasNodeSelectionHandlers
{
//TODO listen for tree changes on the view and clear the selection
public static final int DRAG_THRESHOLD = 3; //drags less than this distance will be treated like a click and select a single node
public static final int BUTTON = NativeEvent.BUTTON_LEFT;
private final DetailView view;
private Set<INode> currentSelection = new HashSet<INode>();
Rectangle selectionBox = new Rectangle();
/**
* Creates a new SelectionMouseHandler that selects nodes on the given view. SelectionEvents are
* broadcast on the view's eventBus (as well as to any handlers added with addSelectionHandler()).
*
* @param view
*/
public SelectionMouseHandler(DetailView view)
{
super(view);
this.view = view;
}
@Override
public void onMouseDown(MouseDownEvent event)
{
super.onMouseDown(event);
SavedMouseEvent downEvent = super.getCurrentMouseDownEvent(BUTTON);
if (downEvent != null)
{
if (!downEvent.isControlKeyDown && !downEvent.isMetaKeyDown && !downEvent.isAltKeyDown)
{
clearSelection();
}
}
}
@Override
public void onMouseUp(MouseUpEvent upEvent)
{
SavedMouseEvent downEvent = super.getCurrentMouseDownEvent(BUTTON); //getting this before it's nulled by super.onMouseUp(upEvent)
boolean isDragging = isDragging(BUTTON);
super.onMouseUp(upEvent);
// downEvent can be null. (I can't recreate it reliably.)
if (upEvent.getNativeButton() == BUTTON && downEvent != null)
{
if (isDragging)
{
Vector2 up = new Vector2(upEvent.getX(), upEvent.getY());
Box2D box = Box2D.createBox(downEvent.getLocation(), up);
addToSelection(view.getNodesIn(box));
}
else
{
//handle an actual (non-drag) click. (onClick() gets called whether dragging or not)
INode node = view.getNodeAt(upEvent.getX(), upEvent.getY());
if (node != null)
{
if (downEvent.isShiftKeyDown)
{
if (downEvent.isAltKeyDown)
{
addToSelection(node, false);
addSubtreeToSelection(view.getTree().getRootNode().mrca(currentSelection));
}
else
{
addSubtreeToSelection(node);
}
}
else
{
addToSelection(node, true);
}
}
}
}
//clear selection box
updateSelectionArea(null);
}
@Override
public void onMouseMove(MouseMoveEvent event)
{
super.onMouseMove(event);
if (isDragging(BUTTON))
{
Vector2 start = super.getCurrentMouseDownEvent(BUTTON).getLocation();
Vector2 end = new Vector2(event.getX(), event.getY());
Box2D box = Box2D.createBox(start, end);
updateSelectionArea(box);
}
}
@Override
public void onMouseOut(MouseOutEvent event)
{
super.onMouseOut(event);
updateSelectionArea(null); //assume the button is released when leaving the widget. (see BaseMouseHandler.onMouseOver())
}
@Override
public HandlerRegistration addSelectionHandler(NodeSelectionHandler handler)
{
return getEventBus().addHandlerToSource(NodeSelectionEvent.TYPE, this, handler);
}
public HandlerRegistration addSelectionAreaHandler(SelectionAreaChangeHandler handler)
{
return getEventBus().addHandlerToSource(SelectionAreaChangeEvent.TYPE, this, handler);
}
@Override
public void fireEvent(GwtEvent<?> event)
{
getEventBus().fireEventFromSource(event, this);
}
public EventBus getEventBus()
{
return view.getEventBus();
}
private void updateSelectionArea(Box2D box)
{
if (box != null)
{
selectionBox.setMin(box.getMin());
selectionBox.setMax(box.getMax());
view.addOverlay(selectionBox);
}
else
{
view.removeOverlay(selectionBox);
}
view.requestRender();
getEventBus().fireEventFromSource(new SelectionAreaChangeEvent(box), this);
}
private void addToSelection(INode node, boolean fireEvent)
{
boolean change = currentSelection.add(node);
if (change && fireEvent)
{
Logger.getLogger("").log(Level.FINEST, "Added node to selection. " + currentSelection.size() + " total nodes are selected.");
getEventBus().fireEventFromSource(new NodeSelectionEvent(currentSelection), this);
}
}
private void addToSelection(Set<INode> nodes)
{
boolean change = currentSelection.addAll(nodes);
if (change)
{
Logger.getLogger("").log(Level.FINEST, "Added nodes to selection. " + currentSelection.size() + " total nodes are selected.");
getEventBus().fireEventFromSource(new NodeSelectionEvent(currentSelection), this);
}
}
private void clearSelection()
{
Logger.getLogger("").log(Level.FINEST, "Cleared selection");
currentSelection = new HashSet<INode>();
getEventBus().fireEventFromSource(new NodeSelectionEvent(currentSelection), this);
}
private void addSubtreeToSelection(INode node)
{
addToSelection(node, false);
INode[] children = node.getChildren();
if (children != null)
{
for (INode child : children)
{
if (child != null)
{
addSubtreeToSelection(child);
}
}
}
getEventBus().fireEventFromSource(new NodeSelectionEvent(currentSelection), this);
}
}