/***************************************************************************** * Copyright (c) 2010 CEA * * * 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: * Atos Origin - Initial API and implementation * *****************************************************************************/ package org.eclipse.papyrus.uml.diagram.sequence.locator; import java.util.Collections; import java.util.List; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.emf.ecore.EObject; import org.eclipse.gef.EditPart; import org.eclipse.gmf.runtime.diagram.ui.editparts.ConnectionEditPart; import org.eclipse.gmf.runtime.diagram.ui.editparts.GraphicalEditPart; import org.eclipse.gmf.runtime.diagram.ui.editparts.ShapeNodeEditPart; import org.eclipse.gmf.runtime.gef.ui.figures.DefaultSizeNodeFigure; import org.eclipse.papyrus.uml.diagram.common.locator.AdvancedBorderItemLocator; import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.DestructionOccurrenceSpecificationEditPart; import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.DurationConstraintEditPart; import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.LifelineEditPart; import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.LifelineEditPart.LifelineFigure; import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.TimeConstraintEditPart; import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.TimeObservationEditPart; import org.eclipse.papyrus.uml.diagram.sequence.figures.LifelineDotLineCustomFigure; import org.eclipse.papyrus.uml.diagram.sequence.util.SequenceUtil; import org.eclipse.uml2.uml.DurationConstraint; import org.eclipse.uml2.uml.Element; import org.eclipse.uml2.uml.IntervalConstraint; import org.eclipse.uml2.uml.Message; import org.eclipse.uml2.uml.MessageOccurrenceSpecification; import org.eclipse.uml2.uml.NamedElement; import org.eclipse.uml2.uml.OccurrenceSpecification; import org.eclipse.uml2.uml.TimeConstraint; import org.eclipse.uml2.uml.TimeObservation; /** * This class is used to constrain the position of a Time/Duration related object on a Lifeline. */ public class TimeMarkElementPositionLocator extends AdvancedBorderItemLocator { /** * The edit part which figure is located. * Used to know the horizontal gap or whether the occurrence specifies a destruction event. */ private EditPart editPart = null; /** Constructor **/ public TimeMarkElementPositionLocator(IFigure parentFigure) { super(parentFigure); } /** Constructor **/ public TimeMarkElementPositionLocator(IFigure borderItem, IFigure parentFigure, Rectangle constraint) { super(borderItem, parentFigure, constraint); } /** Constructor **/ public TimeMarkElementPositionLocator(IFigure parentFigure, int preferredSide) { super(parentFigure, preferredSide); } /** Set the edit part which figure is placed by this locator */ public void setEditPart(EditPart part) { editPart = part; } /** * {@inheritDoc} */ @Override public Rectangle getValidLocation(Rectangle proposedLocation, IFigure borderItem) { Rectangle realLocation = new Rectangle(proposedLocation); int side = findClosestSideOfParent(realLocation, getParentBorder()); Point newTopLeft = locateOnBorder(realLocation.getTopLeft(), side, 0, borderItem); // correct in case the figure is linked to a destruction event if(timeElementPointsDestructionEvent()) { Point destr = getDestructionEventCenter(); if(destr != null) { if(editPart instanceof DurationConstraintEditPart) { // bottom is at the destruction event y realLocation.height = destr.y - newTopLeft.y; } else if(editPart instanceof TimeObservationEditPart || editPart instanceof TimeConstraintEditPart) { // center is at the destruction event y newTopLeft.y = destr.y - realLocation.height / 2; } } } realLocation.setLocation(newTopLeft); return realLocation; } /** * The y location takes precedence on the preferred side or on collision. * * @param suggestedLocation * the suggester location (y must be respected) * @param suggestedSide * the suggested side * @param circuitCount * recursion count to avoid an infinite loop * @return point */ protected Point locateOnBorder(Point suggestedLocation, int suggestedSide, int circuitCount, IFigure borderItem) { Point recommendedLocation = locateOnParent(suggestedLocation, suggestedSide, borderItem); IFigure conflictingBorderItem = getConflictingBorderItemFigure(recommendedLocation, borderItem); // max circuit count of 2 allows to try other side, then go back to original side if occupied if(circuitCount < 2 && conflictingBorderItem != null) { if(suggestedSide == PositionConstants.WEST) { // west is occupied, try east return locateOnBorder(recommendedLocation, PositionConstants.EAST, circuitCount + 1, borderItem); } else { //EAST // east is occupied, try west return locateOnBorder(recommendedLocation, PositionConstants.WEST, circuitCount + 1, borderItem); } } return recommendedLocation; } /** * Utility to calculate the bounds of the dot line to use as parent with consideration for the handle * bounds inset. * * @return <code>Rectangle</code> that is the bounds of the parent dot line. */ protected Rectangle getParentDotLineBorder() { // get the dot line of the lifeline if available for(Object childFig : getParentFigure().getChildren()) { if(childFig instanceof LifelineFigure) { LifelineDotLineCustomFigure dotline = ((LifelineFigure)childFig).getFigureLifelineDotLineFigure(); return dotline.getBounds().getCopy(); } } return super.getParentBorder(); } /** * Ensure the suggested location actually lies on the parent boundary. The * side takes precedence. * * @param suggestedLocation * @param suggestedSide * @return point */ protected Point locateOnParent(Point suggestedLocation, int suggestedSide, IFigure borderItem) { int[] horizontalGap = getHorizontalGap(); Rectangle bounds = getParentDotLineBorder(); int parentFigureXCenter = bounds.getCenter().x; int parentFigureHeight = bounds.height; int parentFigureY = bounds.y; Dimension borderItemSize = getCorrectSize(borderItem); int newX = suggestedLocation.x; int newY = suggestedLocation.y; int westX = parentFigureXCenter - borderItemSize.width + horizontalGap[0]; int eastX = parentFigureXCenter + horizontalGap[1]; int southY = parentFigureY + parentFigureHeight - getBorderItemOffset().height; int northY = parentFigureY + getBorderItemOffset().height; if(suggestedSide == PositionConstants.WEST) { if(suggestedLocation.x != westX) { newX = westX; } if(suggestedLocation.y < bounds.getTopLeft().y) { newY = northY; } else if(suggestedLocation.y > bounds.getBottomLeft().y - borderItemSize.height) { newY = southY - borderItemSize.height; } } else { // EAST if(suggestedLocation.x != eastX) { newX = eastX; } if(suggestedLocation.y < bounds.getTopLeft().y) { newY = northY; } else if(suggestedLocation.y > bounds.getBottomLeft().y - borderItemSize.height) { newY = southY - borderItemSize.height; } } return new Point(newX, newY); } /** * Get the center of the destruction event figure (relative) * * @return the destruction center or null */ private Point getDestructionEventCenter() { LifelineEditPart lifeline = SequenceUtil.getParentLifelinePart(editPart); if(lifeline != null) { for(Object child : lifeline.getChildren()) { if(child instanceof DestructionOccurrenceSpecificationEditPart) { return ((DestructionOccurrenceSpecificationEditPart)child).getFigure().getBounds().getCenter(); } } } return null; } /** * Know whether the figure must be located all down because its element references a destruction event * * @return true if destruction event is referenced */ private boolean timeElementPointsDestructionEvent() { if(editPart instanceof GraphicalEditPart) { // get event occurrence(s) EObject element = ((GraphicalEditPart)editPart).resolveSemanticElement(); List<? extends Element> occurrences = Collections.emptyList(); if(element instanceof TimeObservation) { NamedElement occurence = ((TimeObservation)element).getEvent(); occurrences = Collections.singletonList(occurence); } else if(element instanceof TimeConstraint || element instanceof DurationConstraint) { occurrences = ((IntervalConstraint)element).getConstrainedElements(); } for(Element occurrence : occurrences) { if(occurrence instanceof DestructionOccurrenceSpecificationEditPart) { return true; } } } return false; } /** * Get the gaps to know how the figure must be translated from the center of the lifeline, taking in account executions. * * @return a size 2 array with left translation at index 0 et right at index 1 */ private int[] getHorizontalGap() { int[] horizontalGap = new int[]{ 0, 0 }; if(editPart instanceof GraphicalEditPart) { // get event occurrence(s) LifelineEditPart lifeline = SequenceUtil.getParentLifelinePart(editPart); EObject element = ((GraphicalEditPart)editPart).resolveSemanticElement(); List<? extends Element> occurrences = Collections.emptyList(); if(element instanceof TimeObservation) { NamedElement occurence = ((TimeObservation)element).getEvent(); occurrences = Collections.singletonList(occurence); } else if(element instanceof TimeConstraint || element instanceof DurationConstraint) { occurrences = ((IntervalConstraint)element).getConstrainedElements(); } boolean horizontalGapSet = false; // check bounds of event's execution for(Element occurrence : occurrences) { if(occurrence instanceof OccurrenceSpecification) { EditPart part = SequenceUtil.getLinkedEditPart(lifeline, (OccurrenceSpecification)occurrence); if(part instanceof ConnectionEditPart && occurrence instanceof MessageOccurrenceSpecification) { // get the bounds of the execution on which the connection is connected ConnectionEditPart conn = (ConnectionEditPart)part; Message message = ((MessageOccurrenceSpecification)occurrence).getMessage(); if(message != null) { boolean start = message.getSendEvent() != null && message.getSendEvent().equals(occurrence); if(start) { part = conn.getSource(); } else { part = conn.getTarget(); } } } if(part instanceof ShapeNodeEditPart && !lifeline.equals(part)) { Rectangle execBounds = ((ShapeNodeEditPart)part).getFigure().getBounds(); Rectangle parentBounds = getParentBorder(); int leftGap = execBounds.getLeft().x - parentBounds.getCenter().x; int rightGap = execBounds.getRight().x - parentBounds.getCenter().x; if(part instanceof DestructionOccurrenceSpecificationEditPart) { // center of destruction event is considered leftGap = 0; rightGap = 0; } if(!horizontalGapSet) { horizontalGap[0] = leftGap; horizontalGap[1] = rightGap; horizontalGapSet = true; } else { // take the more exterior gap of all events horizontalGap[0] = Math.min(horizontalGap[0], leftGap); horizontalGap[1] = Math.max(horizontalGap[1], rightGap); } } } } } return horizontalGap; } /** * Find the closest side when x,y is inside parent. * * @param proposedLocation * @param parentBorder * @return draw constant */ public static int findClosestSideOfParent(Rectangle proposedLocation, Rectangle parentBorder) { // Rectangle parentBorder = getParentBorder(); Point parentCenter = parentBorder.getCenter(); Point childCenter = proposedLocation.getCenter(); if(childCenter.x < parentCenter.x) // West, North or South. { return PositionConstants.WEST; } else { // EAST, NORTH or SOUTH return PositionConstants.EAST; } } /** * Relocate the child border item within the parent * * @see org.eclipse.papyrus.uml.diagram.common.locator.AdvancedBorderItemLocator#relocate(org.eclipse.draw2d.IFigure) * * @param borderItem * the item to relocate */ @Override public void relocate(IFigure borderItem) { // reset bounds of borderItem Dimension size = getCorrectSize(borderItem); Rectangle rectSuggested = getConstraint().getCopy(); if(rectSuggested.getTopLeft().x == 0 && rectSuggested.getTopLeft().y == 0) { rectSuggested.setLocation(getPreferredLocation(borderItem)); } else { // recovered constraint must be translated with the parent location to be absolute rectSuggested.setLocation(rectSuggested.getLocation().translate(getParentBorder().getTopLeft())); } rectSuggested.setSize(size); Rectangle validLocation = getValidLocation(rectSuggested, borderItem); // the constraint is not reset, but the item bounds are borderItem.setBounds(validLocation); if(timeElementPointsDestructionEvent() && editPart instanceof DurationConstraintEditPart) { // except when duration constraint is reduced ... int w = getConstraint().getSize().width; getConstraint().setSize(w, validLocation.height); } // ensure the side property is correctly set setCurrentSideOfParent(findClosestSideOfParent(borderItem.getBounds(), getParentBorder())); // redraw time mark if necessary redrawTimeMarks(borderItem, validLocation); } /** * Gets the size of the border item figure. Take in account the constraint as a minimum size. * * @param borderItem * @return the size of the border item figure. */ protected final Dimension getCorrectSize(IFigure borderItem) { Dimension size = getConstraint().getSize(); if(size.isEmpty()) { size.union(borderItem.getPreferredSize(size.width, size.height)); } return size; } /** * Redraw the time marks * * @param borderItem * the relocated border item * @param location * the new location */ private void redrawTimeMarks(IFigure borderItem, Rectangle location) { for(Object child : borderItem.getChildren()) { if(child instanceof TimeConstraintEditPart.TimeMarkElementFigure) { ((TimeConstraintEditPart.TimeMarkElementFigure)child).setCurrentSideOfFigure(getCurrentSideOfParent(), location); } else if(child instanceof TimeObservationEditPart.TimeMarkElementFigure) { ((TimeObservationEditPart.TimeMarkElementFigure)child).setCurrentSideOfFigure(getCurrentSideOfParent(), location); } else if(child instanceof DurationConstraintEditPart.DurationConstraintFigure) { ((DurationConstraintEditPart.DurationConstraintFigure)child).updateArrow(location.width, location.height); } else if(child instanceof DefaultSizeNodeFigure) { redrawTimeMarks((IFigure)child, location); } } } }