X-Git-Url: http://drtracing.org/?a=blobdiff_plain;f=org.eclipse.linuxtools.tmf.ui%2Fsrc%2Forg%2Feclipse%2Flinuxtools%2Ftmf%2Fui%2Fwidgets%2Ftimegraph%2Fwidgets%2FTimeGraphControl.java;h=8b433a8ec612417d0c95ad6820303eeacc1aae71;hb=0fab12b0f3f481a91b0d61e1a8dfbcb4d944758a;hp=baf682427194c14d865e0de1af1006b6eef264d9;hpb=b83af2c3da542d3ba4b07f93292afeec9958863e;p=deliverable%2Ftracecompass.git diff --git a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java index baf6824271..8b433a8ec6 100644 --- a/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java +++ b/org.eclipse.linuxtools.tmf.ui/src/org/eclipse/linuxtools/tmf/ui/widgets/timegraph/widgets/TimeGraphControl.java @@ -1,1598 +1,2787 @@ -/***************************************************************************** - * Copyright (c) 2007, 2008 Intel Corporation, 2009, 2010, 2011, 2012 Ericsson. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Intel Corporation - Initial API and implementation - * Ruslan A. Scherbakov, Intel - Initial API and implementation - * Alvaro Sanchez-Leon - Updated for TMF - * Patrick Tasse - Refactoring - * - *****************************************************************************/ - -package org.eclipse.linuxtools.tmf.ui.widgets.timegraph.widgets; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Vector; - -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.linuxtools.tmf.ui.widgets.timegraph.ITimeGraphPresentationProvider; -import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.ITimeGraphTreeListener; -import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.StateItem; -import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.TimeGraphTreeExpansionEvent; -import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.model.ITimeEvent; -import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.model.ITimeGraphEntry; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ControlEvent; -import org.eclipse.swt.events.ControlListener; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.MouseMoveListener; -import org.eclipse.swt.events.MouseTrackListener; -import org.eclipse.swt.events.MouseWheelListener; -import org.eclipse.swt.events.PaintEvent; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.events.TraverseEvent; -import org.eclipse.swt.events.TraverseListener; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Cursor; -import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.ScrollBar; - -public class TimeGraphControl extends TimeGraphBaseControl implements FocusListener, KeyListener, MouseMoveListener, MouseListener, MouseWheelListener, ControlListener, SelectionListener, MouseTrackListener, TraverseListener, ISelectionProvider { - - private static final int DRAG_NONE = 0; - private static final int DRAG_TRACE_ITEM = 1; - private static final int DRAG_SPLIT_LINE = 2; - public static final boolean DEFAULT_DRAW_THREAD_JOIN = true; - public static final boolean DEFAULT_DRAW_THREAD_WAIT = true; - public static final boolean DEFAULT_DRAW_THREAD_RELEASE = true; - public static final int H_SCROLLBAR_MAX = Integer.MAX_VALUE - 1; - private static final int CUSTOM_ITEM_HEIGHT = -1; // get item height from provider - - private static final double zoomCoeff = 1.5; - - private ITimeDataProvider _timeProvider; - private boolean _isInFocus = false; - private boolean _isDragCursor3 = false; - private boolean _isWaitCursor = true; - private boolean _mouseOverSplitLine = false; - private int _itemHeight = CUSTOM_ITEM_HEIGHT; - private int _minimumItemWidth = 0; - private int _topIndex = 0; - private int _dragState = DRAG_NONE; - private int _dragX0 = 0; - private int _dragX = 0; - private int _idealNameSpace = 0; - // private double _timeStep = 10000000; - private long _time0bak; - private long _time1bak; - private ITimeGraphPresentationProvider fTimeGraphProvider = null; - private ItemData _data = null; - private List _selectionListeners; - private List _selectionChangedListeners = new ArrayList(); - private List _treeListeners = new ArrayList(); - private Cursor _dragCursor3; - private Cursor _WaitCursor; - - // Vertical formatting formatting for the state control view - private boolean _visibleVerticalScroll = true; - private int _borderWidth = 0; - private int _headerHeight = 0; - - private Listener mouseScrollFilterListener; - - protected LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources()); - protected Color[] fEventColorMap = null; - - public TimeGraphControl(Composite parent, TimeGraphColorScheme colors) { - - super(parent, colors, SWT.NO_BACKGROUND | SWT.H_SCROLL | SWT.DOUBLE_BUFFERED); - - _data = new ItemData(); - - addFocusListener(this); - addMouseListener(this); - addMouseMoveListener(this); - addMouseTrackListener(this); - addMouseWheelListener(this); - addTraverseListener(this); - addKeyListener(this); - addControlListener(this); - ScrollBar scrollHor = getHorizontalBar(); - - if (scrollHor != null) { - scrollHor.addSelectionListener(this); - } - - _dragCursor3 = new Cursor(super.getDisplay(), SWT.CURSOR_SIZEWE); - _WaitCursor = new Cursor(super.getDisplay(), SWT.CURSOR_WAIT); - } - - @Override - public void dispose() { - super.dispose(); - _dragCursor3.dispose(); - _WaitCursor.dispose(); - fResourceManager.dispose(); - } - - /** - * Sets the timegraph provider used by this timegraph viewer. - * - * @param timeGraphProvider the timegraph provider - */ - public void setTimeGraphProvider(ITimeGraphPresentationProvider timeGraphProvider) { - fTimeGraphProvider = timeGraphProvider; - _data.provider = timeGraphProvider; -// RGB[] eventColorTable = fTimeGraphProvider.getEventColorTable(); -// if (eventColorTable != null) { -// fEventColorMap = new Color[eventColorTable.length]; -// for (int i = 0; i < eventColorTable.length; i++) { -// fEventColorMap[i] = fResourceManager.createColor(eventColorTable[i]); -// } -// } else { -// fEventColorMap = new Color[] { }; -// } - - StateItem[] stateItems = fTimeGraphProvider.getStateTable(); - if (stateItems != null) { - fEventColorMap = new Color[stateItems.length]; - for (int i = 0; i < stateItems.length; i++) { - fEventColorMap[i] = fResourceManager.createColor(stateItems[i].getStateColor()); - } - } else { - fEventColorMap = new Color[] { }; - } - - - } - - public void setTimeProvider(ITimeDataProvider timeProvider) { - _timeProvider = timeProvider; - adjustScrolls(); - redraw(); - } - - public void addSelectionListener(SelectionListener listener) { - if (listener == null) - SWT.error(SWT.ERROR_NULL_ARGUMENT); - if (null == _selectionListeners) - _selectionListeners = new ArrayList(); - _selectionListeners.add(listener); - } - - public void removeSelectionListener(SelectionListener listener) { - if (null != _selectionListeners) - _selectionListeners.remove(listener); - } - - public void fireSelectionChanged() { - if (null != _selectionListeners) { - Iterator it = _selectionListeners.iterator(); - while (it.hasNext()) { - SelectionListener listener = it.next(); - listener.widgetSelected(null); - } - } - } - - public void fireDefaultSelection() { - if (null != _selectionListeners) { - Iterator it = _selectionListeners.iterator(); - while (it.hasNext()) { - SelectionListener listener = it.next(); - listener.widgetDefaultSelected(null); - } - } - } - - public ITimeGraphEntry[] getTraces() { - return _data.getTraces(); - } - - public boolean[] getTraceFilter() { - return _data.getTraceFilter(); - } - - public void refreshData() { - _data.refreshData(); - adjustScrolls(); - redraw(); - } - - public void refreshData(ITimeGraphEntry traces[]) { - _data.refreshData(traces); - adjustScrolls(); - redraw(); - } - - public void adjustScrolls() { - if (null == _timeProvider) { - getHorizontalBar().setValues(0, 1, 1, 1, 1, 1); - return; - } - - // HORIZONTAL BAR - // Visible window - long time0 = _timeProvider.getTime0(); - long time1 = _timeProvider.getTime1(); - // Time boundaries - long timeMin = _timeProvider.getMinTime(); - long timeMax = _timeProvider.getMaxTime(); - - long delta = timeMax - timeMin; - - int timePos = 0; - int thumb = H_SCROLLBAR_MAX; - - if (delta != 0) { - // Thumb size (page size) - thumb = Math.max(1, (int) (H_SCROLLBAR_MAX * ((double) (time1 - time0) / delta))); - // At the beginning of visible window - timePos = (int) (H_SCROLLBAR_MAX * ((double) (time0 - timeMin) / delta)); - } - - // position, minimum, maximum, thumb size, increment (half page)t, page - // increment size (full page) - getHorizontalBar().setValues(timePos, 0, H_SCROLLBAR_MAX, thumb, Math.max(1, thumb / 2), Math.max(2, thumb)); - } - - boolean ensureVisibleItem(int idx, boolean redraw) { - boolean changed = false; - if (idx < 0) { - for (idx = 0; idx < _data._expandedItems.length; idx++) { - if (((TimeGraphItem) _data._expandedItems[idx])._selected) - break; - } - } - if (idx >= _data._expandedItems.length) - return changed; - if (idx < _topIndex) { - setTopIndex(idx); - //FIXME:getVerticalBar().setSelection(_topItem); - if (redraw) - redraw(); - changed = true; - } else { - int page = countPerPage(); - if (idx >= _topIndex + page) { - setTopIndex(idx - page + 1); - //FIXME:getVerticalBar().setSelection(_topItem); - if (redraw) - redraw(); - changed = true; - } - } - return changed; - } - - public void setTopIndex(int idx) { - idx = Math.min(idx, _data._expandedItems.length - countPerPage()); - idx = Math.max(0, idx); - _topIndex = idx; - redraw(); - } - - public void setExpandedState(ITimeGraphEntry entry, boolean expanded) { - TimeGraphItem item = _data.findItem(entry); - if (item != null && item._expanded != expanded) { - item._expanded = expanded; - _data.updateExpandedItems(); - redraw(); - } - } - - public void addTreeListener (ITimeGraphTreeListener listener) { - if (!_treeListeners.contains(listener)) { - _treeListeners.add(listener); - } - } - - public void removeTreeListener (ITimeGraphTreeListener listener) { - if (_treeListeners.contains(listener)) { - _treeListeners.remove(listener); - } - } - - public void fireTreeEvent(ITimeGraphEntry entry, boolean expanded) { - TimeGraphTreeExpansionEvent event = new TimeGraphTreeExpansionEvent(this, entry); - for (ITimeGraphTreeListener listener : _treeListeners) { - if (expanded) { - listener.treeExpanded(event); - } else { - listener.treeCollapsed(event); - } - } - } - - @Override - public ISelection getSelection() { - TimeGraphSelection sel = new TimeGraphSelection(); - ITimeGraphEntry trace = getSelectedTrace(); - if (null != trace && null != _timeProvider) { - long selectedTime = _timeProvider.getSelectedTime(); - ITimeEvent event = Utils.findEvent(trace, selectedTime, 0); - if (event != null) - sel.add(event); - else - sel.add(trace); - } - return sel; - } - - public ISelection getSelectionTrace() { - TimeGraphSelection sel = new TimeGraphSelection(); - ITimeGraphEntry trace = getSelectedTrace(); - if (null != trace) { - sel.add(trace); - } - return sel; - } - - public void selectTrace(int n) { - if (n != 1 && n != -1) - return; - boolean changed = false; - int lastSelection = -1; - for (int i = 0; i < _data._expandedItems.length; i++) { - TimeGraphItem item = (TimeGraphItem) _data._expandedItems[i]; - if (item._selected) { - lastSelection = i; - if (1 == n && i < _data._expandedItems.length - 1) { - item._selected = false; - if (item._hasChildren) { - _data.expandItem(i); - fireTreeEvent(item._trace, item._expanded); - } - item = (TimeGraphItem) _data._expandedItems[i + 1]; - if (item._hasChildren) { - _data.expandItem(i + 1); - fireTreeEvent(item._trace, item._expanded); - item = (TimeGraphItem) _data._expandedItems[i + 2]; - } - item._selected = true; - changed = true; - } else if (-1 == n && i > 0) { - i--; - TimeGraphItem prevItem = (TimeGraphItem) _data._expandedItems[i]; - if (prevItem._hasChildren) { - if (prevItem._expanded) { - if (i > 0) { - i--; - prevItem = (TimeGraphItem) _data._expandedItems[i]; - } - } - if (!prevItem._expanded) { - _data.expandItem(i); - fireTreeEvent(prevItem._trace, prevItem._expanded); - prevItem = (TimeGraphItem) _data._expandedItems[i + prevItem.children.size()]; - item._selected = false; - prevItem._selected = true; - changed = true; - } - } else { - item._selected = false; - prevItem._selected = true; - changed = true; - } - } - break; - } - } - if (lastSelection < 0 && _data._expandedItems.length > 0) { - TimeGraphItem item = (TimeGraphItem) _data._expandedItems[0]; - if (item._hasChildren) { - _data.expandItem(0); - fireTreeEvent(item._trace, item._expanded); - item = (TimeGraphItem) _data._expandedItems[1]; - item._selected = true; - changed = true; - } else { - item._selected = true; - changed = true; - } - } - if (changed) { - ensureVisibleItem(-1, false); - redraw(); - fireSelectionChanged(); - } - } - - public void selectEvent(int n) { - if (null == _timeProvider) - return; - ITimeGraphEntry trace = getSelectedTrace(); - if (trace == null) - return; - long selectedTime = _timeProvider.getSelectedTime(); - long endTime = _timeProvider.getEndTime(); - ITimeEvent nextEvent; - if (-1 == n && selectedTime > endTime) - nextEvent = Utils.findEvent(trace, selectedTime, 0); - else - nextEvent = Utils.findEvent(trace, selectedTime, n); - if (null == nextEvent && -1 == n) - nextEvent = Utils.getFirstEvent(trace); - if (null != nextEvent) { - long nextTime = nextEvent.getTime(); - // If last event detected e.g. going back or not moving to a next - // event - if (nextTime <= selectedTime && n == 1) { - // Select to the end of this last event - nextTime = nextEvent.getTime() + nextEvent.getDuration(); - // but not beyond the end of the trace - if (nextTime > endTime) { - nextTime = endTime; - } - } - _timeProvider.setSelectedTimeInt(nextTime, true); - fireSelectionChanged(); - } else if (1 == n) { - _timeProvider.setSelectedTimeInt(endTime, true); - fireSelectionChanged(); - } - } - - public void selectNextEvent() { - selectEvent(1); - // Notify if visible time window has been adjusted - _timeProvider.setStartFinishTimeNotify(_timeProvider.getTime0(), _timeProvider.getTime1()); - } - - public void selectPrevEvent() { - selectEvent(-1); - // Notify if visible time window has been adjusted - _timeProvider.setStartFinishTimeNotify(_timeProvider.getTime0(), _timeProvider.getTime1()); - } - - public void selectNextTrace() { - selectTrace(1); - } - - public void selectPrevTrace() { - selectTrace(-1); - } - - /** - * Zooming based on mouse cursor location with mouse scrolling - * - * @param zoomIn - */ - public void zoom(boolean zoomIn) { - int globalX = getDisplay().getCursorLocation().x; - Point p = toControl(globalX, 0); - int nameSpace = _timeProvider.getNameSpace(); - int timeSpace = _timeProvider.getTimeSpace(); - int xPos = Math.max(nameSpace, Math.min(nameSpace + timeSpace, p.x)); - long time0 = _timeProvider.getTime0(); - long time1 = _timeProvider.getTime1(); - long interval = time1 - time0; - if (interval == 0) { - interval = 1; - } // to allow getting out of single point interval - long newInterval; - if (zoomIn) { - newInterval = Math.max(Math.round((double) interval * 0.8), _timeProvider.getMinTimeInterval()); - } else { - newInterval = (long) Math.ceil((double) interval * 1.25); - } - long center = time0 + Math.round(((double) (xPos - nameSpace) / timeSpace * interval)); - long newTime0 = center - Math.round((double) newInterval * (center - time0) / interval); - long newTime1 = newTime0 + newInterval; - _timeProvider.setStartFinishTime(newTime0, newTime1); - } - - /** - * zoom in using single click - */ - public void zoomIn() { - long _time0 = _timeProvider.getTime0(); - long _time1 = _timeProvider.getTime1(); - long _range = _time1 - _time0; - long selTime = _timeProvider.getSelectedTime(); - if (selTime <= _time0 || selTime >= _time1) { - selTime = (_time0 + _time1) / 2; - } - long time0 = selTime - (long) ((selTime - _time0) / zoomCoeff); - long time1 = selTime + (long) ((_time1 - selTime) / zoomCoeff); - - long inaccuracy = (_timeProvider.getMaxTime() - _timeProvider.getMinTime()) - (time1 - time0); - - // Trace.debug("selTime:" + selTime + " time0:" + time0 + " time1:" - // + time1 + " inaccuracy:" + inaccuracy); - - if (inaccuracy > 0 && inaccuracy < 100) { - _timeProvider.setStartFinishTimeNotify(_timeProvider.getMinTime(), _timeProvider.getMaxTime()); - return; - } - - long m = _timeProvider.getMinTimeInterval(); - if ((time1 - time0) < m) { - time0 = selTime - (long) ((selTime - _time0) * m / _range); - time1 = time0 + m; - } - - _timeProvider.setStartFinishTimeNotify(time0, time1); - } - - /** - * zoom out using single click - */ - public void zoomOut() { - long _time0 = _timeProvider.getTime0(); - long _time1 = _timeProvider.getTime1(); - long selTime = _timeProvider.getSelectedTime(); - if (selTime <= _time0 || selTime >= _time1) { - selTime = (_time0 + _time1) / 2; - } - long time0 = (long) (selTime - (selTime - _time0) * zoomCoeff); - long time1 = (long) (selTime + (_time1 - selTime) * zoomCoeff); - - long inaccuracy = (_timeProvider.getMaxTime() - _timeProvider.getMinTime()) - (time1 - time0); - if (inaccuracy > 0 && inaccuracy < 100) { - _timeProvider.setStartFinishTimeNotify(_timeProvider.getMinTime(), _timeProvider.getMaxTime()); - return; - } - - _timeProvider.setStartFinishTimeNotify(time0, time1); - } - - public ITimeGraphEntry getSelectedTrace() { - ITimeGraphEntry trace = null; - int idx = getSelectedIndex(); - if (idx >= 0) - trace = _data._expandedItems[idx]._trace; - return trace; - } - - public int getSelectedIndex() { - int idx = -1; - for (int i = 0; i < _data._expandedItems.length; i++) { - TimeGraphItem item = (TimeGraphItem) _data._expandedItems[i]; - if (item._selected) { - idx = i; - break; - } - } - return idx; - } - - boolean toggle(int idx) { - boolean toggled = false; - if (idx >= 0 && idx < _data._expandedItems.length) { - TimeGraphItem item = (TimeGraphItem) _data._expandedItems[idx]; - if (item._hasChildren) { - item._expanded = !item._expanded; - _data.updateExpandedItems(); - adjustScrolls(); - redraw(); - toggled = true; - fireTreeEvent(item._trace, item._expanded); - } - } - return toggled; - } - - int getItemIndexAtY(int y) { - if (y < 0) { - return -1; - } - if (_itemHeight == CUSTOM_ITEM_HEIGHT) { - int ySum = 0; - for (int idx = _topIndex; idx < _data._expandedItems.length; idx++) { - ySum += _data._expandedItems[idx].itemHeight; - if (y < ySum) { - return idx; - } - } - return -1; - } - int idx = y / _itemHeight; - idx += _topIndex; - if (idx < _data._expandedItems.length) { - return idx; - } - return -1; - } - - boolean isOverSplitLine(int x) { - if (x < 0 || null == _timeProvider) - return false; - int w = 4; - int nameWidth = _timeProvider.getNameSpace(); - if (x > nameWidth - w && x < nameWidth + w) { - return true; - } else { - return false; - } - } - - TimeGraphItem getItem(Point pt) { - int idx = getItemIndexAtY(pt.y); - return idx >= 0 ? (TimeGraphItem) _data._expandedItems[idx] : null; - } - - long getTimeAtX(int x) { - if (null == _timeProvider) - return -1; - long hitTime = -1; - Point size = getCtrlSize(); - long time0 = _timeProvider.getTime0(); - long time1 = _timeProvider.getTime1(); - int nameWidth = _timeProvider.getNameSpace(); - x -= nameWidth; - if (x >= 0 && size.x >= nameWidth) { - if (time1 - time0 > size.x - nameWidth - RIGHT_MARGIN) { - // get the last possible time represented by the pixel position - // by taking the time of the next pixel position minus 1 - // nanosecond - hitTime = time0 + (long) ((time1 - time0) * ((double) (x + 1) / (size.x - nameWidth - RIGHT_MARGIN))) - 1; - } else { - hitTime = time0 + (long) ((time1 - time0) * ((double) (x) / (size.x - nameWidth - RIGHT_MARGIN))); - } - } - return hitTime; - } - - void selectItem(int idx, boolean addSelection) { - boolean changed = false; - if (addSelection) { - if (idx >= 0 && idx < _data._expandedItems.length) { - TimeGraphItem item = (TimeGraphItem) _data._expandedItems[idx]; - changed = (item._selected == false); - item._selected = true; - } - } else { - for (int i = 0; i < _data._expandedItems.length; i++) { - TimeGraphItem item = (TimeGraphItem) _data._expandedItems[i]; - if ((i == idx && !item._selected) || (idx == -1 && item._selected)) { - changed = true; - } - item._selected = i == idx; - } - } - changed |= ensureVisibleItem(idx, true); - if (changed) - redraw(); - } - - public void selectItem(ITimeGraphEntry trace, boolean addSelection) { - int idx = _data.findItemIndex(trace); - selectItem(idx, addSelection); - } - - public int countPerPage() { - int height = getCtrlSize().y; - int count = 0; - if (_itemHeight == CUSTOM_ITEM_HEIGHT) { - int ySum = 0; - for (int idx = _topIndex; idx < _data._expandedItems.length; idx++) { - ySum += _data._expandedItems[idx].itemHeight; - if (ySum >= height) { - return count; - } - count++; - } - for (int idx = _topIndex - 1; idx >= 0; idx--) { - ySum += _data._expandedItems[idx].itemHeight; - if (ySum >= height) { - return count; - } - count++; - } - return count; - } - if (height > 0) { - count = height / _itemHeight; - } - return count; - } - - public int getTopIndex() { - return _topIndex; - } - - public int getExpandedElementCount() { - return _data._expandedItems.length; - } - - Point getCtrlSize() { - Point size = getSize(); - if (getHorizontalBar().isVisible()) { - size.y -= getHorizontalBar().getSize().y; - } - return size; - } - - Rectangle getNameRect(Rectangle bound, int idx, int nameWidth) { - int x = bound.x; - int y = bound.y + (idx - _topIndex) * _itemHeight; - int width = nameWidth; - int height = _itemHeight; - if (_itemHeight == CUSTOM_ITEM_HEIGHT) { - int ySum = 0; - for (int i = _topIndex; i < idx; i++) { - ySum += _data._expandedItems[i].itemHeight; - } - y = bound.y + ySum; - height = _data._expandedItems[idx].itemHeight; - } - return new Rectangle(x, y, width, height); - } - - Rectangle getStatesRect(Rectangle bound, int idx, int nameWidth) { - int x = bound.x + nameWidth; - int y = bound.y + (idx - _topIndex) * _itemHeight; - int width = bound.width - x; - int height = _itemHeight; - if (_itemHeight == CUSTOM_ITEM_HEIGHT) { - int ySum = 0; - for (int i = _topIndex; i < idx; i++) { - ySum += _data._expandedItems[i].itemHeight; - } - y = bound.y + ySum; - height = _data._expandedItems[idx].itemHeight; - } - return new Rectangle(x, y, width, height); - } - - @Override - void paint(Rectangle bounds, PaintEvent e) { - GC gc = e.gc; - gc.setBackground(_colors.getColor(TimeGraphColorScheme.BACKGROUND)); - drawBackground(gc, bounds.x, bounds.y, bounds.width, bounds.height); - - if (bounds.width < 2 || bounds.height < 2 || null == _timeProvider) - return; - - _idealNameSpace = 0; - int nameSpace = _timeProvider.getNameSpace(); - - // draw empty name space background - gc.setBackground(_colors.getBkColor(false, false, true)); - drawBackground(gc, bounds.x, bounds.y, nameSpace, bounds.height); - - drawItems(bounds, _timeProvider, _data._expandedItems, _topIndex, nameSpace, gc); - - // draw selected time - long time0 = _timeProvider.getTime0(); - long time1 = _timeProvider.getTime1(); - long selectedTime = _timeProvider.getSelectedTime(); - double pixelsPerNanoSec = (bounds.width - nameSpace <= RIGHT_MARGIN) ? 0 : (double) (bounds.width - nameSpace - RIGHT_MARGIN) / (time1 - time0); - int x = bounds.x + nameSpace + (int) ((double) (selectedTime - time0) * pixelsPerNanoSec); - if (x >= nameSpace && x < bounds.x + bounds.width) { - gc.setForeground(_colors.getColor(TimeGraphColorScheme.SELECTED_TIME)); - gc.drawLine(x, bounds.y, x, bounds.y + bounds.height); - } - - // draw drag line, no line if name space is 0. - if (DRAG_SPLIT_LINE == _dragState) { - gc.setForeground(_colors.getColor(TimeGraphColorScheme.BLACK)); - gc.drawLine(bounds.x + nameSpace, bounds.y, bounds.x + nameSpace, bounds.y + bounds.height - 1); - } else if (DRAG_NONE == _dragState && _mouseOverSplitLine && _timeProvider.getNameSpace() > 0) { - gc.setForeground(_colors.getColor(TimeGraphColorScheme.RED)); - gc.drawLine(bounds.x + nameSpace, bounds.y, bounds.x + nameSpace, bounds.y + bounds.height - 1); - } - } - - public void drawItems(Rectangle bounds, ITimeDataProvider timeProvider, TimeGraphItem[] items, int topIndex, int nameSpace, GC gc) { - for (int i = topIndex; i < items.length; i++) { - TimeGraphItem item = (TimeGraphItem) items[i]; - drawItem(item, bounds, timeProvider, i, nameSpace, gc); - } - fTimeGraphProvider.postDrawControl(bounds, gc); - } - - /** - * Draws the item - * - * @param item the item to draw - * @param bounds the container rectangle - * @param i the item index - * @param nameSpace the name space - * @param gc - */ - protected void drawItem(TimeGraphItem item, Rectangle bounds, ITimeDataProvider timeProvider, int i, int nameSpace, GC gc) { - ITimeGraphEntry entry = item._trace; - long time0 = timeProvider.getTime0(); - long time1 = timeProvider.getTime1(); - long selectedTime = timeProvider.getSelectedTime(); - - Rectangle nameRect = getNameRect(bounds, i, nameSpace); - if (nameRect.y >= bounds.y + bounds.height) { - return; - } - - if (item._trace.getTimeEventsIterator() == null) { - Rectangle statesRect = getStatesRect(bounds, i, nameSpace); - nameRect.width += statesRect.width; - drawName(item, nameRect, gc); - } else { - drawName(item, nameRect, gc); - } - Rectangle rect = getStatesRect(bounds, i, nameSpace); - if (rect.isEmpty()) { - fTimeGraphProvider.postDrawEntry(entry, bounds, gc); - return; - } - if (time1 <= time0) { - gc.setBackground(_colors.getBkColor(false, false, false)); - gc.fillRectangle(rect); - fTimeGraphProvider.postDrawEntry(entry, bounds, gc); - return; - } - - // Initialize _rect1 to same values as enclosing rectangle rect - Rectangle stateRect = Utils.clone(rect); - boolean selected = item._selected; - // K pixels per second - double pixelsPerNanoSec = (rect.width <= RIGHT_MARGIN) ? 0 : (double) (rect.width - RIGHT_MARGIN) / (time1 - time0); - - boolean group = item._trace.getTimeEventsIterator() == null; - - if (!group) { - fillSpace(rect, gc, selected); - // Drawing rectangle is smaller than reserved space - stateRect.y += 3; - stateRect.height -= 6; - - long maxDuration = (timeProvider.getTimeSpace() == 0) ? Long.MAX_VALUE : 1 * (time1 - time0) / timeProvider.getTimeSpace(); - Iterator iterator = entry.getTimeEventsIterator(time0, time1, maxDuration); - - int lastX = -1; - while (iterator.hasNext()) { - ITimeEvent event = iterator.next(); - int x = rect.x + (int) ((event.getTime() - time0) * pixelsPerNanoSec); - int xEnd = rect.x + (int) ((event.getTime() + event.getDuration() - time0) * pixelsPerNanoSec); - if (x >= rect.x + rect.width || xEnd < rect.x) { - // event is out of bounds - continue; - } - xEnd = Math.min(rect.x + rect.width, xEnd); - stateRect.x = Math.max(rect.x, x); - stateRect.width = Math.max(0, xEnd - stateRect.x + 1); - if (stateRect.x == lastX) { - stateRect.width -= 1; - if (stateRect.width > 0) { - gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); - gc.drawPoint(stateRect.x, stateRect.y - 2); - stateRect.x += 1; - } - } else { - lastX = x; - } - boolean timeSelected = selectedTime >= event.getTime() && selectedTime < event.getTime() + event.getDuration(); - drawState(_colors, event, stateRect, gc, selected, timeSelected); - } - } - fTimeGraphProvider.postDrawEntry(entry, bounds, gc); - } - - protected void drawName(TimeGraphItem item, Rectangle bounds, GC gc) { - boolean group = item._trace.getTimeEventsIterator() == null; - if (group) { - gc.setBackground(_colors.getBkColorGroup(item._selected, _isInFocus)); - gc.fillRectangle(bounds); - if (item._selected && _isInFocus) { - gc.setForeground(_colors.getBkColor(item._selected, _isInFocus, false)); - gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1); - } - } else { - gc.setBackground(_colors.getBkColor(item._selected, _isInFocus, true)); - gc.setForeground(_colors.getFgColor(item._selected, _isInFocus)); - gc.fillRectangle(bounds); - } - - // No name to be drawn - if (_timeProvider.getNameSpace() == 0) { - return; - } - - int leftMargin = MARGIN + item.level * EXPAND_SIZE; - if (item._hasChildren) { - gc.setForeground(_colors.getFgColorGroup(false, false)); - gc.setBackground(_colors.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._expanded) { - 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._trace); - 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._name; - Point size = gc.stringExtent(name); - if (_idealNameSpace < leftMargin + size.x + MARGIN) { - _idealNameSpace = leftMargin + size.x + MARGIN; - } - if (!group) { - // 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$ - } - } - 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; - gc.setForeground(_colors.getFgColor(item._selected, _isInFocus)); - int textWidth = Utils.drawText(gc, name, rect, true); - leftMargin += textWidth + MARGIN; - rect.y -= 2; - - if (!group) { - // draw middle line - int x = bounds.x + leftMargin; - int width = bounds.width - x; - int midy = bounds.y + bounds.height / 2; - gc.setForeground(_colors.getColor(TimeGraphColorScheme.MID_LINE)); - gc.drawLine(x, midy, x + width, midy); - } - } - } - - protected void drawState(TimeGraphColorScheme colors, ITimeEvent event, - Rectangle rect, GC gc, boolean selected, boolean timeSelected) { - - int colorIdx = fTimeGraphProvider.getEventTableIndex(event); - if (colorIdx < 0) { - return; - } - boolean visible = rect.width == 0 ? false : true; - - if (visible) { - Color stateColor = null; - if (colorIdx < fEventColorMap.length) { - stateColor = fEventColorMap[colorIdx]; - } else { - stateColor = Display.getDefault().getSystemColor(SWT.COLOR_BLACK); - } - - timeSelected = timeSelected && selected; - if (timeSelected) { - // modify the color? - } - // fill all rect area - gc.setBackground(stateColor); - gc.fillRectangle(rect); - // get the border color? - gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); - - // draw bounds - if (!timeSelected) { - // Draw the top and bottom borders i.e. no side borders - // top - gc.drawLine(rect.x, rect.y, rect.x + rect.width - 1, rect.y); - // bottom - gc.drawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width - 1, rect.y + rect.height - 1); - } - } else { - gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); - gc.drawPoint(rect.x, rect.y - 2); - /* - // selected rectangle area is not visible but can be represented - // with a broken vertical line of specified width. - int width = 1; - rect.width = width; - gc.setForeground(stateColor); - int s = gc.getLineStyle(); - int w = gc.getLineWidth(); - gc.setLineStyle(SWT.LINE_DOT); - gc.setLineWidth(width); - // Trace.debug("Rectangle not visible, drawing vertical line with: " - // + rect.x + "," + rect.y + "," + rect.x + "," + rect.y - // + rect.height); - gc.drawLine(rect.x, rect.y, rect.x, rect.y + rect.height - 1); - gc.setLineStyle(s); - gc.setLineWidth(w); - if (!timeSelected) { - gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); - gc.drawPoint(rect.x, rect.y); - gc.drawPoint(rect.x, rect.y + rect.height - 1); - } - */ - } - fTimeGraphProvider.postDrawEvent(event, rect, gc); - } - - protected void fillSpace(Rectangle rect, GC gc, boolean selected) { - gc.setBackground(_colors.getBkColor(selected, _isInFocus, false)); - gc.fillRectangle(rect); - // draw middle line - gc.setForeground(_colors.getColor(TimeGraphColorScheme.MID_LINE)); - int midy = rect.y + rect.height / 2; - gc.drawLine(rect.x, midy, rect.x + rect.width, midy); - } - - @Override - public void keyTraversed(TraverseEvent e) { - if ((e.detail == SWT.TRAVERSE_TAB_NEXT) || (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)) - e.doit = true; - } - - @Override - public void keyPressed(KeyEvent e) { - int idx = -1; - if (_data._expandedItems.length == 0) { - return; - } - if (SWT.HOME == e.keyCode) { - idx = 0; - } else if (SWT.END == e.keyCode) { - idx = _data._expandedItems.length - 1; - } else if (SWT.ARROW_DOWN == e.keyCode) { - idx = getSelectedIndex(); - if (idx < 0) - idx = 0; - else if (idx < _data._expandedItems.length - 1) - idx++; - } else if (SWT.ARROW_UP == e.keyCode) { - idx = getSelectedIndex(); - if (idx < 0) - idx = 0; - else if (idx > 0) - idx--; - } else if (SWT.ARROW_LEFT == e.keyCode) { - selectPrevEvent(); - } else if (SWT.ARROW_RIGHT == e.keyCode) { - selectNextEvent(); - } else if (SWT.PAGE_DOWN == e.keyCode) { - int page = countPerPage(); - idx = getSelectedIndex(); - if (idx < 0) - idx = 0; - idx += page; - if (idx >= _data._expandedItems.length) - idx = _data._expandedItems.length - 1; - } else if (SWT.PAGE_UP == e.keyCode) { - int page = countPerPage(); - idx = getSelectedIndex(); - if (idx < 0) - idx = 0; - idx -= page; - if (idx < 0) - idx = 0; - } else if (SWT.CR == e.keyCode) { - idx = getSelectedIndex(); - if (idx >= 0) { - if (_data._expandedItems[idx]._hasChildren) { - toggle(idx); - } else { - fireDefaultSelection(); - } - } - idx = -1; - } - if (idx >= 0) { - selectItem(idx, false); - fireSelectionChanged(); - } - } - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void focusGained(FocusEvent e) { - _isInFocus = true; - redraw(); - } - - @Override - public void focusLost(FocusEvent e) { - _isInFocus = false; - if (DRAG_NONE != _dragState) { - setCapture(false); - _dragState = DRAG_NONE; - } - redraw(); - } - - public boolean isInFocus() { - return _isInFocus; - } - - /** - * Provide the possibilty to control the wait cursor externally e.g. data - * requests in progress - * - * @param waitInd - */ - public void waitCursor(boolean waitInd) { - // Update cursor as indicated - if (waitInd) { - setCursor(_WaitCursor); - _isWaitCursor = true; - } else { - setCursor(null); - _isWaitCursor = false; - } - - // Get ready for next mouse move - _isDragCursor3 = false; - } - - /** - *

