diff options
Diffstat (limited to 'plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout')
12 files changed, 3400 insertions, 0 deletions
diff --git a/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/AbstractSequenceLayout.java b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/AbstractSequenceLayout.java new file mode 100644 index 0000000000..6ef85337cd --- /dev/null +++ b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/AbstractSequenceLayout.java @@ -0,0 +1,214 @@ +/******************************************************************************* + * Copyright (c) 2011, 2012 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.sequence.business.internal.layout; + +import java.util.Map; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; + +import org.eclipse.sirius.common.tools.api.util.Option; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.EndOfLife; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceElement; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.InstanceRole; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Lifeline; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.LostMessageEnd; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Message; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.SequenceDiagram; +import org.eclipse.sirius.diagram.sequence.business.internal.query.ISequenceElementQuery; + +/** + * Computes the appropriate graphical locations of sequence events and lifelines + * on a sequence diagram to reflect the semantic order. + * + * @param <S> + * the layouted element type. + * + * @param <T> + * the layout data type. + * + * @author mporhel + */ +public abstract class AbstractSequenceLayout<S, T> { + + /** + * The diagram to layout. + */ + protected final SequenceDiagram sequenceDiagram; + + /** + * A map to register old layout data. + */ + protected final Map<ISequenceElement, T> oldLayoutData; + + /** + * Constructor. + * + * @param sequenceDiagram + * the sequence diagram to layout. + */ + public AbstractSequenceLayout(SequenceDiagram sequenceDiagram) { + this.sequenceDiagram = sequenceDiagram; + + this.oldLayoutData = Maps.newHashMap(); + } + + /** + * Compute and apply a specific layout. Should be use in a + * {@link org.eclipse.emf.transaction.RecordingCommand}. + * + * @param pack + * pack the space between instance roles. + * + * @return true if a layout was applied + */ + public final boolean layout(boolean pack) { + // initialisation + init(pack); + + // vertical range computation + Map<? extends S, T> finalRanges = computeLayout(pack); + + // Apply computed ranges + boolean applied = applyComputedLayout(finalRanges, pack); + + dispose(); + + return applied; + + } + + /** + * Init the needed context for layout computation. + * + * @param pack + * pack the diagram + */ + protected abstract void init(boolean pack); + + /** + * Get old layout data before layout application. + * + * @param ise + * the requested sequence element. + * @return the old layout data. + */ + protected abstract T getOldLayoutData(S ise); + + /** + * Computes the absolute vertical (Y) location for all the messages in the + * sequence diagram. + * + * @param pack + * pack the diagram + * @return a map associating each message edit part to the new absolute + * vertical location it should have. + */ + protected abstract Map<? extends S, T> computeLayout(boolean pack); + + /** + * Apply the computed layout. + * + * @param finalRanges + * a map associating each message edit part to the new absolute + * vertical location it should have. + * @param pack + * pack the diagram + * + * @return true if a layout was applied + */ + protected abstract boolean applyComputedLayout(Map<? extends S, T> finalRanges, boolean pack); + + /** + * Dispose the layout context after layout application. + */ + protected void dispose() { + oldLayoutData.clear(); + } + + /** + * Return the non-explicitely created lifelines. + * + * @return the non-explicitely created lifelines. + */ + protected Iterable<Lifeline> getLifeLinesWithoutCreation() { + Predicate<Lifeline> isMainLifeline = new Predicate<Lifeline>() { + public boolean apply(Lifeline input) { + boolean main = true; + InstanceRole instanceRole = input.getInstanceRole(); + if (instanceRole != null) { + main = !instanceRole.isExplicitlyCreated(); + } + return main; + } + }; + return Iterables.filter(sequenceDiagram.getAllLifelines(), isMainLifeline); + } + + /** + * Return the non-explicitely destructed lifelines. + * + * @return the non-explicitely destructed lifelines. + */ + protected Iterable<Lifeline> getLifeLinesWithoutDestruction() { + Predicate<Lifeline> isLifelineWithoutDestruction = new Predicate<Lifeline>() { + public boolean apply(Lifeline input) { + boolean result = true; + // filter lifeline with endOfLife + Option<EndOfLife> endOfLife = input.getEndOfLife(); + if (endOfLife.some()) { + result = !endOfLife.get().isExplicitelyDestroyed(); + } + return result; + } + }; + return Iterables.filter(sequenceDiagram.getAllLifelines(), isLifelineWithoutDestruction); + } + + /** + * Check if the current lost end has been created from a tool application. + * Tool creation flags will be erased after the first layout. + * + * @param lostEnd + * the current end. + * @return true if the end was created by a tool. + */ + public static boolean createdFromTool(LostMessageEnd lostEnd) { + boolean toolCreated = false; + ISequenceElementQuery query = new ISequenceElementQuery(lostEnd); + if (query.hasAbsoluteBoundsFlag() && query.getFlaggedAbsoluteBounds().x == LayoutConstants.TOOL_CREATION_FLAG_FROM_SEMANTIC.x) { + toolCreated = true; + } + return toolCreated; + } + + /** + * Check if the current lost end has been created from a tool application. + * Tool creation flags will be erased after the first layout. + * + * @param lostEnd + * the current end. + * @return true if the end was created by a tool. + */ + public static boolean createdFromExternalChange(LostMessageEnd lostEnd) { + boolean externalCreation = false; + Option<Message> message = lostEnd.getMessage(); + ISequenceElementQuery query = new ISequenceElementQuery(lostEnd); + if (query.hasAbsoluteBoundsFlag() && query.getFlaggedAbsoluteBounds().x == LayoutConstants.EXTERNAL_CHANGE_FLAG.x) { + externalCreation = true; + } else if (message.some()) { + query = new ISequenceElementQuery(message.get()); + externalCreation = query.hasAbsoluteBoundsFlag() && query.getFlaggedAbsoluteBounds().x == LayoutConstants.EXTERNAL_CHANGE_FLAG.x; + } + return externalCreation; + } +} diff --git a/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/AbstractSequenceOrderingLayout.java b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/AbstractSequenceOrderingLayout.java new file mode 100644 index 0000000000..85561687d0 --- /dev/null +++ b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/AbstractSequenceOrderingLayout.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 2012 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.sequence.business.internal.layout; + +import java.util.List; +import java.util.Map; + +import org.eclipse.draw2d.geometry.Rectangle; + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import org.eclipse.sirius.diagram.sequence.business.internal.elements.SequenceDiagram; + +/** + * Computes the appropriate graphical locations of sequence events and lifelines + * on a sequence diagram to reflect the semantic order. + * + * @param <S> + * the layouted element type. + * + * @param <T> + * the layout data type. + * + * @param <U> + * the ordering type. + * + * @author mporhel + */ +public abstract class AbstractSequenceOrderingLayout<S, T, U> extends AbstractSequenceLayout<S, T> { + + /** + * The semantic ordering. + */ + protected final List<U> semanticOrdering; + + /** + * The graphical ordering. + */ + protected final List<U> graphicalOrdering; + + /** + * Semantic flagged ordering elements. + */ + protected final List<U> flaggedEnds; + + /** + * Old flagged absolute bounds. + */ + protected final Map<S, Rectangle> oldFlaggedLayoutData; + + /** + * Constructor. + * + * @param sequenceDiagram + * the sequence diagram to layout. + */ + public AbstractSequenceOrderingLayout(SequenceDiagram sequenceDiagram) { + super(sequenceDiagram); + + this.semanticOrdering = Lists.newArrayList(); + this.graphicalOrdering = Lists.newArrayList(); + this.flaggedEnds = Lists.newArrayList(); + this.oldFlaggedLayoutData = Maps.newHashMap(); + } + + /** + * Dispose the layout context after layout application. + */ + protected void dispose() { + semanticOrdering.clear(); + graphicalOrdering.clear(); + flaggedEnds.clear(); + oldFlaggedLayoutData.clear(); + super.dispose(); + } + + /** + * + * Look in the semantic, graphical and flaggedEnds orderings to retrieve the + * safest predecessor and try to keep a stable delta regarding it. + * + * @param currentPos + * the current position (x or y) + * @param element + * the current ordering element + * @param alreadyComputedLocations + * the already computed locations + * @return the delta stable position. + */ + protected int getDeltaStablePosition(final int currentPos, final U element, Map<? extends U, Integer> alreadyComputedLocations) { + int deltaStablePos = currentPos; + int semanticIndex = semanticOrdering.indexOf(element); + int graphicalIndex = graphicalOrdering.indexOf(element); + int flaggedIndex = flaggedEnds.indexOf(element); + + if (flaggedIndex != -1 && semanticIndex != 0 && graphicalIndex != -1) { + List<U> semanticPredecessors = Lists.newArrayList(semanticOrdering.subList(0, semanticIndex)); + List<U> graphicalPredecessors = Lists.newArrayList(graphicalOrdering.subList(0, graphicalIndex)); + List<U> flaggedPredecessors = Lists.newArrayList(flaggedEnds.subList(0, flaggedIndex)); + + // Intersection + semanticPredecessors.retainAll(flaggedEnds); + graphicalPredecessors.retainAll(flaggedEnds); + flaggedPredecessors.retainAll(semanticPredecessors); + + // Which is the safer position ? + Function<U, Integer> oldPosition = getOldPosition(); + U flaggedPred = null; + + if (Iterables.elementsEqual(semanticPredecessors, graphicalPredecessors) && !graphicalPredecessors.isEmpty()) { + flaggedPred = graphicalPredecessors.get(graphicalPredecessors.size() - 1); + } else { + // Desynchronisation -> flagged position + oldPosition = getOldFlaggedPosition(); + + // Look for the last semantic predecessor with same index in + // semantic and flagged lists. + U potentialSafePred = null; + for (int i = 0; i < flaggedPredecessors.size(); i++) { + U semPot = semanticPredecessors.get(i); + U flaggedPot = flaggedPredecessors.get(i); + if (semPot != null && semPot.equals(flaggedPot)) { + potentialSafePred = semPot; + } + } + + if (potentialSafePred != null) { + flaggedPred = potentialSafePred; + } + } + + if (flaggedPred != null) { + Integer predY = oldPosition.apply(flaggedPred); + Integer flaggedY = oldPosition.apply(element); + int delta = flaggedY - predY; + if (delta >= 0) { + deltaStablePos = alreadyComputedLocations.get(flaggedPred) + delta; + } + } + } + return deltaStablePos; + } + + /** + * A function to retrieve the old positions. + * + * @return the function. + */ + protected abstract Function<U, Integer> getOldPosition(); + + /** + * A function to retrieve the old flagged positions. + * + * @return the function. + */ + protected abstract Function<U, Integer> getOldFlaggedPosition(); +} diff --git a/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/EventEndToPositionFunction.java b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/EventEndToPositionFunction.java new file mode 100644 index 0000000000..0f77634cb8 --- /dev/null +++ b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/EventEndToPositionFunction.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2010 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.sequence.business.internal.layout; + +import java.util.Collection; +import java.util.Iterator; + +import com.google.common.base.Function; +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 org.eclipse.sirius.common.tools.api.util.Option; +import org.eclipse.sirius.DDiagramElement; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.AbstractFrame; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.AbstractNodeEvent; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceEvent; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Operand; +import org.eclipse.sirius.diagram.sequence.business.internal.ordering.EventEndHelper; +import org.eclipse.sirius.diagram.sequence.business.internal.query.ISequenceElementQuery; +import org.eclipse.sirius.diagram.sequence.ordering.CompoundEventEnd; +import org.eclipse.sirius.diagram.sequence.ordering.EventEnd; +import org.eclipse.sirius.diagram.sequence.ordering.SingleEventEnd; +import org.eclipse.sirius.diagram.sequence.util.Range; + +/** + * Function to compute position of and EventEnd. + * + * @author pcmporhel + */ +public class EventEndToPositionFunction implements Function<EventEnd, Integer> { + + private final Function<EventEnd, Collection<ISequenceEvent>> eventEndToSequenceEvents; + + private final Function<ISequenceEvent, Option<Range>> ranges; + + /** + * Constructor. + * + * @param eventEndToSequenceEvents + * function to get sequence event linked to the given event end. + * @param ranges + * ranges of sequence events. + */ + public EventEndToPositionFunction(Function<EventEnd, Collection<ISequenceEvent>> eventEndToSequenceEvents, Function<ISequenceEvent, Option<Range>> ranges) { + this.eventEndToSequenceEvents = eventEndToSequenceEvents; + this.ranges = ranges; + } + + /** + * {@inheritDoc} + */ + public Integer apply(EventEnd from) { + return getOldPosition(from, eventEndToSequenceEvents.apply(from)); + } + + private Integer getOldPosition(EventEnd end, Collection<ISequenceEvent> ises) { + SingleEventEnd see = null; + ISequenceEvent ise = null; + if (end instanceof SingleEventEnd && !ises.isEmpty()) { + see = (SingleEventEnd) end; + ise = ises.iterator().next(); + } else if (end instanceof CompoundEventEnd && !ises.isEmpty()) { + CompoundEventEnd cee = (CompoundEventEnd) end; + if (EventEndHelper.PUNCTUAL_COMPOUND_EVENT_END.apply(cee)) { + ise = getSafeEvent(ises); + } else { + ise = getSafeEvent(ises); + see = ise == null ? null : EventEndHelper.getSingleEventEnd(end, ((DDiagramElement) ise.getNotationView().getElement()).getTarget()); + } + } + return getOldPositionFromRange(see, ise); + } + + private ISequenceEvent getSafeEvent(Collection<ISequenceEvent> ises) { + ISequenceEvent ise = null; + Predicate<Object> safe = Predicates.or(Predicates.instanceOf(AbstractNodeEvent.class), Predicates.instanceOf(AbstractFrame.class)); + Collection<? extends ISequenceEvent> safeEvents = Lists.newArrayList(Iterables.filter(ises, safe)); + + if (!safeEvents.isEmpty()) { + ise = safeEvents.iterator().next(); + } else if (Iterables.size(Iterables.filter(ises, Operand.class)) == 2) { + ise = getSafeOperandEnd(ises); + } else { + ise = ises.iterator().next(); + } + return ise; + } + + private ISequenceEvent getSafeOperandEnd(Collection<ISequenceEvent> ises) { + ISequenceEvent ise = null; + + Iterator<ISequenceEvent> iterator = ises.iterator(); + ISequenceEvent pot1 = iterator.next(); + ISequenceEvent pot2 = iterator.next(); + + if (new ISequenceElementQuery(pot1).hasAbsoluteBoundsFlag()) { + ise = pot1; + } else if (new ISequenceElementQuery(pot2).hasAbsoluteBoundsFlag()) { + ise = pot2; + } + return ise; + } + + /** + * Get the old position of the corresponding event end, regarding the given + * event old range. event. + * + * @param see + * event end + * @param ise + * corresponding event + * @return old position + */ + protected Integer getOldPositionFromRange(SingleEventEnd see, ISequenceEvent ise) { + Integer oldPosition = 0; + Option<Range> oldRange = ranges.apply(ise); + + if (ise != null && oldRange.some()) { + if (see != null) { + oldPosition = see.isStart() ? oldRange.get().getLowerBound() : oldRange.get().getUpperBound(); + } else if (see == null && ise.isLogicallyInstantaneous()) { + oldPosition = oldRange.get().middleValue(); + } + } + return oldPosition; + } +} diff --git a/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/LayoutConstants.java b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/LayoutConstants.java new file mode 100644 index 0000000000..347faa8070 --- /dev/null +++ b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/LayoutConstants.java @@ -0,0 +1,193 @@ +/******************************************************************************* + * Copyright (c) 2010 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.sequence.business.internal.layout; + +import org.eclipse.draw2d.geometry.Rectangle; + +/** + * A utility class to centralize all the dimensions ("magic number") used during + * the layout of sequence diagrams. Unless otherwise stated, all the dimensions + * and locations are in pixels. + * + * @author pcdavid, smonnier, mporhel, edugueperoux + */ +public final class LayoutConstants { + + /** + * This should be LayoutUtils.SCALE, but we do not want to depend here on + * the plug-in where LayoutUtils is defined. + */ + public static final int UNIT = 10; + + /** + * The minimal amount of vertical space between the bottom of an instance + * role and the first element (message or execution) on its root execution + * for packing layout. + */ + public static final int TIME_START_OFFSET = 3 * UNIT; + + /** + * The minimal amount of vertical space between the bottom of an instance + * role and the first element (message or execution) on its root execution + * for non-packing layout. + */ + public static final int TIME_START_MIN_OFFSET = 1 * UNIT; + + /** + * The minimal amount of vertical space between the last element (message or + * execution) on a root execution and the bottom of the root execution. + */ + public static final int TIME_STOP_OFFSET = 5 * UNIT; + + /** + * The x position of the first left lifeline. + */ + public static final int LIFELINES_START_X = 5 * UNIT; + + /** + * The lifeline top position. + */ + public static final int LIFELINES_START_Y = 5 * UNIT; + + /** + * The minimum horizontal gap to keep between two neighboring lifelines. + */ + public static final int LIFELINES_MIN_X_GAP = 1 * UNIT; + + /** + * The minimum horizontal gap to keep between two neighboring lifelines. + */ + public static final int LIFELINES_MIN_PACKED_X_GAP = 3 * UNIT; + + /** + * The minimum vertical range upper bound for lifelines. + */ + public static final int LIFELINES_MIN_Y = 20 * UNIT; + + /** + * The minimum vertical space to leave between two successive messages. + */ + public static final int MIN_INTER_SEQUENCE_EVENTS_VERTICAL_GAP = 2 * UNIT; + + /** + * The minimum margin between the start/finish of an execution and its + * first/last child. + */ + public static final int EXECUTION_CHILDREN_MARGIN = 5; + + /** + * The default width used for newly created interaction uses. + */ + public static final int DEFAULT_INTERACTION_USE_WIDTH = 6 * UNIT; + + /** + * The default width used for newly created combined fragments. + */ + public static final int DEFAULT_COMBINED_FRAGMENT_WIDTH = 10 * UNIT; + + /** + * The default width used for newly created operands. + */ + public static final int DEFAULT_OPERAND_WIDTH = 10 * UNIT; + + /** + * The default height used for newly created interaction uses. + */ + public static final int DEFAULT_INTERACTION_USE_HEIGHT = 5 * UNIT; + + /** + * How much space between the top of a combined fragment and the top of its + * first operand. + */ + public static final int COMBINED_FRAGMENT_TITLE_HEIGHT = 3 * UNIT; + + /** + * The default height used for newly created operand. + */ + public static final int DEFAULT_OPERAND_HEIGHT = 6 * UNIT; + + /** + * The default height used for newly created combined fragments. + */ + public static final int DEFAULT_COMBINED_FRAGMENT_HEIGHT = COMBINED_FRAGMENT_TITLE_HEIGHT + DEFAULT_OPERAND_HEIGHT; + + /** + * The default height used for newly created executions. + */ + public static final int DEFAULT_EXECUTION_HEIGHT = 3 * UNIT; + + /** + * The default width used for newly created executions. + */ + public static final int DEFAULT_EXECUTION_WIDTH = 2 * UNIT; + + /** + * How much space there is between bendpoints of a message to self. + */ + public static final int MESSAGE_TO_SELF_BENDPOINT_VERTICAL_GAP = UNIT; + + /** + * How much space there is between bendpoints of a message to self. + */ + public static final int MESSAGE_TO_SELF_BENDPOINT_HORIZONTAL_GAP = 3 * UNIT; + + /** + * How much space there is between bendpoints of a two included messages to + * self. + */ + public static final int MESSAGE_TO_SELF_HORIZONTAL_GAP = 2 * UNIT; + + /** + * The default width used for lost messages. + */ + public static final int LOST_MESSAGE_DEFAULT_WIDTH = 6 * UNIT; + + /** Defines the visual appearance of lifelines. */ + public static final int[] LIFELINE_DASH_STYLE = new int[] { 10, 10 }; + + /** Defines the visual appearance of operands. */ + public static final int[] OPERAND_DASH_STYLE = new int[] { 5, 5 }; + + /** + * Default height of execution after a layout, also the default height of + * syncCall/asyncCall execution after creation. + */ + public static final int INTERACTION_EXECUTION_MIN_HEIGHT_AFTER_LAYOUT = 50; + + /** + * Default height of execution after a layout. + */ + public static final int INTERACTION_STATE_MIN_HEIGHT_AFTER_LAYOUT = 30; + + /** + * Margin between a parent frame and its children. + */ + public static final int BORDER_FRAME_MARGIN = UNIT; + + /** + * Tool creation flag for directly created DDiagramElement. + */ + public static final Rectangle TOOL_CREATION_FLAG = new Rectangle(-1, -1, 0, 0); + + /** + * Tool creation flag for created DDiagramElement from semantic. + */ + public static final Rectangle TOOL_CREATION_FLAG_FROM_SEMANTIC = new Rectangle(-1, -2, 0, 0); + + /** + * External Reparent / Reconnect detection flag. + */ + public static final Rectangle EXTERNAL_CHANGE_FLAG = new Rectangle(-2, 0, 0, 0); + + private LayoutConstants() { + // Prevents instantiation. + } +} diff --git a/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/SequenceLayout.java b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/SequenceLayout.java new file mode 100644 index 0000000000..eda7496903 --- /dev/null +++ b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/SequenceLayout.java @@ -0,0 +1,138 @@ +/******************************************************************************* + * Copyright (c) 2010, 2012 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.sequence.business.internal.layout; + +import org.eclipse.gmf.runtime.notation.Diagram; +import org.eclipse.gmf.runtime.notation.Node; +import org.eclipse.gmf.runtime.notation.Size; + +import com.google.common.collect.Iterables; + +import org.eclipse.sirius.common.tools.api.util.Option; +import org.eclipse.sirius.CollapseFilter; +import org.eclipse.sirius.DDiagramElement; +import org.eclipse.sirius.diagram.business.api.query.NodeQuery; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceElementAccessor; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceNode; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.SequenceDiagram; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.flag.SequenceDiagramAbsoluteBoundsFlagger; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.horizontal.SequenceHorizontalLayout; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.observation.SequenceObservationLayout; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.vertical.SequenceVerticalLayout; + +/** + * Computes the appropriate graphical locations of sequence events and lifelines + * on a sequence diagram to reflect the semantic order. + * + * @author pcdavid, mporhel + */ +public class SequenceLayout { + + private final Option<SequenceDiagram> sequenceDiagram; + + private SequenceHorizontalLayout sequenceHorizontalLayout; + + private SequenceVerticalLayout sequenceVerticalLayout; + + private SequenceObservationLayout sequenceObservationLayout; + + /** + * Constructor. + * + * @param diagram + * the sequence diagram for which to compute the messages + * locations. + */ + public SequenceLayout(Diagram diagram) { + this.sequenceDiagram = ISequenceElementAccessor.getSequenceDiagram(diagram); + this.sequenceHorizontalLayout = new SequenceHorizontalLayout(sequenceDiagram.get()); + this.sequenceVerticalLayout = new SequenceVerticalLayout(sequenceDiagram.get()); + this.sequenceObservationLayout = new SequenceObservationLayout(sequenceDiagram.get()); + } + + public Option<SequenceDiagram> getSequenceDiagram() { + return sequenceDiagram; + } + + /** + * Compute and apply horizontal layout. Should be use in a + * {@link org.eclipse.emf.transaction.RecordingCommand}. + * + * @param pack + * pack the space between instance roles. + * @return true if horizontal layout has been done + */ + public boolean horizontalLayout(boolean pack) { + if (this.sequenceHorizontalLayout != null && this.sequenceDiagram.some()) { + return this.sequenceHorizontalLayout.layout(pack); + } + return false; + } + + /** + * Compute and apply vertical layout. Should be use in a + * {@link org.eclipse.emf.transaction.RecordingCommand}. + * + * @param pack + * pack the space between sequence events + * @return true if vertical layout has been done + */ + public boolean verticalLayout(boolean pack) { + if (this.sequenceVerticalLayout != null && this.sequenceDiagram.some()) { + return this.sequenceVerticalLayout.layout(pack); + } + return false; + } + + /** + * Compute and apply observation layout. Should be use in a + * {@link org.eclipse.emf.transaction.RecordingCommand} and after vertical + * and horizontal layout. + * + * It will place the ObservationPoint. + * + * @param pack + * pack the space between sequence events + * @return true if horizontal layout has been done + */ + public boolean observationLayout(boolean pack) { + if (this.sequenceObservationLayout != null && this.sequenceDiagram.some()) { + return this.sequenceObservationLayout.layout(pack); + } + return false; + } + + /** + * Flag DDiagramElement with their absolute bounds. + */ + public void flagSequenceEvents() { + // Flag event with their new position + if (this.sequenceDiagram.some()) { + SequenceDiagramAbsoluteBoundsFlagger flagHelper = new SequenceDiagramAbsoluteBoundsFlagger(sequenceDiagram.get()); + flagHelper.flag(); + + updateCollapseFilters(); + } + } + + private void updateCollapseFilters() { + for (ISequenceNode isn : Iterables.concat(sequenceDiagram.get().getAllAbstractNodeEvents(), sequenceDiagram.get().getAllObservationPoints(), sequenceDiagram.get().getAllLifelines())) { + Node node = isn.getNotationNode(); + if (new NodeQuery(node).isCollapsed() && node.getElement() instanceof DDiagramElement && node.getLayoutConstraint() instanceof Size) { + Size size = (Size) node.getLayoutConstraint(); + DDiagramElement dde = (DDiagramElement) node.getElement(); + for (CollapseFilter collapseFilter : Iterables.filter(dde.getGraphicalFilters(), CollapseFilter.class)) { + collapseFilter.setHeight(size.getHeight()); + } + } + } + } +} diff --git a/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/flag/AbstractSequenceAbsoluteBoundsFlagger.java b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/flag/AbstractSequenceAbsoluteBoundsFlagger.java new file mode 100644 index 0000000000..41dfc0c4fb --- /dev/null +++ b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/flag/AbstractSequenceAbsoluteBoundsFlagger.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2011 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.sequence.business.internal.layout.flag; + +import java.util.Collection; + +import org.eclipse.draw2d.geometry.Rectangle; +import org.eclipse.emf.ecore.EObject; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import org.eclipse.sirius.AbsoluteBoundsFilter; +import org.eclipse.sirius.DDiagramElement; +import org.eclipse.sirius.SiriusFactory; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceElement; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.LostMessageEnd; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Message; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.LayoutConstants; + +/** + * Helper to compute and attach absolute bounds flag for sequence events. + * + * @author mporhel + */ +public abstract class AbstractSequenceAbsoluteBoundsFlagger { + + /** + * Compute absolute bounds flags for each delimited sequence events of the + * current diagram. + */ + public final void flag() { + for (ISequenceElement ise : getEventsToFlag()) { + flag(ise); + } + } + + private void flag(ISequenceElement ise) { + // add flag + EObject element = ise.getNotationView().getElement(); + if (element instanceof DDiagramElement) { + DDiagramElement dde = (DDiagramElement) element; + Rectangle absBounds = ise.getProperLogicalBounds(); + + AbsoluteBoundsFilter flag = getOrCreateFlag(dde); + + if (ise instanceof LostMessageEnd && !((LostMessageEnd) ise).getMessage().some()) { + flag.setX(LayoutConstants.TOOL_CREATION_FLAG_FROM_SEMANTIC.x); + } + + // Update flag + if (flag != null && absBounds != null) { + if (!(ise instanceof Message)) { + flag.setX(absBounds.x); + flag.setWidth(absBounds.width); + } + flag.setY(absBounds.y); + flag.setHeight(absBounds.height); + } + } + } + + /** + * Gets events to flag. + * + * @return a collection of events to flag. + */ + protected abstract Collection<ISequenceElement> getEventsToFlag(); + + private AbsoluteBoundsFilter getOrCreateFlag(DDiagramElement dde) { + AbsoluteBoundsFilter flag = null; + Collection<AbsoluteBoundsFilter> flags = Lists.newArrayList(Iterables.filter(dde.getGraphicalFilters(), AbsoluteBoundsFilter.class)); + for (AbsoluteBoundsFilter prevFlag : flags) { + flag = prevFlag; + break; + } + + if (flag == null) { + flag = SiriusFactory.eINSTANCE.createAbsoluteBoundsFilter(); + dde.getGraphicalFilters().add(flag); + } + return flag; + } + +} diff --git a/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/flag/SequenceDiagramAbsoluteBoundsFlagger.java b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/flag/SequenceDiagramAbsoluteBoundsFlagger.java new file mode 100644 index 0000000000..30ca92ada5 --- /dev/null +++ b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/flag/SequenceDiagramAbsoluteBoundsFlagger.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2011 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.sequence.business.internal.layout.flag; + +import java.util.Collection; +import java.util.List; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceElement; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.SequenceDiagram; + +/** + * Helper to compute and attach absolute bounds flag for sequence events. + * + * @author mporhel + */ +public class SequenceDiagramAbsoluteBoundsFlagger extends AbstractSequenceAbsoluteBoundsFlagger { + + private SequenceDiagram diagram; + + /** + * Constructor. + * + * @param sequenceDiagram + * the sequence diagram to flag. + */ + public SequenceDiagramAbsoluteBoundsFlagger(SequenceDiagram sequenceDiagram) { + this.diagram = sequenceDiagram; + } + + /** + * {@inheritDoc} + */ + @Override + protected Collection<ISequenceElement> getEventsToFlag() { + List<ISequenceElement> eventsToFlag = Lists.newArrayList(); + if (diagram != null) { + Iterables.addAll(eventsToFlag, diagram.getAllDelimitedSequenceEvents()); + Iterables.addAll(eventsToFlag, diagram.getAllLostMessageEnds()); + Iterables.addAll(eventsToFlag, diagram.getAllInstanceRoles()); + } + return eventsToFlag; + } +} diff --git a/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/flag/SequenceEventAbsoluteBoundsFlagger.java b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/flag/SequenceEventAbsoluteBoundsFlagger.java new file mode 100644 index 0000000000..201dfdab79 --- /dev/null +++ b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/flag/SequenceEventAbsoluteBoundsFlagger.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2011 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.sequence.business.internal.layout.flag; + +import java.util.Collection; +import java.util.List; + +import com.google.common.collect.Lists; + +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceElement; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceEvent; + +/** + * Helper to compute and attach absolute bounds flag for sequence events. + * + * @author mporhel + */ +public class SequenceEventAbsoluteBoundsFlagger extends AbstractSequenceAbsoluteBoundsFlagger { + + private ISequenceEvent ise; + + /** + * Constructor. + * + * @param ise + * the sequence event to flag. + */ + public SequenceEventAbsoluteBoundsFlagger(ISequenceEvent ise) { + this.ise = ise; + } + + /** + * {@inheritDoc} + */ + @Override + protected Collection<ISequenceElement> getEventsToFlag() { + List<ISequenceElement> eventsToFlag = Lists.newArrayList(); + if (ise != null) { + eventsToFlag.add(ise); + } + return eventsToFlag; + } +} diff --git a/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/horizontal/LostMessageEndHorizontalLayoutHelper.java b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/horizontal/LostMessageEndHorizontalLayoutHelper.java new file mode 100644 index 0000000000..de4e392692 --- /dev/null +++ b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/horizontal/LostMessageEndHorizontalLayoutHelper.java @@ -0,0 +1,451 @@ +/******************************************************************************* + * Copyright (c) 2010 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.sequence.business.internal.layout.horizontal; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.draw2d.geometry.Rectangle; +import org.eclipse.emf.ecore.EObject; + +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; + +import org.eclipse.sirius.common.tools.api.util.Option; +import org.eclipse.sirius.DEdge; +import org.eclipse.sirius.EdgeTarget; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceEvent; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceNode; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.InstanceRole; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Lifeline; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.LostMessageEnd; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Message; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Message.Kind; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Operand; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.SequenceDiagram; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.AbstractSequenceLayout; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.LayoutConstants; +import org.eclipse.sirius.diagram.sequence.util.Range; + +/** + * Computes the appropriate graphical locations of sequence events and lifelines + * on a sequence diagram to reflect the semantic order. + * + * @author pcdavid, mporhel + */ +public class LostMessageEndHorizontalLayoutHelper { + + private final Map<LostMessageEnd, Message> lostMessages = Maps.newHashMap(); + + private final Multimap<Lifeline, LostMessageEnd> lostSources = HashMultimap.create(); + + private final Multimap<Lifeline, LostMessageEnd> lostTargets = HashMultimap.create(); + + private Map<LostMessageEnd, Operand> operands = Maps.newHashMap(); + + private Multimap<Operand, LostMessageEnd> operands2lostEnds = HashMultimap.create(); + + private Set<LostMessageEnd> diagramLostEnds = Sets.newHashSet(); + + private SequenceDiagram sequenceDiagram; + + private Set<LostMessageEnd> unconnectedLostEnds = Sets.newHashSet(); + + /** + * Constructor. + * + * @param diagram + * the sequence diagram for which to compute the horizontal + * locations. + */ + public LostMessageEndHorizontalLayoutHelper(SequenceDiagram diagram) { + this.sequenceDiagram = diagram; + } + + /** + * Populate lost message ends and helper context. + */ + public void populateLostMessageEnds() { + for (Message msg : sequenceDiagram.getAllMessages()) { + populateLostEnds(msg); + } + + registerUnconnectedLostEnds(); + } + + private void registerUnconnectedLostEnds() { + Predicate<LostMessageEnd> unConnectedEnds = Predicates.not(Predicates.in(lostMessages.keySet())); + for (LostMessageEnd lme : Iterables.filter(sequenceDiagram.getAllLostMessageEnds(), unConnectedEnds)) { + unconnectedLostEnds.add(lme); + + // look viewpoint edges + if (lme.getNotationNode().getElement() instanceof EdgeTarget) { + EdgeTarget targetNode = getKnownTargetNode(lme); + if (targetNode != null) { + ISequenceEvent ise = getISequenceEvent(targetNode); + if (ise != null && ise.getLifeline().some()) { + lostSources.put(ise.getLifeline().get(), lme); + Option<Operand> parentOperand = ise.getParentOperand(); + if (parentOperand.some()) { + operands.put(lme, parentOperand.get()); + operands2lostEnds.put(parentOperand.get(), lme); + } else { + diagramLostEnds.add(lme); + } + } + } + EdgeTarget sourceNode = getKnownSourceNode(lme); + if (sourceNode != null) { + ISequenceEvent ise = getISequenceEvent(sourceNode); + if (ise != null && ise.getLifeline().some()) { + lostTargets.put(ise.getLifeline().get(), lme); + Option<Operand> parentOperand = ise.getParentOperand(); + if (parentOperand.some()) { + operands.put(lme, parentOperand.get()); + operands2lostEnds.put(parentOperand.get(), lme); + } else { + diagramLostEnds.add(lme); + } + } + } + } + } + } + + private void populateLostEnds(Message msg) { + ISequenceNode sourceElement = msg.getSourceElement(); + ISequenceNode targetElement = msg.getTargetElement(); + + Option<Operand> parentOperand = msg.getParentOperand(); + if (sourceElement != null && targetElement != null) { + Option<Lifeline> sourceLifeline = sourceElement.getLifeline(); + Option<Lifeline> targetLifeline = targetElement.getLifeline(); + + // Only messages with one lost end are handled. + if (sourceElement instanceof LostMessageEnd && targetLifeline.some()) { + LostMessageEnd sourceLME = (LostMessageEnd) sourceElement; + lostSources.put(targetLifeline.get(), sourceLME); + lostMessages.put(sourceLME, msg); + if (parentOperand.some()) { + operands.put(sourceLME, parentOperand.get()); + operands2lostEnds.put(parentOperand.get(), sourceLME); + } else { + diagramLostEnds.add(sourceLME); + } + } else if (targetElement instanceof LostMessageEnd && sourceLifeline.some()) { + LostMessageEnd targetLME = (LostMessageEnd) targetElement; + lostTargets.put(sourceLifeline.get(), targetLME); + lostMessages.put(targetLME, msg); + if (parentOperand.some()) { + operands.put(targetLME, parentOperand.get()); + operands2lostEnds.put(parentOperand.get(), targetLME); + } else { + diagramLostEnds.add(targetLME); + } + } + } + } + + /** + * Computes the delta between lostEnds and their attached lifeline. + * + * @param pack + * pack the space between instance roles. + * @return computed deltas. + */ + public Map<LostMessageEnd, Integer> computeLostMessageEndDeltaWithLifeline(boolean pack) { + Map<LostMessageEnd, Integer> deltas = Maps.newHashMap(); + + for (Lifeline lifeline : sequenceDiagram.getAllLifelines()) { + int lifelineX = lifeline.getProperLogicalBounds().x; + + // Align sources on left + Map<Operand, Integer> maxOpSourceDeltas = Maps.newHashMap(); + int maxLifelineSourceDelta = 0; + for (LostMessageEnd lostSource : lostSources.get(lifeline)) { + Rectangle bounds = lostSource.getProperLogicalBounds().getCopy(); + int delta = bounds.x - lifelineX; + if (pack || AbstractSequenceLayout.createdFromTool(lostSource) || AbstractSequenceLayout.createdFromExternalChange(lostSource)) { + delta = getFoundEndPackedX(lostSource, lifeline, lifelineX, bounds.width); + } + deltas.put(lostSource, delta); + + // Force align + if (operands.containsKey(lostSource)) { + Operand op = operands.get(lostSource); + int opMax = 0; + if (maxOpSourceDeltas.containsKey(op)) { + opMax = maxOpSourceDeltas.get(op); + } + opMax = Math.min(opMax, delta); + maxOpSourceDeltas.put(op, opMax); + } else { + Kind kind = getMessageKind(lostSource); + + if (!Message.Kind.CREATION.equals(kind) && !Message.Kind.DESTRUCTION.equals(kind)) { + maxLifelineSourceDelta = Math.min(maxLifelineSourceDelta, delta); + } + } + } + + // Align targets on right + Map<Operand, Integer> maxOpTargetDeltas = Maps.newHashMap(); + int maxLifelineTargetDelta = 0; + for (LostMessageEnd lostTarget : lostTargets.get(lifeline)) { + Rectangle bounds = lostTarget.getProperLogicalBounds().getCopy(); + int delta = bounds.x - lifelineX; + if (pack || AbstractSequenceLayout.createdFromTool(lostTarget) || AbstractSequenceLayout.createdFromExternalChange(lostTarget)) { + delta = LayoutConstants.LOST_MESSAGE_DEFAULT_WIDTH; + if (lostMessages.containsKey(lostTarget)) { + Message message = lostMessages.get(lostTarget); + delta += message.getSourceElement().getProperLogicalBounds().right() - lifelineX; + } else if (unconnectedLostEnds.contains(lostTarget)) { + ISequenceEvent sourceEvent = getISequenceEvent(getKnownSourceNode(lostTarget)); + if (sourceEvent != null) { + delta += sourceEvent.getProperLogicalBounds().right() - lifelineX; + } + } + } + deltas.put(lostTarget, delta); + + // Force align + if (operands.containsKey(lostTarget)) { + Operand op = operands.get(lostTarget); + int opMax = 0; + if (maxOpTargetDeltas.containsKey(op)) { + opMax = maxOpTargetDeltas.get(op); + } + opMax = Math.max(opMax, delta); + maxOpTargetDeltas.put(op, opMax); + } else { + maxLifelineTargetDelta = Math.max(maxLifelineTargetDelta, delta); + } + } + + // Force align + if (pack) { + for (Map.Entry<Operand, Integer> entry : maxOpSourceDeltas.entrySet()) { + Integer maxSourceDelta = entry.getValue(); + for (LostMessageEnd source : Iterables.filter(operands2lostEnds.get(entry.getKey()), Predicates.in(lostSources.get(lifeline)))) { + deltas.put(source, maxSourceDelta); + } + } + for (LostMessageEnd source : Iterables.filter(lostSources.get(lifeline), Predicates.not(Predicates.in(operands.keySet())))) { + Kind kind = getMessageKind(source); + + if (!Message.Kind.CREATION.equals(kind) && !Message.Kind.DESTRUCTION.equals(kind)) { + deltas.put(source, maxLifelineSourceDelta); + } + } + for (Map.Entry<Operand, Integer> entry : maxOpTargetDeltas.entrySet()) { + Integer maxTargetDelta = entry.getValue(); + for (LostMessageEnd target : Iterables.filter(operands2lostEnds.get(entry.getKey()), Predicates.in(lostTargets.get(lifeline)))) { + deltas.put(target, maxTargetDelta); + } + } + for (LostMessageEnd target : Iterables.filter(lostTargets.get(lifeline), Predicates.not(Predicates.in(operands.keySet())))) { + deltas.put(target, maxLifelineTargetDelta); + } + } + } + return deltas; + } + + private int getFoundEndPackedX(LostMessageEnd lostSourceEnd, Lifeline lifeline, int lifelineX, int lostEndWidth) { + int refX = lifelineX; + Kind kind = getMessageKind(lostSourceEnd); + + if (Message.Kind.CREATION.equals(kind) && lifeline.getInstanceRole() != null) { + refX = lifeline.getInstanceRole().getProperLogicalBounds().x; + } else if (Message.Kind.DESTRUCTION.equals(kind) && lifeline.getEndOfLife().some()) { + refX = lifeline.getEndOfLife().get().getProperLogicalBounds().x; + } + return refX - lifelineX - LayoutConstants.LOST_MESSAGE_DEFAULT_WIDTH - lostEndWidth; + } + + /** + * Get the found ends gap. + * + * @param lifeline + * the current lifeline. + * @param zone + * zone to get look for lost messages, can be null. + * @param lostEndsDelta + * the computed deltas with lifeline. + * @return the gap. + */ + public int getLeftGap(Lifeline lifeline, Range zone, Map<LostMessageEnd, Integer> lostEndsDelta) { + int foundEndsGap = getLostMessageEndsGap(lifeline, lostSources, zone, lostEndsDelta, true); + int lostMessageEndsGap = getLostMessageEndsGap(lifeline, lostTargets, zone, lostEndsDelta, true); + return Math.max(lostMessageEndsGap, foundEndsGap); + } + + /** + * Get the lost ends gap. + * + * @param lifeline + * the current lifeline. + * @param zone + * zone to get look for lost messages, can be null. + * @param lostEndsDelta + * the computed deltas with lifeline. + * @return the gap. + */ + public int getRightEndsGap(Lifeline lifeline, Range zone, Map<LostMessageEnd, Integer> lostEndsDelta) { + int lostMessageEndsGap = getLostMessageEndsGap(lifeline, lostTargets, zone, lostEndsDelta, false); + int foundEndsGap = getLostMessageEndsGap(lifeline, lostSources, zone, lostEndsDelta, false); + return Math.max(lostMessageEndsGap, foundEndsGap); + } + + private EdgeTarget getKnownSourceNode(LostMessageEnd lme) { + EdgeTarget sourceNode = null; + EdgeTarget dde = (EdgeTarget) lme.getNotationNode().getElement(); + Iterable<DEdge> incomingEdges = Iterables.filter(dde.getIncomingEdges(), Message.viewpointElementPredicate()); + if (!Iterables.isEmpty(incomingEdges)) { + DEdge msg = incomingEdges.iterator().next(); + sourceNode = msg.getSourceNode(); + } + return sourceNode; + } + + private EdgeTarget getKnownTargetNode(LostMessageEnd lme) { + EdgeTarget targetNode = null; + EdgeTarget dde = (EdgeTarget) lme.getNotationNode().getElement(); + Iterable<DEdge> outgoingEdges = Iterables.filter(dde.getOutgoingEdges(), Message.viewpointElementPredicate()); + if (!Iterables.isEmpty(outgoingEdges)) { + DEdge msg = outgoingEdges.iterator().next(); + targetNode = msg.getTargetNode(); + } + return targetNode; + } + + // Get the first known event in hierarchy. + private ISequenceEvent getISequenceEvent(EdgeTarget lostNode) { + ISequenceEvent correspondingEvent = null; + List<ISequenceEvent> knownEnds = Lists.newArrayList(); + knownEnds.addAll(sequenceDiagram.getAllLifelines()); + knownEnds.addAll(sequenceDiagram.getAllExecutions()); + + for (ISequenceEvent ise : knownEnds) { + if (ise.getNotationView() != null && lostNode != null && lostNode.equals(ise.getNotationView().getElement())) { + correspondingEvent = ise; + break; + } + } + if (correspondingEvent == null) { + EObject eContainer = lostNode.eContainer(); + for (ISequenceEvent ise : knownEnds) { + if (ise.getNotationView() != null && eContainer != null && eContainer.equals(ise.getNotationView().getElement())) { + correspondingEvent = ise; + } + } + } + return correspondingEvent; + } + + private Kind getMessageKind(LostMessageEnd lostSourceEnd) { + Kind kind = Message.Kind.BASIC; + Message message = lostMessages.get(lostSourceEnd); + if (message != null) { + kind = message.getKind(); + } else if (unconnectedLostEnds.contains(lostSourceEnd) && lostSourceEnd.getNotationNode().getElement() instanceof EdgeTarget) { + EdgeTarget dde = (EdgeTarget) lostSourceEnd.getNotationNode().getElement(); + if (!dde.getOutgoingEdges().isEmpty()) { + kind = Message.VIEWPOINT_MESSAGE_KIND.apply(dde.getOutgoingEdges().iterator().next()); + } + } + return kind; + } + + private int getLostMessageEndsGap(Lifeline lifeline, Multimap<Lifeline, LostMessageEnd> lostEnds, Range zone, Map<LostMessageEnd, Integer> lostEndsDelta, boolean revertDelta) { + int gap = 0; + if (lostEnds.containsKey(lifeline)) { + int maxLostEndWidth = 0; + int maxDelta = 0; + int frameBorder = 0; + for (LostMessageEnd lme : lostEnds.get(lifeline)) { + // Message in zone ? + if (zone == null || lostMessages.containsKey(lme) && zone.includesAtLeastOneBound(lostMessages.get(lme).getVerticalRange())) { + int delta = lostEndsDelta.get(lme); + maxDelta = Math.max(maxDelta, revertDelta ? -delta : delta); + if (maxDelta > 0) { + frameBorder = LayoutConstants.BORDER_FRAME_MARGIN; + maxLostEndWidth = Math.max(maxLostEndWidth, lme.getProperLogicalBounds().width); + } + } + } + gap = maxDelta + maxLostEndWidth + frameBorder; + } + return gap; + } + + /** + * Dispose the helper context. + */ + public void dispose() { + operands.clear(); + operands2lostEnds.clear(); + diagramLostEnds.clear(); + lostMessages.clear(); + lostSources.clear(); + lostTargets.clear(); + unconnectedLostEnds.clear(); + } + + /** + * Compute the lost message ends futur locations. + * + * @param irMoves + * computed instance roles new locations. + * @param lostEndsDelta + * computed delats between lost message ends and lifelines. + * @return lost message ends locations. + */ + public Map<LostMessageEnd, Rectangle> computeLostMessageEndsHorizontalBounds(Map<InstanceRole, Rectangle> irMoves, Map<LostMessageEnd, Integer> lostEndsDelta) { + Map<LostMessageEnd, Rectangle> lostMessageEndMoves = Maps.newHashMap(); + + for (Lifeline lifeline : sequenceDiagram.getAllLifelines()) { + int newLifelineX = getNewLifelineX(lifeline, irMoves); + + // Compute new ends x. + for (LostMessageEnd lostSource : Iterables.concat(lostSources.get(lifeline), lostTargets.get(lifeline))) { + int deltaWithLifeline = lostEndsDelta.get(lostSource); + Rectangle bounds = lostSource.getProperLogicalBounds().getCopy(); + + bounds.x = newLifelineX + deltaWithLifeline; + lostMessageEndMoves.put(lostSource, bounds); + } + } + return lostMessageEndMoves; + } + + private int getNewLifelineX(Lifeline lifeline, Map<InstanceRole, Rectangle> irMoves) { + int lifelineOldX = lifeline.getProperLogicalBounds().x; + InstanceRole instanceRole = lifeline.getInstanceRole(); + + int deltaX = 0; + if (instanceRole != null && irMoves.containsKey(instanceRole)) { + Rectangle irBounds = instanceRole.getProperLogicalBounds().getCopy(); + Rectangle irFutureBounds = irMoves.get(instanceRole); + + deltaX = irFutureBounds.x - irBounds.x; + } + return lifelineOldX + deltaX; + } + +} diff --git a/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/horizontal/SequenceHorizontalLayout.java b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/horizontal/SequenceHorizontalLayout.java new file mode 100644 index 0000000000..b9d75d0b76 --- /dev/null +++ b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/horizontal/SequenceHorizontalLayout.java @@ -0,0 +1,656 @@ +/******************************************************************************* + * Copyright (c) 2010, 2012 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.sequence.business.internal.layout.horizontal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.draw2d.geometry.Insets; +import org.eclipse.draw2d.geometry.Rectangle; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.gmf.runtime.notation.Bendpoints; +import org.eclipse.gmf.runtime.notation.Bounds; +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.RelativeBendpoints; +import org.eclipse.gmf.runtime.notation.datatype.RelativeBendpoint; + +import com.google.common.base.Function; +import com.google.common.base.Predicates; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import com.google.common.collect.Ordering; + +import org.eclipse.sirius.common.tools.api.util.Option; +import org.eclipse.sirius.diagram.sequence.SequenceDDiagram; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.AbstractFrame; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.AbstractNodeEvent; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.CombinedFragment; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceElement; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceElementAccessor; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.InstanceRole; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Lifeline; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.LostMessageEnd; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Message; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Operand; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.SequenceDiagram; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.AbstractSequenceOrderingLayout; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.LayoutConstants; +import org.eclipse.sirius.diagram.sequence.business.internal.query.ISequenceElementQuery; +import org.eclipse.sirius.diagram.sequence.util.Range; + +/** + * Computes the appropriate graphical locations of sequence events and lifelines + * on a sequence diagram to reflect the semantic order. + * + * @author pcdavid, mporhel + */ +public class SequenceHorizontalLayout extends AbstractSequenceOrderingLayout<ISequenceElement, Rectangle, InstanceRole> { + + private static final Function<Rectangle, Integer> RECT_TO_X = new Function<Rectangle, Integer>() { + public Integer apply(Rectangle input) { + return input.x; + } + }; + + private final Insets padding = new Insets(LayoutConstants.LIFELINES_START_Y, LayoutConstants.LIFELINES_START_X, 0, LayoutConstants.LIFELINES_MIN_X_GAP - LayoutConstants.LIFELINES_START_X); + + private final Collection<AbstractFrame> frames = Lists.newArrayList(); + + private final Multimap<AbstractFrame, Lifeline> coverage = HashMultimap.create(); + + private final Multimap<Lifeline, AbstractFrame> invCoverage = HashMultimap.create(); + + private final Map<AbstractFrame, Range> ranges = Maps.newHashMap(); + + private final Map<AbstractFrame, Integer> frameChildrenDepths = Maps.newHashMap(); + + private final Map<Lifeline, Integer> lifelineChildrenDepths = Maps.newHashMap(); + + private LostMessageEndHorizontalLayoutHelper lostMessageEndHorizontalLayoutHelper; + + private final Map<Message, Integer> reflexiveMessagesToLayout = Maps.newHashMap(); + + /** + * Constructor. + * + * @param diagram + * the sequence diagram for which to compute the horizontal + * locations. + */ + public SequenceHorizontalLayout(SequenceDiagram diagram) { + super(diagram); + } + + /** + * {@inheritDoc} + */ + @Override + protected void init(boolean pack) { + if (pack) { + this.padding.left = LayoutConstants.LIFELINES_START_X; + this.padding.right = LayoutConstants.LIFELINES_MIN_PACKED_X_GAP - LayoutConstants.LIFELINES_START_X; + } + + populateSortedIntanceRoles(); + + populateLifelineDepth(); + populateFrames(); + populateLostMessageEnds(); + + populateReflexiveMessages(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Rectangle getOldLayoutData(ISequenceElement ise) { + return ise.getProperLogicalBounds(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Map<ISequenceElement, Rectangle> computeLayout(boolean pack) { + LinkedHashMap<ISequenceElement, Rectangle> allMoves = Maps.newLinkedHashMap(); + + Map<LostMessageEnd, Integer> lostEndsDelta = lostMessageEndHorizontalLayoutHelper.computeLostMessageEndDeltaWithLifeline(pack); + Map<Message, Rectangle> reflexiveMessagesMoves = computeReflexiveMessagesHorizontalBounds(); + + Map<InstanceRole, Rectangle> irMoves = computeInstanceRoleHorizontalLocations(pack, lostEndsDelta); + Map<LostMessageEnd, Rectangle> lostMessageEndMoves = lostMessageEndHorizontalLayoutHelper.computeLostMessageEndsHorizontalBounds(irMoves, lostEndsDelta); + Map<AbstractFrame, Rectangle> frameMoves = computeFrameHorizontalBounds(irMoves, lostEndsDelta); + + allMoves.putAll(irMoves); + allMoves.putAll(frameMoves); + allMoves.putAll(lostMessageEndMoves); + allMoves.putAll(reflexiveMessagesMoves); + return allMoves; + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean applyComputedLayout(Map<? extends ISequenceElement, Rectangle> bounds, boolean pack) { + boolean applied = false; + + applied = layoutInstanceRoles(bounds, pack) || applied; + applied = layoutFrames(bounds, pack) || applied; + applied = layoutLostMessageEnds(bounds, pack) || applied; + applied = layoutReflexiveMessages(bounds, pack) || applied; + + // if (pack) { + // correctGmfLocations(); + // } + + return applied; + } + + /** + * {@inheritDoc} + */ + @Override + protected void dispose() { + frameChildrenDepths.clear(); + lifelineChildrenDepths.clear(); + coverage.clear(); + invCoverage.clear(); + ranges.clear(); + frames.clear(); + lostMessageEndHorizontalLayoutHelper.dispose(); + reflexiveMessagesToLayout.clear(); + + super.dispose(); + } + + @SuppressWarnings("unused") + private void correctGmfLocations() { + layoutAbstractNodes(); + layoutLifeline(); + } + + private void layoutLifeline() { + for (Lifeline lifeline : sequenceDiagram.getAllLifelines()) { + Rectangle properLogicalBounds = lifeline.getProperLogicalBounds(); + Rectangle parentLogicalBounds = lifeline.getInstanceRole().getProperLogicalBounds(); + LayoutConstraint layoutConstraint = lifeline.getNotationNode().getLayoutConstraint(); + if (layoutConstraint instanceof Location) { + Location loc = (Location) layoutConstraint; + loc.setX(properLogicalBounds.x - parentLogicalBounds.x); + } + } + } + + private void layoutAbstractNodes() { + for (AbstractNodeEvent exec : sequenceDiagram.getAllAbstractNodeEvents()) { + Rectangle properLogicalBounds = exec.getProperLogicalBounds(); + Rectangle parentLogicalBounds = exec.getHierarchicalParentEvent().getProperLogicalBounds(); + LayoutConstraint layoutConstraint = exec.getNotationNode().getLayoutConstraint(); + if (layoutConstraint instanceof Location) { + Location loc = (Location) layoutConstraint; + loc.setX(properLogicalBounds.x - parentLogicalBounds.x); + } + } + } + + private void populateSortedIntanceRoles() { + // Graphicall order + graphicalOrdering.addAll(sequenceDiagram.getSortedInstanceRole()); + + // If a semantic order is specified, sort the instance roles. + semanticOrdering.addAll(graphicalOrdering); + SequenceDDiagram sequenceDDiagram = sequenceDiagram.getSequenceDDiagram(); + if (sequenceDDiagram != null && !sequenceDDiagram.getInstanceRoleSemanticOrdering().getSemanticInstanceRoles().isEmpty()) { + final List<EObject> semanticOrder = sequenceDDiagram.getInstanceRoleSemanticOrdering().getSemanticInstanceRoles(); + Function<InstanceRole, Integer> semanticIndex = new Function<InstanceRole, Integer>() { + public Integer apply(InstanceRole ir) { + Option<EObject> semIr = ir.getSemanticTargetElement(); + return semIr.some() ? semanticOrder.indexOf(semIr.get()) : -1; + } + }; + Collections.sort(semanticOrdering, Ordering.natural().onResultOf(semanticIndex)); + } + + for (InstanceRole role : semanticOrdering) { + ISequenceElementQuery query = new ISequenceElementQuery(role); + if (query.hasAbsoluteBoundsFlag()) { + Rectangle flaggedAbsoluteBounds = query.getFlaggedAbsoluteBounds(); + oldFlaggedLayoutData.put(role, flaggedAbsoluteBounds); + flaggedEnds.add(role); + } + } + + Collections.sort(flaggedEnds, Ordering.natural().onResultOf(getOldFlaggedPosition())); + } + + private void populateLifelineDepth() { + List<Lifeline> allLifelines = sequenceDiagram.getAllLifelines(); + for (Lifeline lifeline : allLifelines) { + int depth = getOrComputeMaxChildrenDepth(lifeline.getNotationNode(), lifeline.getVerticalRange()); + lifelineChildrenDepths.put(lifeline, depth); + } + } + + private void populateLostMessageEnds() { + lostMessageEndHorizontalLayoutHelper = new LostMessageEndHorizontalLayoutHelper(sequenceDiagram); + lostMessageEndHorizontalLayoutHelper.populateLostMessageEnds(); + } + + private void populateFrames() { + Collection<AbstractFrame> allFrames = Lists.newArrayList(); + allFrames.addAll(sequenceDiagram.getAllInteractionUses()); + allFrames.addAll(sequenceDiagram.getAllCombinedFragments()); + + for (AbstractFrame frame : allFrames) { + Collection<Lifeline> coveredLifelines = frame.computeCoveredLifelines(); + if (!coveredLifelines.isEmpty()) { + frames.add(frame); + coverage.putAll(frame, coveredLifelines); + ranges.put(frame, frame.getVerticalRange()); + } + } + + for (AbstractFrame frame : frames) { + getOrComputeMaxChildrenDepth(frame, Collections.singletonList(frame)); + } + + Multimaps.invertFrom(coverage, invCoverage); + } + + private void populateReflexiveMessages() { + for (Message msg : sequenceDiagram.getAllMessages()) { + if (msg.isReflective()) { + int width = msg.getReflexiveMessageWidth(); + if (width != 0) { + reflexiveMessagesToLayout.put(msg, width); + } + } + } + + } + + private int getOrComputeMaxChildrenDepth(Node node, Range range) { + int maxChildrenDepth = 0; + for (Node child : Iterables.filter(Iterables.filter(node.getChildren(), Node.class), AbstractNodeEvent.notationPredicate())) { + AbstractNodeEvent childExec = ISequenceElementAccessor.getAbstractNodeEvent(child).get(); + if (range == null || range.intersects(childExec.getVerticalRange())) { + int childDepth = 1 + getOrComputeMaxChildrenDepth(child, range); + maxChildrenDepth = Math.max(childDepth, maxChildrenDepth); + } + } + return maxChildrenDepth; + } + + private int getOrComputeMaxChildrenDepth(AbstractFrame frame, Collection<AbstractFrame> framesToIgnore) { + int children = 0; + if (!frameChildrenDepths.containsKey(frame)) { + Collection<Lifeline> frameCoverage = coverage.get(frame); + Range frameRange = ranges.get(frame); + for (AbstractFrame potentialChild : Iterables.filter(frames, Predicates.not(Predicates.in(framesToIgnore)))) { + Collection<Lifeline> potentialCoverage = coverage.get(potentialChild); + Range potentialRange = ranges.get(potentialChild); + + if (frame != potentialChild && frameCoverage.containsAll(potentialCoverage) && frameRange.includes(potentialRange)) { + ArrayList<AbstractFrame> newArrayList = Lists.newArrayList(framesToIgnore); + newArrayList.add(potentialChild); + children = Math.max(children, 1 + getOrComputeMaxChildrenDepth(potentialChild, newArrayList)); + } + } + frameChildrenDepths.put(frame, children); + } else { + children = frameChildrenDepths.get(frame); + } + return children; + } + + private Map<AbstractFrame, Rectangle> computeFrameHorizontalBounds(Map<InstanceRole, Rectangle> irMoves, Map<LostMessageEnd, Integer> lostEndsDelta) { + Map<AbstractFrame, Rectangle> frameMoves = Maps.newHashMap(); + + for (AbstractFrame frame : frames) { + Rectangle newBounds = null; + Lifeline leftLifeline = null; + Lifeline rightLifeline = null; + for (Lifeline lifeline : coverage.get(frame)) { + Rectangle lBounds = getInstanceRoleBounds(lifeline.getInstanceRole(), irMoves); + if (newBounds == null) { + newBounds = lBounds.getCopy(); + } else { + newBounds.union(lBounds); + } + + // look for right lifeline + if (lBounds.right() == newBounds.right()) { + rightLifeline = lifeline; + } + + // look for left lifeline + if (lBounds.x == newBounds.x) { + leftLifeline = lifeline; + } + } + + Integer frameDepth = frameChildrenDepths.get(frame); + int frameDepthGap = frameDepth * LayoutConstants.BORDER_FRAME_MARGIN; + + if (rightLifeline != null) { + Range verticalRange = frame.getVerticalRange(); + int irWidth = getInstanceRoleBounds(rightLifeline.getInstanceRole(), irMoves).width; + int lifelineRightGap = getLifelineRightGap(rightLifeline, verticalRange, irWidth, lostEndsDelta); + newBounds.width += Math.max(lifelineRightGap, frameDepthGap); + } + + if (leftLifeline != null) { + Range verticalRange = frame.getVerticalRange(); + int irWidth = getInstanceRoleBounds(leftLifeline.getInstanceRole(), irMoves).width; + int lifelineLeftGap = getLifelineLeftGap(rightLifeline, verticalRange, irWidth, lostEndsDelta); + lifelineLeftGap = Math.max(lifelineLeftGap, frameDepthGap); + + newBounds.x -= lifelineLeftGap; + newBounds.width += lifelineLeftGap; + } + + frameMoves.put(frame, newBounds); + } + return frameMoves; + } + + private Rectangle getInstanceRoleBounds(InstanceRole instanceRole, Map<InstanceRole, Rectangle> irMoves) { + if (irMoves.containsKey(instanceRole)) { + return irMoves.get(instanceRole); + } + return instanceRole.getBounds(); + } + + /** + * Computes the horizontal absolute location of instance roles. + * + * @param pack + * pack the diagram + * @param lostEndsDelta + * @param reflexiveMessagesMoves + * + * @return a map associating each instance role edit part to the new + * absolute horizontal location it should have. + */ + private Map<InstanceRole, Rectangle> computeInstanceRoleHorizontalLocations(boolean pack, Map<LostMessageEnd, Integer> lostEndsDelta) { + final Map<InstanceRole, Rectangle> computedMoves = new HashMap<InstanceRole, Rectangle>(); + + // initial position + int currentX = padding.left; + for (InstanceRole instanceRole : semanticOrdering) { + currentX = computeLocation(currentX, instanceRole, pack, lostEndsDelta, computedMoves); + } + return computedMoves; + } + + /** + * Compute and store the new bounds of the instance roles, the x location + * will be the only modified value. Return the next minimum x. + */ + private int computeLocation(final int currentX, final InstanceRole instanceRole, boolean pack, Map<LostMessageEnd, Integer> lostEndsDelta, final Map<InstanceRole, Rectangle> computedMoves) { + final Rectangle oldBounds = instanceRole.getProperLogicalBounds(); + final Option<Lifeline> lifeline = instanceRole.getLifeline(); + + int newMinX = currentX; + int rightComputedGap = 0; + if (lifeline.some()) { + int maxFrameDepth = getMaxFrameDepth(lifeline.get()); + int foundMessagesGap = getLifelineLeftGap(lifeline.get(), null, oldBounds.width, lostEndsDelta); + int frameGap = maxFrameDepth * LayoutConstants.BORDER_FRAME_MARGIN; + int rightGap = getLifelineRightGap(lifeline.get(), null, oldBounds.width, lostEndsDelta); + + // Make space for frame and found messages. + newMinX += frameGap + foundMessagesGap; + // Update computed gap + rightComputedGap = frameGap + rightGap; + } + + Rectangle newBounds = oldBounds.getCopy(); + if (pack) { + newBounds.x = newMinX; + } else { + // shift the current instancerole to the right ? + // don't reduce previous delta with known/flagged predecessor + int deltaStablePosition = getDeltaStablePosition(newMinX, instanceRole, Maps.transformValues(computedMoves, RECT_TO_X)); + + newBounds.x = Math.max(newMinX, Math.max(newBounds.x, deltaStablePosition)); + } + + // Store computed move + computedMoves.put(instanceRole, newBounds); + + // Return the next minX : right of the current instance role + minimum + // gap + place for frames and found messages + return newBounds.right() + getMinInstanceRoleGap() + rightComputedGap; + } + + private int getMaxReflexiveDepth(Lifeline lifeline, Range zone) { + int maxWidth = 0; + for (Message msg : reflexiveMessagesToLayout.keySet()) { + if (lifeline.equals(msg.getLifeline().get()) && (zone == null || zone.includes(msg.getVerticalRange()))) { + maxWidth = Math.max(maxWidth, reflexiveMessagesToLayout.get(msg)); + } + } + return maxWidth; + } + + private Map<Message, Rectangle> computeReflexiveMessagesHorizontalBounds() { + final Map<Message, Rectangle> computedMoves = new HashMap<Message, Rectangle>(); + for (Message msg : reflexiveMessagesToLayout.keySet()) { + Rectangle properLogicalBounds = msg.getProperLogicalBounds(); + properLogicalBounds.width = reflexiveMessagesToLayout.get(msg); + computedMoves.put(msg, properLogicalBounds); + } + return computedMoves; + } + + private int getMinInstanceRoleGap() { + return padding.right + padding.left; + } + + private int getMaxExecDepth(Lifeline lifeline) { + int depth = 0; + if (lifelineChildrenDepths.containsKey(lifeline)) { + depth = lifelineChildrenDepths.get(lifeline); + } + return depth; + } + + private int getMaxFrameDepth(Lifeline lifeline) { + int depth = 0; + Collection<AbstractFrame> collection = invCoverage.get(lifeline); + if (collection != null && !collection.isEmpty()) { + for (AbstractFrame abstractFrame : collection) { + Integer integer = frameChildrenDepths.get(abstractFrame); + depth = Math.max(integer, depth); + } + } + return depth; + } + + /** + * Compute the right gap for the given lifeline and range. + * + * @param lifeline + * the current lifeline. + * @param zone + * if not null, the restricted vertical range to look for + * execution and lost ends. + * @param irWidth + * the instance role width. + * @param lostEndsDelta + * @return the right gap for the current lifeline. + */ + private int getLifelineRightGap(Lifeline lifeline, Range zone, int irWidth, Map<LostMessageEnd, Integer> lostEndsDelta) { + int rightGap = 0; + int execDepth = zone == null ? getMaxExecDepth(lifeline) : getOrComputeMaxChildrenDepth(lifeline.getNotationNode(), zone); + + // handle Execution and State hierarchy + if (execDepth != 0) { + // the first execution is centered on the lifeline. + int gap = LayoutConstants.UNIT; + // TODO get the execution vsm size. + gap += (execDepth - 1) * 15; + // Remove the mid instance role size to get the gap relative to its + // right and ad the minimum gap between last execution and frame + // border. + gap += -irWidth / 2 + LayoutConstants.UNIT; + rightGap = Math.max(0, gap); + } + + // Make space for reflexive messages + int reflexiveGap = getMaxReflexiveDepth(lifeline, zone) - irWidth / 2; + rightGap = Math.max(rightGap, reflexiveGap); + + // handle lost message ends + rightGap = Math.max(rightGap, lostMessageEndHorizontalLayoutHelper.getRightEndsGap(lifeline, zone, lostEndsDelta) - irWidth / 2); + + return rightGap; + } + + private int getLifelineLeftGap(Lifeline lifeline, Range zone, int irWidth, Map<LostMessageEnd, Integer> lostEndsDelta) { + int leftGap = 0; + + // handle lost message ends + leftGap = Math.max(leftGap, lostMessageEndHorizontalLayoutHelper.getLeftGap(lifeline, zone, lostEndsDelta) - irWidth / 2); + + return leftGap; + } + + private boolean layoutInstanceRoles(Map<? extends ISequenceElement, Rectangle> bounds, boolean pack) { + boolean applied = false; + for (InstanceRole instanceRole : Iterables.filter(bounds.keySet(), InstanceRole.class)) { + final Node node = instanceRole.getNotationNode(); + final Integer computedX = bounds.get(instanceRole).x; + + LayoutConstraint layoutConstraint = node.getLayoutConstraint(); + if (layoutConstraint instanceof Location) { + Location location = (Location) layoutConstraint; + location.setX(computedX); + } + applied = true; + } + return applied; + } + + private boolean layoutFrames(Map<? extends ISequenceElement, Rectangle> bounds, boolean pack) { + boolean applied = false; + for (AbstractFrame frame : Iterables.filter(bounds.keySet(), AbstractFrame.class)) { + Rectangle newBounds = bounds.get(frame); + Node notationNode = frame.getNotationNode(); + LayoutConstraint layoutConstraint = notationNode.getLayoutConstraint(); + if (layoutConstraint instanceof Bounds && newBounds != null) { + Bounds b = (Bounds) layoutConstraint; + b.setWidth(newBounds.width); + b.setX(newBounds.x); + applied = true; + } + + if (frame instanceof CombinedFragment) { + CombinedFragment cf = (CombinedFragment) frame; + for (Operand op : cf.getOperands()) { + Node opNode = op.getNotationNode(); + LayoutConstraint opLC = opNode.getLayoutConstraint(); + if (opLC instanceof Bounds && newBounds != null) { + Bounds opBounds = (Bounds) opLC; + opBounds.setWidth(newBounds.width); + opBounds.setX(0); + applied = true; + } + } + } + } + return applied; + } + + private boolean layoutLostMessageEnds(Map<? extends ISequenceElement, Rectangle> bounds, boolean pack) { + boolean applied = false; + for (LostMessageEnd lostMessageEnd : Iterables.filter(bounds.keySet(), LostMessageEnd.class)) { + final Node node = lostMessageEnd.getNotationNode(); + final Integer computedX = bounds.get(lostMessageEnd).x; + + LayoutConstraint layoutConstraint = node.getLayoutConstraint(); + if (layoutConstraint instanceof Location) { + Location location = (Location) layoutConstraint; + location.setX(computedX); + applied = true; + } + } + return applied; + } + + private boolean layoutReflexiveMessages(Map<? extends ISequenceElement, Rectangle> bounds, boolean pack) { + boolean applied = false; + for (Message msg : Iterables.filter(bounds.keySet(), Message.class)) { + Bendpoints bendpoints = msg.getNotationEdge().getBendpoints(); + if (msg.isReflective() && bendpoints instanceof RelativeBendpoints) { + RelativeBendpoints relativeBendpoints = (RelativeBendpoints) bendpoints; + Iterable<RelativeBendpoint> points = Iterables.filter(relativeBendpoints.getPoints(), RelativeBendpoint.class); + if (Iterables.size(points) == 4) { + RelativeBendpoint p0 = Iterables.get(points, 0); + RelativeBendpoint p1 = Iterables.get(points, 1); + RelativeBendpoint p2 = Iterables.get(points, 2); + RelativeBendpoint p3 = Iterables.get(points, 3); + + int deltaX = bounds.get(msg).width - p1.getSourceX(); + RelativeBendpoint newP1 = new RelativeBendpoint(p1.getSourceX() + deltaX, p1.getSourceY(), p1.getTargetX() + deltaX, p1.getTargetY()); + RelativeBendpoint newP2 = new RelativeBendpoint(p2.getSourceX() + deltaX, p2.getSourceY(), p2.getTargetX() + deltaX, p2.getTargetY()); + + List<RelativeBendpoint> newPoints = Lists.newArrayList(); + newPoints.add(p0); + newPoints.add(newP1); + newPoints.add(newP2); + newPoints.add(p3); + + relativeBendpoints.setPoints(newPoints); + applied = true; + } + } + } + return applied; + } + + @Override + protected Function<InstanceRole, Integer> getOldPosition() { + return new Function<InstanceRole, Integer>() { + public Integer apply(InstanceRole input) { + return input.getProperLogicalBounds().x; + } + }; + } + + @Override + protected Function<InstanceRole, Integer> getOldFlaggedPosition() { + return new Function<InstanceRole, Integer>() { + public Integer apply(InstanceRole input) { + int oldFlaggedPosition = Integer.MIN_VALUE; + Rectangle flaggedData = oldFlaggedLayoutData.get(input); + if (flaggedData != null) { + oldFlaggedPosition = flaggedData.x; + } + return oldFlaggedPosition; + } + }; + } +} diff --git a/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/observation/SequenceObservationLayout.java b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/observation/SequenceObservationLayout.java new file mode 100644 index 0000000000..f390fc1778 --- /dev/null +++ b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/observation/SequenceObservationLayout.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * Copyright (c) 2012 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.sequence.business.internal.layout.observation; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.draw2d.geometry.Point; +import org.eclipse.draw2d.geometry.Rectangle; +import org.eclipse.gmf.runtime.notation.Bounds; +import org.eclipse.gmf.runtime.notation.Node; + +import com.google.common.collect.Maps; + +import org.eclipse.sirius.common.tools.api.util.Option; +import org.eclipse.sirius.DNode; +import org.eclipse.sirius.diagram.business.internal.query.DNodeQuery; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.AbstractFrame; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Execution; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceEvent; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Message; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ObservationPoint; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Operand; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.SequenceDiagram; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.State; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.AbstractSequenceLayout; +import org.eclipse.sirius.diagram.sequence.business.internal.ordering.EventEndHelper; +import org.eclipse.sirius.diagram.sequence.ordering.CompoundEventEnd; +import org.eclipse.sirius.diagram.sequence.ordering.EventEnd; +import org.eclipse.sirius.diagram.sequence.ordering.SingleEventEnd; + +/** + * Computes the appropriate graphical locations of observation points on a + * sequence diagram. + * + * @author mporhel + */ +public class SequenceObservationLayout extends AbstractSequenceLayout<ObservationPoint, Point> { + + private Map<EventEnd, ObservationPoint> endToObservationPoint; + + /** + * Constructor. + * + * @param sequenceDiagram + * the sequence diagram for which to compute the observation + * point locations. + */ + public SequenceObservationLayout(SequenceDiagram sequenceDiagram) { + super(sequenceDiagram); + this.endToObservationPoint = Maps.newHashMap(); + } + + @Override + protected void init(boolean pack) { + for (ObservationPoint point : sequenceDiagram.getAllObservationPoints()) { + Option<EventEnd> observedEventEnd = point.getObservedEventEnd(); + + if (observedEventEnd.some()) { + endToObservationPoint.put(observedEventEnd.get(), point); + } + } + + } + + @Override + protected Map<? extends ObservationPoint, Point> computeLayout(boolean pack) { + HashMap<ObservationPoint, Point> computedLayout = Maps.newHashMap(); + + for (ISequenceEvent ise : sequenceDiagram.getAllDelimitedSequenceEvents()) { + Rectangle bounds = ise.getProperLogicalBounds(); + + List<EventEnd> foundEnds = sequenceDiagram.findEnds(ise); + for (EventEnd eventEnd : foundEnds) { + Point refPoint = null; + ObservationPoint observationPoint = endToObservationPoint.get(eventEnd); + if (observationPoint != null) { + if (eventEnd instanceof SingleEventEnd) { + SingleEventEnd see = (SingleEventEnd) eventEnd; + if (ise instanceof Message) { + Message msg = (Message) ise; + if (msg.isReflective()) { + refPoint = see.isStart() ? bounds.getTopLeft().getCopy() : bounds.getBottomRight().getCopy(); + } else { + refPoint = see.isStart() ? bounds.getTopLeft().getCopy() : bounds.getBottomRight().getCopy(); + } + } else if (ise instanceof AbstractFrame || ise instanceof Operand) { + refPoint = see.isStart() ? bounds.getTopLeft().getCopy() : bounds.getBottomLeft().getCopy(); + } else { + refPoint = see.isStart() ? bounds.getTop().getCopy() : bounds.getBottom().getCopy(); + } + + } else if (eventEnd instanceof CompoundEventEnd) { + if (ise instanceof State && ise.isLogicallyInstantaneous()) { + // getcenter ? + refPoint = bounds.getLeft().getCopy(); + } else if (ise instanceof Execution) { + SingleEventEnd see = EventEndHelper.getSingleEventEnd(eventEnd, ise.getSemanticTargetElement().get()); + refPoint = see.isStart() ? bounds.getTop().getCopy() : bounds.getBottom().getCopy(); + } else if (ise instanceof Operand) { + SingleEventEnd see = EventEndHelper.getSingleEventEnd(eventEnd, ise.getSemanticTargetElement().get()); + refPoint = see.isStart() ? bounds.getTopLeft().getCopy() : bounds.getBottomLeft().getCopy(); + } + } + if (refPoint != null) { + computedLayout.put(observationPoint, refPoint); + } + } + } + } + + return computedLayout; + } + + @Override + protected boolean applyComputedLayout(Map<? extends ObservationPoint, Point> finalLocations, boolean pack) { + boolean applied = false; + for (ObservationPoint point : sequenceDiagram.getAllObservationPoints()) { + Point computedCenter = finalLocations.get(point); + if (computedCenter != null) { + Node notationNode = point.getNotationNode(); + Bounds bounds = (Bounds) notationNode.getLayoutConstraint(); + + // Center the node on the computed location. + int midWidth = bounds.getWidth() / 2; + if (bounds.getWidth() == -1) { + midWidth = new DNodeQuery((DNode) point.getNotationNode().getElement()).getDefaultDimension().width / 2; + } + int midHeight = bounds.getHeight() / 2; + if (bounds.getHeight() == -1) { + midHeight = new DNodeQuery((DNode) point.getNotationNode().getElement()).getDefaultDimension().height / 2; + } + + int x = computedCenter.x - midWidth; + int y = computedCenter.y - midHeight; + + bounds.setX(x); + bounds.setY(y); + applied = true; + } + } + return applied; + } + + @Override + protected Point getOldLayoutData(ObservationPoint obsPoint) { + return obsPoint.getObservedLogicalLocation(); + } + + @Override + protected void dispose() { + this.endToObservationPoint.clear(); + + super.dispose(); + } +} diff --git a/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/vertical/SequenceVerticalLayout.java b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/vertical/SequenceVerticalLayout.java new file mode 100644 index 0000000000..9e3b48f756 --- /dev/null +++ b/plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/vertical/SequenceVerticalLayout.java @@ -0,0 +1,1080 @@ +/******************************************************************************* + * Copyright (c) 2010, 2012 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.sequence.business.internal.layout.vertical; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.draw2d.geometry.Rectangle; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.gmf.runtime.notation.Edge; +import org.eclipse.gmf.runtime.notation.LayoutConstraint; +import org.eclipse.gmf.runtime.notation.Location; +import org.eclipse.gmf.runtime.notation.Size; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Iterables; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Ordering; +import com.google.common.collect.Sets; + +import org.eclipse.sirius.common.tools.api.util.Option; +import org.eclipse.sirius.common.tools.api.util.Options; +import org.eclipse.sirius.DDiagramElement; +import org.eclipse.sirius.DNode; +import org.eclipse.sirius.diagram.business.internal.query.DNodeQuery; +import org.eclipse.sirius.diagram.sequence.SequenceDDiagram; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.AbstractNodeEvent; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.CombinedFragment; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.EndOfLife; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceElement; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceElementAccessor; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceEvent; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceNode; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.InstanceRole; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.InteractionUse; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Lifeline; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.LostMessageEnd; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Message; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.Operand; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.SequenceDiagram; +import org.eclipse.sirius.diagram.sequence.business.internal.elements.State; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.AbstractSequenceLayout; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.AbstractSequenceOrderingLayout; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.EventEndToPositionFunction; +import org.eclipse.sirius.diagram.sequence.business.internal.layout.LayoutConstants; +import org.eclipse.sirius.diagram.sequence.business.internal.ordering.EventEndHelper; +import org.eclipse.sirius.diagram.sequence.business.internal.query.ISequenceElementQuery; +import org.eclipse.sirius.diagram.sequence.business.internal.query.SequenceMessageViewQuery; +import org.eclipse.sirius.diagram.sequence.ordering.CompoundEventEnd; +import org.eclipse.sirius.diagram.sequence.ordering.EventEnd; +import org.eclipse.sirius.diagram.sequence.ordering.SingleEventEnd; +import org.eclipse.sirius.diagram.sequence.util.Range; + +/** + * Computes the appropriate graphical locations of sequence events and lifelines + * on a sequence diagram to reflect the semantic order. + * + * @author pcdavid, mporhel + */ +public class SequenceVerticalLayout extends AbstractSequenceOrderingLayout<ISequenceElement, Range, EventEnd> { + + /** + * A map to link an {@link EventEnd} to the attached {@link ISequenceEvent}. + */ + protected final Map<EventEnd, Message> creators; + + /** + * A map to link an {@link EventEnd} to the attached {@link ISequenceEvent}. + */ + protected final Map<EventEnd, Message> destructors; + + /** + * A map to link an {@link EventEnd} to an attached {@link LostMessageEnd}. + */ + protected final Map<EventEnd, LostMessageEnd> losts; + + /** + * Unconnected lostMessageEnds. + */ + protected final List<LostMessageEnd> unconnectedLostEnds; + + /** + * Semantic flagged event ends at creation. + */ + protected final List<EventEnd> toolCreatedEnds = Lists.newArrayList(); + + /** + * A map to link an {@link EventEnd} to the attached {@link ISequenceEvent}. + */ + protected final Multimap<EventEnd, ISequenceEvent> endToISequencEvents; + + /** + * A map to link an {@link ISequenceEvent} to its starting and ending + * {@link EventEnd}. + */ + protected final Multimap<ISequenceEvent, EventEnd> iSequenceEventsToEventEnds; + + /** + * A function to compute the sequence events corresponding to an event end. + */ + protected final Function<EventEnd, Collection<ISequenceEvent>> eventEndToSequenceEvents = new Function<EventEnd, Collection<ISequenceEvent>>() { + public Collection<ISequenceEvent> apply(EventEnd from) { + return endToISequencEvents.get(from); + } + }; + + /** + * The global time range of the diagram. Can be udpated during layout + * computation. + */ + protected Range timeRange; + + /** + * A function to get the instance role height of a lifeline. + */ + private final Function<Lifeline, Integer> instanceRoleHeight = new Function<Lifeline, Integer>() { + public Integer apply(Lifeline from) { + InstanceRole irep = from.getInstanceRole(); + if (irep != null) { + return ((Size) irep.getNotationNode().getLayoutConstraint()).getHeight(); + } + return 0; + } + }; + + /** + * An ordering to sort {@link Lifeline} regarding the height of their + * {@link InstanceRole}. + */ + private final Ordering<Lifeline> heightOrdering = Ordering.natural().onResultOf(instanceRoleHeight); + + private final Function<ISequenceEvent, Option<Range>> oldRangeFunction = new Function<ISequenceEvent, Option<Range>>() { + public Option<Range> apply(ISequenceEvent from) { + Range range = oldLayoutData.get(from); + if (range == null) { + range = Range.emptyRange(); + } + return Options.newSome(range); + } + }; + + private final Function<ISequenceEvent, Option<Range>> oldFlaggedRange = new Function<ISequenceEvent, Option<Range>>() { + public Option<Range> apply(ISequenceEvent from) { + Rectangle rect = oldFlaggedLayoutData.get(from); + Range result = null; + if (rect != null) { + result = Range.verticalRange(rect); + } + return Options.newSome(result); + } + }; + + private final Function<EventEnd, Integer> eventEndOldPosition = new EventEndToPositionFunction(eventEndToSequenceEvents, oldRangeFunction) { + + @Override + protected Integer getOldPositionFromRange(SingleEventEnd see, ISequenceEvent ise) { + Integer oldPos = super.getOldPositionFromRange(see, ise); + + if (ise instanceof Message && !ise.isLogicallyInstantaneous() && see != null) { + // Real position (diagram initialization, message creation) + Message smep = (Message) ise; + Edge notationView = smep.getNotationEdge(); + SequenceMessageViewQuery query = new SequenceMessageViewQuery(notationView); + oldPos = see.isStart() ? query.getFirstPointVerticalPosition(true) : query.getLastPointVerticalPosition(true); + } + + return oldPos; + } + + }; + + private final Function<EventEnd, Integer> eventEndOldFlaggedPosition = new EventEndToPositionFunction(eventEndToSequenceEvents, oldFlaggedRange); + + /** + * Constructor. + * + * @param sequenceDiagram + * the sequence diagram for which to compute the messages + * locations. + */ + public SequenceVerticalLayout(SequenceDiagram sequenceDiagram) { + super(sequenceDiagram); + + this.iSequenceEventsToEventEnds = LinkedHashMultimap.create(); + this.endToISequencEvents = HashMultimap.create(); + + this.creators = Maps.newHashMap(); + this.destructors = Maps.newHashMap(); + this.losts = Maps.newHashMap(); + this.unconnectedLostEnds = Lists.newArrayList(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void init(boolean pack) { + initSortedEventEnds(pack); + initLifelinesOldLayoutData(); + initTimeBounds(pack); + registerEventEnds(); + + lookForUnconnectedLostEnd(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Range getOldLayoutData(ISequenceElement ise) { + Range verticalRange = Range.emptyRange(); + + if (ise instanceof ISequenceEvent) { + verticalRange = ((ISequenceEvent) ise).getVerticalRange(); + + if (ise instanceof Message) { + Message msg = (Message) ise; + ISequenceElementQuery query = null; + ISequenceNode sourceElement = msg.getSourceElement(); + ISequenceNode targetElement = msg.getTargetElement(); + if (sourceElement instanceof LostMessageEnd && AbstractSequenceLayout.createdFromTool((LostMessageEnd) sourceElement)) { + query = new ISequenceElementQuery(sourceElement); + } else if (targetElement instanceof LostMessageEnd && AbstractSequenceLayout.createdFromTool((LostMessageEnd) targetElement)) { + query = new ISequenceElementQuery(targetElement); + } + + if (query != null && query.hasAbsoluteBoundsFlag()) { + Rectangle flag = query.getFlaggedAbsoluteBounds(); + verticalRange = new Range(flag.y, flag.y); + } + } + } + + return verticalRange; + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean applyComputedLayout(Map<? extends ISequenceElement, Range> finalRanges, boolean pack) { + boolean applied = false; + Iterable<ISequenceEvent> keySet = Iterables.filter(finalRanges.keySet(), ISequenceEvent.class); + + // Begin with lifelines and executions (anchor positions move) + for (ISequenceEvent ise : Iterables.filter(keySet, Predicates.not(Predicates.instanceOf(Message.class)))) { + final Range newRange = finalRanges.get(ise); + ise.setVerticalRange(newRange); + applied = true; + } + + // Then apply computed vertical range on messages + for (Message smep : Iterables.filter(keySet, Message.class)) { + final Range newRange = finalRanges.get(smep); + smep.setVerticalRange(newRange); + applied = true; + } + + applied = layoutUnconnectedLostMessageEnd() || applied; + + return applied; + } + + /** + * {@inheritDoc} + */ + @Override + protected Map<? extends ISequenceElement, Range> computeLayout(boolean pack) { + LinkedHashMap<ISequenceEvent, Range> sequenceEventRanges = new LinkedHashMap<ISequenceEvent, Range>(); + + // Compute the position of each event end. + Map<EventEnd, Integer> endLocations = computeEndBounds(pack); + + // Compute ISequenceEvent vertical ranges from event end locations. + Map<ISequenceEvent, Range> basicRanges = computeBasicRanges(endLocations); + + // Compute punctual States vertical range + Map<ISequenceEvent, Range> punctualEventRanges = computePunctualEventsGraphicalRanges(endLocations, pack); + + // Update lifeline size. + Map<ISequenceEvent, Range> lifelinesRanges = computeLifelineRanges(endLocations); + + sequenceEventRanges.putAll(lifelinesRanges); + sequenceEventRanges.putAll(basicRanges); + sequenceEventRanges.putAll(punctualEventRanges); + + return sequenceEventRanges; + } + + /** + * {@inheritDoc} + */ + @Override + protected void dispose() { + creators.clear(); + destructors.clear(); + losts.clear(); + unconnectedLostEnds.clear(); + toolCreatedEnds.clear(); + + endToISequencEvents.clear(); + iSequenceEventsToEventEnds.clear(); + + super.dispose(); + } + + private Map<ISequenceEvent, Range> computePunctualEventsGraphicalRanges(Map<EventEnd, Integer> endLocations, boolean pack) { + final Map<ISequenceEvent, Range> sequenceEventsToRange = new LinkedHashMap<ISequenceEvent, Range>(); + if (pack) { + for (EventEnd cee : Iterables.filter(semanticOrdering, EventEndHelper.PUNCTUAL_COMPOUND_EVENT_END)) { + if (endLocations.containsKey(cee) && endToISequencEvents.containsKey(cee)) { + int loc = endLocations.get(cee); + Collection<ISequenceEvent> ises = endToISequencEvents.get(cee); + + if (Iterables.any(ises, Predicates.instanceOf(State.class)) && ises.size() == 1) { + State ise = (State) ises.iterator().next(); + int midSize = getAbstractNodeEventVerticalSize(cee, ise, ises, pack) / 2; + sequenceEventsToRange.put(ise, new Range(loc - midSize, loc + midSize)); + } + } + } + } + return sequenceEventsToRange; + } + + /** + * Computes the absolute vertical (Y) location for all the messages in the + * sequence diagram. + * + * @return a map associating each message edit part to the new absolute + * vertical location it should have. + */ + private Map<EventEnd, Integer> computeEndBounds(boolean pack) { + final Map<EventEnd, Integer> result = Maps.newLinkedHashMap(); + + if (semanticOrdering == null || semanticOrdering.isEmpty()) { + return result; + } + + // current y location + int currentY = this.timeRange.getLowerBound(); + + EventEnd endBefore = null; + for (EventEnd end : semanticOrdering) { + currentY = computeLocation(currentY, end, endBefore, pack, result); + result.put(end, currentY); + endBefore = end; + } + return result; + } + + private int getGapFromCommonSequenceEvent(EventEnd end, Collection<ISequenceEvent> commonIses, boolean pack, int genericGap) { + int beforeGap = genericGap; + + if (commonIses.isEmpty()) { + return beforeGap; + } + + ISequenceEvent commonIse = commonIses.iterator().next(); + if (commonIse instanceof Message && ((Message) commonIse).isReflective()) { + beforeGap = LayoutConstants.MESSAGE_TO_SELF_BENDPOINT_VERTICAL_GAP; + } else if (commonIse instanceof AbstractNodeEvent) { + beforeGap = Math.max(genericGap, getAbstractNodeEventVerticalSize(end, (AbstractNodeEvent) commonIse, commonIses, pack)); + } else if (commonIse instanceof InteractionUse) { + beforeGap = LayoutConstants.DEFAULT_INTERACTION_USE_HEIGHT; + } else if (commonIse instanceof Operand) { + beforeGap = LayoutConstants.DEFAULT_OPERAND_HEIGHT; + } + return beforeGap; + } + + private int getAbstractNodeEventVerticalSize(EventEnd end, AbstractNodeEvent ise, Collection<ISequenceEvent> commonIses, boolean pack) { + int vSize = 0; + if (pack) { + DNode execution = (DNode) ise.getNotationView().getElement(); + int specifiedVSize = getSpecifiedVSize(execution); + if (specifiedVSize != 0) { + vSize = specifiedVSize; + } + } else if (isFlagguedByRefreshExtension(end, commonIses)) { + Rectangle rect = oldFlaggedLayoutData.get(ise); + vSize = rect != null ? rect.height : LayoutConstants.DEFAULT_EXECUTION_HEIGHT; + } else { + Range range = oldLayoutData.get(ise); + vSize = range != null ? range.width() : LayoutConstants.DEFAULT_EXECUTION_HEIGHT; + } + + return vSize; + } + + private Map<ISequenceEvent, Range> computeBasicRanges(Map<EventEnd, Integer> endLocations) { + final Map<ISequenceEvent, Range> sequenceEventsToRange = new LinkedHashMap<ISequenceEvent, Range>(); + Predicate<ISequenceEvent> notMoved = Predicates.not(Predicates.in(sequenceEventsToRange.keySet())); + + // CombinedFragments + for (EventEnd sortedEnd : semanticOrdering) { + Predicate<ISequenceEvent> frames = Predicates.and(notMoved, Predicates.or(Predicates.instanceOf(CombinedFragment.class), Predicates.instanceOf(InteractionUse.class))); + for (ISequenceEvent ise : Iterables.filter(endToISequencEvents.get(sortedEnd), frames)) { + computeFinalRange(endLocations, sequenceEventsToRange, ise); + } + } + + // Operands + for (EventEnd sortedEnd : semanticOrdering) { + Predicate<ISequenceEvent> operands = Predicates.and(notMoved, Predicates.instanceOf(Operand.class)); + for (ISequenceEvent ise : Iterables.filter(endToISequencEvents.get(sortedEnd), operands)) { + computeFinalRange(endLocations, sequenceEventsToRange, ise); + } + } + + // Other sequence events + for (EventEnd sortedEnd : semanticOrdering) { + for (ISequenceEvent ise : Iterables.filter(endToISequencEvents.get(sortedEnd), notMoved)) { + computeFinalRange(endLocations, sequenceEventsToRange, ise); + } + } + return sequenceEventsToRange; + } + + private void computeFinalRange(Map<EventEnd, Integer> endLocations, final Map<ISequenceEvent, Range> sequenceEventsToRange, ISequenceEvent ise) { + Collection<EventEnd> ends = iSequenceEventsToEventEnds.get(ise); + if (ends.size() == 2) { + Iterator<EventEnd> it = ends.iterator(); + EventEnd start = it.next(); + EventEnd finish = it.next(); + + Range newRange = getNewRange(ise, start, finish, endLocations); + sequenceEventsToRange.put(ise, newRange); + } else if (ends.size() == 1 && ise.isLogicallyInstantaneous() && (ise instanceof Message || EventEndHelper.PUNCTUAL_COMPOUND_EVENT_END.apply(ends.iterator().next()))) { + Iterator<EventEnd> it = ends.iterator(); + EventEnd middle = it.next(); + + Range newRange = getNewRange(ise, middle, middle, endLocations); + sequenceEventsToRange.put(ise, newRange); + } + } + + private Range getNewRange(final ISequenceEvent event, final EventEnd start, final EventEnd end, final Map<EventEnd, Integer> endLocations) { + Range oldRange = oldLayoutData.containsKey(event) ? oldLayoutData.get(event) : Range.emptyRange(); + int lowerBound = endLocations.containsKey(start) ? endLocations.get(start) : oldRange.getLowerBound(); + int upperBound = endLocations.containsKey(end) ? endLocations.get(end) : oldRange.getUpperBound(); + + if (event.isLogicallyInstantaneous() && start == end) { + lowerBound = lowerBound - oldRange.width() / 2; + upperBound = lowerBound + oldRange.width(); + } + + updateTimerange(upperBound); + return new Range(lowerBound, upperBound); + } + + private Map<ISequenceEvent, Range> computeLifelineRanges(Map<EventEnd, Integer> endLocations) { + final Map<ISequenceEvent, Range> sequenceEventsToRange = new LinkedHashMap<ISequenceEvent, Range>(); + int endOfLife = timeRange.getUpperBound() + LayoutConstants.TIME_STOP_OFFSET; + + layoutLifelinesWithoutCreation(sequenceEventsToRange); + layoutCreatedLifelines(endLocations, sequenceEventsToRange); + layoutDestructedLifelines(endLocations, sequenceEventsToRange); + layoutNonDestructedLifelines(sequenceEventsToRange, Math.max(endOfLife, LayoutConstants.LIFELINES_MIN_Y)); + + return sequenceEventsToRange; + } + + private void layoutLifelinesWithoutCreation(final Map<ISequenceEvent, Range> sequenceEventsToRange) { + for (ISequenceEvent event : getLifeLinesWithoutCreation()) { + Range oldRange = oldLayoutData.get(event); + Option<Lifeline> parentLifeline = event.getLifeline(); + if (parentLifeline.some()) { + InstanceRole instanceRole = parentLifeline.get().getInstanceRole(); + if (instanceRole != null) { + int newLBound = getLifelineMinLowerBound(instanceRole); + if (newLBound != oldRange.getLowerBound()) { + sequenceEventsToRange.put(event, new Range(newLBound, oldRange.getUpperBound())); + } + } + } + } + } + + private void layoutCreatedLifelines(Map<EventEnd, Integer> endLocations, final Map<ISequenceEvent, Range> sequenceEventsToRange) { + for (Message smep : creators.values()) { + Collection<EventEnd> ends = iSequenceEventsToEventEnds.get(smep); + if (!ends.isEmpty()) { + Iterator<EventEnd> it = ends.iterator(); + EventEnd first = it.next(); + if (endLocations.containsKey(first)) { + int endMove = endLocations.get(first); + int vGap = getTargetFigureMidHeight(smep); + Lifeline lep = smep.getTargetElement().getLifeline().get(); + Range oldRange = sequenceEventsToRange.containsKey(lep) ? sequenceEventsToRange.get(lep) : oldLayoutData.get(lep); + sequenceEventsToRange.put(lep, new Range(endMove + vGap, endMove + vGap + oldRange.width())); + } + } + } + } + + private void layoutDestructedLifelines(Map<EventEnd, Integer> endLocations, final Map<ISequenceEvent, Range> sequenceEventsToRange) { + for (Message smep : destructors.values()) { + Collection<EventEnd> ends = iSequenceEventsToEventEnds.get(smep); + if (!ends.isEmpty()) { + Iterator<EventEnd> it = ends.iterator(); + EventEnd first = it.next(); + int endMove = endLocations.get(first); + int vGap = getTargetFigureMidHeight(smep); + int newY = endMove - vGap; + Lifeline lep = ((EndOfLife) smep.getTargetElement()).getLifeline().get(); + Range oldRange = sequenceEventsToRange.containsKey(lep) ? sequenceEventsToRange.get(lep) : oldLayoutData.get(lep); + sequenceEventsToRange.put(lep, new Range(oldRange.getLowerBound(), newY)); + } + } + } + + private void layoutNonDestructedLifelines(final Map<ISequenceEvent, Range> sequenceEventsToRange, int endOfLife) { + // update lifeline ranges + for (ISequenceEvent event : getLifeLinesWithoutDestruction()) { + Range currentRange = sequenceEventsToRange.containsKey(event) ? sequenceEventsToRange.get(event) : oldLayoutData.get(event); + if (currentRange.getUpperBound() != endOfLife) { + sequenceEventsToRange.put(event, new Range(currentRange.getLowerBound(), endOfLife)); + } + } + } + + private int computeLocation(final int currentY, final EventEnd end, final EventEnd endBefore, final boolean pack, Map<EventEnd, Integer> alreadyComputedLocations) { + int location = currentY; + Collection<ISequenceEvent> commonIses = getCommonISequenceEvent(endBefore, end); + + if (shouldMove(commonIses)) { + int newMinY = getMinY(endBefore, end, commonIses, pack, location, alreadyComputedLocations); + if (pack) { + location = newMinY; + } else { + // try to save position + int oldPosition = getOldStablePosition(currentY, end); + + // don't minimize previous range + int rangeStableY = getRangeStablePosition(currentY, end, alreadyComputedLocations); + + // don't reduce previous delta with known/flagged predecessor + int deltaStableY = getDeltaStablePosition(currentY, end, alreadyComputedLocations); + + location = Math.max(newMinY, Math.max(oldPosition, Math.max(deltaStableY, rangeStableY))); + } + } + return location; + } + + private int getOldStablePosition(final int currentY, final EventEnd end) { + int oldPosition = currentY; + + // Should we trust GMF positions ? + if (flaggedEnds.contains(end) || toolCreatedEnds.contains(end)) { + oldPosition = eventEndOldPosition.apply(end); + } + + if (isFlagguedByRefreshExtension(end, endToISequencEvents.get(end))) { + oldPosition = eventEndOldFlaggedPosition.apply(end); + } + + return oldPosition; + } + + private int getRangeStablePosition(final int currentY, final EventEnd end, Map<EventEnd, Integer> alreadyComputedLocations) { + int rangeStabilityPos = currentY; + Collection<ISequenceEvent> ises = endToISequencEvents.get(end); + for (ISequenceEvent ise : ises) { + if (!ise.isLogicallyInstantaneous()) { + SingleEventEnd see = EventEndHelper.getSingleEventEnd(end, ise.getSemanticTargetElement().get()); + if (!see.isStart() && !(ise instanceof Message && !Iterables.isEmpty(Iterables.filter(iSequenceEventsToEventEnds.get(ise), CompoundEventEnd.class)))) { + int startLocation = getStartLocation(ise, alreadyComputedLocations); + Option<Range> oldRange = oldRangeFunction.apply(ise); + + if (isFlagguedByRefreshExtension(end, Collections.singleton(ise))) { + oldRange = oldFlaggedRange.apply(ise); + } + + int width = oldRange.some() ? oldRange.get().width() : 0; + rangeStabilityPos = Math.max(rangeStabilityPos, startLocation + width); + } + } + } + return rangeStabilityPos; + } + + private boolean isFlagguedByRefreshExtension(EventEnd end, Collection<ISequenceEvent> ises) { + if (flaggedEnds.contains(end)) { + for (ISequenceEvent ise : ises) { + Rectangle flaggedAbsoluteBounds = new ISequenceElementQuery(ise).getFlaggedAbsoluteBounds(); + if (flaggedAbsoluteBounds.x == LayoutConstants.EXTERNAL_CHANGE_FLAG.x) { + return true; + } + } + } + return false; + } + + private boolean shouldMove(Collection<ISequenceEvent> commonIses) { + boolean shouldMove = true; + if (!commonIses.isEmpty()) { + ISequenceEvent commonIse = commonIses.iterator().next(); + shouldMove = !commonIse.isLogicallyInstantaneous(); + } + + return shouldMove; + } + + private int getMinY(EventEnd endBefore, EventEnd end, Collection<ISequenceEvent> commonIses, boolean pack, int currentLocation, Map<EventEnd, Integer> alreadyComputedLocations) { + int genericGap = getGenericGap(endBefore, end, pack); + int minGap = genericGap; + + if (!commonIses.isEmpty()) { + int commonIseGap = getGapFromCommonSequenceEvent(end, commonIses, pack, genericGap); + minGap = commonIseGap; + } else { + boolean operands = Iterables.any(eventEndToSequenceEvents.apply(end), Predicates.instanceOf(Operand.class)); + if (operands) { + minGap = getGapBeforeOperandEnd(endBefore, end, currentLocation, genericGap, alreadyComputedLocations); + } + } + + return currentLocation + minGap; + } + + private int getGenericGap(EventEnd endBefore, EventEnd end, boolean pack) { + int beforeGap = 0; + + if (endBefore != null) { + Collection<ISequenceEvent> endBeforeEvents = eventEndToSequenceEvents.apply(endBefore); + beforeGap = pack ? LayoutConstants.MIN_INTER_SEQUENCE_EVENTS_VERTICAL_GAP : LayoutConstants.EXECUTION_CHILDREN_MARGIN; + + // Predecessor : Logically instantaneouse States + Iterable<State> predStates = Iterables.filter(endBeforeEvents, State.class); + if (EventEndHelper.PUNCTUAL_COMPOUND_EVENT_END.apply(endBefore) && endBeforeEvents.size() == 1 && Iterables.size(predStates) == 1) { + State predState = Iterables.getOnlyElement(predStates); + if (predState.isLogicallyInstantaneous()) { + beforeGap += getAbstractNodeEventVerticalSize(endBefore, predState, endBeforeEvents, pack) / 2; + } + } + + if (Iterables.any(endBeforeEvents, Predicates.instanceOf(InteractionUse.class)) && endBefore instanceof SingleEventEnd && ((SingleEventEnd) endBefore).isStart()) { + beforeGap = LayoutConstants.DEFAULT_INTERACTION_USE_HEIGHT / 2; + } + + if (Iterables.any(eventEndToSequenceEvents.apply(end), Predicates.instanceOf(InteractionUse.class)) && end instanceof SingleEventEnd && !((SingleEventEnd) end).isStart()) { + beforeGap = LayoutConstants.DEFAULT_INTERACTION_USE_HEIGHT / 2; + } + + if (creators.keySet().contains(endBefore)) { + if (pack) { + beforeGap += getTargetFigureMidHeight(creators.get(endBefore)) + LayoutConstants.TIME_START_OFFSET - LayoutConstants.MIN_INTER_SEQUENCE_EVENTS_VERTICAL_GAP; + } else { + beforeGap += getTargetFigureMidHeight(creators.get(endBefore)); + } + } else if (losts.containsKey(endBefore)) { + beforeGap += losts.get(endBefore).getBounds().height / 2; + } + } else { + beforeGap = pack ? LayoutConstants.TIME_START_OFFSET : LayoutConstants.TIME_START_MIN_OFFSET; + } + + if (destructors.keySet().contains(end)) { + beforeGap += getTargetFigureMidHeight(destructors.get(end)); + } else if (losts.containsKey(end)) { + beforeGap += losts.get(end).getBounds().height / 2; + } + + // current event : Logically instantaneouse States + Collection<ISequenceEvent> endEvents = eventEndToSequenceEvents.apply(end); + Iterable<State> states = Iterables.filter(endEvents, State.class); + if (EventEndHelper.PUNCTUAL_COMPOUND_EVENT_END.apply(end) && endEvents.size() == 1 && Iterables.size(states) == 1) { + State state = Iterables.getOnlyElement(states); + if (state.isLogicallyInstantaneous()) { + beforeGap += getAbstractNodeEventVerticalSize(endBefore, state, endEvents, pack) / 2; + } + } + return beforeGap; + } + + private int getGapBeforeOperandEnd(EventEnd endBefore, EventEnd end, int currentLocation, int genericGap, Map<EventEnd, Integer> alreadyComputedLocations) { + int beforeGap = genericGap; + Iterable<Operand> operands = Iterables.filter(eventEndToSequenceEvents.apply(end), Operand.class); + if (!Iterables.isEmpty(operands) && endBefore instanceof SingleEventEnd) { + if (Iterables.any(eventEndToSequenceEvents.apply(endBefore), Predicates.instanceOf(CombinedFragment.class)) && ((SingleEventEnd) endBefore).isStart()) { + beforeGap = LayoutConstants.COMBINED_FRAGMENT_TITLE_HEIGHT; + } else { + Operand op = selectEndedOperand(end, operands); + if (op != null) { + int startLoc = getStartLocation(op, alreadyComputedLocations); + int minEndLoc = startLoc + LayoutConstants.DEFAULT_OPERAND_HEIGHT; + beforeGap = Math.max(minEndLoc - currentLocation, genericGap); + } + } + } + return beforeGap; + } + + private Operand selectEndedOperand(EventEnd end, Iterable<Operand> operands) { + Operand op = null; + if (end instanceof CompoundEventEnd) { + for (SingleEventEnd see : ((CompoundEventEnd) end).getEventEnds()) { + if (!see.isStart()) { + EObject semanticEvent = see.getSemanticEvent(); + for (Operand opp : operands) { + EObject eObject = opp.getSemanticTargetElement().get(); + if (semanticEvent != null && semanticEvent.equals(eObject)) { + op = opp; + } + } + } + } + } + return op; + } + + private int getStartLocation(ISequenceEvent ise, Map<EventEnd, Integer> alreadyComputedLocations) { + Collection<EventEnd> ends = iSequenceEventsToEventEnds.get(ise); + for (EventEnd end : ends) { + SingleEventEnd see = EventEndHelper.getSingleEventEnd(end, ise.getSemanticTargetElement().get()); + if (see.isStart() && alreadyComputedLocations.containsKey(end)) { + return alreadyComputedLocations.get(end); + } + } + return 0; + } + + private boolean layoutUnconnectedLostMessageEnd() { + boolean applied = false; + for (LostMessageEnd lme : unconnectedLostEnds) { + if (createdFromTool(lme)) { + ISequenceElementQuery query = new ISequenceElementQuery(lme); + int y = query.getFlaggedAbsoluteBounds().y; + if (y != -1) { + LayoutConstraint layoutConstraint = lme.getNotationNode().getLayoutConstraint(); + if (layoutConstraint instanceof Location) { + Rectangle bounds = lme.getProperLogicalBounds(); + ((Location) layoutConstraint).setY(y - bounds.height / 2); + applied = true; + } + } + } + } + return applied; + } + + private void initSortedEventEnds(boolean pack) { + SequenceDDiagram sequenceDDiagram = (SequenceDDiagram) sequenceDiagram.getNotationDiagram().getElement(); + graphicalOrdering.addAll(sequenceDDiagram.getGraphicalOrdering().getEventEnds()); + semanticOrdering.addAll(sequenceDDiagram.getSemanticOrdering().getEventEnds()); + } + + private void initLifelinesOldLayoutData() { + Collection<Lifeline> lifelines = new ArrayList<Lifeline>(); + lifelines.addAll(sequenceDiagram.getAllLifelines()); + for (ISequenceEvent ise : lifelines) { + oldLayoutData.put(ise, getOldLayoutData(ise)); + } + } + + private void lookForUnconnectedLostEnd() { + Collection<LostMessageEnd> allLostMessageEnds = sequenceDiagram.getAllLostMessageEnds(); + Collection<LostMessageEnd> discoveredLostEnds = Lists.newArrayList(); + for (Message knownMsgs : Iterables.filter(iSequenceEventsToEventEnds.keySet(), Message.class)) { + ISequenceNode sourceElement = knownMsgs.getSourceElement(); + if (sourceElement instanceof LostMessageEnd) { + discoveredLostEnds.add((LostMessageEnd) sourceElement); + } + + ISequenceNode targetElement = knownMsgs.getTargetElement(); + if (targetElement instanceof LostMessageEnd) { + discoveredLostEnds.add((LostMessageEnd) targetElement); + } + } + + Iterables.removeAll(allLostMessageEnds, discoveredLostEnds); + + unconnectedLostEnds.addAll(allLostMessageEnds); + } + + /** + * Determines the range of absolute Y locations in which the messages can be + * laid out. + * + * @param pack + * packing layout if true. + * @return + */ + protected void initTimeBounds(boolean pack) { + int minTimeBounds = getMinTimeBounds(); + int startTime = minTimeBounds; + int endTime = getMaxTimeBounds(pack, minTimeBounds) - LayoutConstants.TIME_STOP_OFFSET; + this.timeRange = new Range(startTime, endTime); + } + + private int getMaxTimeBounds(boolean pack, int minTimeBounds) { + int max = getSpecifiedMaxTimeBounds(minTimeBounds); + + if (!pack) { + Iterable<Lifeline> lifelinesWithoutDestruction = getLifeLinesWithoutDestruction(); + + // Avoid to handle lifelines to move up for max computation. + Predicate<Lifeline> isMaxRangeCandidate = new Predicate<Lifeline>() { + public boolean apply(Lifeline input) { + InstanceRole irep = input.getInstanceRole(); + if (irep != null) { + return irep.getBounds().getLocation().y <= LayoutConstants.LIFELINES_START_Y; + } + return false; + } + }; + + Collection<Lifeline> lifelinesToConsider = Lists.newArrayList(Iterables.filter(lifelinesWithoutDestruction, isMaxRangeCandidate)); + Ordering<ISequenceEvent> maxOrdering = Ordering.natural().onResultOf(Functions.compose(Range.upperBoundFunction(), ISequenceEvent.VERTICAL_RANGE)); + if (!lifelinesToConsider.isEmpty()) { + Lifeline lep = maxOrdering.max(lifelinesToConsider); + max = lep.getVerticalRange().getUpperBound(); + } + } + + return max; + } + + private int getSpecifiedMaxTimeBounds(int minTimeBounds) { + List<Lifeline> allLifelines = sequenceDiagram.getAllLifelines(); + int timeBounds = LayoutConstants.LIFELINES_MIN_Y; + + for (Lifeline lep : allLifelines) { + DDiagramElement dde = (DDiagramElement) lep.getNotationNode().getElement(); + if (dde instanceof DNode && Lifeline.viewpointElementPredicate().apply(dde)) { + DNode node = (DNode) dde; + int specifiedVSize = getSpecifiedVSize(node); + int endOfLifeVsize = getSpecifiedEndOfLifeVSize(node); + timeBounds = Math.max(LayoutConstants.LIFELINES_MIN_Y, minTimeBounds + specifiedVSize - endOfLifeVsize / 2); + } + } + + return timeBounds; + } + + private int getSpecifiedEndOfLifeVSize(DNode node) { + int endOfLifeVsize = 0; + + List<DNode> endOfLifes = Lists.newArrayList(Iterables.filter(node.getOwnedBorderedNodes(), new Predicate<DNode>() { + public boolean apply(DNode input) { + return input.isVisible() && EndOfLife.viewpointElementPredicate().apply(input); + } + })); + + if (!endOfLifes.isEmpty()) { + endOfLifeVsize = getSpecifiedVSize(endOfLifes.iterator().next()); + } + return endOfLifeVsize; + } + + /** + * Return the specified size of the given node. + * + * @param node + * the given node. + * @return the specified height of a {@link DNode}. + */ + protected int getSpecifiedVSize(DNode node) { + return new DNodeQuery(node).getDefaultDimension().height; + } + + private int getMinTimeBounds() { + int min = 2 * LayoutConstants.LIFELINES_START_Y; + Iterable<Lifeline> lifelinesWithoutCreation = getLifeLinesWithoutCreation(); + + // Avoid to handle lifelines to move up for min computation. + Predicate<Lifeline> isMinRangeCandidate = new Predicate<Lifeline>() { + public boolean apply(Lifeline input) { + InstanceRole irep = input.getInstanceRole(); + if (irep != null) { + return irep.getBounds().getLocation().y <= LayoutConstants.LIFELINES_START_Y; + } + return false; + } + }; + + Collection<Lifeline> lifelinesToConsider = Lists.newArrayList(Iterables.filter(lifelinesWithoutCreation, isMinRangeCandidate)); + if (!lifelinesToConsider.isEmpty()) { + Lifeline lep = heightOrdering.max(lifelinesToConsider); + min = LayoutConstants.LIFELINES_START_Y + instanceRoleHeight.apply(lep); + } + return min; + } + + /** + * Get the middle height of the given message's targeted figure. + * + * @param mover + * the given message. + * @return the middle height. + */ + protected int getTargetFigureMidHeight(Message mover) { + int midHeight = 0; + if (mover != null && mover.getTargetElement() != null) { + midHeight = mover.getTargetElement().getBounds().height / 2; + } + return midHeight; + } + + /** + * Increase the time range to the given upperBound. If the current upper + * bounds is bigger than the parameter, it does nothing. + * + * @param upperBound + * the new minimum upperBound. + */ + protected void updateTimerange(int upperBound) { + if (upperBound > timeRange.getUpperBound()) { + timeRange = new Range(timeRange.getLowerBound(), upperBound); + } + } + + /** + * Return the minimum valid lower start time on the {@link Lifeline} of the + * given {@link InstanceRole}. + * + * @param irep + * the current {@link InstanceRole}. + * @return the lifeline minimum valid lower bound. + */ + protected int getLifelineMinLowerBound(final InstanceRole irep) { + int vGap = LayoutConstants.LIFELINES_START_Y; + vGap += irep.getBounds().height; + return vGap; + } + + /** + * Register event old and init context (ends, old layout data, previous + * bounds flag, creators, destructors, ...). + */ + protected void registerEventEnds() { + for (EventEnd end : Lists.newArrayList(semanticOrdering)) { + registerEventEnd(end); + } + Collections.sort(flaggedEnds, Ordering.natural().onResultOf(eventEndOldFlaggedPosition)); + } + + private void registerEventEnd(EventEnd end) { + Collection<EObject> semanticEvents = EventEndHelper.getSemanticEvents(end); + Collection<ISequenceEvent> eventParts = Sets.newLinkedHashSet(); + for (EObject semanticEvent : semanticEvents) { + eventParts.addAll(ISequenceElementAccessor.getEventsForSemanticElement(sequenceDiagram, semanticEvent)); + } + + // ISequenceEvent has not been created + if (eventParts.isEmpty()) { + Collection<DDiagramElement> ddes = Sets.newLinkedHashSet(); + for (EObject semanticEvent : semanticEvents) { + ddes.addAll(ISequenceElementAccessor.getDiagramElementsForSemanticElement(sequenceDiagram, semanticEvent)); + } + + // No ISequenceEvent has been created but DDiagramElement exists, + // gmf refresh did not occurs, abort + // current layout. + if (!ddes.isEmpty()) { + semanticOrdering.clear(); + graphicalOrdering.clear(); + } + } + + boolean flagged = false; + boolean toolCreated = false; + boolean toolSemanticCreated = false; + boolean lost = false; + for (ISequenceEvent ise : eventParts) { + Range oldData = getOldLayoutData(ise); + oldLayoutData.put(ise, oldData); + endToISequencEvents.put(end, ise); + iSequenceEventsToEventEnds.put(ise, end); + + ISequenceElementQuery query = new ISequenceElementQuery(ise); + if (query.hasAbsoluteBoundsFlag()) { + Rectangle flaggedAbsoluteBounds = query.getFlaggedAbsoluteBounds(); + if (LayoutConstants.TOOL_CREATION_FLAG.equals(flaggedAbsoluteBounds)) { + toolCreated = true; + } else if (LayoutConstants.TOOL_CREATION_FLAG_FROM_SEMANTIC.equals(flaggedAbsoluteBounds)) { + toolSemanticCreated = true; + } else { + if (flaggedAbsoluteBounds.height == -1) { + // Correct auto-size + flaggedAbsoluteBounds.height = 0; + } + oldFlaggedLayoutData.put(ise, flaggedAbsoluteBounds); + flagged = true; + } + } + + if (ise instanceof Message) { + Message smep = (Message) ise; + ISequenceNode targetElement = smep.getTargetElement(); + if (targetElement instanceof InstanceRole) { + creators.put(end, smep); + } else if (targetElement instanceof EndOfLife) { + destructors.put(end, smep); + } else if (targetElement instanceof LostMessageEnd) { + lost = true; + losts.put(end, (LostMessageEnd) targetElement); + } + + ISequenceNode sourceElement = smep.getSourceElement(); + if (sourceElement instanceof LostMessageEnd) { + lost = true; + losts.put(end, (LostMessageEnd) sourceElement); + } + } + } + + if (flagged && !toolCreated) { + flaggedEnds.add(end); + } else if (isSafeToolCreation(end)) { + if (toolCreated) { + toolCreatedEnds.add(end); + } else if (toolSemanticCreated && ((end instanceof SingleEventEnd && ((SingleEventEnd) end).isStart()) || lost)) { + toolCreatedEnds.add(end); + } + } + } + + private boolean isSafeToolCreation(EventEnd end) { + boolean safe = !(end instanceof CompoundEventEnd); + safe = safe || EventEndHelper.PUNCTUAL_COMPOUND_EVENT_END.apply(end); + for (Message msg : Iterables.filter(endToISequencEvents.get(end), Message.class)) { + safe = safe || msg.getSourceElement() instanceof LostMessageEnd || msg.getTargetElement() instanceof LostMessageEnd; + } + + return safe; + } + + /** + * Get the common {@link ISequenceEvent} between the given ends. + * + * @param end1 + * a first {@link EventEnd} + * @param end2 + * a second {@link EventEnd} + * @return the common events between given ends + */ + protected Collection<ISequenceEvent> getCommonISequenceEvent(EventEnd end1, EventEnd end2) { + if (end1 == null || end2 == null) { + return Collections.<ISequenceEvent> emptyList(); + } + Collection<ISequenceEvent> ises1 = endToISequencEvents.get(end1); + Collection<ISequenceEvent> ises2 = endToISequencEvents.get(end2); + Collection<ISequenceEvent> commonIses = Lists.newArrayList(ises2); + Iterables.retainAll(commonIses, ises1); + return commonIses; + } + + @Override + protected Function<EventEnd, Integer> getOldPosition() { + return eventEndOldPosition; + } + + @Override + protected Function<EventEnd, Integer> getOldFlaggedPosition() { + return eventEndOldFlaggedPosition; + } +} |