blob: fcffbc33a4ecd1c70e0af767b3dac1b46bbb13ad [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2007, 2019 Intel Corporation, Ericsson
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Intel Corporation - Initial API and implementation
* Vitaly A. Provodin, Intel - Initial API and implementation
* Alvaro Sanchez-Leon - Updated for TMF
* Patrick Tasse - Refactoring
*****************************************************************************/
package org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Control;
import org.eclipse.tracecompass.internal.tmf.ui.Messages;
import org.eclipse.tracecompass.tmf.core.TmfStrings;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.ui.viewers.TmfAbstractToolTipHandler;
import org.eclipse.tracecompass.tmf.ui.views.FormatTimeUtils;
import org.eclipse.tracecompass.tmf.ui.views.FormatTimeUtils.Resolution;
import org.eclipse.tracecompass.tmf.ui.views.FormatTimeUtils.TimeFormat;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphPresentationProvider;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ILinkEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.NullTimeEvent;
/**
* Handler for the tool tips in the generic time graph view.
*
* @version 1.0
* @author Alvaro Sanchez-Leon
* @author Patrick Tasse
*/
public class TimeGraphTooltipHandler extends TmfAbstractToolTipHandler {
private static final String ROW_CATEGORY = "Row"; //$NON-NLS-1$
private static final String MARKER_CATEGORY_PREFIX = "Marker "; //$NON-NLS-1$
private static final String LINK_CATEGORY = "Link"; //$NON-NLS-1$
private static final String STATE_CATEGORY = "State"; //$NON-NLS-1$
private static final String MARKER_OFFSET = " "; //$NON-NLS-1$
private static final String MIN_STRING = "< 0.01%"; //$NON-NLS-1$
private static final double MIN_RATIO = 0.0001;
private static final String MAX_STRING = "> 1000%"; //$NON-NLS-1$
private static final int MAX_RATIO = 10;
private static final int HOVER_MAX_DIST = 10;
private ITimeDataProvider fTimeDataProvider;
private ITimeGraphPresentationProvider fTimeGraphProvider = null;
/**
* Standard constructor
*
* @param graphProv
* The presentation provider
* @param timeProv
* The time provider
*/
public TimeGraphTooltipHandler(ITimeGraphPresentationProvider graphProv,
ITimeDataProvider timeProv) {
fTimeGraphProvider = graphProv;
fTimeDataProvider = timeProv;
}
/**
* Set the time data provider
*
* @param timeDataProvider
* The time data provider
*/
public void setTimeProvider(ITimeDataProvider timeDataProvider) {
fTimeDataProvider = timeDataProvider;
}
@Override
protected void fill(Control control, MouseEvent event, Point pt) {
TimeGraphControl timeGraphControl = (TimeGraphControl) control;
if ((event.stateMask & SWT.MODIFIER_MASK) != SWT.SHIFT) {
ILinkEvent linkEvent = timeGraphControl.getArrow(pt);
if (linkEvent != null) {
fillValues(linkEvent);
}
}
if (getTipComposite().getChildren().length == 0) {
ITimeGraphEntry entry = timeGraphControl.getEntry(pt);
if (entry == null) {
return;
}
fillValues(pt, timeGraphControl, entry);
Iterable<IMarkerEvent> markers = getMarkers(timeGraphControl, entry, pt);
int i = 0;
for (IMarkerEvent marker : markers) {
fillValues(MARKER_CATEGORY_PREFIX + i, marker);
i++;
}
}
}
/**
* Get a list of markers intersecting a point
* @param timeGraphControl
*
* @param entry
* the entry to observe, can be null
* @param pt
* the point to look at
* @return the return value
*/
private static Iterable<IMarkerEvent> getMarkers(TimeGraphControl timeGraphControl, ITimeGraphEntry entry, Point pt) {
List<IMarkerEvent> markers = timeGraphControl.getMarkers();
List<IMarkerEvent> retVal = new ArrayList<>();
if (markers != null) {
// Include markers up to 5 pixels left and right of cursor
int fuzz = 5;
long fuzzStart = timeGraphControl.getTimeAtX(pt.x - fuzz);
long fuzzEnd = timeGraphControl.getTimeAtX(pt.x + fuzz);
for (IMarkerEvent marker : markers) {
if (Objects.equals(marker.getEntry(), entry)) {
long duration = marker.getDuration();
long startTime = marker.getTime();
long endTime = startTime + duration;
if ((startTime <= fuzzEnd) && (endTime >= fuzzStart)) {
retVal.add(marker);
}
}
}
}
return retVal;
}
private void fillValues(String itemName, IMarkerEvent marker) {
Map<String, String> toolTips = fTimeGraphProvider.getEventHoverToolTipInfo(marker);
String category = marker.getCategory();
String label = marker.getLabel();
addItem(itemName, category == null ? Messages.TimeGraphTooltipHandler_DefaultMarkerName : category, label == null ? "" : label); //$NON-NLS-1$
long timestamp = marker.getTime();
long duration = marker.getDuration();
if (duration == 0) {
addItem(ToolTipString.fromString(itemName), ToolTipString.fromString(MARKER_OFFSET + Messages.TimeGraphTooltipHandler_Timestamp), ToolTipString.fromTimestamp(TmfTimestamp.fromNanos(timestamp).toString(), timestamp));
} else {
addItem(ToolTipString.fromString(itemName), ToolTipString.fromString(MARKER_OFFSET + TmfStrings.startTime()), ToolTipString.fromTimestamp(TmfTimestamp.fromNanos(timestamp).toString(), timestamp));
addItem(ToolTipString.fromString(itemName), ToolTipString.fromString(MARKER_OFFSET + TmfStrings.endTime()), ToolTipString.fromTimestamp(TmfTimestamp.fromNanos(timestamp + duration).toString(), timestamp + duration));
}
if (toolTips != null) {
for (Entry<String, String> tooltip : toolTips.entrySet()) {
addItem(itemName, MARKER_OFFSET + tooltip.getKey(), tooltip.getValue()); // $NON-NLS-1$
}
}
}
private void fillValues(Point pt, TimeGraphControl timeGraphControl, ITimeGraphEntry entry) {
if (entry == null) {
return;
}
if (entry.hasTimeEvents()) {
long currPixelTime = timeGraphControl.getTimeAtX(pt.x);
long nextPixelTime = timeGraphControl.getTimeAtX(pt.x + 1);
if (nextPixelTime == currPixelTime) {
nextPixelTime++;
}
ITimeEvent currEvent = Utils.findEvent(entry, currPixelTime, 0);
ITimeEvent nextEvent = Utils.findEvent(entry, currPixelTime, 1);
/*
* if there is no current event at the start of the current pixel
* range, or if the current event starts before the current pixel
* range, use the next event as long as it starts within the current
* pixel range
*/
if ((currEvent == null || currEvent.getTime() < currPixelTime) &&
(nextEvent != null && nextEvent.getTime() < nextPixelTime)) {
currEvent = nextEvent;
currPixelTime = nextEvent.getTime();
}
/*
* if there is still no current event, use the closest between the
* next and previous event, as long as they are within a distance
* threshold
*/
if (currEvent == null || currEvent instanceof NullTimeEvent) {
int nextDelta = Integer.MAX_VALUE;
int prevDelta = Integer.MAX_VALUE;
long nextTime = 0;
long prevTime = 0;
if (nextEvent != null && !(nextEvent instanceof NullTimeEvent)) {
nextTime = nextEvent.getTime();
nextDelta = Math.abs(timeGraphControl.getXForTime(nextTime) - pt.x);
}
ITimeEvent prevEvent = Utils.findEvent(entry, currPixelTime, -1);
if (prevEvent != null && !(prevEvent instanceof NullTimeEvent)) {
prevTime = prevEvent.getTime() + prevEvent.getDuration() - 1;
prevDelta = Math.abs(pt.x - timeGraphControl.getXForTime(prevTime));
}
if (nextDelta < HOVER_MAX_DIST && nextDelta <= prevDelta) {
currEvent = nextEvent;
currPixelTime = nextTime;
} else if (prevDelta < HOVER_MAX_DIST) {
currEvent = prevEvent;
currPixelTime = prevTime;
}
}
// state name
String stateTypeName = fTimeGraphProvider.getStateTypeName(entry);
String entryName = entry.getName();
if (stateTypeName == null) {
stateTypeName = fTimeGraphProvider.getStateTypeName();
}
if (!entryName.isEmpty()) {
addItem(ROW_CATEGORY, stateTypeName, entry.getName());
}
if (currEvent == null || currEvent instanceof NullTimeEvent) {
return;
}
// state
String state = fTimeGraphProvider.getEventName(currEvent);
if (state != null) {
addItem(STATE_CATEGORY, Messages.TmfTimeTipHandler_TRACE_STATE, state);
}
// This block receives a list of <String, String> values to
// be added to the tip table
Map<String, String> eventAddOns = fTimeGraphProvider.getEventHoverToolTipInfo(currEvent, currPixelTime);
if (eventAddOns != null) {
for (Entry<String, String> eventAddOn : eventAddOns.entrySet()) {
addItem(STATE_CATEGORY, eventAddOn.getKey(), eventAddOn.getValue());
}
}
if (fTimeGraphProvider.displayTimesInTooltip()) {
long eventStartTime = -1;
long eventDuration = -1;
long eventEndTime = -1;
eventStartTime = currEvent.getTime();
eventDuration = currEvent.getDuration();
if (eventDuration < 0 && nextEvent != null) {
eventEndTime = nextEvent.getTime();
eventDuration = eventEndTime - eventStartTime;
} else {
eventEndTime = eventStartTime + eventDuration;
}
Resolution res = Resolution.NANOSEC;
TimeFormat tf = fTimeDataProvider.getTimeFormat().convert();
String startTime = "?"; //$NON-NLS-1$
String duration = "?"; //$NON-NLS-1$
String endTime = "?"; //$NON-NLS-1$
if (fTimeDataProvider instanceof ITimeDataProviderConverter) {
ITimeDataProviderConverter tdp = (ITimeDataProviderConverter) fTimeDataProvider;
if (eventStartTime > -1) {
eventStartTime = tdp.convertTime(eventStartTime);
startTime = FormatTimeUtils.formatTime(eventStartTime, tf, res);
}
if (eventEndTime > -1) {
eventEndTime = tdp.convertTime(eventEndTime);
endTime = FormatTimeUtils.formatTime(eventEndTime, tf, res);
}
if (eventDuration > -1) {
duration = FormatTimeUtils.formatDelta(eventEndTime - eventStartTime, tf, res);
}
} else {
if (eventStartTime > -1) {
startTime = FormatTimeUtils.formatTime(eventStartTime, tf, res);
}
if (eventEndTime > -1) {
endTime = FormatTimeUtils.formatTime(eventEndTime, tf, res);
}
if (eventDuration > -1) {
duration = FormatTimeUtils.formatDelta(eventDuration, tf, res);
}
}
if (tf == TimeFormat.CALENDAR) {
addItem(STATE_CATEGORY, Messages.TmfTimeTipHandler_TRACE_DATE,
eventStartTime > -1 ? FormatTimeUtils.formatDate(eventStartTime) : "?"); //$NON-NLS-1$
}
if (eventDuration > 0) {
addItem(ToolTipString.fromString(STATE_CATEGORY), ToolTipString.fromString(Messages.TmfTimeTipHandler_TRACE_START_TIME), ToolTipString.fromTimestamp(startTime, eventStartTime));
addItem(ToolTipString.fromString(STATE_CATEGORY), ToolTipString.fromString(Messages.TmfTimeTipHandler_TRACE_STOP_TIME), ToolTipString.fromTimestamp(endTime, eventEndTime));
} else {
addItem(ToolTipString.fromString(STATE_CATEGORY), ToolTipString.fromString(Messages.TmfTimeTipHandler_TRACE_EVENT_TIME), ToolTipString.fromTimestamp(startTime, eventStartTime));
}
if (eventDuration > 0) {
addItem(STATE_CATEGORY, Messages.TmfTimeTipHandler_DURATION, duration);
long begin = fTimeDataProvider.getSelectionBegin();
long end = fTimeDataProvider.getSelectionEnd();
final long delta = Math.abs(end - begin);
final double durationRatio = (double) eventDuration / (double) delta;
if (delta > 0) {
String percentage;
if (durationRatio > MAX_RATIO) {
percentage = MAX_STRING;
} else if (durationRatio < MIN_RATIO) {
percentage = MIN_STRING;
} else {
percentage = String.format("%,.2f%%", durationRatio * 100.0); //$NON-NLS-1$
}
addItem(STATE_CATEGORY, Messages.TmfTimeTipHandler_PERCENT_OF_SELECTION, percentage);
}
}
}
}
}
private void fillValues(ILinkEvent linkEvent) {
addItem(LINK_CATEGORY, Messages.TmfTimeTipHandler_LINK_SOURCE, linkEvent.getEntry().getName());
addItem(LINK_CATEGORY, Messages.TmfTimeTipHandler_LINK_TARGET, linkEvent.getDestinationEntry().getName());
// This block receives a list of <String, String> values to be
// added to the tip table
Map<String, String> eventAddOns = fTimeGraphProvider.getEventHoverToolTipInfo(linkEvent);
if (eventAddOns != null) {
for (Entry<String, String> eventAddOn : eventAddOns.entrySet()) {
addItem(LINK_CATEGORY, eventAddOn.getKey(), eventAddOn.getValue());
}
}
if (fTimeGraphProvider.displayTimesInTooltip()) {
long sourceTime = linkEvent.getTime();
long duration = linkEvent.getDuration();
long targetTime = sourceTime + duration;
if (fTimeDataProvider instanceof ITimeDataProviderConverter) {
ITimeDataProviderConverter tdp = (ITimeDataProviderConverter) fTimeDataProvider;
sourceTime = tdp.convertTime(sourceTime);
targetTime = tdp.convertTime(targetTime);
duration = targetTime - sourceTime;
}
Resolution res = Resolution.NANOSEC;
TimeFormat tf = fTimeDataProvider.getTimeFormat().convert();
if (tf == TimeFormat.CALENDAR) {
addItem(LINK_CATEGORY, Messages.TmfTimeTipHandler_TRACE_DATE, FormatTimeUtils.formatDate(sourceTime));
}
if (duration > 0) {
addItem(ToolTipString.fromString(LINK_CATEGORY), ToolTipString.fromString(Messages.TmfTimeTipHandler_LINK_SOURCE_TIME), ToolTipString.fromTimestamp(FormatTimeUtils.formatTime(sourceTime, tf, res), sourceTime));
addItem(ToolTipString.fromString(LINK_CATEGORY), ToolTipString.fromString(Messages.TmfTimeTipHandler_LINK_TARGET_TIME), ToolTipString.fromTimestamp(FormatTimeUtils.formatTime(targetTime, tf, res), targetTime));
addItem(LINK_CATEGORY, Messages.TmfTimeTipHandler_DURATION, FormatTimeUtils.formatDelta(duration, tf, res));
} else {
addItem(ToolTipString.fromString(LINK_CATEGORY), ToolTipString.fromString(Messages.TmfTimeTipHandler_LINK_TIME), ToolTipString.fromTimestamp(FormatTimeUtils.formatTime(sourceTime, tf, res), sourceTime));
}
}
}
}