- * If the x, y position is over the vertical split line (name to time - * ranges), then change the cursor to a drag cursor to indicate the user the - * possibility of resizing - *

- * - * @param x - * @param y - */ - void updateCursor(int x, int y) { - // if Wait cursor not active, check for the need to change to a drag - // cursor - if (_isWaitCursor == false) { - boolean isSplitLine = isOverSplitLine(x); - // No dragcursor is name space is fixed to zero - if (isSplitLine && !_isDragCursor3 && _timeProvider.getNameSpace() > 0) { - setCursor(_dragCursor3); - _isDragCursor3 = true; - } else if (!isSplitLine && _isDragCursor3) { - setCursor(null); - _isDragCursor3 = false; - } - } - } - - @Override - public void mouseMove(MouseEvent e) { - if (null == _timeProvider) - return; - Point size = getCtrlSize(); - if (DRAG_TRACE_ITEM == _dragState) { - int nameWidth = _timeProvider.getNameSpace(); - int x = e.x - nameWidth; - if (x > 0 && size.x > nameWidth && _dragX != x) { - _dragX = x; - double pixelsPerNanoSec = (size.x - nameWidth <= RIGHT_MARGIN) ? 0 : (double) (size.x - nameWidth - RIGHT_MARGIN) / (_time1bak - _time0bak); - long timeDelta = (long) ((pixelsPerNanoSec == 0) ? 0 : ((_dragX - _dragX0) / pixelsPerNanoSec)); - long time1 = _time1bak - timeDelta; - long maxTime = _timeProvider.getMaxTime(); - if (time1 > maxTime) - time1 = maxTime; - long time0 = time1 - (_time1bak - _time0bak); - if (time0 < _timeProvider.getMinTime()) { - time0 = _timeProvider.getMinTime(); - time1 = time0 + (_time1bak - _time0bak); - } - _timeProvider.setStartFinishTime(time0, time1); - } - } else if (DRAG_SPLIT_LINE == _dragState) { - _dragX = e.x; - _timeProvider.setNameSpace(e.x); - } else if (DRAG_NONE == _dragState) { - boolean mouseOverSplitLine = isOverSplitLine(e.x); - if (_mouseOverSplitLine != mouseOverSplitLine) { - redraw(); - } - _mouseOverSplitLine = mouseOverSplitLine; - // Make sure any time changes are notified to the application e.g. - // getting back from the horizontal scroll bar or zoomed using the - // mouse wheel - _timeProvider.notifyStartFinishTime(); - } - updateCursor(e.x, e.y); - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - if (null == _timeProvider) - return; - if (1 == e.button) { - if (isOverSplitLine(e.x) && _timeProvider.getNameSpace() != 0) { - _timeProvider.setNameSpace(_idealNameSpace); - boolean mouseOverSplitLine = isOverSplitLine(e.x); - if (_mouseOverSplitLine != mouseOverSplitLine) { - redraw(); - } - _mouseOverSplitLine = mouseOverSplitLine; - return; - } - int idx = getItemIndexAtY(e.y); - if (idx >= 0) { - selectItem(idx, false); - fireDefaultSelection(); - } - } - } - - @Override - public void mouseDown(MouseEvent e) { - if (null == _timeProvider) - return; - int idx; - if (1 == e.button) { - int nameSpace = _timeProvider.getNameSpace(); - if (nameSpace != 0) { - if (isOverSplitLine(e.x)) { - _dragState = DRAG_SPLIT_LINE; - _dragX = _dragX0 = e.x; - _time0bak = _timeProvider.getTime0(); - _time1bak = _timeProvider.getTime1(); - redraw(); - return; - } - } - - idx = getItemIndexAtY(e.y); - if (idx >= 0) { - TimeGraphItem item = _data._expandedItems[idx]; - if (item._hasChildren && e.x < nameSpace && e.x < MARGIN + (item.level + 1) * EXPAND_SIZE) { - toggle(idx); - } else { - long hitTime = getTimeAtX(e.x); - if (hitTime >= 0) { - // _timeProvider.setSelectedTimeInt(hitTime, false); - setCapture(true); - _dragState = DRAG_TRACE_ITEM; - _dragX = _dragX0 = e.x - nameSpace; - _time0bak = _timeProvider.getTime0(); - _time1bak = _timeProvider.getTime1(); - } - } - selectItem(idx, false); - fireSelectionChanged(); - } else { - selectItem(idx, false); // clear selection - redraw(); - fireSelectionChanged(); - } - } - } - - @Override - public void mouseUp(MouseEvent e) { - if (DRAG_NONE != _dragState) { - setCapture(false); - if (DRAG_TRACE_ITEM == _dragState) { - // Notify time provider to check the need for listener - // notification - _timeProvider.notifyStartFinishTime(); - if (_dragX == _dragX0) { // click without drag - long time = getTimeAtX(e.x); - _timeProvider.setSelectedTimeInt(time, false); - } - } else if (DRAG_SPLIT_LINE == _dragState) { - redraw(); - } - _dragState = DRAG_NONE; - } - } - - @Override - public void mouseEnter(MouseEvent e) { - if (mouseScrollFilterListener == null) { - mouseScrollFilterListener = new Listener() { - // This filter is used to prevent scrolling of the view when the - // mouse wheel is used to zoom - @Override - public void handleEvent(Event event) { - event.doit = false; - } - }; - getDisplay().addFilter(SWT.MouseWheel, mouseScrollFilterListener); - } - } - - @Override - public void mouseExit(MouseEvent e) { - if (mouseScrollFilterListener != null) { - getDisplay().removeFilter(SWT.MouseWheel, mouseScrollFilterListener); - mouseScrollFilterListener = null; - } - if (_mouseOverSplitLine) { - _mouseOverSplitLine = false; - redraw(); - } - } - - @Override - public void mouseHover(MouseEvent e) { - } - - @Override - public void mouseScrolled(MouseEvent e) { - if ((mouseScrollFilterListener == null) || _dragState != DRAG_NONE) { - return; - } - if (e.x < _timeProvider.getNameSpace() || e.x > getSize().x) { - setTopIndex(getTopIndex() - e.count); - } else if (_timeProvider.getTime0() != _timeProvider.getTime1()) { - if (e.count > 0) { - zoom(true); - } else if (e.count < 0) { - zoom(false); - } - } - } - - @Override - public void controlMoved(ControlEvent e) { - } - - @Override - public void controlResized(ControlEvent e) { - adjustScrolls(); - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } - - @Override - public void widgetSelected(SelectionEvent e) { - if (e.widget == getVerticalBar()) { - setTopIndex(getVerticalBar().getSelection()); - } else if (e.widget == getHorizontalBar() && null != _timeProvider) { - int start = getHorizontalBar().getSelection(); - long time0 = _timeProvider.getTime0(); - long time1 = _timeProvider.getTime1(); - long timeMin = _timeProvider.getMinTime(); - long timeMax = _timeProvider.getMaxTime(); - long delta = timeMax - timeMin; - - long range = time1 - time0; - // _timeRangeFixed = true; - time0 = timeMin + Math.round(delta * ((double) start / H_SCROLLBAR_MAX)); - time1 = time0 + range; - - // TODO: Follow-up with Bug 310310 - // In Linux SWT.DRAG is the only value received - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=310310 - if (e.detail == SWT.DRAG) { - _timeProvider.setStartFinishTime(time0, time1); - } else { - _timeProvider.setStartFinishTimeNotify(time0, time1); - } - } - } - - public boolean isVisibleVerticalScroll() { - return _visibleVerticalScroll; - } - - @Override - public int getBorderWidth() { - return _borderWidth; - } - - public void setBorderWidth(int borderWidth) { - this._borderWidth = borderWidth; - } - - public int getHeaderHeight() { - return _headerHeight; - } - - public void setHeaderHeight(int headerHeight) { - this._headerHeight = headerHeight; - } - - public int getItemHeight() { - return _itemHeight; - } - - public void setItemHeight(int rowHeight) { - this._itemHeight = rowHeight; - } - - public void setMinimumItemWidth(int width) { - this._minimumItemWidth = width; - } - - public int getMinimumItemWidth() { - return _minimumItemWidth; - } - - public Vector getFilteredOut() { - return _data.getFilteredOut(); - } - - // @Override - @Override - public void addSelectionChangedListener(ISelectionChangedListener listener) { - if (listener != null) { - if (!_selectionChangedListeners.contains(listener)) { - _selectionChangedListeners.add(listener); - } - } - } - - // @Override - @Override - public void removeSelectionChangedListener(ISelectionChangedListener listener) { - if (listener != null) { - _selectionChangedListeners.remove(listener); - } - } - - // @Override - @Override - public void setSelection(ISelection selection) { - if (selection instanceof TimeGraphSelection) { - TimeGraphSelection sel = (TimeGraphSelection) selection; - Object ob = sel.getFirstElement(); - if (ob instanceof ITimeGraphEntry) { - ITimeGraphEntry trace = (ITimeGraphEntry) ob; - selectItem(trace, false); - } - } - - } - -} - -class ItemData { - public TimeGraphItem[] _expandedItems = new TimeGraphItem[0]; - public TimeGraphItem[] _items = new TimeGraphItem[0]; - private ITimeGraphEntry _traces[] = new ITimeGraphEntry[0]; - private boolean traceFilter[] = new boolean[0]; - private Vector filteredOut = new Vector(); - public ITimeGraphPresentationProvider provider; - - public ItemData() { - } - - TimeGraphItem findItem(ITimeGraphEntry entry) { - if (entry == null) - return null; - - for (int i = 0; i < _items.length; i++) { - TimeGraphItem item = _items[i]; - if (item._trace == entry) { - return item; - } - } - - return null; - } - - int findItemIndex(ITimeGraphEntry trace) { - if (trace == null) - return -1; - - for (int i = 0; i < _expandedItems.length; i++) { - TimeGraphItem item = _expandedItems[i]; - if (item._trace == trace) { - return i; - } - } - - return -1; - } - - public void refreshData() { - List itemList = new ArrayList(); - filteredOut.clear(); - for (int i = 0; i < _traces.length; i++) { - ITimeGraphEntry entry = _traces[i]; - refreshData(itemList, null, 0, entry); - } - _items = itemList.toArray(new TimeGraphItem[0]); - updateExpandedItems(); - } - - private void refreshData(List itemList, TimeGraphItem parent, int level, ITimeGraphEntry entry) { - TimeGraphItem item = new TimeGraphItem(entry, entry.getName(), level); - if (parent != null) { - parent.children.add(item); - } - item.itemHeight = provider.getItemHeight(entry); - itemList.add(item); - if (entry.hasChildren()) { - item._expanded = true; - item._hasChildren = true; - for (ITimeGraphEntry child : entry.getChildren()) { - refreshData(itemList, item, level + 1, child); - } - } - } - - public void updateExpandedItems() { - List expandedItemList = new ArrayList(); - for (int i = 0; i < _traces.length; i++) { - ITimeGraphEntry entry = _traces[i]; - TimeGraphItem item = findItem(entry); - refreshExpanded(expandedItemList, item); - } - _expandedItems = expandedItemList.toArray(new TimeGraphItem[0]); - } - - private void refreshExpanded(List expandedItemList, TimeGraphItem item) { - expandedItemList.add(item); - if (item._hasChildren && item._expanded) { - for (TimeGraphItem child : item.children) { - refreshExpanded(expandedItemList, child); - } - } - } - - public void expandItem(int idx) { - if (idx < 0 || idx >= _expandedItems.length) - return; - TimeGraphItem item = (TimeGraphItem) _expandedItems[idx]; - if (item._hasChildren && !item._expanded) { - item._expanded = true; - updateExpandedItems(); - } - } - - public void refreshData(ITimeGraphEntry traces[]) { - if (traces == null || traces.length == 0) { - traceFilter = null; - } else if (traceFilter == null || traces.length != traceFilter.length) { - traceFilter = new boolean[traces.length]; - java.util.Arrays.fill(traceFilter, true); - } - - _traces = traces; - refreshData(); - } - - public ITimeGraphEntry[] getTraces() { - return _traces; - } - - public boolean[] getTraceFilter() { - return traceFilter; - } - - public Vector getFilteredOut() { - return filteredOut; - } -} +/***************************************************************************** + * Copyright (c) 2007, 2014 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 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Intel Corporation - Initial API and implementation + * Ruslan A. Scherbakov, Intel - Initial API and implementation + * Alvaro Sanchez-Leon, Ericsson - Updated for TMF + * Patrick Tasse, Ericsson - Refactoring + * Geneviève Bastien, École Polytechnique de Montréal - Move code to + * provide base classes for time graph view + * Add display of links between items + * Xavier Raynaud, Kalray - Code optimization + * Generoso Pagano, Inria - Support for drag selection listeners + *****************************************************************************/ + +package org.eclipse.linuxtools.tmf.ui.widgets.timegraph.widgets; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jface.action.IStatusLineManager; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.viewers.AbstractTreeViewer; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.ITimeGraphColorListener; +import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.ITimeGraphPresentationProvider; +import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.ITimeGraphPresentationProvider2; +import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.ITimeGraphTimeListener; +import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.ITimeGraphTreeListener; +import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.StateItem; +import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.TimeGraphTimeEvent; +import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.TimeGraphTreeExpansionEvent; +import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.model.ILinkEvent; +import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.model.ITimeEvent; +import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.model.ITimeGraphEntry; +import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.widgets.Utils.Resolution; +import org.eclipse.linuxtools.tmf.ui.widgets.timegraph.widgets.Utils.TimeFormat; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MenuDetectEvent; +import org.eclipse.swt.events.MenuDetectListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.MouseTrackListener; +import org.eclipse.swt.events.MouseWheelListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.events.TypedEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.ScrollBar; + +/** + * Time graph control implementation + * + * @version 1.0 + * @author Alvaro Sanchez-Leon + * @author Patrick Tasse + */ +public class TimeGraphControl extends TimeGraphBaseControl + implements FocusListener, KeyListener, MouseMoveListener, MouseListener, MouseWheelListener, + ControlListener, SelectionListener, MouseTrackListener, TraverseListener, ISelectionProvider, + MenuDetectListener, ITmfTimeGraphDrawingHelper, ITimeGraphColorListener { + + /** Max scrollbar size */ + public static final int H_SCROLLBAR_MAX = Integer.MAX_VALUE - 1; + + /** Constant indicating that all levels of the time graph should be expanded + * @since 3.1 */ + public static final int ALL_LEVELS = AbstractTreeViewer.ALL_LEVELS; + + private static final int DRAG_NONE = 0; + private static final int DRAG_TRACE_ITEM = 1; + private static final int DRAG_SPLIT_LINE = 2; + private static final int DRAG_ZOOM = 3; + private static final int DRAG_SELECTION = 4; + + private static final int CUSTOM_ITEM_HEIGHT = -1; // get item height from provider + + private static final double ZOOM_FACTOR = 1.5; + private static final double ZOOM_IN_FACTOR = 0.8; + private static final double ZOOM_OUT_FACTOR = 1.25; + + private static final int SNAP_WIDTH = 2; + private static final int ARROW_HOVER_MAX_DIST = 5; + + private static final int NO_STATUS = -1; + + /** Resource manager */ + private LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources()); + + /** Color map for event types */ + private Color[] fEventColorMap = null; + + private ITimeDataProvider fTimeProvider; + private IStatusLineManager fStatusLineManager = null; + private TimeGraphScale fTimeGraphScale = null; + + private boolean fIsInFocus = false; + private boolean fMouseOverSplitLine = false; + private int fGlobalItemHeight = CUSTOM_ITEM_HEIGHT; + private int fMinimumItemWidth = 0; + private int fTopIndex = 0; + private int fDragState = DRAG_NONE; + private int fDragButton; + private int fDragX0 = 0; + private int fDragX = 0; + private 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 fSelectionListeners; + private List fDragSelectionListeners; + private final List fSelectionChangedListeners = new ArrayList<>(); + private final List fTreeListeners = new ArrayList<>(); + private final List fTimeGraphEntryMenuListeners = new ArrayList<>(); + private final List fTimeEventMenuListeners = new ArrayList<>(); + private final Cursor fDragCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_HAND); + private final Cursor fResizeCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_IBEAM); + private final Cursor fWaitCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_WAIT); + private final Cursor fZoomCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_SIZEWE); + private final List fFilters = new ArrayList<>(); + private MenuDetectEvent fPendingMenuDetectEvent = null; + private boolean fHideArrows = false; + private int fAutoExpandLevel = ALL_LEVELS; + + private int fBorderWidth = 0; + private int fHeaderHeight = 0; + + private Listener fMouseScrollFilterListener; + + private MouseScrollNotifier fMouseScrollNotifier; + private final Object fMouseScrollNotifierLock = new Object(); + + private class MouseScrollNotifier extends Thread { + private static final long DELAY = 400L; + private static final long POLLING_INTERVAL = 10L; + private long fLastScrollTime = Long.MAX_VALUE; + + @Override + public void run() { + while ((System.currentTimeMillis() - fLastScrollTime) < DELAY) { + try { + Thread.sleep(POLLING_INTERVAL); + } catch (Exception e) { + return; + } + } + if (!isInterrupted()) { + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + if (isDisposed()) { + return; + } + fTimeProvider.notifyStartFinishTime(); + } + }); + } + synchronized (fMouseScrollNotifierLock) { + fMouseScrollNotifier = null; + } + } + + public void mouseScrolled() { + fLastScrollTime = System.currentTimeMillis(); + } + } + + /** + * Standard constructor + * + * @param parent + * The parent composite object + * @param colors + * The color scheme to use + */ + public TimeGraphControl(Composite parent, TimeGraphColorScheme colors) { + + super(parent, colors, SWT.NO_BACKGROUND | SWT.H_SCROLL | SWT.DOUBLE_BUFFERED); + + fItemData = new ItemData(); + + addFocusListener(this); + addMouseListener(this); + addMouseMoveListener(this); + addMouseTrackListener(this); + addMouseWheelListener(this); + addTraverseListener(this); + addKeyListener(this); + addControlListener(this); + addMenuDetectListener(this); + ScrollBar scrollHor = getHorizontalBar(); + + if (scrollHor != null) { + scrollHor.addSelectionListener(this); + } + } + + @Override + public void dispose() { + super.dispose(); + fResourceManager.dispose(); + } + + /** + * Sets the timegraph provider used by this timegraph viewer. + * + * @param timeGraphProvider the timegraph provider + */ + public void setTimeGraphProvider(ITimeGraphPresentationProvider timeGraphProvider) { + fTimeGraphProvider = timeGraphProvider; + + if (timeGraphProvider instanceof ITimeGraphPresentationProvider2) { + ((ITimeGraphPresentationProvider2) timeGraphProvider).setDrawingHelper(this); + ((ITimeGraphPresentationProvider2) timeGraphProvider).addColorListener(this); + } + + StateItem[] stateItems = fTimeGraphProvider.getStateTable(); + colorSettingsChanged(stateItems); + } + + /** + * Gets the timegraph provider used by this timegraph viewer. + * + * @return the timegraph provider, or null if not set. + * @since 3.0 + */ + public ITimeGraphPresentationProvider getTimeGraphProvider() { + return fTimeGraphProvider; + } + + /** + * Gets the color map used by this timegraph viewer. + * + * @return a color map, or null if not set. + * @since 3.0 + */ + public Color[] getEventColorMap() { + return fEventColorMap; + } + + /** + * Assign the given time provider + * + * @param timeProvider + * The time provider + */ + public void setTimeProvider(ITimeDataProvider timeProvider) { + fTimeProvider = timeProvider; + adjustScrolls(); + redraw(); + } + + /** + * Assign the status line manager + * + * @param statusLineManager + * The status line manager, or null to disable status line messages + * @since 2.1 + */ + public void setStatusLineManager(IStatusLineManager statusLineManager) { + if (fStatusLineManager != null && statusLineManager == null) { + fStatusLineManager.setMessage(""); //$NON-NLS-1$ + } + fStatusLineManager = statusLineManager; + } + + /** + * Assign the time graph scale + * + * @param timeGraphScale + * The time graph scale + * @since 2.1 + */ + public void setTimeGraphScale(TimeGraphScale timeGraphScale) { + fTimeGraphScale = timeGraphScale; + } + + /** + * Add a selection listener + * + * @param listener + * The listener to add + */ + public void addSelectionListener(SelectionListener listener) { + if (listener == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + if (null == fSelectionListeners) { + fSelectionListeners = new ArrayList<>(); + } + fSelectionListeners.add(listener); + } + + /** + * Remove a selection listener + * + * @param listener + * The listener to remove + */ + public void removeSelectionListener(SelectionListener listener) { + if (null != fSelectionListeners) { + fSelectionListeners.remove(listener); + } + } + + /** + * Selection changed callback + */ + public void fireSelectionChanged() { + if (null != fSelectionListeners) { + Iterator it = fSelectionListeners.iterator(); + while (it.hasNext()) { + SelectionListener listener = it.next(); + listener.widgetSelected(null); + } + } + } + + /** + * Default selection callback + */ + public void fireDefaultSelection() { + if (null != fSelectionListeners) { + Iterator it = fSelectionListeners.iterator(); + while (it.hasNext()) { + SelectionListener listener = it.next(); + listener.widgetDefaultSelected(null); + } + } + } + + /** + * Add a drag selection listener + * + * @param listener + * The listener to add + * @since 3.1 + */ + public void addDragSelectionListener(ITimeGraphTimeListener listener) { + if (listener == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + if (null == fDragSelectionListeners) { + fDragSelectionListeners = new ArrayList<>(); + } + fDragSelectionListeners.add(listener); + } + + /** + * Remove a drag selection listener + * + * @param listener + * The listener to remove + * @since 3.1 + */ + public void removeDragSelectionListener(ITimeGraphTimeListener listener) { + if (null != fDragSelectionListeners) { + fDragSelectionListeners.remove(listener); + } + } + + /** + * Drag Selection changed callback + * + * @param start + * Time interval start + * @param end + * Time interval end + * @since 3.1 + */ + public void fireDragSelectionChanged(long start, long end) { + // check for backward intervals + long beginTime, endTime; + if (start > end) { + beginTime = end; + endTime = start; + } else { + beginTime = start; + endTime = end; + } + // call the listeners + if (null != fDragSelectionListeners) { + Iterator it = fDragSelectionListeners.iterator(); + while (it.hasNext()) { + ITimeGraphTimeListener listener = it.next(); + listener.timeSelected(new TimeGraphTimeEvent(this, beginTime, endTime)); + } + } + } + + /** + * Get the traces in the model + * + * @return The array of traces + */ + public ITimeGraphEntry[] getTraces() { + return fItemData.getEntries(); + } + + /** + * Get the on/off trace filters + * + * @return The array of filters + */ + public boolean[] getTraceFilter() { + return fItemData.getEntryFilter(); + } + + /** + * Refresh the data for the thing + */ + public void refreshData() { + fItemData.refreshData(); + adjustScrolls(); + redraw(); + } + + /** + * Refresh data for the given traces + * + * @param traces + * The traces to refresh + */ + public void refreshData(ITimeGraphEntry[] traces) { + fItemData.refreshData(traces); + adjustScrolls(); + redraw(); + } + + /** + * Refresh the links (arrows) of this widget + * + * @param events The link events to refresh + * @since 2.1 + */ + public void refreshArrows(List events) { + fItemData.refreshArrows(events); + } + + /** + * Adjust the scoll bars + */ + public void adjustScrolls() { + if (null == fTimeProvider) { + getHorizontalBar().setValues(0, 1, 1, 1, 1, 1); + return; + } + + // HORIZONTAL BAR + // Visible window + long time0 = fTimeProvider.getTime0(); + long time1 = fTimeProvider.getTime1(); + // Time boundaries + long timeMin = fTimeProvider.getMinTime(); + long timeMax = fTimeProvider.getMaxTime(); + + long delta = timeMax - timeMin; + + int timePos = 0; + int thumb = H_SCROLLBAR_MAX; + + if (delta != 0) { + // Thumb size (page size) + thumb = Math.max(1, (int) (H_SCROLLBAR_MAX * ((double) (time1 - time0) / delta))); + // At the beginning of visible window + timePos = (int) (H_SCROLLBAR_MAX * ((double) (time0 - timeMin) / delta)); + } + + // position, minimum, maximum, thumb size, increment (half page)t, page + // increment size (full page) + getHorizontalBar().setValues(timePos, 0, H_SCROLLBAR_MAX, thumb, Math.max(1, thumb / 2), Math.max(2, thumb)); + } + + boolean ensureVisibleItem(int idx, boolean redraw) { + boolean changed = false; + int index = idx; + if (index < 0) { + for (index = 0; index < fItemData.fExpandedItems.length; index++) { + if (fItemData.fExpandedItems[index].fSelected) { + break; + } + } + } + if (index >= fItemData.fExpandedItems.length) { + return changed; + } + if (index < fTopIndex) { + setTopIndex(index); + if (redraw) { + redraw(); + } + changed = true; + } else { + int page = countPerPage(); + if (index >= fTopIndex + page) { + setTopIndex(index - page + 1); + if (redraw) { + redraw(); + } + changed = true; + } + } + return changed; + } + + /** + * Assign the given index as the top one + * + * @param idx + * The index + */ + public void setTopIndex(int idx) { + int index = Math.min(idx, fItemData.fExpandedItems.length - countPerPage()); + index = Math.max(0, index); + fTopIndex = index; + redraw(); + } + + /** + * Sets the auto-expand level to be used when the entries are refreshed + * using {@link #refreshData()} or {@link #refreshData(ITimeGraphEntry[])}. + * The value 0 means that there is no auto-expand; 1 means that top-level + * entries are expanded, but not their children; 2 means that top-level + * entries are expanded, and their children, but not grand-children; and so + * on. + *

+ * The value {@link #ALL_LEVELS} means that all subtrees should be expanded. + *

+ * @param level + * non-negative level, or ALL_LEVELS to expand all + * levels of the tree + * @since 3.1 + */ + public void setAutoExpandLevel(int level) { + fAutoExpandLevel = level; + } + + /** + * Returns the auto-expand level. + * + * @return non-negative level, or ALL_LEVELS if all levels of + * the tree are expanded automatically + * @see #setAutoExpandLevel + * @since 3.1 + */ + public int getAutoExpandLevel() { + return fAutoExpandLevel; + } + + /** + * Set the expanded state of a given entry + * + * @param entry + * The entry + * @param expanded + * True if expanded, false if collapsed + */ + public void setExpandedState(ITimeGraphEntry entry, boolean expanded) { + Item item = fItemData.findItem(entry); + if (item != null && item.fExpanded != expanded) { + item.fExpanded = expanded; + fItemData.updateExpandedItems(); + redraw(); + } + } + + /** + * Collapses all nodes of the viewer's tree, starting with the root. + * + * @since 2.0 + */ + public void collapseAll() { + for (Item item : fItemData.fItems) { + item.fExpanded = false; + } + fItemData.updateExpandedItems(); + redraw(); + } + + /** + * Expands all nodes of the viewer's tree, starting with the root. + * + * @since 2.0 + */ + public void expandAll() { + for (Item item : fItemData.fItems) { + item.fExpanded = true; + } + fItemData.updateExpandedItems(); + redraw(); + } + + /** + * Add a tree listener + * + * @param listener + * The listener to add + */ + public void addTreeListener(ITimeGraphTreeListener listener) { + if (!fTreeListeners.contains(listener)) { + fTreeListeners.add(listener); + } + } + + /** + * Remove a tree listener + * + * @param listener + * The listener to remove + */ + public void removeTreeListener(ITimeGraphTreeListener listener) { + if (fTreeListeners.contains(listener)) { + fTreeListeners.remove(listener); + } + } + + /** + * Tree event callback + * + * @param entry + * The affected entry + * @param expanded + * The expanded state (true for expanded, false for collapsed) + */ + public void fireTreeEvent(ITimeGraphEntry entry, boolean expanded) { + TimeGraphTreeExpansionEvent event = new TimeGraphTreeExpansionEvent(this, entry); + for (ITimeGraphTreeListener listener : fTreeListeners) { + if (expanded) { + listener.treeExpanded(event); + } else { + listener.treeCollapsed(event); + } + } + } + + /** + * Add a menu listener on {@link ITimeGraphEntry}s + * @param listener + * The listener to add + * @since 1.2 + */ + public void addTimeGraphEntryMenuListener(MenuDetectListener listener) { + if (!fTimeGraphEntryMenuListeners.contains(listener)) { + fTimeGraphEntryMenuListeners.add(listener); + } + } + + /** + * Remove a menu listener on {@link ITimeGraphEntry}s + * + * @param listener + * The listener to remove + * @since 1.2 + */ + public void removeTimeGraphEntryMenuListener(MenuDetectListener listener) { + if (fTimeGraphEntryMenuListeners.contains(listener)) { + fTimeGraphEntryMenuListeners.remove(listener); + } + } + + /** + * Menu event callback on {@link ITimeGraphEntry}s + * + * @param event + * The MenuDetectEvent, with field {@link TypedEvent#data} set to the selected {@link ITimeGraphEntry} + */ + private void fireMenuEventOnTimeGraphEntry(MenuDetectEvent event) { + for (MenuDetectListener listener : fTimeGraphEntryMenuListeners) { + listener.menuDetected(event); + } + } + + /** + * Add a menu listener on {@link ITimeEvent}s + * + * @param listener + * The listener to add + * @since 1.2 + */ + public void addTimeEventMenuListener(MenuDetectListener listener) { + if (!fTimeEventMenuListeners.contains(listener)) { + fTimeEventMenuListeners.add(listener); + } + } + + /** + * Remove a menu listener on {@link ITimeEvent}s + * + * @param listener + * The listener to remove + * @since 1.2 + */ + public void removeTimeEventMenuListener(MenuDetectListener listener) { + if (fTimeEventMenuListeners.contains(listener)) { + fTimeEventMenuListeners.remove(listener); + } + } + + /** + * Menu event callback on {@link ITimeEvent}s + * + * @param event + * The MenuDetectEvent, with field {@link TypedEvent#data} set to the selected {@link ITimeEvent} + */ + private void fireMenuEventOnTimeEvent(MenuDetectEvent event) { + for (MenuDetectListener listener : fTimeEventMenuListeners) { + listener.menuDetected(event); + } + } + + @Override + public ISelection getSelection() { + TimeGraphSelection sel = new TimeGraphSelection(); + ITimeGraphEntry trace = getSelectedTrace(); + if (null != trace && null != fTimeProvider) { + long selectedTime = fTimeProvider.getSelectionBegin(); + ITimeEvent event = Utils.findEvent(trace, selectedTime, 0); + if (event != null) { + sel.add(event); + } else { + sel.add(trace); + } + } + return sel; + } + + /** + * Get the selection object + * + * @return The selection + */ + public ISelection getSelectionTrace() { + TimeGraphSelection sel = new TimeGraphSelection(); + ITimeGraphEntry trace = getSelectedTrace(); + if (null != trace) { + sel.add(trace); + } + return sel; + } + + /** + * Enable/disable one of the traces in the model + * + * @param n + * 1 to enable it, -1 to disable. The method returns immediately + * if another value is used. + */ + public void selectTrace(int n) { + if ((n != 1) && (n != -1)) { + return; + } + + boolean changed = false; + int lastSelection = -1; + for (int i = 0; i < fItemData.fExpandedItems.length; i++) { + Item item = fItemData.fExpandedItems[i]; + if (item.fSelected) { + lastSelection = i; + if ((1 == n) && (i < fItemData.fExpandedItems.length - 1)) { + item.fSelected = false; + item = fItemData.fExpandedItems[i + 1]; + item.fSelected = true; + changed = true; + } else if ((-1 == n) && (i > 0)) { + item.fSelected = false; + item = fItemData.fExpandedItems[i - 1]; + item.fSelected = true; + changed = true; + } + break; + } + } + + if (lastSelection < 0 && fItemData.fExpandedItems.length > 0) { + Item item = fItemData.fExpandedItems[0]; + item.fSelected = true; + changed = true; + } + + if (changed) { + ensureVisibleItem(-1, false); + redraw(); + fireSelectionChanged(); + } + } + + /** + * Select an event + * + * @param n + * 1 for next event, -1 for previous event + */ + public void selectEvent(int n) { + if (null == fTimeProvider) { + return; + } + ITimeGraphEntry trace = getSelectedTrace(); + if (trace == null) { + return; + } + long selectedTime = fTimeProvider.getSelectionBegin(); + long endTime = fTimeProvider.getEndTime(); + ITimeEvent nextEvent; + if (-1 == n && selectedTime > endTime) { + nextEvent = Utils.findEvent(trace, selectedTime, 0); + } else { + nextEvent = Utils.findEvent(trace, selectedTime, n); + } + if (null == nextEvent && -1 == n) { + nextEvent = Utils.getFirstEvent(trace); + } + if (null != nextEvent) { + long nextTime = nextEvent.getTime(); + // If last event detected e.g. going back or not moving to a next + // event + if (nextTime <= selectedTime && n == 1) { + // Select to the end of this last event + nextTime = nextEvent.getTime() + nextEvent.getDuration(); + // but not beyond the end of the trace + if (nextTime > endTime) { + nextTime = endTime; + } + } else if (n == -1 && nextEvent.getTime() + nextEvent.getDuration() < selectedTime) { + // for previous event go to its end time unless we were already there + nextTime = nextEvent.getTime() + nextEvent.getDuration(); + } + fTimeProvider.setSelectedTimeNotify(nextTime, true); + fireSelectionChanged(); + } else if (1 == n) { + fTimeProvider.setSelectedTimeNotify(endTime, true); + fireSelectionChanged(); + } + } + + /** + * Select the next event + */ + public void selectNextEvent() { + selectEvent(1); + // Notify if visible time window has been adjusted + fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1()); + } + + /** + * Select the previous event + */ + public void selectPrevEvent() { + selectEvent(-1); + // Notify if visible time window has been adjusted + fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1()); + } + + /** + * Select the next trace + */ + public void selectNextTrace() { + selectTrace(1); + } + + /** + * Select the previous trace + */ + public void selectPrevTrace() { + selectTrace(-1); + } + + /** + * Zoom based on mouse cursor location with mouse scrolling + * + * @param zoomIn true to zoom in, false to zoom out + */ + public void zoom(boolean zoomIn) { + int globalX = getDisplay().getCursorLocation().x; + Point p = toControl(globalX, 0); + int nameSpace = fTimeProvider.getNameSpace(); + int timeSpace = fTimeProvider.getTimeSpace(); + int xPos = Math.max(nameSpace, Math.min(nameSpace + timeSpace, p.x)); + long time0 = fTimeProvider.getTime0(); + long time1 = fTimeProvider.getTime1(); + long interval = time1 - time0; + if (interval == 0) { + interval = 1; + } // to allow getting out of single point interval + long newInterval; + if (zoomIn) { + newInterval = Math.max(Math.round(interval * ZOOM_IN_FACTOR), fTimeProvider.getMinTimeInterval()); + } else { + newInterval = (long) Math.ceil(interval * ZOOM_OUT_FACTOR); + } + long center = time0 + Math.round(((double) (xPos - nameSpace) / timeSpace * interval)); + long newTime0 = center - Math.round((double) newInterval * (center - time0) / interval); + long newTime1 = newTime0 + newInterval; + fTimeProvider.setStartFinishTime(newTime0, newTime1); + synchronized (fMouseScrollNotifierLock) { + if (fMouseScrollNotifier == null) { + fMouseScrollNotifier = new MouseScrollNotifier(); + fMouseScrollNotifier.start(); + } + fMouseScrollNotifier.mouseScrolled(); + } + } + + /** + * zoom in using single click + */ + public void zoomIn() { + long prevTime0 = fTimeProvider.getTime0(); + long prevTime1 = fTimeProvider.getTime1(); + long prevRange = prevTime1 - prevTime0; + if (prevRange == 0) { + return; + } + ITimeDataProvider provider = fTimeProvider; + long selTime = (provider.getSelectionEnd() + provider.getSelectionBegin()) / 2; + if (selTime <= prevTime0 || selTime >= prevTime1) { + selTime = (prevTime0 + prevTime1) / 2; + } + long time0 = selTime - (long) ((selTime - prevTime0) / ZOOM_FACTOR); + long time1 = selTime + (long) ((prevTime1 - selTime) / ZOOM_FACTOR); + + long inaccuracy = (fTimeProvider.getMaxTime() - fTimeProvider.getMinTime()) - (time1 - time0); + + if (inaccuracy > 0 && inaccuracy < 100) { + fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getMinTime(), fTimeProvider.getMaxTime()); + return; + } + + long min = fTimeProvider.getMinTimeInterval(); + if ((time1 - time0) < min) { + time0 = selTime - (selTime - prevTime0) * min / prevRange; + time1 = time0 + min; + } + + fTimeProvider.setStartFinishTimeNotify(time0, time1); + } + + /** + * zoom out using single click + */ + public void zoomOut() { + long prevTime0 = fTimeProvider.getTime0(); + long prevTime1 = fTimeProvider.getTime1(); + ITimeDataProvider provider = fTimeProvider; + long selTime = (provider.getSelectionEnd() + provider.getSelectionBegin()) / 2; + if (selTime <= prevTime0 || selTime >= prevTime1) { + selTime = (prevTime0 + prevTime1) / 2; + } + long time0 = (long) (selTime - (selTime - prevTime0) * ZOOM_FACTOR); + long time1 = (long) (selTime + (prevTime1 - selTime) * ZOOM_FACTOR); + + long inaccuracy = (fTimeProvider.getMaxTime() - fTimeProvider.getMinTime()) - (time1 - time0); + if (inaccuracy > 0 && inaccuracy < 100) { + fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getMinTime(), fTimeProvider.getMaxTime()); + return; + } + + fTimeProvider.setStartFinishTimeNotify(time0, time1); + } + + /** + * Hide arrows + * + * @param hideArrows true to hide arrows + * + * @since 2.1 + */ + public void hideArrows(boolean hideArrows) { + fHideArrows = hideArrows; + } + + /** + * Follow the arrow forward + * + * @since 2.1 + */ + public void followArrowFwd() { + ITimeGraphEntry trace = getSelectedTrace(); + if (trace == null) { + return; + } + long selectedTime = fTimeProvider.getSelectionBegin(); + for (ILinkEvent link : fItemData.fLinks) { + if (link.getEntry() == trace && link.getTime() == selectedTime) { + selectItem(link.getDestinationEntry(), false); + if (link.getDuration() != 0) { + fTimeProvider.setSelectedTimeNotify(link.getTime() + link.getDuration(), true); + // Notify if visible time window has been adjusted + fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1()); + } + fireSelectionChanged(); + return; + } + } + selectNextEvent(); + } + + /** + * Follow the arrow backward + * + * @since 2.1 + */ + public void followArrowBwd() { + ITimeGraphEntry trace = getSelectedTrace(); + if (trace == null) { + return; + } + long selectedTime = fTimeProvider.getSelectionBegin(); + for (ILinkEvent link : fItemData.fLinks) { + if (link.getDestinationEntry() == trace && link.getTime() + link.getDuration() == selectedTime) { + selectItem(link.getEntry(), false); + if (link.getDuration() != 0) { + fTimeProvider.setSelectedTimeNotify(link.getTime(), true); + // Notify if visible time window has been adjusted + fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1()); + } + fireSelectionChanged(); + return; + } + } + selectPrevEvent(); + } + + /** + * Return the currently selected trace + * + * @return The entry matching the trace + */ + public ITimeGraphEntry getSelectedTrace() { + ITimeGraphEntry trace = null; + int idx = getSelectedIndex(); + if (idx >= 0) { + trace = fItemData.fExpandedItems[idx].fEntry; + } + return trace; + } + + /** + * Retrieve the index of the currently selected item + * + * @return The index + */ + public int getSelectedIndex() { + int idx = -1; + for (int i = 0; i < fItemData.fExpandedItems.length; i++) { + Item item = fItemData.fExpandedItems[i]; + if (item.fSelected) { + idx = i; + break; + } + } + return idx; + } + + boolean toggle(int idx) { + boolean toggled = false; + if (idx >= 0 && idx < fItemData.fExpandedItems.length) { + Item item = fItemData.fExpandedItems[idx]; + if (item.fHasChildren) { + item.fExpanded = !item.fExpanded; + fItemData.updateExpandedItems(); + adjustScrolls(); + redraw(); + toggled = true; + fireTreeEvent(item.fEntry, item.fExpanded); + } + } + return toggled; + } + + /** + * Gets the index of the item at the given location. + * + * @param y + * the y coordinate + * @return the index of the item at the given location, of -1 if none. + * @since 3.0 + */ + 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; + } + } + return -1; + } + + boolean isOverSplitLine(int x) { + if (x < 0 || null == fTimeProvider) { + return false; + } + int nameWidth = fTimeProvider.getNameSpace(); + return Math.abs(x - nameWidth) < SNAP_WIDTH; + } + + /** + * Gets the {@link ITimeGraphEntry} at the given location. + * + * @param pt + * a point in the widget + * @return the {@link ITimeGraphEntry} at this point, or null + * if none. + * @since 3.0 + */ + protected ITimeGraphEntry getEntry(Point pt) { + int idx = getItemIndexAtY(pt.y); + return idx >= 0 ? fItemData.fExpandedItems[idx].fEntry : null; + } + + /** + * Return the arrow event closest to the given point that is no further than + * a maximum distance. + * + * @param pt + * a point in the widget + * @return The closest arrow event, or null if there is none close enough. + * @since 3.1 + */ + protected ILinkEvent getArrow(Point pt) { + if (fHideArrows) { + return null; + } + ILinkEvent linkEvent = null; + double minDistance = Double.MAX_VALUE; + for (ILinkEvent event : fItemData.fLinks) { + Rectangle rect = getArrowRectangle(new Rectangle(0, 0, 0, 0), event); + if (rect != null) { + int x1 = rect.x; + int y1 = rect.y; + int x2 = x1 + rect.width; + int y2 = y1 + rect.height; + double d = Utils.distance(pt.x, pt.y, x1, y1, x2, y2); + if (minDistance > d) { + minDistance = d; + linkEvent = event; + } + } + } + if (minDistance <= ARROW_HOVER_MAX_DIST) { + return linkEvent; + } + return null; + } + + /** + * @since 2.0 + */ + @Override + public int getXForTime(long time) { + if (null == fTimeProvider) { + return -1; + } + long time0 = fTimeProvider.getTime0(); + long time1 = fTimeProvider.getTime1(); + int width = getCtrlSize().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); + return x; + } + + /** + * @since 2.0 + */ + @Override + public long getTimeAtX(int coord) { + if (null == fTimeProvider) { + return -1; + } + long hitTime = -1; + Point size = getCtrlSize(); + long time0 = fTimeProvider.getTime0(); + long time1 = fTimeProvider.getTime1(); + int nameWidth = fTimeProvider.getNameSpace(); + final int x = coord - nameWidth; + int timeWidth = size.x - nameWidth - RIGHT_MARGIN; + if (x >= 0 && size.x >= nameWidth) { + if (time1 - time0 > timeWidth) { + // nanosecond smaller than one pixel: use the first integer nanosecond of this pixel's time range + hitTime = time0 + (long) Math.ceil((time1 - time0) * ((double) x / timeWidth)); + } else { + // nanosecond greater than one pixel: use the nanosecond that covers this pixel start position + hitTime = time0 + (long) Math.floor((time1 - time0) * ((double) x / timeWidth)); + } + } + return hitTime; + } + + void selectItem(int idx, boolean addSelection) { + boolean changed = false; + if (addSelection) { + if (idx >= 0 && idx < fItemData.fExpandedItems.length) { + Item item = fItemData.fExpandedItems[idx]; + changed = !item.fSelected; + item.fSelected = true; + } + } else { + for (int i = 0; i < fItemData.fExpandedItems.length; i++) { + Item item = fItemData.fExpandedItems[i]; + if ((i == idx && !item.fSelected) || (idx == -1 && item.fSelected)) { + changed = true; + } + item.fSelected = i == idx; + } + } + changed |= ensureVisibleItem(idx, true); + if (changed) { + redraw(); + } + } + + /** + * Callback for item selection + * + * @param trace + * The entry matching the trace + * @param addSelection + * If the selection is added or removed + */ + public void selectItem(ITimeGraphEntry trace, boolean addSelection) { + int idx = fItemData.findItemIndex(trace); + selectItem(idx, addSelection); + } + + /** + * Retrieve the number of entries shown per page. + * + * @return The count + */ + public int countPerPage() { + int height = getCtrlSize().y; + int count = 0; + int ySum = 0; + for (int idx = fTopIndex; idx < fItemData.fExpandedItems.length; idx++) { + ySum += fItemData.fExpandedItems[idx].fItemHeight; + if (ySum >= height) { + return count; + } + count++; + } + for (int idx = fTopIndex - 1; idx >= 0; idx--) { + ySum += fItemData.fExpandedItems[idx].fItemHeight; + if (ySum >= height) { + return count; + } + count++; + } + return count; + } + + /** + * Get the index of the top element + * + * @return The index + */ + public int getTopIndex() { + return fTopIndex; + } + + /** + * Get the number of expanded items + * + * @return The count of expanded items + */ + public int getExpandedElementCount() { + return fItemData.fExpandedItems.length; + } + + /** + * Get an array of all expanded elements + * + * @return The expanded elements + */ + public ITimeGraphEntry[] getExpandedElements() { + ArrayList elements = new ArrayList<>(); + for (Item item : fItemData.fExpandedItems) { + elements.add(item.fEntry); + } + return elements.toArray(new ITimeGraphEntry[0]); + } + + Point getCtrlSize() { + Point size = getSize(); + if (getHorizontalBar().isVisible()) { + size.y -= getHorizontalBar().getSize().y; + } + return size; + } + + Rectangle getNameRect(Rectangle bound, int idx, int nameWidth) { + Rectangle rect = getStatesRect(bound, idx, nameWidth); + rect.x = bound.x; + rect.width = nameWidth; + return rect; + } + + Rectangle getStatesRect(Rectangle bound, int idx, int nameWidth) { + int x = bound.x + nameWidth; + int width = bound.width - x; + int ySum = 0; + if (idx >= fTopIndex) { + for (int i = fTopIndex; i < idx; i++) { + ySum += fItemData.fExpandedItems[i].fItemHeight; + } + } else { + for (int i = fTopIndex - 1; i >= idx; i--) { + ySum -= fItemData.fExpandedItems[i].fItemHeight; + } + } + int y = bound.y + ySum; + int height = fItemData.fExpandedItems[idx].fItemHeight; + return new Rectangle(x, y, width, height); + } + + @Override + void paint(Rectangle bounds, PaintEvent e) { + GC gc = e.gc; + gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.BACKGROUND)); + drawBackground(gc, bounds.x, bounds.y, bounds.width, bounds.height); + + if (bounds.width < 2 || bounds.height < 2 || null == fTimeProvider) { + return; + } + + fIdealNameSpace = 0; + int nameSpace = fTimeProvider.getNameSpace(); + + // draw empty name space background + gc.setBackground(getColorScheme().getBkColor(false, false, true)); + drawBackground(gc, bounds.x, bounds.y, nameSpace, bounds.height); + + // draw items + drawItems(bounds, fTimeProvider, fItemData.fExpandedItems, fTopIndex, nameSpace, gc); + drawLinks(bounds, fTimeProvider, fItemData.fLinks, nameSpace, gc); + fTimeGraphProvider.postDrawControl(bounds, gc); + + int alpha = gc.getAlpha(); + gc.setAlpha(100); + + long time0 = fTimeProvider.getTime0(); + long time1 = fTimeProvider.getTime1(); + long selectionBegin = fTimeProvider.getSelectionBegin(); + long selectionEnd = fTimeProvider.getSelectionEnd(); + double pixelsPerNanoSec = (bounds.width - nameSpace <= RIGHT_MARGIN) ? 0 : (double) (bounds.width - nameSpace - RIGHT_MARGIN) / (time1 - time0); + int x0 = bounds.x + nameSpace + (int) ((selectionBegin - time0) * pixelsPerNanoSec); + int x1 = bounds.x + nameSpace + (int) ((selectionEnd - time0) * pixelsPerNanoSec); + + // draw selection lines + if (fDragState != DRAG_SELECTION) { + gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.SELECTED_TIME)); + if (x0 >= nameSpace && x0 < bounds.x + bounds.width) { + gc.drawLine(x0, bounds.y, x0, bounds.y + bounds.height); + } + if (x1 != x0) { + if (x1 >= nameSpace && x1 < bounds.x + bounds.width) { + gc.drawLine(x1, bounds.y, x1, bounds.y + bounds.height); + } + } + } + + // draw selection background + if (selectionBegin != 0 && selectionEnd != 0 && fDragState != DRAG_SELECTION) { + x0 = Math.max(nameSpace, Math.min(bounds.x + bounds.width, x0)); + x1 = Math.max(nameSpace, Math.min(bounds.x + bounds.width, x1)); + gc.setBackground(getColorScheme().getBkColor(false, false, true)); + if (x1 - x0 > 1) { + gc.fillRectangle(new Rectangle(x0 + 1, bounds.y, x1 - x0 - 1, bounds.height)); + } else if (x0 - x1 > 1) { + gc.fillRectangle(new Rectangle(x1 + 1, bounds.y, x0 - x1 - 1, bounds.height)); + } + } + + // draw drag selection background + if (fDragState == DRAG_ZOOM || fDragState == DRAG_SELECTION) { + gc.setBackground(getColorScheme().getBkColor(false, false, true)); + if (fDragX0 < fDragX) { + gc.fillRectangle(new Rectangle(fDragX0, bounds.y, fDragX - fDragX0, bounds.height)); + } else if (fDragX0 > fDragX) { + gc.fillRectangle(new Rectangle(fDragX, bounds.y, fDragX0 - fDragX, bounds.height)); + } + } + + // draw 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) { + gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.TOOL_FOREGROUND)); + gc.drawLine(fDragX0, bounds.y, fDragX0, bounds.y + bounds.height - 1); + if (fDragX != fDragX0) { + gc.drawLine(fDragX, bounds.y, fDragX, bounds.y + bounds.height - 1); + } + } else if (DRAG_SELECTION == fDragState && Math.max(fDragX, fDragX0) > nameSpace) { + gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.SELECTED_TIME)); + gc.drawLine(fDragX0, bounds.y, fDragX0, bounds.y + bounds.height - 1); + if (fDragX != fDragX0) { + gc.drawLine(fDragX, bounds.y, fDragX, bounds.y + bounds.height - 1); + } + } else if (DRAG_NONE == fDragState && fMouseOverSplitLine && fTimeProvider.getNameSpace() > 0) { + gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.RED)); + gc.drawLine(bounds.x + nameSpace, bounds.y, bounds.x + nameSpace, bounds.y + bounds.height - 1); + } + + gc.setAlpha(alpha); + } + + /** + * Draw many items at once + * + * @param bounds + * The rectangle of the area + * @param timeProvider + * The time provider + * @param items + * The array items to draw + * @param topIndex + * The index of the first element to draw + * @param nameSpace + * The width reserved for the names + * @param gc + * Reference to the SWT GC object + */ + public void drawItems(Rectangle bounds, ITimeDataProvider timeProvider, + Item[] items, int topIndex, int nameSpace, GC gc) { + for (int i = topIndex; i < items.length; i++) { + Item item = items[i]; + drawItem(item, bounds, timeProvider, i, nameSpace, gc); + } + } + + /** + * Draws the item + * + * @param item the item to draw + * @param bounds the container rectangle + * @param timeProvider Time provider + * @param i the item index + * @param nameSpace the name space + * @param gc Graphics context + */ + protected void drawItem(Item item, Rectangle bounds, ITimeDataProvider timeProvider, int i, int nameSpace, GC gc) { + ITimeGraphEntry entry = item.fEntry; + long time0 = timeProvider.getTime0(); + long time1 = timeProvider.getTime1(); + long selectedTime = fTimeProvider.getSelectionBegin(); + + Rectangle nameRect = getNameRect(bounds, i, nameSpace); + if (nameRect.y >= bounds.y + bounds.height) { + return; + } + + if (! item.fEntry.hasTimeEvents()) { + Rectangle statesRect = getStatesRect(bounds, i, nameSpace); + nameRect.width += statesRect.width; + drawName(item, nameRect, gc); + } else { + drawName(item, nameRect, gc); + } + Rectangle rect = getStatesRect(bounds, i, nameSpace); + if (rect.isEmpty()) { + fTimeGraphProvider.postDrawEntry(entry, rect, gc); + return; + } + if (time1 <= time0) { + gc.setBackground(getColorScheme().getBkColor(false, false, false)); + gc.fillRectangle(rect); + fTimeGraphProvider.postDrawEntry(entry, rect, gc); + return; + } + + // Initialize _rect1 to same values as enclosing rectangle rect + Rectangle stateRect = Utils.clone(rect); + boolean selected = item.fSelected; + // K pixels per second + double pixelsPerNanoSec = (rect.width <= RIGHT_MARGIN) ? 0 : (double) (rect.width - RIGHT_MARGIN) / (time1 - time0); + + if (item.fEntry.hasTimeEvents()) { + gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height)); + fillSpace(rect, gc, selected); + // Drawing rectangle is smaller than reserved space + stateRect.y += 3; + stateRect.height -= 6; + + long maxDuration = (timeProvider.getTimeSpace() == 0) ? Long.MAX_VALUE : 1 * (time1 - time0) / timeProvider.getTimeSpace(); + Iterator iterator = entry.getTimeEventsIterator(time0, time1, maxDuration); + + int lastX = -1; + while (iterator.hasNext()) { + ITimeEvent event = iterator.next(); + int x = rect.x + (int) ((event.getTime() - time0) * pixelsPerNanoSec); + int xEnd = rect.x + (int) ((event.getTime() + event.getDuration() - time0) * pixelsPerNanoSec); + if (x >= rect.x + rect.width || xEnd < rect.x) { + // event is out of bounds + continue; + } + xEnd = Math.min(rect.x + rect.width, xEnd); + stateRect.x = Math.max(rect.x, x); + stateRect.width = Math.max(0, xEnd - stateRect.x + 1); + if (stateRect.x == lastX) { + stateRect.width -= 1; + if (stateRect.width > 0) { + gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK)); + gc.drawPoint(stateRect.x, stateRect.y - 2); + stateRect.x += 1; + } + } + boolean timeSelected = selectedTime >= event.getTime() && selectedTime < event.getTime() + event.getDuration(); + if (drawState(getColorScheme(), event, stateRect, gc, selected, timeSelected)) { + lastX = x; + } + } + gc.setClipping((Rectangle) null); + } + fTimeGraphProvider.postDrawEntry(entry, rect, gc); + } + + /** + * Draw the links + * + * @param bounds + * The rectangle of the area + * @param timeProvider + * The time provider + * @param links + * The array items to draw + * @param nameSpace + * The width reserved for the names + * @param gc + * Reference to the SWT GC object + * @since 2.1 + */ + public void drawLinks(Rectangle bounds, ITimeDataProvider timeProvider, + List links, int nameSpace, GC gc) { + if (fHideArrows) { + return; + } + gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height)); + for (ILinkEvent event : links) { + drawLink(event, bounds, timeProvider, nameSpace, gc); + } + gc.setClipping((Rectangle) null); + } + + /** + * Draws the link type events of this item + * + * @param event + * the item to draw + * @param bounds + * the container rectangle + * @param timeProvider + * Time provider + * @param nameSpace + * the name space + * @param gc + * Graphics context + * @since 2.1 + */ + protected void drawLink(ILinkEvent event, Rectangle bounds, ITimeDataProvider timeProvider, int nameSpace, GC gc) { + drawArrow(getColorScheme(), event, getArrowRectangle(bounds, event), gc); + } + + private Rectangle getArrowRectangle(Rectangle bounds, ILinkEvent event) { + int srcIndex = fItemData.findItemIndex(event.getEntry()); + int destIndex = fItemData.findItemIndex(event.getDestinationEntry()); + + if ((srcIndex == -1) || (destIndex == -1)) { + return null; + } + + Rectangle src = getStatesRect(bounds, srcIndex, fTimeProvider.getNameSpace()); + Rectangle dst = getStatesRect(bounds, destIndex, fTimeProvider.getNameSpace()); + + int x0 = getXForTime(event.getTime()); + int x1 = getXForTime(event.getTime() + event.getDuration()); + + // limit the x-coordinates to prevent integer overflow in calculations + // and also GC.drawLine doesn't draw properly with large coordinates + final int limit = Integer.MAX_VALUE / 1024; + x0 = Math.max(-limit, Math.min(x0, limit)); + x1 = Math.max(-limit, Math.min(x1, limit)); + + int y0 = src.y + src.height / 2; + int y1 = dst.y + dst.height / 2; + return new Rectangle(x0, y0, x1 - x0, y1 - y0); + } + + /** + * Draw the state (color fill) + * + * @param colors + * Color scheme + * @param event + * Time event for which we're drawing the state + * @param rect + * Where to draw + * @param gc + * Graphics context + * @return true if the state was drawn + * @since 2.1 + */ + protected boolean drawArrow(TimeGraphColorScheme colors, ITimeEvent event, + Rectangle rect, GC gc) { + + if (rect == null) { + return false; + } + int colorIdx = fTimeGraphProvider.getStateTableIndex(event); + if (colorIdx < 0) { + return false; + } + boolean visible = ((rect.height == 0) && (rect.width == 0)) ? false : true; + + if (visible) { + Color stateColor = null; + if (colorIdx < fEventColorMap.length) { + stateColor = fEventColorMap[colorIdx]; + } else { + stateColor = Display.getDefault().getSystemColor(SWT.COLOR_BLACK); + } + + gc.setForeground(stateColor); + gc.setBackground(stateColor); + + /* Draw the arrow */ + gc.drawLine(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height); + drawArrowHead(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, gc); + + } + fTimeGraphProvider.postDrawEvent(event, rect, gc); + return visible; + } + + /* + * @author Francis Giraldeau + * + * Inspiration: + * http://stackoverflow.com/questions/3010803/draw-arrow-on-line-algorithm + * + * The algorithm was taken from this site, not the code itself + */ + private static void drawArrowHead(int x0, int y0, int x1, int y1, GC gc) + { + int factor = 10; + double cos = 0.9510; + double sin = 0.3090; + long lenx = x1 - x0; + long leny = y1 - y0; + double len = Math.sqrt(lenx * lenx + leny * leny); + + double dx = factor * lenx / len; + double dy = factor * leny / len; + int end1X = (int) Math.round((x1 - (dx * cos + dy * -sin))); + int end1Y = (int) Math.round((y1 - (dx * sin + dy * cos))); + int end2X = (int) Math.round((x1 - (dx * cos + dy * sin))); + int end2Y = (int) Math.round((y1 - (dx * -sin + dy * cos))); + int[] arrow = new int[] { x1, y1, end1X, end1Y, end2X, end2Y, x1, y1 }; + gc.fillPolygon(arrow); + } + + /** + * Draw the name of an item. + * + * @param item + * Item object + * @param bounds + * Where to draw the name + * @param gc + * Graphics context + */ + protected void drawName(Item item, Rectangle bounds, GC gc) { + boolean hasTimeEvents = item.fEntry.hasTimeEvents(); + if (! hasTimeEvents) { + gc.setBackground(getColorScheme().getBkColorGroup(item.fSelected, fIsInFocus)); + gc.fillRectangle(bounds); + if (item.fSelected && fIsInFocus) { + gc.setForeground(getColorScheme().getBkColor(item.fSelected, fIsInFocus, false)); + gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1); + } + } else { + gc.setBackground(getColorScheme().getBkColor(item.fSelected, fIsInFocus, true)); + gc.setForeground(getColorScheme().getFgColor(item.fSelected, fIsInFocus)); + gc.fillRectangle(bounds); + } + + // No name to be drawn + 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; + } + 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$ + } + } + 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; + gc.setForeground(getColorScheme().getFgColor(item.fSelected, fIsInFocus)); + int textWidth = Utils.drawText(gc, name, rect, true); + leftMargin += textWidth + MARGIN; + rect.y -= 2; + + if (hasTimeEvents) { + // draw middle line + int x = bounds.x + leftMargin; + int width = bounds.width - x; + int midy = bounds.y + bounds.height / 2; + gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.MID_LINE)); + gc.drawLine(x, midy, x + width, midy); + } + } + } + + /** + * Draw the state (color fill) + * + * @param colors + * Color scheme + * @param event + * Time event for which we're drawing the state + * @param rect + * Where to draw + * @param gc + * Graphics context + * @param selected + * Is this time event currently selected (so it appears + * highlighted) + * @param timeSelected + * Is the timestamp currently selected + * @return true if the state was drawn + * @since 2.0 + */ + protected boolean drawState(TimeGraphColorScheme colors, ITimeEvent event, + Rectangle rect, GC gc, boolean selected, boolean timeSelected) { + + int colorIdx = fTimeGraphProvider.getStateTableIndex(event); + if (colorIdx < 0 && colorIdx != ITimeGraphPresentationProvider.TRANSPARENT) { + return false; + } + boolean visible = rect.width == 0 ? false : true; + Color black = Display.getDefault().getSystemColor(SWT.COLOR_BLACK); + gc.setForeground(black); + + if (visible) { + if (colorIdx == ITimeGraphPresentationProvider.TRANSPARENT) { + // Only draw the top and bottom borders + gc.drawLine(rect.x, rect.y, rect.x + rect.width - 1, rect.y); + gc.drawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width - 1, rect.y + rect.height - 1); + if (rect.width == 1) { + gc.drawPoint(rect.x, rect.y - 2); + } + return false; + } + Color stateColor = null; + if (colorIdx < fEventColorMap.length) { + stateColor = fEventColorMap[colorIdx]; + } else { + stateColor = black; + } + + boolean reallySelected = timeSelected && selected; + // fill all rect area + gc.setBackground(stateColor); + gc.fillRectangle(rect); + + if (reallySelected) { + gc.drawLine(rect.x, rect.y - 1, rect.x + rect.width - 1, rect.y - 1); + gc.drawLine(rect.x, rect.y + rect.height, rect.x + rect.width - 1, rect.y + rect.height); + } + } else { + gc.drawPoint(rect.x, rect.y - 2); + } + fTimeGraphProvider.postDrawEvent(event, rect, gc); + return visible; + } + + /** + * Fill the space between two contiguous time events + * + * @param rect + * Rectangle to fill + * @param gc + * Graphics context + * @param selected + * Is this time event selected or not + */ + protected void fillSpace(Rectangle rect, GC gc, boolean selected) { + gc.setBackground(getColorScheme().getBkColor(selected, fIsInFocus, false)); + gc.fillRectangle(rect); + if (fDragState == DRAG_ZOOM) { + gc.setBackground(getColorScheme().getBkColor(selected, fIsInFocus, true)); + if (fDragX0 < fDragX) { + gc.fillRectangle(new Rectangle(fDragX0, rect.y, fDragX - fDragX0, rect.height)); + } else if (fDragX0 > fDragX) { + gc.fillRectangle(new Rectangle(fDragX, rect.y, fDragX0 - fDragX, rect.height)); + } + } + // draw middle line + gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.MID_LINE)); + int midy = rect.y + rect.height / 2; + gc.drawLine(rect.x, midy, rect.x + rect.width, midy); + } + + @Override + public void keyTraversed(TraverseEvent e) { + if ((e.detail == SWT.TRAVERSE_TAB_NEXT) || (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)) { + e.doit = true; + } + } + + @Override + public void keyPressed(KeyEvent e) { + int idx = -1; + if (fItemData.fExpandedItems.length == 0) { + return; + } + if (SWT.HOME == e.keyCode) { + idx = 0; + } else if (SWT.END == e.keyCode) { + idx = fItemData.fExpandedItems.length - 1; + } else if (SWT.ARROW_DOWN == e.keyCode) { + idx = getSelectedIndex(); + if (idx < 0) { + idx = 0; + } else if (idx < fItemData.fExpandedItems.length - 1) { + idx++; + } + } else if (SWT.ARROW_UP == e.keyCode) { + idx = getSelectedIndex(); + if (idx < 0) { + idx = 0; + } else if (idx > 0) { + idx--; + } + } else if (SWT.ARROW_LEFT == e.keyCode) { + selectPrevEvent(); + } else if (SWT.ARROW_RIGHT == e.keyCode) { + selectNextEvent(); + } else if (SWT.PAGE_DOWN == e.keyCode) { + int page = countPerPage(); + idx = getSelectedIndex(); + if (idx < 0) { + idx = 0; + } + idx += page; + if (idx >= fItemData.fExpandedItems.length) { + idx = fItemData.fExpandedItems.length - 1; + } + } else if (SWT.PAGE_UP == e.keyCode) { + int page = countPerPage(); + idx = getSelectedIndex(); + if (idx < 0) { + idx = 0; + } + idx -= page; + if (idx < 0) { + idx = 0; + } + } else if (SWT.CR == e.keyCode) { + idx = getSelectedIndex(); + if (idx >= 0) { + if (fItemData.fExpandedItems[idx].fHasChildren) { + toggle(idx); + } else { + fireDefaultSelection(); + } + } + idx = -1; + } + if (idx >= 0) { + selectItem(idx, false); + fireSelectionChanged(); + } + int x = toControl(e.display.getCursorLocation()).x; + updateCursor(x, e.stateMask | e.keyCode); + } + + @Override + public void keyReleased(KeyEvent e) { + int x = toControl(e.display.getCursorLocation()).x; + updateCursor(x, e.stateMask & ~e.keyCode); + } + + @Override + public void focusGained(FocusEvent e) { + fIsInFocus = true; + if (fMouseScrollFilterListener == null) { + fMouseScrollFilterListener = new Listener() { + // This filter is used to prevent horizontal scrolling of the view + // when the mouse wheel is used to zoom + @Override + public void handleEvent(Event event) { + event.doit = false; + } + }; + getDisplay().addFilter(SWT.MouseWheel, fMouseScrollFilterListener); + } + redraw(); + updateStatusLine(NO_STATUS); + } + + @Override + public void focusLost(FocusEvent e) { + fIsInFocus = false; + if (fMouseScrollFilterListener != null) { + getDisplay().removeFilter(SWT.MouseWheel, fMouseScrollFilterListener); + fMouseScrollFilterListener = null; + } + if (DRAG_NONE != fDragState) { + setCapture(false); + fDragState = DRAG_NONE; + } + redraw(); + updateStatusLine(NO_STATUS); + } + + /** + * @return If the current view is focused + */ + public boolean isInFocus() { + return fIsInFocus; + } + + /** + * Provide the possibility to control the wait cursor externally e.g. data + * requests in progress + * + * @param waitInd Should we wait indefinitely? + */ + public void waitCursor(boolean waitInd) { + // Update cursor as indicated + if (waitInd) { + setCursor(fWaitCursor); + } else { + setCursor(null); + } + } + + private void updateCursor(int x, int stateMask) { + // if Wait cursor not active, check for the need to change the cursor + if (getCursor() == fWaitCursor) { + return; + } + Cursor cursor = null; + if (fDragState == DRAG_SPLIT_LINE) { + } else if (fDragState == DRAG_SELECTION) { + cursor = fResizeCursor; + } else if (fDragState == DRAG_TRACE_ITEM) { + cursor = fDragCursor; + } else if (fDragState == DRAG_ZOOM) { + cursor = fZoomCursor; + } else if ((stateMask & SWT.MODIFIER_MASK) == SWT.CTRL) { + cursor = fDragCursor; + } else if ((stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) { + cursor = fResizeCursor; + } else if (!isOverSplitLine(x)) { + long selectionBegin = fTimeProvider.getSelectionBegin(); + long selectionEnd = fTimeProvider.getSelectionEnd(); + int xBegin = getXForTime(selectionBegin); + int xEnd = getXForTime(selectionEnd); + if (Math.abs(x - xBegin) < SNAP_WIDTH || Math.abs(x - xEnd) < SNAP_WIDTH) { + cursor = fResizeCursor; + } + } + if (getCursor() != cursor) { + setCursor(cursor); + } + } + + private void updateStatusLine(int x) { + // use the time provider of the time graph scale for the status line + ITimeDataProvider tdp = fTimeGraphScale.getTimeProvider(); + if (fStatusLineManager == null || null == tdp || + tdp.getTime0() == tdp.getTime1()) { + return; + } + TimeFormat tf = tdp.getTimeFormat(); + Resolution res = Resolution.NANOSEC; + StringBuilder message = new StringBuilder(); + if (x >= 0 && fDragState == DRAG_NONE) { + long time = getTimeAtX(x); + if (time >= 0) { + if (tdp instanceof ITimeDataProviderConverter) { + time = ((ITimeDataProviderConverter) tdp).convertTime(time); + } + long selectionBegin = tdp.getSelectionBegin(); + long selectionEnd = tdp.getSelectionEnd(); + message.append(NLS.bind("T: {0}{1} T1: {2}{3}", //$NON-NLS-1$ + new Object[] { + tf == TimeFormat.CALENDAR ? Utils.formatDate(time) + ' ' : "", //$NON-NLS-1$ + Utils.formatTime(time, tf, res), + tf == TimeFormat.CALENDAR ? Utils.formatDate(Math.min(selectionBegin, selectionEnd)) + ' ' : "", //$NON-NLS-1$ + Utils.formatTime(Math.min(selectionBegin, selectionEnd), tf, res) + })); + if (selectionBegin != selectionEnd) { + message.append(NLS.bind(" T2: {0}{1} \u0394: {2}", //$NON-NLS-1$ + new Object[] { + tf == TimeFormat.CALENDAR ? Utils.formatDate(Math.max(selectionBegin, selectionEnd)) + ' ' : "", //$NON-NLS-1$ + Utils.formatTime(Math.max(selectionBegin, selectionEnd), tf, res), + Utils.formatDelta(Math.abs(selectionBegin - selectionEnd), tf, res) + })); + } + } + } else if (fDragState == DRAG_SELECTION || fDragState == DRAG_ZOOM) { + long time0 = fDragTime0; + long time = getTimeAtX(fDragX); + if (tdp instanceof ITimeDataProviderConverter) { + time0 = ((ITimeDataProviderConverter) tdp).convertTime(time0); + time = ((ITimeDataProviderConverter) tdp).convertTime(time); + } + message.append(NLS.bind("T1: {0}{1} T2: {2}{3} \u0394: {4}", //$NON-NLS-1$ + new Object[] { + tf == TimeFormat.CALENDAR ? Utils.formatDate(Math.min(time, time0)) + ' ' : "", //$NON-NLS-1$ + Utils.formatTime(Math.min(time, time0), tf, res), + tf == TimeFormat.CALENDAR ? Utils.formatDate(Math.max(time, time0)) + ' ' : "", //$NON-NLS-1$ + Utils.formatTime(Math.max(time, time0), tf, res), + Utils.formatDelta(Math.abs(time - time0), tf, res) + })); + } + fStatusLineManager.setMessage(message.toString()); + } + + @Override + public void mouseMove(MouseEvent e) { + if (null == fTimeProvider) { + return; + } + Point size = getCtrlSize(); + if (DRAG_TRACE_ITEM == fDragState) { + int nameWidth = fTimeProvider.getNameSpace(); + if (e.x > nameWidth && size.x > nameWidth && fDragX != e.x) { + fDragX = e.x; + double pixelsPerNanoSec = (size.x - nameWidth <= RIGHT_MARGIN) ? 0 : (double) (size.x - nameWidth - RIGHT_MARGIN) / (fTime1bak - fTime0bak); + long timeDelta = (long) ((pixelsPerNanoSec == 0) ? 0 : ((fDragX - fDragX0) / pixelsPerNanoSec)); + long time1 = fTime1bak - timeDelta; + long maxTime = fTimeProvider.getMaxTime(); + if (time1 > maxTime) { + time1 = maxTime; + } + long time0 = time1 - (fTime1bak - fTime0bak); + if (time0 < fTimeProvider.getMinTime()) { + time0 = fTimeProvider.getMinTime(); + time1 = time0 + (fTime1bak - fTime0bak); + } + fTimeProvider.setStartFinishTime(time0, time1); + } + } else if (DRAG_SPLIT_LINE == fDragState) { + fDragX = e.x; + fTimeProvider.setNameSpace(e.x); + } else if (DRAG_SELECTION == fDragState) { + fDragX = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), size.x - RIGHT_MARGIN); + redraw(); + fTimeGraphScale.setDragRange(fDragX0, fDragX); + fireDragSelectionChanged(getTimeAtX(fDragX0), getTimeAtX(fDragX)); + } else if (DRAG_ZOOM == fDragState) { + fDragX = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), size.x - RIGHT_MARGIN); + redraw(); + fTimeGraphScale.setDragRange(fDragX0, fDragX); + } else if (DRAG_NONE == fDragState) { + boolean mouseOverSplitLine = isOverSplitLine(e.x); + if (fMouseOverSplitLine != mouseOverSplitLine) { + redraw(); + } + fMouseOverSplitLine = mouseOverSplitLine; + } + updateCursor(e.x, e.stateMask); + updateStatusLine(e.x); + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + if (null == fTimeProvider) { + return; + } + if (1 == e.button && (e.stateMask & SWT.BUTTON_MASK) == 0) { + if (isOverSplitLine(e.x) && fTimeProvider.getNameSpace() != 0) { + fTimeProvider.setNameSpace(fIdealNameSpace); + boolean mouseOverSplitLine = isOverSplitLine(e.x); + if (fMouseOverSplitLine != mouseOverSplitLine) { + redraw(); + } + fMouseOverSplitLine = mouseOverSplitLine; + return; + } + int idx = getItemIndexAtY(e.y); + if (idx >= 0) { + selectItem(idx, false); + fireDefaultSelection(); + } + } + } + + @Override + public void mouseDown(MouseEvent e) { + if (fDragState != DRAG_NONE || null == fTimeProvider || + fTimeProvider.getTime0() == fTimeProvider.getTime1() || + getCtrlSize().x - fTimeProvider.getNameSpace() <= 0) { + return; + } + int idx; + if (1 == e.button && (e.stateMask & SWT.MODIFIER_MASK) == 0) { + int nameSpace = fTimeProvider.getNameSpace(); + if (nameSpace != 0 && isOverSplitLine(e.x)) { + fDragState = DRAG_SPLIT_LINE; + fDragButton = e.button; + fDragX = e.x; + fDragX0 = fDragX; + fTime0bak = fTimeProvider.getTime0(); + fTime1bak = fTimeProvider.getTime1(); + redraw(); + updateCursor(e.x, e.stateMask); + return; + } + } + if (1 == e.button && ((e.stateMask & SWT.MODIFIER_MASK) == 0 || (e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT)) { + int nameSpace = fTimeProvider.getNameSpace(); + idx = getItemIndexAtY(e.y); + if (idx >= 0) { + Item item = fItemData.fExpandedItems[idx]; + if (item.fHasChildren && e.x < nameSpace && e.x < MARGIN + (item.fLevel + 1) * EXPAND_SIZE) { + toggle(idx); + return; + } + selectItem(idx, false); + fireSelectionChanged(); + } else { + selectItem(idx, false); // clear selection + fireSelectionChanged(); + } + long hitTime = getTimeAtX(e.x); + if (hitTime >= 0) { + setCapture(true); + + fDragState = DRAG_SELECTION; + fDragButton = e.button; + fDragX = e.x; + fDragX0 = fDragX; + fDragTime0 = getTimeAtX(fDragX0); + long selectionBegin = fTimeProvider.getSelectionBegin(); + long selectionEnd = fTimeProvider.getSelectionEnd(); + int xBegin = getXForTime(selectionBegin); + int xEnd = getXForTime(selectionEnd); + if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) { + long time = getTimeAtX(e.x); + if (Math.abs(time - selectionBegin) < Math.abs(time - selectionEnd)) { + fDragX0 = xEnd; + fDragTime0 = selectionEnd; + } else { + fDragX0 = xBegin; + fDragTime0 = selectionBegin; + } + } else { + long time = getTimeAtX(e.x); + if (Math.abs(e.x - xBegin) < SNAP_WIDTH && Math.abs(time - selectionBegin) <= Math.abs(time - selectionEnd)) { + fDragX0 = xEnd; + fDragTime0 = selectionEnd; + } else if (Math.abs(e.x - xEnd) < SNAP_WIDTH && Math.abs(time - selectionEnd) <= Math.abs(time - selectionBegin)) { + fDragX0 = xBegin; + fDragTime0 = selectionBegin; + } + } + fTime0bak = fTimeProvider.getTime0(); + fTime1bak = fTimeProvider.getTime1(); + redraw(); + updateCursor(e.x, e.stateMask); + fTimeGraphScale.setDragRange(fDragX0, fDragX); + } + } else if (2 == e.button || (1 == e.button && (e.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL)) { + long hitTime = getTimeAtX(e.x); + if (hitTime > 0) { + setCapture(true); + fDragState = DRAG_TRACE_ITEM; + fDragButton = e.button; + fDragX = e.x; + fDragX0 = fDragX; + fTime0bak = fTimeProvider.getTime0(); + fTime1bak = fTimeProvider.getTime1(); + updateCursor(e.x, e.stateMask); + } + } else if (3 == e.button) { + setCapture(true); + fDragX = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), getCtrlSize().x - RIGHT_MARGIN); + fDragX0 = fDragX; + fDragTime0 = getTimeAtX(fDragX0); + fDragState = DRAG_ZOOM; + fDragButton = e.button; + redraw(); + updateCursor(e.x, e.stateMask); + fTimeGraphScale.setDragRange(fDragX0, fDragX); + } + } + + @Override + public void mouseUp(MouseEvent e) { + if (fPendingMenuDetectEvent != null && e.button == 3) { + menuDetected(fPendingMenuDetectEvent); + } + if (DRAG_NONE != fDragState) { + setCapture(false); + if (e.button == fDragButton && DRAG_TRACE_ITEM == fDragState) { + 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) { + if (fDragX == fDragX0) { // click without selecting anything + long time = getTimeAtX(e.x); + fTimeProvider.setSelectedTimeNotify(time, false); + } else { + long time0 = fDragTime0; + long time1 = getTimeAtX(fDragX); + if (time0 <= time1) { + fTimeProvider.setSelectionRangeNotify(time0, time1); + } else { + fTimeProvider.setSelectionRangeNotify(time1, time0); + } + } + fDragState = DRAG_NONE; + redraw(); + fTimeGraphScale.setDragRange(-1, -1); + } else if (e.button == fDragButton && DRAG_ZOOM == fDragState) { + int nameWidth = fTimeProvider.getNameSpace(); + if (Math.max(fDragX, fDragX0) > nameWidth && fDragX != fDragX0) { + long time0 = getTimeAtX(fDragX0); + long time1 = getTimeAtX(fDragX); + if (time0 < time1) { + fTimeProvider.setStartFinishTimeNotify(time0, time1); + } else { + fTimeProvider.setStartFinishTimeNotify(time1, time0); + } + } else { + redraw(); + } + fDragState = DRAG_NONE; + fTimeGraphScale.setDragRange(-1, -1); + } + } + updateCursor(e.x, e.stateMask); + updateStatusLine(e.x); + } + + @Override + public void mouseEnter(MouseEvent e) { + } + + @Override + public void mouseExit(MouseEvent e) { + if (fMouseOverSplitLine) { + fMouseOverSplitLine = false; + redraw(); + } + updateStatusLine(NO_STATUS); + } + + @Override + public void mouseHover(MouseEvent e) { + } + + @Override + public void mouseScrolled(MouseEvent e) { + if ((fMouseScrollFilterListener == null) || fDragState != DRAG_NONE) { + return; + } + boolean zoomScroll = 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 > getCtrlSize().x) { + // over the horizontal scroll bar + zoomScroll = false; + } else if (e.y >= 0 && e.y < getCtrlSize().y && e.x < fTimeProvider.getNameSpace()) { + // over the name space + zoomScroll = false; + } else { + zoomScroll = true; + } + } + if (zoomScroll && fTimeProvider.getTime0() != fTimeProvider.getTime1()) { + if (e.count > 0) { + zoom(true); + } else if (e.count < 0) { + zoom(false); + } + } else { + setTopIndex(getTopIndex() - e.count); + } + } + + @Override + public void controlMoved(ControlEvent e) { + } + + @Override + public void controlResized(ControlEvent e) { + adjustScrolls(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + + @Override + public void widgetSelected(SelectionEvent e) { + if (e.widget == getVerticalBar()) { + setTopIndex(getVerticalBar().getSelection()); + } else if (e.widget == getHorizontalBar() && null != fTimeProvider) { + int start = getHorizontalBar().getSelection(); + long time0 = fTimeProvider.getTime0(); + long time1 = fTimeProvider.getTime1(); + long timeMin = fTimeProvider.getMinTime(); + long timeMax = fTimeProvider.getMaxTime(); + long delta = timeMax - timeMin; + + long range = time1 - time0; + time0 = timeMin + Math.round(delta * ((double) start / H_SCROLLBAR_MAX)); + time1 = time0 + range; + + // TODO: Follow-up with Bug 310310 + // In Linux SWT.DRAG is the only value received + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=310310 + if (e.detail == SWT.DRAG) { + fTimeProvider.setStartFinishTime(time0, time1); + } else { + fTimeProvider.setStartFinishTimeNotify(time0, time1); + } + } + } + + @Override + public int getBorderWidth() { + return fBorderWidth; + } + + /** + * Set the border width + * + * @param borderWidth + * The width + */ + public void setBorderWidth(int borderWidth) { + this.fBorderWidth = borderWidth; + } + + /** + * @return The current height of the header row + */ + public int getHeaderHeight() { + return fHeaderHeight; + } + + /** + * Set the height of the header row + * + * @param headerHeight + * The height + */ + public void setHeaderHeight(int headerHeight) { + this.fHeaderHeight = headerHeight; + } + + /** + * @return The default height of regular item rows + */ + public int getItemHeight() { + return fGlobalItemHeight; + } + + /** + * Set the default height of regular item rows. + * + * @param rowHeight + * The height + */ + public void setItemHeight(int rowHeight) { + this.fGlobalItemHeight = rowHeight; + } + + /** + * Set the height of a specific item. Overrides the default item height. + * + * @param entry + * A time graph entry + * @param rowHeight + * The height + * @return true if the height is successfully stored, false otherwise + * + * @since 2.1 + */ + public boolean setItemHeight(ITimeGraphEntry entry, int rowHeight) { + Item item = fItemData.findItem(entry); + if (item != null) { + item.fItemHeight = rowHeight; + return true; + } + return false; + } + + /** + * Set the minimum item width + * + * @param width The minimum width + */ + public void setMinimumItemWidth(int width) { + this.fMinimumItemWidth = width; + } + + /** + * @return The minimum item width + */ + public int getMinimumItemWidth() { + return fMinimumItemWidth; + } + + /** + * @return The entries that are currently filtered out + * + * @since 2.0 + */ + public List getFilteredOut() { + return fItemData.getFilteredOut(); + } + + @Override + public void addSelectionChangedListener(ISelectionChangedListener listener) { + if (listener != null && !fSelectionChangedListeners.contains(listener)) { + fSelectionChangedListeners.add(listener); + } + } + + @Override + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + if (listener != null) { + fSelectionChangedListeners.remove(listener); + } + } + + @Override + public void setSelection(ISelection selection) { + if (selection instanceof TimeGraphSelection) { + TimeGraphSelection sel = (TimeGraphSelection) selection; + Object ob = sel.getFirstElement(); + if (ob instanceof ITimeGraphEntry) { + ITimeGraphEntry trace = (ITimeGraphEntry) ob; + selectItem(trace, false); + } + } + + } + + /** + * @param filter The filter object to be attached to the view + * @since 2.0 + */ + public void addFilter(ViewerFilter filter) { + if (!fFilters.contains(filter)) { + fFilters.add(filter); + } + } + + /** + * @param filter The filter object to be attached to the view + * @since 2.0 + */ + public void removeFilter(ViewerFilter filter) { + fFilters.remove(filter); + } + + /** + * @since 3.0 + */ + @Override + public void colorSettingsChanged(StateItem[] stateItems) { + /* Destroy previous colors from the resource manager */ + if (fEventColorMap != null) { + for (Color color : fEventColorMap) { + fResourceManager.destroyColor(color.getRGB()); + } + } + if (stateItems != null) { + fEventColorMap = new Color[stateItems.length]; + for (int i = 0; i < stateItems.length; i++) { + fEventColorMap[i] = fResourceManager.createColor(stateItems[i].getStateColor()); + } + } else { + fEventColorMap = new Color[] { }; + } + redraw(); + } + + private class ItemData { + private final Map fItemMap = new LinkedHashMap<>(); + private Item[] fExpandedItems = new Item[0]; + private Item[] fItems = new Item[0]; + private ITimeGraphEntry fRootEntries[] = new ITimeGraphEntry[0]; + private List fLinks = new ArrayList<>(); + private boolean fEntryFilter[] = new boolean[0]; + private final ArrayList fFilteredOut = new ArrayList<>(); + + public ItemData() { + } + + public Item findItem(ITimeGraphEntry entry) { + return fItemMap.get(entry); + } + + public int findItemIndex(ITimeGraphEntry entry) { + Item item = fItemMap.get(entry); + if (item == null) { + return -1; + } + return item.fExpandedIndex; + } + + public void refreshData() { + fItemMap.clear(); + fFilteredOut.clear(); + ITimeGraphEntry selection = getSelectedTrace(); + for (int i = 0; i < fRootEntries.length; i++) { + ITimeGraphEntry entry = fRootEntries[i]; + refreshData(fItemMap, null, 0, entry); + } + fItems = fItemMap.values().toArray(new Item[0]); + updateExpandedItems(); + if (selection != null) { + for (Item item : fExpandedItems) { + if (item.fEntry == selection) { + item.fSelected = true; + break; + } + } + } + } + + private void refreshData(Map itemMap, Item parent, int level, ITimeGraphEntry entry) { + Item item = new Item(entry, entry.getName(), level); + if (parent != null) { + parent.fChildren.add(item); + } + if (fGlobalItemHeight == CUSTOM_ITEM_HEIGHT) { + item.fItemHeight = fTimeGraphProvider.getItemHeight(entry); + } else { + item.fItemHeight = fGlobalItemHeight; + } + itemMap.put(entry, item); + if (entry.hasChildren()) { + item.fExpanded = fAutoExpandLevel == ALL_LEVELS || level < fAutoExpandLevel; + item.fHasChildren = true; + for (ITimeGraphEntry child : entry.getChildren()) { + refreshData(itemMap, item, level + 1, child); + } + } + } + + public void updateExpandedItems() { + for (Item item : fItems) { + item.fExpandedIndex = -1; + } + List expandedItemList = new ArrayList<>(); + for (int i = 0; i < fRootEntries.length; i++) { + ITimeGraphEntry entry = fRootEntries[i]; + Item item = findItem(entry); + refreshExpanded(expandedItemList, item); + } + fExpandedItems = expandedItemList.toArray(new Item[0]); + fTopIndex = Math.min(fTopIndex, Math.max(0, fExpandedItems.length - 1)); + } + + private void refreshExpanded(List expandedItemList, Item item) { + // Check for filters + boolean display = true; + for (ViewerFilter filter : fFilters) { + if (!filter.select(null, item.fEntry.getParent(), item.fEntry)) { + display = false; + break; + } + } + if (display) { + item.fExpandedIndex = expandedItemList.size(); + expandedItemList.add(item); + if (item.fHasChildren && item.fExpanded) { + for (Item child : item.fChildren) { + refreshExpanded(expandedItemList, child); + } + } + } + } + + public void refreshData(ITimeGraphEntry[] entries) { + if (entries == null) { + fEntryFilter = null; + fRootEntries = null; + } else { + if (entries.length == 0) { + fEntryFilter = null; + } else if (fEntryFilter == null || entries.length != fEntryFilter.length) { + fEntryFilter = new boolean[entries.length]; + java.util.Arrays.fill(fEntryFilter, true); + } + fRootEntries = Arrays.copyOf(entries, entries.length); + } + + refreshData(); + } + + public void refreshArrows(List events) { + /* If links are null, reset the list */ + if (events != null) { + fLinks = events; + } else { + fLinks = new ArrayList<>(); + } + } + + public ITimeGraphEntry[] getEntries() { + return fRootEntries; + } + + public boolean[] getEntryFilter() { + return fEntryFilter; + } + + public List getFilteredOut() { + return fFilteredOut; + } + } + + private class Item { + private boolean fExpanded; + private int fExpandedIndex; + private boolean fSelected; + private boolean fHasChildren; + private int fItemHeight; + private final int fLevel; + private final List fChildren; + private final String fName; + private final ITimeGraphEntry fEntry; + + public Item(ITimeGraphEntry entry, String name, int level) { + this.fEntry = entry; + this.fName = name; + this.fLevel = level; + this.fChildren = new ArrayList<>(); + } + + @Override + public String toString() { + return fName; + } + } + + /** + * @since 1.2 + */ + @Override + public void menuDetected(MenuDetectEvent e) { + if (null == fTimeProvider) { + return; + } + if (e.detail == SWT.MENU_MOUSE) { + 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; + return; + } + fPendingMenuDetectEvent = null; + if (fDragState != DRAG_ZOOM || fDragX != fDragX0) { + return; + } + } else { + if (fDragState != DRAG_NONE) { + 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; + if (entry.hasTimeEvents()) { + ITimeEvent event = Utils.findEvent(entry, getTimeAtX(p.x), 2); + if (event != null) { + e.data = event; + fireMenuEventOnTimeEvent(e); + return; + } + } + e.data = entry; + fireMenuEventOnTimeGraphEntry(e); + } + } + +} + +