blob: fb6764ec194941b21f62324e2aabfc513cbd30d8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2011 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
* SAP AG - Changed for initial API, implementation and documentation of Graphiti:
* changed calculateNewSelection to correctly handle HandleBounds
* Felix Velasco (mwenz) - Bug 349416 - Support drag&drop operations on FixPointAnchors
* the same way as for BoxRelativeAnchors
*
*******************************************************************************/
package org.eclipse.graphiti.ui.internal.editor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.KeyHandler;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.SharedCursors;
import org.eclipse.gef.handles.HandleBounds;
import org.eclipse.gef.tools.AbstractTool;
import org.eclipse.graphiti.ui.internal.parts.AdvancedAnchorEditPart;
import org.eclipse.graphiti.ui.internal.parts.ConnectionDecoratorEditPart;
import org.eclipse.graphiti.ui.internal.services.GraphitiUiInternal;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.widgets.Display;
/**
* This is a copy of class org.eclipse.gef.tools.MarqueeSelectionTool. In this
* GEF class there are too many methods declared as private. Hence overriding of
* this class does not make sense. Only the implementation of
* calculateNewSelection() has been changed. In this case the insets of the GF
* figures will be considered.
* <p>
* A Tool which selects multiple objects inside a rectangular area of a
* Graphical Viewer. If the SHIFT key is pressed at the beginning of the drag,
* the enclosed items will be appended to the current selection. If the MOD1 key
* is pressed at the beginning of the drag, the enclosed items will have their
* selection state inverted.
* <P>
* By default, only editparts whose figure's are on the primary layer will be
* considered within the enclosed rectangle.
*
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*/
public class GFMarqueeSelectionTool extends AbstractTool {
/**
* The property to be used in
* {@link AbstractTool#setProperties(java.util.Map)} for
* {@link #setMarqueeBehavior(int)}.
*/
public static final Object PROPERTY_MARQUEE_BEHAVIOR = "marqueeBehavior"; //$NON-NLS-1$
/**
* This behaviour selects nodes completely encompassed by the marquee
* rectangle. This is the default behaviour for this tool.
*/
public static final int BEHAVIOR_NODES_CONTAINED = new Integer(1).intValue();
/**
* This behaviour selects connections that intersect the marquee rectangle.
*/
public static final int BEHAVIOR_CONNECTIONS_TOUCHED = new Integer(2).intValue();
/**
* This behaviour selects nodes completely encompassed by the marquee
* rectangle, and all connections between those nodes.
*/
public static final int BEHAVIOR_NODES_AND_CONNECTIONS = new Integer(3).intValue();
/**
* The Constant DEFAULT_MODE.
*/
static final int DEFAULT_MODE = 0;
/**
* The Constant TOGGLE_MODE.
*/
static final int TOGGLE_MODE = 1;
/**
* The Constant APPEND_MODE.
*/
static final int APPEND_MODE = 2;
private Figure marqueeRectangleFigure;
private Set<GraphicalEditPart> allChildren = new HashSet<GraphicalEditPart>();
private Collection<EditPart> selectedEditParts;
private Request targetRequest;
private int marqueeBehavior = BEHAVIOR_NODES_CONTAINED;
private int mode;
private static final Request MARQUEE_REQUEST = new Request(RequestConstants.REQ_SELECTION);
/**
* Creates a new MarqueeSelectionTool of default type
* {@link #BEHAVIOR_NODES_CONTAINED}.
*/
public GFMarqueeSelectionTool() {
setDefaultCursor(SharedCursors.CROSS);
setUnloadWhenFinished(false);
}
/**
* Apply property.
*
* @param key
* the key
* @param value
* the value
*
* @see org.eclipse.gef.tools.AbstractTool#applyProperty(java.lang.Object,
* java.lang.Object)
*/
@Override
protected void applyProperty(Object key, Object value) {
if (PROPERTY_MARQUEE_BEHAVIOR.equals(key)) {
if (value instanceof Integer)
setMarqueeBehavior(((Integer) value).intValue());
return;
}
super.applyProperty(key, value);
}
private void calculateConnections(Collection<EditPart> newSelections, Collection<EditPart> deselections) {
// determine the currently selected nodes minus the ones that are to be
// deselected
Collection<EditPart> currentNodes = new HashSet<EditPart>();
if (getSelectionMode() != DEFAULT_MODE) { // everything is deselected
// in default mode
List<EditPart> selectedEditParts2 = GraphitiUiInternal.getGefService().getSelectedEditParts(getCurrentViewer());
Iterator<EditPart> iter = selectedEditParts2.iterator();
while (iter.hasNext()) {
EditPart selected = iter.next();
if (!(selected instanceof ConnectionEditPart) && !deselections.contains(selected))
currentNodes.add(selected);
}
}
// add new connections to be selected to newSelections
Collection<EditPart> connections = new ArrayList<EditPart>();
for (Iterator<EditPart> nodes = newSelections.iterator(); nodes.hasNext();) {
GraphicalEditPart node = (GraphicalEditPart) nodes.next();
for (Iterator<?> itr = node.getSourceConnections().iterator(); itr.hasNext();) {
ConnectionEditPart sourceConn = (ConnectionEditPart) itr.next();
if (sourceConn.getSelected() == EditPart.SELECTED_NONE
&& (newSelections.contains(sourceConn.getTarget()) || currentNodes.contains(sourceConn.getTarget())))
connections.add(sourceConn);
}
for (Iterator<?> itr = node.getTargetConnections().iterator(); itr.hasNext();) {
ConnectionEditPart targetConn = (ConnectionEditPart) itr.next();
if (targetConn.getSelected() == EditPart.SELECTED_NONE
&& (newSelections.contains(targetConn.getSource()) || currentNodes.contains(targetConn.getSource())))
connections.add(targetConn);
}
}
newSelections.addAll(connections);
// add currently selected connections that are to be deselected to
// deselections
connections = new HashSet<EditPart>();
for (Iterator<EditPart> nodes = deselections.iterator(); nodes.hasNext();) {
GraphicalEditPart node = (GraphicalEditPart) nodes.next();
for (Iterator<?> itr = node.getSourceConnections().iterator(); itr.hasNext();) {
ConnectionEditPart sourceConn = (ConnectionEditPart) itr.next();
if (sourceConn.getSelected() != EditPart.SELECTED_NONE)
connections.add(sourceConn);
}
for (Iterator<?> itr = node.getTargetConnections().iterator(); itr.hasNext();) {
ConnectionEditPart targetConn = (ConnectionEditPart) itr.next();
if (targetConn.getSelected() != EditPart.SELECTED_NONE)
connections.add(targetConn);
}
}
deselections.addAll(connections);
}
private void calculateNewSelection(Collection<EditPart> newSelections, Collection<EditPart> deselections) {
Rectangle marqueeRect = getMarqueeSelectionRectangle();
for (Iterator<GraphicalEditPart> itr = getAllChildren().iterator(); itr.hasNext();) {
GraphicalEditPart child = itr.next();
IFigure figure = child.getFigure();
if (!child.isSelectable() || child.getTargetEditPart(MARQUEE_REQUEST) != child || !isFigureVisible(figure)
|| !figure.isShowing())
continue;
// Graphiti: start change
Rectangle r;
if (figure instanceof HandleBounds) {
r = ((HandleBounds) figure).getHandleBounds().getCopy();
} else {
r = figure.getBounds().getCopy();
}
// Graphiti: end change
figure.translateToAbsolute(r);
boolean included = false;
if (child instanceof ConnectionEditPart && marqueeRect.intersects(r)) {
Rectangle relMarqueeRect = Rectangle.SINGLETON;
figure.translateToRelative(relMarqueeRect.setBounds(marqueeRect));
included = ((Connection) figure).getPoints().intersects(relMarqueeRect);
} else
included = marqueeRect.contains(r);
if (included) {
if (child.getSelected() == EditPart.SELECTED_NONE || getSelectionMode() != TOGGLE_MODE)
newSelections.add(child);
else
deselections.add(child);
}
}
if (marqueeBehavior == BEHAVIOR_NODES_AND_CONNECTIONS)
calculateConnections(newSelections, deselections);
}
private Request createTargetRequest() {
return MARQUEE_REQUEST;
}
/**
* Erases feedback if necessary and puts the tool into the terminal state.
*/
@Override
public void deactivate() {
if (isInState(STATE_DRAG_IN_PROGRESS)) {
eraseMarqueeFeedback();
eraseTargetFeedback();
}
super.deactivate();
allChildren.clear();
setState(STATE_TERMINAL);
}
private void eraseMarqueeFeedback() {
if (marqueeRectangleFigure != null) {
removeFeedback(marqueeRectangleFigure);
marqueeRectangleFigure = null;
}
}
private void eraseTargetFeedback() {
if (selectedEditParts == null)
return;
Iterator<EditPart> oldEditParts = selectedEditParts.iterator();
while (oldEditParts.hasNext()) {
EditPart editPart = oldEditParts.next();
editPart.eraseTargetFeedback(getTargetRequest());
}
}
private Set<GraphicalEditPart> getAllChildren() {
if (allChildren.isEmpty())
getAllChildren(getCurrentViewer().getRootEditPart(), allChildren);
return allChildren;
}
private void getAllChildren(EditPart editPart, Set<GraphicalEditPart> allChildren) {
List<EditPart> children = GraphitiUiInternal.getGefService().getEditPartChildren(editPart);
for (int i = 0; i < children.size(); i++) {
GraphicalEditPart child = (GraphicalEditPart) children.get(i);
if (marqueeBehavior == BEHAVIOR_NODES_CONTAINED || marqueeBehavior == BEHAVIOR_NODES_AND_CONNECTIONS)
allChildren.add(child);
if (marqueeBehavior == BEHAVIOR_CONNECTIONS_TOUCHED) {
allChildren.addAll(GraphitiUiInternal.getGefService().getSourceConnections(child));
allChildren.addAll(GraphitiUiInternal.getGefService().getTargetConnections(child));
}
getAllChildren(child, allChildren);
}
}
/**
* Gets the command name.
*
* @return the command name
*
* @see org.eclipse.gef.tools.AbstractTool#getCommandName()
*/
@Override
protected String getCommandName() {
return REQ_SELECTION;
}
/**
* Gets the debug name.
*
* @return the debug name
*
* @see org.eclipse.gef.tools.AbstractTool#getDebugName()
*/
@Override
protected String getDebugName() {
return "Marquee Tool: " + marqueeBehavior;//$NON-NLS-1$
}
private IFigure getMarqueeFeedbackFigure() {
if (marqueeRectangleFigure == null) {
marqueeRectangleFigure = new MarqueeRectangleFigure();
addFeedback(marqueeRectangleFigure);
}
return marqueeRectangleFigure;
}
private Rectangle getMarqueeSelectionRectangle() {
return new Rectangle(getStartLocation(), getLocation());
}
private int getSelectionMode() {
return mode;
}
private Request getTargetRequest() {
if (targetRequest == null)
targetRequest = createTargetRequest();
return targetRequest;
}
/**
* Handle button down.
*
* @param button
* the button
*
* @return true, if handle button down
*
* @see org.eclipse.gef.tools.AbstractTool#handleButtonDown(int)
*/
@Override
protected boolean handleButtonDown(int button) {
if (!isGraphicalViewer())
return true;
// if ((button == 3 || button == 1) && isInState(STATE_INITIAL))
// performMarqueeSelect();
if (button != 1 && button != 3) {
setState(STATE_INVALID);
handleInvalidInput();
}
if (stateTransition(STATE_INITIAL, STATE_DRAG_IN_PROGRESS)) {
if (getCurrentInput().isModKeyDown(SWT.MOD1))
setSelectionMode(TOGGLE_MODE);
else if (getCurrentInput().isShiftKeyDown())
setSelectionMode(APPEND_MODE);
else
setSelectionMode(DEFAULT_MODE);
}
return true;
}
/**
* Handle button up.
*
* @param button
* the button
*
* @return true, if handle button up
*
* @see org.eclipse.gef.tools.AbstractTool#handleButtonUp(int)
*/
@Override
protected boolean handleButtonUp(int button) {
if (stateTransition(STATE_DRAG_IN_PROGRESS, STATE_TERMINAL)) {
eraseTargetFeedback();
eraseMarqueeFeedback();
performMarqueeSelect();
}
handleFinished();
return true;
}
/**
* Handle drag in progress.
*
* @return true, if handle drag in progress
*
* @see org.eclipse.gef.tools.AbstractTool#handleDragInProgress()
*/
@Override
protected boolean handleDragInProgress() {
if (isInState(STATE_DRAG | STATE_DRAG_IN_PROGRESS)) {
showMarqueeFeedback();
eraseTargetFeedback();
calculateNewSelection(selectedEditParts = new ArrayList<EditPart>(), new ArrayList<EditPart>());
showTargetFeedback();
}
return true;
}
/**
* Handle focus lost.
*
* @return true, if handle focus lost
*
* @see org.eclipse.gef.tools.AbstractTool#handleFocusLost()
*/
@Override
protected boolean handleFocusLost() {
if (isInState(STATE_DRAG | STATE_DRAG_IN_PROGRESS)) {
handleFinished();
return true;
}
return false;
}
/**
* This method is called when mouse or keyboard input is invalid and erases
* the feedback.
*
* @return <code>true</code>
*/
@Override
protected boolean handleInvalidInput() {
eraseTargetFeedback();
eraseMarqueeFeedback();
return true;
}
/**
* Handles high-level processing of a key down event. KeyEvents are
* forwarded to the current viewer's {@link KeyHandler}, via
* {@link KeyHandler#keyPressed(KeyEvent)}.
*
* @param e
* the e
*
* @return true, if handle key down
*
* @see AbstractTool#handleKeyDown(KeyEvent)
*/
@Override
protected boolean handleKeyDown(KeyEvent e) {
if (super.handleKeyDown(e))
return true;
if (getCurrentViewer().getKeyHandler() != null)
return getCurrentViewer().getKeyHandler().keyPressed(e);
return false;
}
private boolean isFigureVisible(IFigure fig) {
Rectangle figBounds = fig.getBounds().getCopy();
IFigure walker = fig.getParent();
while (!figBounds.isEmpty() && walker != null) {
walker.translateToParent(figBounds);
figBounds.intersect(walker.getBounds());
walker = walker.getParent();
}
return !figBounds.isEmpty();
}
private boolean isGraphicalViewer() {
return getCurrentViewer() instanceof GraphicalViewer;
}
/**
* MarqueeSelectionTool is only interested in GraphicalViewers, not
* TreeViewers.
*
* @param viewer
* the viewer
*
* @return true, if checks if is viewer important
*
* @see org.eclipse.gef.tools.AbstractTool#isViewerImportant(org.eclipse.gef.EditPartViewer)
*/
@Override
protected boolean isViewerImportant(EditPartViewer viewer) {
return viewer instanceof GraphicalViewer;
}
private void performMarqueeSelect() {
EditPartViewer viewer = getCurrentViewer();
Collection<EditPart> newSelections = new LinkedHashSet<EditPart>(), deselections = new HashSet<EditPart>();
calculateNewSelection(newSelections, deselections);
if (getSelectionMode() != DEFAULT_MODE) {
List<EditPart> selectedEditParts2 = GraphitiUiInternal.getGefService().getSelectedEditParts(viewer);
newSelections.addAll(selectedEditParts2);
newSelections.removeAll(deselections);
}
/*
* Filter the current selection of editParts. Remove those editParts
* where the parentEditPart is already in the current selection. Anchors
* and ConnectionDecorators are always excluded from the selection.
*/
Collection<EditPart> filteredSelections = new ArrayList<EditPart>();
for (Object object : newSelections) {
if (object instanceof EditPart) {
EditPart ep = (EditPart) object;
if (newSelections.contains(ep.getParent())) {
continue;
}
if (ep instanceof AdvancedAnchorEditPart || ep instanceof ConnectionDecoratorEditPart) {
continue;
}
filteredSelections.add(ep);
}
}
viewer.setSelection(new StructuredSelection(filteredSelections.toArray()));
}
/**
* Sets the type of parts that this tool will select. This method should
* only be invoked once: when the tool is being initialized.
*
* @param type
* {@link #BEHAVIOR_CONNECTIONS_TOUCHED} or
* {@link #BEHAVIOR_NODES_CONTAINED} or
* {@link #BEHAVIOR_NODES_AND_CONNECTIONS}
*
* @since 3.1
*/
public void setMarqueeBehavior(int type) {
if (type != BEHAVIOR_CONNECTIONS_TOUCHED && type != BEHAVIOR_NODES_CONTAINED && type != BEHAVIOR_NODES_AND_CONNECTIONS)
throw new IllegalArgumentException("Invalid marquee behaviour specified."); //$NON-NLS-1$
marqueeBehavior = type;
}
private void setSelectionMode(int mode) {
this.mode = mode;
}
/**
* Sets the viewer.
*
* @param viewer
* the viewer
*
* @see org.eclipse.gef.Tool#setViewer(org.eclipse.gef.EditPartViewer)
*/
@Override
public void setViewer(EditPartViewer viewer) {
if (viewer == getCurrentViewer())
return;
super.setViewer(viewer);
if (viewer instanceof GraphicalViewer)
setDefaultCursor(SharedCursors.CROSS);
else
setDefaultCursor(SharedCursors.NO);
}
private void showMarqueeFeedback() {
Rectangle rect = getMarqueeSelectionRectangle().getCopy();
getMarqueeFeedbackFigure().translateToRelative(rect);
getMarqueeFeedbackFigure().setBounds(rect);
}
private void showTargetFeedback() {
for (Iterator<EditPart> itr = selectedEditParts.iterator(); itr.hasNext();) {
EditPart editPart = itr.next();
editPart.showTargetFeedback(getTargetRequest());
}
}
/**
* The Class MarqueeRectangleFigure.
*/
class MarqueeRectangleFigure extends Figure {
private static final int DELAY = 110; // animation delay in
// millisecond
private int offset = 0;
private boolean schedulePaint = true;
/**
* Paint figure.
*
* @param graphics
* the graphics
*
* @see org.eclipse.draw2d.Figure#paintFigure(org.eclipse.draw2d.Graphics)
*/
@Override
protected void paintFigure(Graphics graphics) {
Rectangle bounds = getBounds().getCopy();
graphics.translate(getLocation());
graphics.setXORMode(true);
graphics.setForegroundColor(ColorConstants.white);
graphics.setBackgroundColor(ColorConstants.black);
graphics.setLineStyle(Graphics.LINE_DOT);
int[] points = new int[6];
points[0] = 0 + offset;
points[1] = 0;
points[2] = bounds.width - 1;
points[3] = 0;
points[4] = bounds.width - 1;
points[5] = bounds.height - 1;
graphics.drawPolyline(points);
points[0] = 0;
points[1] = 0 + offset;
points[2] = 0;
points[3] = bounds.height - 1;
points[4] = bounds.width - 1;
points[5] = bounds.height - 1;
graphics.drawPolyline(points);
graphics.translate(getLocation().getNegated());
if (schedulePaint) {
Display.getCurrent().timerExec(DELAY, new Runnable() {
public void run() {
offset++;
if (offset > 5)
offset = 0;
schedulePaint = true;
repaint();
}
});
}
schedulePaint = false;
}
}
}