X-Git-Url: http://git.efficios.com/?a=blobdiff_plain;f=tmf%2Forg.eclipse.tracecompass.tmf.ui%2Fsrc%2Forg%2Feclipse%2Ftracecompass%2Ftmf%2Fui%2Fwidgets%2Ftimegraph%2Fwidgets%2FTimeGraphControl.java;h=2f5c35895a97bfd14661edb75131ff0076479f36;hb=refs%2Fheads%2Fnext-previous-event;hp=9baee8006b44c588c4cb1c9cdf6e4df754a733b1;hpb=1d0124437aa286f3ac568a22587329a6e8b74ea3;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 9baee8006b..2f5c35895a 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,14 +20,20 @@ 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; @@ -35,6 +41,7 @@ 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.SelectionChangedEvent; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; @@ -56,6 +63,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; @@ -64,6 +73,7 @@ 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.tracecompass.tmf.core.signal.TmfSignalManager; import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo; import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal; @@ -119,6 +129,11 @@ public class TimeGraphControl extends TimeGraphBaseControl 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; + /** Resource manager */ private LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources()); @@ -132,6 +147,8 @@ public class TimeGraphControl extends TimeGraphBaseControl 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; @@ -140,13 +157,13 @@ 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 long fTime0bak; private long fTime1bak; private ITimeGraphPresentationProvider fTimeGraphProvider = null; private ItemData fItemData = null; - private List fBookmarks = null; private List fMarkers = null; private boolean fMarkersVisible = true; private List fSelectionListeners; @@ -159,13 +176,14 @@ public class TimeGraphControl extends TimeGraphBaseControl 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 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 fVerticalZoomAlignEntry = null; + private long fVerticalZoomAlignTime = 0; private int fBorderWidth = 0; private int fHeaderHeight = 0; @@ -192,12 +210,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 +244,15 @@ 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 + */ + public ITimeDataProvider getTimeDataProvider() { + return fTimeProvider; + } + /** * Gets the color map used by this timegraph viewer. * @@ -308,6 +335,12 @@ public class TimeGraphControl extends TimeGraphBaseControl listener.widgetSelected(null); } } + + if (null != fSelectionChangedListeners) { + for (ISelectionChangedListener listener : fSelectionChangedListeners) { + listener.selectionChanged(new SelectionChangedEvent(this, getSelection())); + } + } } /** @@ -421,7 +454,7 @@ public class TimeGraphControl extends TimeGraphBaseControl * * @return The unmodifiable link event list * - * @since 2.0 + * @since 1.1 */ public List getArrows() { return Collections.unmodifiableList(fItemData.fLinks); @@ -472,6 +505,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[])} @@ -508,7 +571,7 @@ public class TimeGraphControl extends TimeGraphBaseControl * @param entry * The entry * @return true if the entry is expanded, false if collapsed - * @since 2.0 + * @since 1.1 */ public boolean getExpandedState(ITimeGraphEntry entry) { Item item = fItemData.fItemMap.get(entry); @@ -532,6 +595,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,6 +834,14 @@ public class TimeGraphControl extends TimeGraphBaseControl } } + @Override + public boolean setFocus() { + if ((fTimeProvider != null) && fTimeProvider.getNameSpace() > 0) { + fHasNamespaceFocus = true; + } + return super.setFocus(); + } + @Override public ISelection getSelection() { TimeGraphSelection sel = new TimeGraphSelection(); @@ -676,10 +849,9 @@ public class TimeGraphControl extends TimeGraphBaseControl if (null != trace && null != fTimeProvider) { long selectedTime = fTimeProvider.getSelectionBegin(); ITimeEvent event = Utils.findEvent(trace, selectedTime, 0); + sel.add(trace); if (event != null) { sel.add(event); - } else { - sel.add(trace); } } return sel; @@ -789,14 +961,14 @@ public class TimeGraphControl extends TimeGraphBaseControl 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); } @@ -957,6 +1129,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. * @@ -999,18 +1199,6 @@ public class TimeGraphControl extends TimeGraphBaseControl return fGridLineColor; } - /** - * Set the bookmarks list. - * - * @param bookmarks - * The bookmarks list, or null - * @since 2.0 - */ - public void setBookmarks(List bookmarks) { - fBookmarks = bookmarks; - fTimeGraphScale.setBookmarks(bookmarks); - } - /** * Set the markers list. * @@ -1020,7 +1208,6 @@ public class TimeGraphControl extends TimeGraphBaseControl */ public void setMarkers(List markers) { fMarkers = markers; - fTimeGraphScale.setMarkers(markers); } /** @@ -1042,7 +1229,6 @@ public class TimeGraphControl extends TimeGraphBaseControl */ public void setMarkersVisible(boolean visible) { fMarkersVisible = visible; - fTimeGraphScale.setMarkersVisible(visible); } /** @@ -1082,7 +1268,7 @@ public class TimeGraphControl extends TimeGraphBaseControl 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); } @@ -1115,7 +1301,7 @@ public class TimeGraphControl extends TimeGraphBaseControl 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); } @@ -1184,14 +1370,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; @@ -1212,8 +1404,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; } @@ -1383,6 +1576,21 @@ 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; + } + Rectangle getNameRect(Rectangle bounds, int idx, int nameWidth) { Rectangle rect = getItemRect(bounds, idx); rect.width = nameWidth; @@ -1432,18 +1640,12 @@ public class TimeGraphControl extends TimeGraphBaseControl // draw the background markers drawMarkers(bounds, fTimeProvider, fMarkers, false, nameSpace, gc); - // draw the background bookmarks - drawMarkers(bounds, fTimeProvider, fBookmarks, 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 foreground bookmarks - drawMarkers(bounds, fTimeProvider, fBookmarks, true, nameSpace, gc); - // draw the links (arrows) drawLinks(bounds, fTimeProvider, fItemData.fLinks, nameSpace, gc); @@ -1657,14 +1859,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); } } @@ -1733,14 +1936,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); @@ -1935,6 +2138,9 @@ public class TimeGraphControl extends TimeGraphBaseControl return; } + int height = bounds.height - getMarginForHeight(bounds.height); + setFontForHeight(height, gc); + int leftMargin = MARGIN + item.fLevel * EXPAND_SIZE; if (item.fHasChildren) { gc.setForeground(getColorScheme().getFgColorGroup(false, false)); @@ -2102,6 +2308,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)) { @@ -2169,6 +2403,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); @@ -2255,6 +2530,15 @@ public class TimeGraphControl extends TimeGraphBaseControl } } + /** + * 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(); @@ -2361,6 +2645,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); } @@ -2482,15 +2772,21 @@ 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(); + } } } @@ -2516,7 +2812,7 @@ public class TimeGraphControl extends TimeGraphBaseControl } 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(); @@ -2561,57 +2857,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) { @@ -2715,7 +3085,7 @@ public class TimeGraphControl extends TimeGraphBaseControl * * @param blend * true if sub-pixel events should be blended, false otherwise. - * @since 2.0 + * @since 1.1 */ public void setBlendSubPixelEvents(boolean blend) { fBlendSubPixelEvents = blend; @@ -2751,7 +3121,7 @@ public class TimeGraphControl extends TimeGraphBaseControl /** * @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); } @@ -2760,7 +3130,7 @@ public class TimeGraphControl extends TimeGraphBaseControl /** * @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); } @@ -2768,9 +3138,9 @@ public class TimeGraphControl extends TimeGraphBaseControl * Returns this control's filters. * * @return an array of viewer filters - * @since 2.0 + * @since 1.2 */ - public ViewerFilter[] getFilters() { + public @NonNull ViewerFilter[] getFilters() { return Iterables.toArray(fFilters, ViewerFilter.class); } @@ -2779,9 +3149,9 @@ public class TimeGraphControl extends TimeGraphBaseControl * * @param filters * an array of viewer filters, or null - * @since 2.0 + * @since 1.2 */ - public void setFilters(ViewerFilter[] filters) { + public void setFilters(@NonNull ViewerFilter[] filters) { fFilters.clear(); if (filters != null) { fFilters.addAll(Arrays.asList(filters)); @@ -2859,6 +3229,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); @@ -2963,6 +3334,7 @@ public class TimeGraphControl extends TimeGraphBaseControl if (null == fTimeProvider) { return; } + Point p = toControl(e.x, e.y); if (e.detail == SWT.MENU_MOUSE) { if (fPendingMenuDetectEvent == null) { /* Feature in Linux. The MenuDetectEvent is received before mouseDown. @@ -2970,10 +3342,16 @@ public class TimeGraphControl extends TimeGraphBaseControl * 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 ((p.x >= fTimeProvider.getNameSpace()) && (fDragState != DRAG_ZOOM || fDragX != fDragX0)) { return; } } else { @@ -2981,21 +3359,34 @@ public class TimeGraphControl extends TimeGraphBaseControl return; } } - Point p = toControl(e.x, e.y); int idx = getItemIndexAtY(p.y); if (idx >= 0 && idx < fItemData.fExpandedItems.length) { + e.doit = true; 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 (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 (e.doit && (menu != null)) { + menu.setVisible(true); + } } } - e.data = entry; - fireMenuEventOnTimeGraphEntry(e); } }