| /***************************************************************************** |
| * Copyright (c) 2007, 2019 Intel Corporation and others |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * 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.tracecompass.tmf.ui.widgets.timegraph.widgets; |
| |
| import java.util.AbstractMap.SimpleEntry; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Objects; |
| import java.util.Queue; |
| import java.util.Set; |
| |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.jface.action.IStatusLineManager; |
| import org.eclipse.jface.resource.ColorRegistry; |
| 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.IStructuredSelection; |
| import org.eclipse.jface.viewers.ITableLabelProvider; |
| import org.eclipse.jface.viewers.SelectionChangedEvent; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jface.viewers.ViewerFilter; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.ControlListener; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.events.FocusListener; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.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.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.Font; |
| import org.eclipse.swt.graphics.FontData; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.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.Menu; |
| import org.eclipse.swt.widgets.Tree; |
| import org.eclipse.swt.widgets.TreeColumn; |
| import org.eclipse.tracecompass.common.core.math.SaturatedArithmetic; |
| import org.eclipse.tracecompass.internal.provisional.tmf.ui.widgets.timegraph.IStylePresentationProvider; |
| import org.eclipse.tracecompass.internal.tmf.ui.util.LineClipper; |
| import org.eclipse.tracecompass.internal.tmf.ui.util.StylePropertiesUtils; |
| import org.eclipse.tracecompass.internal.tmf.ui.util.SymbolHelper; |
| import org.eclipse.tracecompass.internal.tmf.ui.widgets.timegraph.model.TimeGraphLineEntry; |
| import org.eclipse.tracecompass.internal.tmf.ui.widgets.timegraph.model.TimeLineEvent; |
| import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle; |
| import org.eclipse.tracecompass.tmf.core.model.StyleProperties; |
| import org.eclipse.tracecompass.tmf.core.model.StyleProperties.BorderStyle; |
| import org.eclipse.tracecompass.tmf.core.model.StyleProperties.SymbolType; |
| import org.eclipse.tracecompass.tmf.core.model.timegraph.IFilterProperty; |
| import org.eclipse.tracecompass.tmf.core.presentation.RGBAColor; |
| import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager; |
| import org.eclipse.tracecompass.tmf.ui.colors.RGBAUtil; |
| import org.eclipse.tracecompass.tmf.ui.model.StyleManager; |
| import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo; |
| import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal; |
| import org.eclipse.tracecompass.tmf.ui.views.FormatTimeUtils; |
| import org.eclipse.tracecompass.tmf.ui.views.FormatTimeUtils.Resolution; |
| import org.eclipse.tracecompass.tmf.ui.views.FormatTimeUtils.TimeFormat; |
| import org.eclipse.tracecompass.tmf.ui.views.ITmfTimeAligned; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphColorListener; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphPresentationProvider; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphTimeListener; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphTreeListener; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphViewerFilterListener; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.StateItem; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphTimeEvent; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphTreeExpansionEvent; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ILinkEvent; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEvent; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEventStyleStrings; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.MarkerEvent; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry; |
| |
| import com.google.common.collect.Iterables; |
| |
| /** |
| * Time graph control implementation |
| * |
| * @author Alvaro Sanchez-Leon |
| * @author Patrick Tasse |
| */ |
| public class TimeGraphControl extends TimeGraphBaseControl |
| implements FocusListener, KeyListener, MouseMoveListener, MouseListener, |
| MouseWheelListener, MouseTrackListener, TraverseListener, ISelectionProvider, |
| MenuDetectListener, ITmfTimeGraphDrawingHelper, ITimeGraphColorListener, Listener { |
| |
| /** |
| * Default state width ratio |
| * @since 4.1 |
| */ |
| public static final float DEFAULT_STATE_WIDTH = 1.0f; |
| |
| /** |
| * Default link width ratio |
| * @since 4.1 |
| */ |
| public static final float DEFAULT_LINK_WIDTH = 0.1f; |
| |
| /** |
| * Constant indicating that all levels of the time graph should be expanded |
| */ |
| public static final int ALL_LEVELS = AbstractTreeViewer.ALL_LEVELS; |
| |
| private static final @NonNull String HIGHLIGHTED_BOUND_COLOR = "#ff3300"; //$NON-NLS-1$ |
| private static final int HIGHLIGHTED_BOUND_WIDTH = 4; |
| private static final int DRAG_MARGIN = 5; |
| |
| private static final int DRAG_NONE = 0; |
| private static final int DRAG_TRACE_ITEM = 1; |
| private static final int DRAG_SPLIT_LINE = 2; |
| private static final int DRAG_ZOOM = 3; |
| private static final int DRAG_SELECTION = 4; |
| |
| /** |
| * Get item height from provider |
| */ |
| private static final int CUSTOM_ITEM_HEIGHT = -1; |
| private static final int MIN_MIDLINE_HEIGHT = 3; |
| |
| 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 = 3; |
| private static final int ARROW_HOVER_MAX_DIST = 5; |
| |
| /** |
| * base to height ratio |
| */ |
| private static final double ARROW_RATIO = Math.sqrt(3) / 2; |
| private static final int NO_STATUS = -1; |
| private static final int STATUS_WITHOUT_CURSOR_TIME = -2; |
| |
| private static final int MAX_LABEL_LENGTH = 256; |
| |
| /** points per inch */ |
| private static final int PPI = 72; |
| private static final int DPI = 96; |
| |
| private static final int OPAQUE = 255; |
| |
| private static final int VERTICAL_ZOOM_DELAY = 400; |
| |
| private static final String PREFERRED_WIDTH = "width"; //$NON-NLS-1$ |
| |
| private static final ColorRegistry COLOR_REGISTRY = new ColorRegistry(); |
| |
| /** |
| * The alpha color component value for dimmed events |
| * |
| * @since 4.0 |
| */ |
| private static final int DIMMED_ALPHA_COEFFICIENT = 4; |
| |
| /** Resource manager */ |
| private LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources()); |
| |
| /** Color map for event types */ |
| private Color[] fEventColorMap = null; |
| |
| private ITimeDataProvider fTimeProvider; |
| private ITableLabelProvider fLabelProvider; |
| private IStatusLineManager fStatusLineManager = null; |
| private Tree fTree = null; |
| private TimeGraphScale fTimeGraphScale = null; |
| |
| private boolean fIsInFocus = false; |
| private boolean fMouseOverSplitLine = false; |
| private int fGlobalItemHeight = CUSTOM_ITEM_HEIGHT; |
| private int fHeightAdjustment = 0; |
| private int fMaxItemHeight = 0; |
| private Map<Integer, Font> fFonts = new HashMap<>(); |
| private boolean fBlendSubPixelEvents = false; |
| private int fMinimumItemWidth = 0; |
| private int fTopIndex = 0; |
| private int fDragState = DRAG_NONE; |
| private boolean fDragBeginMarker = false; |
| private int fDragButton; |
| private int fDragX0 = 0; |
| private int fDragX = 0; |
| private ITimeGraphEntry fDragEntry = null; |
| private boolean fHasNamespaceFocus = false; |
| /** |
| * Used to preserve accuracy of modified selection |
| */ |
| private long fDragTime0 = 0; |
| private int fIdealNameSpace = 0; |
| private boolean fAutoResizeColumns = true; |
| private long fTime0bak; |
| private long fTime1bak; |
| private ITimeGraphPresentationProvider fTimeGraphProvider = null; |
| private ItemData fItemData = null; |
| private List<IMarkerEvent> fMarkers = null; |
| private boolean fMarkersVisible = true; |
| private List<SelectionListener> fSelectionListeners; |
| private List<ITimeGraphTimeListener> fDragSelectionListeners; |
| private final List<ISelectionChangedListener> fSelectionChangedListeners = new ArrayList<>(); |
| private final List<ITimeGraphTreeListener> fTreeListeners = new ArrayList<>(); |
| private final List<ITimeGraphViewerFilterListener> fViewerFilterListeners = new ArrayList<>(); |
| private final List<MenuDetectListener> fTimeGraphEntryMenuListeners = new ArrayList<>(); |
| private final List<MenuDetectListener> 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 Set<@NonNull ViewerFilter> fFilters = new LinkedHashSet<>(); |
| private MenuDetectEvent fPendingMenuDetectEvent = null; |
| private boolean fGridLinesVisible = true; |
| private Color fGridLineColor = Display.getDefault().getSystemColor(SWT.COLOR_GRAY); |
| private boolean fLabelsVisible = true; |
| private boolean fMidLinesVisible = true; |
| private boolean fHideArrows = false; |
| private int fAutoExpandLevel = ALL_LEVELS; |
| private Entry<ITimeGraphEntry, Integer> fVerticalZoomAlignEntry = null; |
| private long fVerticalZoomAlignTime = 0; |
| private int fBorderWidth = 0; |
| private int fHeaderHeight = 0; |
| private int fLastTransparentX = -1; |
| |
| private boolean fFilterActive; |
| private boolean fHasSavedFilters; |
| |
| /** |
| * 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.DOUBLE_BUFFERED); |
| |
| fItemData = new ItemData(); |
| |
| addFocusListener(this); |
| addMouseListener(this); |
| addMouseMoveListener(this); |
| addMouseTrackListener(this); |
| addMouseWheelListener(this); |
| addTraverseListener(this); |
| addKeyListener(this); |
| addMenuDetectListener(this); |
| addListener(SWT.MouseWheel, this); |
| addDisposeListener((e) -> { |
| fResourceManager.dispose(); |
| for (Font font : fFonts.values()) { |
| font.dispose(); |
| } |
| }); |
| } |
| |
| /** |
| * Sets the timegraph provider used by this timegraph viewer. |
| * |
| * @param timeGraphProvider |
| * the timegraph provider |
| */ |
| public void setTimeGraphProvider(ITimeGraphPresentationProvider timeGraphProvider) { |
| fTimeGraphProvider = timeGraphProvider; |
| |
| timeGraphProvider.setDrawingHelper(this); |
| timeGraphProvider.addColorListener(this); |
| |
| StateItem[] stateItems = fTimeGraphProvider.getStateTable(); |
| colorSettingsChanged(stateItems); |
| } |
| |
| /** |
| * Gets the timegraph provider used by this timegraph viewer. |
| * |
| * @return the timegraph provider, or <code>null</code> if not set. |
| */ |
| public ITimeGraphPresentationProvider getTimeGraphProvider() { |
| return fTimeGraphProvider; |
| } |
| |
| /** |
| * Gets the time data provider used by this viewer. |
| * |
| * @return The time data provider, or <code>null</code> if not set |
| * @since 2.1 |
| */ |
| public ITimeDataProvider getTimeDataProvider() { |
| return fTimeProvider; |
| } |
| |
| /** |
| * Gets the color map used by this timegraph viewer. |
| * |
| * @return a color map, or <code>null</code> if not set. |
| */ |
| public Color[] getEventColorMap() { |
| return fEventColorMap; |
| } |
| |
| /** |
| * Assign the given time provider |
| * |
| * @param timeProvider |
| * The time provider |
| */ |
| public void setTimeProvider(ITimeDataProvider timeProvider) { |
| fTimeProvider = timeProvider; |
| redraw(); |
| } |
| |
| /** |
| * Set the label provider for the name space |
| * |
| * @param labelProvider |
| * The label provider |
| * @since 2.3 |
| */ |
| public void setLabelProvider(ITableLabelProvider labelProvider) { |
| fLabelProvider = labelProvider; |
| redraw(); |
| } |
| |
| /** |
| * Get the label provider for the name space |
| * |
| * @return The label provider |
| * @since 2.3 |
| */ |
| public ITableLabelProvider getLabelProvider() { |
| return fLabelProvider; |
| } |
| |
| /** |
| * Assign the status line manager |
| * |
| * @param statusLineManager |
| * The status line manager, or null to disable status line messages |
| */ |
| public void setStatusLineManager(IStatusLineManager statusLineManager) { |
| if (fStatusLineManager != null && statusLineManager == null) { |
| fStatusLineManager.setMessage(""); //$NON-NLS-1$ |
| } |
| fStatusLineManager = statusLineManager; |
| } |
| |
| /** |
| * Assign the tree that represents the name space header |
| * |
| * @param tree |
| * The tree |
| * @since 2.3 |
| */ |
| public void setTree(Tree tree) { |
| fTree = tree; |
| } |
| |
| /** |
| * Returns the tree control associated with this time graph control. The tree is |
| * only used for column handling of the name space and contains no tree items. |
| * |
| * @return the tree control |
| * @since 2.3 |
| */ |
| public Tree getTree() { |
| return fTree; |
| } |
| |
| /** |
| * Sets the columns for this time graph control's name space. |
| * |
| * @param columnNames |
| * the column names |
| * @since 2.3 |
| */ |
| public void setColumns(String[] columnNames) { |
| Tree tree = getTree(); |
| for (TreeColumn column : tree.getColumns()) { |
| column.dispose(); |
| } |
| ControlListener controlListener = new ControlListener() { |
| @Override |
| public void controlResized(ControlEvent e) { |
| if (fAutoResizeColumns && ((TreeColumn) e.widget).getWidth() < (Integer) e.widget.getData(PREFERRED_WIDTH)) { |
| fAutoResizeColumns = false; |
| } |
| redraw(); |
| } |
| |
| @Override |
| public void controlMoved(ControlEvent e) { |
| redraw(); |
| } |
| }; |
| for (String columnName : columnNames) { |
| TreeColumn column = new TreeColumn(tree, SWT.LEFT); |
| column.setMoveable(true); |
| column.setText(columnName); |
| column.pack(); |
| column.setData(PREFERRED_WIDTH, column.getWidth()); |
| column.addControlListener(controlListener); |
| } |
| } |
| |
| /** |
| * Assign the time graph scale |
| * |
| * @param timeGraphScale |
| * The time graph scale |
| */ |
| 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<SelectionListener> it = fSelectionListeners.iterator(); |
| while (it.hasNext()) { |
| SelectionListener listener = it.next(); |
| listener.widgetSelected(null); |
| } |
| } |
| |
| if (null != fSelectionChangedListeners) { |
| for (ISelectionChangedListener listener : fSelectionChangedListeners) { |
| listener.selectionChanged(new SelectionChangedEvent(this, getSelection())); |
| } |
| } |
| } |
| |
| /** |
| * Default selection callback |
| */ |
| public void fireDefaultSelection() { |
| if (null != fSelectionListeners) { |
| Iterator<SelectionListener> it = fSelectionListeners.iterator(); |
| while (it.hasNext()) { |
| SelectionListener listener = it.next(); |
| listener.widgetDefaultSelected(null); |
| } |
| } |
| } |
| |
| /** |
| * Add a drag selection listener |
| * |
| * @param listener |
| * The listener to add |
| */ |
| 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 |
| */ |
| 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 |
| */ |
| 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<ITimeGraphTimeListener> 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(); |
| } |
| |
| /** |
| * Refresh the data for the thing |
| */ |
| public void refreshData() { |
| fItemData.refreshData(); |
| redraw(); |
| } |
| |
| /** |
| * Refresh data for the given traces |
| * |
| * @param traces |
| * The traces to refresh |
| */ |
| public void refreshData(ITimeGraphEntry[] traces) { |
| fItemData.refreshData(traces); |
| redraw(); |
| } |
| |
| /** |
| * Refresh the links (arrows) of this widget |
| * |
| * @param events |
| * The link event list |
| */ |
| public void refreshArrows(List<ILinkEvent> events) { |
| fItemData.refreshArrows(events); |
| } |
| |
| /** |
| * Get the links (arrows) of this widget |
| * |
| * @return The unmodifiable link event list |
| * |
| * @since 1.1 |
| */ |
| public List<ILinkEvent> getArrows() { |
| return Collections.unmodifiableList(fItemData.fLinks); |
| } |
| |
| 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(); |
| } |
| |
| /** |
| * Set the top index so that the requested element is at the specified position. |
| * |
| * @param entry |
| * the time graph entry to be positioned |
| * @param y |
| * the requested y-coordinate |
| * @since 2.0 |
| */ |
| public void setElementPosition(ITimeGraphEntry entry, int y) { |
| Item item = fItemData.fItemMap.get(entry); |
| if (item == null || item.fExpandedIndex == -1) { |
| return; |
| } |
| int index = item.fExpandedIndex; |
| Rectangle itemRect = getItemRect(getClientArea(), index); |
| int delta = itemRect.y + itemRect.height - y; |
| int topIndex = getItemIndexAtY(delta); |
| if (topIndex != -1) { |
| setTopIndex(topIndex); |
| } else { |
| if (delta < 0) { |
| setTopIndex(0); |
| } else { |
| setTopIndex(getExpandedElementCount()); |
| } |
| } |
| } |
| |
| /** |
| * Sets the auto-expand level to be used for new entries discovered when calling |
| * {@link #refreshData()} or {@link #refreshData(ITimeGraphEntry[])} . The value |
| * 0 means that there is no auto-expand; 1 means that top-level entries are |
| * expanded, but not their children; 2 means that top-level entries are |
| * expanded, and their children, but not grand-children; and so on. |
| * <p> |
| * The value {@link #ALL_LEVELS} means that all subtrees should be expanded. |
| * </p> |
| * |
| * @param level |
| * non-negative level, or <code>ALL_LEVELS</code> to expand all |
| * levels of the tree |
| */ |
| public void setAutoExpandLevel(int level) { |
| fAutoExpandLevel = level; |
| } |
| |
| /** |
| * Returns the auto-expand level. |
| * |
| * @return non-negative level, or <code>ALL_LEVELS</code> if all levels of the |
| * tree are expanded automatically |
| * @see #setAutoExpandLevel |
| */ |
| public int getAutoExpandLevel() { |
| return fAutoExpandLevel; |
| } |
| |
| /** |
| * Get the expanded state of a given entry. |
| * |
| * @param entry |
| * The entry |
| * @return true if the entry is expanded, false if collapsed |
| * @since 1.1 |
| */ |
| public boolean getExpandedState(ITimeGraphEntry entry) { |
| Item item = fItemData.fItemMap.get(entry); |
| return (item != null ? item.fExpanded : false); |
| } |
| |
| /** |
| * Set the expanded state of a given entry |
| * |
| * @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(); |
| } |
| } |
| |
| /** |
| * Set the expanded state of a given list of entries |
| * |
| * @param entries |
| * The list of entries |
| * @param expanded |
| * True if expanded, false if collapsed |
| * @since 3.1 |
| */ |
| public void setExpandedState(Iterable<ITimeGraphEntry> entries, boolean expanded) { |
| for (ITimeGraphEntry entry : entries) { |
| Item item = fItemData.findItem(entry); |
| if (item != null) { |
| item.fExpanded = expanded; |
| } |
| } |
| fItemData.updateExpandedItems(); |
| redraw(); |
| } |
| |
| /** |
| * Set the expanded state of a given entry to certain relative level. It will |
| * call fireTreeEvent() for each changed entry. At the end it will call |
| * redraw(). |
| * |
| * @param entry |
| * The entry |
| * @param level |
| * level to expand to or negative for all levels |
| * @param expanded |
| * True if expanded, false if collapsed |
| */ |
| private void setExpandedState(ITimeGraphEntry entry, int level, boolean expanded) { |
| setExpandedStateInt(entry, level, expanded); |
| redraw(); |
| } |
| |
| /** |
| * Set the expanded state of a given entry and its children to the first level |
| * that has one collapsed entry. |
| * |
| * @param entry |
| * The entry |
| */ |
| private void setExpandedStateLevel(ITimeGraphEntry entry) { |
| int level = findExpandedLevel(entry); |
| if (level >= 0) { |
| setExpandedStateInt(entry, level, true); |
| redraw(); |
| } |
| } |
| |
| /* |
| * Inner class for finding relative level with at least one collapsed entry. |
| */ |
| private class SearchNode { |
| SearchNode(ITimeGraphEntry e, int l) { |
| entry = e; |
| level = l; |
| } |
| |
| ITimeGraphEntry entry; |
| int level; |
| } |
| |
| /** |
| * Finds the relative level with at least one collapsed entry. |
| * |
| * @param entry |
| * the start entry |
| * @return the found level or -1 if all levels are already expanded. |
| */ |
| private int findExpandedLevel(ITimeGraphEntry entry) { |
| Queue<SearchNode> queue = new LinkedList<>(); |
| SearchNode root = new SearchNode(entry, 0); |
| SearchNode node = root; |
| queue.add(root); |
| |
| while (!queue.isEmpty()) { |
| node = queue.remove(); |
| if (node.entry.hasChildren() && !getExpandedState(node.entry)) { |
| return node.level; |
| } |
| for (ITimeGraphEntry e : node.entry.getChildren()) { |
| if (e.hasChildren()) { |
| SearchNode n = new SearchNode(e, node.level + 1); |
| queue.add(n); |
| } |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Set the expanded state of a given entry to certain relative level. It will |
| * call fireTreeEvent() for each changed entry. No redraw is done. |
| * |
| * @param entry |
| * The entry |
| * @param level |
| * level to expand to or negative for all levels |
| * @param expanded |
| * True if expanded, false if collapsed |
| */ |
| private void setExpandedStateInt(ITimeGraphEntry entry, int aLevel, boolean expanded) { |
| int level = aLevel; |
| if ((level > 0) || (level < 0)) { |
| level--; |
| if (entry.hasChildren()) { |
| for (ITimeGraphEntry e : entry.getChildren()) { |
| setExpandedStateInt(e, level, expanded); |
| } |
| } |
| } |
| Item item = fItemData.findItem(entry); |
| if (item != null && item.fExpanded != expanded) { |
| item.fExpanded = expanded; |
| fItemData.updateExpandedItems(); |
| fireTreeEvent(item.fEntry, item.fExpanded); |
| } |
| } |
| |
| /** |
| * Collapses all nodes of the viewer's tree, starting with the root. |
| */ |
| 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. |
| */ |
| 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 viewer filter listener |
| * |
| * @param listener |
| * The listener to add |
| * @since 3.2 |
| */ |
| public void addViewerFilterListener(ITimeGraphViewerFilterListener listener) { |
| if (!fViewerFilterListeners.contains(listener)) { |
| fViewerFilterListeners.add(listener); |
| } |
| } |
| |
| /** |
| * Remove a viewer filter listener |
| * |
| * @param listener |
| * The listener to remove |
| * @since 3.2 |
| */ |
| public void removeViewerFilterListener(ITimeGraphViewerFilterListener listener) { |
| if (fViewerFilterListeners.contains(listener)) { |
| fViewerFilterListeners.remove(listener); |
| } |
| } |
| |
| /** |
| * Viewer filter added callback |
| * |
| * @param filters |
| * The added filters |
| * |
| * @since 3.1 |
| */ |
| private void fireFiltersAdded(@NonNull Iterable<ViewerFilter> filters) { |
| for (ITimeGraphViewerFilterListener listener : fViewerFilterListeners) { |
| listener.filtersAdded(filters); |
| } |
| } |
| |
| /** |
| * Viewer filter changed callback |
| * |
| * @param filters |
| * The changed filters |
| * |
| * @since 3.1 |
| */ |
| private void fireFiltersChanged(@NonNull Iterable<ViewerFilter> filters) { |
| for (ITimeGraphViewerFilterListener listener : fViewerFilterListeners) { |
| listener.filtersChanged(filters); |
| } |
| } |
| |
| /** |
| * Viewer filter removed callback |
| * |
| * @param filters |
| * The removed filters |
| * |
| * @since 3.1 |
| */ |
| private void fireFiltersRemoved(@NonNull Iterable<ViewerFilter> filters) { |
| for (ITimeGraphViewerFilterListener listener : fViewerFilterListeners) { |
| listener.filtersRemoved(filters); |
| } |
| } |
| |
| /** |
| * Add a menu listener on {@link ITimeGraphEntry}s |
| * |
| * @param listener |
| * The listener to add |
| */ |
| 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 |
| */ |
| 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 |
| */ |
| 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 |
| */ |
| 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 boolean setFocus() { |
| if ((fTimeProvider != null) && fTimeProvider.getNameSpace() > 0) { |
| fHasNamespaceFocus = true; |
| } |
| return super.setFocus(); |
| } |
| |
| /** |
| * Returns the current selection for this time graph. If a time graph entry is |
| * selected, it will be the first element in the selection. If a time event is |
| * selected, it will be the second element in the selection. |
| * |
| * @return the current selection |
| */ |
| @Override |
| public ISelection getSelection() { |
| ITimeGraphEntry entry = getSelectedTrace(); |
| if (null != entry && null != fTimeProvider) { |
| long selectedTime = fTimeProvider.getSelectionBegin(); |
| ITimeEvent event = Utils.findEvent(entry, selectedTime, 0); |
| if (event == null) { |
| return new StructuredSelection(entry); |
| } |
| return new StructuredSelection(new Object[] { entry, event }); |
| } |
| return StructuredSelection.EMPTY; |
| } |
| |
| /** |
| * Get the selection object |
| * |
| * @return The selection |
| */ |
| public ISelection getSelectionTrace() { |
| ITimeGraphEntry entry = getSelectedTrace(); |
| if (null != entry) { |
| return new StructuredSelection(entry); |
| } |
| return StructuredSelection.EMPTY; |
| } |
| |
| /** |
| * 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 |
| * @param extend |
| * true to extend selection range, false for single selection |
| * @since 1.0 |
| */ |
| public void selectEvent(int n, boolean extend) { |
| if (null == fTimeProvider) { |
| return; |
| } |
| ITimeGraphEntry entry = getSelectedTrace(); |
| if (entry == null) { |
| return; |
| } |
| long time = fTimeProvider.getSelectionEnd(); |
| if (n > 0) { |
| time = Math.min(Utils.nextChange(entry, time), fTimeProvider.getMaxTime()); |
| } else if (n < 0) { |
| time = Math.max(Utils.prevChange(entry, time), fTimeProvider.getMinTime()); |
| } |
| if (extend) { |
| fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), time, true); |
| } else { |
| fTimeProvider.setSelectedTimeNotify(time, true); |
| } |
| fireSelectionChanged(); |
| updateStatusLine(STATUS_WITHOUT_CURSOR_TIME); |
| } |
| |
| /** |
| * Select the next event |
| * |
| * @param extend |
| * true to extend selection range, false for single selection |
| * @since 1.0 |
| */ |
| public void selectNextEvent(boolean extend) { |
| selectEvent(1, extend); |
| // Notify if visible time window has been adjusted |
| fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1()); |
| } |
| |
| /** |
| * Select the previous event |
| * |
| * @param extend |
| * true to extend selection range, false for single selection |
| * @since 1.0 |
| */ |
| public void selectPrevEvent(boolean extend) { |
| selectEvent(-1, extend); |
| // 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); |
| } |
| |
| /** |
| * Scroll left or right by one quarter window size |
| * |
| * @param left |
| * true to scroll left, false to scroll right |
| */ |
| public void horizontalScroll(boolean left) { |
| long time0 = fTimeProvider.getTime0(); |
| long time1 = fTimeProvider.getTime1(); |
| long timeMin = fTimeProvider.getMinTime(); |
| long timeMax = fTimeProvider.getMaxTime(); |
| long range = time1 - time0; |
| if (range <= 0) { |
| return; |
| } |
| long increment = Math.max(1, range / 4); |
| if (left) { |
| time0 = Math.max(time0 - increment, timeMin); |
| time1 = time0 + range; |
| } else { |
| time1 = Math.min(time1 + increment, timeMax); |
| time0 = time1 - range; |
| } |
| fTimeProvider.setStartFinishTimeNotify(time0, time1); |
| } |
| |
| /** |
| * Zoom based on mouse cursor location with mouse scrolling |
| * |
| * @param zoomIn |
| * true to zoom in, false to zoom out |
| */ |
| public void zoom(boolean zoomIn) { |
| Point cursorDisplayLocation = getDisplay().getCursorLocation(); |
| Point cursorControlLocation = toControl(cursorDisplayLocation); |
| Point cursorParentLocation = getParent().toControl(cursorDisplayLocation); |
| Rectangle controlBounds = getBounds(); |
| // check the X axis only |
| if (!controlBounds.contains(cursorParentLocation.x, controlBounds.y)) { |
| return; |
| } |
| int nameSpace = fTimeProvider.getNameSpace(); |
| int timeSpace = fTimeProvider.getTimeSpace(); |
| int xPos = Math.max(nameSpace, Math.min(nameSpace + timeSpace, cursorControlLocation.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); |
| /* snap to bounds if zooming out of range */ |
| newTime0 = Math.max(fTimeProvider.getMinTime(), Math.min(newTime0, fTimeProvider.getMaxTime() - newInterval)); |
| long newTime1 = newTime0 + newInterval; |
| fTimeProvider.setStartFinishTimeNotify(newTime0, newTime1); |
| } |
| |
| /** |
| * 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 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 newInterval; |
| long time0; |
| if (prevTime1 - prevTime0 <= 1) { |
| newInterval = 2; |
| time0 = selTime - 1; |
| } else { |
| newInterval = (long) Math.ceil((prevTime1 - prevTime0) * ZOOM_FACTOR); |
| time0 = selTime - (long) Math.ceil((selTime - prevTime0) * ZOOM_FACTOR); |
| } |
| /* snap to bounds if zooming out of range */ |
| time0 = Math.max(fTimeProvider.getMinTime(), Math.min(time0, fTimeProvider.getMaxTime() - newInterval)); |
| long time1 = time0 + newInterval; |
| |
| fTimeProvider.setStartFinishTimeNotify(time0, time1); |
| } |
| |
| /** |
| * Zoom vertically. |
| * |
| * @param zoomIn |
| * true to zoom in, false to zoom out |
| * @since 2.0 |
| */ |
| public void verticalZoom(boolean zoomIn) { |
| if (zoomIn) { |
| fHeightAdjustment++; |
| } else { |
| fHeightAdjustment--; |
| fHeightAdjustment = Math.max(fHeightAdjustment, 1 - fMaxItemHeight); |
| } |
| fItemData.refreshData(); |
| redraw(); |
| } |
| |
| /** |
| * Reset the vertical zoom to default. |
| * |
| * @since 2.0 |
| */ |
| public void resetVerticalZoom() { |
| fHeightAdjustment = 0; |
| fItemData.refreshData(); |
| redraw(); |
| } |
| |
| /** |
| * Set the vertical grid lines visibility. The default is true. |
| * |
| * @param visible |
| * true to show the grid lines, false otherwise |
| * @since 2.0 |
| */ |
| public void setGridLinesVisible(boolean visible) { |
| fGridLinesVisible = visible; |
| redraw(); |
| } |
| |
| /** |
| * Get the vertical grid lines visibility. |
| * |
| * @return true if the grid lines are visible, false otherwise |
| * @since 2.0 |
| */ |
| public boolean getGridLinesVisible() { |
| return fGridLinesVisible; |
| } |
| |
| /** |
| * Set the grid line color. The default is SWT.COLOR_GRAY. |
| * |
| * @param color |
| * the grid line color |
| * @since 2.0 |
| */ |
| public void setGridLineColor(Color color) { |
| fGridLineColor = color; |
| redraw(); |
| } |
| |
| /** |
| * Get the grid line color. |
| * |
| * @return the grid line color |
| * @since 2.0 |
| */ |
| public Color getGridLineColor() { |
| return fGridLineColor; |
| } |
| |
| /** |
| * Set the label visibility. The default is true. |
| * |
| * @param visible |
| * true to show the labels, false otherwise |
| * @since 5.2 |
| */ |
| public void setLabelsVisible(boolean visible) { |
| fLabelsVisible = visible; |
| redraw(); |
| } |
| |
| /** |
| * Set the horizontal middle lines visibility. The default is true. |
| * |
| * @param visible |
| * true to show the middle lines, false otherwise |
| * @since 4.0 |
| */ |
| public void setMidLinesVisible(boolean visible) { |
| fMidLinesVisible = visible; |
| redraw(); |
| } |
| |
| /** |
| * Get the horizontal middle lines visibility. |
| * |
| * @return true if the middle lines are visible, false otherwise |
| * @since 4.0 |
| */ |
| public boolean getMidLinesVisible() { |
| return fMidLinesVisible; |
| } |
| |
| /** |
| * Set the markers list. |
| * |
| * @param markers |
| * The markers list, or null |
| * @since 2.0 |
| */ |
| public void setMarkers(List<IMarkerEvent> markers) { |
| fMarkers = markers; |
| } |
| |
| /** |
| * Get the markers list. |
| * |
| * @return The markers list, or null |
| * @since 2.0 |
| */ |
| public List<IMarkerEvent> getMarkers() { |
| return fMarkers; |
| } |
| |
| /** |
| * Set the markers visibility. The default is true. |
| * |
| * @param visible |
| * true to show the markers, false otherwise |
| * @since 2.0 |
| */ |
| public void setMarkersVisible(boolean visible) { |
| fMarkersVisible = visible; |
| } |
| |
| /** |
| * Get the markers visibility. |
| * |
| * @return true if the markers are visible, false otherwise |
| * @since 2.0 |
| */ |
| public boolean getMarkersVisible() { |
| return fMarkersVisible; |
| } |
| |
| /** |
| * Hide arrows |
| * |
| * @param hideArrows |
| * true to hide arrows |
| */ |
| public void hideArrows(boolean hideArrows) { |
| fHideArrows = hideArrows; |
| } |
| |
| /** |
| * Follow the arrow forward |
| * |
| * @param extend |
| * true to extend selection range, false for single selection |
| * @since 1.0 |
| */ |
| public void followArrowFwd(boolean extend) { |
| ITimeGraphEntry trace = getSelectedTrace(); |
| if (trace == null) { |
| return; |
| } |
| long selectedTime = fTimeProvider.getSelectionEnd(); |
| for (ILinkEvent link : fItemData.fLinks) { |
| if (link.getEntry() == trace && link.getTime() == selectedTime) { |
| selectItem(link.getDestinationEntry(), false); |
| if (link.getDuration() != 0) { |
| if (extend) { |
| fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime() + link.getDuration(), true); |
| } else { |
| fTimeProvider.setSelectedTimeNotify(link.getTime() + link.getDuration(), true); |
| } |
| // Notify if visible time window has been adjusted |
| fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1()); |
| } |
| fireSelectionChanged(); |
| updateStatusLine(STATUS_WITHOUT_CURSOR_TIME); |
| return; |
| } |
| } |
| selectNextEvent(extend); |
| } |
| |
| /** |
| * Follow the arrow backward |
| * |
| * @param extend |
| * true to extend selection range, false for single selection |
| * @since 1.0 |
| */ |
| public void followArrowBwd(boolean extend) { |
| ITimeGraphEntry trace = getSelectedTrace(); |
| if (trace == null) { |
| return; |
| } |
| long selectedTime = fTimeProvider.getSelectionEnd(); |
| for (ILinkEvent link : fItemData.fLinks) { |
| if (link.getDestinationEntry() == trace && link.getTime() + link.getDuration() == selectedTime) { |
| selectItem(link.getEntry(), false); |
| if (link.getDuration() != 0) { |
| if (extend) { |
| fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime(), true); |
| } else { |
| fTimeProvider.setSelectedTimeNotify(link.getTime(), true); |
| } |
| // Notify if visible time window has been adjusted |
| fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1()); |
| } |
| fireSelectionChanged(); |
| updateStatusLine(STATUS_WITHOUT_CURSOR_TIME); |
| return; |
| } |
| } |
| selectPrevEvent(extend); |
| } |
| |
| /** |
| * 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(); |
| 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. |
| */ |
| protected int getItemIndexAtY(int y) { |
| int ySum = 0; |
| if (y < 0) { |
| for (int idx = fTopIndex - 1; idx >= 0; idx--) { |
| ySum -= fItemData.fExpandedItems[idx].fItemHeight; |
| if (y >= ySum) { |
| return idx; |
| } |
| } |
| } else { |
| for (int idx = fTopIndex; idx < fItemData.fExpandedItems.length; idx++) { |
| ySum += fItemData.fExpandedItems[idx].fItemHeight; |
| if (y < ySum) { |
| return idx; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| boolean isOverSplitLine(int x) { |
| if (x < 0 || null == fTimeProvider) { |
| return false; |
| } |
| int nameWidth = fTimeProvider.getNameSpace(); |
| return Math.abs(x - nameWidth) <= SNAP_WIDTH; |
| } |
| |
| boolean isOverTimeSpace(int x, int y) { |
| Point size = getSize(); |
| return x >= fTimeProvider.getNameSpace() && x < size.x && y >= 0 && y < size.y; |
| } |
| |
| /** |
| * Gets the {@link ITimeGraphEntry} at the given location. |
| * |
| * @param pt |
| * a point in the widget |
| * @return the {@link ITimeGraphEntry} at this point, or <code>null</code> if |
| * none. |
| * @since 2.0 |
| */ |
| public 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. |
| */ |
| protected ILinkEvent getArrow(Point pt) { |
| if (fHideArrows) { |
| return null; |
| } |
| ILinkEvent linkEvent = null; |
| double minDistance = Double.MAX_VALUE; |
| Rectangle clientArea = getClientArea(); |
| for (ILinkEvent event : fItemData.fLinks) { |
| Rectangle rect = getArrowRectangle(clientArea, 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; |
| } |
| |
| @Override |
| public int getXForTime(long time) { |
| if (null == fTimeProvider) { |
| return -1; |
| } |
| long time0 = fTimeProvider.getTime0(); |
| long time1 = fTimeProvider.getTime1(); |
| int width = getSize().x; |
| int nameSpace = fTimeProvider.getNameSpace(); |
| double pixelsPerNanoSec = (width - nameSpace <= RIGHT_MARGIN) ? 0 : (double) (width - nameSpace - RIGHT_MARGIN) / (time1 - time0); |
| int x = SaturatedArithmetic.add(getBounds().x + nameSpace, (int) ((time - time0) * pixelsPerNanoSec)); |
| return x; |
| } |
| |
| @Override |
| public long getTimeAtX(int coord) { |
| if (null == fTimeProvider) { |
| return -1; |
| } |
| long hitTime = -1; |
| Point size = getSize(); |
| 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 closest nanosecond to |
| // this pixel's start time |
| hitTime = time0 + Math.round((time1 - time0) * ((double) x / timeWidth)); |
| } |
| } |
| return hitTime; |
| } |
| |
| void selectItem(int idx, boolean addSelection) { |
| selectItem(idx, addSelection, true); |
| } |
| |
| void selectItem(int idx, boolean addSelection, boolean reveal) { |
| boolean changed = false; |
| if (addSelection) { |
| if (idx >= 0 && idx < fItemData.fExpandedItems.length) { |
| Item 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; |
| } |
| } |
| if (reveal) { |
| changed |= ensureVisibleItem(idx, true); |
| } |
| if (changed) { |
| redraw(); |
| } |
| } |
| |
| /** |
| * Select an entry and make it visible |
| * |
| * @param entry |
| * The entry to select |
| * @param addSelection |
| * <code>true</code> to add the entry to the current selection, or |
| * <code>false</code> to set a new selection |
| */ |
| public void selectItem(ITimeGraphEntry entry, boolean addSelection) { |
| int idx = fItemData.findItemIndex(entry); |
| selectItem(idx, addSelection); |
| } |
| |
| /** |
| * Select an entry |
| * |
| * @param entry |
| * The entry to select |
| * @param addSelection |
| * <code>true</code> to add the entry to the current selection, or |
| * <code>false</code> to set a new selection |
| * @param reveal |
| * <code>true</code> if the selection is to be made visible, and |
| * <code>false</code> otherwise |
| * @since 2.3 |
| */ |
| public void selectItem(ITimeGraphEntry entry, boolean addSelection, boolean reveal) { |
| int idx = fItemData.findItemIndex(entry); |
| selectItem(idx, addSelection, reveal); |
| } |
| |
| /** |
| * Retrieve the number of entries shown per page. |
| * |
| * @return The count |
| */ |
| public int countPerPage() { |
| int height = getSize().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 (visible) items |
| * |
| * @return The count of expanded (visible) items |
| */ |
| public int getExpandedElementCount() { |
| return fItemData.fExpandedItems.length; |
| } |
| |
| /** |
| * Get an array of all expanded (visible) elements |
| * |
| * @return The expanded (visible) elements |
| */ |
| public ITimeGraphEntry[] getExpandedElements() { |
| ArrayList<ITimeGraphEntry> elements = new ArrayList<>(); |
| for (Item item : fItemData.fExpandedItems) { |
| elements.add(item.fEntry); |
| } |
| return elements.toArray(new ITimeGraphEntry[0]); |
| } |
| |
| /** |
| * Get the expanded (visible) element at the specified index. |
| * |
| * @param index |
| * the element index |
| * @return The expanded (visible) element or null if out of range |
| * @since 2.0 |
| */ |
| public ITimeGraphEntry getExpandedElement(int index) { |
| if (index < 0 || index >= fItemData.fExpandedItems.length) { |
| return null; |
| } |
| return fItemData.fExpandedItems[index].fEntry; |
| } |
| |
| /** |
| * Get the bounds of the specified entry relative to its parent time graph. |
| * |
| * @param entry |
| * the time graph entry |
| * @return the bounds of the entry, or null if the entry is not visible |
| * @since 2.3 |
| */ |
| public Rectangle getItemBounds(ITimeGraphEntry entry) { |
| int idx = fItemData.findItemIndex(entry); |
| if (idx >= 0) { |
| return getItemRect(getClientArea(), idx); |
| } |
| return null; |
| } |
| |
| Rectangle getNameRect(Rectangle bounds, int idx, int nameWidth) { |
| Rectangle rect = getItemRect(bounds, idx); |
| rect.width = nameWidth; |
| return rect; |
| } |
| |
| Rectangle getStatesRect(Rectangle bounds, int idx, int nameWidth) { |
| Rectangle rect = getItemRect(bounds, idx); |
| rect.x += nameWidth; |
| rect.width -= nameWidth; |
| return rect; |
| } |
| |
| Rectangle getItemRect(Rectangle bounds, int idx) { |
| int ySum = 0; |
| if (idx >= fTopIndex) { |
| for (int i = fTopIndex; i < idx; i++) { |
| ySum += fItemData.fExpandedItems[i].fItemHeight; |
| } |
| } else { |
| for (int i = fTopIndex - 1; i >= idx; i--) { |
| ySum -= fItemData.fExpandedItems[i].fItemHeight; |
| } |
| } |
| int y = bounds.y + ySum; |
| int height = fItemData.fExpandedItems[idx].fItemHeight; |
| return new Rectangle(bounds.x, y, bounds.width, height); |
| } |
| |
| @Override |
| void paint(Rectangle bounds, PaintEvent e) { |
| GC gc = e.gc; |
| |
| if (bounds.width < 2 || bounds.height < 2 || null == fTimeProvider) { |
| return; |
| } |
| |
| fIdealNameSpace = 0; |
| int nameSpace = fTimeProvider.getNameSpace(); |
| |
| // draw the background layer |
| drawBackground(bounds, nameSpace, gc); |
| |
| // draw the grid lines |
| drawGridLines(bounds, gc); |
| |
| // draw the background markers |
| drawMarkers(bounds, fTimeProvider, fMarkers, false, nameSpace, gc); |
| |
| // draw the items |
| drawItems(bounds, fTimeProvider, fItemData.fExpandedItems, fTopIndex, nameSpace, gc); |
| |
| // draw the foreground markers |
| drawMarkers(bounds, fTimeProvider, fMarkers, true, nameSpace, gc); |
| |
| // draw the links (arrows) |
| drawLinks(bounds, fTimeProvider, fItemData.fLinks, nameSpace, gc); |
| |
| fTimeGraphProvider.postDrawControl(bounds, gc); |
| |
| gc.setAlpha(OPAQUE * 2 / 5); |
| |
| 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 = SaturatedArithmetic.add(bounds.x + nameSpace, (int) ((selectionBegin - time0) * pixelsPerNanoSec)); |
| int x1 = SaturatedArithmetic.add(bounds.x + nameSpace, (int) ((selectionEnd - time0) * pixelsPerNanoSec)); |
| |
| // draw selection lines |
| if (fDragState != DRAG_SELECTION) { |
| 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 split line |
| if (DRAG_SPLIT_LINE == fDragState || |
| (DRAG_NONE == fDragState && fMouseOverSplitLine && fTimeProvider.getNameSpace() > 0)) { |
| gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.DARK_GRAY)); |
| } else { |
| gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.GRAY)); |
| } |
| gc.fillRectangle(bounds.x + nameSpace - SNAP_WIDTH, bounds.y, SNAP_WIDTH, bounds.height); |
| |
| if (DRAG_ZOOM == fDragState && Math.max(fDragX, fDragX0) > nameSpace) { |
| gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.TOOL_FOREGROUND)); |
| gc.drawLine(fDragX0, bounds.y, fDragX0, bounds.y + bounds.height - 1); |
| if (fDragX != fDragX0) { |
| 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); |
| } |
| } |
| |
| gc.setAlpha(OPAQUE); |
| } |
| |
| /** |
| * Draw the background layer. Fills the background of the control's name space |
| * and states space, updates the background of items if necessary, and draws the |
| * item's name text and middle line. |
| * |
| * @param bounds |
| * The bounds of the control |
| * @param nameSpace |
| * The name space width |
| * @param gc |
| * Graphics context |
| * @since 2.0 |
| */ |
| protected void drawBackground(Rectangle bounds, int nameSpace, GC gc) { |
| // draw empty name space background |
| gc.setBackground(getColorScheme().getBkColor(false, false, true)); |
| drawBackground(gc, bounds.x, bounds.y, nameSpace, bounds.height); |
| |
| // draw empty states space background |
| gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.BACKGROUND)); |
| drawBackground(gc, bounds.x + nameSpace, bounds.y, bounds.width - nameSpace, bounds.height); |
| |
| for (int i = fTopIndex; i < fItemData.fExpandedItems.length; i++) { |
| if (fItemData.fExpandedItems[i].fItemHeight == 0) { |
| continue; |
| } |
| Rectangle itemRect = getItemRect(bounds, i); |
| if (itemRect.y >= bounds.y + bounds.height) { |
| break; |
| } |
| Item item = fItemData.fExpandedItems[i]; |
| // draw the background of selected item and items with no time |
| // events |
| if (!item.fEntry.hasTimeEvents()) { |
| gc.setBackground(getColorScheme().getBkColorGroup(item.fSelected, fIsInFocus)); |
| gc.fillRectangle(itemRect); |
| } else if (item.fSelected) { |
| gc.setBackground(getColorScheme().getBkColor(true, fIsInFocus, true)); |
| gc.fillRectangle(itemRect.x, itemRect.y, nameSpace, itemRect.height); |
| gc.setBackground(getColorScheme().getBkColor(true, fIsInFocus, false)); |
| gc.fillRectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height); |
| } |
| // draw the name space |
| Rectangle nameRect = new Rectangle(itemRect.x, itemRect.y, nameSpace, itemRect.height); |
| drawName(item, nameRect, gc); |
| if (fMidLinesVisible && item.fEntry.hasTimeEvents() && item.fItemHeight > MIN_MIDLINE_HEIGHT && !(item.fEntry instanceof TimeGraphLineEntry)) { |
| Rectangle rect = new Rectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height); |
| drawMidLine(rect, gc); |
| } |
| } |
| } |
| |
| /** |
| * Draw the grid lines |
| * |
| * @param bounds |
| * The bounds of the control |
| * @param gc |
| * Graphics context |
| * @since 2.0 |
| */ |
| public void drawGridLines(Rectangle bounds, GC gc) { |
| if (!fGridLinesVisible) { |
| return; |
| } |
| gc.setForeground(fGridLineColor); |
| gc.setAlpha(fGridLineColor.getAlpha()); |
| for (int x : fTimeGraphScale.getTickList()) { |
| gc.drawLine(x, bounds.y, x, bounds.y + bounds.height); |
| } |
| gc.setAlpha(OPAQUE); |
| } |
| |
| /** |
| * Draw the markers |
| * |
| * @param bounds |
| * The rectangle of the area |
| * @param timeProvider |
| * The time provider |
| * @param markers |
| * The list of markers |
| * @param foreground |
| * true to draw the foreground markers, false otherwise |
| * @param nameSpace |
| * The width reserved for the names |
| * @param gc |
| * Reference to the SWT GC object |
| * @since 2.0 |
| */ |
| protected void drawMarkers(Rectangle bounds, ITimeDataProvider timeProvider, List<IMarkerEvent> markers, boolean foreground, int nameSpace, GC gc) { |
| if (!fMarkersVisible || markers == null || markers.isEmpty()) { |
| return; |
| } |
| gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height)); |
| /* the list can grow concurrently but cannot shrink */ |
| for (int i = 0; i < markers.size(); i++) { |
| IMarkerEvent marker = markers.get(i); |
| if (marker.isForeground() == foreground) { |
| drawMarker(marker, bounds, timeProvider, nameSpace, gc); |
| } |
| } |
| gc.setClipping((Rectangle) null); |
| } |
| |
| /** |
| * Draw a single marker |
| * |
| * @param marker |
| * The marker event |
| * @param bounds |
| * The bounds of the control |
| * @param timeProvider |
| * The time provider |
| * @param nameSpace |
| * The width reserved for the name |
| * @param gc |
| * Reference to the SWT GC object |
| * @since 2.0 |
| */ |
| protected void drawMarker(IMarkerEvent marker, Rectangle bounds, ITimeDataProvider timeProvider, int nameSpace, GC gc) { |
| Rectangle rect = Utils.clone(bounds); |
| if (marker.getEntry() != null) { |
| int index = fItemData.findItemIndex(marker.getEntry()); |
| if (index == -1) { |
| return; |
| } |
| rect = getStatesRect(bounds, index, nameSpace); |
| if (rect.y < 0 || rect.y > bounds.height) { |
| return; |
| } |
| } |
| int x0 = getXForTime(marker.getTime()); |
| int x1 = getXForTime(marker.getTime() + marker.getDuration()); |
| if (x0 > bounds.width || x1 < nameSpace) { |
| return; |
| } |
| rect.x = Math.max(nameSpace, Math.min(bounds.width, x0)); |
| rect.width = Math.max(1, Math.min(bounds.width, x1) - rect.x); |
| |
| StyleManager styleManager = getStyleManager(); |
| OutputElementStyle elementStyle = getElementStyle(marker); |
| if (elementStyle == null) { |
| return; |
| } |
| |
| RGBAColor rgba = styleManager.getColorStyle(elementStyle, StyleProperties.COLOR); |
| int colorInt = (rgba != null) ? rgba.toInt() : RGBAUtil.fromRGBA(marker.getColor()); |
| Color color = getColor(colorInt); |
| Object symbolType = styleManager.getStyle(elementStyle, StyleProperties.SYMBOL_TYPE); |
| if (symbolType != null && symbolType != SymbolType.NONE) { |
| gc.setAlpha(colorInt & 0xff); |
| Float heightFactor = styleManager.getFactorStyle(elementStyle, StyleProperties.HEIGHT); |
| heightFactor = (heightFactor != null) ? Math.max(0.0f, Math.min(1.0f, heightFactor)) : 1.0f; |
| int symbolSize = (int) Math.ceil(rect.height * heightFactor); |
| switch (String.valueOf(symbolType)) { |
| case SymbolType.CROSS: |
| SymbolHelper.drawCross(gc, color, symbolSize, rect.x, rect.y + rect.height / 2); |
| break; |
| case SymbolType.PLUS: |
| SymbolHelper.drawPlus(gc, color, symbolSize, rect.x, rect.y + rect.height / 2); |
| break; |
| case SymbolType.SQUARE: |
| SymbolHelper.drawSquare(gc, color, symbolSize, rect.x, rect.y + rect.height / 2); |
| break; |
| case SymbolType.TRIANGLE: |
| SymbolHelper.drawTriangle(gc, color, symbolSize, rect.x, rect.y + rect.height / 2); |
| break; |
| case SymbolType.INVERTED_TRIANGLE: |
| SymbolHelper.drawInvertedTriangle(gc, color, symbolSize, rect.x, rect.y + rect.height / 2); |
| break; |
| case SymbolType.CIRCLE: |
| SymbolHelper.drawCircle(gc, color, symbolSize, rect.x, rect.y + rect.height / 2); |
| break; |
| default: |
| SymbolHelper.drawDiamond(gc, color, symbolSize, rect.x, rect.y + rect.height / 2); |
| } |
| gc.setAlpha(OPAQUE); |
| if (marker.getDuration() == 0) { |
| return; |
| } |
| } |
| oldDrawMarker(marker, gc, rect); |
| } |
| |
| private void oldDrawMarker(IMarkerEvent marker, GC gc, Rectangle rect) { |
| Color color = getColorScheme().getColor(marker.getColor()); |
| gc.setBackground(color); |
| gc.setAlpha(color.getAlpha()); |
| gc.fillRectangle(rect); |
| gc.setAlpha(OPAQUE); |
| String label = marker.getLabel(); |
| if (fLabelsVisible && label != null && marker.getEntry() != null) { |
| label = label.substring(0, Math.min(label.indexOf('\n') != -1 ? label.indexOf('\n') : label.length(), MAX_LABEL_LENGTH)); |
| gc.setForeground(color); |
| Utils.drawText(gc, label, rect.x - gc.textExtent(label).x, rect.y, true); |
| } |
| } |
| |
| /** |
| * Draw many items at once |
| * |
| * @param bounds |
| * The bounds of the control |
| * @param timeProvider |
| * The time provider |
| * @param items |
| * The array items to draw |
| * @param topIndex |
| * The index of the first element to draw |
| * @param nameSpace |
| * The name space width |
| * @param gc |
| * Graphics context |
| */ |
| public void drawItems(Rectangle bounds, ITimeDataProvider timeProvider, |
| Item[] items, int topIndex, int nameSpace, GC gc) { |
| int bottomIndex = Integer.min(topIndex + countPerPage() + 1, items.length); |
| for (int i = topIndex; i < bottomIndex; i++) { |
| Item item = items[i]; |
| drawItem(item, bounds, timeProvider, i, nameSpace, gc); |
| } |
| } |
| |
| /** |
| * Draws the item |
| * |
| * @param item |
| * The item to draw |
| * @param bounds |
| * The bounds of the control |
| * @param timeProvider |
| * The time provider |
| * @param i |
| * The expanded item index |
| * @param nameSpace |
| * The name space width |
| * @param gc |
| * Graphics context |
| */ |
| protected void drawItem(Item item, Rectangle bounds, ITimeDataProvider timeProvider, int i, int nameSpace, GC gc) { |
| if (fItemData.fExpandedItems[i].fItemHeight == 0) { |
| return; |
| } |
| Rectangle itemRect = getItemRect(bounds, i); |
| if (itemRect.y >= bounds.y + bounds.height) { |
| return; |
| } |
| |
| ITimeGraphEntry entry = item.fEntry; |
| long time0 = timeProvider.getTime0(); |
| long time1 = timeProvider.getTime1(); |
| long selectedTime = fTimeProvider.getSelectionEnd(); |
| |
| Rectangle rect = new Rectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height); |
| if (rect.isEmpty() || (time1 <= time0)) { |
| fTimeGraphProvider.postDrawEntry(entry, rect, gc); |
| return; |
| } |
| |
| boolean selected = item.fSelected; |
| // K pixels per second |
| double pixelsPerNanoSec = (rect.width <= RIGHT_MARGIN) ? 0 : (double) (rect.width - RIGHT_MARGIN) / (time1 - time0); |
| |
| if (entry.hasTimeEvents() && !(isFilterActive() && hasSavedFilters() && !((TimeGraphEntry) item.fEntry).hasZoomedEvents())) { |
| gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height)); |
| fillSpace(rect, gc, selected); |
| |
| int margins = getMarginForHeight(rect.height); |
| int height = rect.height - margins; |
| int topMargin = (margins + 1) / 2; |
| Rectangle stateRect = new Rectangle(rect.x, rect.y + topMargin, rect.width, height); |
| |
| /* Set the font for this item */ |
| setFontForHeight(height, gc); |
| |
| long maxDuration = (timeProvider.getTimeSpace() == 0) ? Long.MAX_VALUE : 1 * (time1 - time0) / timeProvider.getTimeSpace(); |
| Iterator<ITimeEvent> iterator = entry.getTimeEventsIterator(time0, time1, maxDuration); |
| switch (entry.getStyle()) { |
| case LINE: |
| drawLineGraphEntry(gc, time0, rect, pixelsPerNanoSec, iterator); |
| break; |
| case STATE: |
| drawTimeGraphEntry(gc, time0, selectedTime, rect, selected, pixelsPerNanoSec, stateRect, iterator); |
| break; |
| default: |
| break; |
| } |
| gc.setClipping((Rectangle) null); |
| } |
| fTimeGraphProvider.postDrawEntry(entry, rect, gc); |
| } |
| |
| private static class LongPoint { |
| final int x; |
| final long y; |
| |
| public LongPoint(int x, long y) { |
| this.x = x; |
| this.y = y; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(x, y); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof LongPoint) { |
| LongPoint longPoint = (LongPoint) obj; |
| return longPoint.x == x && longPoint.y == y; |
| } |
| return false; |
| } |
| } |
| |
| private void drawLineGraphEntry(GC gc, long time0, Rectangle rect, double pixelsPerNanoSec, Iterator<ITimeEvent> iterator) { |
| // clamp 0 - max positive |
| long max = Long.MIN_VALUE; |
| long min = 0; |
| List<List<LongPoint>> seriesModel = new ArrayList<>(); |
| TimeLineEvent lastValid = null; |
| while (iterator.hasNext()) { |
| ITimeEvent event = iterator.next(); |
| if (!(event instanceof TimeLineEvent)) { |
| continue; |
| } |
| int x = SaturatedArithmetic.add(rect.x, (int) ((event.getTime() - time0) * pixelsPerNanoSec)); |
| if (x >= rect.x + rect.width) { |
| // event is out of bounds |
| continue; |
| } |
| TimeLineEvent timeEvent = (TimeLineEvent) event; |
| List<Long> values = timeEvent.getValues(); |
| for (int i = 0; i < values.size(); i++) { |
| if (seriesModel.size() <= i) { |
| seriesModel.add(new ArrayList<>()); |
| } |
| Long val = values.get(i); |
| if (val != null) { |
| // get max and min, this is a relative scale. |
| max = Math.max(Math.abs(val), max); |
| min = 0; |
| lastValid = timeEvent; |
| seriesModel.get(i).add(new LongPoint(x, val)); |
| } |
| } |
| } |
| if (lastValid == null) { |
| return; |
| } |
| double scale = (max - min) == 0 ? 1.0 : (double) rect.height / (max - min); |
| |
| StyleManager styleManager = getStyleManager(); |
| OutputElementStyle elementStyle = getElementStyle(lastValid); |
| if (elementStyle == null) { |
| return; |
| } |
| |
| RGBAColor rgba = styleManager.getColorStyle(elementStyle, StyleProperties.COLOR); |
| int colorInt = (rgba != null) ? rgba.toInt() : OPAQUE; |
| Color color = getColor(colorInt); |
| for (int i = 0; i < seriesModel.size(); i++) { |
| Color prev = gc.getForeground(); |
| int prevAlpha = gc.getAlpha(); |
| gc.setAlpha(colorInt & 0xff); |
| gc.setForeground(color); |
| List<LongPoint> series = seriesModel.get(i); |
| int[] points = new int[series.size() * 2]; |
| for (int point = 0; point < series.size(); point++) { |
| LongPoint longPoint = series.get(point); |
| points[point * 2] = longPoint.x; |
| points[point * 2 + 1] = rect.height - (int) ((longPoint.y - min) * scale) + rect.y; |
| } |
| gc.drawPolyline(points); |
| gc.setForeground(prev); |
| gc.setAlpha(prevAlpha); |
| } |
| } |
| |
| private void drawTimeGraphEntry(GC gc, long time0, long selectedTime, Rectangle rect, boolean selected, double pixelsPerNanoSec, Rectangle stateRect, Iterator<ITimeEvent> iterator) { |
| int lastX = -1; |
| fLastTransparentX = -1; |
| while (iterator.hasNext()) { |
| ITimeEvent event = iterator.next(); |
| int x = SaturatedArithmetic.add(rect.x, (int) ((event.getTime() - time0) * pixelsPerNanoSec)); |
| int xEnd = SaturatedArithmetic.add(rect.x, (int) ((event.getTime() + event.getDuration() - time0) * pixelsPerNanoSec)); |
| if (x >= rect.x + rect.width || xEnd < rect.x) { |
| // event is out of bounds |
| continue; |
| } |
| xEnd = Math.min(rect.x + rect.width, xEnd); |
| stateRect.x = Math.max(rect.x, x); |
| stateRect.width = Math.max(1, xEnd - stateRect.x + 1); |
| if (stateRect.x < lastX) { |
| stateRect.width -= (lastX - stateRect.x); |
| if (stateRect.width > 0) { |
| stateRect.x = lastX; |
| } else { |
| stateRect.width = 0; |
| } |
| } |
| if (drawState(getColorScheme(), event, stateRect, gc, selected, selectedTime >= event.getTime() && selectedTime < event.getTime() + event.getDuration())) { |
| lastX = stateRect.x + stateRect.width; |
| } |
| } |
| } |
| |
| /** |
| * Draw the links |
| * |
| * @param bounds |
| * The bounds of the control |
| * @param timeProvider |
| * The time provider |
| * @param links |
| * The list of link events |
| * @param nameSpace |
| * The name space width |
| * @param gc |
| * Graphics context |
| */ |
| public void drawLinks(Rectangle bounds, ITimeDataProvider timeProvider, |
| List<ILinkEvent> links, int nameSpace, GC gc) { |
| if (fHideArrows) { |
| return; |
| } |
| gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height)); |
| /* the list can grow concurrently but cannot shrink */ |
| for (int i = 0; i < links.size(); i++) { |
| drawLink(links.get(i), bounds, timeProvider, nameSpace, gc); |
| } |
| gc.setClipping((Rectangle) null); |
| } |
| |
| /** |
| * Draws a link type event |
| * |
| * @param event |
| * The link event to draw |
| * @param bounds |
| * The bounds of the control |
| * @param timeProvider |
| * The time provider |
| * @param nameSpace |
| * The name space width |
| * @param gc |
| * Graphics context |
| */ |
| protected void drawLink(ILinkEvent event, Rectangle bounds, ITimeDataProvider timeProvider, int nameSpace, GC gc) { |
| drawArrow(getColorScheme(), event, getArrowRectangle(bounds, event), gc); |
| } |
| |
| private @Nullable 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 LineClipper.clip(bounds, x0, y0, x1, y1); |
| } |
| |
| /** |
| * Draw an arrow |
| * |
| * @param colors |
| * Color scheme |
| * @param event |
| * Time event for which we're drawing the arrow |
| * @param rect |
| * The arrow rectangle |
| * @param gc |
| * Graphics context |
| * @return true if the arrow was drawn |
| */ |
| protected boolean drawArrow(TimeGraphColorScheme colors, ITimeEvent event, |
| Rectangle rect, GC gc) { |
| |
| if (rect == null || ((rect.height == 0) && (rect.width == 0))) { |
| return false; |
| } |
| StyleManager styleManager = getStyleManager(); |
| OutputElementStyle elementStyle = getElementStyle(event); |
| if (elementStyle == null) { |
| return false; |
| } |
| |
| RGBAColor rgba = styleManager.getColorStyle(elementStyle, StyleProperties.COLOR); |
| int colorInt = (rgba != null) ? rgba.toInt() : OPAQUE; |
| Color color = getColor(colorInt); |
| int alpha = colorInt & 0xff; |
| int prevAlpha = gc.getAlpha(); |
| gc.setAlpha(alpha); |
| |
| gc.setForeground(color); |
| gc.setBackground(color); |
| int old = gc.getLineWidth(); |
| Float heightFactor = styleManager.getFactorStyle(elementStyle, StyleProperties.HEIGHT); |
| heightFactor = (heightFactor != null) ? Math.max(0.0f, Math.min(1.0f, heightFactor)) * 10.0f : 1.0f; |
| gc.setLineWidth(heightFactor.intValue()); |
| /* Draw the arrow */ |
| Point newEndpoint = drawArrowHead(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, heightFactor, gc); |
| gc.drawLine(rect.x, rect.y, newEndpoint.x, newEndpoint.y); |
| gc.setLineWidth(old); |
| gc.setAlpha(prevAlpha); |
| if (!Boolean.TRUE.equals(styleManager.getStyle(elementStyle, ITimeEventStyleStrings.annotated()))) { |
| fTimeGraphProvider.postDrawEvent(event, rect, gc); |
| } |
| return true; |
| } |
| |
| /* |
| * @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 Point drawArrowHead(int x0, int y0, int x1, int y1, float scale, GC gc) { |
| double factor = 10 + 2.0 * scale; |
| 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); |
| /* |
| * The returned point corresponds to the point at 1/4 from the head basis basis |
| * to the tip. it will be used as end point the arrow line |
| */ |
| return new Point((3 * ((end1X + end2X) / 2) + x1) / 4, (3 * ((end1Y + end2Y) / 2) + y1) / 4); |
| } |
| |
| /** |
| * Draw the name space of an item. |
| * |
| * @param item |
| * Item object |
| * @param bounds |
| * The bounds of the item's name space |
| * @param gc |
| * Graphics context |
| */ |
| protected void drawName(Item item, Rectangle bounds, GC gc) { |
| // No name space to be drawn |
| if (fTimeProvider.getNameSpace() == 0) { |
| return; |
| } |
| |
| boolean hasTimeEvents = item.fEntry.hasTimeEvents(); |
| if (hasTimeEvents) { |
| gc.setClipping(bounds); |
| } |
| |
| int height = bounds.height - getMarginForHeight(bounds.height); |
| setFontForHeight(height, gc); |
| |
| String name = fLabelProvider == null ? item.fName : fLabelProvider.getColumnText(item.fEntry, 0); |
| Rectangle rect = Utils.clone(bounds); |
| rect.y += (bounds.height - gc.stringExtent(name).y) / 2; |
| Tree tree = getTree(); |
| TreeColumn[] columns = tree.getColumns(); |
| int idealNameSpace = 0; |
| for (int i = 0; i < columns.length; i++) { |
| int columnIndex = tree.getColumnOrder()[i]; |
| TreeColumn column = columns[columnIndex]; |
| rect.width = column.getWidth(); |
| gc.setClipping(rect.x, bounds.y, Math.min(rect.width, bounds.x + bounds.width - rect.x - SNAP_WIDTH), bounds.height); |
| int width = MARGIN; |
| if (i == 0) { |
| // first visible column |
| width += item.fLevel * EXPAND_SIZE; |
| if (item.fHasChildren) { |
| // draw expand/collapse arrow |
| gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.DARK_GRAY)); |
| int arrowHeightHint = (height < 4) ? height : (height < 6) ? height - 1 : height - 2; |
| int arrowHalfHeight = Math.max(1, Math.min(arrowHeightHint, (int) Math.round((EXPAND_SIZE - 2) / ARROW_RATIO))) / 2; |
| int arrowHalfWidth = (Math.max(1, Math.min(EXPAND_SIZE - 2, (int) Math.round(arrowHeightHint * ARROW_RATIO))) + 1) / 2; |
| int x1 = bounds.x + width + 1; |
| int x2 = x1 + 2 * arrowHalfWidth; |
| int midy = bounds.y + bounds.height / 2; |
| int y1 = midy - arrowHalfHeight; |
| int y2 = midy + arrowHalfHeight; |
| if (!item.fExpanded) { // > |
| gc.fillPolygon(new int[] { x1, y1, x2, midy, x1, y2 }); |
| } else { // v |
| int midx = x1 + arrowHalfWidth; |
| gc.fillPolygon(new int[] { x1, y1, x2, y1, midx, y2 }); |
| } |
| } |
| width += EXPAND_SIZE + MARGIN; |
| |
| Image img = fLabelProvider != null ? fLabelProvider.getColumnImage(item.fEntry, columnIndex) |
| : columnIndex == 0 ? fTimeGraphProvider.getItemImage(item.fEntry) : null; |
| if (img != null) { |
| // draw icon |
| int imgHeight = img.getImageData().height; |
| int imgWidth = img.getImageData().width; |
| int dstHeight = Math.min(bounds.height, imgHeight); |
| int dstWidth = dstHeight * imgWidth / imgHeight; |
| int x = width; |
| int y = bounds.y + (bounds.height - dstHeight) / 2; |
| gc.drawImage(img, 0, 0, imgWidth, imgHeight, x, y, dstWidth, dstHeight); |
| width += imgWidth + MARGIN; |
| } |
| } else { |
| if (fLabelProvider == null) { |
| break; |
| } |
| } |
| String label = fLabelProvider != null ? fLabelProvider.getColumnText(item.fEntry, columnIndex) |
| : columnIndex == 0 ? item.fName : ""; //$NON-NLS-1$ |
| gc.setForeground(getColorScheme().getFgColor(item.fSelected, fIsInFocus)); |
| Rectangle textRect = new Rectangle(rect.x + width, rect.y, rect.width - width, rect.height); |
| int textWidth = Utils.drawText(gc, label, textRect, true); |
| width += textWidth + MARGIN; |
| if (textWidth > 0) { |
| idealNameSpace = rect.x + width; |
| } |
| if (fMidLinesVisible && item.fEntry.hasTimeEvents() && columns.length == 1 && item.fItemHeight > MIN_MIDLINE_HEIGHT) { |
| drawMidLine(new Rectangle(bounds.x + width, bounds.y, bounds.x + bounds.width, bounds.height), gc); |
| } |
| if (fAutoResizeColumns && width > column.getWidth()) { |
| column.setData(PREFERRED_WIDTH, width); |
| column.setWidth(width); |
| } |
| gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.MID_LINE)); |
| if (i < columns.length - 1) { |
| // not the last visible column: draw the vertical cell border |
| int x = rect.x + rect.width - 1; |
| gc.drawLine(x, bounds.y, x, bounds.y + bounds.height); |
| } |
| rect.x += rect.width; |
| } |
| fIdealNameSpace = Math.max(fIdealNameSpace, idealNameSpace); |
| |
| gc.setClipping((Rectangle) null); |
| } |
| |
| /** |
| * Draw the state (color fill) |
| * |
| * @param colors |
| * Color scheme |
| * @param event |
| * Time event for which we're drawing the state |
| * @param rect |
| * The state rectangle |
| * @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 |
| */ |
| protected boolean drawState(TimeGraphColorScheme colors, ITimeEvent event, |
| Rectangle rect, GC gc, boolean selected, boolean timeSelected) { |
| |
| StyleManager styleManager = getStyleManager(); |
| OutputElementStyle elementStyle = getElementStyle(event); |
| if (elementStyle == null) { |
| return false; |
| } |
| boolean transparent = elementStyle.getParentKey() == null && elementStyle.getStyleValues().isEmpty(); |
| boolean visible = rect.width <= 0 ? false : true; |
| rect.width = Math.max(1, rect.width); |
| Color black = Display.getDefault().getSystemColor(SWT.COLOR_BLACK); |
| gc.setForeground(black); |
| Float heightFactor = styleManager.getFactorStyle(elementStyle, StyleProperties.HEIGHT); |
| heightFactor = (heightFactor != null) ? Math.max(0.0f, Math.min(1.0f, heightFactor)) : DEFAULT_STATE_WIDTH; |
| int height = 0; |
| if (heightFactor != 0.0f && rect.height != 0) { |
| height = Math.max(1, (int) (rect.height * heightFactor)); |
| } |
| Rectangle drawRect = new Rectangle(rect.x, rect.y + ((rect.height - height) / 2), rect.width, height); |
| |
| if (transparent) { |
| if (visible) { |
| // Avoid overlapping transparent states |
| int x = Math.max(fLastTransparentX, drawRect.x); |
| int width = drawRect.x + drawRect.width - x; |
| if (width > 0) { |
| // Draw transparent background |
| drawRect.x = x; |
| drawRect.width = width; |
| gc.setAlpha(OPAQUE / 4); |
| gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_GRAY)); |
| gc.fillRectangle(drawRect); |
| fLastTransparentX = Math.max(fLastTransparentX, drawRect.x + drawRect.width); |
| gc.setAlpha(OPAQUE); |
| } else { |
| drawRect.width = 0; |
| } |
| if (drawRect.width <= 2) { |
| // Draw point over state |
| gc.drawPoint(rect.x, rect.y - 2); |
| if (drawRect.width == 2) { |
| gc.drawPoint(rect.x + 1, rect.y - 2); |
| } |
| } else { |
| // Draw the top and bottom borders |
| gc.drawLine(drawRect.x, drawRect.y, drawRect.x + drawRect.width - 1, drawRect.y); |
| gc.drawLine(drawRect.x, drawRect.y + drawRect.height - 1, drawRect.x + drawRect.width - 1, drawRect.y + drawRect.height - 1); |
| } |
| } else { |
| gc.drawPoint(rect.x, rect.y - 2); |
| } |
| fTimeGraphProvider.postDrawEvent(event, drawRect, gc); |
| return false; |
| } |
| |
| int arc = Math.min(drawRect.height + 1, drawRect.width) / 2; |
| RGBAColor rgba = styleManager.getColorStyle(elementStyle, StyleProperties.BACKGROUND_COLOR); |
| int colorInt = (rgba != null) ? rgba.toInt() : OPAQUE; |
| Color color = getColor(colorInt); |
| int alpha = colorInt & 0xff; |
| boolean reallySelected = timeSelected && selected; |
| // fill all rect area |
| gc.setBackground(color); |
| boolean draw = visible || fBlendSubPixelEvents; |
| int old = gc.getAlpha(); |
| if (draw) { |
| gc.setAlpha(visible ? alpha : alpha / 2); |
| if (arc >= 1) { |
| gc.fillRoundRectangle(drawRect.x, drawRect.y, drawRect.width, drawRect.height, arc, arc); |
| } else { |
| gc.fillRectangle(drawRect); |
| } |
| } |
| |
| gc.setAlpha(OPAQUE); |
| |
| //draw border line |
| Object borderStyle = styleManager.getStyle(elementStyle, StyleProperties.BORDER_STYLE); |
| if (borderStyle != null && !BorderStyle.NONE.equals(borderStyle)) { |
| rgba = styleManager.getColorStyle(elementStyle, StyleProperties.BORDER_COLOR); |
| int borderColorInt = (rgba != null) ? rgba.toInt() : OPAQUE; |
| int borderAlpha = borderColorInt & 0xff; |
| Color oldForeground = gc.getForeground(); |
| int oldLineWidth = gc.getLineWidth(); |
| int oldAlpha = gc.getAlpha(); |
| Color borderColor = getColor(borderColorInt); |
| gc.setForeground(borderColor); |
| gc.setAlpha(borderAlpha); |
| Object borderWidth = styleManager.getStyle(elementStyle, StyleProperties.BORDER_WIDTH); |
| gc.setLineWidth((borderWidth instanceof Integer) ? (int) borderWidth : 1); |
| gc.drawRoundRectangle(drawRect.x, drawRect.y, drawRect.width, drawRect.height , arc, arc); |
| gc.setForeground(oldForeground); |
| gc.setLineWidth(oldLineWidth); |
| gc.setAlpha(oldAlpha); |
| } |
| |
| if (reallySelected) { |
| gc.drawRoundRectangle(drawRect.x - 1, drawRect.y - 1, drawRect.width, drawRect.height + 1, arc, arc); |
| } |
| if (!visible) { |
| gc.drawPoint(rect.x, rect.y - 2); |
| } |
| if (visible && !Boolean.TRUE.equals(styleManager.getStyle(elementStyle, ITimeEventStyleStrings.annotated()))) { |
| String label = event.getLabel(); |
| if (fLabelsVisible && label != null && !label.isEmpty() && rect.width > rect.height) { |
| gc.setForeground(Utils.getDistinctColor(color.getRGB())); |
| Utils.drawText(gc, label, drawRect.x, drawRect.y, drawRect.width, drawRect.height, true, true); |
| gc.setForeground(black); |
| } |
| fTimeGraphProvider.postDrawEvent(event, drawRect, gc); |
| } |
| gc.setAlpha(old); |
| return visible && !event.isPropertyActive(IFilterProperty.DIMMED); |
| } |
| |
| private StyleManager getStyleManager() { |
| return (fTimeGraphProvider instanceof IStylePresentationProvider) ? |
| ((IStylePresentationProvider) fTimeGraphProvider).getStyleManager() : |
| StyleManager.empty(); |
| } |
| |
| private @Nullable OutputElementStyle getElementStyle(ITimeEvent event) { |
| OutputElementStyle elementStyle; |
| if (fTimeGraphProvider instanceof IStylePresentationProvider) { |
| IStylePresentationProvider provider = (IStylePresentationProvider) fTimeGraphProvider; |
| elementStyle = provider.getElementStyle(event); |
| if (elementStyle == null) { |
| return null; |
| } |
| if (elementStyle.getParentKey() == null && elementStyle.getStyleValues().isEmpty()) { |
| return elementStyle; |
| } |
| return new OutputElementStyle(elementStyle.getParentKey(), applyEventStyleProperties(new HashMap<>(elementStyle.getStyleValues()), event)); |
| } |
| if (!(event instanceof MarkerEvent)) { |
| int colorIdx = fTimeGraphProvider.getStateTableIndex(event); |
| if (colorIdx == ITimeGraphPresentationProvider.INVISIBLE) { |
| return null; |
| } |
| if (colorIdx == ITimeGraphPresentationProvider.TRANSPARENT) { |
| return new OutputElementStyle(null, new HashMap<>()); |
| } |
| } |
| @NonNull Map<@NonNull String, @NonNull Object> styleMap = StylePropertiesUtils.updateEventStyleProperties(fTimeGraphProvider.getEventStyle(event)); |
| return new OutputElementStyle(null, applyEventStyleProperties(styleMap, event)); |
| } |
| |
| private static @NonNull Map<@NonNull String, @NonNull Object> applyEventStyleProperties(@NonNull Map<@NonNull String, @NonNull Object> styleMap, ITimeEvent event) { |
| if (event.isPropertyActive(IFilterProperty.DIMMED)) { |
| float opacity = (float) styleMap.getOrDefault(StyleProperties.OPACITY, 1.0f); |
| styleMap.put(StyleProperties.OPACITY, opacity / DIMMED_ALPHA_COEFFICIENT); |
| styleMap.put(ITimeEventStyleStrings.annotated(), Boolean.TRUE); |
| } |
| if (event.isPropertyActive(IFilterProperty.BOUND)) { |
| styleMap.put(StyleProperties.BORDER_COLOR, HIGHLIGHTED_BOUND_COLOR); |
| styleMap.put(StyleProperties.BORDER_WIDTH, HIGHLIGHTED_BOUND_WIDTH); |
| styleMap.put(StyleProperties.BORDER_STYLE, BorderStyle.SOLID); |
| styleMap.put(ITimeEventStyleStrings.annotated(), Boolean.FALSE); |
| } |
| return styleMap; |
| } |
| |
| private static Color getColor(int colorInt) { |
| String hexRGB = Integer.toHexString(colorInt); |
| Color color = COLOR_REGISTRY.get(hexRGB); |
| if (color == null) { |
| COLOR_REGISTRY.put(hexRGB, RGBAUtil.fromInt(colorInt).rgb); |
| color = COLOR_REGISTRY.get(hexRGB); |
| } |
| return color; |
| } |
| |
| /** |
| * Fill an item's states rectangle |
| * |
| * @param rect |
| * The states rectangle |
| * @param gc |
| * Graphics context |
| * @param selected |
| * true if the item is selected |
| */ |
| protected void fillSpace(Rectangle rect, GC gc, boolean selected) { |
| /* Nothing to draw */ |
| } |
| |
| /** |
| * Draw a line at the middle height of a rectangle |
| * |
| * @param rect |
| * The rectangle |
| * @param gc |
| * Graphics context |
| */ |
| private void drawMidLine(Rectangle rect, GC gc) { |
| gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.MID_LINE)); |
| int midy = rect.y + rect.height / 2; |
| gc.drawLine(rect.x, midy, rect.x + rect.width, midy); |
| } |
| |
| private static int getMarginForHeight(int height) { |
| /* |
| * State rectangle is smaller than the item bounds when height is > 4. Don't use |
| * any margin if the height is below or equal that threshold. Use a maximum of 6 |
| * pixels for both margins, otherwise try to use 13 pixels for the state height, |
| * but with a minimum margin of 1. |
| */ |
| final int MARGIN_THRESHOLD = 4; |
| final int PREFERRED_HEIGHT = 13; |
| final int MIN_MARGIN = 1; |
| final int MAX_MARGIN = 6; |
| return height <= MARGIN_THRESHOLD ? 0 : Math.max(Math.min(height - PREFERRED_HEIGHT, MAX_MARGIN), MIN_MARGIN); |
| } |
| |
| private void setFontForHeight(int pixels, GC gc) { |
| /* convert font height from pixels to points */ |
| int height = Math.max(pixels * PPI / DPI, 1); |
| Font font = fFonts.computeIfAbsent(height, fontHeight -> { |
| FontData fontData = gc.getFont().getFontData()[0]; |
| fontData.setHeight(fontHeight); |
| return new Font(gc.getDevice(), fontData); |
| }); |
| gc.setFont(font); |
| } |
| |
| @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 { |
| // Skip invisible items |
| while (idx < fItemData.fExpandedItems.length - 1) { |
| idx++; |
| if (fItemData.fExpandedItems[idx].fItemHeight > 0) { |
| break; |
| } |
| } |
| } |
| } else if (SWT.ARROW_UP == e.keyCode) { |
| idx = getSelectedIndex(); |
| if (idx < 0) { |
| idx = 0; |
| } else { |
| // Skip invisible items |
| while (idx > 0) { |
| idx--; |
| if (fItemData.fExpandedItems[idx].fItemHeight > 0) { |
| break; |
| } |
| } |
| } |
| } else if (SWT.ARROW_LEFT == e.keyCode && fDragState == DRAG_NONE) { |
| boolean extend = (e.stateMask & SWT.SHIFT) != 0; |
| selectPrevEvent(extend); |
| } else if (SWT.ARROW_RIGHT == e.keyCode && fDragState == DRAG_NONE) { |
| boolean extend = (e.stateMask & SWT.SHIFT) != 0; |
| selectNextEvent(extend); |
| } 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; |
| } else if ((e.character == '+' || e.character == '=') && ((e.stateMask & SWT.CTRL) != 0)) { |
| fVerticalZoomAlignEntry = getVerticalZoomAlignSelection(); |
| verticalZoom(true); |
| if (fVerticalZoomAlignEntry != null) { |
| setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue()); |
| } |
| } else if (e.character == '-' && ((e.stateMask & SWT.CTRL) != 0)) { |
| fVerticalZoomAlignEntry = getVerticalZoomAlignSelection(); |
| verticalZoom(false); |
| if (fVerticalZoomAlignEntry != null) { |
| setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue()); |
| } |
| } else if (e.character == '0' && ((e.stateMask & SWT.CTRL) != 0)) { |
| fVerticalZoomAlignEntry = getVerticalZoomAlignSelection(); |
| resetVerticalZoom(); |
| if (fVerticalZoomAlignEntry != null) { |
| setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue()); |
| } |
| } else if ((e.character == '+' || e.character == '=') && ((e.stateMask & SWT.CTRL) == 0)) { |
| if (fHasNamespaceFocus) { |
| ITimeGraphEntry entry = getSelectedTrace(); |
| setExpandedState(entry, 0, true); |
| } |
| } else if (e.character == '-' && ((e.stateMask & SWT.CTRL) == 0)) { |
| if (fHasNamespaceFocus) { |
| ITimeGraphEntry entry = getSelectedTrace(); |
| if ((entry != null) && entry.hasChildren()) { |
| setExpandedState(entry, -1, false); |
| } |
| } |
| } else if ((e.character == '*') && ((e.stateMask & SWT.CTRL) == 0)) { |
| if (fHasNamespaceFocus) { |
| ITimeGraphEntry entry = getSelectedTrace(); |
| if ((entry != null) && entry.hasChildren()) { |
| setExpandedStateLevel(entry); |
| } |
| } |
| } |
| if (idx >= 0) { |
| selectItem(idx, false); |
| 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; |
| redraw(); |
| updateStatusLine(STATUS_WITHOUT_CURSOR_TIME); |
| } |
| |
| @Override |
| public void focusLost(FocusEvent e) { |
| fIsInFocus = false; |
| 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); |
| } |
| } |
| |
| /** |
| * Update the status line following a change of selection. |
| * |
| * @since 2.0 |
| */ |
| public void updateStatusLine() { |
| updateStatusLine(STATUS_WITHOUT_CURSOR_TIME); |
| } |
| |
| private void updateStatusLine(int x) { |
| // use the time provider of the time graph scale for the status line |
| ITimeDataProvider tdp = fTimeGraphScale.getTimeProvider(); |
| if (fStatusLineManager == null || null == tdp || |
| tdp.getTime0() == tdp.getTime1()) { |
| return; |
| } |
| |
| long cursorTime = -1; |
| long selectionBeginTime = 0; |
| long selectionEndTime = 0; |
| if ((x >= 0 || x == STATUS_WITHOUT_CURSOR_TIME) && fDragState == DRAG_NONE) { |
| if (x != STATUS_WITHOUT_CURSOR_TIME) { |
| long time = getTimeAtX(x); |
| if (time >= 0) { |
| if (tdp instanceof ITimeDataProviderConverter) { |
| time = ((ITimeDataProviderConverter) tdp).convertTime(time); |
| } |
| cursorTime = time; |
| } |
| } |
| selectionBeginTime = tdp.getSelectionBegin(); |
| selectionEndTime = tdp.getSelectionEnd(); |
| } else if (fDragState == DRAG_SELECTION || fDragState == DRAG_ZOOM) { |
| long time0 = fDragBeginMarker ? getTimeAtX(fDragX0) : fDragTime0; |
| long time = fDragBeginMarker ? fDragTime0 : getTimeAtX(fDragX); |
| if (tdp instanceof ITimeDataProviderConverter) { |
| time0 = ((ITimeDataProviderConverter) tdp).convertTime(time0); |
| time = ((ITimeDataProviderConverter) tdp).convertTime(time); |
| } |
| // Use the time of T2 to update the cursor time |
| cursorTime = time; |
| selectionBeginTime = time0; |
| selectionEndTime = time; |
| } |
| String message = buildStatusMessage(cursorTime, selectionBeginTime, selectionEndTime, tdp.getTimeFormat().convert(), Resolution.NANOSEC); |
| fStatusLineManager.setMessage(message); |
| } |
| |
| private static String buildStatusMessage(long cursorTime, long selectionBeginTime, long selectionEndTime, TimeFormat tf, Resolution res) { |
| StringBuilder message = new StringBuilder(); |
| |
| if (cursorTime >= 0) { |
| message.append(NLS.bind("T: {0}{1} ", //$NON-NLS-1$ |
| new Object[] { |
| tf == TimeFormat.CALENDAR ? FormatTimeUtils.formatDate(cursorTime) + ' ' : "", //$NON-NLS-1$ |
| FormatTimeUtils.formatTime(cursorTime, tf, res) |
| })); |
| } |
| |
| message.append(NLS.bind("T1: {0}{1}", //$NON-NLS-1$ |
| new Object[] { |
| tf == TimeFormat.CALENDAR ? FormatTimeUtils.formatDate(selectionBeginTime) + ' ' : "", //$NON-NLS-1$ |
| FormatTimeUtils.formatTime(selectionBeginTime, tf, res) |
| })); |
| |
| if (selectionBeginTime != selectionEndTime) { |
| message.append(NLS.bind(" T2: {0}{1} \u0394: {2}", //$NON-NLS-1$ |
| new Object[] { |
| tf == TimeFormat.CALENDAR ? FormatTimeUtils.formatDate(selectionEndTime) + ' ' : "", //$NON-NLS-1$ |
| FormatTimeUtils.formatTime(selectionEndTime, tf, res), |
| FormatTimeUtils.formatDelta(selectionEndTime - selectionBeginTime, tf, res) |
| })); |
| } |
| return message.toString(); |
| } |
| |
| @Override |
| public void mouseMove(MouseEvent e) { |
| if (null == fTimeProvider) { |
| return; |
| } |
| Point size = getSize(); |
| 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.setStartFinishTimeNotify(time0, time1); |
| setElementPosition(fDragEntry, e.y); |
| } |
| } else if (DRAG_SPLIT_LINE == fDragState) { |
| fDragX = e.x; |
| fTimeProvider.setNameSpace(e.x); |
| TmfSignalManager.dispatchSignal(new TmfTimeViewAlignmentSignal(this, getTimeViewAlignmentInfo())); |
| } else if (DRAG_SELECTION == fDragState) { |
| if (fDragBeginMarker) { |
| fDragX0 = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), size.x - RIGHT_MARGIN); |
| } else { |
| 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; |
| } |
| |
| if (e.x >= fTimeProvider.getNameSpace()) { |
| fHasNamespaceFocus = false; |
| } else { |
| fHasNamespaceFocus = true; |
| } |
| 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; |
| TmfSignalManager.dispatchSignal(new TmfTimeViewAlignmentSignal(this, getTimeViewAlignmentInfo())); |
| return; |
| } |
| int idx = getItemIndexAtY(e.y); |
| if (idx >= 0) { |
| selectItem(idx, false); |
| fireDefaultSelection(); |
| } |
| } |
| } |
| |
| @Override |
| public void mouseDown(MouseEvent e) { |
| if (fDragState != DRAG_NONE) { |
| return; |
| } |
| 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; |
| 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(); |
| int 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(); |
| } |
| } else if (3 == e.button && e.x < fTimeProvider.getNameSpace()) { |
| int idx = getItemIndexAtY(e.y); |
| selectItem(idx, false); |
| fireSelectionChanged(); |
| } |
| if (fTimeProvider == null || |
| fTimeProvider.getTime0() == fTimeProvider.getTime1() || |
| getSize().x - fTimeProvider.getNameSpace() <= 0) { |
| return; |
| } |
| if (1 == e.button && ((e.stateMask & SWT.MODIFIER_MASK) == 0 || (e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT)) { |
| long hitTime = getTimeAtX(e.x); |
| if (hitTime >= 0) { |
| setCapture(true); |
| |
| fDragState = DRAG_SELECTION; |
| fDragBeginMarker = false; |
| 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)) { |
| fDragBeginMarker = true; |
| fDragX = xEnd; |
| fDragX0 = e.x; |
| 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)) { |
| fDragBeginMarker = true; |
| fDragX = xEnd; |
| fDragX0 = e.x; |
| 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; |
| fDragEntry = getExpandedElement(getItemIndexAtY(e.y)); |
| fTime0bak = fTimeProvider.getTime0(); |
| fTime1bak = fTimeProvider.getTime1(); |
| updateCursor(e.x, e.stateMask); |
| } |
| } else if (3 == e.button) { |
| if (e.x >= fTimeProvider.getNameSpace()) { |
| setCapture(true); |
| fDragX = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), getSize().x - RIGHT_MARGIN); |
| fDragX0 = fDragX; |
| fDragTime0 = getTimeAtX(fDragX0); |
| fDragState = DRAG_ZOOM; |
| fDragButton = e.button; |
| redraw(); |
| updateCursor(e.x, e.stateMask); |
| fTimeGraphScale.setDragRange(fDragX0, fDragX); |
| } |
| } |
| } |
| |
| @Override |
| public void mouseUp(MouseEvent e) { |
| if (fPendingMenuDetectEvent != null && e.button == 3) { |
| if ((fDragState == DRAG_ZOOM) && isInDragZoomMargin()) { |
| // Select entry and time event for single click |
| long time = getTimeAtX(fDragX0); |
| fTimeProvider.setSelectionRangeNotify(time, time, false); |
| int idx = getItemIndexAtY(e.y); |
| selectItem(idx, false); |
| fireSelectionChanged(); |
| } |
| menuDetected(fPendingMenuDetectEvent); |
| } |
| if (DRAG_NONE != fDragState) { |
| setCapture(false); |
| if (e.button == fDragButton && DRAG_TRACE_ITEM == fDragState) { |
| fDragState = DRAG_NONE; |
| fDragEntry = null; |
| if (fDragX != fDragX0) { |
| fTimeProvider.notifyStartFinishTime(); |
| } |
| } else if (e.button == fDragButton && DRAG_SPLIT_LINE == fDragState) { |
| fDragState = DRAG_NONE; |
| redraw(); |
| } else if (e.button == fDragButton && DRAG_SELECTION == fDragState) { |
| fDragState = DRAG_NONE; |
| if (fDragX == fDragX0) { // click without selecting anything |
| long time = getTimeAtX(e.x); |
| fTimeProvider.setSelectedTimeNotify(time, false); |
| } else { |
| long time0 = fDragBeginMarker ? getTimeAtX(fDragX0) : fDragTime0; |
| long time1 = fDragBeginMarker ? fDragTime0 : getTimeAtX(fDragX); |
| fTimeProvider.setSelectionRangeNotify(time0, time1, false); |
| } |
| redraw(); |
| fTimeGraphScale.setDragRange(-1, -1); |
| } else if (e.button == fDragButton && DRAG_ZOOM == fDragState) { |
| fDragState = DRAG_NONE; |
| int nameWidth = fTimeProvider.getNameSpace(); |
| if ((Math.max(fDragX, fDragX0) > nameWidth) && !isInDragZoomMargin()) { |
| long time0 = getTimeAtX(fDragX0); |
| long time1 = getTimeAtX(fDragX); |
| if (time0 < time1) { |
| fTimeProvider.setStartFinishTimeNotify(time0, time1); |
| } else { |
| fTimeProvider.setStartFinishTimeNotify(time1, time0); |
| } |
| } else { |
| redraw(); |
| } |
| fTimeGraphScale.setDragRange(-1, -1); |
| } |
| } |
| updateCursor(e.x, e.stateMask); |
| updateStatusLine(e.x); |
| } |
| |
| @Override |
| public void mouseEnter(MouseEvent e) { |
| // Do nothing |
| } |
| |
| @Override |
| public void mouseExit(MouseEvent e) { |
| if (fMouseOverSplitLine) { |
| fMouseOverSplitLine = false; |
| redraw(); |
| } |
| updateStatusLine(STATUS_WITHOUT_CURSOR_TIME); |
| } |
| |
| @Override |
| public void mouseHover(MouseEvent e) { |
| // Do nothing |
| } |
| |
| @Override |
| public void mouseScrolled(MouseEvent e) { |
| if (fDragState != DRAG_NONE || e.count == 0) { |
| return; |
| } |
| |
| /* |
| * On some platforms the mouse scroll event is sent to the control that has |
| * focus even if it is not under the cursor. Handle the event only if over the |
| * time graph control. |
| */ |
| Point size = getSize(); |
| Rectangle bounds = new Rectangle(0, 0, size.x, size.y); |
| if (!bounds.contains(e.x, e.y)) { |
| return; |
| } |
| |
| boolean horizontalZoom = false; |
| boolean horizontalScroll = false; |
| boolean verticalZoom = false; |
| boolean verticalScroll = false; |
| |
| // over the time graph control |
| if ((e.stateMask & SWT.MODIFIER_MASK) == (SWT.SHIFT | SWT.CTRL)) { |
| verticalZoom = true; |
| } else if (e.x < fTimeProvider.getNameSpace()) { |
| // over the name space |
| verticalScroll = true; |
| } else { |
| // over the state area |
| if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL) { |
| // over the state area, CTRL pressed |
| horizontalZoom = true; |
| } else if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) { |
| // over the state area, SHIFT pressed |
| horizontalScroll = true; |
| } else { |
| // over the state area, no modifier pressed |
| verticalScroll = true; |
| } |
| } |
| if (verticalZoom) { |
| fVerticalZoomAlignEntry = getVerticalZoomAlignCursor(e.y); |
| verticalZoom(e.count > 0); |
| if (fVerticalZoomAlignEntry != null) { |
| setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue()); |
| } |
| } else if (horizontalZoom && fTimeProvider.getTime0() != fTimeProvider.getTime1()) { |
| zoom(e.count > 0); |
| } else if (horizontalScroll) { |
| horizontalScroll(e.count > 0); |
| } else if (verticalScroll) { |
| setTopIndex(getTopIndex() - e.count); |
| } |
| } |
| |
| /** |
| * Get the vertical zoom alignment entry and position based on the current |
| * selection. If there is no selection or if the selection is not visible, |
| * return an alignment entry with a null time graph entry. |
| * |
| * @return a map entry where the key is the selection's time graph entry and the |
| * value is the center y-coordinate of that entry, or null |
| */ |
| private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignSelection() { |
| Entry<ITimeGraphEntry, Integer> alignEntry = getVerticalZoomAlignOngoing(); |
| if (alignEntry != null) { |
| return alignEntry; |
| } |
| int index = getSelectedIndex(); |
| if (index == -1 || index >= getExpandedElementCount()) { |
| return new SimpleEntry<>(null, 0); |
| } |
| Rectangle bounds = getClientArea(); |
| Rectangle itemRect = getItemRect(bounds, index); |
| if (itemRect.y < bounds.y || itemRect.y > bounds.y + bounds.height) { |
| /* selection is not visible */ |
| return new SimpleEntry<>(null, 0); |
| } |
| ITimeGraphEntry entry = getExpandedElement(index); |
| int y = itemRect.y + itemRect.height / 2; |
| return new SimpleEntry<>(entry, y); |
| } |
| |
| /** |
| * Get the vertical zoom alignment entry and position at the specified cursor |
| * position. |
| * |
| * @param y |
| * the cursor y-coordinate |
| * @return a map entry where the key is the time graph entry under the cursor |
| * and the value is the cursor y-coordinate |
| */ |
| private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignCursor(int y) { |
| Entry<ITimeGraphEntry, Integer> alignEntry = getVerticalZoomAlignOngoing(); |
| if (alignEntry != null) { |
| return alignEntry; |
| } |
| int index = getItemIndexAtY(y); |
| if (index == -1) { |
| index = getExpandedElementCount() - 1; |
| } |
| ITimeGraphEntry entry = getExpandedElement(index); |
| return new SimpleEntry<>(entry, y); |
| } |
| |
| /** |
| * Get the vertical zoom alignment entry and position if there is an ongoing one |
| * and we are within the vertical zoom delay, or otherwise return null. |
| * |
| * @return a map entry where the key is a time graph entry and the value is a |
| * y-coordinate, or null |
| */ |
| private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignOngoing() { |
| long currentTimeMillis = System.currentTimeMillis(); |
| if (currentTimeMillis < fVerticalZoomAlignTime + VERTICAL_ZOOM_DELAY) { |
| /* |
| * If the vertical zoom is triggered repeatedly in a short amount of time, use |
| * the initial event's entry and position. |
| */ |
| fVerticalZoomAlignTime = currentTimeMillis; |
| return fVerticalZoomAlignEntry; |
| } |
| fVerticalZoomAlignTime = currentTimeMillis; |
| return null; |
| } |
| |
| @Override |
| public void handleEvent(Event event) { |
| if (event.type == SWT.MouseWheel) { |
| // prevent horizontal scrolling when the mouse wheel is used to |
| // scroll vertically or zoom |
| event.doit = false; |
| } |
| } |
| |
| @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; |
| for (Item item : fItemData.fItems) { |
| item.fItemHeight = 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 |
| */ |
| 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; |
| } |
| |
| /** |
| * Set whether all time events with a duration shorter than one pixel should be |
| * blended in. If false, only the first such time event will be drawn and the |
| * subsequent time events in the same pixel will be discarded. The default value |
| * is false. |
| * |
| * @param blend |
| * true if sub-pixel events should be blended, false otherwise. |
| * @since 1.1 |
| */ |
| public void setBlendSubPixelEvents(boolean blend) { |
| fBlendSubPixelEvents = blend; |
| } |
| |
| @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 IStructuredSelection) { |
| Object ob = ((IStructuredSelection) selection).getFirstElement(); |
| if (ob instanceof ITimeGraphEntry) { |
| selectItem((ITimeGraphEntry) ob, false); |
| } |
| } |
| |
| } |
| |
| /** |
| * Add a new viewer filter object |
| * |
| * @param filter |
| * The filter object to be attached to the view |
| * @since 3.1 |
| */ |
| public void addFilter(@NonNull ViewerFilter filter) { |
| fFilters.add(filter); |
| fireFiltersAdded(Collections.singleton(filter)); |
| } |
| |
| /** |
| * Change the viewer filter object |
| * |
| * The filter elements are already updated, we only let the listener know that a |
| * change happened at this point |
| * |
| * @param filter |
| * The filter object to be attached to the view |
| * @since 3.2 |
| */ |
| public void changeFilter(@NonNull ViewerFilter filter) { |
| fireFiltersChanged(Collections.singleton(filter)); |
| } |
| |
| /** |
| * Remove a viewer filter object |
| * |
| * @param filter |
| * The filter object to be attached to the view |
| */ |
| public void removeFilter(@NonNull ViewerFilter filter) { |
| fFilters.remove(filter); |
| fireFiltersRemoved(Collections.singleton(filter)); |
| } |
| |
| /** |
| * Returns this control's filters. |
| * |
| * @return an array of viewer filters |
| * @since 1.2 |
| */ |
| public @NonNull ViewerFilter[] getFilters() { |
| return Iterables.toArray(fFilters, ViewerFilter.class); |
| } |
| |
| /** |
| * Sets the filters, replacing any previous filters. |
| * |
| * @param filters |
| * an array of viewer filters, or null |
| * @since 1.2 |
| */ |
| public void setFilters(@NonNull ViewerFilter[] filters) { |
| fFilters.clear(); |
| if (filters != null) { |
| List<@NonNull ViewerFilter> filtersList = Arrays.asList(filters); |
| fFilters.addAll(filtersList); |
| fireFiltersChanged(filtersList); |
| } |
| } |
| |
| @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 Map<ITimeGraphEntry, Item> fItemMap = new LinkedHashMap<>(); |
| private Item[] fExpandedItems = new Item[0]; |
| private Item[] fItems = new Item[0]; |
| private ITimeGraphEntry fRootEntries[] = new ITimeGraphEntry[0]; |
| private List<ILinkEvent> fLinks = new ArrayList<>(); |
| |
| public ItemData() { |
| // Do nothing |
| } |
| |
| 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() { |
| ITimeGraphEntry selection = getSelectedTrace(); |
| Map<ITimeGraphEntry, Item> itemMap = new LinkedHashMap<>(); |
| fMaxItemHeight = 0; |
| for (int i = 0; i < fRootEntries.length; i++) { |
| ITimeGraphEntry entry = fRootEntries[i]; |
| refreshData(itemMap, null, 0, entry); |
| } |
| fItemMap = itemMap; |
| 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<ITimeGraphEntry, Item> 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; |
| } |
| fMaxItemHeight = Math.max(fMaxItemHeight, item.fItemHeight); |
| item.fItemHeight = Math.max(1, item.fItemHeight + fHeightAdjustment); |
| itemMap.put(entry, item); |
| if (entry.hasChildren()) { |
| Item oldItem = fItemMap.get(entry); |
| if (oldItem != null && oldItem.fHasChildren && level == oldItem.fLevel && entry.getParent() == oldItem.fEntry.getParent()) { |
| /* existing items keep their old expanded state */ |
| item.fExpanded = oldItem.fExpanded; |
| } else { |
| /* |
| * new items set the expanded state according to auto-expand level |
| */ |
| item.fExpanded = fAutoExpandLevel == ALL_LEVELS || level < fAutoExpandLevel; |
| } |
| item.fHasChildren = true; |
| for (ITimeGraphEntry child : entry.getChildren()) { |
| refreshData(itemMap, item, level + 1, child); |
| } |
| } |
| } |
| |
| public void updateExpandedItems() { |
| for (Item item : fItems) { |
| item.fExpandedIndex = -1; |
| } |
| List<Item> expandedItemList = new ArrayList<>(); |
| for (int i = 0; i < fRootEntries.length; i++) { |
| ITimeGraphEntry entry = fRootEntries[i]; |
| Item item = findItem(entry); |
| refreshExpanded(expandedItemList, item); |
| } |
| |
| if (hasSavedFilters()) { |
| filterData(expandedItemList); |
| } |
| |
| fExpandedItems = expandedItemList.toArray(new Item[0]); |
| fTopIndex = Math.min(fTopIndex, Math.max(0, fExpandedItems.length - 1)); |
| } |
| |
| private boolean hasEvents(Item item) { |
| ITimeGraphEntry entry = item.fEntry; |
| return (!entry.hasTimeEvents() || (entry instanceof TimeGraphEntry && ((TimeGraphEntry) entry).hasZoomedEvents())); |
| } |
| |
| private void filterData(List<Item> expandedItemList) { |
| for (Item item : expandedItemList) { |
| if (!hasEvents(item)) { |
| item.fItemHeight = 0; |
| } |
| } |
| } |
| |
| private void refreshExpanded(Collection<Item> 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) { |
| fRootEntries = null; |
| } else { |
| fRootEntries = Arrays.copyOf(entries, entries.length); |
| } |
| |
| refreshData(); |
| } |
| |
| public void refreshArrows(List<ILinkEvent> events) { |
| /* If links are null, reset the list */ |
| if (events != null) { |
| fLinks = events; |
| } else { |
| fLinks = new ArrayList<>(); |
| } |
| } |
| |
| public ITimeGraphEntry[] getEntries() { |
| return fRootEntries; |
| } |
| } |
| |
| 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<Item> 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; |
| } |
| } |
| |
| @Override |
| public void menuDetected(MenuDetectEvent e) { |
| if (null == fTimeProvider) { |
| return; |
| } |
| /* |
| * This flag indicates if menu was prevented from being shown below and |
| * therefore must be made visible on callback from mouseUp(). |
| */ |
| boolean pendingEventCallback = fPendingMenuDetectEvent != null; |
| Point p = toControl(e.x, e.y); |
| if (e.detail == SWT.MENU_MOUSE && isOverTimeSpace(p.x, p.y)) { |
| if (fPendingMenuDetectEvent == null) { |
| /* |
| * Feature in Linux. The MenuDetectEvent is received before mouseDown. Store the |
| * event and trigger it later just before handling mouseUp. This allows for the |
| * method to detect if mouse is used to drag zoom. |
| */ |
| fPendingMenuDetectEvent = e; |
| /* |
| * Prevent the platform to show the menu when returning. The menu will be shown |
| * (see below) when this method is called again during mouseUp(). |
| */ |
| e.doit = false; |
| return; |
| } |
| fPendingMenuDetectEvent = null; |
| if (fDragState != DRAG_ZOOM || !isInDragZoomMargin()) { |
| /* |
| * Don't show the menu on mouseUp() if a drag zoom is in progress with a drag |
| * range outside of the drag zoom margin, or if any other drag operation, or |
| * none, is in progress. |
| */ |
| e.doit = false; |
| return; |
| } |
| } else { |
| if (fDragState != DRAG_NONE) { |
| /* |
| * Don't show the menu on keyboard menu or mouse menu outside of the time space |
| * if any drag operation is in progress. |
| */ |
| e.doit = false; |
| return; |
| } |
| } |
| int idx = getItemIndexAtY(p.y); |
| if (idx >= 0 && idx < fItemData.fExpandedItems.length) { |
| Item item = fItemData.fExpandedItems[idx]; |
| ITimeGraphEntry entry = item.fEntry; |
| |
| /* Send menu event for the time graph entry */ |
| e.doit = true; |
| e.data = entry; |
| fireMenuEventOnTimeGraphEntry(e); |
| Menu menu = getMenu(); |
| if (pendingEventCallback && e.doit && (menu != null)) { |
| menu.setVisible(true); |
| } |
| |
| /* Send menu event for time event */ |
| if (entry.hasTimeEvents()) { |
| ITimeEvent event = Utils.findEvent(entry, getTimeAtX(p.x), 2); |
| if (event != null) { |
| e.doit = true; |
| e.data = event; |
| fireMenuEventOnTimeEvent(e); |
| menu = getMenu(); |
| if (pendingEventCallback && e.doit && (menu != null)) { |
| menu.setVisible(true); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Perform the alignment operation. |
| * |
| * @param offset |
| * the alignment offset |
| * |
| * @see ITmfTimeAligned |
| * |
| * @since 1.0 |
| */ |
| public void performAlign(int offset) { |
| fTimeProvider.setNameSpace(offset); |
| } |
| |
| /** |
| * Return the time alignment information |
| * |
| * @return the time alignment information |
| * |
| * @see ITmfTimeAligned |
| * |
| * @since 1.0 |
| */ |
| public TmfTimeViewAlignmentInfo getTimeViewAlignmentInfo() { |
| return new TmfTimeViewAlignmentInfo(getShell(), toDisplay(0, 0), fTimeProvider.getNameSpace()); |
| } |
| |
| private boolean isInDragZoomMargin() { |
| return (Math.abs(fDragX - fDragX0) < DRAG_MARGIN); |
| } |
| |
| /** |
| * Set the filtering status of the timegraph |
| * @param isActive True whether there is active filters, false otherwise |
| * @since 4.0 |
| */ |
| public void setFilterActive(boolean isActive) { |
| fFilterActive = isActive; |
| } |
| |
| /** |
| * Test if a time event filter is applied, basically it tells if the view is in |
| * filter mode |
| * |
| * @return True if the view is in filter mode, false otherwise |
| * @since 4.0 |
| * |
| */ |
| public boolean isFilterActive() { |
| return fFilterActive; |
| } |
| |
| /** |
| * Set whether filters have been saved or not. |
| * |
| * @param hasSavedFilter |
| * The saved filter status |
| * @since 4.0 |
| */ |
| public void setSavedFilterStatus(boolean hasSavedFilter) { |
| fHasSavedFilters = hasSavedFilter; |
| } |
| |
| /** |
| * Tells whether the timegraph has saved filters or not |
| * |
| * @return True whether there is saved filters, false otherwise |
| * @since 4.0 |
| */ |
| public boolean hasSavedFilters() { |
| return fHasSavedFilters; |
| } |
| } |