org.eclipse.sirius.diagram.ui.graphical.edit.policies.MoveEdgeGroupManager.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.sirius.diagram.ui.graphical.edit.policies.MoveEdgeGroupManager.java

Source

/*******************************************************************************
 * Copyright (c) 2015, 2016 THALES GLOBAL SERVICES.
 * 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:
 *    Obeo - initial API and implementation
 *******************************************************************************/
package org.eclipse.sirius.diagram.ui.graphical.edit.policies;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gef.commands.UnexecutableCommand;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.requests.BendpointRequest;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gmf.runtime.common.core.command.ICommand;
import org.eclipse.gmf.runtime.diagram.ui.commands.ICommandProxy;
import org.eclipse.gmf.runtime.diagram.ui.commands.SetBoundsCommand;
import org.eclipse.gmf.runtime.diagram.ui.figures.IBorderItemLocator;
import org.eclipse.gmf.runtime.diagram.ui.l10n.DiagramUIMessages;
import org.eclipse.gmf.runtime.diagram.ui.requests.RequestConstants;
import org.eclipse.gmf.runtime.emf.core.util.EObjectAdapter;
import org.eclipse.gmf.runtime.notation.LayoutConstraint;
import org.eclipse.gmf.runtime.notation.Location;
import org.eclipse.gmf.runtime.notation.Node;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.sirius.diagram.ui.edit.api.part.AbstractDiagramBorderNodeEditPart;
import org.eclipse.sirius.diagram.ui.provider.Messages;
import org.eclipse.sirius.diagram.ui.tools.api.figure.locator.DBorderItemLocator;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

/**
 * This class is responsible for managing the move edge group tool. see #471104
 * relevant ticket or specification for more details.
 * 
 * @author <a href="mailto:florian.barbin@obeo.fr">Florian Barbin</a>
 *
 */
public class MoveEdgeGroupManager {

    /**
     * Key for extended EDGE_GROUP_MOVE meta-data.
     */
    public static final String EDGE_GROUP_MOVE_KEY = "EDGE_GROUP_MOVE"; //$NON-NLS-1$

    /**
     * Key for extended EDGE_MOVE_DELTA meta-data.
     */
    public static final String EDGE_MOVE_DELTA = "EDGE_MOVE_DELTA"; //$NON-NLS-1$

    /**
     * Key for extended EDGE_GROUP_MOVE_HAS_BEEN_ACTIVATED_KEY meta-data. This
     * key is used to indicate that the tool has been activated at least one
     * time during the edge moving. It's useful to know if it's necessary to
     * erase source and target feedbacks.
     */
    public static final String EDGE_GROUP_MOVE_HAS_BEEN_ACTIVATED_KEY = "EDGE_GROUP_MOVE_HAS_BEEN_ACTIVATED_KEY"; //$NON-NLS-1$

    private Request request;

    private int direction = PositionConstants.NONE;

    /**
     * Default constructor.
     * 
     * @param request
     *            the tool request.
     */
    public MoveEdgeGroupManager(Request request) {
        this.request = request;
    }

    /**
     * Returns whether the move edge group tool is activated or not.
     * 
     * @return true if the tool is activate, otherwise false.
     */
    public boolean isToolActivated() {
        Object value = request.getExtendedData().get(EDGE_GROUP_MOVE_KEY);
        return value != null && Boolean.TRUE.equals(value);
    }

    /**
     * Shows the group feedback.
     */
    public void showGroupFeedback() {
        if (accept()) {
            ConnectionEditPart connectionEditPart = ((BendpointRequest) request).getSource();
            for (ConnectionEditPart selectedConnectionEditPart : Iterables
                    .filter(connectionEditPart.getViewer().getSelectedEditParts(), ConnectionEditPart.class)) {
                showGroupFeedback(selectedConnectionEditPart);
            }
        }

    }

