/*****************************************************************************
- * Copyright (c) 2007, 2015 Intel Corporation and others
+ * Copyright (c) 2007, 2016 Intel Corporation and others
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
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.Collections;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Queue;
+import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
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.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.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.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo;
import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal;
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.ITimeGraphEntry;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.Utils.Resolution;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.Utils.TimeFormat;
+import com.google.common.collect.Iterables;
+
/**
* Time graph control implementation
*
/** Constant indicating that all levels of the time graph should be expanded */
public static final int ALL_LEVELS = AbstractTreeViewer.ALL_LEVELS;
+ 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 double ZOOM_IN_FACTOR = 0.8;
private static final double ZOOM_OUT_FACTOR = 1.25;
- private static final int SNAP_WIDTH = 2;
+ private static final int SNAP_WIDTH = 3;
private static final int ARROW_HOVER_MAX_DIST = 5;
+ private static final double ARROW_RATIO = Math.sqrt(3) / 2; // base to height ratio
+
private static final int NO_STATUS = -1;
private static final int STATUS_WITHOUT_CURSOR_TIME = -2;
+ private static final int MAX_LABEL_LENGTH = 256;
+
+ private static final int PPI = 72; // points per inch
+ private static final int DPI = Display.getDefault().getDPI().y;
+
+ private static final int VERTICAL_ZOOM_DELAY = 400;
+
+ private static final String PREFERRED_WIDTH = "width"; //$NON-NLS-1$
+
/** Resource manager */
private LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources());
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 Map<Integer, Font> fFonts = new HashMap<>();
+ private boolean fBlendSubPixelEvents = false;
private int fMinimumItemWidth = 0;
private int fTopIndex = 0;
private int fDragState = DRAG_NONE;
private int fDragButton;
private int fDragX0 = 0;
private int fDragX = 0;
+ private boolean fHasNamespaceFocus = false;
private long fDragTime0 = 0; // used to preserve accuracy of modified selection
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 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 List<ViewerFilter> fFilters = new ArrayList<>();
+ private final List<@NonNull ViewerFilter> fFilters = new ArrayList<>();
private MenuDetectEvent fPendingMenuDetectEvent = null;
+ private boolean fGridLinesVisible = true;
+ private Color fGridLineColor = Display.getDefault().getSystemColor(SWT.COLOR_GRAY);
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;
addKeyListener(this);
addMenuDetectListener(this);
addListener(SWT.MouseWheel, this);
- }
-
- @Override
- public void dispose() {
- super.dispose();
- fResourceManager.dispose();
+ addDisposeListener((e) -> {
+ fResourceManager.dispose();
+ for (Font font : fFonts.values()) {
+ font.dispose();
+ }
+ });
}
/**
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.
*
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
*
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) {
+ for (TreeColumn column : fTree.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(fTree, SWT.LEFT);
+ column.setMoveable(true);
+ column.setText(columnName);
+ column.pack();
+ column.setData(PREFERRED_WIDTH, column.getWidth());
+ column.addControlListener(controlListener);
+ }
+ }
+
/**
* Assign the time graph scale
*
listener.widgetSelected(null);
}
}
+
+ if (null != fSelectionChangedListeners) {
+ for (ISelectionChangedListener listener : fSelectionChangedListeners) {
+ listener.selectionChanged(new SelectionChangedEvent(this, getSelection()));
+ }
+ }
}
/**
return fItemData.getEntries();
}
- /**
- * Get the on/off trace filters
- *
- * @return The array of filters
- */
- public boolean[] getTraceFilter() {
- return fItemData.getEntryFilter();
- }
-
/**
* Refresh the data for the thing
*/
*
* @return The unmodifiable link event list
*
- * @since 2.0
+ * @since 1.1
*/
public List<ILinkEvent> getArrows() {
return Collections.unmodifiableList(fItemData.fLinks);
}
/**
- * Sets the auto-expand level to be used when the entries are refreshed
- * using {@link #refreshData()} or {@link #refreshData(ITimeGraphEntry[])}.
- * The value 0 means that there is no auto-expand; 1 means that top-level
+ * 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
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
*
}
}
+ /**
+ * 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.
*/
}
}
+ @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() {
- TimeGraphSelection sel = new TimeGraphSelection();
- ITimeGraphEntry trace = getSelectedTrace();
- if (null != trace && null != fTimeProvider) {
+ ITimeGraphEntry entry = getSelectedTrace();
+ if (null != entry && null != fTimeProvider) {
long selectedTime = fTimeProvider.getSelectionBegin();
- ITimeEvent event = Utils.findEvent(trace, selectedTime, 0);
- if (event != null) {
- sel.add(event);
- } else {
- sel.add(trace);
+ ITimeEvent event = Utils.findEvent(entry, selectedTime, 0);
+ if (event == null) {
+ return new StructuredSelection(entry);
}
+ return new StructuredSelection(new Object[] { entry, event });
}
- return sel;
+ return StructuredSelection.EMPTY;
}
/**
* @return The selection
*/
public ISelection getSelectionTrace() {
- TimeGraphSelection sel = new TimeGraphSelection();
- ITimeGraphEntry trace = getSelectedTrace();
- if (null != trace) {
- sel.add(trace);
+ ITimeGraphEntry entry = getSelectedTrace();
+ if (null != entry) {
+ return new StructuredSelection(entry);
}
- return sel;
+ return StructuredSelection.EMPTY;
}
/**
nextTime = nextEvent.getTime() + nextEvent.getDuration();
}
if (extend) {
- fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), nextTime);
+ fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), nextTime, true);
} else {
fTimeProvider.setSelectedTimeNotify(nextTime, true);
}
fireSelectionChanged();
} else if (n == 1) {
if (extend) {
- fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), endTime);
+ fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), endTime, true);
} else {
fTimeProvider.setSelectedTimeNotify(endTime, true);
}
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--;
+ }
+ fItemData.refreshData();
+ redraw();
+ }
+
+ /**
+ * Reset the vertical zoom to default.
+ *
+ * @since 2.0
+ */
+ public void resetVerticalZoom() {
+ fHeightAdjustment = 0;
+ fItemData.refreshData();
+ redraw();
+ }
+
+ /**
+ * Set the 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;
+ }
+
+ /**
+ * Get the 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;
+ }
+
+ /**
+ * Get the grid line color.
+ *
+ * @return the grid line color
+ * @since 2.0
+ */
+ public Color getGridLineColor() {
+ return fGridLineColor;
+ }
+
+ /**
+ * 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
*
selectItem(link.getDestinationEntry(), false);
if (link.getDuration() != 0) {
if (extend) {
- fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime() + link.getDuration());
+ fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime() + link.getDuration(), true);
} else {
fTimeProvider.setSelectedTimeNotify(link.getTime() + link.getDuration(), true);
}
selectItem(link.getEntry(), false);
if (link.getDuration() != 0) {
if (extend) {
- fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime());
+ fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime(), true);
} else {
fTimeProvider.setSelectedTimeNotify(link.getTime(), true);
}
* @return the index of the item at the given location, of -1 if none.
*/
protected int getItemIndexAtY(int y) {
- if (y < 0) {
- return -1;
- }
int ySum = 0;
- for (int idx = fTopIndex; idx < fItemData.fExpandedItems.length; idx++) {
- ySum += fItemData.fExpandedItems[idx].fItemHeight;
- if (y < ySum) {
- return idx;
+ 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;
return false;
}
int nameWidth = fTimeProvider.getNameSpace();
- return Math.abs(x - nameWidth) < SNAP_WIDTH;
+ 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.
*
* a point in the widget
* @return the {@link ITimeGraphEntry} at this point, or <code>null</code>
* if none.
+ * @since 2.0
*/
- protected ITimeGraphEntry getEntry(Point pt) {
+ public ITimeGraphEntry getEntry(Point pt) {
int idx = getItemIndexAtY(pt.y);
return idx >= 0 ? fItemData.fExpandedItems[idx].fEntry : null;
}
int width = getSize().x;
int nameSpace = fTimeProvider.getNameSpace();
double pixelsPerNanoSec = (width - nameSpace <= RIGHT_MARGIN) ? 0 : (double) (width - nameSpace - RIGHT_MARGIN) / (time1 - time0);
- int x = getBounds().x + nameSpace + (int) ((time - time0) * pixelsPerNanoSec);
+ int x = SaturatedArithmetic.add(getBounds().x + nameSpace, (int) ((time - time0) * pixelsPerNanoSec));
return x;
}
}
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.fSelected = i == idx;
}
}
- changed |= ensureVisibleItem(idx, true);
+ if (reveal) {
+ changed |= ensureVisibleItem(idx, true);
+ }
if (changed) {
redraw();
}
}
/**
- * Callback for item selection
+ * Select an entry and make it visible
*
- * @param trace
- * The entry matching the trace
+ * @param entry
+ * The entry to select
* @param addSelection
- * If the selection is added or removed
+ * <code>true</code> to add the entry to the current selection,
+ * or <code>false</code> to set a new selection
*/
- public void selectItem(ITimeGraphEntry trace, boolean addSelection) {
- int idx = fItemData.findItemIndex(trace);
+ 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.
*
}
/**
- * Get the number of expanded items
+ * Get the number of expanded (visible) items
*
- * @return The count of expanded items
+ * @return The count of expanded (visible) items
*/
public int getExpandedElementCount() {
return fItemData.fExpandedItems.length;
}
/**
- * Get an array of all expanded elements
+ * Get an array of all expanded (visible) elements
*
- * @return The expanded elements
+ * @return The expanded (visible) elements
*/
public ITimeGraphEntry[] getExpandedElements() {
ArrayList<ITimeGraphEntry> elements = new ArrayList<>();
return elements.toArray(new ITimeGraphEntry[0]);
}
- Rectangle getNameRect(Rectangle bound, int idx, int nameWidth) {
- Rectangle rect = getStatesRect(bound, idx, nameWidth);
- rect.x = bound.x;
+ /**
+ * 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
+ *
+ * @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(getBounds(), idx);
+ }
+ return null;
+ }
+
+ Rectangle getNameRect(Rectangle bounds, int idx, int nameWidth) {
+ Rectangle rect = getItemRect(bounds, idx);
rect.width = nameWidth;
return rect;
}
- Rectangle getStatesRect(Rectangle bound, int idx, int nameWidth) {
- int x = bound.x + nameWidth;
- int width = bound.width - x;
+ 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;
}
}
- int y = bound.y + ySum;
+ int y = bounds.y + ySum;
int height = fItemData.fExpandedItems[idx].fItemHeight;
- return new Rectangle(x, y, width, height);
+ return new Rectangle(bounds.x, y, bounds.width, height);
}
@Override
void paint(Rectangle bounds, PaintEvent e) {
GC gc = e.gc;
- gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.BACKGROUND));
- drawBackground(gc, bounds.x, bounds.y, bounds.width, bounds.height);
if (bounds.width < 2 || bounds.height < 2 || null == fTimeProvider) {
return;
fIdealNameSpace = 0;
int nameSpace = fTimeProvider.getNameSpace();
- // draw empty name space background
- gc.setBackground(getColorScheme().getBkColor(false, false, true));
- drawBackground(gc, bounds.x, bounds.y, nameSpace, bounds.height);
+ // draw the background layer
+ drawBackground(bounds, nameSpace, gc);
+
+ // draw the grid lines
+ drawGridLines(bounds, gc);
- // draw items
+ // 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);
int alpha = gc.getAlpha();
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 = bounds.x + nameSpace + (int) ((selectionBegin - time0) * pixelsPerNanoSec);
- int x1 = bounds.x + nameSpace + (int) ((selectionEnd - time0) * pixelsPerNanoSec);
+ 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) {
}
}
- // draw drag line
- if (DRAG_SPLIT_LINE == fDragState) {
- gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.BLACK));
- gc.drawLine(bounds.x + nameSpace, bounds.y, bounds.x + nameSpace, bounds.y + bounds.height - 1);
- } else if (DRAG_ZOOM == fDragState && Math.max(fDragX, fDragX0) > nameSpace) {
+ // 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) {
if (fDragX != fDragX0) {
gc.drawLine(fDragX, bounds.y, fDragX, bounds.y + bounds.height - 1);
}
- } else if (DRAG_NONE == fDragState && fMouseOverSplitLine && fTimeProvider.getNameSpace() > 0) {
- gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.RED));
- gc.drawLine(bounds.x + nameSpace, bounds.y, bounds.x + nameSpace, bounds.y + bounds.height - 1);
}
gc.setAlpha(alpha);
}
/**
- * Draw many items at once
+ * 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++) {
+ 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 (item.fEntry.hasTimeEvents()) {
+ 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(255);
+ }
+
+ /**
+ * 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);
+
+ Color color = getColorScheme().getColor(marker.getColor());
+ gc.setBackground(color);
+ gc.setAlpha(color.getAlpha());
+ gc.fillRectangle(rect);
+ gc.setAlpha(255);
+ String label = marker.getLabel();
+ if (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 width reserved for the names
+ * The name space width
* @param gc
- * Reference to the SWT GC object
+ * Graphics context
*/
public void drawItems(Rectangle bounds, ITimeDataProvider timeProvider,
Item[] items, int topIndex, int nameSpace, GC gc) {
/**
* Draws the item
*
- * @param item the item to draw
- * @param bounds the container rectangle
- * @param timeProvider Time provider
- * @param i the item index
- * @param nameSpace the name space
- * @param gc Graphics context
+ * @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) {
+ 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 nameRect = getNameRect(bounds, i, nameSpace);
- if (nameRect.y >= bounds.y + bounds.height) {
- return;
- }
-
- if (! item.fEntry.hasTimeEvents()) {
- Rectangle statesRect = getStatesRect(bounds, i, nameSpace);
- nameRect.width += statesRect.width;
- drawName(item, nameRect, gc);
- } else {
- drawName(item, nameRect, gc);
- }
- Rectangle rect = getStatesRect(bounds, i, nameSpace);
- if (rect.isEmpty()) {
- fTimeGraphProvider.postDrawEntry(entry, rect, gc);
- return;
- }
- if (time1 <= time0) {
- gc.setBackground(getColorScheme().getBkColor(false, false, false));
- gc.fillRectangle(rect);
+ Rectangle rect = new Rectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height);
+ if (rect.isEmpty() || (time1 <= time0)) {
fTimeGraphProvider.postDrawEntry(entry, rect, gc);
return;
}
- // Initialize _rect1 to same values as enclosing rectangle rect
- Rectangle stateRect = Utils.clone(rect);
boolean selected = item.fSelected;
// K pixels per second
double pixelsPerNanoSec = (rect.width <= RIGHT_MARGIN) ? 0 : (double) (rect.width - RIGHT_MARGIN) / (time1 - time0);
if (item.fEntry.hasTimeEvents()) {
gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height));
fillSpace(rect, gc, selected);
- // Drawing rectangle is smaller than reserved space
- stateRect.y += 3;
- stateRect.height -= 6;
+
+ 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);
int lastX = -1;
while (iterator.hasNext()) {
ITimeEvent event = iterator.next();
- int x = rect.x + (int) ((event.getTime() - time0) * pixelsPerNanoSec);
- int xEnd = rect.x + (int) ((event.getTime() + event.getDuration() - time0) * pixelsPerNanoSec);
+ 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;
}
boolean timeSelected = selectedTime >= event.getTime() && selectedTime < event.getTime() + event.getDuration();
if (drawState(getColorScheme(), event, stateRect, gc, selected, timeSelected)) {
- lastX = x;
+ lastX = stateRect.x;
}
}
gc.setClipping((Rectangle) null);
* Draw the links
*
* @param bounds
- * The rectangle of the area
+ * The bounds of the control
* @param timeProvider
* The time provider
* @param links
- * The array items to draw
+ * The list of link events
* @param nameSpace
- * The width reserved for the names
+ * The name space width
* @param gc
- * Reference to the SWT GC object
+ * Graphics context
*/
public void drawLinks(Rectangle bounds, ITimeDataProvider timeProvider,
List<ILinkEvent> links, int nameSpace, GC gc) {
return;
}
gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height));
- for (ILinkEvent event : links) {
- drawLink(event, bounds, timeProvider, nameSpace, gc);
+ /* 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 the link type events of this item
+ * Draws a link type event
*
* @param event
- * the item to draw
+ * The link event to draw
* @param bounds
- * the container rectangle
+ * The bounds of the control
* @param timeProvider
- * Time provider
+ * The time provider
* @param nameSpace
- * the name space
+ * The name space width
* @param gc
* Graphics context
*/
}
/**
- * Draw the state (color fill)
+ * Draw an arrow
*
* @param colors
* Color scheme
* @param event
- * Time event for which we're drawing the state
+ * Time event for which we're drawing the arrow
* @param rect
- * Where to draw
+ * The arrow rectangle
* @param gc
* Graphics context
- * @return true if the state was drawn
+ * @return true if the arrow was drawn
*/
protected boolean drawArrow(TimeGraphColorScheme colors, ITimeEvent event,
Rectangle rect, GC gc) {
}
/**
- * Draw the name of an item.
+ * Draw the name space of an item.
*
* @param item
* Item object
* @param bounds
- * Where to draw the name
+ * The bounds of the item's name space
* @param gc
* Graphics context
*/
protected void drawName(Item item, Rectangle bounds, GC gc) {
- boolean hasTimeEvents = item.fEntry.hasTimeEvents();
- if (! hasTimeEvents) {
- gc.setBackground(getColorScheme().getBkColorGroup(item.fSelected, fIsInFocus));
- gc.fillRectangle(bounds);
- if (item.fSelected && fIsInFocus) {
- gc.setForeground(getColorScheme().getBkColor(item.fSelected, fIsInFocus, false));
- gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1);
- }
- } else {
- gc.setBackground(getColorScheme().getBkColor(item.fSelected, fIsInFocus, true));
- gc.setForeground(getColorScheme().getFgColor(item.fSelected, fIsInFocus));
- gc.fillRectangle(bounds);
- }
-
- // No name to be drawn
+ // No name space to be drawn
if (fTimeProvider.getNameSpace() == 0) {
return;
}
- int leftMargin = MARGIN + item.fLevel * EXPAND_SIZE;
- if (item.fHasChildren) {
- gc.setForeground(getColorScheme().getFgColorGroup(false, false));
- gc.setBackground(getColorScheme().getBkColor(false, false, false));
- Rectangle rect = Utils.clone(bounds);
- rect.x += leftMargin;
- rect.y += (bounds.height - EXPAND_SIZE) / 2;
- rect.width = EXPAND_SIZE;
- rect.height = EXPAND_SIZE;
- gc.fillRectangle(rect);
- gc.drawRectangle(rect.x, rect.y, rect.width - 1, rect.height - 1);
- int midy = rect.y + rect.height / 2;
- gc.drawLine(rect.x + 2, midy, rect.x + rect.width - 3, midy);
- if (!item.fExpanded) {
- int midx = rect.x + rect.width / 2;
- gc.drawLine(midx, rect.y + 2, midx, rect.y + rect.height - 3);
- }
- }
- leftMargin += EXPAND_SIZE + MARGIN;
-
- Image img = fTimeGraphProvider.getItemImage(item.fEntry);
- if (img != null) {
- // draw icon
- int imgHeight = img.getImageData().height;
- int imgWidth = img.getImageData().width;
- int x = leftMargin;
- int y = bounds.y + (bounds.height - imgHeight) / 2;
- gc.drawImage(img, x, y);
- leftMargin += imgWidth + MARGIN;
- }
- String name = item.fName;
- Point size = gc.stringExtent(name);
- if (fIdealNameSpace < leftMargin + size.x + MARGIN) {
- fIdealNameSpace = leftMargin + size.x + MARGIN;
- }
+ boolean hasTimeEvents = item.fEntry.hasTimeEvents();
if (hasTimeEvents) {
- // cut long string with "..."
- int width = bounds.width - leftMargin;
- int cuts = 0;
- while (size.x > width && name.length() > 1) {
- cuts++;
- name = name.substring(0, name.length() - 1);
- size = gc.stringExtent(name + "..."); //$NON-NLS-1$
- }
- if (cuts > 0) {
- name += "..."; //$NON-NLS-1$
- }
+ 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.x += leftMargin;
- rect.width -= leftMargin;
- // draw text
- if (rect.width > 0) {
- rect.y += (bounds.height - gc.stringExtent(name).y) / 2;
+ rect.y += (bounds.height - gc.stringExtent(name).y) / 2;
+ TreeColumn[] columns = fTree.getColumns();
+ int idealNameSpace = 0;
+ for (int i = 0; i < columns.length; i++) {
+ int columnIndex = fTree.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));
- int textWidth = Utils.drawText(gc, name, rect, true);
- leftMargin += textWidth + MARGIN;
- rect.y -= 2;
-
- if (hasTimeEvents) {
- // draw middle line
- int x = bounds.x + leftMargin;
- int width = bounds.width - x;
- int midy = bounds.y + bounds.height / 2;
- gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.MID_LINE));
- gc.drawLine(x, midy, x + width, midy);
+ 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 (columns.length == 1) {
+ 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);
}
/**
* @param event
* Time event for which we're drawing the state
* @param rect
- * Where to draw
+ * The state rectangle
* @param gc
* Graphics context
* @param selected
return false;
}
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);
- if (visible) {
- if (colorIdx == ITimeGraphPresentationProvider.TRANSPARENT) {
+ if (colorIdx == ITimeGraphPresentationProvider.TRANSPARENT) {
+ if (visible) {
// Only draw the top and bottom borders
gc.drawLine(rect.x, rect.y, rect.x + rect.width - 1, rect.y);
gc.drawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width - 1, rect.y + rect.height - 1);
if (rect.width == 1) {
gc.drawPoint(rect.x, rect.y - 2);
}
- fTimeGraphProvider.postDrawEvent(event, rect, gc);
- return false;
- }
- Color stateColor = null;
- if (colorIdx < fEventColorMap.length) {
- stateColor = fEventColorMap[colorIdx];
- } else {
- stateColor = black;
}
+ fTimeGraphProvider.postDrawEvent(event, rect, gc);
+ return false;
+ }
+ Color stateColor = null;
+ if (colorIdx < fEventColorMap.length) {
+ stateColor = fEventColorMap[colorIdx];
+ } else {
+ stateColor = black;
+ }
- boolean reallySelected = timeSelected && selected;
- // fill all rect area
- gc.setBackground(stateColor);
+ boolean reallySelected = timeSelected && selected;
+ // fill all rect area
+ gc.setBackground(stateColor);
+ if (visible) {
+ gc.fillRectangle(rect);
+ } else if (fBlendSubPixelEvents) {
+ gc.setAlpha(128);
gc.fillRectangle(rect);
+ gc.setAlpha(255);
+ }
- if (reallySelected) {
- gc.drawLine(rect.x, rect.y - 1, rect.x + rect.width - 1, rect.y - 1);
- gc.drawLine(rect.x, rect.y + rect.height, rect.x + rect.width - 1, rect.y + rect.height);
- }
- } else {
+ if (reallySelected) {
+ gc.drawLine(rect.x, rect.y - 1, rect.x + rect.width - 1, rect.y - 1);
+ gc.drawLine(rect.x, rect.y + rect.height, rect.x + rect.width - 1, rect.y + rect.height);
+ }
+ if (!visible) {
gc.drawPoint(rect.x, rect.y - 2);
}
fTimeGraphProvider.postDrawEvent(event, rect, gc);
}
/**
- * Fill the space between two contiguous time events
+ * Fill an item's states rectangle
*
* @param rect
- * Rectangle to fill
+ * The states rectangle
* @param gc
* Graphics context
* @param selected
- * Is this time event selected or not
+ * true if the item is selected
*/
protected void fillSpace(Rectangle rect, GC gc, boolean selected) {
- gc.setBackground(getColorScheme().getBkColor(selected, fIsInFocus, false));
- gc.fillRectangle(rect);
- if (fDragState == DRAG_ZOOM) {
- gc.setBackground(getColorScheme().getBkColor(selected, fIsInFocus, true));
- if (fDragX0 < fDragX) {
- gc.fillRectangle(new Rectangle(fDragX0, rect.y, fDragX - fDragX0, rect.height));
- } else if (fDragX0 > fDragX) {
- gc.fillRectangle(new Rectangle(fDragX, rect.y, fDragX0 - fDragX, rect.height));
- }
- }
- // draw middle line
+ /* 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.get(height);
+ if (font == null) {
+ FontData fontData = gc.getFont().getFontData()[0];
+ fontData.setHeight(height);
+ font = new Font(gc.getDevice(), fontData);
+ fFonts.put(height, font);
+ }
+ gc.setFont(font);
+ }
+
@Override
public void keyTraversed(TraverseEvent e) {
if ((e.detail == SWT.TRAVERSE_TAB_NEXT) || (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)) {
}
}
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 {
+ zoomIn();
+ }
+ } else if (e.character == '-' && ((e.stateMask & SWT.CTRL) == 0)) {
+ if (fHasNamespaceFocus) {
+ ITimeGraphEntry entry = getSelectedTrace();
+ if ((entry != null) && entry.hasChildren()) {
+ setExpandedState(entry, -1, false);
+ }
+ } else {
+ zoomOut();
+ }
+ } 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);
}
}
+ /**
+ * 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();
}
fMouseOverSplitLine = mouseOverSplitLine;
}
+
+ if (e.x >= fTimeProvider.getNameSpace()) {
+ fHasNamespaceFocus = false;
+ } else {
+ fHasNamespaceFocus = true;
+ }
updateCursor(e.x, e.stateMask);
updateStatusLine(e.x);
}
@Override
public void mouseDown(MouseEvent e) {
- if (fDragState != DRAG_NONE || null == fTimeProvider ||
- fTimeProvider.getTime0() == fTimeProvider.getTime1() ||
- getSize().x - fTimeProvider.getNameSpace() <= 0) {
+ if (fDragState != DRAG_NONE) {
return;
}
- int idx;
if (1 == e.button && (e.stateMask & SWT.MODIFIER_MASK) == 0) {
int nameSpace = fTimeProvider.getNameSpace();
if (nameSpace != 0 && isOverSplitLine(e.x)) {
fDragButton = e.button;
fDragX = e.x;
fDragX0 = fDragX;
- fTime0bak = fTimeProvider.getTime0();
- fTime1bak = fTimeProvider.getTime1();
redraw();
updateCursor(e.x, e.stateMask);
return;
}
}
+ if (fTimeProvider == null ||
+ fTimeProvider.getTime0() == fTimeProvider.getTime1() ||
+ getSize().x - fTimeProvider.getNameSpace() <= 0) {
+ return;
+ }
+ int idx;
if (1 == e.button && ((e.stateMask & SWT.MODIFIER_MASK) == 0 || (e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT)) {
int nameSpace = fTimeProvider.getNameSpace();
idx = getItemIndexAtY(e.y);
updateCursor(e.x, e.stateMask);
}
} else if (3 == e.button) {
- 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);
+ 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);
+ } else {
+ idx = getItemIndexAtY(e.y);
+ selectItem(idx, false);
+ fireSelectionChanged();
+ }
}
}
@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;
if (fDragX != fDragX0) {
fTimeProvider.notifyStartFinishTime();
}
- fDragState = DRAG_NONE;
} 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);
+ fTimeProvider.setSelectionRangeNotify(time0, time1, false);
}
- fDragState = DRAG_NONE;
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 && fDragX != fDragX0) {
+ if ((Math.max(fDragX, fDragX0) > nameWidth) && !isInDragZoomMargin()) {
long time0 = getTimeAtX(fDragX0);
long time1 = getTimeAtX(fDragX);
if (time0 < time1) {
} else {
redraw();
}
- fDragState = DRAG_NONE;
fTimeGraphScale.setDragRange(-1, -1);
}
}
@Override
public void mouseScrolled(MouseEvent e) {
- if (fDragState != DRAG_NONE) {
+ if (fDragState != DRAG_NONE || e.count == 0) {
return;
}
- boolean zoomScroll = false;
+
+ /*
+ * 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;
- Point p = getParent().toControl(getDisplay().getCursorLocation());
- Point parentSize = getParent().getSize();
- if (p.x >= 0 && p.x < parentSize.x && p.y >= 0 && p.y < parentSize.y) {
- // over the parent control
- if (e.x > getSize().x) {
- // over the vertical scroll bar
- zoomScroll = false;
- } else if (e.y < 0) {
- // over the time scale
- zoomScroll = true;
- } else if (e.y >= getSize().y) {
- // over the horizontal scroll bar
- if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL) {
- zoomScroll = true;
- } else {
- horizontalScroll = true;
- }
+ 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 {
- if (e.x < fTimeProvider.getNameSpace()) {
- // over the name space
- zoomScroll = false;
- } else {
- // over the state area
- if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL) {
- // over the state area, CTRL pressed
- zoomScroll = true;
- } else {
- // over the state area, CTRL not pressed
- zoomScroll = false;
- }
- }
+ // over the state area, no modifier pressed
+ verticalScroll = true;
}
}
- if (zoomScroll && fTimeProvider.getTime0() != fTimeProvider.getTime1()) {
- if (e.count > 0) {
- zoom(true);
- } else if (e.count < 0) {
- zoom(false);
+ 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 {
+ } 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) {
*/
public void setItemHeight(int rowHeight) {
this.fGlobalItemHeight = rowHeight;
+ for (Item item : fItemData.fItems) {
+ item.fItemHeight = rowHeight;
+ }
}
/**
}
/**
- * @return The entries that are currently filtered out
+ * 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 List<ITimeGraphEntry> getFilteredOut() {
- return fItemData.getFilteredOut();
+ public void setBlendSubPixelEvents(boolean blend) {
+ fBlendSubPixelEvents = blend;
}
@Override
@Override
public void setSelection(ISelection selection) {
- if (selection instanceof TimeGraphSelection) {
- TimeGraphSelection sel = (TimeGraphSelection) selection;
- Object ob = sel.getFirstElement();
+ if (selection instanceof IStructuredSelection) {
+ Object ob = ((IStructuredSelection) selection).getFirstElement();
if (ob instanceof ITimeGraphEntry) {
- ITimeGraphEntry trace = (ITimeGraphEntry) ob;
- selectItem(trace, false);
+ selectItem((ITimeGraphEntry) ob, false);
}
}
/**
* @param filter The filter object to be attached to the view
*/
- public void addFilter(ViewerFilter filter) {
+ public void addFilter(@NonNull ViewerFilter filter) {
if (!fFilters.contains(filter)) {
fFilters.add(filter);
}
/**
* @param filter The filter object to be attached to the view
*/
- public void removeFilter(ViewerFilter filter) {
+ public void removeFilter(@NonNull ViewerFilter filter) {
fFilters.remove(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) {
+ fFilters.addAll(Arrays.asList(filters));
+ }
+ }
+
@Override
public void colorSettingsChanged(StateItem[] stateItems) {
/* Destroy previous colors from the resource manager */
}
private class ItemData {
- private final Map<ITimeGraphEntry, Item> fItemMap = new LinkedHashMap<>();
+ 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<>();
- private boolean fEntryFilter[] = new boolean[0];
- private final ArrayList<ITimeGraphEntry> fFilteredOut = new ArrayList<>();
public ItemData() {
}
}
public void refreshData() {
- fItemMap.clear();
- fFilteredOut.clear();
ITimeGraphEntry selection = getSelectedTrace();
+ Map<ITimeGraphEntry, Item> itemMap = new LinkedHashMap<>();
for (int i = 0; i < fRootEntries.length; i++) {
ITimeGraphEntry entry = fRootEntries[i];
- refreshData(fItemMap, null, 0, entry);
+ refreshData(itemMap, null, 0, entry);
}
+ fItemMap = itemMap;
fItems = fItemMap.values().toArray(new Item[0]);
updateExpandedItems();
if (selection != null) {
} else {
item.fItemHeight = fGlobalItemHeight;
}
+ item.fItemHeight = Math.max(1, item.fItemHeight + fHeightAdjustment);
itemMap.put(entry, item);
if (entry.hasChildren()) {
- item.fExpanded = fAutoExpandLevel == ALL_LEVELS || level < fAutoExpandLevel;
+ 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 refreshData(ITimeGraphEntry[] entries) {
if (entries == null) {
- fEntryFilter = null;
fRootEntries = null;
} else {
- if (entries.length == 0) {
- fEntryFilter = null;
- } else if (fEntryFilter == null || entries.length != fEntryFilter.length) {
- fEntryFilter = new boolean[entries.length];
- java.util.Arrays.fill(fEntryFilter, true);
- }
fRootEntries = Arrays.copyOf(entries, entries.length);
}
public ITimeGraphEntry[] getEntries() {
return fRootEntries;
}
-
- public boolean[] getEntryFilter() {
- return fEntryFilter;
- }
-
- public List<ITimeGraphEntry> getFilteredOut() {
- return fFilteredOut;
- }
}
private class Item {
if (null == fTimeProvider) {
return;
}
- if (e.detail == SWT.MENU_MOUSE) {
+ /*
+ * 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 || fDragX != fDragX0) {
+ 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;
}
}
- Point p = toControl(e.x, e.y);
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);
- return;
+ menu = getMenu();
+ if (pendingEventCallback && e.doit && (menu != null)) {
+ menu.setVisible(true);
+ }
}
}
- e.data = entry;
- fireMenuEventOnTimeGraphEntry(e);
}
}
public TmfTimeViewAlignmentInfo getTimeViewAlignmentInfo() {
return new TmfTimeViewAlignmentInfo(getShell(), toDisplay(0, 0), fTimeProvider.getNameSpace());
}
-}
-
+ private boolean isInDragZoomMargin() {
+ return (Math.abs(fDragX - fDragX0) < DRAG_MARGIN);
+ }
+}