/****************************************************************************** * Copyright (c) 2011-2013, Linagora * * 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: * Linagora - initial API and implementation *******************************************************************************/ package com.ebmwebsourcing.petals.services.eip.designer.edit.commands; import org.eclipse.draw2d.Connection; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.gef.GraphicalEditPart; import org.eclipse.gef.commands.CompoundCommand; import com.ebmwebsourcing.petals.services.eip.designer.edit.parts.AbstractNodeEditPart; import com.ebmwebsourcing.petals.services.eip.designer.model.AbstractNode; import com.ebmwebsourcing.petals.services.eip.designer.model.EipConnection; /** * This command switches the position of two nodes. * <p> * By position, we mean both the diagram position (coordinates) and the * position in the invocation order. The latter is defined by the index of * a connection in the outgoing connections of an EIP node. * </p> * <p> * Such a command only makes sense for ROUTER, DYNAMIC-ROUTER and ROUTING-SLIP. * However, there is no restriction at this level. The command can be executed for * any pattern. * </p> * <p> * When two nodes are switched, their incoming connection remain the same. * The position of these connections are themselves switched in array of outgoing * connections the invoking node owns. * </p> * * @author Vincent Zurczak - EBM WebSourcing */ public class NodeSwitchCommand extends CompoundCommand { private final AbstractNodeEditPart part1, part2; private final AbstractNode node1, node2; /** * Constructor. * @param part1 * @param part2 */ public NodeSwitchCommand( AbstractNodeEditPart part1, AbstractNodeEditPart part2 ) { if( part1 == null || part2 == null ) throw new IllegalArgumentException(); this.part1 = part1; this.part2 = part2; this.node1 = (AbstractNode) part1.getModel(); this.node2 = (AbstractNode) part2.getModel(); // Switch the connections EipNodeSwitchOutgoingConnectionsCommand cmd = new EipNodeSwitchOutgoingConnectionsCommand(); cmd.setConn1( this.node1.getIncomingConnection()); cmd.setConn2( this.node2.getIncomingConnection()); cmd.setEipNode( this.node1.getIncomingConnection().getSource()); add( cmd ); // Find the lowest node // Be CAREFUL! The y-axis is downward oriented. AbstractNode bottomNode, topNode; AbstractNodeEditPart bottomEp, topEp; if( this.node1.getLocation().y < this.node2.getLocation().y ) { bottomNode = this.node2; bottomEp = part2; topNode = this.node1; topEp = part1; } else { bottomNode = this.node1; bottomEp = part1; topNode = this.node2; topEp = part2; } Point bottomLoc = bottomNode.getLocation(); Point topLoc = topNode.getLocation(); // Compute the offset to adjust the Y-coordinates int yOffset = bottomEp.getFigure().getSize().height - topEp.getFigure().getSize().height; // Move up the lowest node NodeChangeLayoutCommand move = new NodeChangeLayoutCommand(); move.setNode( bottomNode ); move.setLocation( topLoc ); add( move ); // Move down the other node move = new NodeChangeLayoutCommand(); move.setNode( topNode ); move.setLocation( new Point( bottomLoc.x, bottomLoc.y + yOffset )); add( move ); // Fix the Y-coordinate for the following nodes in the column boolean startUpdate = false; int found = 0; for( EipConnection c : bottomNode.getIncomingConnection().getSource().getOutgoingConnections()) { // Only handle intermediate nodes if( found > 1 ) continue; // Do not update nodes that are before // Besides, the graphical position does not say anything // about the invocation order => test for both nodes if( c.getTarget().equals( topNode ) || c.getTarget().equals( bottomNode )) { startUpdate = true; found ++; continue; } // Adjust the Y coordinate (+/-) if( startUpdate ) { move = new NodeChangeLayoutCommand(); move.setNode( c.getTarget()); Point loc = c.getTarget().getLocation(); move.setLocation( new Point( loc.x, loc.y + yOffset )); add( move ); } } } /* * (non-Jsdoc) * @see org.eclipse.gef.commands.Command * #canExecute() */ @Override public boolean canExecute() { return this.node1.getIncomingConnection() != null && this.node2.getIncomingConnection() != null && this.node1.getIncomingConnection().getSource() != null && this.node1.getIncomingConnection().getSource() == this.node2.getIncomingConnection().getSource(); } /* * (non-Jsdoc) * @see org.eclipse.gef.commands.CompoundCommand * #execute() */ @Override public void execute() { animate( this.part1, this.node1, this.part2, this.node2 ); super.execute(); } /* * (non-Jsdoc) * @see org.eclipse.gef.commands.CompoundCommand * #undo() */ @Override public void undo() { animate( this.part2, this.node2, this.part1, this.node1 ); super.undo(); } /** * Animates the position changes on the diagram. * @param fromPart * @param fromNode * @param toPart * @param toNode */ private static void animate( AbstractNodeEditPart fromPart, AbstractNode fromNode, AbstractNodeEditPart toPart, AbstractNode toNode ) { // Compute intermediate coordinates Point loc1 = fromNode.getLocation(); Point loc2 = toNode.getLocation(); int centerY = fromNode.getLocation().y - toNode.getLocation().y; centerY = Math.abs( centerY ) / 2; int centerX = fromNode.getLocation().x - toNode.getLocation().x; centerX = Math.abs( centerX ) / 2; // Play an animation... // Hide the connections setTargetConnectionsVisible( fromPart, false ); setTargetConnectionsVisible( toPart, false ); // We have a circle, whose center is at the middle of the segment delimited // by the two node locations. // One node will go through one side of the circle, while the // second one will go through the other half. int step = 5; int loopX = 0, loopY = 0; // First part of the path while( loopX < centerX || loopY < centerY ) { int newX = loc1.x > loc2.x ? loc1.x - loopX : loc1.x + loopX; int newY = loc1.y > loc2.y ? loc1.y - loopY : loc1.y + loopY; Rectangle r = new Rectangle( newX, newY, -1, -1 ); ((GraphicalEditPart) fromPart.getParent()).setLayoutConstraint( fromPart, fromPart.getFigure(), r ); newX = loc1.x > loc2.x ? loc2.x + loopX : loc2.x - loopX; newY = loc1.y > loc2.y ? loc2.y + loopY : loc2.y - loopY; r = new Rectangle( newX, newY, -1, -1 ); ((GraphicalEditPart) toPart.getParent()).setLayoutConstraint( toPart, toPart.getFigure(), r ); ((GraphicalEditPart) toPart.getParent()).getFigure().getUpdateManager().performUpdate(); loopX += step; loopY += step; try { Thread.sleep( 10 ); } catch( InterruptedException e ) { // nothing } } // Second part of the path while( loopX > 0 || loopY > 0 ) { int newX = loc1.x > loc2.x ? loc1.x - loopX : loc1.x + loopX; int newY = loc1.y > loc2.y ? loc2.y + loopY : loc2.y - loopY; Rectangle r = new Rectangle( newX, newY, -1, -1 ); ((GraphicalEditPart) fromPart.getParent()).setLayoutConstraint( fromPart, fromPart.getFigure(), r ); newX = loc1.x > loc2.x ? loc2.x + loopX : loc2.x - loopX; newY = loc1.y > loc2.y ? loc1.y - loopY : loc1.y + loopY; r = new Rectangle( newX, newY, -1, -1 ); ((GraphicalEditPart) toPart.getParent()).setLayoutConstraint( toPart, toPart.getFigure(), r ); ((GraphicalEditPart) toPart.getParent()).getFigure().getUpdateManager().performUpdate(); loopX -= step; loopY -= step; try { Thread.sleep( 10 ); } catch( InterruptedException e ) { // nothing } } // Show the connections setTargetConnectionsVisible( fromPart, true ); setTargetConnectionsVisible( toPart, true ); // TODO: add padding and move the intermediate nodes... } /** * Defines the visibility of the target connections. * @param ep * @param visible */ private static void setTargetConnectionsVisible( GraphicalEditPart ep, boolean visible ) { for( Object o : ep.getTargetConnections()) { if( o instanceof Connection ) ((Connection) o).setVisible( visible ); } } }