/***************************************************************************** * Copyright (c) 2009-2010 CEA LIST. * * * 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: * Vincent Lorenzo (CEA LIST) vincent.lorenzo@cea.fr - Initial API and implementation * *****************************************************************************/ package org.eclipse.papyrus.uml.diagram.menu.actions; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.List; 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.gef.EditPart; import org.eclipse.gef.RequestConstants; import org.eclipse.gef.RootEditPart; import org.eclipse.gef.commands.Command; import org.eclipse.gef.commands.CompoundCommand; import org.eclipse.gef.commands.UnexecutableCommand; import org.eclipse.gef.requests.ChangeBoundsRequest; import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart; import org.eclipse.papyrus.uml.diagram.common.layout.DistributionConstants; import org.eclipse.papyrus.uml.diagram.common.layout.DistributionTree; import org.eclipse.papyrus.uml.diagram.common.layout.EditPartTree; import org.eclipse.papyrus.uml.diagram.common.layout.LayoutUtils; /** * * This action allows to distribute element in the diagram or in their container * */ public class DistributeNodeAction extends AbstractDistributeAction { /** the tree used to organize the editpart */ private EditPartTree rootTree; /** indicates if the bounds used for the distribution are the parent bounds */ private boolean parentContainer; /** a margin used between the editpart and its container */ private double margin = 10; public DistributeNodeAction(int distribution, List<IGraphicalEditPart> selectedElements) { super(distribution, selectedElements); } /** * * @see org.eclipse.papyrus.uml.diagram.common.actions.AbstractDistributeAction#buildAction(java.util.List) * * @param elementsForAction */ @Override protected void buildAction(List<?> elementsForAction) { switch(distribution) { case DistributionConstants.DISTRIBUTE_H_CONTAINER_INT: case DistributionConstants.DISTRIBUTE_V_CONTAINER_INT: this.parentContainer = true; break; case DistributionConstants.DISTRIBUTE_H_NODES_INT: case DistributionConstants.DISTRIBUTE_V_NODES_INT: this.parentContainer = false; break; default: break; } List<EditPart> elements = new ArrayList<EditPart>(); for(int i = 0; i < elementsForAction.size(); i++) { elements.add(i, (EditPart)elementsForAction.get(i)); } rootTree = new DistributionTree(elements); //we create the requests and store them in rootTree createRequests(elements); } /** * * @see org.eclipse.papyrus.uml.diagram.common.actions.AbstractDistributeAction#getCommand() * * @return */ @Override public Command getCommand() { CompoundCommand command = new CompoundCommand("Command to Distribute Nodes"); //$NON-NLS-1$ Enumeration eptEnum = rootTree.breadthFirstEnumeration(); while(eptEnum.hasMoreElements()) { EditPartTree ept = (EditPartTree)eptEnum.nextElement(); if(ept.getEditPart() != null) { ChangeBoundsRequest currentReq = (ChangeBoundsRequest)ept.getRequest(); if(currentReq != null) { Command curCommand = null; curCommand = ept.getEditPart().getCommand(currentReq); if(curCommand != null) { command.add(curCommand); } } } } return command.isEmpty() ? UnexecutableCommand.INSTANCE : (Command)command; } /** * * Create the request for each editpart. Each request is stored in the node of the LayoutTree corresponding to this editpart * * @param editparts * the editparts to distribute * */ protected void createRequests(List<EditPart> editparts) { int depth = this.rootTree.getDepth(); for(int i = depth; i >= 0; i--) {//we iterate by level in the rootTree, beginning by the deepest level List<EditPartTree> epTrees = rootTree.getChildLevel(i);//get all the node of the same level if(i == 0) {//we work on the children of the children of epTree, but we have a problem when we want distribute element for the level 1! epTrees.add(rootTree); } for(int iter = 0; iter < epTrees.size(); iter++) {//we iterate on the same level nodes //get all the children for this node List<EditPartTree> children = epTrees.get(iter).getChildLevel(1); removeUnselectedTree(children); if((children.size() >= 2 && parentContainer) || (children.size() >= 3 && !parentContainer)) { //get the corresponding editparts List<EditPart> childrenEP = new ArrayList<EditPart>(); for(EditPartTree editPartTree : children) { childrenEP.add(editPartTree.getEditPart()); } //obtain the container area for these elements PrecisionRectangle boundsArea = calcultateArea(childrenEP); //we put a small space between the container and the node if(parentContainer/* && !isPortSelection */) { if(!(children.get(0).getEditPart().getParent() instanceof RootEditPart)) {//when the parent is the diagram, we doesn't add a margin! double usedMargin = margin > LayoutUtils.scrollBarSize ? margin : LayoutUtils.scrollBarSize; boundsArea.setX(boundsArea.preciseX() + usedMargin); boundsArea.setY(boundsArea.preciseY() + usedMargin); boundsArea.setWidth(boundsArea.preciseWidth() - 2 * usedMargin); boundsArea.setHeight(boundsArea.preciseHeight() - 2 * usedMargin); } } double[] hSpaceAndvSpace = calculatesSpaceBetweenNodes(boundsArea, childrenEP); //we sort the EditpartTree following x or y value (it depends on the action) Collections.sort(children, new CoordinatesComparator()); //variable containing the new position for the editpart (x or y following the distribution) double newPosition = 0; //we determine the location for the first editpart switch(this.distribution) { case DistributionConstants.DISTRIBUTE_H_CONTAINER_INT: newPosition = (horizontalDegradedMode == false) ? (boundsArea.preciseX + hSpaceAndvSpace[0]) : boundsArea.preciseX(); break; case DistributionConstants.DISTRIBUTE_H_NODES_INT: newPosition = boundsArea.preciseX; break; case DistributionConstants.DISTRIBUTE_V_CONTAINER_INT: newPosition = (verticalDegradedMode == false) ? (boundsArea.preciseY + hSpaceAndvSpace[1]) : boundsArea.preciseY(); break; case DistributionConstants.DISTRIBUTE_V_NODES_INT: newPosition = boundsArea.preciseY; break; default: break; } //request creation for all editparts for(EditPartTree editPartTree : children) { if(editPartTree.isSelected()) { //the new location for the editpart PrecisionPoint ptLocation = null; PrecisionRectangle absolutePosition = LayoutUtils.getAbsolutePosition(editPartTree.getEditPart()); // if(!isPortSelection) { if(this.distribution == DistributionConstants.DISTRIBUTE_H_CONTAINER_INT || this.distribution == DistributionConstants.DISTRIBUTE_H_NODES_INT) { ptLocation = new PrecisionPoint(newPosition, absolutePosition.preciseY); newPosition += absolutePosition.preciseWidth() + hSpaceAndvSpace[0]; } else {//vertical distribution ptLocation = new PrecisionPoint(absolutePosition.preciseX, newPosition); newPosition += absolutePosition.preciseHeight() + hSpaceAndvSpace[1]; } //we create the request if(ptLocation != null) { ChangeBoundsRequest req = new ChangeBoundsRequest(RequestConstants.REQ_MOVE); req.setEditParts(editPartTree.getEditPart()); PrecisionPoint oldLocation = new PrecisionPoint(absolutePosition.preciseX, absolutePosition.preciseY); Dimension delta = ptLocation.getDifference(oldLocation); req.setMoveDelta(new Point(delta.width, delta.height)); req.setSizeDelta(absolutePosition.getSize().getDifference(absolutePosition.getSize())); editPartTree.setRequest(req); } } } } } } } /** * Removes the unselected {@link EditPartTree} from the list * * @param epTrees * the {@link EditPartTree} list */ protected void removeUnselectedTree(List<EditPartTree> epTrees) { List<EditPartTree> removedChildren = new ArrayList<EditPartTree>(); for(EditPartTree editPartTree : epTrees) { if(!editPartTree.isSelected()) { removedChildren.add(editPartTree); } } epTrees.removeAll(removedChildren); } /** * Calculates the area used to do the distribution * * @param nodeChild * the editpart to distribute * @return * an area in which we can move the editpart */ protected PrecisionRectangle calcultateArea(List<EditPart> nodeChild) { //the returned area PrecisionRectangle area = new PrecisionRectangle(); if(parentContainer) { area = LayoutUtils.getAbsolutePosition(nodeChild.get(0).getParent()); } else { PrecisionRectangle tmpArea; tmpArea = LayoutUtils.getAbsolutePosition(nodeChild.get(0)); double minX = tmpArea.preciseX(); double maxX = tmpArea.preciseX() + tmpArea.preciseWidth(); double minY = tmpArea.preciseY(); double maxY = tmpArea.preciseY() + tmpArea.preciseHeight(); for(EditPart currentEP : nodeChild) {//we search the rectangle containing all the selected editpart if(currentEP.getSelected() != EditPart.SELECTED_NONE) { tmpArea = LayoutUtils.getAbsolutePosition(currentEP); minX = tmpArea.preciseX < minX ? tmpArea.preciseX : minX; minY = tmpArea.preciseY < minY ? tmpArea.preciseY : minY; maxX = tmpArea.getTopRight().preciseX() > maxX ? tmpArea.getTopRight().preciseX() : maxX; maxY = tmpArea.getBottomRight().preciseY() > maxY ? tmpArea.getBottomRight().preciseY() : maxY; } } area.setX(minX); area.setY(minY); area.setHeight(java.lang.Math.abs(maxY - minY)); area.setWidth(java.lang.Math.abs(maxX - minX)); } return area; } /** * Calculates the horizontal space and the vertical space to distribute the nodes * Set the fields {@link #horizontalDegradedMode} and {@link #verticalDegradedMode} to {@code true} or {@code false} * * @param boundsArea * the Rectangle used to do the distribution * @param nodeChild * the node to distribute in the Rectangle * @return {@code double[2]} with : * <ul> * <li>{@code double[0]} : the horizontal space between the nodes</li> * <li>{@code double[1]} : the vertical space between the nodes</li> * </ul> */ protected double[] calculatesSpaceBetweenNodes(PrecisionRectangle boundsArea, List<EditPart> nodeChild) { //reset of these 2 fields this.horizontalDegradedMode = false; this.verticalDegradedMode = false; //variables used to calculate the spacing double vertical = 0; double horizontal = 0; double vSpace = 0; double hSpace = 0; double[] hSpaceAndvSpace = new double[]{ 0, 0 }; //we calculate the length take by the element for(EditPart currentEP : nodeChild) { if(currentEP.getSelected() != EditPart.SELECTED_NONE) {//if the node is not selected, we ignore it PrecisionRectangle rect = LayoutUtils.getAbsolutePosition(currentEP); vertical += rect.preciseHeight(); horizontal += rect.preciseWidth(); } } //we determine the divisor double divisor = 1; if(parentContainer) { divisor = nodeChild.size() + 1; } else if(!parentContainer) { divisor = nodeChild.size() - 1; } hSpace = ((boundsArea.preciseWidth() - horizontal) / divisor); vSpace = ((boundsArea.preciseHeight() - vertical) / divisor); if(hSpace < 0 && parentContainer) { this.horizontalDegradedMode = true; double diff = boundsArea.preciseWidth() - horizontal; hSpace = diff / (divisor - 2); } if(vSpace < 0 && parentContainer) { this.verticalDegradedMode = true; double diff = boundsArea.preciseHeight() - vertical; vSpace = diff / (divisor - 2); } hSpaceAndvSpace[0] = hSpace; hSpaceAndvSpace[1] = vSpace; return hSpaceAndvSpace; } /** * * This class provides a comparator for the {@link EditPartTree}, using the coordinates of the representing {@link EditPart} */ protected class CoordinatesComparator implements Comparator<Object> { /** * * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) * * @param o1 * @param o2 * @return */ public int compare(Object o1, Object o2) { PrecisionRectangle rect1 = LayoutUtils.getAbsolutePosition(((EditPartTree)o1).getEditPart()); PrecisionRectangle rect2 = LayoutUtils.getAbsolutePosition(((EditPartTree)o2).getEditPart()); if(distribution == DistributionConstants.DISTRIBUTE_H_CONTAINER_INT || distribution == DistributionConstants.DISTRIBUTE_H_NODES_INT) { if(rect1.preciseX() < rect2.preciseX()) { return -1; } else if(rect1.preciseX() == rect2.preciseX()) { return 0; } else { return 1; } } else //vertical distribution if(rect1.preciseY() < rect2.preciseY()) { return -1; } else if(rect1.preciseY() == rect2.preciseY()) { return 0; } else { return 1; } } } }