    @SuppressWarnings("unchecked")
    private void showGroupFeedback(ConnectionEditPart connectionEditPart) {
        AbstractDiagramBorderNodeEditPart sourceEditPart = (AbstractDiagramBorderNodeEditPart) connectionEditPart
                .getSource();
        AbstractDiagramBorderNodeEditPart targetEditPart = (AbstractDiagramBorderNodeEditPart) connectionEditPart
                .getTarget();
        Set<EditPart> movedBorderNodes = Sets.newLinkedHashSet();
        for (ConnectionEditPart cep : Iterables.filter(sourceEditPart.getViewer().getSelectedEditParts(),
                ConnectionEditPart.class)) {
            movedBorderNodes.add(cep.getSource());
            movedBorderNodes.add(cep.getTarget());
        }
        ChangeBoundsRequest sourceChangeBoundsRequest = createChangeBoundsRequest(sourceEditPart);
        sourceChangeBoundsRequest.getEditParts().addAll(movedBorderNodes);
        ChangeBoundsRequest targetChangeBoundsRequest = createChangeBoundsRequest(targetEditPart);
        targetChangeBoundsRequest.getEditParts().addAll(movedBorderNodes);
        sourceEditPart.showSourceFeedback(sourceChangeBoundsRequest);
        targetEditPart.showSourceFeedback(targetChangeBoundsRequest);
    }

    /**
     * Erases the group feedback if this action has been activated.
     */
    public void eraseGroupFeedback() {
        if (request instanceof BendpointRequest) {
            if (isToolActivatedOrHasBeenActivated()) {
                ConnectionEditPart connectionEditPart = ((BendpointRequest) request).getSource();
                for (ConnectionEditPart selectedConnectionEditPart : Iterables
                        .filter(connectionEditPart.getViewer().getSelectedEditParts(), ConnectionEditPart.class)) {
                    eraseGroupFeedback(selectedConnectionEditPart);
                }
            }
        }

    }

    /**
     * Erases the group feedback of a specific {@link ConnectionEditPart} if
     * this action has been activated.
     * 
     * @param connectionEditPart
     *            the {@link ConnectionEditPart} to erase the feedback
     */
    private void eraseGroupFeedback(ConnectionEditPart connectionEditPart) {
        EditPart sourceEditPart = connectionEditPart.getSource();
        EditPart targetEditPart = connectionEditPart.getTarget();

        if (sourceEditPart instanceof AbstractDiagramBorderNodeEditPart
                && targetEditPart instanceof AbstractDiagramBorderNodeEditPart) {
            AbstractDiagramBorderNodeEditPart sourceBorderNodeEditPart = (AbstractDiagramBorderNodeEditPart) sourceEditPart;
            AbstractDiagramBorderNodeEditPart targetBorderNodeEditPart = (AbstractDiagramBorderNodeEditPart) targetEditPart;

            ChangeBoundsRequest changeBoundsRequest = new ChangeBoundsRequest(RequestConstants.REQ_DROP);
            sourceBorderNodeEditPart.eraseSourceFeedback(changeBoundsRequest);
            targetBorderNodeEditPart.eraseSourceFeedback(changeBoundsRequest);

        }
    }

    /**
     * Provides the command that will perform the edge move group.
     * 
     * @return the command if the request is authorized, null otherwise.
     */
    public Command getCommand() {
        if (accept()) {
            ConnectionEditPart connectionEditPart = ((BendpointRequest) request).getSource();
            CompoundCommand cc = new CompoundCommand(Messages.EdgeGroupMoveMessage);
            for (ConnectionEditPart selectedConnectionEditPart : Iterables
                    .filter(connectionEditPart.getViewer().getSelectedEditParts(), ConnectionEditPart.class)) {
                cc.add(getCommand(selectedConnectionEditPart));
            }
            return cc;
        }
        return null;
    }

    private Command getCommand(ConnectionEditPart connectionEditPart) {
        EditPart sourceEditPart = connectionEditPart.getSource();
        EditPart targetEditPart = connectionEditPart.getTarget();
        Command srcCommand = createCommand((AbstractDiagramBorderNodeEditPart) sourceEditPart);
        if (srcCommand != null) {
            Command tgtCommand = createCommand((AbstractDiagramBorderNodeEditPart) targetEditPart);
            if (tgtCommand != null) {
                srcCommand = srcCommand.chain(tgtCommand);
                return srcCommand;
            }
        }
        return null;
    }

    /**
     * Returns whether the move edge group tool is activated or has been
     * activated during the drag.
     * 
     * @return true if the tool is activate or has been activated, otherwise
     *         false.
     */
    private boolean isToolActivatedOrHasBeenActivated() {
        Object value = request.getExtendedData().get(EDGE_GROUP_MOVE_HAS_BEEN_ACTIVATED_KEY);
        return value != null && Boolean.TRUE.equals(value);
    }

