/** * Layouts all lifelines. The lifelines are placed at a certain height and along the x-axis with a * specified distance between them. Lifelines that contain a stereotype in their name are placed * slightly higher such that the bottom of each name is flush among all lifelines. */ private void layoutLifelines() { float currentLifelineX = LIFELINE_START_X; Set<Lifeline> layoutedLifelines = new HashSet<Lifeline>(); // Layout lifelines if they have no position. Otherwise they might have been moved on the // x-axis, // which we allow to do. for (InteractionFragment fragment : specification.getFragments()) { // In case the message add event hasn't been received yet there will be no lifeline that is // covered. // This is the case when a message and a reply are added. // The structure exists, but not all commands have been executed. for (Lifeline lifeline : fragment.getCovered()) { if (!layoutedLifelines.contains(lifeline)) { LifelineView lifelineView = lifelines.get(lifeline); LayoutElement layoutElement = lifelineView.getLayoutElement(); if (layoutElement.getX() == 0) { lifelineView.getLayoutElement().setX(currentLifelineX); lifelineView.getLayoutElement().setY(LIFELINE_Y); } /** * Lifelines of metaclasses are double as high as regular lifelines. This causes problems * when dealing with combined fragments or when drawing a message from a message view of a * create message to one (as the first message). Therefore, these lifelines are moved up * so that all lifelines are flush on the bottom of their name container. * * @see issue #230 */ if (lifeline.getRepresents() instanceof StructuralFeature) { StructuralFeature structuralFeature = (StructuralFeature) lifeline.getRepresents(); if (structuralFeature.isStatic()) { float y = LIFELINE_Y - (lifelineView.getNameHeight() / 2); if (y != lifelineView.getLayoutElement().getY()) { lifelineView.getLayoutElement().setY(y); } } } currentLifelineX = currentLifelineX + lifelineView.getWidth() + LIFEINE_SPACING; layoutedLifelines.add(lifeline); } } } }
/** * Layouts all fragments along this view. Can be called recursively for different fragment * containers. * * @param container the {@link FragmentContainer} for which fragments should be layouted * @param nextfragmentY the y-position for the next fragment * @return the y-position for the next fragment */ private float layoutFragments(FragmentContainer container, float nextfragmentY) { float previousFragmentY = nextfragmentY; float currentFragmentY = nextfragmentY; boolean receiveEventNext = false; // Make sure that all messages are updated, since this might be necessary due to moving the // lifelines around. for (InteractionFragment fragment : container.getFragments()) { int index = fragment.getContainer().getFragments().indexOf(fragment); for (Lifeline lifeline : fragment.getCovered()) { LifelineView lifelineView = lifelines.get(lifeline); // In case of a create message the lifeline has to be moved down depending on the name // height. if (fragment instanceof MessageOccurrenceSpecification) { MessageOccurrenceSpecification messageEnd = (MessageOccurrenceSpecification) fragment; // If the message was removed, but the message ends are still there, // ignore them. if (!messages.containsKey(messageEnd.getMessage())) { continue; } if (messageEnd.getMessage().getMessageSort() == MessageSort.CREATE_MESSAGE && messageEnd.getMessage().getReceiveEvent() == messageEnd) { float lifelineNameHeight = lifelineView.getNameHeight(); float difference = lifelineNameHeight - BOX_HEIGHT; float lifelineY = currentFragmentY - (difference / 2f); /** Convert global position to relative one. */ lifelineY = getGlobalVecToParentRelativeSpace(lifelineView, new Vector3D(0, lifelineY)).y; lifelineView.getLayoutElement().setY(lifelineY); currentFragmentY += lifelineNameHeight; receiveEventNext = false; continue; } } RamRectangleComponent fragmentView = lifelineView.getFragmentView(fragment, index); // view is null when message was just removed if (fragmentView != null) { RamRectangleComponent spacer = lifelineView.getSpacerForFragmentAt(fragment, index); // Start with the position of the first spacer. if (currentFragmentY == LIFELINE_Y) { currentFragmentY = spacer.getPosition(TransformSpace.GLOBAL).getY(); } if (!receiveEventNext) { currentFragmentY += BOX_HEIGHT; } Vector3D currentPosition = fragmentView.getPosition(TransformSpace.GLOBAL); if (currentPosition.getY() != currentFragmentY) { float difference = currentFragmentY - currentPosition.getY(); float height = spacer.getHeight() + difference; // Fix the height to not go below the minimum size. if (height < BOX_HEIGHT) { height = BOX_HEIGHT; } spacer.setMinimumHeight(height); // Need to inform parent manually. spacer.updateParent(); } if (fragment instanceof MessageOccurrenceSpecification) { MessageOccurrenceSpecification messageEnd = (MessageOccurrenceSpecification) fragment; Message message = messageEnd.getMessage(); /** * Prevent moving downwards if the next event will be a receive event. However, if it is * a self message we need to move downwards. If it is the send event of a reply that * ends in a gate, there will be no next event. */ if (!message.isSelfMessage() && message.getSendEvent() == messageEnd && message.getReceiveEvent().eClass() != RamPackage.Literals.GATE) { receiveEventNext = true; } else { receiveEventNext = false; } } if (fragment instanceof CombinedFragment) { CombinedFragment combinedFragment = (CombinedFragment) fragment; CombinedFragmentView combinedFragmentView = combinedFragments.get(combinedFragment); if (combinedFragment.getCovered().indexOf(lifeline) == 0) { Vector3D position = combinedFragmentView.getPosition(TransformSpace.GLOBAL); position.setY(currentFragmentY); combinedFragmentView.setPositionGlobal(position); receiveEventNext = true; } // Only increase y if it is the last lifeline. int lastIndex = combinedFragment.getCovered().size() - 1; if (combinedFragment.getCovered().indexOf(lifeline) == lastIndex) { for (InteractionOperand operand : combinedFragment.getOperands()) { float operandHeight = combinedFragmentView.getOperandMinimumHeight(operand); if (operand.getFragments().size() > 0) { operandHeight += layoutFragments(operand, currentFragmentY + operandHeight); } // Add an additional space due to the additional spacer in the operand. // This also supports empty operands. operandHeight += BOX_HEIGHT; combinedFragmentView.setOperandHeight(operand, operandHeight); currentFragmentY += operandHeight; } combinedFragmentView.updateLayout(); receiveEventNext = false; } } else if (!receiveEventNext) { currentFragmentY += fragmentView.getHeight(); } } } } return currentFragmentY - previousFragmentY; }