Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout')
-rw-r--r--plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/AbstractSequenceLayout.java214
-rw-r--r--plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/AbstractSequenceOrderingLayout.java168
-rw-r--r--plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/EventEndToPositionFunction.java137
-rw-r--r--plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/LayoutConstants.java193
-rw-r--r--plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/SequenceLayout.java138
-rw-r--r--plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/flag/AbstractSequenceAbsoluteBoundsFlagger.java93
-rw-r--r--plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/flag/SequenceDiagramAbsoluteBoundsFlagger.java54
-rw-r--r--plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/flag/SequenceEventAbsoluteBoundsFlagger.java51
-rw-r--r--plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/horizontal/LostMessageEndHorizontalLayoutHelper.java451
-rw-r--r--plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/horizontal/SequenceHorizontalLayout.java656
-rw-r--r--plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/observation/SequenceObservationLayout.java165
-rw-r--r--plugins/org.eclipse.sirius.diagram.sequence/src/org/eclipse/sirius/diagram/sequence/business/internal/layout/vertical/SequenceVerticalLayout.java1080
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;
+ }
+}

Back to the top