    /**
     * Determines if the given request is a valid request for this tool.<br/>
     * Each selected edge should respect the following rules:
     * <ul>
     * <li>a border node as source</li>
     * <li>a border node as target</li>
     * <li>source node has only one connection: the moved edge.</li>
     * <li>target node has only one connection: the moved edge.</li>
     * <li>Both border nodes are on the same axe (Horizontal or Vertical)</li>
     * </ul>
     * <br/>
     * Furthermore, every selected edge group should be in the same direction
     * and only edges should be selected.
     * 
     * @return true if the moved edges and border nodes can be activated,
     *         otherwise false.
     */
    @SuppressWarnings("unchecked")
    private boolean accept() {
        if (request instanceof BendpointRequest) {
            ConnectionEditPart connectionEditPart = ((BendpointRequest) request).getSource();
            // The selected diagram element should only contain edges otherwise
            // the move is not valid
            final Set<Integer> edgeDirections = Sets.newLinkedHashSet();
            boolean result = Iterables.all(connectionEditPart.getViewer().getSelectedEditParts(), Predicates
                    .and(Predicates.instanceOf(ConnectionEditPart.class), new Predicate<ConnectionEditPart>() {
                        /**
                         * Determines if the given edge respects the following rules:
                         * <ul>
                         * <li>a border node as source</li>
                         * <li>a border node as target</li>
                         * <li>source node has only one connection: the moved edge.</li>
                         * <li>target node has only one connection: the moved edge.</li>
                         * <li>Both border nodes are on the same axe (Horizontal or
                         * Vertical)</li>
                         * </ul>
                         */
                        @Override
                        public boolean apply(ConnectionEditPart input) {
                            EditPart sourceEditPart = input.getSource();
                            EditPart targetEditPart = input.getTarget();

                            if (sourceEditPart instanceof AbstractDiagramBorderNodeEditPart
                                    && targetEditPart instanceof AbstractDiagramBorderNodeEditPart) {
                                if (getAllConnections((AbstractDiagramBorderNodeEditPart) sourceEditPart)
                                        .size() == 1
                                        && getAllConnections((AbstractDiagramBorderNodeEditPart) targetEditPart)
                                                .size() == 1) {
                                    int sourceDirection = getBorderNodeDirection(
                                            (AbstractDiagramBorderNodeEditPart) sourceEditPart);
                                    int targetDirection = getBorderNodeDirection(
                                            (AbstractDiagramBorderNodeEditPart) targetEditPart);
                                    if (sourceDirection == targetDirection) {
                                        direction = sourceDirection;
                                        edgeDirections.add(sourceDirection);
                                        return true;
                                    }
                                }
                            }
                            return false;
                        }
                    }));
            // There should be only one kind of direction for every edges to
            // authorize the move
            return result && edgeDirections.size() == 1;
        }
        return false;
    }

    private Command createCommand(AbstractDiagramBorderNodeEditPart editPart) {
        Object model = editPart.getModel();
        if (model instanceof Node) {
            LayoutConstraint layoutConstraint = ((Node) model).getLayoutConstraint();
            if (layoutConstraint instanceof Location) {
                PrecisionPoint delta = getDeltaFromRequest();
                double scale = getZoomScale(editPart);
                delta = delta.setPreciseLocation(Math.round(delta.x() / scale), Math.round(delta.y() / scale));
                if (!conflictDetected(editPart, delta)) {
                    Point location = new Point(((Location) layoutConstraint).getX(),
                            ((Location) layoutConstraint).getY());
                    Point newLocation = location.translate(delta);
                    final ICommand moveCommand = new SetBoundsCommand(editPart.getEditingDomain(),
                            DiagramUIMessages.Commands_MoveElement, new EObjectAdapter((View) editPart.getModel()),
                            newLocation);
                    return new ICommandProxy(moveCommand);
                }
            }
        }
        // If some conflict is detected then the move is forbidden
        return UnexecutableCommand.INSTANCE;
    }

