/**********************************************************************
* 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 static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
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;
/**
* The Frame class is the base sequence diagram graph nodes container.<br>
* For instance, only one frame can be drawn in the View.<br>
* Lifelines, Messages and Stop which are supposed to represent a Sequence diagram are drawn in a Frame.<br>
* Only the graph node added to their representing list will be drawn.
*
* The lifelines are appended along the X axsis when added in a frame.<br>
* The syncMessages are ordered along the Y axsis depending on the event occurrence they are attached to.<br>
*
* @see org.eclipse.tracecompass.tmf.ui.views.uml2sd.core.Lifeline Lifeline for more event occurence details
* @author sveyrier
* @version 1.0
*/
public class BasicFrame extends GraphNode {
// ------------------------------------------------------------------------
// Attributes
// ------------------------------------------------------------------------
/**
* Contains the max elapsed time between two consecutive messages in the whole frame
*/
private ITmfTimestamp fMaxTime = TmfTimestamp.fromSeconds(0);
/**
* Contains the min elapsed time between two consecutive messages in the whole frame
*/
private ITmfTimestamp fMinTime = TmfTimestamp.fromSeconds(0);
/**
* Indicate if the min and max elapsed time between two consecutive messages in the whole frame need to be computed
*/
private boolean fComputeMinMax = true;
/**
* Store the preference set by the user regarding the external time. This flag is used determine if the min and max
* need to be recomputed in case this preference is changed.
*/
private boolean fLastExternalTimePref = SDViewPref.getInstance().excludeExternalTime();
/**
* The greater event occurrence created on graph nodes drawn in this Frame This directly impact the Frame height
*/
private int fVerticalIndex = 0;
/**
* The index along the x axis where the next lifeline will is drawn This directly impact the Frame width
*/
private int fHorizontalIndex = 0;
/**
* The time information flag.
*/
private boolean fHasTimeInfo = false;
/**
* The current Frame visible area - x coordinates
*/
private int fVisibleAreaX;
/**
* The current Frame visible area - y coordinates
*/
private int fVisibleAreaY;
/**
* The current Frame visible area - width
*/
private int fVisibleAreaWidth;
/**
* The current Frame visible area - height
*/
private int fVisibleAreaHeight;
/**
* The event occurrence spacing (-1 for none)
*/
private int fForceEventOccurrenceSpacing = -1;
/**
* Flag to indicate customized minumum and maximum.
*/
private boolean fCustomMinMax = false;
/**
* The minimum time between messages of the sequence diagram frame.
*/
private ITmfTimestamp fMinSDTime = TmfTimestamp.fromSeconds(0);
/**
* The maximum time between messages of the sequence diagram frame.
*/
private ITmfTimestamp fMaxSDTime = TmfTimestamp.fromSeconds(0);
/**
* Flag to indicate that initial minimum has to be computed.
*/
private boolean fInitSDMin = true;
// ------------------------------------------------------------------------
// Constructors
// ------------------------------------------------------------------------
/**
* Creates an empty frame.
*/
public BasicFrame() {
Metrics.setForcedEventSpacing(fForceEventOccurrenceSpacing);
}
// ------------------------------------------------------------------------
// Methods
// ------------------------------------------------------------------------
/**
*
* Returns the greater event occurence known by the Frame
*
* @return the greater event occurrence
*/
protected int getMaxEventOccurrence() {
return fVerticalIndex;
}
/**
* Set the greater event occurrence created in GraphNodes included in the frame
*
* @param eventOccurrence the new greater event occurrence
*/
protected void setMaxEventOccurrence(int eventOccurrence) {
fVerticalIndex = eventOccurrence;
}
/**
* This method increase the lifeline place holder The return value is usually assign to a lifeline. This can be used
* to set the lifelines drawing order. Also, calling this method two times and assigning only the last given index
* to a lifeline will increase this lifeline draw spacing (2 times the default spacing) from the last added
* lifeline.
*
* @return a new lifeline index
*/
protected int getNewHorizontalIndex() {
return ++fHorizontalIndex;
}
/**
* Returns the current horizontal index
*
* @return the current horizontal index
* @see Frame#getNewHorizontalIndex() for horizontal index description
*/
protected int getHorizontalIndex() {
return fHorizontalIndex;
}
@Override
public void addNode(GraphNode nodeToAdd) {
setComputeMinMax(true);
super.addNode(nodeToAdd);
}
@Override
public int getX() {
return Metrics.FRAME_H_MARGIN;
}
@Override
public int getY() {
return Metrics.FRAME_V_MARGIN;
}
@Override
public int getWidth() {
if (fHorizontalIndex == 0) {
return 3 * Metrics.swimmingLaneWidth() + Metrics.LIFELINE_H_MAGIN * 2 - Metrics.FRAME_H_MARGIN - Metrics.LIFELINE_SPACING / 2;
}
return fHorizontalIndex * Metrics.swimmingLaneWidth() + Metrics.LIFELINE_H_MAGIN * 2 + 1 - Metrics.LIFELINE_SPACING;
}
@Override
public int getHeight() {
// The Frame height depends on the maximum number of messages added to a lifeline
if (fVerticalIndex == 0) {
return 5 * (Metrics.getMessagesSpacing() + Metrics.getMessageFontHeigth()) + Metrics.LIFELINE_NAME_H_MARGIN + Metrics.FRAME_NAME_H_MARGIN + Metrics.getFrameFontHeigth() + Metrics.LIFELINE_VT_MAGIN + Metrics.LIFELINE_VB_MAGIN
+ Metrics.LIFELINE_NAME_H_MARGIN + Metrics.FRAME_NAME_H_MARGIN + Metrics.getLifelineFontHeigth() * 2;
}
if (fForceEventOccurrenceSpacing >= 0) {
Metrics.setForcedEventSpacing(fForceEventOccurrenceSpacing);
}
return fVerticalIndex * (Metrics.getMessagesSpacing() + Metrics.getMessageFontHeigth()) + Metrics.LIFELINE_NAME_H_MARGIN + Metrics.FRAME_NAME_H_MARGIN + Metrics.getFrameFontHeigth() + Metrics.LIFELINE_VT_MAGIN + Metrics.LIFELINE_VB_MAGIN
+ Metrics.LIFELINE_NAME_H_MARGIN + Metrics.FRAME_NAME_H_MARGIN + Metrics.getLifelineFontHeigth() * 2;
}
/**
* @return true if mininum and maximum time needs to be calculated else false
*/
protected boolean isComputeMinMax() {
return fComputeMinMax;
}
/**
* @return true if mininum and maximum time needs to be calculated else false
*/
protected boolean isCustomMinMax() {
return fCustomMinMax;
}
/**
* gets the initialization flag for SD minimum.
*
* @return the initialization flag for SD minimum
*/
protected boolean getInitSDMin() {
return fInitSDMin;
}
/**
* Returns the graph node which contains the point given in parameter for the given graph node list and starting the
* iteration at the given index<br>
* WARNING: Only graph nodes with smaller coordinates than the current visible area can be returned.<br>
*
* @param x the x coordinate of the point to test
* @param y the y coordinate of the point to test
* @param list the list to search in
* @param fromIndex list browsing starting point
* @return the graph node containing the point given in parameter, null otherwise
*
* @see org.eclipse.tracecompass.tmf.ui.views.uml2sd.core.GraphNode#getNodeFromListAt(int, int, java.util.List, int)
*/
@Override
protected GraphNode getNodeFromListAt(int x, int y, List<GraphNode> list, int fromIndex) {
if (list == null) {
return null;
}
for (int i = fromIndex; i < list.size(); i++) {
GraphNode node = list.get(i);
// only lifeline list is x ordered
// Stop browsing the list if the node is outside the visible area
// all others nodes will be not visible
if ((node instanceof Lifeline) && (node.getX() > fVisibleAreaX + fVisibleAreaWidth)) {
break;
}
if (node.getHeight() < 0) {
if (node.getY() + node.getHeight() > fVisibleAreaY + fVisibleAreaHeight) {
break;
}
} else {
if (node.getY() > fVisibleAreaY + fVisibleAreaHeight) {
break;
}
}
if (node.contains(x, y)) {
return node;
}
}
return null;
}
/**
* Draw the Frame rectangle
*
* @param context the context to draw to
*/
protected void drawFrame(IGC context) {
ISDPreferences pref = SDViewPref.getInstance();
context.setBackground(pref.getBackGroundColor(ISDPreferences.PREF_FRAME));
context.setForeground(pref.getForeGroundColor(ISDPreferences.PREF_FRAME));
int x = getX();
int y = getY();
int w = getWidth();
int h = getHeight();
// Draw the frame main rectangle
context.fillRectangle(x, y, w, h);
context.drawRectangle(x, y, w, h);
context.setBackground(pref.getBackGroundColor(ISDPreferences.PREF_FRAME_NAME));
context.setForeground(pref.getForeGroundColor(ISDPreferences.PREF_FRAME_NAME));
context.setFont(pref.getFont(ISDPreferences.PREF_FRAME_NAME));
int nameWidth = context.textExtent(getName()) + 2 * Metrics.FRAME_NAME_V_MARGIN;
int nameHeight = Metrics.getFrameFontHeigth() + +Metrics.FRAME_NAME_H_MARGIN * 2;
// Draw the frame name area
if (nameWidth > w) {
nameWidth = w;
}
int[] points = { x, y, x + nameWidth, y, x + nameWidth, y - 11 + nameHeight, x - 11 + nameWidth, y + nameHeight, x, y + nameHeight, x, y + nameHeight };
context.fillPolygon(points);
context.drawPolygon(points);
context.drawLine(x, y, x, y + nameHeight);
context.setForeground(pref.getFontColor(ISDPreferences.PREF_FRAME_NAME));
context.drawTextTruncatedCentred(getName(), x, y, nameWidth - 11, nameHeight, false);
context.setBackground(pref.getBackGroundColor(ISDPreferences.PREF_FRAME));
context.setForeground(pref.getForeGroundColor(ISDPreferences.PREF_FRAME));
}
@Override
public void draw(IGC context) {
draw(context, true);
}
/**
* Draws the Frame on the given context.<br>
* This method start width GraphNodes ordering if needed.<br>
* After, depending on the visible area, only visible GraphNodes are drawn.<br>
*
* @param context the context to draw to
* @param drawFrame indicate if the frame rectangle need to be redrawn
* @see org.eclipse.tracecompass.tmf.ui.views.uml2sd.core.GraphNode#draw(IGC)
*/
protected void draw(IGC context, boolean drawFrame) {
fVisibleAreaHeight = context.getVisibleHeight();
fVisibleAreaWidth = context.getVisibleWidth();
fVisibleAreaX = context.getContentsX();
fVisibleAreaY = context.getContentsY();
if (fForceEventOccurrenceSpacing >= 0) {
Metrics.setForcedEventSpacing(fForceEventOccurrenceSpacing);
} else {
Metrics.setForcedEventSpacing(-1);
}
super.drawChildenNodes(context);
}
/**
* Sets the event occurrence spacing (-1 for none)
*
* @param space A spacing to set.
*/
public void forceEventOccurrenceSpacing(int space) {
fForceEventOccurrenceSpacing = space;
}
/**
* Return the X coordinates of the frame visible area
*
* @return the X coordinates of the frame visible area
*/
public int getVisibleAreaX() {
return fVisibleAreaX;
}
/**
* Return the frame visible area width
*
* @return the frame visible area width
*/
public int getVisibleAreaWidth() {
return fVisibleAreaWidth;
}
/**
* Return the frame visible area height
*
* @return the frame visible area height
*/
public int getVisibleAreaHeight() {
return fVisibleAreaHeight;
}
/**
* Return the X coordinates of the frame visible area
*
* @return the X coordinates of the frame visible area
*/
public int getVisibleAreaY() {
return fVisibleAreaY;
}
/**
* Return the minimum time stored in the frame taking all GraphNodes into account
*
* @return the minimum GraphNode time
*/
public ITmfTimestamp getMinTime() {
if (fLastExternalTimePref != SDViewPref.getInstance().excludeExternalTime()) {
fLastExternalTimePref = SDViewPref.getInstance().excludeExternalTime();
setComputeMinMax(true);
}
if ((fComputeMinMax) && (!fCustomMinMax)) {
computeMinMax();
setComputeMinMax(false);
}
return fMinTime;
}
/**
* Set the minimum timestamp of the frame.
*
* @param min
* The minimum timestamp
*/
public void setMin(ITmfTimestamp min) {
fMinTime = min;
fCustomMinMax = true;
}
/**
* Set the maximum timestamp of the frame.
*
* @param max
* The maximum timestamp
*/
public void setMax(ITmfTimestamp max) {
fMaxTime = max;
fCustomMinMax = true;
}
/**
* Reset min/max timestamp values to the default ones.
*/
public void resetCustomMinMax() {
fCustomMinMax = false;
setComputeMinMax(true);
}
/**
* Return the maximum time stored in the frame taking all GraphNodes into account
*
* @return the maximum GraphNode time
*/
public ITmfTimestamp getMaxTime() {
if (fLastExternalTimePref != SDViewPref.getInstance().excludeExternalTime()) {
fLastExternalTimePref = SDViewPref.getInstance().excludeExternalTime();
setComputeMinMax(true);
}
if (fComputeMinMax) {
computeMinMax();
setComputeMinMax(false);
}
return fMaxTime;
}
/**
* Computes the minimum and maximum time between consecutive messages within the frame.
*/
protected void computeMaxMinTime() {
if (!fInitSDMin) {
return;
}
List<SDTimeEvent> timeArray = buildTimeArray();
if ((timeArray == null) || timeArray.isEmpty()) {
return;
}
for (int i = 0; i < timeArray.size(); i++) {
SDTimeEvent m = timeArray.get(i);
if (m.getTime().compareTo(fMaxSDTime) > 0) {
fMaxSDTime = m.getTime();
}
if ((m.getTime().compareTo(fMinSDTime) < 0) || fInitSDMin) {
fMinSDTime = m.getTime();
fInitSDMin = false;
}
}
}
/**
* Returns the minimum time between consecutive messages.
*
* @return the minimum time between consecutive messages
*/
public ITmfTimestamp getSDMinTime() {
computeMaxMinTime();
return fMinSDTime;
}
/**
* Returns the maximum time between consecutive messages.
*
* @return the maximum time between consecutive messages
*/
public ITmfTimestamp getSDMaxTime() {
computeMaxMinTime();
return fMaxSDTime;
}
/**
* Browse all the GraphNode to compute the min and max times store in the Frame
*/
protected void computeMinMax() {
List<SDTimeEvent> timeArray = buildTimeArray();
if ((timeArray == null) || timeArray.isEmpty()) {
return;
}
for (int i = 0; i < timeArray.size() - 1; i++) {
SDTimeEvent m1 = timeArray.get(i);
SDTimeEvent m2 = timeArray.get(i + 1);
updateMinMax(m1, m2);
}
}
/**
* Updates the minimum and maximum time between consecutive message within the frame based on the given values.
*
* @param m1 A first SD time event.
* @param m2 A second SD time event.
*/
protected void updateMinMax(SDTimeEvent m1, SDTimeEvent m2) {
ITmfTimestamp delta = m2.getTime().getDelta(m1.getTime());
if (fComputeMinMax) {
fMinTime = delta;
if (fMinTime.compareTo(TmfTimestamp.ZERO) < 0) {
fMinTime = TmfTimestamp.create(0, m1.getTime().getScale());
}
fMaxTime = fMinTime;
setComputeMinMax(false);
}
if ((delta.compareTo(fMinTime) < 0) && (delta.compareTo(TmfTimestamp.ZERO) > 0)) {
fMinTime = delta;
}
if ((delta.compareTo(fMaxTime) > 0) && (delta.compareTo(TmfTimestamp.ZERO) > 0)) {
fMaxTime = delta;
}
}
/**
* Builds the time array based on the list of graph nodes.
*
* @return the time array else empty list.
*/
protected List<SDTimeEvent> buildTimeArray() {
if (!hasChildren()) {
return new ArrayList<>();
}
Iterator<String> it = getForwardSortMap().keySet().iterator();
List<SDTimeEvent> timeArray = new ArrayList<>();
while (it.hasNext()) {
String nodeType = it.next();
List<GraphNode> list = checkNotNull(getNodeMap().get(nodeType));
for (int i = 0; i < list.size(); i++) {
Object timedNode = list.get(i);
if ((timedNode instanceof ITimeRange) && ((ITimeRange) timedNode).hasTimeInfo()) {
int event = list.get(i).getStartOccurrence();
ITmfTimestamp time = ((ITimeRange) list.get(i)).getStartTime();
SDTimeEvent f = new SDTimeEvent(time, event, (ITimeRange) list.get(i));
timeArray.add(f);
if (event != list.get(i).getEndOccurrence()) {
event = (list.get(i)).getEndOccurrence();
time = ((ITimeRange) list.get(i)).getEndTime();
f = new SDTimeEvent(time, event, (ITimeRange) list.get(i));
timeArray.add(f);
}
}
}
}
return timeArray;
}
@Override
public String getArrayId() {
return null;
}
@Override
public boolean contains(int x, int y) {
return false;
}
/**
* @return true if frame has time info else false
*/
public boolean hasTimeInfo() {
return fHasTimeInfo;
}
/**
* Sets the flag whether the frame has time info or not
*
* @param hasTimeInfo
* true if frame has time info else false
*/
public void setHasTimeInfo(boolean hasTimeInfo) {
fHasTimeInfo = hasTimeInfo;
}
/**
* Sets the flag for minimum and maximum computation.
*
* @param computeMinMax
* true if mininum and maximum time needs to be calculated else false
*/
public void setComputeMinMax(boolean computeMinMax) {
fComputeMinMax = computeMinMax;
}
/**
* Sets the initialization flag for SD minimum.
*
* @param initSDMin
* the flag to set
*/
public void setInitSDMin(boolean initSDMin) {
fInitSDMin = initSDMin;
}
}