/********************************************************************** * Copyright (c) 2005, 2014 IBM Corporation, Ericsson * 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: * IBM - Initial API and implementation * Bernd Hufmann - Updated for TMF **********************************************************************/ package org.eclipse.tracecompass.tmf.ui.views.uml2sd.core; import java.util.Comparator; import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp; import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; import org.eclipse.tracecompass.tmf.ui.views.uml2sd.drawings.IGC; import org.eclipse.tracecompass.tmf.ui.views.uml2sd.preferences.ISDPreferences; import org.eclipse.tracecompass.tmf.ui.views.uml2sd.preferences.SDViewPref; import org.eclipse.tracecompass.tmf.ui.views.uml2sd.util.SortAsyncForBackward; import org.eclipse.tracecompass.tmf.ui.views.uml2sd.util.SortAsyncMessageComparator; /** * A AsyncMessage is a asynchronous message which appear at two different event occurrences on each lifeline ends (sender * and receiver).<br> * <br> * <br> * Usage example: * * <pre> * Frame frame; * Lifeline lifeLine1; * Lifeline lifeLine2; * * AsyncMessage message = new AsyncMessage(); * // Create a new event occurrence on each lifeline * lifeline1.getNewOccurrenceIndex(); * lifeline2.getNewOccurrenceIndex(); * // Set the message sender and receiver * message.setStartLifeline(lifeLine1); * message.setEndLifline(lifeline2); * message.setName("Message label"); * // add the message to the frame * frame.addMessage(message); * </pre> * * @see Lifeline Lifeline for more event occurence details * @version 1.0 * @author sveyrier */ public class AsyncMessage extends BaseMessage implements ITimeRange { // ------------------------------------------------------------------------ // Constants // ------------------------------------------------------------------------ /** * The grahNode ID constant */ public static final String ASYNC_MESS_TAG = "AsyncMessage"; //$NON-NLS-1$ // ------------------------------------------------------------------------ // Attributes // ------------------------------------------------------------------------ /** * Flag whether message has time information or not. */ private boolean fHasTime = false; /** * The time when the message begin */ private ITmfTimestamp fEndTime = TmfTimestamp.fromSeconds(0); /** * The time when the message end */ private ITmfTimestamp fStartTime = TmfTimestamp.fromSeconds(0); /** * The associated message. */ protected AsyncMessageReturn fMessageReturn = null; // ------------------------------------------------------------------------ // Constructors // ------------------------------------------------------------------------ /** * Default constructor */ public AsyncMessage() { setColorPrefId(ISDPreferences.PREF_ASYNC_MESS); } // ------------------------------------------------------------------------ // Methods // ------------------------------------------------------------------------ @Override public int getX() { int x = super.getX(true); int activationWidth = Metrics.EXECUTION_OCCURRENCE_WIDTH / 2; if ((getStartLifeline() != null) && (getEndLifeline() != null) && (getStartLifeline().getX() > getEndLifeline().getX())) { activationWidth = -activationWidth; } if (isMessageStartInActivation(getStartOccurrence())) { x = x + activationWidth; } return x; } @Override public int getY() { if ((getStartLifeline() != null) && (getEndLifeline() != null)) { return getEndLifeline().getY() + getEndLifeline().getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * getStartOccurrence(); } return super.getY(); } @Override public int getWidth() { int width = super.getWidth(true); int activationWidth = Metrics.EXECUTION_OCCURRENCE_WIDTH / 2; if ((getStartLifeline() != null) && (getEndLifeline() != null) && (getStartLifeline().getX() > getEndLifeline().getX())) { activationWidth = -activationWidth; } if (isMessageStartInActivation(getStartOccurrence())) { width = width - activationWidth; } if (isMessageEndInActivation(getEndOccurrence())) { width = width - activationWidth; } return width; } @Override public int getHeight() { if ((getStartLifeline() != null) && (getEndLifeline() != null)) { return (getEndLifeline().getY() + getEndLifeline().getHeight() + (Metrics.getMessageFontHeigth() + Metrics.getMessagesSpacing()) * getEndOccurrence()) - getY(); } return super.getHeight(); } /** * Set the message return associated with this message. * * @param message the message return to associate */ protected void setMessageReturn(AsyncMessageReturn message) { fMessageReturn = message; } /** * Set the event occurrence attached to this message for its end lifeline * * @param occurrence the event occurrence to set */ @Override public void setEndOccurrence(int occurrence) { super.setEndOccurrence(occurrence); if (getStartLifeline() == null) { setStartOccurrence(occurrence); } informFrame(getEndLifeline(), occurrence); } /** * Informs the given lifeline about the maximum occurrence if applicable. * * @param lifeLine * Concerned lifeline * @param occurrence * Occurrence number */ protected void informFrame(Lifeline lifeLine, int occurrence) { if ((lifeLine != null) && (lifeLine.getFrame() != null) && (lifeLine.getFrame().getMaxEventOccurrence() < occurrence)) { lifeLine.getFrame().setMaxEventOccurrence(occurrence); } } /** * Set the event occurrence attached to this message for its start lifeline * * @param occurrence the event occurrence to set */ @Override public void setStartOccurrence(int occurrence) { super.setStartOccurrence(occurrence); if (getEndLifeline() == null) { setEndOccurrence(getStartOccurrence()); } informFrame(getStartLifeline(), occurrence); } /** * Set the lifeLine which has sent the message.<br> * A new EventOccurence will be create on this lifeLine.<br> * * @param lifeline the message sender */ public void autoSetStartLifeline(Lifeline lifeline) { lifeline.getNewEventOccurrence(); setStartLifeline(lifeline); } /** * Set the lifeLine which has received the message.<br> * A new EventOccurence will be create on this lifeLine.<br> * * @param lifeline the message receiver */ public void autoSetEndLifeline(Lifeline lifeline) { lifeline.getNewEventOccurrence(); setEndLifeline(lifeline); } @Override public void setStartLifeline(Lifeline lifeline) { super.setStartLifeline(lifeline); setStartOccurrence(getStartLifeline().getEventOccurrence()); if (getEndLifeline() == null) { setEndOccurrence(getStartOccurrence()); } } @Override public void setEndLifeline(Lifeline lifeline) { super.setEndLifeline(lifeline); setEventOccurrence(getEndLifeline().getEventOccurrence()); } /** * Returns true if the point C is on the segment defined with the point A and B * * @param xA point A x coordinate * @param yA point A y coordinate * @param xB point B x coordinate * @param yB point B y coordinate * @param xC point C x coordinate * @param yC point C y coordinate * @return Return true if the point C is on the segment defined with the point A and B, else otherwise */ protected boolean isNearSegment(int xA, int yA, int xB, int yB, int xC, int yC) { if ((xA > xB) && (xC > xA)) { return false; } if ((xA < xB) && (xC > xB)) { return false; } if ((xA < xB) && (xC < xA)) { return false; } if ((xA > xB) && (xC < xB)) { return false; } double distAB = Math.sqrt((xB - xA) * (xB - xA) + (yB - yA) * (yB - yA)); double scalar = ((xB - xA) * (xC - xA) + (yB - yA) * (yC - yA)) / distAB; double distAC = Math.sqrt((xC - xA) * (xC - xA) + (yC - yA) * (yC - yA)); double distToSegment = Math.sqrt(Math.abs(distAC * distAC - scalar * scalar)); if (distToSegment <= Metrics.MESSAGE_SELECTION_TOLERANCE) { return true; } return false; } @Override public boolean contains(int x, int y) { // Is it a self message? if (getStartLifeline() == getEndLifeline()) { return super.contains(x, y); } if (isNearSegment(getX(), getY(), getX() + getWidth(), getY() + getHeight(), x, y)) { return true; } int messageMaxWidth = Metrics.swimmingLaneWidth() - Metrics.EXECUTION_OCCURRENCE_WIDTH; int nameWidth = getName().length() * Metrics.getAverageCharWidth(); if (getName().length() * Metrics.getAverageCharWidth() > messageMaxWidth) { if (GraphNode.contains(getX(), getY() - Metrics.MESSAGES_NAME_SPACING - Metrics.getMessageFontHeigth(), messageMaxWidth, Metrics.getMessageFontHeigth(), x, y)) { return true; } } else { if (GraphNode.contains(getX() + (messageMaxWidth - nameWidth) / 2, getY() + getHeight() / 2 - Metrics.MESSAGES_NAME_SPACING - Metrics.getMessageFontHeigth(), nameWidth, Metrics.getMessageFontHeigth(), x, y)) { return true; } } return false; } /** * Draws the asynchronous message using giving graphical context. * * @param context A graphical context to draw in. */ protected void drawAsyncMessage(IGC context) { if (getStartLifeline() != null && getEndLifeline() != null && getStartLifeline() == getEndLifeline() && (getStartOccurrence() != getEndOccurrence())) { int x = getX(); int y = getY(); int height = getHeight(); int tempx = 0; boolean startInActivation = isMessageStartInActivation(getStartOccurrence()); boolean endInActivation = isMessageEndInActivation(getEndOccurrence()); if (endInActivation && !startInActivation) { tempx = Metrics.EXECUTION_OCCURRENCE_WIDTH / 2; } if (startInActivation && !endInActivation) { tempx = -Metrics.EXECUTION_OCCURRENCE_WIDTH / 2; } int tempy = Metrics.INTERNAL_MESSAGE_WIDTH / 2; if (getHeight() <= Metrics.INTERNAL_MESSAGE_WIDTH) { tempy = getHeight() / 2; } context.drawLine(x, y, x + Metrics.INTERNAL_MESSAGE_WIDTH / 2, y); context.drawLine(x + Metrics.INTERNAL_MESSAGE_WIDTH, y + tempy, x + Metrics.INTERNAL_MESSAGE_WIDTH, y + height - tempy); context.drawLine(x + tempx, y + height, x + Metrics.INTERNAL_MESSAGE_WIDTH / 2, y + height); Double xt = Double.valueOf(Math.cos(0.75) * 7); Double yt = Double.valueOf(Math.sin(0.75) * 7); context.drawLine(x + xt.intValue() + tempx, y + height + yt.intValue(), x + tempx, y + height); context.drawArc(x, y, Metrics.INTERNAL_MESSAGE_WIDTH, 2 * tempy, 0, 90); context.drawArc(x, y + height, Metrics.INTERNAL_MESSAGE_WIDTH, -2 * tempy, 0, -90); context.drawLine(x + xt.intValue() + tempx, y + height - yt.intValue(), x + tempx, y + height); context.drawTextTruncated(getName(), x + Metrics.INTERNAL_MESSAGE_WIDTH + Metrics.INTERNAL_MESSAGE_V_MARGIN, y, Metrics.swimmingLaneWidth() - Metrics.EXECUTION_OCCURRENCE_WIDTH + -Metrics.INTERNAL_MESSAGE_WIDTH, +Metrics.MESSAGES_NAME_SPACING - Metrics.getMessageFontHeigth(), !isSelected()); } else { super.draw(context); } } @Override public void draw(IGC context) { if (!isVisible()) { return; } ISDPreferences pref = SDViewPref.getInstance(); // Draw it selected? if (isSelected() && (getStartLifeline() != null && getEndLifeline() != null && getStartLifeline() == getEndLifeline() && (getStartOccurrence() != getEndOccurrence()))) { /* * Draw it twice First time, bigger inverting selection colors Second time, regular drawing using selection * colors This create the highlight effect */ context.setForeground(pref.getBackGroundColorSelection()); context.setLineWidth(Metrics.SELECTION_LINE_WIDTH); drawAsyncMessage(context); context.setBackground(pref.getBackGroundColorSelection()); context.setForeground(pref.getForeGroundColorSelection()); // Second drawing is done after the else } else { context.setBackground(pref.getBackGroundColor(getColorPrefId())); context.setForeground(pref.getForeGroundColor(getColorPrefId())); } if (hasFocus()) { context.setDrawTextWithFocusStyle(true); } context.setLineWidth(Metrics.NORMAL_LINE_WIDTH); drawAsyncMessage(context); if (hasFocus()) { context.setDrawTextWithFocusStyle(false); } } /** * Set the time when the message end * * @param time the time when the message end */ public void setEndTime(ITmfTimestamp time) { fEndTime = time; fHasTime = true; if (getStartLifeline() != null && getStartLifeline().getFrame() != null) { getStartLifeline().getFrame().setHasTimeInfo(true); } else if (getEndLifeline() != null && getEndLifeline().getFrame() != null) { getEndLifeline().getFrame().setHasTimeInfo(true); } } /** * Set the time when the message start * * @param time the time when the message start */ public void setStartTime(ITmfTimestamp time) { fStartTime = time; fHasTime = true; if (getStartLifeline() != null && getStartLifeline().getFrame() != null) { getStartLifeline().getFrame().setHasTimeInfo(true); } else if (getEndLifeline() != null && getEndLifeline().getFrame() != null) { getEndLifeline().getFrame().setHasTimeInfo(true); } } @Override public ITmfTimestamp getEndTime() { return fEndTime; } @Override public ITmfTimestamp getStartTime() { return fStartTime; } @Override public boolean hasTimeInfo() { return fHasTime; } /** * @return message return instance or null */ public AsyncMessageReturn getMessageReturn() { return fMessageReturn; } @Override public boolean isVisible(int x, int y, int width, int height) { int toDrawY = getY(); int toDrawHeight = getHeight(); if ((toDrawY > y + height + Metrics.MESSAGES_NAME_SPACING + Metrics.getMessageFontHeigth()) && (toDrawY + toDrawHeight > y + height + Metrics.MESSAGES_NAME_SPACING + Metrics.getMessageFontHeigth())) { return false; } if (toDrawY < y && (toDrawY + toDrawHeight < y)) { return false; } return super.isVisible(x, y, width, height); } @Override public Comparator<GraphNode> getComparator() { return new SortAsyncMessageComparator(); } @Override public String getArrayId() { return ASYNC_MESS_TAG; } @Override public Comparator<GraphNode> getBackComparator() { return new SortAsyncForBackward(); } @Override public boolean positiveDistanceToPoint(int x, int y) { int mY = getY(); int mH = getHeight(); if ((mY > y) || (mY + mH > y)) { return true; } return false; } }