    private boolean conflictDetected(AbstractDiagramBorderNodeEditPart editPart, Point moveDelta) {
        BorderNodeCollapseManager borderNodeCollapseManager = new BorderNodeCollapseManager();

        IBorderItemLocator locator = editPart.getBorderItemLocator();
        IFigure figure = editPart.getFigure();
        Rectangle bounds = figure.getBounds().getCopy();
        bounds.translate(moveDelta);

        Rectangle newBounds = borderNodeCollapseManager.expandCollapsedNodeBounds(editPart,
                new PrecisionRectangle(bounds));
        if (newBounds != null) {
            bounds.setBounds(newBounds);
        }

        List<IFigure> figureToIgnore = Lists.newArrayList();
        figureToIgnore.add(figure);
        for (ConnectionEditPart connectionEditPart : Iterables.filter(editPart.getViewer().getSelectedEditParts(),
                ConnectionEditPart.class)) {
            EditPart source = connectionEditPart.getSource();
            EditPart target = connectionEditPart.getTarget();
            if (source instanceof AbstractDiagramBorderNodeEditPart
                    && source.getParent().equals(editPart.getParent())) {
                figureToIgnore.add(((AbstractDiagramBorderNodeEditPart) source).getFigure());
            }
            if (target instanceof AbstractDiagramBorderNodeEditPart
                    && target.getParent().equals(editPart.getParent())) {
                figureToIgnore.add(((AbstractDiagramBorderNodeEditPart) target).getFigure());
            }
        }

        Rectangle validLocation = ((DBorderItemLocator) locator).getValidLocation(bounds, figure, figureToIgnore,
                Collections.<IFigure>emptyList());
        if (borderNodeCollapseManager.hasBeenExpanded()) {
            borderNodeCollapseManager.restoreCollapsedNode(editPart);
        }
        return !validLocation.equals(bounds);
    }

    private double getZoomScale(AbstractDiagramBorderNodeEditPart editPart) {
        ZoomManager zoomManager = editPart.getZoomManager();
        if (zoomManager != null) {
            return zoomManager.getZoom();
        }
        return 1d;
    }

    private ChangeBoundsRequest createChangeBoundsRequest(AbstractDiagramBorderNodeEditPart editPart) {

        ChangeBoundsRequest changeBoundsRequest = new ChangeBoundsRequest(RequestConstants.REQ_MOVE);
        changeBoundsRequest.setEditParts(editPart);
        Point moveDelta = getDeltaFromRequest();

        changeBoundsRequest.setMoveDelta(moveDelta);
        changeBoundsRequest.setLocation(((BendpointRequest) request).getLocation());

        return changeBoundsRequest;
    }

    private PrecisionPoint getDeltaFromRequest() {
        Object delta = request.getExtendedData().get(EDGE_MOVE_DELTA);
        Point moveDelta = new Point();
        if (delta != null) {
            if (delta instanceof Dimension) {
                moveDelta.setX(((Dimension) delta).width());
                moveDelta.setY(((Dimension) delta).height());
            }

            if (direction == PositionConstants.VERTICAL) {
                moveDelta.setX(0);
            } else {
                moveDelta.setY(0);
            }
        }
        return new PrecisionPoint(moveDelta);
    }

    @SuppressWarnings("unchecked")
    private List<Object> getAllConnections(AbstractDiagramBorderNodeEditPart borderNodeEditPart) {
        List<Object> connections = new ArrayList<Object>();
        connections.addAll(borderNodeEditPart.getSourceConnections());
        connections.addAll(borderNodeEditPart.getTargetConnections());
        return connections;
    }

    /**
     * Provides the border node move direction. If the border node is located on
     * the EAST or WEST parent side, the move direction is VERTICAL. If the
     * border node is located on the NORTH or the SOUTH parent side, the move
     * direction is HORIZONTAL.
     * 
     * @param borderNodeEditPart
     *            the border node edit part.
     * @return {@link PositionConstants#VERTICAL} or
     *         {@link PositionConstants#HORIZONTAL} following the border node
     *         location on parent.
     */
    private int getBorderNodeDirection(AbstractDiagramBorderNodeEditPart borderNodeEditPart) {
        IBorderItemLocator borderItemLocator = borderNodeEditPart.getBorderItemLocator();
        int side = borderItemLocator.getCurrentSideOfParent();
        int localDirection = PositionConstants.NONE;
        switch (side) {
        case PositionConstants.EAST:
            localDirection = PositionConstants.VERTICAL;
            break;
        case PositionConstants.WEST:
            localDirection = PositionConstants.VERTICAL;
            break;
        case PositionConstants.NORTH:
            localDirection = PositionConstants.HORIZONTAL;
            break;
        case PositionConstants.SOUTH:
            localDirection = PositionConstants.HORIZONTAL;
            break;
        default:
            break;
        }
        return localDirection;
    }
}