X-Git-Url: http://drtracing.org/?a=blobdiff_plain;f=tmf%2Forg.eclipse.tracecompass.tmf.ui%2Fsrc%2Forg%2Feclipse%2Ftracecompass%2Ftmf%2Fui%2Fwidgets%2Ftimegraph%2Fwidgets%2FTimeGraphControl.java;h=9eeb3b372588a099dd5788319dc9ba663d43bcde;hb=c44be4c05a2750b5238109321ffe494a5c5aa002;hp=5a1e37d1dacfd6b2d34cf860640d9dcc63d5ae62;hpb=367e2932ca88708df2a460eabb905d5113b1636c;p=deliverable%2Ftracecompass.git diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java index 5a1e37d1da..9eeb3b3725 100644 --- a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java @@ -1,5 +1,5 @@ /***************************************************************************** - * 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 @@ -20,13 +20,18 @@ 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; @@ -36,9 +41,15 @@ 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; @@ -57,6 +68,8 @@ 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; @@ -65,6 +78,10 @@ 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.tmf.core.signal.TmfSignalManager; import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo; import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal; @@ -100,6 +117,8 @@ public class TimeGraphControl extends TimeGraphBaseControl /** 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; @@ -112,14 +131,23 @@ public class TimeGraphControl extends TimeGraphBaseControl 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 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()); @@ -127,12 +155,16 @@ public class TimeGraphControl extends TimeGraphBaseControl 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 fFonts = new HashMap<>(); private boolean fBlendSubPixelEvents = false; private int fMinimumItemWidth = 0; private int fTopIndex = 0; @@ -141,8 +173,10 @@ public class TimeGraphControl extends TimeGraphBaseControl 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; @@ -165,7 +199,8 @@ public class TimeGraphControl extends TimeGraphBaseControl private Color fGridLineColor = Display.getDefault().getSystemColor(SWT.COLOR_GRAY); private boolean fHideArrows = false; private int fAutoExpandLevel = ALL_LEVELS; - + private Entry fVerticalZoomAlignEntry = null; + private long fVerticalZoomAlignTime = 0; private int fBorderWidth = 0; private int fHeaderHeight = 0; @@ -192,12 +227,12 @@ public class TimeGraphControl extends TimeGraphBaseControl 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(); + } + }); } /** @@ -226,6 +261,16 @@ public class TimeGraphControl extends TimeGraphBaseControl return fTimeGraphProvider; } + /** + * Gets the time data provider used by this viewer. + * + * @return The time data provider, or null if not set + * @since 2.1 + */ + public ITimeDataProvider getTimeDataProvider() { + return fTimeProvider; + } + /** * Gets the color map used by this timegraph viewer. * @@ -246,6 +291,28 @@ public class TimeGraphControl extends TimeGraphBaseControl 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 * @@ -259,6 +326,63 @@ public class TimeGraphControl extends TimeGraphBaseControl 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 * @@ -308,6 +432,12 @@ public class TimeGraphControl extends TimeGraphBaseControl listener.widgetSelected(null); } } + + if (null != fSelectionChangedListeners) { + for (ISelectionChangedListener listener : fSelectionChangedListeners) { + listener.selectionChanged(new SelectionChangedEvent(this, getSelection())); + } + } } /** @@ -472,6 +602,36 @@ public class TimeGraphControl extends TimeGraphBaseControl 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[])} @@ -532,6 +692,108 @@ public class TimeGraphControl extends TimeGraphBaseControl } } + /** + * 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 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. */ @@ -669,20 +931,33 @@ public class TimeGraphControl extends TimeGraphBaseControl } } + @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; } /** @@ -691,12 +966,11 @@ public class TimeGraphControl extends TimeGraphBaseControl * @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; } /** @@ -957,6 +1231,34 @@ public class TimeGraphControl extends TimeGraphBaseControl 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. * @@ -1008,7 +1310,6 @@ public class TimeGraphControl extends TimeGraphBaseControl */ public void setMarkers(List markers) { fMarkers = markers; - fTimeGraphScale.setMarkers(markers); } /** @@ -1030,7 +1331,6 @@ public class TimeGraphControl extends TimeGraphBaseControl */ public void setMarkersVisible(boolean visible) { fMarkersVisible = visible; - fTimeGraphScale.setMarkersVisible(visible); } /** @@ -1172,14 +1472,20 @@ public class TimeGraphControl extends TimeGraphBaseControl * @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; @@ -1190,9 +1496,13 @@ public class TimeGraphControl extends TimeGraphBaseControl 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. * @@ -1200,8 +1510,9 @@ public class TimeGraphControl extends TimeGraphBaseControl * a point in the widget * @return the {@link ITimeGraphEntry} at this point, or null * 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; } @@ -1250,7 +1561,7 @@ public class TimeGraphControl extends TimeGraphBaseControl 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; } @@ -1279,6 +1590,10 @@ public class TimeGraphControl extends TimeGraphBaseControl } 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) { @@ -1295,25 +1610,46 @@ public class TimeGraphControl extends TimeGraphBaseControl 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 + * true to add the entry to the current selection, + * or false 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 + * true to add the entry to the current selection, + * or false to set a new selection + * @param reveal + * true if the selection is to be made visible, and + * false 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. * @@ -1371,6 +1707,36 @@ public class TimeGraphControl extends TimeGraphBaseControl 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 + * + * @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; @@ -1439,8 +1805,8 @@ public class TimeGraphControl extends TimeGraphBaseControl 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) { @@ -1477,11 +1843,16 @@ public class TimeGraphControl extends TimeGraphBaseControl } } - // 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) { @@ -1493,9 +1864,6 @@ public class TimeGraphControl extends TimeGraphBaseControl 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); @@ -1539,12 +1907,10 @@ public class TimeGraphControl extends TimeGraphBaseControl gc.setBackground(getColorScheme().getBkColor(true, fIsInFocus, false)); gc.fillRectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height); } - // draw the name and middle line - if (! item.fEntry.hasTimeEvents()) { - drawName(item, itemRect, gc); - } else { - Rectangle nameRect = new Rectangle(itemRect.x, itemRect.y, nameSpace, itemRect.height); - drawName(item, nameRect, gc); + // 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); } @@ -1639,14 +2005,15 @@ public class TimeGraphControl extends TimeGraphBaseControl rect.x = Math.max(nameSpace, Math.min(bounds.width, x0)); rect.width = Math.max(1, Math.min(bounds.width, x1) - rect.x); - gc.setBackground(marker.getColor()); - gc.setAlpha(marker.getColor().getAlpha()); + 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(marker.getColor()); + gc.setForeground(color); Utils.drawText(gc, label, rect.x - gc.textExtent(label).x, rect.y, true); } } @@ -1715,14 +2082,14 @@ public class TimeGraphControl extends TimeGraphBaseControl if (item.fEntry.hasTimeEvents()) { gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height)); fillSpace(rect, gc, selected); - /* - * State rectangle is smaller than item bounds. Use a margin height - * of 3 pixels, keep at least 3 pixels for the state, but not more - * than the item height. Favor the top margin for the remainder. - */ - int height = Math.min(rect.height, Math.max(3, rect.height - 6)); - int margin = (rect.height - height + 1) / 2; - Rectangle stateRect = new Rectangle(rect.x, rect.y + margin, rect.width, height); + + 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 iterator = entry.getTimeEventsIterator(time0, time1, maxDuration); @@ -1730,8 +2097,8 @@ public class TimeGraphControl extends TimeGraphBaseControl 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; @@ -1900,7 +2267,7 @@ public class TimeGraphControl extends TimeGraphBaseControl } /** - * Draw the name of an item. + * Draw the name space of an item. * * @param item * Item object @@ -1910,79 +2277,98 @@ public class TimeGraphControl extends TimeGraphBaseControl * Graphics context */ protected void drawName(Item item, Rectangle bounds, GC gc) { - boolean hasTimeEvents = item.fEntry.hasTimeEvents(); - - // 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; - - if (hasTimeEvents) { - // draw middle line - rect.x = bounds.x + leftMargin; - rect.y = bounds.y; - rect.width = bounds.width - rect.x; - drawMidLine(rect, gc); + 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); } /** @@ -2084,6 +2470,34 @@ public class TimeGraphControl extends TimeGraphBaseControl 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)) { @@ -2151,6 +2565,47 @@ public class TimeGraphControl extends TimeGraphBaseControl } } 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); @@ -2352,6 +2807,12 @@ public class TimeGraphControl extends TimeGraphBaseControl } fMouseOverSplitLine = mouseOverSplitLine; } + + if (e.x >= fTimeProvider.getNameSpace()) { + fHasNamespaceFocus = false; + } else { + fHasNamespaceFocus = true; + } updateCursor(e.x, e.stateMask); updateStatusLine(e.x); } @@ -2382,12 +2843,9 @@ public class TimeGraphControl extends TimeGraphBaseControl @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)) { @@ -2395,13 +2853,17 @@ public class TimeGraphControl extends TimeGraphBaseControl 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); @@ -2473,34 +2935,49 @@ public class TimeGraphControl extends TimeGraphBaseControl 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); @@ -2509,12 +2986,12 @@ public class TimeGraphControl extends TimeGraphBaseControl long time1 = fDragBeginMarker ? fDragTime0 : getTimeAtX(fDragX); 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) { @@ -2525,7 +3002,6 @@ public class TimeGraphControl extends TimeGraphBaseControl } else { redraw(); } - fDragState = DRAG_NONE; fTimeGraphScale.setDragRange(-1, -1); } } @@ -2552,57 +3028,131 @@ public class TimeGraphControl extends TimeGraphBaseControl @Override public void mouseScrolled(MouseEvent e) { - if (fDragState != DRAG_NONE) { + 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 zoomScroll = false; + + 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 getVerticalZoomAlignSelection() { + Entry 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 getVerticalZoomAlignCursor(int y) { + Entry 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 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) { @@ -2728,12 +3278,10 @@ public class TimeGraphControl extends TimeGraphBaseControl @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); } } @@ -2759,7 +3307,7 @@ public class TimeGraphControl extends TimeGraphBaseControl * Returns this control's filters. * * @return an array of viewer filters - * @since 2.0 + * @since 1.2 */ public @NonNull ViewerFilter[] getFilters() { return Iterables.toArray(fFilters, ViewerFilter.class); @@ -2770,7 +3318,7 @@ public class TimeGraphControl extends TimeGraphBaseControl * * @param filters * an array of viewer filters, or null - * @since 2.0 + * @since 1.2 */ public void setFilters(@NonNull ViewerFilter[] filters) { fFilters.clear(); @@ -2850,6 +3398,7 @@ public class TimeGraphControl extends TimeGraphBaseControl } else { item.fItemHeight = fGlobalItemHeight; } + item.fItemHeight = Math.max(1, item.fItemHeight + fHeightAdjustment); itemMap.put(entry, item); if (entry.hasChildren()) { Item oldItem = fItemMap.get(entry); @@ -2954,39 +3503,74 @@ public class TimeGraphControl extends TimeGraphBaseControl 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); } } @@ -3016,4 +3600,8 @@ public class TimeGraphControl extends TimeGraphBaseControl public TmfTimeViewAlignmentInfo getTimeViewAlignmentInfo() { return new TmfTimeViewAlignmentInfo(getShell(), toDisplay(0, 0), fTimeProvider.getNameSpace()); } + + private boolean isInDragZoomMargin() { + return (Math.abs(fDragX - fDragX0) < DRAG_MARGIN); + } }