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