| /******************************************************************************* |
| * 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; |
| } |
| |
| } |
| |
| } |