blob: 752a3effbf6dc6df755a5bcbc3f34dc2d1a0b638 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2007, 2019 Intel Corporation and others
*
* 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
* Ruslan A. Scherbakov, Intel - Initial API and implementation
* Alvaro Sanchez-Leon, Ericsson - Updated for TMF
* Patrick Tasse, Ericsson - Refactoring
* Geneviève Bastien, École Polytechnique de Montréal - Move code to
* provide base classes for time graph view
* Add display of links between items
* Xavier Raynaud, Kalray - Code optimization
* Generoso Pagano, Inria - Support for drag selection listeners
*****************************************************************************/
package org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MenuDetectEvent;
import org.eclipse.swt.events.MenuDetectListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.events.TypedEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.tracecompass.common.core.math.SaturatedArithmetic;
import org.eclipse.tracecompass.internal.provisional.tmf.ui.widgets.timegraph.IStylePresentationProvider;
import org.eclipse.tracecompass.internal.tmf.ui.util.LineClipper;
import org.eclipse.tracecompass.internal.tmf.ui.util.StylePropertiesUtils;
import org.eclipse.tracecompass.internal.tmf.ui.util.SymbolHelper;
import org.eclipse.tracecompass.internal.tmf.ui.widgets.timegraph.model.TimeGraphLineEntry;
import org.eclipse.tracecompass.internal.tmf.ui.widgets.timegraph.model.TimeLineEvent;
import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle;
import org.eclipse.tracecompass.tmf.core.model.StyleProperties;
import org.eclipse.tracecompass.tmf.core.model.StyleProperties.BorderStyle;
import org.eclipse.tracecompass.tmf.core.model.StyleProperties.SymbolType;
import org.eclipse.tracecompass.tmf.core.model.timegraph.IFilterProperty;
import org.eclipse.tracecompass.tmf.core.presentation.RGBAColor;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.ui.colors.RGBAUtil;
import org.eclipse.tracecompass.tmf.ui.model.StyleManager;
import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo;
import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal;
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.views.ITmfTimeAligned;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphColorListener;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphPresentationProvider;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphTimeListener;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphTreeListener;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphViewerFilterListener;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.StateItem;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphTimeEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphTreeExpansionEvent;
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.ITimeEventStyleStrings;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.MarkerEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry;
import com.google.common.collect.Iterables;
/**
* Time graph control implementation
*
* @author Alvaro Sanchez-Leon
* @author Patrick Tasse
*/
public class TimeGraphControl extends TimeGraphBaseControl
implements FocusListener, KeyListener, MouseMoveListener, MouseListener,
MouseWheelListener, MouseTrackListener, TraverseListener, ISelectionProvider,
MenuDetectListener, ITmfTimeGraphDrawingHelper, ITimeGraphColorListener, Listener {
/**
* Default state width ratio
* @since 4.1
*/
public static final float DEFAULT_STATE_WIDTH = 1.0f;
/**
* Default link width ratio
* @since 4.1
*/
public static final float DEFAULT_LINK_WIDTH = 0.1f;
/**
* Constant indicating that all levels of the time graph should be expanded
*/
public static final int ALL_LEVELS = AbstractTreeViewer.ALL_LEVELS;
private static final @NonNull String HIGHLIGHTED_BOUND_COLOR = "#ff3300"; //$NON-NLS-1$
private static final int HIGHLIGHTED_BOUND_WIDTH = 4;
private static final int DRAG_MARGIN = 5;
private static final int DRAG_NONE = 0;
private static final int DRAG_TRACE_ITEM = 1;
private static final int DRAG_SPLIT_LINE = 2;
private static final int DRAG_ZOOM = 3;
private static final int DRAG_SELECTION = 4;
/**
* Get item height from provider
*/
private static final int CUSTOM_ITEM_HEIGHT = -1;
private static final int MIN_MIDLINE_HEIGHT = 3;
private static final double ZOOM_FACTOR = 1.5;
private static final double ZOOM_IN_FACTOR = 0.8;
private static final double ZOOM_OUT_FACTOR = 1.25;
private static final int SNAP_WIDTH = 3;
private static final int ARROW_HOVER_MAX_DIST = 5;
/**
* base to height ratio
*/
private static final double ARROW_RATIO = Math.sqrt(3) / 2;
private static final int NO_STATUS = -1;
private static final int STATUS_WITHOUT_CURSOR_TIME = -2;
private static final int MAX_LABEL_LENGTH = 256;
/** points per inch */
private static final int PPI = 72;
private static final int DPI = 96;
private static final int OPAQUE = 255;
private static final int VERTICAL_ZOOM_DELAY = 400;
private static final String PREFERRED_WIDTH = "width"; //$NON-NLS-1$
private static final ColorRegistry COLOR_REGISTRY = new ColorRegistry();
/**
* The alpha color component value for dimmed events
*
* @since 4.0
*/
private static final int DIMMED_ALPHA_COEFFICIENT = 4;
/** Resource manager */
private LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources());
/** Color map for event types */
private Color[] fEventColorMap = null;
private ITimeDataProvider fTimeProvider;
private ITableLabelProvider fLabelProvider;
private IStatusLineManager fStatusLineManager = null;
private Tree fTree = null;
private TimeGraphScale fTimeGraphScale = null;
private boolean fIsInFocus = false;
private boolean fMouseOverSplitLine = false;
private int fGlobalItemHeight = CUSTOM_ITEM_HEIGHT;
private int fHeightAdjustment = 0;
private int fMaxItemHeight = 0;
private Map<Integer, Font> fFonts = new HashMap<>();
private boolean fBlendSubPixelEvents = false;
private int fMinimumItemWidth = 0;
private int fTopIndex = 0;
private int fDragState = DRAG_NONE;
private boolean fDragBeginMarker = false;
private int fDragButton;
private int fDragX0 = 0;
private int fDragX = 0;
private ITimeGraphEntry fDragEntry = null;
private boolean fHasNamespaceFocus = false;
/**
* Used to preserve accuracy of modified selection
*/
private long fDragTime0 = 0;
private int fIdealNameSpace = 0;
private boolean fAutoResizeColumns = true;
private long fTime0bak;
private long fTime1bak;
private ITimeGraphPresentationProvider fTimeGraphProvider = null;
private ItemData fItemData = null;
private List<IMarkerEvent> fMarkers = null;
private boolean fMarkersVisible = true;
private List<SelectionListener> fSelectionListeners;
private List<ITimeGraphTimeListener> fDragSelectionListeners;
private final List<ISelectionChangedListener> fSelectionChangedListeners = new ArrayList<>();
private final List<ITimeGraphTreeListener> fTreeListeners = new ArrayList<>();
private final List<ITimeGraphViewerFilterListener> fViewerFilterListeners = new ArrayList<>();
private final List<MenuDetectListener> fTimeGraphEntryMenuListeners = new ArrayList<>();
private final List<MenuDetectListener> fTimeEventMenuListeners = new ArrayList<>();
private final Cursor fDragCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_HAND);
private final Cursor fResizeCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_IBEAM);
private final Cursor fWaitCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_WAIT);
private final Cursor fZoomCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_SIZEWE);
private final Set<@NonNull ViewerFilter> fFilters = new LinkedHashSet<>();
private MenuDetectEvent fPendingMenuDetectEvent = null;
private boolean fGridLinesVisible = true;
private Color fGridLineColor = Display.getDefault().getSystemColor(SWT.COLOR_GRAY);
private boolean fLabelsVisible = true;
private boolean fMidLinesVisible = true;
private boolean fHideArrows = false;
private int fAutoExpandLevel = ALL_LEVELS;
private Entry<ITimeGraphEntry, Integer> fVerticalZoomAlignEntry = null;
private long fVerticalZoomAlignTime = 0;
private int fBorderWidth = 0;
private int fHeaderHeight = 0;
private int fLastTransparentX = -1;
private boolean fFilterActive;
private boolean fHasSavedFilters;
/**
* Standard constructor
*
* @param parent
* The parent composite object
* @param colors
* The color scheme to use
*/
public TimeGraphControl(Composite parent, TimeGraphColorScheme colors) {
super(parent, colors, SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED);
fItemData = new ItemData();
addFocusListener(this);
addMouseListener(this);
addMouseMoveListener(this);
addMouseTrackListener(this);
addMouseWheelListener(this);
addTraverseListener(this);
addKeyListener(this);
addMenuDetectListener(this);
addListener(SWT.MouseWheel, this);
addDisposeListener((e) -> {
fResourceManager.dispose();
for (Font font : fFonts.values()) {
font.dispose();
}
});
}
/**
* Sets the timegraph provider used by this timegraph viewer.
*
* @param timeGraphProvider
* the timegraph provider
*/
public void setTimeGraphProvider(ITimeGraphPresentationProvider timeGraphProvider) {
fTimeGraphProvider = timeGraphProvider;
timeGraphProvider.setDrawingHelper(this);
timeGraphProvider.addColorListener(this);
StateItem[] stateItems = fTimeGraphProvider.getStateTable();
colorSettingsChanged(stateItems);
}
/**
* Gets the timegraph provider used by this timegraph viewer.
*
* @return the timegraph provider, or <code>null</code> if not set.
*/
public ITimeGraphPresentationProvider getTimeGraphProvider() {
return fTimeGraphProvider;
}
/**
* Gets the time data provider used by this viewer.
*
* @return The time data provider, or <code>null</code> if not set
* @since 2.1
*/
public ITimeDataProvider getTimeDataProvider() {
return fTimeProvider;
}
/**
* Gets the color map used by this timegraph viewer.
*
* @return a color map, or <code>null</code> if not set.
*/
public Color[] getEventColorMap() {
return fEventColorMap;
}
/**
* Assign the given time provider
*
* @param timeProvider
* The time provider
*/
public void setTimeProvider(ITimeDataProvider timeProvider) {
fTimeProvider = timeProvider;
redraw();
}
/**
* Set the label provider for the name space
*
* @param labelProvider
* The label provider
* @since 2.3
*/
public void setLabelProvider(ITableLabelProvider labelProvider) {
fLabelProvider = labelProvider;
redraw();
}
/**
* Get the label provider for the name space
*
* @return The label provider
* @since 2.3
*/
public ITableLabelProvider getLabelProvider() {
return fLabelProvider;
}
/**
* Assign the status line manager
*
* @param statusLineManager
* The status line manager, or null to disable status line messages
*/
public void setStatusLineManager(IStatusLineManager statusLineManager) {
if (fStatusLineManager != null && statusLineManager == null) {
fStatusLineManager.setMessage(""); //$NON-NLS-1$
}
fStatusLineManager = statusLineManager;
}
/**
* Assign the tree that represents the name space header
*
* @param tree
* The tree
* @since 2.3
*/
public void setTree(Tree tree) {
fTree = tree;
}
/**
* Returns the tree control associated with this time graph control. The tree is
* only used for column handling of the name space and contains no tree items.
*
* @return the tree control
* @since 2.3
*/
public Tree getTree() {
return fTree;
}
/**
* Sets the columns for this time graph control's name space.
*
* @param columnNames
* the column names
* @since 2.3
*/
public void setColumns(String[] columnNames) {
Tree tree = getTree();
for (TreeColumn column : tree.getColumns()) {
column.dispose();
}
ControlListener controlListener = new ControlListener() {
@Override
public void controlResized(ControlEvent e) {
if (fAutoResizeColumns && ((TreeColumn) e.widget).getWidth() < (Integer) e.widget.getData(PREFERRED_WIDTH)) {
fAutoResizeColumns = false;
}
redraw();
}
@Override
public void controlMoved(ControlEvent e) {
redraw();
}
};
for (String columnName : columnNames) {
TreeColumn column = new TreeColumn(tree, SWT.LEFT);
column.setMoveable(true);
column.setText(columnName);
column.pack();
column.setData(PREFERRED_WIDTH, column.getWidth());
column.addControlListener(controlListener);
}
}
/**
* Assign the time graph scale
*
* @param timeGraphScale
* The time graph scale
*/
public void setTimeGraphScale(TimeGraphScale timeGraphScale) {
fTimeGraphScale = timeGraphScale;
}
/**
* Add a selection listener
*
* @param listener
* The listener to add
*/
public void addSelectionListener(SelectionListener listener) {
if (listener == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
if (null == fSelectionListeners) {
fSelectionListeners = new ArrayList<>();
}
fSelectionListeners.add(listener);
}
/**
* Remove a selection listener
*
* @param listener
* The listener to remove
*/
public void removeSelectionListener(SelectionListener listener) {
if (null != fSelectionListeners) {
fSelectionListeners.remove(listener);
}
}
/**
* Selection changed callback
*/
public void fireSelectionChanged() {
if (null != fSelectionListeners) {
Iterator<SelectionListener> it = fSelectionListeners.iterator();
while (it.hasNext()) {
SelectionListener listener = it.next();
listener.widgetSelected(null);
}
}
if (null != fSelectionChangedListeners) {
for (ISelectionChangedListener listener : fSelectionChangedListeners) {
listener.selectionChanged(new SelectionChangedEvent(this, getSelection()));
}
}
}
/**
* Default selection callback
*/
public void fireDefaultSelection() {
if (null != fSelectionListeners) {
Iterator<SelectionListener> it = fSelectionListeners.iterator();
while (it.hasNext()) {
SelectionListener listener = it.next();
listener.widgetDefaultSelected(null);
}
}
}
/**
* Add a drag selection listener
*
* @param listener
* The listener to add
*/
public void addDragSelectionListener(ITimeGraphTimeListener listener) {
if (listener == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
if (null == fDragSelectionListeners) {
fDragSelectionListeners = new ArrayList<>();
}
fDragSelectionListeners.add(listener);
}
/**
* Remove a drag selection listener
*
* @param listener
* The listener to remove
*/
public void removeDragSelectionListener(ITimeGraphTimeListener listener) {
if (null != fDragSelectionListeners) {
fDragSelectionListeners.remove(listener);
}
}
/**
* Drag Selection changed callback
*
* @param start
* Time interval start
* @param end
* Time interval end
*/
public void fireDragSelectionChanged(long start, long end) {
// check for backward intervals
long beginTime, endTime;
if (start > end) {
beginTime = end;
endTime = start;
} else {
beginTime = start;
endTime = end;
}
// call the listeners
if (null != fDragSelectionListeners) {
Iterator<ITimeGraphTimeListener> it = fDragSelectionListeners.iterator();
while (it.hasNext()) {
ITimeGraphTimeListener listener = it.next();
listener.timeSelected(new TimeGraphTimeEvent(this, beginTime, endTime));
}
}
}
/**
* Get the traces in the model
*
* @return The array of traces
*/
public ITimeGraphEntry[] getTraces() {
return fItemData.getEntries();
}
/**
* Refresh the data for the thing
*/
public void refreshData() {
fItemData.refreshData();
redraw();
}
/**
* Refresh data for the given traces
*
* @param traces
* The traces to refresh
*/
public void refreshData(ITimeGraphEntry[] traces) {
fItemData.refreshData(traces);
redraw();
}
/**
* Refresh the links (arrows) of this widget
*
* @param events
* The link event list
*/
public void refreshArrows(List<ILinkEvent> events) {
fItemData.refreshArrows(events);
}
/**
* Get the links (arrows) of this widget
*
* @return The unmodifiable link event list
*
* @since 1.1
*/
public List<ILinkEvent> getArrows() {
return Collections.unmodifiableList(fItemData.fLinks);
}
boolean ensureVisibleItem(int idx, boolean redraw) {
boolean changed = false;
int index = idx;
if (index < 0) {
for (index = 0; index < fItemData.fExpandedItems.length; index++) {
if (fItemData.fExpandedItems[index].fSelected) {
break;
}
}
}
if (index >= fItemData.fExpandedItems.length) {
return changed;
}
if (index < fTopIndex) {
setTopIndex(index);
if (redraw) {
redraw();
}
changed = true;
} else {
int page = countPerPage();
if (index >= fTopIndex + page) {
setTopIndex(index - page + 1);
if (redraw) {
redraw();
}
changed = true;
}
}
return changed;
}
/**
* Assign the given index as the top one
*
* @param idx
* The index
*/
public void setTopIndex(int idx) {
int index = Math.min(idx, fItemData.fExpandedItems.length - countPerPage());
index = Math.max(0, index);
fTopIndex = index;
redraw();
}
/**
* Set the top index so that the requested element is at the specified position.
*
* @param entry
* the time graph entry to be positioned
* @param y
* the requested y-coordinate
* @since 2.0
*/
public void setElementPosition(ITimeGraphEntry entry, int y) {
Item item = fItemData.fItemMap.get(entry);
if (item == null || item.fExpandedIndex == -1) {
return;
}
int index = item.fExpandedIndex;
Rectangle itemRect = getItemRect(getClientArea(), index);
int delta = itemRect.y + itemRect.height - y;
int topIndex = getItemIndexAtY(delta);
if (topIndex != -1) {
setTopIndex(topIndex);
} else {
if (delta < 0) {
setTopIndex(0);
} else {
setTopIndex(getExpandedElementCount());
}
}
}
/**
* Sets the auto-expand level to be used for new entries discovered when calling
* {@link #refreshData()} or {@link #refreshData(ITimeGraphEntry[])} . The value
* 0 means that there is no auto-expand; 1 means that top-level entries are
* expanded, but not their children; 2 means that top-level entries are
* expanded, and their children, but not grand-children; and so on.
* <p>
* The value {@link #ALL_LEVELS} means that all subtrees should be expanded.
* </p>
*
* @param level
* non-negative level, or <code>ALL_LEVELS</code> to expand all
* levels of the tree
*/
public void setAutoExpandLevel(int level) {
fAutoExpandLevel = level;
}
/**
* Returns the auto-expand level.
*
* @return non-negative level, or <code>ALL_LEVELS</code> if all levels of the
* tree are expanded automatically
* @see #setAutoExpandLevel
*/
public int getAutoExpandLevel() {
return fAutoExpandLevel;
}
/**
* Get the expanded state of a given entry.
*
* @param entry
* The entry
* @return true if the entry is expanded, false if collapsed
* @since 1.1
*/
public boolean getExpandedState(ITimeGraphEntry entry) {
Item item = fItemData.fItemMap.get(entry);
return (item != null ? item.fExpanded : false);
}
/**
* Set the expanded state of a given entry
*
* @param entry
* The entry
* @param expanded
* True if expanded, false if collapsed
*/
public void setExpandedState(ITimeGraphEntry entry, boolean expanded) {
Item item = fItemData.findItem(entry);
if (item != null && item.fExpanded != expanded) {
item.fExpanded = expanded;
fItemData.updateExpandedItems();
redraw();
}
}
/**
* Set the expanded state of a given list of entries
*
* @param entries
* The list of entries
* @param expanded
* True if expanded, false if collapsed
* @since 3.1
*/
public void setExpandedState(Iterable<ITimeGraphEntry> entries, boolean expanded) {
for (ITimeGraphEntry entry : entries) {
Item item = fItemData.findItem(entry);
if (item != null) {
item.fExpanded = expanded;
}
}
fItemData.updateExpandedItems();
redraw();
}
/**
* Set the expanded state of a given entry to certain relative level. It will
* call fireTreeEvent() for each changed entry. At the end it will call
* redraw().
*
* @param entry
* The entry
* @param level
* level to expand to or negative for all levels
* @param expanded
* True if expanded, false if collapsed
*/
private void setExpandedState(ITimeGraphEntry entry, int level, boolean expanded) {
setExpandedStateInt(entry, level, expanded);
redraw();
}
/**
* Set the expanded state of a given entry and its children to the first level
* that has one collapsed entry.
*
* @param entry
* The entry
*/
private void setExpandedStateLevel(ITimeGraphEntry entry) {
int level = findExpandedLevel(entry);
if (level >= 0) {
setExpandedStateInt(entry, level, true);
redraw();
}
}
/*
* Inner class for finding relative level with at least one collapsed entry.
*/
private class SearchNode {
SearchNode(ITimeGraphEntry e, int l) {
entry = e;
level = l;
}
ITimeGraphEntry entry;
int level;
}
/**
* Finds the relative level with at least one collapsed entry.
*
* @param entry
* the start entry
* @return the found level or -1 if all levels are already expanded.
*/
private int findExpandedLevel(ITimeGraphEntry entry) {
Queue<SearchNode> queue = new LinkedList<>();
SearchNode root = new SearchNode(entry, 0);
SearchNode node = root;
queue.add(root);
while (!queue.isEmpty()) {
node = queue.remove();
if (node.entry.hasChildren() && !getExpandedState(node.entry)) {
return node.level;
}
for (ITimeGraphEntry e : node.entry.getChildren()) {
if (e.hasChildren()) {
SearchNode n = new SearchNode(e, node.level + 1);
queue.add(n);
}
}
}
return -1;
}
/**
* Set the expanded state of a given entry to certain relative level. It will
* call fireTreeEvent() for each changed entry. No redraw is done.
*
* @param entry
* The entry
* @param level
* level to expand to or negative for all levels
* @param expanded
* True if expanded, false if collapsed
*/
private void setExpandedStateInt(ITimeGraphEntry entry, int aLevel, boolean expanded) {
int level = aLevel;
if ((level > 0) || (level < 0)) {
level--;
if (entry.hasChildren()) {
for (ITimeGraphEntry e : entry.getChildren()) {
setExpandedStateInt(e, level, expanded);
}
}
}
Item item = fItemData.findItem(entry);
if (item != null && item.fExpanded != expanded) {
item.fExpanded = expanded;
fItemData.updateExpandedItems();
fireTreeEvent(item.fEntry, item.fExpanded);
}
}
/**
* Collapses all nodes of the viewer's tree, starting with the root.
*/
public void collapseAll() {
for (Item item : fItemData.fItems) {
item.fExpanded = false;
}
fItemData.updateExpandedItems();
redraw();
}
/**
* Expands all nodes of the viewer's tree, starting with the root.
*/
public void expandAll() {
for (Item item : fItemData.fItems) {
item.fExpanded = true;
}
fItemData.updateExpandedItems();
redraw();
}
/**
* Add a tree listener
*
* @param listener
* The listener to add
*/
public void addTreeListener(ITimeGraphTreeListener listener) {
if (!fTreeListeners.contains(listener)) {
fTreeListeners.add(listener);
}
}
/**
* Remove a tree listener
*
* @param listener
* The listener to remove
*/
public void removeTreeListener(ITimeGraphTreeListener listener) {
if (fTreeListeners.contains(listener)) {
fTreeListeners.remove(listener);
}
}
/**
* Tree event callback
*
* @param entry
* The affected entry
* @param expanded
* The expanded state (true for expanded, false for collapsed)
*/
public void fireTreeEvent(ITimeGraphEntry entry, boolean expanded) {
TimeGraphTreeExpansionEvent event = new TimeGraphTreeExpansionEvent(this, entry);
for (ITimeGraphTreeListener listener : fTreeListeners) {
if (expanded) {
listener.treeExpanded(event);
} else {
listener.treeCollapsed(event);
}
}
}
/**
* Add a viewer filter listener
*
* @param listener
* The listener to add
* @since 3.2
*/
public void addViewerFilterListener(ITimeGraphViewerFilterListener listener) {
if (!fViewerFilterListeners.contains(listener)) {
fViewerFilterListeners.add(listener);
}
}
/**
* Remove a viewer filter listener
*
* @param listener
* The listener to remove
* @since 3.2
*/
public void removeViewerFilterListener(ITimeGraphViewerFilterListener listener) {
if (fViewerFilterListeners.contains(listener)) {
fViewerFilterListeners.remove(listener);
}
}
/**
* Viewer filter added callback
*
* @param filters
* The added filters
*
* @since 3.1
*/
private void fireFiltersAdded(@NonNull Iterable<ViewerFilter> filters) {
for (ITimeGraphViewerFilterListener listener : fViewerFilterListeners) {
listener.filtersAdded(filters);
}
}
/**
* Viewer filter changed callback
*
* @param filters
* The changed filters
*
* @since 3.1
*/
private void fireFiltersChanged(@NonNull Iterable<ViewerFilter> filters) {
for (ITimeGraphViewerFilterListener listener : fViewerFilterListeners) {
listener.filtersChanged(filters);
}
}
/**
* Viewer filter removed callback
*
* @param filters
* The removed filters
*
* @since 3.1
*/
private void fireFiltersRemoved(@NonNull Iterable<ViewerFilter> filters) {
for (ITimeGraphViewerFilterListener listener : fViewerFilterListeners) {
listener.filtersRemoved(filters);
}
}
/**
* Add a menu listener on {@link ITimeGraphEntry}s
*
* @param listener
* The listener to add
*/
public void addTimeGraphEntryMenuListener(MenuDetectListener listener) {
if (!fTimeGraphEntryMenuListeners.contains(listener)) {
fTimeGraphEntryMenuListeners.add(listener);
}
}
/**
* Remove a menu listener on {@link ITimeGraphEntry}s
*
* @param listener
* The listener to remove
*/
public void removeTimeGraphEntryMenuListener(MenuDetectListener listener) {
if (fTimeGraphEntryMenuListeners.contains(listener)) {
fTimeGraphEntryMenuListeners.remove(listener);
}
}
/**
* Menu event callback on {@link ITimeGraphEntry}s
*
* @param event
* The MenuDetectEvent, with field {@link TypedEvent#data} set to the
* selected {@link ITimeGraphEntry}
*/
private void fireMenuEventOnTimeGraphEntry(MenuDetectEvent event) {
for (MenuDetectListener listener : fTimeGraphEntryMenuListeners) {
listener.menuDetected(event);
}
}
/**
* Add a menu listener on {@link ITimeEvent}s
*
* @param listener
* The listener to add
*/
public void addTimeEventMenuListener(MenuDetectListener listener) {
if (!fTimeEventMenuListeners.contains(listener)) {
fTimeEventMenuListeners.add(listener);
}
}
/**
* Remove a menu listener on {@link ITimeEvent}s
*
* @param listener
* The listener to remove
*/
public void removeTimeEventMenuListener(MenuDetectListener listener) {
if (fTimeEventMenuListeners.contains(listener)) {
fTimeEventMenuListeners.remove(listener);
}
}
/**
* Menu event callback on {@link ITimeEvent}s
*
* @param event
* The MenuDetectEvent, with field {@link TypedEvent#data} set to the
* selected {@link ITimeEvent}
*/
private void fireMenuEventOnTimeEvent(MenuDetectEvent event) {
for (MenuDetectListener listener : fTimeEventMenuListeners) {
listener.menuDetected(event);
}
}
@Override
public boolean setFocus() {
if ((fTimeProvider != null) && fTimeProvider.getNameSpace() > 0) {
fHasNamespaceFocus = true;
}
return super.setFocus();
}
/**
* Returns the current selection for this time graph. If a time graph entry is
* selected, it will be the first element in the selection. If a time event is
* selected, it will be the second element in the selection.
*
* @return the current selection
*/
@Override
public ISelection getSelection() {
ITimeGraphEntry entry = getSelectedTrace();
if (null != entry && null != fTimeProvider) {
long selectedTime = fTimeProvider.getSelectionBegin();
ITimeEvent event = Utils.findEvent(entry, selectedTime, 0);
if (event == null) {
return new StructuredSelection(entry);
}
return new StructuredSelection(new Object[] { entry, event });
}
return StructuredSelection.EMPTY;
}
/**
* Get the selection object
*
* @return The selection
*/
public ISelection getSelectionTrace() {
ITimeGraphEntry entry = getSelectedTrace();
if (null != entry) {
return new StructuredSelection(entry);
}
return StructuredSelection.EMPTY;
}
/**
* Enable/disable one of the traces in the model
*
* @param n
* 1 to enable it, -1 to disable. The method returns immediately if
* another value is used.
*/
public void selectTrace(int n) {
if ((n != 1) && (n != -1)) {
return;
}
boolean changed = false;
int lastSelection = -1;
for (int i = 0; i < fItemData.fExpandedItems.length; i++) {
Item item = fItemData.fExpandedItems[i];
if (item.fSelected) {
lastSelection = i;
if ((1 == n) && (i < fItemData.fExpandedItems.length - 1)) {
item.fSelected = false;
item = fItemData.fExpandedItems[i + 1];
item.fSelected = true;
changed = true;
} else if ((-1 == n) && (i > 0)) {
item.fSelected = false;
item = fItemData.fExpandedItems[i - 1];
item.fSelected = true;
changed = true;
}
break;
}
}
if (lastSelection < 0 && fItemData.fExpandedItems.length > 0) {
Item item = fItemData.fExpandedItems[0];
item.fSelected = true;
changed = true;
}
if (changed) {
ensureVisibleItem(-1, false);
redraw();
fireSelectionChanged();
}
}
/**
* Select an event
*
* @param n
* 1 for next event, -1 for previous event
* @param extend
* true to extend selection range, false for single selection
* @since 1.0
*/
public void selectEvent(int n, boolean extend) {
if (null == fTimeProvider) {
return;
}
ITimeGraphEntry entry = getSelectedTrace();
if (entry == null) {
return;
}
long time = fTimeProvider.getSelectionEnd();
if (n > 0) {
time = Math.min(Utils.nextChange(entry, time), fTimeProvider.getMaxTime());
} else if (n < 0) {
time = Math.max(Utils.prevChange(entry, time), fTimeProvider.getMinTime());
}
if (extend) {
fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), time, true);
} else {
fTimeProvider.setSelectedTimeNotify(time, true);
}
fireSelectionChanged();
updateStatusLine(STATUS_WITHOUT_CURSOR_TIME);
}
/**
* Select the next event
*
* @param extend
* true to extend selection range, false for single selection
* @since 1.0
*/
public void selectNextEvent(boolean extend) {
selectEvent(1, extend);
// Notify if visible time window has been adjusted
fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1());
}
/**
* Select the previous event
*
* @param extend
* true to extend selection range, false for single selection
* @since 1.0
*/
public void selectPrevEvent(boolean extend) {
selectEvent(-1, extend);
// Notify if visible time window has been adjusted
fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1());
}
/**
* Select the next trace
*/
public void selectNextTrace() {
selectTrace(1);
}
/**
* Select the previous trace
*/
public void selectPrevTrace() {
selectTrace(-1);
}
/**
* Scroll left or right by one quarter window size
*
* @param left
* true to scroll left, false to scroll right
*/
public void horizontalScroll(boolean left) {
long time0 = fTimeProvider.getTime0();
long time1 = fTimeProvider.getTime1();
long timeMin = fTimeProvider.getMinTime();
long timeMax = fTimeProvider.getMaxTime();
long range = time1 - time0;
if (range <= 0) {
return;
}
long increment = Math.max(1, range / 4);
if (left) {
time0 = Math.max(time0 - increment, timeMin);
time1 = time0 + range;
} else {
time1 = Math.min(time1 + increment, timeMax);
time0 = time1 - range;
}
fTimeProvider.setStartFinishTimeNotify(time0, time1);
}
/**
* Zoom based on mouse cursor location with mouse scrolling
*
* @param zoomIn
* true to zoom in, false to zoom out
*/
public void zoom(boolean zoomIn) {
Point cursorDisplayLocation = getDisplay().getCursorLocation();
Point cursorControlLocation = toControl(cursorDisplayLocation);
Point cursorParentLocation = getParent().toControl(cursorDisplayLocation);
Rectangle controlBounds = getBounds();
// check the X axis only
if (!controlBounds.contains(cursorParentLocation.x, controlBounds.y)) {
return;
}
int nameSpace = fTimeProvider.getNameSpace();
int timeSpace = fTimeProvider.getTimeSpace();
int xPos = Math.max(nameSpace, Math.min(nameSpace + timeSpace, cursorControlLocation.x));
long time0 = fTimeProvider.getTime0();
long time1 = fTimeProvider.getTime1();
long interval = time1 - time0;
if (interval == 0) {
interval = 1;
} // to allow getting out of single point interval
long newInterval;
if (zoomIn) {
newInterval = Math.max(Math.round(interval * ZOOM_IN_FACTOR), fTimeProvider.getMinTimeInterval());
} else {
newInterval = (long) Math.ceil(interval * ZOOM_OUT_FACTOR);
}
long center = time0 + Math.round(((double) (xPos - nameSpace) / timeSpace * interval));
long newTime0 = center - Math.round((double) newInterval * (center - time0) / interval);
/* snap to bounds if zooming out of range */
newTime0 = Math.max(fTimeProvider.getMinTime(), Math.min(newTime0, fTimeProvider.getMaxTime() - newInterval));
long newTime1 = newTime0 + newInterval;
fTimeProvider.setStartFinishTimeNotify(newTime0, newTime1);
}
/**
* zoom in using single click
*/
public void zoomIn() {
long prevTime0 = fTimeProvider.getTime0();
long prevTime1 = fTimeProvider.getTime1();
long prevRange = prevTime1 - prevTime0;
if (prevRange == 0) {
return;
}
ITimeDataProvider provider = fTimeProvider;
long selTime = (provider.getSelectionEnd() + provider.getSelectionBegin()) / 2;
if (selTime < prevTime0 || selTime > prevTime1) {
selTime = (prevTime0 + prevTime1) / 2;
}
long time0 = selTime - (long) ((selTime - prevTime0) / ZOOM_FACTOR);
long time1 = selTime + (long) ((prevTime1 - selTime) / ZOOM_FACTOR);
long min = fTimeProvider.getMinTimeInterval();
if ((time1 - time0) < min) {
time0 = selTime - (selTime - prevTime0) * min / prevRange;
time1 = time0 + min;
}
fTimeProvider.setStartFinishTimeNotify(time0, time1);
}
/**
* zoom out using single click
*/
public void zoomOut() {
long prevTime0 = fTimeProvider.getTime0();
long prevTime1 = fTimeProvider.getTime1();
ITimeDataProvider provider = fTimeProvider;
long selTime = (provider.getSelectionEnd() + provider.getSelectionBegin()) / 2;
if (selTime < prevTime0 || selTime > prevTime1) {
selTime = (prevTime0 + prevTime1) / 2;
}
long newInterval;
long time0;
if (prevTime1 - prevTime0 <= 1) {
newInterval = 2;
time0 = selTime - 1;
} else {
newInterval = (long) Math.ceil((prevTime1 - prevTime0) * ZOOM_FACTOR);
time0 = selTime - (long) Math.ceil((selTime - prevTime0) * ZOOM_FACTOR);
}
/* snap to bounds if zooming out of range */
time0 = Math.max(fTimeProvider.getMinTime(), Math.min(time0, fTimeProvider.getMaxTime() - newInterval));
long time1 = time0 + newInterval;
fTimeProvider.setStartFinishTimeNotify(time0, time1);
}
/**
* Zoom vertically.
*
* @param zoomIn
* true to zoom in, false to zoom out
* @since 2.0
*/
public void verticalZoom(boolean zoomIn) {
if (zoomIn) {
fHeightAdjustment++;
} else {
fHeightAdjustment--;
fHeightAdjustment = Math.max(fHeightAdjustment, 1 - fMaxItemHeight);
}
fItemData.refreshData();
redraw();
}
/**
* Reset the vertical zoom to default.
*
* @since 2.0
*/
public void resetVerticalZoom() {
fHeightAdjustment = 0;
fItemData.refreshData();
redraw();
}
/**
* Set the vertical grid lines visibility. The default is true.
*
* @param visible
* true to show the grid lines, false otherwise
* @since 2.0
*/
public void setGridLinesVisible(boolean visible) {
fGridLinesVisible = visible;
redraw();
}
/**
* Get the vertical grid lines visibility.
*
* @return true if the grid lines are visible, false otherwise
* @since 2.0
*/
public boolean getGridLinesVisible() {
return fGridLinesVisible;
}
/**
* Set the grid line color. The default is SWT.COLOR_GRAY.
*
* @param color
* the grid line color
* @since 2.0
*/
public void setGridLineColor(Color color) {
fGridLineColor = color;
redraw();
}
/**
* Get the grid line color.
*
* @return the grid line color
* @since 2.0
*/
public Color getGridLineColor() {
return fGridLineColor;
}
/**
* Set the label visibility. The default is true.
*
* @param visible
* true to show the labels, false otherwise
* @since 5.2
*/
public void setLabelsVisible(boolean visible) {
fLabelsVisible = visible;
redraw();
}
/**
* Set the horizontal middle lines visibility. The default is true.
*
* @param visible
* true to show the middle lines, false otherwise
* @since 4.0
*/
public void setMidLinesVisible(boolean visible) {
fMidLinesVisible = visible;
redraw();
}
/**
* Get the horizontal middle lines visibility.
*
* @return true if the middle lines are visible, false otherwise
* @since 4.0
*/
public boolean getMidLinesVisible() {
return fMidLinesVisible;
}
/**
* Set the markers list.
*
* @param markers
* The markers list, or null
* @since 2.0
*/
public void setMarkers(List<IMarkerEvent> markers) {
fMarkers = markers;
}
/**
* Get the markers list.
*
* @return The markers list, or null
* @since 2.0
*/
public List<IMarkerEvent> getMarkers() {
return fMarkers;
}
/**
* Set the markers visibility. The default is true.
*
* @param visible
* true to show the markers, false otherwise
* @since 2.0
*/
public void setMarkersVisible(boolean visible) {
fMarkersVisible = visible;
}
/**
* Get the markers visibility.
*
* @return true if the markers are visible, false otherwise
* @since 2.0
*/
public boolean getMarkersVisible() {
return fMarkersVisible;
}
/**
* Hide arrows
*
* @param hideArrows
* true to hide arrows
*/
public void hideArrows(boolean hideArrows) {
fHideArrows = hideArrows;
}
/**
* Follow the arrow forward
*
* @param extend
* true to extend selection range, false for single selection
* @since 1.0
*/
public void followArrowFwd(boolean extend) {
ITimeGraphEntry trace = getSelectedTrace();
if (trace == null) {
return;
}
long selectedTime = fTimeProvider.getSelectionEnd();
for (ILinkEvent link : fItemData.fLinks) {
if (link.getEntry() == trace && link.getTime() == selectedTime) {
selectItem(link.getDestinationEntry(), false);
if (link.getDuration() != 0) {
if (extend) {
fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime() + link.getDuration(), true);
} else {
fTimeProvider.setSelectedTimeNotify(link.getTime() + link.getDuration(), true);
}
// Notify if visible time window has been adjusted
fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1());
}
fireSelectionChanged();
updateStatusLine(STATUS_WITHOUT_CURSOR_TIME);
return;
}
}
selectNextEvent(extend);
}
/**
* Follow the arrow backward
*
* @param extend
* true to extend selection range, false for single selection
* @since 1.0
*/
public void followArrowBwd(boolean extend) {
ITimeGraphEntry trace = getSelectedTrace();
if (trace == null) {
return;
}
long selectedTime = fTimeProvider.getSelectionEnd();
for (ILinkEvent link : fItemData.fLinks) {
if (link.getDestinationEntry() == trace && link.getTime() + link.getDuration() == selectedTime) {
selectItem(link.getEntry(), false);
if (link.getDuration() != 0) {
if (extend) {
fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime(), true);
} else {
fTimeProvider.setSelectedTimeNotify(link.getTime(), true);
}
// Notify if visible time window has been adjusted
fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1());
}
fireSelectionChanged();
updateStatusLine(STATUS_WITHOUT_CURSOR_TIME);
return;
}
}
selectPrevEvent(extend);
}
/**
* Return the currently selected trace
*
* @return The entry matching the trace
*/
public ITimeGraphEntry getSelectedTrace() {
ITimeGraphEntry trace = null;
int idx = getSelectedIndex();
if (idx >= 0) {
trace = fItemData.fExpandedItems[idx].fEntry;
}
return trace;
}
/**
* Retrieve the index of the currently selected item
*
* @return The index
*/
public int getSelectedIndex() {
int idx = -1;
for (int i = 0; i < fItemData.fExpandedItems.length; i++) {
Item item = fItemData.fExpandedItems[i];
if (item.fSelected) {
idx = i;
break;
}
}
return idx;
}
boolean toggle(int idx) {
boolean toggled = false;
if (idx >= 0 && idx < fItemData.fExpandedItems.length) {
Item item = fItemData.fExpandedItems[idx];
if (item.fHasChildren) {
item.fExpanded = !item.fExpanded;
fItemData.updateExpandedItems();
redraw();
toggled = true;
fireTreeEvent(item.fEntry, item.fExpanded);
}
}
return toggled;
}
/**
* Gets the index of the item at the given location.
*
* @param y
* the y coordinate
* @return the index of the item at the given location, of -1 if none.
*/
protected int getItemIndexAtY(int y) {
int ySum = 0;
if (y < 0) {
for (int idx = fTopIndex - 1; idx >= 0; idx--) {
ySum -= fItemData.fExpandedItems[idx].fItemHeight;
if (y >= ySum) {
return idx;
}
}
} else {
for (int idx = fTopIndex; idx < fItemData.fExpandedItems.length; idx++) {
ySum += fItemData.fExpandedItems[idx].fItemHeight;
if (y < ySum) {
return idx;
}
}
}
return -1;
}
boolean isOverSplitLine(int x) {
if (x < 0 || null == fTimeProvider) {
return false;
}
int nameWidth = fTimeProvider.getNameSpace();
return Math.abs(x - nameWidth) <= SNAP_WIDTH;
}
boolean isOverTimeSpace(int x, int y) {
Point size = getSize();
return x >= fTimeProvider.getNameSpace() && x < size.x && y >= 0 && y < size.y;
}
/**
* Gets the {@link ITimeGraphEntry} at the given location.
*
* @param pt
* a point in the widget
* @return the {@link ITimeGraphEntry} at this point, or <code>null</code> if
* none.
* @since 2.0
*/
public ITimeGraphEntry getEntry(Point pt) {
int idx = getItemIndexAtY(pt.y);
return idx >= 0 ? fItemData.fExpandedItems[idx].fEntry : null;
}
/**
* Return the arrow event closest to the given point that is no further than a
* maximum distance.
*
* @param pt
* a point in the widget
* @return The closest arrow event, or null if there is none close enough.
*/
protected ILinkEvent getArrow(Point pt) {
if (fHideArrows) {
return null;
}
ILinkEvent linkEvent = null;
double minDistance = Double.MAX_VALUE;
Rectangle clientArea = getClientArea();
for (ILinkEvent event : fItemData.fLinks) {
Rectangle rect = getArrowRectangle(clientArea, event);
if (rect != null) {
int x1 = rect.x;
int y1 = rect.y;
int x2 = x1 + rect.width;
int y2 = y1 + rect.height;
double d = Utils.distance(pt.x, pt.y, x1, y1, x2, y2);
if (minDistance > d) {
minDistance = d;
linkEvent = event;
}
}
}
if (minDistance <= ARROW_HOVER_MAX_DIST) {
return linkEvent;
}
return null;
}
@Override
public int getXForTime(long time) {
if (null == fTimeProvider) {
return -1;
}
long time0 = fTimeProvider.getTime0();
long time1 = fTimeProvider.getTime1();
int width = getSize().x;
int nameSpace = fTimeProvider.getNameSpace();
double pixelsPerNanoSec = (width - nameSpace <= RIGHT_MARGIN) ? 0 : (double) (width - nameSpace - RIGHT_MARGIN) / (time1 - time0);
int x = SaturatedArithmetic.add(getBounds().x + nameSpace, (int) ((time - time0) * pixelsPerNanoSec));
return x;
}
@Override
public long getTimeAtX(int coord) {
if (null == fTimeProvider) {
return -1;
}
long hitTime = -1;
Point size = getSize();
long time0 = fTimeProvider.getTime0();
long time1 = fTimeProvider.getTime1();
int nameWidth = fTimeProvider.getNameSpace();
final int x = coord - nameWidth;
int timeWidth = size.x - nameWidth - RIGHT_MARGIN;
if (x >= 0 && size.x >= nameWidth) {
if (time1 - time0 > timeWidth) {
// nanosecond smaller than one pixel: use the first integer
// nanosecond of this pixel's time range
hitTime = time0 + (long) Math.ceil((time1 - time0) * ((double) x / timeWidth));
} else {
// nanosecond greater than one pixel: use closest nanosecond to
// this pixel's start time
hitTime = time0 + Math.round((time1 - time0) * ((double) x / timeWidth));
}
}
return hitTime;
}
void selectItem(int idx, boolean addSelection) {
selectItem(idx, addSelection, true);
}
void selectItem(int idx, boolean addSelection, boolean reveal) {
boolean changed = false;
if (addSelection) {
if (idx >= 0 && idx < fItemData.fExpandedItems.length) {
Item item = fItemData.fExpandedItems[idx];
changed = !item.fSelected;
item.fSelected = true;
}
} else {
for (int i = 0; i < fItemData.fExpandedItems.length; i++) {
Item item = fItemData.fExpandedItems[i];
if ((i == idx && !item.fSelected) || (idx == -1 && item.fSelected)) {
changed = true;
}
item.fSelected = i == idx;
}
}
if (reveal) {
changed |= ensureVisibleItem(idx, true);
}
if (changed) {
redraw();
}
}
/**
* Select an entry and make it visible
*
* @param entry
* The entry to select
* @param addSelection
* <code>true</code> to add the entry to the current selection, or
* <code>false</code> to set a new selection
*/
public void selectItem(ITimeGraphEntry entry, boolean addSelection) {
int idx = fItemData.findItemIndex(entry);
selectItem(idx, addSelection);
}
/**
* Select an entry
*
* @param entry
* The entry to select
* @param addSelection
* <code>true</code> to add the entry to the current selection, or
* <code>false</code> to set a new selection
* @param reveal
* <code>true</code> if the selection is to be made visible, and
* <code>false</code> otherwise
* @since 2.3
*/
public void selectItem(ITimeGraphEntry entry, boolean addSelection, boolean reveal) {
int idx = fItemData.findItemIndex(entry);
selectItem(idx, addSelection, reveal);
}
/**
* Retrieve the number of entries shown per page.
*
* @return The count
*/
public int countPerPage() {
int height = getSize().y;
int count = 0;
int ySum = 0;
for (int idx = fTopIndex; idx < fItemData.fExpandedItems.length; idx++) {
ySum += fItemData.fExpandedItems[idx].fItemHeight;
if (ySum >= height) {
return count;
}
count++;
}
for (int idx = fTopIndex - 1; idx >= 0; idx--) {
ySum += fItemData.fExpandedItems[idx].fItemHeight;
if (ySum >= height) {
return count;
}
count++;
}
return count;
}
/**
* Get the index of the top element
*
* @return The index
*/
public int getTopIndex() {
return fTopIndex;
}
/**
* Get the number of expanded (visible) items
*
* @return The count of expanded (visible) items
*/
public int getExpandedElementCount() {
return fItemData.fExpandedItems.length;
}
/**
* Get an array of all expanded (visible) elements
*
* @return The expanded (visible) elements
*/
public ITimeGraphEntry[] getExpandedElements() {
ArrayList<ITimeGraphEntry> elements = new ArrayList<>();
for (Item item : fItemData.fExpandedItems) {
elements.add(item.fEntry);
}
return elements.toArray(new ITimeGraphEntry[0]);
}
/**
* Get the expanded (visible) element at the specified index.
*
* @param index
* the element index
* @return The expanded (visible) element or null if out of range
* @since 2.0
*/
public ITimeGraphEntry getExpandedElement(int index) {
if (index < 0 || index >= fItemData.fExpandedItems.length) {
return null;
}
return fItemData.fExpandedItems[index].fEntry;
}
/**
* Get the bounds of the specified entry relative to its parent time graph.
*
* @param entry
* the time graph entry
* @return the bounds of the entry, or null if the entry is not visible
* @since 2.3
*/
public Rectangle getItemBounds(ITimeGraphEntry entry) {
int idx = fItemData.findItemIndex(entry);
if (idx >= 0) {
return getItemRect(getClientArea(), idx);
}
return null;
}
Rectangle getNameRect(Rectangle bounds, int idx, int nameWidth) {
Rectangle rect = getItemRect(bounds, idx);
rect.width = nameWidth;
return rect;
}
Rectangle getStatesRect(Rectangle bounds, int idx, int nameWidth) {
Rectangle rect = getItemRect(bounds, idx);
rect.x += nameWidth;
rect.width -= nameWidth;
return rect;
}
Rectangle getItemRect(Rectangle bounds, int idx) {
int ySum = 0;
if (idx >= fTopIndex) {
for (int i = fTopIndex; i < idx; i++) {
ySum += fItemData.fExpandedItems[i].fItemHeight;
}
} else {
for (int i = fTopIndex - 1; i >= idx; i--) {
ySum -= fItemData.fExpandedItems[i].fItemHeight;
}
}
int y = bounds.y + ySum;
int height = fItemData.fExpandedItems[idx].fItemHeight;
return new Rectangle(bounds.x, y, bounds.width, height);
}
@Override
void paint(Rectangle bounds, PaintEvent e) {
GC gc = e.gc;
if (bounds.width < 2 || bounds.height < 2 || null == fTimeProvider) {
return;
}
fIdealNameSpace = 0;
int nameSpace = fTimeProvider.getNameSpace();
// draw the background layer
drawBackground(bounds, nameSpace, gc);
// draw the grid lines
drawGridLines(bounds, gc);
// draw the background markers
drawMarkers(bounds, fTimeProvider, fMarkers, false, nameSpace, gc);
// draw the items
drawItems(bounds, fTimeProvider, fItemData.fExpandedItems, fTopIndex, nameSpace, gc);
// draw the foreground markers
drawMarkers(bounds, fTimeProvider, fMarkers, true, nameSpace, gc);
// draw the links (arrows)
drawLinks(bounds, fTimeProvider, fItemData.fLinks, nameSpace, gc);
fTimeGraphProvider.postDrawControl(bounds, gc);
gc.setAlpha(OPAQUE * 2 / 5);
long time0 = fTimeProvider.getTime0();
long time1 = fTimeProvider.getTime1();
long selectionBegin = fTimeProvider.getSelectionBegin();
long selectionEnd = fTimeProvider.getSelectionEnd();
double pixelsPerNanoSec = (bounds.width - nameSpace <= RIGHT_MARGIN) ? 0 : (double) (bounds.width - nameSpace - RIGHT_MARGIN) / (time1 - time0);
int x0 = SaturatedArithmetic.add(bounds.x + nameSpace, (int) ((selectionBegin - time0) * pixelsPerNanoSec));
int x1 = SaturatedArithmetic.add(bounds.x + nameSpace, (int) ((selectionEnd - time0) * pixelsPerNanoSec));
// draw selection lines
if (fDragState != DRAG_SELECTION) {
gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.SELECTED_TIME));
if (x0 >= nameSpace && x0 < bounds.x + bounds.width) {
gc.drawLine(x0, bounds.y, x0, bounds.y + bounds.height);
}
if (x1 != x0) {
if (x1 >= nameSpace && x1 < bounds.x + bounds.width) {
gc.drawLine(x1, bounds.y, x1, bounds.y + bounds.height);
}
}
}
// draw selection background
if (selectionBegin != 0 && selectionEnd != 0 && fDragState != DRAG_SELECTION) {
x0 = Math.max(nameSpace, Math.min(bounds.x + bounds.width, x0));
x1 = Math.max(nameSpace, Math.min(bounds.x + bounds.width, x1));
gc.setBackground(getColorScheme().getBkColor(false, false, true));
if (x1 - x0 > 1) {
gc.fillRectangle(new Rectangle(x0 + 1, bounds.y, x1 - x0 - 1, bounds.height));
} else if (x0 - x1 > 1) {
gc.fillRectangle(new Rectangle(x1 + 1, bounds.y, x0 - x1 - 1, bounds.height));
}
}
// draw drag selection background
if (fDragState == DRAG_ZOOM || fDragState == DRAG_SELECTION) {
gc.setBackground(getColorScheme().getBkColor(false, false, true));
if (fDragX0 < fDragX) {
gc.fillRectangle(new Rectangle(fDragX0, bounds.y, fDragX - fDragX0, bounds.height));
} else if (fDragX0 > fDragX) {
gc.fillRectangle(new Rectangle(fDragX, bounds.y, fDragX0 - fDragX, bounds.height));
}
}
// draw split line
if (DRAG_SPLIT_LINE == fDragState ||
(DRAG_NONE == fDragState && fMouseOverSplitLine && fTimeProvider.getNameSpace() > 0)) {
gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.DARK_GRAY));
} else {
gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.GRAY));
}
gc.fillRectangle(bounds.x + nameSpace - SNAP_WIDTH, bounds.y, SNAP_WIDTH, bounds.height);
if (DRAG_ZOOM == fDragState && Math.max(fDragX, fDragX0) > nameSpace) {
gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.TOOL_FOREGROUND));
gc.drawLine(fDragX0, bounds.y, fDragX0, bounds.y + bounds.height - 1);
if (fDragX != fDragX0) {
gc.drawLine(fDragX, bounds.y, fDragX, bounds.y + bounds.height - 1);
}
} else if (DRAG_SELECTION == fDragState && Math.max(fDragX, fDragX0) > nameSpace) {
gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.SELECTED_TIME));
gc.drawLine(fDragX0, bounds.y, fDragX0, bounds.y + bounds.height - 1);
if (fDragX != fDragX0) {
gc.drawLine(fDragX, bounds.y, fDragX, bounds.y + bounds.height - 1);
}
}
gc.setAlpha(OPAQUE);
}
/**
* Draw the background layer. Fills the background of the control's name space
* and states space, updates the background of items if necessary, and draws the
* item's name text and middle line.
*
* @param bounds
* The bounds of the control
* @param nameSpace
* The name space width
* @param gc
* Graphics context
* @since 2.0
*/
protected void drawBackground(Rectangle bounds, int nameSpace, GC gc) {
// draw empty name space background
gc.setBackground(getColorScheme().getBkColor(false, false, true));
drawBackground(gc, bounds.x, bounds.y, nameSpace, bounds.height);
// draw empty states space background
gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.BACKGROUND));
drawBackground(gc, bounds.x + nameSpace, bounds.y, bounds.width - nameSpace, bounds.height);
for (int i = fTopIndex; i < fItemData.fExpandedItems.length; i++) {
if (fItemData.fExpandedItems[i].fItemHeight == 0) {
continue;
}
Rectangle itemRect = getItemRect(bounds, i);
if (itemRect.y >= bounds.y + bounds.height) {
break;
}
Item item = fItemData.fExpandedItems[i];
// draw the background of selected item and items with no time
// events
if (!item.fEntry.hasTimeEvents()) {
gc.setBackground(getColorScheme().getBkColorGroup(item.fSelected, fIsInFocus));
gc.fillRectangle(itemRect);
} else if (item.fSelected) {
gc.setBackground(getColorScheme().getBkColor(true, fIsInFocus, true));
gc.fillRectangle(itemRect.x, itemRect.y, nameSpace, itemRect.height);
gc.setBackground(getColorScheme().getBkColor(true, fIsInFocus, false));
gc.fillRectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height);
}
// draw the name space
Rectangle nameRect = new Rectangle(itemRect.x, itemRect.y, nameSpace, itemRect.height);
drawName(item, nameRect, gc);
if (fMidLinesVisible && item.fEntry.hasTimeEvents() && item.fItemHeight > MIN_MIDLINE_HEIGHT && !(item.fEntry instanceof TimeGraphLineEntry)) {
Rectangle rect = new Rectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height);
drawMidLine(rect, gc);
}
}
}
/**
* Draw the grid lines
*
* @param bounds
* The bounds of the control
* @param gc
* Graphics context
* @since 2.0
*/
public void drawGridLines(Rectangle bounds, GC gc) {
if (!fGridLinesVisible) {
return;
}
gc.setForeground(fGridLineColor);
gc.setAlpha(fGridLineColor.getAlpha());
for (int x : fTimeGraphScale.getTickList()) {
gc.drawLine(x, bounds.y, x, bounds.y + bounds.height);
}
gc.setAlpha(OPAQUE);
}
/**
* Draw the markers
*
* @param bounds
* The rectangle of the area
* @param timeProvider
* The time provider
* @param markers
* The list of markers
* @param foreground
* true to draw the foreground markers, false otherwise
* @param nameSpace
* The width reserved for the names
* @param gc
* Reference to the SWT GC object
* @since 2.0
*/
protected void drawMarkers(Rectangle bounds, ITimeDataProvider timeProvider, List<IMarkerEvent> markers, boolean foreground, int nameSpace, GC gc) {
if (!fMarkersVisible || markers == null || markers.isEmpty()) {
return;
}
gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height));
/* the list can grow concurrently but cannot shrink */
for (int i = 0; i < markers.size(); i++) {
IMarkerEvent marker = markers.get(i);
if (marker.isForeground() == foreground) {
drawMarker(marker, bounds, timeProvider, nameSpace, gc);
}
}
gc.setClipping((Rectangle) null);
}
/**
* Draw a single marker
*
* @param marker
* The marker event
* @param bounds
* The bounds of the control
* @param timeProvider
* The time provider
* @param nameSpace
* The width reserved for the name
* @param gc
* Reference to the SWT GC object
* @since 2.0
*/
protected void drawMarker(IMarkerEvent marker, Rectangle bounds, ITimeDataProvider timeProvider, int nameSpace, GC gc) {
Rectangle rect = Utils.clone(bounds);
if (marker.getEntry() != null) {
int index = fItemData.findItemIndex(marker.getEntry());
if (index == -1) {
return;
}
rect = getStatesRect(bounds, index, nameSpace);
if (rect.y < 0 || rect.y > bounds.height) {
return;
}
}
int x0 = getXForTime(marker.getTime());
int x1 = getXForTime(marker.getTime() + marker.getDuration());
if (x0 > bounds.width || x1 < nameSpace) {
return;
}
rect.x = Math.max(nameSpace, Math.min(bounds.width, x0));
rect.width = Math.max(1, Math.min(bounds.width, x1) - rect.x);
StyleManager styleManager = getStyleManager();
OutputElementStyle elementStyle = getElementStyle(marker);
if (elementStyle == null) {
return;
}
RGBAColor rgba = styleManager.getColorStyle(elementStyle, StyleProperties.COLOR);
int colorInt = (rgba != null) ? rgba.toInt() : RGBAUtil.fromRGBA(marker.getColor());
Color color = getColor(colorInt);
Object symbolType = styleManager.getStyle(elementStyle, StyleProperties.SYMBOL_TYPE);
if (symbolType != null && symbolType != SymbolType.NONE) {
gc.setAlpha(colorInt & 0xff);
Float heightFactor = styleManager.getFactorStyle(elementStyle, StyleProperties.HEIGHT);
heightFactor = (heightFactor != null) ? Math.max(0.0f, Math.min(1.0f, heightFactor)) : 1.0f;
int symbolSize = (int) Math.ceil(rect.height * heightFactor);
switch (String.valueOf(symbolType)) {
case SymbolType.CROSS:
SymbolHelper.drawCross(gc, color, symbolSize, rect.x, rect.y + rect.height / 2);
break;
case SymbolType.PLUS:
SymbolHelper.drawPlus(gc, color, symbolSize, rect.x, rect.y + rect.height / 2);
break;
case SymbolType.SQUARE:
SymbolHelper.drawSquare(gc, color, symbolSize, rect.x, rect.y + rect.height / 2);
break;
case SymbolType.TRIANGLE:
SymbolHelper.drawTriangle(gc, color, symbolSize, rect.x, rect.y + rect.height / 2);
break;
case SymbolType.INVERTED_TRIANGLE:
SymbolHelper.drawInvertedTriangle(gc, color, symbolSize, rect.x, rect.y + rect.height / 2);
break;
case SymbolType.CIRCLE:
SymbolHelper.drawCircle(gc, color, symbolSize, rect.x, rect.y + rect.height / 2);
break;
default:
SymbolHelper.drawDiamond(gc, color, symbolSize, rect.x, rect.y + rect.height / 2);
}
gc.setAlpha(OPAQUE);
if (marker.getDuration() == 0) {
return;
}
}
oldDrawMarker(marker, gc, rect);
}
private void oldDrawMarker(IMarkerEvent marker, GC gc, Rectangle rect) {
Color color = getColorScheme().getColor(marker.getColor());
gc.setBackground(color);
gc.setAlpha(color.getAlpha());
gc.fillRectangle(rect);
gc.setAlpha(OPAQUE);
String label = marker.getLabel();
if (fLabelsVisible && label != null && marker.getEntry() != null) {
label = label.substring(0, Math.min(label.indexOf('\n') != -1 ? label.indexOf('\n') : label.length(), MAX_LABEL_LENGTH));
gc.setForeground(color);
Utils.drawText(gc, label, rect.x - gc.textExtent(label).x, rect.y, true);
}
}
/**
* Draw many items at once
*
* @param bounds
* The bounds of the control
* @param timeProvider
* The time provider
* @param items
* The array items to draw
* @param topIndex
* The index of the first element to draw
* @param nameSpace
* The name space width
* @param gc
* Graphics context
*/
public void drawItems(Rectangle bounds, ITimeDataProvider timeProvider,
Item[] items, int topIndex, int nameSpace, GC gc) {
int bottomIndex = Integer.min(topIndex + countPerPage() + 1, items.length);
for (int i = topIndex; i < bottomIndex; i++) {
Item item = items[i];
drawItem(item, bounds, timeProvider, i, nameSpace, gc);
}
}
/**
* Draws the item
*
* @param item
* The item to draw
* @param bounds
* The bounds of the control
* @param timeProvider
* The time provider
* @param i
* The expanded item index
* @param nameSpace
* The name space width
* @param gc
* Graphics context
*/
protected void drawItem(Item item, Rectangle bounds, ITimeDataProvider timeProvider, int i, int nameSpace, GC gc) {
if (fItemData.fExpandedItems[i].fItemHeight == 0) {
return;
}
Rectangle itemRect = getItemRect(bounds, i);
if (itemRect.y >= bounds.y + bounds.height) {
return;
}
ITimeGraphEntry entry = item.fEntry;
long time0 = timeProvider.getTime0();
long time1 = timeProvider.getTime1();
long selectedTime = fTimeProvider.getSelectionEnd();
Rectangle rect = new Rectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height);
if (rect.isEmpty() || (time1 <= time0)) {
fTimeGraphProvider.postDrawEntry(entry, rect, gc);
return;
}
boolean selected = item.fSelected;
// K pixels per second
double pixelsPerNanoSec = (rect.width <= RIGHT_MARGIN) ? 0 : (double) (rect.width - RIGHT_MARGIN) / (time1 - time0);
if (entry.hasTimeEvents() && !(isFilterActive() && hasSavedFilters() && !((TimeGraphEntry) item.fEntry).hasZoomedEvents())) {
gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height));
fillSpace(rect, gc, selected);
int margins = getMarginForHeight(rect.height);
int height = rect.height - margins;
int topMargin = (margins + 1) / 2;
Rectangle stateRect = new Rectangle(rect.x, rect.y + topMargin, rect.width, height);
/* Set the font for this item */
setFontForHeight(height, gc);
long maxDuration = (timeProvider.getTimeSpace() == 0) ? Long.MAX_VALUE : 1 * (time1 - time0) / timeProvider.getTimeSpace();
Iterator<ITimeEvent> iterator = entry.getTimeEventsIterator(time0, time1, maxDuration);
switch (entry.getStyle()) {
case LINE:
drawLineGraphEntry(gc, time0, rect, pixelsPerNanoSec, iterator);
break;
case STATE:
drawTimeGraphEntry(gc, time0, selectedTime, rect, selected, pixelsPerNanoSec, stateRect, iterator);
break;
default:
break;
}
gc.setClipping((Rectangle) null);
}
fTimeGraphProvider.postDrawEntry(entry, rect, gc);
}
private static class LongPoint {
final int x;
final long y;
public LongPoint(int x, long y) {
this.x = x;
this.y = y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof LongPoint) {
LongPoint longPoint = (LongPoint) obj;
return longPoint.x == x && longPoint.y == y;
}
return false;
}
}
private void drawLineGraphEntry(GC gc, long time0, Rectangle rect, double pixelsPerNanoSec, Iterator<ITimeEvent> iterator) {
// clamp 0 - max positive
long max = Long.MIN_VALUE;
long min = 0;
List<List<LongPoint>> seriesModel = new ArrayList<>();
TimeLineEvent lastValid = null;
while (iterator.hasNext()) {
ITimeEvent event = iterator.next();
if (!(event instanceof TimeLineEvent)) {
continue;
}
int x = SaturatedArithmetic.add(rect.x, (int) ((event.getTime() - time0) * pixelsPerNanoSec));
if (x >= rect.x + rect.width) {
// event is out of bounds
continue;
}
TimeLineEvent timeEvent = (TimeLineEvent) event;
List<Long> values = timeEvent.getValues();
for (int i = 0; i < values.size(); i++) {
if (seriesModel.size() <= i) {
seriesModel.add(new ArrayList<>());
}
Long val = values.get(i);
if (val != null) {
// get max and min, this is a relative scale.
max = Math.max(Math.abs(val), max);
min = 0;
lastValid = timeEvent;
seriesModel.get(i).add(new LongPoint(x, val));
}
}
}
if (lastValid == null) {
return;
}
double scale = (max - min) == 0 ? 1.0 : (double) rect.height / (max - min);
StyleManager styleManager = getStyleManager();
OutputElementStyle elementStyle = getElementStyle(lastValid);
if (elementStyle == null) {
return;
}
RGBAColor rgba = styleManager.getColorStyle(elementStyle, StyleProperties.COLOR);
int colorInt = (rgba != null) ? rgba.toInt() : OPAQUE;
Color color = getColor(colorInt);
for (int i = 0; i < seriesModel.size(); i++) {
Color prev = gc.getForeground();
int prevAlpha = gc.getAlpha();
gc.setAlpha(colorInt & 0xff);
gc.setForeground(color);
List<LongPoint> series = seriesModel.get(i);
int[] points = new int[series.size() * 2];
for (int point = 0; point < series.size(); point++) {
LongPoint longPoint = series.get(point);
points[point * 2] = longPoint.x;
points[point * 2 + 1] = rect.height - (int) ((longPoint.y - min) * scale) + rect.y;
}
gc.drawPolyline(points);
gc.setForeground(prev);
gc.setAlpha(prevAlpha);
}
}
private void drawTimeGraphEntry(GC gc, long time0, long selectedTime, Rectangle rect, boolean selected, double pixelsPerNanoSec, Rectangle stateRect, Iterator<ITimeEvent> iterator) {
int lastX = -1;
fLastTransparentX = -1;
while (iterator.hasNext()) {
ITimeEvent event = iterator.next();
int x = SaturatedArithmetic.add(rect.x, (int) ((event.getTime() - time0) * pixelsPerNanoSec));
int xEnd = SaturatedArithmetic.add(rect.x, (int) ((event.getTime() + event.getDuration() - time0) * pixelsPerNanoSec));
if (x >= rect.x + rect.width || xEnd < rect.x) {
// event is out of bounds
continue;
}
xEnd = Math.min(rect.x + rect.width, xEnd);
stateRect.x = Math.max(rect.x, x);
stateRect.width = Math.max(1, xEnd - stateRect.x + 1);
if (stateRect.x < lastX) {
stateRect.width -= (lastX - stateRect.x);
if (stateRect.width > 0) {
stateRect.x = lastX;
} else {
stateRect.width = 0;
}
}
if (drawState(getColorScheme(), event, stateRect, gc, selected, selectedTime >= event.getTime() && selectedTime < event.getTime() + event.getDuration())) {
lastX = stateRect.x + stateRect.width;
}
}
}
/**
* Draw the links
*
* @param bounds
* The bounds of the control
* @param timeProvider
* The time provider
* @param links
* The list of link events
* @param nameSpace
* The name space width
* @param gc
* Graphics context
*/
public void drawLinks(Rectangle bounds, ITimeDataProvider timeProvider,
List<ILinkEvent> links, int nameSpace, GC gc) {
if (fHideArrows) {
return;
}
gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height));
/* the list can grow concurrently but cannot shrink */
for (int i = 0; i < links.size(); i++) {
drawLink(links.get(i), bounds, timeProvider, nameSpace, gc);
}
gc.setClipping((Rectangle) null);
}
/**
* Draws a link type event
*
* @param event
* The link event to draw
* @param bounds
* The bounds of the control
* @param timeProvider
* The time provider
* @param nameSpace
* The name space width
* @param gc
* Graphics context
*/
protected void drawLink(ILinkEvent event, Rectangle bounds, ITimeDataProvider timeProvider, int nameSpace, GC gc) {
drawArrow(getColorScheme(), event, getArrowRectangle(bounds, event), gc);
}
private @Nullable Rectangle getArrowRectangle(Rectangle bounds, ILinkEvent event) {
int srcIndex = fItemData.findItemIndex(event.getEntry());
int destIndex = fItemData.findItemIndex(event.getDestinationEntry());
if ((srcIndex == -1) || (destIndex == -1)) {
return null;
}
Rectangle src = getStatesRect(bounds, srcIndex, fTimeProvider.getNameSpace());
Rectangle dst = getStatesRect(bounds, destIndex, fTimeProvider.getNameSpace());
int x0 = getXForTime(event.getTime());
int x1 = getXForTime(event.getTime() + event.getDuration());
// limit the x-coordinates to prevent integer overflow in calculations
// and also GC.drawLine doesn't draw properly with large coordinates
final int limit = Integer.MAX_VALUE / 1024;
x0 = Math.max(-limit, Math.min(x0, limit));
x1 = Math.max(-limit, Math.min(x1, limit));
int y0 = src.y + src.height / 2;
int y1 = dst.y + dst.height / 2;
return LineClipper.clip(bounds, x0, y0, x1, y1);
}
/**
* Draw an arrow
*
* @param colors
* Color scheme
* @param event
* Time event for which we're drawing the arrow
* @param rect
* The arrow rectangle
* @param gc
* Graphics context
* @return true if the arrow was drawn
*/
protected boolean drawArrow(TimeGraphColorScheme colors, ITimeEvent event,
Rectangle rect, GC gc) {
if (rect == null || ((rect.height == 0) && (rect.width == 0))) {
return false;
}
StyleManager styleManager = getStyleManager();
OutputElementStyle elementStyle = getElementStyle(event);
if (elementStyle == null) {
return false;
}
RGBAColor rgba = styleManager.getColorStyle(elementStyle, StyleProperties.COLOR);
int colorInt = (rgba != null) ? rgba.toInt() : OPAQUE;
Color color = getColor(colorInt);
int alpha = colorInt & 0xff;
int prevAlpha = gc.getAlpha();
gc.setAlpha(alpha);
gc.setForeground(color);
gc.setBackground(color);
int old = gc.getLineWidth();
Float heightFactor = styleManager.getFactorStyle(elementStyle, StyleProperties.HEIGHT);
heightFactor = (heightFactor != null) ? Math.max(0.0f, Math.min(1.0f, heightFactor)) * 10.0f : 1.0f;
gc.setLineWidth(heightFactor.intValue());
/* Draw the arrow */
Point newEndpoint = drawArrowHead(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, heightFactor, gc);
gc.drawLine(rect.x, rect.y, newEndpoint.x, newEndpoint.y);
gc.setLineWidth(old);
gc.setAlpha(prevAlpha);
if (!Boolean.TRUE.equals(styleManager.getStyle(elementStyle, ITimeEventStyleStrings.annotated()))) {
fTimeGraphProvider.postDrawEvent(event, rect, gc);
}
return true;
}
/*
* @author Francis Giraldeau
*
* Inspiration:
* http://stackoverflow.com/questions/3010803/draw-arrow-on-line-algorithm
*
* The algorithm was taken from this site, not the code itself
*/
private static Point drawArrowHead(int x0, int y0, int x1, int y1, float scale, GC gc) {
double factor = 10 + 2.0 * scale;
double cos = 0.9510;
double sin = 0.3090;
long lenx = x1 - x0;
long leny = y1 - y0;
double len = Math.sqrt(lenx * lenx + leny * leny);
double dx = factor * lenx / len;
double dy = factor * leny / len;
int end1X = (int) Math.round((x1 - (dx * cos + dy * -sin)));
int end1Y = (int) Math.round((y1 - (dx * sin + dy * cos)));
int end2X = (int) Math.round((x1 - (dx * cos + dy * sin)));
int end2Y = (int) Math.round((y1 - (dx * -sin + dy * cos)));
int[] arrow = new int[] { x1, y1, end1X, end1Y, end2X, end2Y, x1, y1 };
gc.fillPolygon(arrow);
/*
* The returned point corresponds to the point at 1/4 from the head basis basis
* to the tip. it will be used as end point the arrow line
*/
return new Point((3 * ((end1X + end2X) / 2) + x1) / 4, (3 * ((end1Y + end2Y) / 2) + y1) / 4);
}
/**
* Draw the name space of an item.
*
* @param item
* Item object
* @param bounds
* The bounds of the item's name space
* @param gc
* Graphics context
*/
protected void drawName(Item item, Rectangle bounds, GC gc) {
// No name space to be drawn
if (fTimeProvider.getNameSpace() == 0) {
return;
}
boolean hasTimeEvents = item.fEntry.hasTimeEvents();
if (hasTimeEvents) {
gc.setClipping(bounds);
}
int height = bounds.height - getMarginForHeight(bounds.height);
setFontForHeight(height, gc);
String name = fLabelProvider == null ? item.fName : fLabelProvider.getColumnText(item.fEntry, 0);
Rectangle rect = Utils.clone(bounds);
rect.y += (bounds.height - gc.stringExtent(name).y) / 2;
Tree tree = getTree();
TreeColumn[] columns = tree.getColumns();
int idealNameSpace = 0;
for (int i = 0; i < columns.length; i++) {
int columnIndex = tree.getColumnOrder()[i];
TreeColumn column = columns[columnIndex];
rect.width = column.getWidth();
gc.setClipping(rect.x, bounds.y, Math.min(rect.width, bounds.x + bounds.width - rect.x - SNAP_WIDTH), bounds.height);
int width = MARGIN;
if (i == 0) {
// first visible column
width += item.fLevel * EXPAND_SIZE;
if (item.fHasChildren) {
// draw expand/collapse arrow
gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.DARK_GRAY));
int arrowHeightHint = (height < 4) ? height : (height < 6) ? height - 1 : height - 2;
int arrowHalfHeight = Math.max(1, Math.min(arrowHeightHint, (int) Math.round((EXPAND_SIZE - 2) / ARROW_RATIO))) / 2;
int arrowHalfWidth = (Math.max(1, Math.min(EXPAND_SIZE - 2, (int) Math.round(arrowHeightHint * ARROW_RATIO))) + 1) / 2;
int x1 = bounds.x + width + 1;
int x2 = x1 + 2 * arrowHalfWidth;
int midy = bounds.y + bounds.height / 2;
int y1 = midy - arrowHalfHeight;
int y2 = midy + arrowHalfHeight;
if (!item.fExpanded) { // >
gc.fillPolygon(new int[] { x1, y1, x2, midy, x1, y2 });
} else { // v
int midx = x1 + arrowHalfWidth;
gc.fillPolygon(new int[] { x1, y1, x2, y1, midx, y2 });
}
}
width += EXPAND_SIZE + MARGIN;
Image img = fLabelProvider != null ? fLabelProvider.getColumnImage(item.fEntry, columnIndex)
: columnIndex == 0 ? fTimeGraphProvider.getItemImage(item.fEntry) : null;
if (img != null) {
// draw icon
int imgHeight = img.getImageData().height;
int imgWidth = img.getImageData().width;
int dstHeight = Math.min(bounds.height, imgHeight);
int dstWidth = dstHeight * imgWidth / imgHeight;
int x = width;
int y = bounds.y + (bounds.height - dstHeight) / 2;
gc.drawImage(img, 0, 0, imgWidth, imgHeight, x, y, dstWidth, dstHeight);
width += imgWidth + MARGIN;
}
} else {
if (fLabelProvider == null) {
break;
}
}
String label = fLabelProvider != null ? fLabelProvider.getColumnText(item.fEntry, columnIndex)
: columnIndex == 0 ? item.fName : ""; //$NON-NLS-1$
gc.setForeground(getColorScheme().getFgColor(item.fSelected, fIsInFocus));
Rectangle textRect = new Rectangle(rect.x + width, rect.y, rect.width - width, rect.height);
int textWidth = Utils.drawText(gc, label, textRect, true);
width += textWidth + MARGIN;
if (textWidth > 0) {
idealNameSpace = rect.x + width;
}
if (fMidLinesVisible && item.fEntry.hasTimeEvents() && columns.length == 1 && item.fItemHeight > MIN_MIDLINE_HEIGHT) {
drawMidLine(new Rectangle(bounds.x + width, bounds.y, bounds.x + bounds.width, bounds.height), gc);
}
if (fAutoResizeColumns && width > column.getWidth()) {
column.setData(PREFERRED_WIDTH, width);
column.setWidth(width);
}
gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.MID_LINE));
if (i < columns.length - 1) {
// not the last visible column: draw the vertical cell border
int x = rect.x + rect.width - 1;
gc.drawLine(x, bounds.y, x, bounds.y + bounds.height);
}
rect.x += rect.width;
}
fIdealNameSpace = Math.max(fIdealNameSpace, idealNameSpace);
gc.setClipping((Rectangle) null);
}
/**
* Draw the state (color fill)
*
* @param colors
* Color scheme
* @param event
* Time event for which we're drawing the state
* @param rect
* The state rectangle
* @param gc
* Graphics context
* @param selected
* Is this time event currently selected (so it appears highlighted)
* @param timeSelected
* Is the timestamp currently selected
* @return true if the state was drawn
*/
protected boolean drawState(TimeGraphColorScheme colors, ITimeEvent event,
Rectangle rect, GC gc, boolean selected, boolean timeSelected) {
StyleManager styleManager = getStyleManager();
OutputElementStyle elementStyle = getElementStyle(event);
if (elementStyle == null) {
return false;
}
boolean transparent = elementStyle.getParentKey() == null && elementStyle.getStyleValues().isEmpty();
boolean visible = rect.width <= 0 ? false : true;
rect.width = Math.max(1, rect.width);
Color black = Display.getDefault().getSystemColor(SWT.COLOR_BLACK);
gc.setForeground(black);
Float heightFactor = styleManager.getFactorStyle(elementStyle, StyleProperties.HEIGHT);
heightFactor = (heightFactor != null) ? Math.max(0.0f, Math.min(1.0f, heightFactor)) : DEFAULT_STATE_WIDTH;
int height = 0;
if (heightFactor != 0.0f && rect.height != 0) {
height = Math.max(1, (int) (rect.height * heightFactor));
}
Rectangle drawRect = new Rectangle(rect.x, rect.y + ((rect.height - height) / 2), rect.width, height);
if (transparent) {
if (visible) {
// Avoid overlapping transparent states
int x = Math.max(fLastTransparentX, drawRect.x);
int width = drawRect.x + drawRect.width - x;
if (width > 0) {
// Draw transparent background
drawRect.x = x;
drawRect.width = width;
gc.setAlpha(OPAQUE / 4);
gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_GRAY));
gc.fillRectangle(drawRect);
fLastTransparentX = Math.max(fLastTransparentX, drawRect.x + drawRect.width);
gc.setAlpha(OPAQUE);
} else {
drawRect.width = 0;
}
if (drawRect.width <= 2) {
// Draw point over state
gc.drawPoint(rect.x, rect.y - 2);
if (drawRect.width == 2) {
gc.drawPoint(rect.x + 1, rect.y - 2);
}
} else {
// Draw the top and bottom borders
gc.drawLine(drawRect.x, drawRect.y, drawRect.x + drawRect.width - 1, drawRect.y);
gc.drawLine(drawRect.x, drawRect.y + drawRect.height - 1, drawRect.x + drawRect.width - 1, drawRect.y + drawRect.height - 1);
}
} else {
gc.drawPoint(rect.x, rect.y - 2);
}
fTimeGraphProvider.postDrawEvent(event, drawRect, gc);
return false;
}
int arc = Math.min(drawRect.height + 1, drawRect.width) / 2;
RGBAColor rgba = styleManager.getColorStyle(elementStyle, StyleProperties.BACKGROUND_COLOR);
int colorInt = (rgba != null) ? rgba.toInt() : OPAQUE;
Color color = getColor(colorInt);
int alpha = colorInt & 0xff;
boolean reallySelected = timeSelected && selected;
// fill all rect area
gc.setBackground(color);
boolean draw = visible || fBlendSubPixelEvents;
int old = gc.getAlpha();
if (draw) {
gc.setAlpha(visible ? alpha : alpha / 2);
if (arc >= 1) {
gc.fillRoundRectangle(drawRect.x, drawRect.y, drawRect.width, drawRect.height, arc, arc);
} else {
gc.fillRectangle(drawRect);
}
}
gc.setAlpha(OPAQUE);
//draw border line
Object borderStyle = styleManager.getStyle(elementStyle, StyleProperties.BORDER_STYLE);
if (borderStyle != null && !BorderStyle.NONE.equals(borderStyle)) {
rgba = styleManager.getColorStyle(elementStyle, StyleProperties.BORDER_COLOR);
int borderColorInt = (rgba != null) ? rgba.toInt() : OPAQUE;
int borderAlpha = borderColorInt & 0xff;
Color oldForeground = gc.getForeground();
int oldLineWidth = gc.getLineWidth();
int oldAlpha = gc.getAlpha();
Color borderColor = getColor(borderColorInt);
gc.setForeground(borderColor);
gc.setAlpha(borderAlpha);
Object borderWidth = styleManager.getStyle(elementStyle, StyleProperties.BORDER_WIDTH);
gc.setLineWidth((borderWidth instanceof Integer) ? (int) borderWidth : 1);
gc.drawRoundRectangle(drawRect.x, drawRect.y, drawRect.width, drawRect.height , arc, arc);
gc.setForeground(oldForeground);
gc.setLineWidth(oldLineWidth);
gc.setAlpha(oldAlpha);
}
if (reallySelected) {
gc.drawRoundRectangle(drawRect.x - 1, drawRect.y - 1, drawRect.width, drawRect.height + 1, arc, arc);
}
if (!visible) {
gc.drawPoint(rect.x, rect.y - 2);
}
if (visible && !Boolean.TRUE.equals(styleManager.getStyle(elementStyle, ITimeEventStyleStrings.annotated()))) {
String label = event.getLabel();
if (fLabelsVisible && label != null && !label.isEmpty() && rect.width > rect.height) {
gc.setForeground(Utils.getDistinctColor(color.getRGB()));
Utils.drawText(gc, label, drawRect.x, drawRect.y, drawRect.width, drawRect.height, true, true);
gc.setForeground(black);
}
fTimeGraphProvider.postDrawEvent(event, drawRect, gc);
}
gc.setAlpha(old);
return visible && !event.isPropertyActive(IFilterProperty.DIMMED);
}
private StyleManager getStyleManager() {
return (fTimeGraphProvider instanceof IStylePresentationProvider) ?
((IStylePresentationProvider) fTimeGraphProvider).getStyleManager() :
StyleManager.empty();
}
private @Nullable OutputElementStyle getElementStyle(ITimeEvent event) {
OutputElementStyle elementStyle;
if (fTimeGraphProvider instanceof IStylePresentationProvider) {
IStylePresentationProvider provider = (IStylePresentationProvider) fTimeGraphProvider;
elementStyle = provider.getElementStyle(event);
if (elementStyle == null) {
return null;
}
if (elementStyle.getParentKey() == null && elementStyle.getStyleValues().isEmpty()) {
return elementStyle;
}
return new OutputElementStyle(elementStyle.getParentKey(), applyEventStyleProperties(new HashMap<>(elementStyle.getStyleValues()), event));
}
if (!(event instanceof MarkerEvent)) {
int colorIdx = fTimeGraphProvider.getStateTableIndex(event);
if (colorIdx == ITimeGraphPresentationProvider.INVISIBLE) {
return null;
}
if (colorIdx == ITimeGraphPresentationProvider.TRANSPARENT) {
return new OutputElementStyle(null, new HashMap<>());
}
}
@NonNull Map<@NonNull String, @NonNull Object> styleMap = StylePropertiesUtils.updateEventStyleProperties(fTimeGraphProvider.getEventStyle(event));
return new OutputElementStyle(null, applyEventStyleProperties(styleMap, event));
}
private static @NonNull Map<@NonNull String, @NonNull Object> applyEventStyleProperties(@NonNull Map<@NonNull String, @NonNull Object> styleMap, ITimeEvent event) {
if (event.isPropertyActive(IFilterProperty.DIMMED)) {
float opacity = (float) styleMap.getOrDefault(StyleProperties.OPACITY, 1.0f);
styleMap.put(StyleProperties.OPACITY, opacity / DIMMED_ALPHA_COEFFICIENT);
styleMap.put(ITimeEventStyleStrings.annotated(), Boolean.TRUE);
}
if (event.isPropertyActive(IFilterProperty.BOUND)) {
styleMap.put(StyleProperties.BORDER_COLOR, HIGHLIGHTED_BOUND_COLOR);
styleMap.put(StyleProperties.BORDER_WIDTH, HIGHLIGHTED_BOUND_WIDTH);
styleMap.put(StyleProperties.BORDER_STYLE, BorderStyle.SOLID);
styleMap.put(ITimeEventStyleStrings.annotated(), Boolean.FALSE);
}
return styleMap;
}
private static Color getColor(int colorInt) {
String hexRGB = Integer.toHexString(colorInt);
Color color = COLOR_REGISTRY.get(hexRGB);
if (color == null) {
COLOR_REGISTRY.put(hexRGB, RGBAUtil.fromInt(colorInt).rgb);
color = COLOR_REGISTRY.get(hexRGB);
}
return color;
}
/**
* Fill an item's states rectangle
*
* @param rect
* The states rectangle
* @param gc
* Graphics context
* @param selected
* true if the item is selected
*/
protected void fillSpace(Rectangle rect, GC gc, boolean selected) {
/* Nothing to draw */
}
/**
* Draw a line at the middle height of a rectangle
*
* @param rect
* The rectangle
* @param gc
* Graphics context
*/
private void drawMidLine(Rectangle rect, GC gc) {
gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.MID_LINE));
int midy = rect.y + rect.height / 2;
gc.drawLine(rect.x, midy, rect.x + rect.width, midy);
}
private static int getMarginForHeight(int height) {
/*
* State rectangle is smaller than the item bounds when height is > 4. Don't use
* any margin if the height is below or equal that threshold. Use a maximum of 6
* pixels for both margins, otherwise try to use 13 pixels for the state height,
* but with a minimum margin of 1.
*/
final int MARGIN_THRESHOLD = 4;
final int PREFERRED_HEIGHT = 13;
final int MIN_MARGIN = 1;
final int MAX_MARGIN = 6;
return height <= MARGIN_THRESHOLD ? 0 : Math.max(Math.min(height - PREFERRED_HEIGHT, MAX_MARGIN), MIN_MARGIN);
}
private void setFontForHeight(int pixels, GC gc) {
/* convert font height from pixels to points */
int height = Math.max(pixels * PPI / DPI, 1);
Font font = fFonts.computeIfAbsent(height, fontHeight -> {
FontData fontData = gc.getFont().getFontData()[0];
fontData.setHeight(fontHeight);
return new Font(gc.getDevice(), fontData);
});
gc.setFont(font);
}
@Override
public void keyTraversed(TraverseEvent e) {
if ((e.detail == SWT.TRAVERSE_TAB_NEXT) || (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)) {
e.doit = true;
}
}
@Override
public void keyPressed(KeyEvent e) {
int idx = -1;
if (fItemData.fExpandedItems.length == 0) {
return;
}
if (SWT.HOME == e.keyCode) {
idx = 0;
} else if (SWT.END == e.keyCode) {
idx = fItemData.fExpandedItems.length - 1;
} else if (SWT.ARROW_DOWN == e.keyCode) {
idx = getSelectedIndex();
if (idx < 0) {
idx = 0;
} else {
// Skip invisible items
while (idx < fItemData.fExpandedItems.length - 1) {
idx++;
if (fItemData.fExpandedItems[idx].fItemHeight > 0) {
break;
}
}
}
} else if (SWT.ARROW_UP == e.keyCode) {
idx = getSelectedIndex();
if (idx < 0) {
idx = 0;
} else {
// Skip invisible items
while (idx > 0) {
idx--;
if (fItemData.fExpandedItems[idx].fItemHeight > 0) {
break;
}
}
}
} else if (SWT.ARROW_LEFT == e.keyCode && fDragState == DRAG_NONE) {
boolean extend = (e.stateMask & SWT.SHIFT) != 0;
selectPrevEvent(extend);
} else if (SWT.ARROW_RIGHT == e.keyCode && fDragState == DRAG_NONE) {
boolean extend = (e.stateMask & SWT.SHIFT) != 0;
selectNextEvent(extend);
} else if (SWT.PAGE_DOWN == e.keyCode) {
int page = countPerPage();
idx = getSelectedIndex();
if (idx < 0) {
idx = 0;
}
idx += page;
if (idx >= fItemData.fExpandedItems.length) {
idx = fItemData.fExpandedItems.length - 1;
}
} else if (SWT.PAGE_UP == e.keyCode) {
int page = countPerPage();
idx = getSelectedIndex();
if (idx < 0) {
idx = 0;
}
idx -= page;
if (idx < 0) {
idx = 0;
}
} else if (SWT.CR == e.keyCode) {
idx = getSelectedIndex();
if (idx >= 0) {
if (fItemData.fExpandedItems[idx].fHasChildren) {
toggle(idx);
} else {
fireDefaultSelection();
}
}
idx = -1;
} else if ((e.character == '+' || e.character == '=') && ((e.stateMask & SWT.CTRL) != 0)) {
fVerticalZoomAlignEntry = getVerticalZoomAlignSelection();
verticalZoom(true);
if (fVerticalZoomAlignEntry != null) {
setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
}
} else if (e.character == '-' && ((e.stateMask & SWT.CTRL) != 0)) {
fVerticalZoomAlignEntry = getVerticalZoomAlignSelection();
verticalZoom(false);
if (fVerticalZoomAlignEntry != null) {
setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
}
} else if (e.character == '0' && ((e.stateMask & SWT.CTRL) != 0)) {
fVerticalZoomAlignEntry = getVerticalZoomAlignSelection();
resetVerticalZoom();
if (fVerticalZoomAlignEntry != null) {
setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
}
} else if ((e.character == '+' || e.character == '=') && ((e.stateMask & SWT.CTRL) == 0)) {
if (fHasNamespaceFocus) {
ITimeGraphEntry entry = getSelectedTrace();
setExpandedState(entry, 0, true);
}
} else if (e.character == '-' && ((e.stateMask & SWT.CTRL) == 0)) {
if (fHasNamespaceFocus) {
ITimeGraphEntry entry = getSelectedTrace();
if ((entry != null) && entry.hasChildren()) {
setExpandedState(entry, -1, false);
}
}
} else if ((e.character == '*') && ((e.stateMask & SWT.CTRL) == 0)) {
if (fHasNamespaceFocus) {
ITimeGraphEntry entry = getSelectedTrace();
if ((entry != null) && entry.hasChildren()) {
setExpandedStateLevel(entry);
}
}
}
if (idx >= 0) {
selectItem(idx, false);
fireSelectionChanged();
}
int x = toControl(e.display.getCursorLocation()).x;
updateCursor(x, e.stateMask | e.keyCode);
}
@Override
public void keyReleased(KeyEvent e) {
int x = toControl(e.display.getCursorLocation()).x;
updateCursor(x, e.stateMask & ~e.keyCode);
}
@Override
public void focusGained(FocusEvent e) {
fIsInFocus = true;
redraw();
updateStatusLine(STATUS_WITHOUT_CURSOR_TIME);
}
@Override
public void focusLost(FocusEvent e) {
fIsInFocus = false;
if (DRAG_NONE != fDragState) {
setCapture(false);
fDragState = DRAG_NONE;
}
redraw();
updateStatusLine(NO_STATUS);
}
/**
* @return If the current view is focused
*/
public boolean isInFocus() {
return fIsInFocus;
}
/**
* Provide the possibility to control the wait cursor externally e.g. data
* requests in progress
*
* @param waitInd
* Should we wait indefinitely?
*/
public void waitCursor(boolean waitInd) {
// Update cursor as indicated
if (waitInd) {
setCursor(fWaitCursor);
} else {
setCursor(null);
}
}
private void updateCursor(int x, int stateMask) {
// if Wait cursor not active, check for the need to change the cursor
if (getCursor() == fWaitCursor) {
return;
}
Cursor cursor = null;
if (fDragState == DRAG_SPLIT_LINE) {
} else if (fDragState == DRAG_SELECTION) {
cursor = fResizeCursor;
} else if (fDragState == DRAG_TRACE_ITEM) {
cursor = fDragCursor;
} else if (fDragState == DRAG_ZOOM) {
cursor = fZoomCursor;
} else if ((stateMask & SWT.MODIFIER_MASK) == SWT.CTRL) {
cursor = fDragCursor;
} else if ((stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) {
cursor = fResizeCursor;
} else if (!isOverSplitLine(x)) {
long selectionBegin = fTimeProvider.getSelectionBegin();
long selectionEnd = fTimeProvider.getSelectionEnd();
int xBegin = getXForTime(selectionBegin);
int xEnd = getXForTime(selectionEnd);
if (Math.abs(x - xBegin) < SNAP_WIDTH || Math.abs(x - xEnd) < SNAP_WIDTH) {
cursor = fResizeCursor;
}
}
if (getCursor() != cursor) {
setCursor(cursor);
}
}
/**
* Update the status line following a change of selection.
*
* @since 2.0
*/
public void updateStatusLine() {
updateStatusLine(STATUS_WITHOUT_CURSOR_TIME);
}
private void updateStatusLine(int x) {
// use the time provider of the time graph scale for the status line
ITimeDataProvider tdp = fTimeGraphScale.getTimeProvider();
if (fStatusLineManager == null || null == tdp ||
tdp.getTime0() == tdp.getTime1()) {
return;
}
long cursorTime = -1;
long selectionBeginTime = 0;
long selectionEndTime = 0;
if ((x >= 0 || x == STATUS_WITHOUT_CURSOR_TIME) && fDragState == DRAG_NONE) {
if (x != STATUS_WITHOUT_CURSOR_TIME) {
long time = getTimeAtX(x);
if (time >= 0) {
if (tdp instanceof ITimeDataProviderConverter) {
time = ((ITimeDataProviderConverter) tdp).convertTime(time);
}
cursorTime = time;
}
}
selectionBeginTime = tdp.getSelectionBegin();
selectionEndTime = tdp.getSelectionEnd();
} else if (fDragState == DRAG_SELECTION || fDragState == DRAG_ZOOM) {
long time0 = fDragBeginMarker ? getTimeAtX(fDragX0) : fDragTime0;
long time = fDragBeginMarker ? fDragTime0 : getTimeAtX(fDragX);
if (tdp instanceof ITimeDataProviderConverter) {
time0 = ((ITimeDataProviderConverter) tdp).convertTime(time0);
time = ((ITimeDataProviderConverter) tdp).convertTime(time);
}
// Use the time of T2 to update the cursor time
cursorTime = time;
selectionBeginTime = time0;
selectionEndTime = time;
}
String message = buildStatusMessage(cursorTime, selectionBeginTime, selectionEndTime, tdp.getTimeFormat().convert(), Resolution.NANOSEC);
fStatusLineManager.setMessage(message);
}
private static String buildStatusMessage(long cursorTime, long selectionBeginTime, long selectionEndTime, TimeFormat tf, Resolution res) {
StringBuilder message = new StringBuilder();
if (cursorTime >= 0) {
message.append(NLS.bind("T: {0}{1} ", //$NON-NLS-1$
new Object[] {
tf == TimeFormat.CALENDAR ? FormatTimeUtils.formatDate(cursorTime) + ' ' : "", //$NON-NLS-1$
FormatTimeUtils.formatTime(cursorTime, tf, res)
}));
}
message.append(NLS.bind("T1: {0}{1}", //$NON-NLS-1$
new Object[] {
tf == TimeFormat.CALENDAR ? FormatTimeUtils.formatDate(selectionBeginTime) + ' ' : "", //$NON-NLS-1$
FormatTimeUtils.formatTime(selectionBeginTime, tf, res)
}));
if (selectionBeginTime != selectionEndTime) {
message.append(NLS.bind(" T2: {0}{1} \u0394: {2}", //$NON-NLS-1$
new Object[] {
tf == TimeFormat.CALENDAR ? FormatTimeUtils.formatDate(selectionEndTime) + ' ' : "", //$NON-NLS-1$
FormatTimeUtils.formatTime(selectionEndTime, tf, res),
FormatTimeUtils.formatDelta(selectionEndTime - selectionBeginTime, tf, res)
}));
}
return message.toString();
}
@Override
public void mouseMove(MouseEvent e) {
if (null == fTimeProvider) {
return;
}
Point size = getSize();
if (DRAG_TRACE_ITEM == fDragState) {
int nameWidth = fTimeProvider.getNameSpace();
if (e.x > nameWidth && size.x > nameWidth && fDragX != e.x) {
fDragX = e.x;
double pixelsPerNanoSec = (size.x - nameWidth <= RIGHT_MARGIN) ? 0 : (double) (size.x - nameWidth - RIGHT_MARGIN) / (fTime1bak - fTime0bak);
long timeDelta = (long) ((pixelsPerNanoSec == 0) ? 0 : ((fDragX - fDragX0) / pixelsPerNanoSec));
long time1 = fTime1bak - timeDelta;
long maxTime = fTimeProvider.getMaxTime();
if (time1 > maxTime) {
time1 = maxTime;
}
long time0 = time1 - (fTime1bak - fTime0bak);
if (time0 < fTimeProvider.getMinTime()) {
time0 = fTimeProvider.getMinTime();
time1 = time0 + (fTime1bak - fTime0bak);
}
fTimeProvider.setStartFinishTimeNotify(time0, time1);
setElementPosition(fDragEntry, e.y);
}
} else if (DRAG_SPLIT_LINE == fDragState) {
fDragX = e.x;
fTimeProvider.setNameSpace(e.x);
TmfSignalManager.dispatchSignal(new TmfTimeViewAlignmentSignal(this, getTimeViewAlignmentInfo()));
} else if (DRAG_SELECTION == fDragState) {
if (fDragBeginMarker) {
fDragX0 = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), size.x - RIGHT_MARGIN);
} else {
fDragX = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), size.x - RIGHT_MARGIN);
}
redraw();
fTimeGraphScale.setDragRange(fDragX0, fDragX);
fireDragSelectionChanged(getTimeAtX(fDragX0), getTimeAtX(fDragX));
} else if (DRAG_ZOOM == fDragState) {
fDragX = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), size.x - RIGHT_MARGIN);
redraw();
fTimeGraphScale.setDragRange(fDragX0, fDragX);
} else if (DRAG_NONE == fDragState) {
boolean mouseOverSplitLine = isOverSplitLine(e.x);
if (fMouseOverSplitLine != mouseOverSplitLine) {
redraw();
}
fMouseOverSplitLine = mouseOverSplitLine;
}
if (e.x >= fTimeProvider.getNameSpace()) {
fHasNamespaceFocus = false;
} else {
fHasNamespaceFocus = true;
}
updateCursor(e.x, e.stateMask);
updateStatusLine(e.x);
}
@Override
public void mouseDoubleClick(MouseEvent e) {
if (null == fTimeProvider) {
return;
}
if (1 == e.button && (e.stateMask & SWT.BUTTON_MASK) == 0) {
if (isOverSplitLine(e.x) && fTimeProvider.getNameSpace() != 0) {
fTimeProvider.setNameSpace(fIdealNameSpace);
boolean mouseOverSplitLine = isOverSplitLine(e.x);
if (fMouseOverSplitLine != mouseOverSplitLine) {
redraw();
}
fMouseOverSplitLine = mouseOverSplitLine;
TmfSignalManager.dispatchSignal(new TmfTimeViewAlignmentSignal(this, getTimeViewAlignmentInfo()));
return;
}
int idx = getItemIndexAtY(e.y);
if (idx >= 0) {
selectItem(idx, false);
fireDefaultSelection();
}
}
}
@Override
public void mouseDown(MouseEvent e) {
if (fDragState != DRAG_NONE) {
return;
}
if (1 == e.button && (e.stateMask & SWT.MODIFIER_MASK) == 0) {
int nameSpace = fTimeProvider.getNameSpace();
if (nameSpace != 0 && isOverSplitLine(e.x)) {
fDragState = DRAG_SPLIT_LINE;
fDragButton = e.button;
fDragX = e.x;
fDragX0 = fDragX;
redraw();
updateCursor(e.x, e.stateMask);
return;
}
}
if (1 == e.button && ((e.stateMask & SWT.MODIFIER_MASK) == 0 || (e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT)) {
int nameSpace = fTimeProvider.getNameSpace();
int idx = getItemIndexAtY(e.y);
if (idx >= 0) {
Item item = fItemData.fExpandedItems[idx];
if (item.fHasChildren && e.x < nameSpace && e.x < MARGIN + (item.fLevel + 1) * EXPAND_SIZE) {
toggle(idx);
return;
}
selectItem(idx, false);
fireSelectionChanged();
} else {
selectItem(idx, false); // clear selection
fireSelectionChanged();
}
} else if (3 == e.button && e.x < fTimeProvider.getNameSpace()) {
int idx = getItemIndexAtY(e.y);
selectItem(idx, false);
fireSelectionChanged();
}
if (fTimeProvider == null ||
fTimeProvider.getTime0() == fTimeProvider.getTime1() ||
getSize().x - fTimeProvider.getNameSpace() <= 0) {
return;
}
if (1 == e.button && ((e.stateMask & SWT.MODIFIER_MASK) == 0 || (e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT)) {
long hitTime = getTimeAtX(e.x);
if (hitTime >= 0) {
setCapture(true);
fDragState = DRAG_SELECTION;
fDragBeginMarker = false;
fDragButton = e.button;
fDragX = e.x;
fDragX0 = fDragX;
fDragTime0 = getTimeAtX(fDragX0);
long selectionBegin = fTimeProvider.getSelectionBegin();
long selectionEnd = fTimeProvider.getSelectionEnd();
int xBegin = getXForTime(selectionBegin);
int xEnd = getXForTime(selectionEnd);
if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) {
long time = getTimeAtX(e.x);
if (Math.abs(time - selectionBegin) < Math.abs(time - selectionEnd)) {
fDragBeginMarker = true;
fDragX = xEnd;
fDragX0 = e.x;
fDragTime0 = selectionEnd;
} else {
fDragX0 = xBegin;
fDragTime0 = selectionBegin;
}
} else {
long time = getTimeAtX(e.x);
if (Math.abs(e.x - xBegin) < SNAP_WIDTH && Math.abs(time - selectionBegin) <= Math.abs(time - selectionEnd)) {
fDragBeginMarker = true;
fDragX = xEnd;
fDragX0 = e.x;
fDragTime0 = selectionEnd;
} else if (Math.abs(e.x - xEnd) < SNAP_WIDTH && Math.abs(time - selectionEnd) <= Math.abs(time - selectionBegin)) {
fDragX0 = xBegin;
fDragTime0 = selectionBegin;
}
}
fTime0bak = fTimeProvider.getTime0();
fTime1bak = fTimeProvider.getTime1();
redraw();
updateCursor(e.x, e.stateMask);
fTimeGraphScale.setDragRange(fDragX0, fDragX);
}
} else if (2 == e.button || (1 == e.button && (e.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL)) {
long hitTime = getTimeAtX(e.x);
if (hitTime > 0) {
setCapture(true);
fDragState = DRAG_TRACE_ITEM;
fDragButton = e.button;
fDragX = e.x;
fDragX0 = fDragX;
fDragEntry = getExpandedElement(getItemIndexAtY(e.y));
fTime0bak = fTimeProvider.getTime0();
fTime1bak = fTimeProvider.getTime1();
updateCursor(e.x, e.stateMask);
}
} else if (3 == e.button) {
if (e.x >= fTimeProvider.getNameSpace()) {
setCapture(true);
fDragX = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), getSize().x - RIGHT_MARGIN);
fDragX0 = fDragX;
fDragTime0 = getTimeAtX(fDragX0);
fDragState = DRAG_ZOOM;
fDragButton = e.button;
redraw();
updateCursor(e.x, e.stateMask);
fTimeGraphScale.setDragRange(fDragX0, fDragX);
}
}
}
@Override
public void mouseUp(MouseEvent e) {
if (fPendingMenuDetectEvent != null && e.button == 3) {
if ((fDragState == DRAG_ZOOM) && isInDragZoomMargin()) {
// Select entry and time event for single click
long time = getTimeAtX(fDragX0);
fTimeProvider.setSelectionRangeNotify(time, time, false);
int idx = getItemIndexAtY(e.y);
selectItem(idx, false);
fireSelectionChanged();
}
menuDetected(fPendingMenuDetectEvent);
}
if (DRAG_NONE != fDragState) {
setCapture(false);
if (e.button == fDragButton && DRAG_TRACE_ITEM == fDragState) {
fDragState = DRAG_NONE;
fDragEntry = null;
if (fDragX != fDragX0) {
fTimeProvider.notifyStartFinishTime();
}
} else if (e.button == fDragButton && DRAG_SPLIT_LINE == fDragState) {
fDragState = DRAG_NONE;
redraw();
} else if (e.button == fDragButton && DRAG_SELECTION == fDragState) {
fDragState = DRAG_NONE;
if (fDragX == fDragX0) { // click without selecting anything
long time = getTimeAtX(e.x);
fTimeProvider.setSelectedTimeNotify(time, false);
} else {
long time0 = fDragBeginMarker ? getTimeAtX(fDragX0) : fDragTime0;
long time1 = fDragBeginMarker ? fDragTime0 : getTimeAtX(fDragX);
fTimeProvider.setSelectionRangeNotify(time0, time1, false);
}
redraw();
fTimeGraphScale.setDragRange(-1, -1);
} else if (e.button == fDragButton && DRAG_ZOOM == fDragState) {
fDragState = DRAG_NONE;
int nameWidth = fTimeProvider.getNameSpace();
if ((Math.max(fDragX, fDragX0) > nameWidth) && !isInDragZoomMargin()) {
long time0 = getTimeAtX(fDragX0);
long time1 = getTimeAtX(fDragX);
if (time0 < time1) {
fTimeProvider.setStartFinishTimeNotify(time0, time1);
} else {
fTimeProvider.setStartFinishTimeNotify(time1, time0);
}
} else {
redraw();
}
fTimeGraphScale.setDragRange(-1, -1);
}
}
updateCursor(e.x, e.stateMask);
updateStatusLine(e.x);
}
@Override
public void mouseEnter(MouseEvent e) {
// Do nothing
}
@Override
public void mouseExit(MouseEvent e) {
if (fMouseOverSplitLine) {
fMouseOverSplitLine = false;
redraw();
}
updateStatusLine(STATUS_WITHOUT_CURSOR_TIME);
}
@Override
public void mouseHover(MouseEvent e) {
// Do nothing
}
@Override
public void mouseScrolled(MouseEvent e) {
if (fDragState != DRAG_NONE || e.count == 0) {
return;
}
/*
* On some platforms the mouse scroll event is sent to the control that has
* focus even if it is not under the cursor. Handle the event only if over the
* time graph control.
*/
Point size = getSize();
Rectangle bounds = new Rectangle(0, 0, size.x, size.y);
if (!bounds.contains(e.x, e.y)) {
return;
}
boolean horizontalZoom = false;
boolean horizontalScroll = false;
boolean verticalZoom = false;
boolean verticalScroll = false;
// over the time graph control
if ((e.stateMask & SWT.MODIFIER_MASK) == (SWT.SHIFT | SWT.CTRL)) {
verticalZoom = true;
} else if (e.x < fTimeProvider.getNameSpace()) {
// over the name space
verticalScroll = true;
} else {
// over the state area
if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL) {
// over the state area, CTRL pressed
horizontalZoom = true;
} else if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) {
// over the state area, SHIFT pressed
horizontalScroll = true;
} else {
// over the state area, no modifier pressed
verticalScroll = true;
}
}
if (verticalZoom) {
fVerticalZoomAlignEntry = getVerticalZoomAlignCursor(e.y);
verticalZoom(e.count > 0);
if (fVerticalZoomAlignEntry != null) {
setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
}
} else if (horizontalZoom && fTimeProvider.getTime0() != fTimeProvider.getTime1()) {
zoom(e.count > 0);
} else if (horizontalScroll) {
horizontalScroll(e.count > 0);
} else if (verticalScroll) {
setTopIndex(getTopIndex() - e.count);
}
}
/**
* Get the vertical zoom alignment entry and position based on the current
* selection. If there is no selection or if the selection is not visible,
* return an alignment entry with a null time graph entry.
*
* @return a map entry where the key is the selection's time graph entry and the
* value is the center y-coordinate of that entry, or null
*/
private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignSelection() {
Entry<ITimeGraphEntry, Integer> alignEntry = getVerticalZoomAlignOngoing();
if (alignEntry != null) {
return alignEntry;
}
int index = getSelectedIndex();
if (index == -1 || index >= getExpandedElementCount()) {
return new SimpleEntry<>(null, 0);
}
Rectangle bounds = getClientArea();
Rectangle itemRect = getItemRect(bounds, index);
if (itemRect.y < bounds.y || itemRect.y > bounds.y + bounds.height) {
/* selection is not visible */
return new SimpleEntry<>(null, 0);
}
ITimeGraphEntry entry = getExpandedElement(index);
int y = itemRect.y + itemRect.height / 2;
return new SimpleEntry<>(entry, y);
}
/**
* Get the vertical zoom alignment entry and position at the specified cursor
* position.
*
* @param y
* the cursor y-coordinate
* @return a map entry where the key is the time graph entry under the cursor
* and the value is the cursor y-coordinate
*/
private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignCursor(int y) {
Entry<ITimeGraphEntry, Integer> alignEntry = getVerticalZoomAlignOngoing();
if (alignEntry != null) {
return alignEntry;
}
int index = getItemIndexAtY(y);
if (index == -1) {
index = getExpandedElementCount() - 1;
}
ITimeGraphEntry entry = getExpandedElement(index);
return new SimpleEntry<>(entry, y);
}
/**
* Get the vertical zoom alignment entry and position if there is an ongoing one
* and we are within the vertical zoom delay, or otherwise return null.
*
* @return a map entry where the key is a time graph entry and the value is a
* y-coordinate, or null
*/
private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignOngoing() {
long currentTimeMillis = System.currentTimeMillis();
if (currentTimeMillis < fVerticalZoomAlignTime + VERTICAL_ZOOM_DELAY) {
/*
* If the vertical zoom is triggered repeatedly in a short amount of time, use
* the initial event's entry and position.
*/
fVerticalZoomAlignTime = currentTimeMillis;
return fVerticalZoomAlignEntry;
}
fVerticalZoomAlignTime = currentTimeMillis;
return null;
}
@Override
public void handleEvent(Event event) {
if (event.type == SWT.MouseWheel) {
// prevent horizontal scrolling when the mouse wheel is used to
// scroll vertically or zoom
event.doit = false;
}
}
@Override
public int getBorderWidth() {
return fBorderWidth;
}
/**
* Set the border width
*
* @param borderWidth
* The width
*/
public void setBorderWidth(int borderWidth) {
this.fBorderWidth = borderWidth;
}
/**
* @return The current height of the header row
*/
public int getHeaderHeight() {
return fHeaderHeight;
}
/**
* Set the height of the header row
*
* @param headerHeight
* The height
*/
public void setHeaderHeight(int headerHeight) {
this.fHeaderHeight = headerHeight;
}
/**
* @return The default height of regular item rows
*/
public int getItemHeight() {
return fGlobalItemHeight;
}
/**
* Set the default height of regular item rows.
*
* @param rowHeight
* The height
*/
public void setItemHeight(int rowHeight) {
this.fGlobalItemHeight = rowHeight;
for (Item item : fItemData.fItems) {
item.fItemHeight = rowHeight;
}
}
/**
* Set the height of a specific item. Overrides the default item height.
*
* @param entry
* A time graph entry
* @param rowHeight
* The height
* @return true if the height is successfully stored, false otherwise
*/
public boolean setItemHeight(ITimeGraphEntry entry, int rowHeight) {
Item item = fItemData.findItem(entry);
if (item != null) {
item.fItemHeight = rowHeight;
return true;
}
return false;
}
/**
* Set the minimum item width
*
* @param width
* The minimum width
*/
public void setMinimumItemWidth(int width) {
this.fMinimumItemWidth = width;
}
/**
* @return The minimum item width
*/
public int getMinimumItemWidth() {
return fMinimumItemWidth;
}
/**
* Set whether all time events with a duration shorter than one pixel should be
* blended in. If false, only the first such time event will be drawn and the
* subsequent time events in the same pixel will be discarded. The default value
* is false.
*
* @param blend
* true if sub-pixel events should be blended, false otherwise.
* @since 1.1
*/
public void setBlendSubPixelEvents(boolean blend) {
fBlendSubPixelEvents = blend;
}
@Override
public void addSelectionChangedListener(ISelectionChangedListener listener) {
if (listener != null && !fSelectionChangedListeners.contains(listener)) {
fSelectionChangedListeners.add(listener);
}
}
@Override
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
if (listener != null) {
fSelectionChangedListeners.remove(listener);
}
}
@Override
public void setSelection(ISelection selection) {
if (selection instanceof IStructuredSelection) {
Object ob = ((IStructuredSelection) selection).getFirstElement();
if (ob instanceof ITimeGraphEntry) {
selectItem((ITimeGraphEntry) ob, false);
}
}
}
/**
* Add a new viewer filter object
*
* @param filter
* The filter object to be attached to the view
* @since 3.1
*/
public void addFilter(@NonNull ViewerFilter filter) {
fFilters.add(filter);
fireFiltersAdded(Collections.singleton(filter));
}
/**
* Change the viewer filter object
*
* The filter elements are already updated, we only let the listener know that a
* change happened at this point
*
* @param filter
* The filter object to be attached to the view
* @since 3.2
*/
public void changeFilter(@NonNull ViewerFilter filter) {
fireFiltersChanged(Collections.singleton(filter));
}
/**
* Remove a viewer filter object
*
* @param filter
* The filter object to be attached to the view
*/
public void removeFilter(@NonNull ViewerFilter filter) {
fFilters.remove(filter);
fireFiltersRemoved(Collections.singleton(filter));
}
/**
* Returns this control's filters.
*
* @return an array of viewer filters
* @since 1.2
*/
public @NonNull ViewerFilter[] getFilters() {
return Iterables.toArray(fFilters, ViewerFilter.class);
}
/**
* Sets the filters, replacing any previous filters.
*
* @param filters
* an array of viewer filters, or null
* @since 1.2
*/
public void setFilters(@NonNull ViewerFilter[] filters) {
fFilters.clear();
if (filters != null) {
List<@NonNull ViewerFilter> filtersList = Arrays.asList(filters);
fFilters.addAll(filtersList);
fireFiltersChanged(filtersList);
}
}
@Override
public void colorSettingsChanged(StateItem[] stateItems) {
/* Destroy previous colors from the resource manager */
if (fEventColorMap != null) {
for (Color color : fEventColorMap) {
fResourceManager.destroyColor(color.getRGB());
}
}
if (stateItems != null) {
fEventColorMap = new Color[stateItems.length];
for (int i = 0; i < stateItems.length; i++) {
fEventColorMap[i] = fResourceManager.createColor(stateItems[i].getStateColor());
}
} else {
fEventColorMap = new Color[] {};
}
redraw();
}
private class ItemData {
private Map<ITimeGraphEntry, Item> fItemMap = new LinkedHashMap<>();
private Item[] fExpandedItems = new Item[0];
private Item[] fItems = new Item[0];
private ITimeGraphEntry fRootEntries[] = new ITimeGraphEntry[0];
private List<ILinkEvent> fLinks = new ArrayList<>();
public ItemData() {
// Do nothing
}
public Item findItem(ITimeGraphEntry entry) {
return fItemMap.get(entry);
}
public int findItemIndex(ITimeGraphEntry entry) {
Item item = fItemMap.get(entry);
if (item == null) {
return -1;
}
return item.fExpandedIndex;
}
public void refreshData() {
ITimeGraphEntry selection = getSelectedTrace();
Map<ITimeGraphEntry, Item> itemMap = new LinkedHashMap<>();
fMaxItemHeight = 0;
for (int i = 0; i < fRootEntries.length; i++) {
ITimeGraphEntry entry = fRootEntries[i];
refreshData(itemMap, null, 0, entry);
}
fItemMap = itemMap;
fItems = fItemMap.values().toArray(new Item[0]);
updateExpandedItems();
if (selection != null) {
for (Item item : fExpandedItems) {
if (item.fEntry == selection) {
item.fSelected = true;
break;
}
}
}
}
private void refreshData(Map<ITimeGraphEntry, Item> itemMap, Item parent, int level, ITimeGraphEntry entry) {
Item item = new Item(entry, entry.getName(), level);
if (parent != null) {
parent.fChildren.add(item);
}
if (fGlobalItemHeight == CUSTOM_ITEM_HEIGHT) {
item.fItemHeight = fTimeGraphProvider.getItemHeight(entry);
} else {
item.fItemHeight = fGlobalItemHeight;
}
fMaxItemHeight = Math.max(fMaxItemHeight, item.fItemHeight);
item.fItemHeight = Math.max(1, item.fItemHeight + fHeightAdjustment);
itemMap.put(entry, item);
if (entry.hasChildren()) {
Item oldItem = fItemMap.get(entry);
if (oldItem != null && oldItem.fHasChildren && level == oldItem.fLevel && entry.getParent() == oldItem.fEntry.getParent()) {
/* existing items keep their old expanded state */
item.fExpanded = oldItem.fExpanded;
} else {
/*
* new items set the expanded state according to auto-expand level
*/
item.fExpanded = fAutoExpandLevel == ALL_LEVELS || level < fAutoExpandLevel;
}
item.fHasChildren = true;
for (ITimeGraphEntry child : entry.getChildren()) {
refreshData(itemMap, item, level + 1, child);
}
}
}
public void updateExpandedItems() {
for (Item item : fItems) {
item.fExpandedIndex = -1;
}
List<Item> expandedItemList = new ArrayList<>();
for (int i = 0; i < fRootEntries.length; i++) {
ITimeGraphEntry entry = fRootEntries[i];
Item item = findItem(entry);
refreshExpanded(expandedItemList, item);
}
if (hasSavedFilters()) {
filterData(expandedItemList);
}
fExpandedItems = expandedItemList.toArray(new Item[0]);
fTopIndex = Math.min(fTopIndex, Math.max(0, fExpandedItems.length - 1));
}
private boolean hasEvents(Item item) {
ITimeGraphEntry entry = item.fEntry;
return (!entry.hasTimeEvents() || (entry instanceof TimeGraphEntry && ((TimeGraphEntry) entry).hasZoomedEvents()));
}
private void filterData(List<Item> expandedItemList) {
for (Item item : expandedItemList) {
if (!hasEvents(item)) {
item.fItemHeight = 0;
}
}
}
private void refreshExpanded(Collection<Item> expandedItemList, Item item) {
// Check for filters
boolean display = true;
for (ViewerFilter filter : fFilters) {
if (!filter.select(null, item.fEntry.getParent(), item.fEntry)) {
display = false;
break;
}
}
if (display) {
item.fExpandedIndex = expandedItemList.size();
expandedItemList.add(item);
if (item.fHasChildren && item.fExpanded) {
for (Item child : item.fChildren) {
refreshExpanded(expandedItemList, child);
}
}
}
}
public void refreshData(ITimeGraphEntry[] entries) {
if (entries == null) {
fRootEntries = null;
} else {
fRootEntries = Arrays.copyOf(entries, entries.length);
}
refreshData();
}
public void refreshArrows(List<ILinkEvent> events) {
/* If links are null, reset the list */
if (events != null) {
fLinks = events;
} else {
fLinks = new ArrayList<>();
}
}
public ITimeGraphEntry[] getEntries() {
return fRootEntries;
}
}
private class Item {
private boolean fExpanded;
private int fExpandedIndex;
private boolean fSelected;
private boolean fHasChildren;
private int fItemHeight;
private final int fLevel;
private final List<Item> fChildren;
private final String fName;
private final ITimeGraphEntry fEntry;
public Item(ITimeGraphEntry entry, String name, int level) {
this.fEntry = entry;
this.fName = name;
this.fLevel = level;
this.fChildren = new ArrayList<>();
}
@Override
public String toString() {
return fName;
}
}
@Override
public void menuDetected(MenuDetectEvent e) {
if (null == fTimeProvider) {
return;
}
/*
* This flag indicates if menu was prevented from being shown below and
* therefore must be made visible on callback from mouseUp().
*/
boolean pendingEventCallback = fPendingMenuDetectEvent != null;
Point p = toControl(e.x, e.y);
if (e.detail == SWT.MENU_MOUSE && isOverTimeSpace(p.x, p.y)) {
if (fPendingMenuDetectEvent == null) {
/*
* Feature in Linux. The MenuDetectEvent is received before mouseDown. Store the
* event and trigger it later just before handling mouseUp. This allows for the
* method to detect if mouse is used to drag zoom.
*/
fPendingMenuDetectEvent = e;
/*
* Prevent the platform to show the menu when returning. The menu will be shown
* (see below) when this method is called again during mouseUp().
*/
e.doit = false;
return;
}
fPendingMenuDetectEvent = null;
if (fDragState != DRAG_ZOOM || !isInDragZoomMargin()) {
/*
* Don't show the menu on mouseUp() if a drag zoom is in progress with a drag
* range outside of the drag zoom margin, or if any other drag operation, or
* none, is in progress.
*/
e.doit = false;
return;
}
} else {
if (fDragState != DRAG_NONE) {
/*
* Don't show the menu on keyboard menu or mouse menu outside of the time space
* if any drag operation is in progress.
*/
e.doit = false;
return;
}
}
int idx = getItemIndexAtY(p.y);
if (idx >= 0 && idx < fItemData.fExpandedItems.length) {
Item item = fItemData.fExpandedItems[idx];
ITimeGraphEntry entry = item.fEntry;
/* Send menu event for the time graph entry */
e.doit = true;
e.data = entry;
fireMenuEventOnTimeGraphEntry(e);
Menu menu = getMenu();
if (pendingEventCallback && e.doit && (menu != null)) {
menu.setVisible(true);
}
/* Send menu event for time event */
if (entry.hasTimeEvents()) {
ITimeEvent event = Utils.findEvent(entry, getTimeAtX(p.x), 2);
if (event != null) {
e.doit = true;
e.data = event;
fireMenuEventOnTimeEvent(e);
menu = getMenu();
if (pendingEventCallback && e.doit && (menu != null)) {
menu.setVisible(true);
}
}
}
}
}
/**
* Perform the alignment operation.
*
* @param offset
* the alignment offset
*
* @see ITmfTimeAligned
*
* @since 1.0
*/
public void performAlign(int offset) {
fTimeProvider.setNameSpace(offset);
}
/**
* Return the time alignment information
*
* @return the time alignment information
*
* @see ITmfTimeAligned
*
* @since 1.0
*/
public TmfTimeViewAlignmentInfo getTimeViewAlignmentInfo() {
return new TmfTimeViewAlignmentInfo(getShell(), toDisplay(0, 0), fTimeProvider.getNameSpace());
}
private boolean isInDragZoomMargin() {
return (Math.abs(fDragX - fDragX0) < DRAG_MARGIN);
}
/**
* Set the filtering status of the timegraph
* @param isActive True whether there is active filters, false otherwise
* @since 4.0
*/
public void setFilterActive(boolean isActive) {
fFilterActive = isActive;
}
/**
* Test if a time event filter is applied, basically it tells if the view is in
* filter mode
*
* @return True if the view is in filter mode, false otherwise
* @since 4.0
*
*/
public boolean isFilterActive() {
return fFilterActive;
}
/**
* Set whether filters have been saved or not.
*
* @param hasSavedFilter
* The saved filter status
* @since 4.0
*/
public void setSavedFilterStatus(boolean hasSavedFilter) {
fHasSavedFilters = hasSavedFilter;
}
/**
* Tells whether the timegraph has saved filters or not
*
* @return True whether there is saved filters, false otherwise
* @since 4.0
*/
public boolean hasSavedFilters() {
return fHasSavedFilters;
}
}