/*****************************************************************************
- * 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.SelectionChangedEvent;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
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.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo;
import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal;
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());
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 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<IMarkerEvent> fBookmarks = null;
private List<IMarkerEvent> fMarkers = null;
private boolean fMarkersVisible = true;
private List<SelectionListener> fSelectionListeners;
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
+ */
+ public ITimeDataProvider getTimeDataProvider() {
+ return fTimeProvider;
+ }
+
/**
* Gets the color map used by this timegraph viewer.
*
listener.widgetSelected(null);
}
}
+
+ if (null != fSelectionChangedListeners) {
+ for (ISelectionChangedListener listener : fSelectionChangedListeners) {
+ listener.selectionChanged(new SelectionChangedEvent(this, getSelection()));
+ }
+ }
}
/**
*
* @return The unmodifiable link event list
*
- * @since 2.0
+ * @since 1.1
*/
public List<ILinkEvent> getArrows() {
return Collections.unmodifiableList(fItemData.fLinks);
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[])}
* @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);
}
}
+ /**
+ * 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();
+ }
+
@Override
public ISelection getSelection() {
TimeGraphSelection sel = new TimeGraphSelection();
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;
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.
*
return fGridLineColor;
}
- /**
- * Set the bookmarks list.
- *
- * @param bookmarks
- * The bookmarks list, or null
- * @since 2.0
- */
- public void setBookmarks(List<IMarkerEvent> bookmarks) {
- fBookmarks = bookmarks;
- fTimeGraphScale.setBookmarks(bookmarks);
- }
-
/**
* Set the markers list.
*
*/
public void setMarkers(List<IMarkerEvent> markers) {
fMarkers = markers;
- fTimeGraphScale.setMarkers(markers);
}
/**
*/
public void setMarkersVisible(boolean visible) {
fMarkersVisible = visible;
- fTimeGraphScale.setMarkersVisible(visible);
}
/**
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;
* 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;
}
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;
// 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);
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);
}
}
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<ITimeEvent> iterator = entry.getTimeEventsIterator(time0, time1, maxDuration);
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));
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);
}
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();
+ }
}
}
} 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();
@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<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) {
*
* @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;
/**
* @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 2.0
+ * @since 1.2
*/
- public ViewerFilter[] getFilters() {
+ public @NonNull ViewerFilter[] getFilters() {
return Iterables.toArray(fFilters, ViewerFilter.class);
}
*
* @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));
} else {
item.fItemHeight = fGlobalItemHeight;
}
+ item.fItemHeight = Math.max(1, item.fItemHeight + fHeightAdjustment);
itemMap.put(entry, item);
if (entry.hasChildren()) {
Item oldItem = fItemMap.get(entry);
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.
* 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 {
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);
}
}