tmf: Support for context-sensitive menu in Time Graph viewers
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / widgets / timegraph / widgets / TimeGraphControl.java
CommitLineData
837a2f8c 1/*****************************************************************************
8910dea2 2 * Copyright (c) 2007, 2016 Intel Corporation and others
4999a196 3 *
837a2f8c
PT
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * Intel Corporation - Initial API and implementation
11 * Ruslan A. Scherbakov, Intel - Initial API and implementation
4999a196
GB
12 * Alvaro Sanchez-Leon, Ericsson - Updated for TMF
13 * Patrick Tasse, Ericsson - Refactoring
14 * Geneviève Bastien, École Polytechnique de Montréal - Move code to
15 * provide base classes for time graph view
bec1f1ac 16 * Add display of links between items
351a2391 17 * Xavier Raynaud, Kalray - Code optimization
f2ca0f69 18 * Generoso Pagano, Inria - Support for drag selection listeners
837a2f8c
PT
19 *****************************************************************************/
20
2bdf0193 21package org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets;
837a2f8c 22
48072ae3 23import java.util.AbstractMap.SimpleEntry;
837a2f8c 24import java.util.ArrayList;
f1fae91f 25import java.util.Arrays;
2fe6a9ea 26import java.util.Collections;
83d0971d 27import java.util.HashMap;
837a2f8c 28import java.util.Iterator;
70e10acc 29import java.util.LinkedHashMap;
837a2f8c 30import java.util.List;
70e10acc 31import java.util.Map;
48072ae3 32import java.util.Map.Entry;
837a2f8c 33
367e2932 34import org.eclipse.jdt.annotation.NonNull;
0fcf3b09 35import org.eclipse.jface.action.IStatusLineManager;
837a2f8c
PT
36import org.eclipse.jface.resource.JFaceResources;
37import org.eclipse.jface.resource.LocalResourceManager;
f4617471 38import org.eclipse.jface.viewers.AbstractTreeViewer;
837a2f8c
PT
39import org.eclipse.jface.viewers.ISelection;
40import org.eclipse.jface.viewers.ISelectionChangedListener;
41import org.eclipse.jface.viewers.ISelectionProvider;
ad4415e1 42import org.eclipse.jface.viewers.SelectionChangedEvent;
6ac5a950 43import org.eclipse.jface.viewers.ViewerFilter;
0fab12b0 44import org.eclipse.osgi.util.NLS;
837a2f8c 45import org.eclipse.swt.SWT;
837a2f8c
PT
46import org.eclipse.swt.events.FocusEvent;
47import org.eclipse.swt.events.FocusListener;
48import org.eclipse.swt.events.KeyEvent;
49import org.eclipse.swt.events.KeyListener;
27df1564
XR
50import org.eclipse.swt.events.MenuDetectEvent;
51import org.eclipse.swt.events.MenuDetectListener;
837a2f8c
PT
52import org.eclipse.swt.events.MouseEvent;
53import org.eclipse.swt.events.MouseListener;
54import org.eclipse.swt.events.MouseMoveListener;
55import org.eclipse.swt.events.MouseTrackListener;
56import org.eclipse.swt.events.MouseWheelListener;
57import org.eclipse.swt.events.PaintEvent;
837a2f8c
PT
58import org.eclipse.swt.events.SelectionListener;
59import org.eclipse.swt.events.TraverseEvent;
60import org.eclipse.swt.events.TraverseListener;
27df1564 61import org.eclipse.swt.events.TypedEvent;
837a2f8c
PT
62import org.eclipse.swt.graphics.Color;
63import org.eclipse.swt.graphics.Cursor;
3bd20aa6
PT
64import org.eclipse.swt.graphics.Font;
65import org.eclipse.swt.graphics.FontData;
837a2f8c
PT
66import org.eclipse.swt.graphics.GC;
67import org.eclipse.swt.graphics.Image;
68import org.eclipse.swt.graphics.Point;
69import org.eclipse.swt.graphics.Rectangle;
70import org.eclipse.swt.widgets.Composite;
71import org.eclipse.swt.widgets.Display;
72import org.eclipse.swt.widgets.Event;
73import org.eclipse.swt.widgets.Listener;
024658d1 74import org.eclipse.swt.widgets.Menu;
d2e4afa7
MAL
75import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
76import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentInfo;
77import org.eclipse.tracecompass.tmf.ui.signal.TmfTimeViewAlignmentSignal;
78import org.eclipse.tracecompass.tmf.ui.views.ITmfTimeAligned;
2bdf0193
AM
79import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphColorListener;
80import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphPresentationProvider;
81import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphPresentationProvider2;
82import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphTimeListener;
83import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.ITimeGraphTreeListener;
84import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.StateItem;
85import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphTimeEvent;
86import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphTreeExpansionEvent;
87import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ILinkEvent;
56b43ab2 88import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEvent;
2bdf0193
AM
89import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent;
90import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
91import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.Utils.Resolution;
92import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.Utils.TimeFormat;
837a2f8c 93
4923d7b9
PT
94import com.google.common.collect.Iterables;
95
837a2f8c
PT
96/**
97 * Time graph control implementation
98 *
837a2f8c
PT
99 * @author Alvaro Sanchez-Leon
100 * @author Patrick Tasse
101 */
496f76d3 102public class TimeGraphControl extends TimeGraphBaseControl
b698ec63
PT
103 implements FocusListener, KeyListener, MouseMoveListener, MouseListener,
104 MouseWheelListener, MouseTrackListener, TraverseListener, ISelectionProvider,
6b11be52 105 MenuDetectListener, ITmfTimeGraphDrawingHelper, ITimeGraphColorListener, Listener {
f1fae91f 106
ae09c4ad 107 /** Constant indicating that all levels of the time graph should be expanded */
f4617471
PT
108 public static final int ALL_LEVELS = AbstractTreeViewer.ALL_LEVELS;
109
837a2f8c
PT
110 private static final int DRAG_NONE = 0;
111 private static final int DRAG_TRACE_ITEM = 1;
112 private static final int DRAG_SPLIT_LINE = 2;
5b2b9bd7 113 private static final int DRAG_ZOOM = 3;
0fcf3b09 114 private static final int DRAG_SELECTION = 4;
a0a88f65 115
837a2f8c
PT
116 private static final int CUSTOM_ITEM_HEIGHT = -1; // get item height from provider
117
f1fae91f
PT
118 private static final double ZOOM_FACTOR = 1.5;
119 private static final double ZOOM_IN_FACTOR = 0.8;
120 private static final double ZOOM_OUT_FACTOR = 1.25;
121
0fcf3b09 122 private static final int SNAP_WIDTH = 2;
1053eaa6 123 private static final int ARROW_HOVER_MAX_DIST = 5;
0fcf3b09
PT
124
125 private static final int NO_STATUS = -1;
33fa1fc7 126 private static final int STATUS_WITHOUT_CURSOR_TIME = -2;
0fcf3b09 127
1d012443
PT
128 private static final int MAX_LABEL_LENGTH = 256;
129
83d0971d
PT
130 private static final int PPI = 72; // points per inch
131 private static final int DPI = Display.getDefault().getDPI().y;
132
48072ae3
PT
133 private static final int VERTICAL_ZOOM_DELAY = 400;
134
f1fae91f
PT
135 /** Resource manager */
136 private LocalResourceManager fResourceManager = new LocalResourceManager(JFaceResources.getResources());
137
138 /** Color map for event types */
139 private Color[] fEventColorMap = null;
140
141 private ITimeDataProvider fTimeProvider;
0fcf3b09
PT
142 private IStatusLineManager fStatusLineManager = null;
143 private TimeGraphScale fTimeGraphScale = null;
144
f1fae91f 145 private boolean fIsInFocus = false;
f1fae91f
PT
146 private boolean fMouseOverSplitLine = false;
147 private int fGlobalItemHeight = CUSTOM_ITEM_HEIGHT;
3bd20aa6 148 private int fHeightAdjustment = 0;
83d0971d 149 private Map<Integer, Font> fFonts = new HashMap<>();
b052914f 150 private boolean fBlendSubPixelEvents = false;
f1fae91f
PT
151 private int fMinimumItemWidth = 0;
152 private int fTopIndex = 0;
153 private int fDragState = DRAG_NONE;
85203d74 154 private boolean fDragBeginMarker = false;
0fcf3b09 155 private int fDragButton;
f1fae91f
PT
156 private int fDragX0 = 0;
157 private int fDragX = 0;
0fcf3b09 158 private long fDragTime0 = 0; // used to preserve accuracy of modified selection
f1fae91f
PT
159 private int fIdealNameSpace = 0;
160 private long fTime0bak;
161 private long fTime1bak;
837a2f8c 162 private ITimeGraphPresentationProvider fTimeGraphProvider = null;
f1fae91f 163 private ItemData fItemData = null;
56b43ab2
PT
164 private List<IMarkerEvent> fMarkers = null;
165 private boolean fMarkersVisible = true;
f1fae91f 166 private List<SelectionListener> fSelectionListeners;
f2ca0f69 167 private List<ITimeGraphTimeListener> fDragSelectionListeners;
507b1336
AM
168 private final List<ISelectionChangedListener> fSelectionChangedListeners = new ArrayList<>();
169 private final List<ITimeGraphTreeListener> fTreeListeners = new ArrayList<>();
170 private final List<MenuDetectListener> fTimeGraphEntryMenuListeners = new ArrayList<>();
171 private final List<MenuDetectListener> fTimeEventMenuListeners = new ArrayList<>();
0fcf3b09
PT
172 private final Cursor fDragCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_HAND);
173 private final Cursor fResizeCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_IBEAM);
174 private final Cursor fWaitCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_WAIT);
175 private final Cursor fZoomCursor = Display.getDefault().getSystemCursor(SWT.CURSOR_SIZEWE);
367e2932 176 private final List<@NonNull ViewerFilter> fFilters = new ArrayList<>();
0b5a90a0 177 private MenuDetectEvent fPendingMenuDetectEvent = null;
55d1fc96
PT
178 private boolean fGridLinesVisible = true;
179 private Color fGridLineColor = Display.getDefault().getSystemColor(SWT.COLOR_GRAY);
79ec0b89 180 private boolean fHideArrows = false;
f4617471 181 private int fAutoExpandLevel = ALL_LEVELS;
48072ae3
PT
182 private Entry<ITimeGraphEntry, Integer> fVerticalZoomAlignEntry = null;
183 private long fVerticalZoomAlignTime = 0;
f1fae91f
PT
184 private int fBorderWidth = 0;
185 private int fHeaderHeight = 0;
837a2f8c 186
837a2f8c
PT
187 /**
188 * Standard constructor
189 *
190 * @param parent
191 * The parent composite object
192 * @param colors
193 * The color scheme to use
194 */
195 public TimeGraphControl(Composite parent, TimeGraphColorScheme colors) {
196
b698ec63 197 super(parent, colors, SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED);
837a2f8c 198
f1fae91f 199 fItemData = new ItemData();
837a2f8c
PT
200
201 addFocusListener(this);
202 addMouseListener(this);
203 addMouseMoveListener(this);
204 addMouseTrackListener(this);
205 addMouseWheelListener(this);
206 addTraverseListener(this);
207 addKeyListener(this);
27df1564 208 addMenuDetectListener(this);
6b11be52 209 addListener(SWT.MouseWheel, this);
25033fef
PT
210 addDisposeListener((e) -> {
211 fResourceManager.dispose();
212 for (Font font : fFonts.values()) {
213 font.dispose();
214 }
215 });
837a2f8c
PT
216 }
217
218 /**
219 * Sets the timegraph provider used by this timegraph viewer.
220 *
221 * @param timeGraphProvider the timegraph provider
222 */
223 public void setTimeGraphProvider(ITimeGraphPresentationProvider timeGraphProvider) {
224 fTimeGraphProvider = timeGraphProvider;
837a2f8c 225
4999a196
GB
226 if (timeGraphProvider instanceof ITimeGraphPresentationProvider2) {
227 ((ITimeGraphPresentationProvider2) timeGraphProvider).setDrawingHelper(this);
496f76d3 228 ((ITimeGraphPresentationProvider2) timeGraphProvider).addColorListener(this);
4999a196
GB
229 }
230
837a2f8c 231 StateItem[] stateItems = fTimeGraphProvider.getStateTable();
496f76d3 232 colorSettingsChanged(stateItems);
837a2f8c
PT
233 }
234
5a66cf9c
XR
235 /**
236 * Gets the timegraph provider used by this timegraph viewer.
237 *
238 * @return the timegraph provider, or <code>null</code> if not set.
5a66cf9c
XR
239 */
240 public ITimeGraphPresentationProvider getTimeGraphProvider() {
241 return fTimeGraphProvider;
242 }
243
244 /**
245 * Gets the color map used by this timegraph viewer.
246 *
247 * @return a color map, or <code>null</code> if not set.
5a66cf9c
XR
248 */
249 public Color[] getEventColorMap() {
250 return fEventColorMap;
251 }
252
837a2f8c
PT
253 /**
254 * Assign the given time provider
255 *
256 * @param timeProvider
257 * The time provider
258 */
259 public void setTimeProvider(ITimeDataProvider timeProvider) {
f1fae91f 260 fTimeProvider = timeProvider;
837a2f8c
PT
261 redraw();
262 }
263
0fcf3b09
PT
264 /**
265 * Assign the status line manager
266 *
267 * @param statusLineManager
268 * The status line manager, or null to disable status line messages
0fcf3b09
PT
269 */
270 public void setStatusLineManager(IStatusLineManager statusLineManager) {
271 if (fStatusLineManager != null && statusLineManager == null) {
272 fStatusLineManager.setMessage(""); //$NON-NLS-1$
273 }
274 fStatusLineManager = statusLineManager;
275 }
276
277 /**
278 * Assign the time graph scale
279 *
280 * @param timeGraphScale
281 * The time graph scale
0fcf3b09
PT
282 */
283 public void setTimeGraphScale(TimeGraphScale timeGraphScale) {
284 fTimeGraphScale = timeGraphScale;
285 }
286
837a2f8c
PT
287 /**
288 * Add a selection listener
289 *
290 * @param listener
291 * The listener to add
292 */
293 public void addSelectionListener(SelectionListener listener) {
294 if (listener == null) {
295 SWT.error(SWT.ERROR_NULL_ARGUMENT);
296 }
f1fae91f 297 if (null == fSelectionListeners) {
507b1336 298 fSelectionListeners = new ArrayList<>();
837a2f8c 299 }
f1fae91f 300 fSelectionListeners.add(listener);
837a2f8c
PT
301 }
302
303 /**
304 * Remove a selection listener
305 *
306 * @param listener
307 * The listener to remove
308 */
309 public void removeSelectionListener(SelectionListener listener) {
f1fae91f
PT
310 if (null != fSelectionListeners) {
311 fSelectionListeners.remove(listener);
837a2f8c
PT
312 }
313 }
314
315 /**
316 * Selection changed callback
317 */
318 public void fireSelectionChanged() {
f1fae91f
PT
319 if (null != fSelectionListeners) {
320 Iterator<SelectionListener> it = fSelectionListeners.iterator();
837a2f8c
PT
321 while (it.hasNext()) {
322 SelectionListener listener = it.next();
323 listener.widgetSelected(null);
324 }
325 }
ad4415e1
BH
326
327 if (null != fSelectionChangedListeners) {
328 for (ISelectionChangedListener listener : fSelectionChangedListeners) {
329 listener.selectionChanged(new SelectionChangedEvent(this, getSelection()));
330 }
331 }
837a2f8c
PT
332 }
333
334 /**
335 * Default selection callback
336 */
337 public void fireDefaultSelection() {
f1fae91f
PT
338 if (null != fSelectionListeners) {
339 Iterator<SelectionListener> it = fSelectionListeners.iterator();
837a2f8c
PT
340 while (it.hasNext()) {
341 SelectionListener listener = it.next();
342 listener.widgetDefaultSelected(null);
343 }
344 }
345 }
346
f2ca0f69
GP
347 /**
348 * Add a drag selection listener
349 *
350 * @param listener
351 * The listener to add
f2ca0f69
GP
352 */
353 public void addDragSelectionListener(ITimeGraphTimeListener listener) {
354 if (listener == null) {
355 SWT.error(SWT.ERROR_NULL_ARGUMENT);
356 }
357 if (null == fDragSelectionListeners) {
358 fDragSelectionListeners = new ArrayList<>();
359 }
360 fDragSelectionListeners.add(listener);
361 }
362
363 /**
364 * Remove a drag selection listener
365 *
366 * @param listener
367 * The listener to remove
f2ca0f69
GP
368 */
369 public void removeDragSelectionListener(ITimeGraphTimeListener listener) {
370 if (null != fDragSelectionListeners) {
371 fDragSelectionListeners.remove(listener);
372 }
373 }
374
375 /**
376 * Drag Selection changed callback
377 *
378 * @param start
379 * Time interval start
380 * @param end
381 * Time interval end
f2ca0f69
GP
382 */
383 public void fireDragSelectionChanged(long start, long end) {
384 // check for backward intervals
385 long beginTime, endTime;
386 if (start > end) {
387 beginTime = end;
388 endTime = start;
389 } else {
390 beginTime = start;
391 endTime = end;
392 }
393 // call the listeners
394 if (null != fDragSelectionListeners) {
395 Iterator<ITimeGraphTimeListener> it = fDragSelectionListeners.iterator();
396 while (it.hasNext()) {
397 ITimeGraphTimeListener listener = it.next();
398 listener.timeSelected(new TimeGraphTimeEvent(this, beginTime, endTime));
399 }
400 }
401 }
402
837a2f8c
PT
403 /**
404 * Get the traces in the model
405 *
406 * @return The array of traces
407 */
408 public ITimeGraphEntry[] getTraces() {
70e10acc 409 return fItemData.getEntries();
837a2f8c
PT
410 }
411
837a2f8c
PT
412 /**
413 * Refresh the data for the thing
414 */
415 public void refreshData() {
f1fae91f 416 fItemData.refreshData();
837a2f8c
PT
417 redraw();
418 }
419
420 /**
421 * Refresh data for the given traces
422 *
423 * @param traces
424 * The traces to refresh
425 */
426 public void refreshData(ITimeGraphEntry[] traces) {
f1fae91f 427 fItemData.refreshData(traces);
837a2f8c
PT
428 redraw();
429 }
430
bec1f1ac
GB
431 /**
432 * Refresh the links (arrows) of this widget
433 *
2fe6a9ea 434 * @param events The link event list
bec1f1ac
GB
435 */
436 public void refreshArrows(List<ILinkEvent> events) {
437 fItemData.refreshArrows(events);
438 }
439
2fe6a9ea
PT
440 /**
441 * Get the links (arrows) of this widget
442 *
443 * @return The unmodifiable link event list
444 *
0336f981 445 * @since 1.1
2fe6a9ea
PT
446 */
447 public List<ILinkEvent> getArrows() {
448 return Collections.unmodifiableList(fItemData.fLinks);
449 }
450
837a2f8c
PT
451 boolean ensureVisibleItem(int idx, boolean redraw) {
452 boolean changed = false;
41b5c37f
AM
453 int index = idx;
454 if (index < 0) {
f1fae91f
PT
455 for (index = 0; index < fItemData.fExpandedItems.length; index++) {
456 if (fItemData.fExpandedItems[index].fSelected) {
837a2f8c
PT
457 break;
458 }
459 }
460 }
f1fae91f 461 if (index >= fItemData.fExpandedItems.length) {
837a2f8c
PT
462 return changed;
463 }
f1fae91f 464 if (index < fTopIndex) {
41b5c37f 465 setTopIndex(index);
837a2f8c
PT
466 if (redraw) {
467 redraw();
468 }
469 changed = true;
470 } else {
471 int page = countPerPage();
f1fae91f 472 if (index >= fTopIndex + page) {
41b5c37f 473 setTopIndex(index - page + 1);
837a2f8c
PT
474 if (redraw) {
475 redraw();
476 }
477 changed = true;
478 }
479 }
480 return changed;
481 }
482
483 /**
484 * Assign the given index as the top one
485 *
486 * @param idx
487 * The index
488 */
489 public void setTopIndex(int idx) {
f1fae91f 490 int index = Math.min(idx, fItemData.fExpandedItems.length - countPerPage());
41b5c37f 491 index = Math.max(0, index);
f1fae91f 492 fTopIndex = index;
837a2f8c
PT
493 redraw();
494 }
495
48072ae3
PT
496 /**
497 * Set the top index so that the requested element is at the specified
498 * position.
499 *
500 * @param entry
501 * the time graph entry to be positioned
502 * @param y
503 * the requested y-coordinate
504 * @since 2.0
505 */
506 public void setElementPosition(ITimeGraphEntry entry, int y) {
507 Item item = fItemData.fItemMap.get(entry);
508 if (item == null || item.fExpandedIndex == -1) {
509 return;
510 }
511 int index = item.fExpandedIndex;
512 Rectangle itemRect = getItemRect(getClientArea(), index);
513 int delta = itemRect.y + itemRect.height - y;
514 int topIndex = getItemIndexAtY(delta);
515 if (topIndex != -1) {
516 setTopIndex(topIndex);
517 } else {
518 if (delta < 0) {
519 setTopIndex(0);
520 } else {
521 setTopIndex(getExpandedElementCount());
522 }
523 }
524 }
525
f4617471 526 /**
df0e3d5f
PT
527 * Sets the auto-expand level to be used for new entries discovered when
528 * calling {@link #refreshData()} or {@link #refreshData(ITimeGraphEntry[])}
529 * . The value 0 means that there is no auto-expand; 1 means that top-level
f4617471
PT
530 * entries are expanded, but not their children; 2 means that top-level
531 * entries are expanded, and their children, but not grand-children; and so
532 * on.
533 * <p>
534 * The value {@link #ALL_LEVELS} means that all subtrees should be expanded.
535 * </p>
df0e3d5f 536 *
f4617471
PT
537 * @param level
538 * non-negative level, or <code>ALL_LEVELS</code> to expand all
539 * levels of the tree
f4617471
PT
540 */
541 public void setAutoExpandLevel(int level) {
542 fAutoExpandLevel = level;
543 }
544
545 /**
546 * Returns the auto-expand level.
547 *
548 * @return non-negative level, or <code>ALL_LEVELS</code> if all levels of
549 * the tree are expanded automatically
550 * @see #setAutoExpandLevel
f4617471
PT
551 */
552 public int getAutoExpandLevel() {
553 return fAutoExpandLevel;
554 }
555
df0e3d5f
PT
556 /**
557 * Get the expanded state of a given entry.
558 *
559 * @param entry
560 * The entry
561 * @return true if the entry is expanded, false if collapsed
0336f981 562 * @since 1.1
df0e3d5f
PT
563 */
564 public boolean getExpandedState(ITimeGraphEntry entry) {
565 Item item = fItemData.fItemMap.get(entry);
566 return (item != null ? item.fExpanded : false);
567 }
568
837a2f8c
PT
569 /**
570 * Set the expanded state of a given entry
571 *
572 * @param entry
573 * The entry
574 * @param expanded
575 * True if expanded, false if collapsed
576 */
577 public void setExpandedState(ITimeGraphEntry entry, boolean expanded) {
f1fae91f
PT
578 Item item = fItemData.findItem(entry);
579 if (item != null && item.fExpanded != expanded) {
580 item.fExpanded = expanded;
581 fItemData.updateExpandedItems();
837a2f8c
PT
582 redraw();
583 }
584 }
585
586 /**
587 * Collapses all nodes of the viewer's tree, starting with the root.
837a2f8c
PT
588 */
589 public void collapseAll() {
f1fae91f
PT
590 for (Item item : fItemData.fItems) {
591 item.fExpanded = false;
837a2f8c 592 }
f1fae91f 593 fItemData.updateExpandedItems();
837a2f8c
PT
594 redraw();
595 }
596
597 /**
598 * Expands all nodes of the viewer's tree, starting with the root.
837a2f8c
PT
599 */
600 public void expandAll() {
f1fae91f
PT
601 for (Item item : fItemData.fItems) {
602 item.fExpanded = true;
837a2f8c 603 }
f1fae91f 604 fItemData.updateExpandedItems();
837a2f8c
PT
605 redraw();
606 }
607
608 /**
609 * Add a tree listener
610 *
611 * @param listener
612 * The listener to add
613 */
614 public void addTreeListener(ITimeGraphTreeListener listener) {
f1fae91f
PT
615 if (!fTreeListeners.contains(listener)) {
616 fTreeListeners.add(listener);
837a2f8c
PT
617 }
618 }
619
620 /**
621 * Remove a tree listener
622 *
623 * @param listener
624 * The listener to remove
625 */
626 public void removeTreeListener(ITimeGraphTreeListener listener) {
f1fae91f
PT
627 if (fTreeListeners.contains(listener)) {
628 fTreeListeners.remove(listener);
837a2f8c
PT
629 }
630 }
631
632 /**
633 * Tree event callback
634 *
635 * @param entry
636 * The affected entry
637 * @param expanded
638 * The expanded state (true for expanded, false for collapsed)
639 */
640 public void fireTreeEvent(ITimeGraphEntry entry, boolean expanded) {
641 TimeGraphTreeExpansionEvent event = new TimeGraphTreeExpansionEvent(this, entry);
f1fae91f 642 for (ITimeGraphTreeListener listener : fTreeListeners) {
837a2f8c
PT
643 if (expanded) {
644 listener.treeExpanded(event);
645 } else {
646 listener.treeCollapsed(event);
647 }
648 }
649 }
650
27df1564
XR
651 /**
652 * Add a menu listener on {@link ITimeGraphEntry}s
ae09c4ad 653 *
27df1564
XR
654 * @param listener
655 * The listener to add
27df1564
XR
656 */
657 public void addTimeGraphEntryMenuListener(MenuDetectListener listener) {
f1fae91f
PT
658 if (!fTimeGraphEntryMenuListeners.contains(listener)) {
659 fTimeGraphEntryMenuListeners.add(listener);
27df1564
XR
660 }
661 }
662
663 /**
664 * Remove a menu listener on {@link ITimeGraphEntry}s
665 *
666 * @param listener
667 * The listener to remove
27df1564
XR
668 */
669 public void removeTimeGraphEntryMenuListener(MenuDetectListener listener) {
f1fae91f
PT
670 if (fTimeGraphEntryMenuListeners.contains(listener)) {
671 fTimeGraphEntryMenuListeners.remove(listener);
27df1564
XR
672 }
673 }
674
675 /**
676 * Menu event callback on {@link ITimeGraphEntry}s
677 *
678 * @param event
679 * The MenuDetectEvent, with field {@link TypedEvent#data} set to the selected {@link ITimeGraphEntry}
680 */
681 private void fireMenuEventOnTimeGraphEntry(MenuDetectEvent event) {
f1fae91f 682 for (MenuDetectListener listener : fTimeGraphEntryMenuListeners) {
27df1564
XR
683 listener.menuDetected(event);
684 }
685 }
686
687 /**
688 * Add a menu listener on {@link ITimeEvent}s
689 *
690 * @param listener
691 * The listener to add
27df1564
XR
692 */
693 public void addTimeEventMenuListener(MenuDetectListener listener) {
f1fae91f
PT
694 if (!fTimeEventMenuListeners.contains(listener)) {
695 fTimeEventMenuListeners.add(listener);
27df1564
XR
696 }
697 }
698
699 /**
700 * Remove a menu listener on {@link ITimeEvent}s
701 *
702 * @param listener
703 * The listener to remove
27df1564
XR
704 */
705 public void removeTimeEventMenuListener(MenuDetectListener listener) {
f1fae91f
PT
706 if (fTimeEventMenuListeners.contains(listener)) {
707 fTimeEventMenuListeners.remove(listener);
27df1564
XR
708 }
709 }
710
711 /**
712 * Menu event callback on {@link ITimeEvent}s
713 *
714 * @param event
715 * The MenuDetectEvent, with field {@link TypedEvent#data} set to the selected {@link ITimeEvent}
716 */
717 private void fireMenuEventOnTimeEvent(MenuDetectEvent event) {
f1fae91f 718 for (MenuDetectListener listener : fTimeEventMenuListeners) {
27df1564
XR
719 listener.menuDetected(event);
720 }
721 }
722
837a2f8c
PT
723 @Override
724 public ISelection getSelection() {
725 TimeGraphSelection sel = new TimeGraphSelection();
726 ITimeGraphEntry trace = getSelectedTrace();
f1fae91f 727 if (null != trace && null != fTimeProvider) {
baf92cac 728 long selectedTime = fTimeProvider.getSelectionBegin();
837a2f8c 729 ITimeEvent event = Utils.findEvent(trace, selectedTime, 0);
9bdf1671 730 sel.add(trace);
837a2f8c
PT
731 if (event != null) {
732 sel.add(event);
837a2f8c
PT
733 }
734 }
735 return sel;
736 }
737
738 /**
739 * Get the selection object
740 *
741 * @return The selection
742 */
743 public ISelection getSelectionTrace() {
744 TimeGraphSelection sel = new TimeGraphSelection();
745 ITimeGraphEntry trace = getSelectedTrace();
746 if (null != trace) {
747 sel.add(trace);
748 }
749 return sel;
750 }
751
752 /**
753 * Enable/disable one of the traces in the model
754 *
755 * @param n
756 * 1 to enable it, -1 to disable. The method returns immediately
757 * if another value is used.
758 */
759 public void selectTrace(int n) {
760 if ((n != 1) && (n != -1)) {
761 return;
762 }
763
764 boolean changed = false;
765 int lastSelection = -1;
f1fae91f
PT
766 for (int i = 0; i < fItemData.fExpandedItems.length; i++) {
767 Item item = fItemData.fExpandedItems[i];
768 if (item.fSelected) {
837a2f8c 769 lastSelection = i;
f1fae91f
PT
770 if ((1 == n) && (i < fItemData.fExpandedItems.length - 1)) {
771 item.fSelected = false;
772 item = fItemData.fExpandedItems[i + 1];
773 item.fSelected = true;
837a2f8c
PT
774 changed = true;
775 } else if ((-1 == n) && (i > 0)) {
f1fae91f
PT
776 item.fSelected = false;
777 item = fItemData.fExpandedItems[i - 1];
778 item.fSelected = true;
837a2f8c
PT
779 changed = true;
780 }
781 break;
782 }
783 }
784
f1fae91f
PT
785 if (lastSelection < 0 && fItemData.fExpandedItems.length > 0) {
786 Item item = fItemData.fExpandedItems[0];
787 item.fSelected = true;
837a2f8c
PT
788 changed = true;
789 }
790
791 if (changed) {
792 ensureVisibleItem(-1, false);
793 redraw();
794 fireSelectionChanged();
795 }
796 }
797
798 /**
799 * Select an event
800 *
801 * @param n
802 * 1 for next event, -1 for previous event
33fa1fc7
PT
803 * @param extend
804 * true to extend selection range, false for single selection
805 * @since 1.0
837a2f8c 806 */
33fa1fc7 807 public void selectEvent(int n, boolean extend) {
f1fae91f 808 if (null == fTimeProvider) {
837a2f8c
PT
809 return;
810 }
811 ITimeGraphEntry trace = getSelectedTrace();
812 if (trace == null) {
813 return;
814 }
33fa1fc7 815 long selectedTime = fTimeProvider.getSelectionEnd();
50d36521 816 long endTime = fTimeProvider.getMaxTime();
837a2f8c 817 ITimeEvent nextEvent;
33fa1fc7 818 if (n == -1 && selectedTime > endTime) {
837a2f8c
PT
819 nextEvent = Utils.findEvent(trace, selectedTime, 0);
820 } else {
821 nextEvent = Utils.findEvent(trace, selectedTime, n);
822 }
33fa1fc7 823 if (null == nextEvent && n == -1) {
837a2f8c
PT
824 nextEvent = Utils.getFirstEvent(trace);
825 }
826 if (null != nextEvent) {
827 long nextTime = nextEvent.getTime();
828 // If last event detected e.g. going back or not moving to a next
829 // event
830 if (nextTime <= selectedTime && n == 1) {
831 // Select to the end of this last event
832 nextTime = nextEvent.getTime() + nextEvent.getDuration();
833 // but not beyond the end of the trace
834 if (nextTime > endTime) {
835 nextTime = endTime;
836 }
f1fae91f 837 } else if (n == -1 && nextEvent.getTime() + nextEvent.getDuration() < selectedTime) {
837a2f8c 838 // for previous event go to its end time unless we were already there
f1fae91f 839 nextTime = nextEvent.getTime() + nextEvent.getDuration();
837a2f8c 840 }
33fa1fc7 841 if (extend) {
84c8aef7 842 fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), nextTime, true);
33fa1fc7
PT
843 } else {
844 fTimeProvider.setSelectedTimeNotify(nextTime, true);
845 }
837a2f8c 846 fireSelectionChanged();
33fa1fc7
PT
847 } else if (n == 1) {
848 if (extend) {
84c8aef7 849 fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), endTime, true);
33fa1fc7
PT
850 } else {
851 fTimeProvider.setSelectedTimeNotify(endTime, true);
852 }
837a2f8c
PT
853 fireSelectionChanged();
854 }
33fa1fc7 855 updateStatusLine(STATUS_WITHOUT_CURSOR_TIME);
837a2f8c
PT
856 }
857
858 /**
859 * Select the next event
33fa1fc7
PT
860 *
861 * @param extend
862 * true to extend selection range, false for single selection
863 * @since 1.0
837a2f8c 864 */
33fa1fc7
PT
865 public void selectNextEvent(boolean extend) {
866 selectEvent(1, extend);
837a2f8c 867 // Notify if visible time window has been adjusted
f1fae91f 868 fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1());
837a2f8c
PT
869 }
870
871 /**
872 * Select the previous event
33fa1fc7
PT
873 *
874 * @param extend
875 * true to extend selection range, false for single selection
876 * @since 1.0
837a2f8c 877 */
33fa1fc7
PT
878 public void selectPrevEvent(boolean extend) {
879 selectEvent(-1, extend);
837a2f8c 880 // Notify if visible time window has been adjusted
f1fae91f 881 fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1());
837a2f8c
PT
882 }
883
884 /**
885 * Select the next trace
886 */
887 public void selectNextTrace() {
888 selectTrace(1);
889 }
890
891 /**
892 * Select the previous trace
893 */
894 public void selectPrevTrace() {
895 selectTrace(-1);
896 }
897
b698ec63
PT
898 /**
899 * Scroll left or right by one half window size
900 *
901 * @param left
902 * true to scroll left, false to scroll right
b698ec63
PT
903 */
904 public void horizontalScroll(boolean left) {
905 long time0 = fTimeProvider.getTime0();
906 long time1 = fTimeProvider.getTime1();
907 long timeMin = fTimeProvider.getMinTime();
908 long timeMax = fTimeProvider.getMaxTime();
909 long range = time1 - time0;
910 if (range <= 0) {
911 return;
912 }
913 long increment = Math.max(1, range / 2);
914 if (left) {
915 time0 = Math.max(time0 - increment, timeMin);
916 time1 = time0 + range;
917 } else {
918 time1 = Math.min(time1 + increment, timeMax);
919 time0 = time1 - range;
920 }
921 fTimeProvider.setStartFinishTimeNotify(time0, time1);
922 }
923
837a2f8c
PT
924 /**
925 * Zoom based on mouse cursor location with mouse scrolling
926 *
927 * @param zoomIn true to zoom in, false to zoom out
928 */
929 public void zoom(boolean zoomIn) {
930 int globalX = getDisplay().getCursorLocation().x;
931 Point p = toControl(globalX, 0);
f1fae91f
PT
932 int nameSpace = fTimeProvider.getNameSpace();
933 int timeSpace = fTimeProvider.getTimeSpace();
837a2f8c 934 int xPos = Math.max(nameSpace, Math.min(nameSpace + timeSpace, p.x));
f1fae91f
PT
935 long time0 = fTimeProvider.getTime0();
936 long time1 = fTimeProvider.getTime1();
837a2f8c
PT
937 long interval = time1 - time0;
938 if (interval == 0) {
939 interval = 1;
940 } // to allow getting out of single point interval
941 long newInterval;
942 if (zoomIn) {
f1fae91f 943 newInterval = Math.max(Math.round(interval * ZOOM_IN_FACTOR), fTimeProvider.getMinTimeInterval());
837a2f8c 944 } else {
f1fae91f 945 newInterval = (long) Math.ceil(interval * ZOOM_OUT_FACTOR);
837a2f8c
PT
946 }
947 long center = time0 + Math.round(((double) (xPos - nameSpace) / timeSpace * interval));
948 long newTime0 = center - Math.round((double) newInterval * (center - time0) / interval);
949 long newTime1 = newTime0 + newInterval;
10ad9fa6 950 fTimeProvider.setStartFinishTimeNotify(newTime0, newTime1);
837a2f8c
PT
951 }
952
953 /**
954 * zoom in using single click
955 */
956 public void zoomIn() {
f1fae91f
PT
957 long prevTime0 = fTimeProvider.getTime0();
958 long prevTime1 = fTimeProvider.getTime1();
959 long prevRange = prevTime1 - prevTime0;
960 if (prevRange == 0) {
3e9a3685
PT
961 return;
962 }
baf92cac
AM
963 ITimeDataProvider provider = fTimeProvider;
964 long selTime = (provider.getSelectionEnd() + provider.getSelectionBegin()) / 2;
f1fae91f
PT
965 if (selTime <= prevTime0 || selTime >= prevTime1) {
966 selTime = (prevTime0 + prevTime1) / 2;
837a2f8c 967 }
f1fae91f
PT
968 long time0 = selTime - (long) ((selTime - prevTime0) / ZOOM_FACTOR);
969 long time1 = selTime + (long) ((prevTime1 - selTime) / ZOOM_FACTOR);
837a2f8c 970
f1fae91f 971 long inaccuracy = (fTimeProvider.getMaxTime() - fTimeProvider.getMinTime()) - (time1 - time0);
837a2f8c
PT
972
973 if (inaccuracy > 0 && inaccuracy < 100) {
f1fae91f 974 fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getMinTime(), fTimeProvider.getMaxTime());
837a2f8c
PT
975 return;
976 }
977
f1fae91f
PT
978 long min = fTimeProvider.getMinTimeInterval();
979 if ((time1 - time0) < min) {
980 time0 = selTime - (selTime - prevTime0) * min / prevRange;
981 time1 = time0 + min;
837a2f8c
PT
982 }
983
f1fae91f 984 fTimeProvider.setStartFinishTimeNotify(time0, time1);
837a2f8c
PT
985 }
986
987 /**
988 * zoom out using single click
989 */
990 public void zoomOut() {
f1fae91f
PT
991 long prevTime0 = fTimeProvider.getTime0();
992 long prevTime1 = fTimeProvider.getTime1();
baf92cac
AM
993 ITimeDataProvider provider = fTimeProvider;
994 long selTime = (provider.getSelectionEnd() + provider.getSelectionBegin()) / 2;
f1fae91f
PT
995 if (selTime <= prevTime0 || selTime >= prevTime1) {
996 selTime = (prevTime0 + prevTime1) / 2;
837a2f8c 997 }
f1fae91f
PT
998 long time0 = (long) (selTime - (selTime - prevTime0) * ZOOM_FACTOR);
999 long time1 = (long) (selTime + (prevTime1 - selTime) * ZOOM_FACTOR);
837a2f8c 1000
f1fae91f 1001 long inaccuracy = (fTimeProvider.getMaxTime() - fTimeProvider.getMinTime()) - (time1 - time0);
837a2f8c 1002 if (inaccuracy > 0 && inaccuracy < 100) {
f1fae91f 1003 fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getMinTime(), fTimeProvider.getMaxTime());
837a2f8c
PT
1004 return;
1005 }
1006
f1fae91f 1007 fTimeProvider.setStartFinishTimeNotify(time0, time1);
837a2f8c
PT
1008 }
1009
3bd20aa6
PT
1010 /**
1011 * Zoom vertically.
1012 *
1013 * @param zoomIn
1014 * true to zoom in, false to zoom out
3bd20aa6
PT
1015 * @since 2.0
1016 */
83d0971d 1017 public void verticalZoom(boolean zoomIn) {
3bd20aa6
PT
1018 if (zoomIn) {
1019 fHeightAdjustment++;
1020 } else {
1021 fHeightAdjustment--;
1022 }
83d0971d 1023 fItemData.refreshData();
3bd20aa6
PT
1024 redraw();
1025 }
1026
1027 /**
1028 * Reset the vertical zoom to default.
1029 *
3bd20aa6
PT
1030 * @since 2.0
1031 */
83d0971d 1032 public void resetVerticalZoom() {
3bd20aa6 1033 fHeightAdjustment = 0;
83d0971d 1034 fItemData.refreshData();
3bd20aa6
PT
1035 redraw();
1036 }
1037
55d1fc96
PT
1038 /**
1039 * Set the grid lines visibility. The default is true.
1040 *
1041 * @param visible
1042 * true to show the grid lines, false otherwise
1043 * @since 2.0
1044 */
1045 public void setGridLinesVisible(boolean visible) {
1046 fGridLinesVisible = visible;
1047 }
1048
1049 /**
1050 * Get the grid lines visibility.
1051 *
1052 * @return true if the grid lines are visible, false otherwise
1053 * @since 2.0
1054 */
1055 public boolean getGridLinesVisible() {
1056 return fGridLinesVisible;
1057 }
1058
1059 /**
1060 * Set the grid line color. The default is SWT.COLOR_GRAY.
1061 *
1062 * @param color
1063 * the grid line color
1064 * @since 2.0
1065 */
1066 public void setGridLineColor(Color color) {
1067 fGridLineColor = color;
1068 }
1069
1070 /**
1071 * Get the grid line color.
1072 *
1073 * @return the grid line color
1074 * @since 2.0
1075 */
1076 public Color getGridLineColor() {
1077 return fGridLineColor;
1078 }
1079
56b43ab2
PT
1080 /**
1081 * Set the markers list.
1082 *
1083 * @param markers
1084 * The markers list, or null
1085 * @since 2.0
1086 */
1087 public void setMarkers(List<IMarkerEvent> markers) {
1088 fMarkers = markers;
56b43ab2
PT
1089 }
1090
1091 /**
1092 * Get the markers list.
1093 *
1094 * @return The markers list, or null
1095 * @since 2.0
1096 */
1097 public List<IMarkerEvent> getMarkers() {
1098 return fMarkers;
1099 }
1100
1101 /**
1102 * Set the markers visibility. The default is true.
1103 *
1104 * @param visible
1105 * true to show the markers, false otherwise
1106 * @since 2.0
1107 */
1108 public void setMarkersVisible(boolean visible) {
1109 fMarkersVisible = visible;
56b43ab2
PT
1110 }
1111
1112 /**
1113 * Get the markers visibility.
1114 *
1115 * @return true if the markers are visible, false otherwise
1116 * @since 2.0
1117 */
1118 public boolean getMarkersVisible() {
1119 return fMarkersVisible;
1120 }
1121
79ec0b89
PT
1122 /**
1123 * Hide arrows
1124 *
1125 * @param hideArrows true to hide arrows
79ec0b89
PT
1126 */
1127 public void hideArrows(boolean hideArrows) {
1128 fHideArrows = hideArrows;
1129 }
1130
086f21ae
PT
1131 /**
1132 * Follow the arrow forward
33fa1fc7
PT
1133 *
1134 * @param extend
1135 * true to extend selection range, false for single selection
1136 * @since 1.0
086f21ae 1137 */
33fa1fc7 1138 public void followArrowFwd(boolean extend) {
086f21ae
PT
1139 ITimeGraphEntry trace = getSelectedTrace();
1140 if (trace == null) {
1141 return;
1142 }
33fa1fc7 1143 long selectedTime = fTimeProvider.getSelectionEnd();
086f21ae
PT
1144 for (ILinkEvent link : fItemData.fLinks) {
1145 if (link.getEntry() == trace && link.getTime() == selectedTime) {
1146 selectItem(link.getDestinationEntry(), false);
1147 if (link.getDuration() != 0) {
33fa1fc7 1148 if (extend) {
84c8aef7 1149 fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime() + link.getDuration(), true);
33fa1fc7
PT
1150 } else {
1151 fTimeProvider.setSelectedTimeNotify(link.getTime() + link.getDuration(), true);
1152 }
086f21ae
PT
1153 // Notify if visible time window has been adjusted
1154 fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1());
1155 }
1156 fireSelectionChanged();
33fa1fc7 1157 updateStatusLine(STATUS_WITHOUT_CURSOR_TIME);
086f21ae
PT
1158 return;
1159 }
1160 }
33fa1fc7 1161 selectNextEvent(extend);
086f21ae
PT
1162 }
1163
1164 /**
1165 * Follow the arrow backward
33fa1fc7
PT
1166 *
1167 * @param extend
1168 * true to extend selection range, false for single selection
1169 * @since 1.0
086f21ae 1170 */
33fa1fc7 1171 public void followArrowBwd(boolean extend) {
086f21ae
PT
1172 ITimeGraphEntry trace = getSelectedTrace();
1173 if (trace == null) {
1174 return;
1175 }
33fa1fc7 1176 long selectedTime = fTimeProvider.getSelectionEnd();
086f21ae
PT
1177 for (ILinkEvent link : fItemData.fLinks) {
1178 if (link.getDestinationEntry() == trace && link.getTime() + link.getDuration() == selectedTime) {
1179 selectItem(link.getEntry(), false);
1180 if (link.getDuration() != 0) {
33fa1fc7 1181 if (extend) {
84c8aef7 1182 fTimeProvider.setSelectionRangeNotify(fTimeProvider.getSelectionBegin(), link.getTime(), true);
33fa1fc7
PT
1183 } else {
1184 fTimeProvider.setSelectedTimeNotify(link.getTime(), true);
1185 }
086f21ae
PT
1186 // Notify if visible time window has been adjusted
1187 fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1());
1188 }
1189 fireSelectionChanged();
33fa1fc7 1190 updateStatusLine(STATUS_WITHOUT_CURSOR_TIME);
086f21ae
PT
1191 return;
1192 }
1193 }
33fa1fc7 1194 selectPrevEvent(extend);
086f21ae
PT
1195 }
1196
837a2f8c
PT
1197 /**
1198 * Return the currently selected trace
1199 *
1200 * @return The entry matching the trace
1201 */
1202 public ITimeGraphEntry getSelectedTrace() {
1203 ITimeGraphEntry trace = null;
1204 int idx = getSelectedIndex();
1205 if (idx >= 0) {
70e10acc 1206 trace = fItemData.fExpandedItems[idx].fEntry;
837a2f8c
PT
1207 }
1208 return trace;
1209 }
1210
1211 /**
1212 * Retrieve the index of the currently selected item
1213 *
1214 * @return The index
1215 */
1216 public int getSelectedIndex() {
1217 int idx = -1;
f1fae91f
PT
1218 for (int i = 0; i < fItemData.fExpandedItems.length; i++) {
1219 Item item = fItemData.fExpandedItems[i];
1220 if (item.fSelected) {
837a2f8c
PT
1221 idx = i;
1222 break;
1223 }
1224 }
1225 return idx;
1226 }
1227
1228 boolean toggle(int idx) {
1229 boolean toggled = false;
f1fae91f
PT
1230 if (idx >= 0 && idx < fItemData.fExpandedItems.length) {
1231 Item item = fItemData.fExpandedItems[idx];
1232 if (item.fHasChildren) {
1233 item.fExpanded = !item.fExpanded;
1234 fItemData.updateExpandedItems();
837a2f8c
PT
1235 redraw();
1236 toggled = true;
70e10acc 1237 fireTreeEvent(item.fEntry, item.fExpanded);
837a2f8c
PT
1238 }
1239 }
1240 return toggled;
1241 }
1242
bedfbbb5
XR
1243 /**
1244 * Gets the index of the item at the given location.
1245 *
1246 * @param y
1247 * the y coordinate
1248 * @return the index of the item at the given location, of -1 if none.
bedfbbb5
XR
1249 */
1250 protected int getItemIndexAtY(int y) {
c004295c 1251 int ySum = 0;
48072ae3
PT
1252 if (y < 0) {
1253 for (int idx = fTopIndex - 1; idx >= 0; idx--) {
1254 ySum -= fItemData.fExpandedItems[idx].fItemHeight;
1255 if (y >= ySum) {
1256 return idx;
1257 }
1258 }
1259 } else {
1260 for (int idx = fTopIndex; idx < fItemData.fExpandedItems.length; idx++) {
1261 ySum += fItemData.fExpandedItems[idx].fItemHeight;
1262 if (y < ySum) {
1263 return idx;
1264 }
837a2f8c 1265 }
837a2f8c
PT
1266 }
1267 return -1;
1268 }
1269
1270 boolean isOverSplitLine(int x) {
f1fae91f 1271 if (x < 0 || null == fTimeProvider) {
837a2f8c
PT
1272 return false;
1273 }
f1fae91f 1274 int nameWidth = fTimeProvider.getNameSpace();
0fcf3b09 1275 return Math.abs(x - nameWidth) < SNAP_WIDTH;
837a2f8c
PT
1276 }
1277
bedfbbb5
XR
1278 /**
1279 * Gets the {@link ITimeGraphEntry} at the given location.
1280 *
1281 * @param pt
1282 * a point in the widget
1283 * @return the {@link ITimeGraphEntry} at this point, or <code>null</code>
1284 * if none.
8c684b48 1285 * @since 2.0
bedfbbb5 1286 */
8c684b48 1287 public ITimeGraphEntry getEntry(Point pt) {
837a2f8c 1288 int idx = getItemIndexAtY(pt.y);
70e10acc 1289 return idx >= 0 ? fItemData.fExpandedItems[idx].fEntry : null;
837a2f8c
PT
1290 }
1291
1053eaa6
PT
1292 /**
1293 * Return the arrow event closest to the given point that is no further than
1294 * a maximum distance.
1295 *
1296 * @param pt
1297 * a point in the widget
1298 * @return The closest arrow event, or null if there is none close enough.
1053eaa6
PT
1299 */
1300 protected ILinkEvent getArrow(Point pt) {
1301 if (fHideArrows) {
1302 return null;
1303 }
1304 ILinkEvent linkEvent = null;
1305 double minDistance = Double.MAX_VALUE;
1306 for (ILinkEvent event : fItemData.fLinks) {
1307 Rectangle rect = getArrowRectangle(new Rectangle(0, 0, 0, 0), event);
1308 if (rect != null) {
1309 int x1 = rect.x;
1310 int y1 = rect.y;
1311 int x2 = x1 + rect.width;
1312 int y2 = y1 + rect.height;
1313 double d = Utils.distance(pt.x, pt.y, x1, y1, x2, y2);
1314 if (minDistance > d) {
1315 minDistance = d;
1316 linkEvent = event;
1317 }
1318 }
1319 }
1320 if (minDistance <= ARROW_HOVER_MAX_DIST) {
1321 return linkEvent;
1322 }
1323 return null;
1324 }
1325
4999a196 1326 @Override
713a70ae 1327 public int getXForTime(long time) {
f1fae91f 1328 if (null == fTimeProvider) {
713a70ae
PT
1329 return -1;
1330 }
f1fae91f
PT
1331 long time0 = fTimeProvider.getTime0();
1332 long time1 = fTimeProvider.getTime1();
b698ec63 1333 int width = getSize().x;
f1fae91f 1334 int nameSpace = fTimeProvider.getNameSpace();
713a70ae
PT
1335 double pixelsPerNanoSec = (width - nameSpace <= RIGHT_MARGIN) ? 0 : (double) (width - nameSpace - RIGHT_MARGIN) / (time1 - time0);
1336 int x = getBounds().x + nameSpace + (int) ((time - time0) * pixelsPerNanoSec);
1337 return x;
1338 }
1339
4999a196 1340 @Override
41b5c37f 1341 public long getTimeAtX(int coord) {
f1fae91f 1342 if (null == fTimeProvider) {
837a2f8c
PT
1343 return -1;
1344 }
1345 long hitTime = -1;
b698ec63 1346 Point size = getSize();
f1fae91f
PT
1347 long time0 = fTimeProvider.getTime0();
1348 long time1 = fTimeProvider.getTime1();
1349 int nameWidth = fTimeProvider.getNameSpace();
41b5c37f 1350 final int x = coord - nameWidth;
837a2f8c
PT
1351 int timeWidth = size.x - nameWidth - RIGHT_MARGIN;
1352 if (x >= 0 && size.x >= nameWidth) {
1353 if (time1 - time0 > timeWidth) {
1354 // nanosecond smaller than one pixel: use the first integer nanosecond of this pixel's time range
1355 hitTime = time0 + (long) Math.ceil((time1 - time0) * ((double) x / timeWidth));
1356 } else {
1357 // nanosecond greater than one pixel: use the nanosecond that covers this pixel start position
1358 hitTime = time0 + (long) Math.floor((time1 - time0) * ((double) x / timeWidth));
1359 }
1360 }
1361 return hitTime;
1362 }
1363
1364 void selectItem(int idx, boolean addSelection) {
1365 boolean changed = false;
1366 if (addSelection) {
f1fae91f
PT
1367 if (idx >= 0 && idx < fItemData.fExpandedItems.length) {
1368 Item item = fItemData.fExpandedItems[idx];
1369 changed = !item.fSelected;
1370 item.fSelected = true;
837a2f8c
PT
1371 }
1372 } else {
f1fae91f
PT
1373 for (int i = 0; i < fItemData.fExpandedItems.length; i++) {
1374 Item item = fItemData.fExpandedItems[i];
1375 if ((i == idx && !item.fSelected) || (idx == -1 && item.fSelected)) {
837a2f8c
PT
1376 changed = true;
1377 }
f1fae91f 1378 item.fSelected = i == idx;
837a2f8c
PT
1379 }
1380 }
1381 changed |= ensureVisibleItem(idx, true);
1382 if (changed) {
1383 redraw();
1384 }
1385 }
1386
1387 /**
1388 * Callback for item selection
1389 *
1390 * @param trace
1391 * The entry matching the trace
1392 * @param addSelection
1393 * If the selection is added or removed
1394 */
1395 public void selectItem(ITimeGraphEntry trace, boolean addSelection) {
f1fae91f 1396 int idx = fItemData.findItemIndex(trace);
837a2f8c
PT
1397 selectItem(idx, addSelection);
1398 }
1399
1400 /**
1401 * Retrieve the number of entries shown per page.
1402 *
1403 * @return The count
1404 */
1405 public int countPerPage() {
b698ec63 1406 int height = getSize().y;
837a2f8c 1407 int count = 0;
c004295c
PT
1408 int ySum = 0;
1409 for (int idx = fTopIndex; idx < fItemData.fExpandedItems.length; idx++) {
1410 ySum += fItemData.fExpandedItems[idx].fItemHeight;
1411 if (ySum >= height) {
1412 return count;
837a2f8c 1413 }
c004295c 1414 count++;
837a2f8c 1415 }
c004295c
PT
1416 for (int idx = fTopIndex - 1; idx >= 0; idx--) {
1417 ySum += fItemData.fExpandedItems[idx].fItemHeight;
1418 if (ySum >= height) {
1419 return count;
1420 }
1421 count++;
837a2f8c
PT
1422 }
1423 return count;
1424 }
1425
1426 /**
1427 * Get the index of the top element
1428 *
1429 * @return The index
1430 */
1431 public int getTopIndex() {
f1fae91f 1432 return fTopIndex;
837a2f8c
PT
1433 }
1434
1435 /**
df0e3d5f 1436 * Get the number of expanded (visible) items
837a2f8c 1437 *
df0e3d5f 1438 * @return The count of expanded (visible) items
837a2f8c
PT
1439 */
1440 public int getExpandedElementCount() {
f1fae91f 1441 return fItemData.fExpandedItems.length;
837a2f8c
PT
1442 }
1443
1444 /**
df0e3d5f 1445 * Get an array of all expanded (visible) elements
837a2f8c 1446 *
df0e3d5f 1447 * @return The expanded (visible) elements
837a2f8c
PT
1448 */
1449 public ITimeGraphEntry[] getExpandedElements() {
507b1336 1450 ArrayList<ITimeGraphEntry> elements = new ArrayList<>();
f1fae91f 1451 for (Item item : fItemData.fExpandedItems) {
70e10acc 1452 elements.add(item.fEntry);
837a2f8c
PT
1453 }
1454 return elements.toArray(new ITimeGraphEntry[0]);
1455 }
1456
48072ae3
PT
1457 /**
1458 * Get the expanded (visible) element at the specified index.
1459 *
1460 * @param index
1461 * the element index
1462 * @return The expanded (visible) element or null if out of range
1463 * @since 2.0
1464 */
1465 public ITimeGraphEntry getExpandedElement(int index) {
1466 if (index < 0 || index >= fItemData.fExpandedItems.length) {
1467 return null;
1468 }
1469 return fItemData.fExpandedItems[index].fEntry;
1470 }
1471
83b36bb4
PT
1472 Rectangle getNameRect(Rectangle bounds, int idx, int nameWidth) {
1473 Rectangle rect = getItemRect(bounds, idx);
c004295c
PT
1474 rect.width = nameWidth;
1475 return rect;
837a2f8c
PT
1476 }
1477
83b36bb4
PT
1478 Rectangle getStatesRect(Rectangle bounds, int idx, int nameWidth) {
1479 Rectangle rect = getItemRect(bounds, idx);
1480 rect.x += nameWidth;
1481 rect.width -= nameWidth;
1482 return rect;
1483 }
1484
1485 Rectangle getItemRect(Rectangle bounds, int idx) {
c004295c
PT
1486 int ySum = 0;
1487 if (idx >= fTopIndex) {
f1fae91f
PT
1488 for (int i = fTopIndex; i < idx; i++) {
1489 ySum += fItemData.fExpandedItems[i].fItemHeight;
837a2f8c 1490 }
c004295c
PT
1491 } else {
1492 for (int i = fTopIndex - 1; i >= idx; i--) {
1493 ySum -= fItemData.fExpandedItems[i].fItemHeight;
1494 }
837a2f8c 1495 }
83b36bb4 1496 int y = bounds.y + ySum;
c004295c 1497 int height = fItemData.fExpandedItems[idx].fItemHeight;
83b36bb4 1498 return new Rectangle(bounds.x, y, bounds.width, height);
837a2f8c
PT
1499 }
1500
1501 @Override
1502 void paint(Rectangle bounds, PaintEvent e) {
1503 GC gc = e.gc;
837a2f8c 1504
f1fae91f 1505 if (bounds.width < 2 || bounds.height < 2 || null == fTimeProvider) {
837a2f8c
PT
1506 return;
1507 }
1508
f1fae91f
PT
1509 fIdealNameSpace = 0;
1510 int nameSpace = fTimeProvider.getNameSpace();
837a2f8c 1511
1c67fff3 1512 // draw the background layer
83b36bb4 1513 drawBackground(bounds, nameSpace, gc);
837a2f8c 1514
55d1fc96
PT
1515 // draw the grid lines
1516 drawGridLines(bounds, gc);
1517
56b43ab2
PT
1518 // draw the background markers
1519 drawMarkers(bounds, fTimeProvider, fMarkers, false, nameSpace, gc);
1520
1c67fff3 1521 // draw the items
f1fae91f 1522 drawItems(bounds, fTimeProvider, fItemData.fExpandedItems, fTopIndex, nameSpace, gc);
1c67fff3 1523
56b43ab2
PT
1524 // draw the foreground markers
1525 drawMarkers(bounds, fTimeProvider, fMarkers, true, nameSpace, gc);
1526
1c67fff3 1527 // draw the links (arrows)
bec1f1ac 1528 drawLinks(bounds, fTimeProvider, fItemData.fLinks, nameSpace, gc);
1c67fff3 1529
bec1f1ac 1530 fTimeGraphProvider.postDrawControl(bounds, gc);
837a2f8c 1531
0fcf3b09
PT
1532 int alpha = gc.getAlpha();
1533 gc.setAlpha(100);
1534
f1fae91f
PT
1535 long time0 = fTimeProvider.getTime0();
1536 long time1 = fTimeProvider.getTime1();
baf92cac
AM
1537 long selectionBegin = fTimeProvider.getSelectionBegin();
1538 long selectionEnd = fTimeProvider.getSelectionEnd();
837a2f8c 1539 double pixelsPerNanoSec = (bounds.width - nameSpace <= RIGHT_MARGIN) ? 0 : (double) (bounds.width - nameSpace - RIGHT_MARGIN) / (time1 - time0);
0fcf3b09
PT
1540 int x0 = bounds.x + nameSpace + (int) ((selectionBegin - time0) * pixelsPerNanoSec);
1541 int x1 = bounds.x + nameSpace + (int) ((selectionEnd - time0) * pixelsPerNanoSec);
1542
1543 // draw selection lines
1544 if (fDragState != DRAG_SELECTION) {
f1fae91f 1545 gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.SELECTED_TIME));
0fcf3b09
PT
1546 if (x0 >= nameSpace && x0 < bounds.x + bounds.width) {
1547 gc.drawLine(x0, bounds.y, x0, bounds.y + bounds.height);
1548 }
1549 if (x1 != x0) {
1550 if (x1 >= nameSpace && x1 < bounds.x + bounds.width) {
1551 gc.drawLine(x1, bounds.y, x1, bounds.y + bounds.height);
1552 }
1553 }
837a2f8c
PT
1554 }
1555
0fcf3b09
PT
1556 // draw selection background
1557 if (selectionBegin != 0 && selectionEnd != 0 && fDragState != DRAG_SELECTION) {
1558 x0 = Math.max(nameSpace, Math.min(bounds.x + bounds.width, x0));
1559 x1 = Math.max(nameSpace, Math.min(bounds.x + bounds.width, x1));
1560 gc.setBackground(getColorScheme().getBkColor(false, false, true));
1561 if (x1 - x0 > 1) {
1562 gc.fillRectangle(new Rectangle(x0 + 1, bounds.y, x1 - x0 - 1, bounds.height));
1563 } else if (x0 - x1 > 1) {
1564 gc.fillRectangle(new Rectangle(x1 + 1, bounds.y, x0 - x1 - 1, bounds.height));
1565 }
1566 }
1567
1568 // draw drag selection background
1569 if (fDragState == DRAG_ZOOM || fDragState == DRAG_SELECTION) {
1570 gc.setBackground(getColorScheme().getBkColor(false, false, true));
1571 if (fDragX0 < fDragX) {
1572 gc.fillRectangle(new Rectangle(fDragX0, bounds.y, fDragX - fDragX0, bounds.height));
1573 } else if (fDragX0 > fDragX) {
1574 gc.fillRectangle(new Rectangle(fDragX, bounds.y, fDragX0 - fDragX, bounds.height));
1575 }
1576 }
1577
1578 // draw drag line
f1fae91f
PT
1579 if (DRAG_SPLIT_LINE == fDragState) {
1580 gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.BLACK));
837a2f8c 1581 gc.drawLine(bounds.x + nameSpace, bounds.y, bounds.x + nameSpace, bounds.y + bounds.height - 1);
0fcf3b09 1582 } else if (DRAG_ZOOM == fDragState && Math.max(fDragX, fDragX0) > nameSpace) {
f1fae91f
PT
1583 gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.TOOL_FOREGROUND));
1584 gc.drawLine(fDragX0, bounds.y, fDragX0, bounds.y + bounds.height - 1);
0fcf3b09
PT
1585 if (fDragX != fDragX0) {
1586 gc.drawLine(fDragX, bounds.y, fDragX, bounds.y + bounds.height - 1);
1587 }
1588 } else if (DRAG_SELECTION == fDragState && Math.max(fDragX, fDragX0) > nameSpace) {
1589 gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.SELECTED_TIME));
1590 gc.drawLine(fDragX0, bounds.y, fDragX0, bounds.y + bounds.height - 1);
1591 if (fDragX != fDragX0) {
1592 gc.drawLine(fDragX, bounds.y, fDragX, bounds.y + bounds.height - 1);
1593 }
f1fae91f
PT
1594 } else if (DRAG_NONE == fDragState && fMouseOverSplitLine && fTimeProvider.getNameSpace() > 0) {
1595 gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.RED));
837a2f8c
PT
1596 gc.drawLine(bounds.x + nameSpace, bounds.y, bounds.x + nameSpace, bounds.y + bounds.height - 1);
1597 }
0fcf3b09
PT
1598
1599 gc.setAlpha(alpha);
837a2f8c
PT
1600 }
1601
83b36bb4 1602 /**
1c67fff3
PT
1603 * Draw the background layer. Fills the background of the control's name
1604 * space and states space, updates the background of items if necessary,
1605 * and draws the item's name text and middle line.
83b36bb4
PT
1606 *
1607 * @param bounds
1c67fff3 1608 * The bounds of the control
83b36bb4 1609 * @param nameSpace
1c67fff3 1610 * The name space width
83b36bb4 1611 * @param gc
1c67fff3 1612 * Graphics context
83b36bb4
PT
1613 * @since 2.0
1614 */
1615 protected void drawBackground(Rectangle bounds, int nameSpace, GC gc) {
1616 // draw empty name space background
1617 gc.setBackground(getColorScheme().getBkColor(false, false, true));
1618 drawBackground(gc, bounds.x, bounds.y, nameSpace, bounds.height);
1619
1620 // draw empty states space background
1621 gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.BACKGROUND));
1622 drawBackground(gc, bounds.x + nameSpace, bounds.y, bounds.width - nameSpace, bounds.height);
1623
83b36bb4
PT
1624 for (int i = fTopIndex; i < fItemData.fExpandedItems.length; i++) {
1625 Rectangle itemRect = getItemRect(bounds, i);
1626 if (itemRect.y >= bounds.y + bounds.height) {
1627 break;
1628 }
1629 Item item = fItemData.fExpandedItems[i];
1c67fff3 1630 // draw the background of selected item and items with no time events
83b36bb4
PT
1631 if (! item.fEntry.hasTimeEvents()) {
1632 gc.setBackground(getColorScheme().getBkColorGroup(item.fSelected, fIsInFocus));
1633 gc.fillRectangle(itemRect);
1634 } else if (item.fSelected) {
1635 gc.setBackground(getColorScheme().getBkColor(true, fIsInFocus, true));
1636 gc.fillRectangle(itemRect.x, itemRect.y, nameSpace, itemRect.height);
1637 gc.setBackground(getColorScheme().getBkColor(true, fIsInFocus, false));
1638 gc.fillRectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height);
1639 }
1c67fff3
PT
1640 // draw the name and middle line
1641 if (! item.fEntry.hasTimeEvents()) {
1642 drawName(item, itemRect, gc);
1643 } else {
1644 Rectangle nameRect = new Rectangle(itemRect.x, itemRect.y, nameSpace, itemRect.height);
1645 drawName(item, nameRect, gc);
1646 Rectangle rect = new Rectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height);
1647 drawMidLine(rect, gc);
1648 }
83b36bb4
PT
1649 }
1650 }
1651
55d1fc96
PT
1652 /**
1653 * Draw the grid lines
1654 *
1655 * @param bounds
1c67fff3 1656 * The bounds of the control
55d1fc96 1657 * @param gc
1c67fff3 1658 * Graphics context
55d1fc96
PT
1659 * @since 2.0
1660 */
1661 public void drawGridLines(Rectangle bounds, GC gc) {
1662 if (!fGridLinesVisible) {
1663 return;
1664 }
1665 gc.setForeground(fGridLineColor);
56b43ab2 1666 gc.setAlpha(fGridLineColor.getAlpha());
55d1fc96
PT
1667 for (int x : fTimeGraphScale.getTickList()) {
1668 gc.drawLine(x, bounds.y, x, bounds.y + bounds.height);
1669 }
56b43ab2
PT
1670 gc.setAlpha(255);
1671 }
1672
1673 /**
1674 * Draw the markers
1675 *
1676 * @param bounds
1677 * The rectangle of the area
1678 * @param timeProvider
1679 * The time provider
1680 * @param markers
1681 * The list of markers
1682 * @param foreground
1683 * true to draw the foreground markers, false otherwise
1684 * @param nameSpace
1685 * The width reserved for the names
1686 * @param gc
1687 * Reference to the SWT GC object
1688 * @since 2.0
1689 */
1690 protected void drawMarkers(Rectangle bounds, ITimeDataProvider timeProvider, List<IMarkerEvent> markers, boolean foreground, int nameSpace, GC gc) {
1691 if (!fMarkersVisible || markers == null || markers.isEmpty()) {
1692 return;
1693 }
1694 gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height));
1695 /* the list can grow concurrently but cannot shrink */
1696 for (int i = 0; i < markers.size(); i++) {
1697 IMarkerEvent marker = markers.get(i);
1698 if (marker.isForeground() == foreground) {
1699 drawMarker(marker, bounds, timeProvider, nameSpace, gc);
1700 }
1701 }
1702 gc.setClipping((Rectangle) null);
1703 }
1704
1705 /**
1706 * Draw a single marker
1707 *
1708 * @param marker
1709 * The marker event
1710 * @param bounds
1711 * The bounds of the control
1712 * @param timeProvider
1713 * The time provider
1714 * @param nameSpace
1715 * The width reserved for the name
1716 * @param gc
1717 * Reference to the SWT GC object
1718 * @since 2.0
1719 */
1720 protected void drawMarker(IMarkerEvent marker, Rectangle bounds, ITimeDataProvider timeProvider, int nameSpace, GC gc) {
1721 Rectangle rect = Utils.clone(bounds);
1722 if (marker.getEntry() != null) {
1723 int index = fItemData.findItemIndex(marker.getEntry());
1724 if (index == -1) {
1725 return;
1726 }
1727 rect = getStatesRect(bounds, index, nameSpace);
1728 if (rect.y < 0 || rect.y > bounds.height) {
1729 return;
1730 }
1731 }
1732 int x0 = getXForTime(marker.getTime());
1733 int x1 = getXForTime(marker.getTime() + marker.getDuration());
1734 if (x0 > bounds.width || x1 < nameSpace) {
1735 return;
1736 }
1737 rect.x = Math.max(nameSpace, Math.min(bounds.width, x0));
1738 rect.width = Math.max(1, Math.min(bounds.width, x1) - rect.x);
1739
8910dea2
PT
1740 Color color = getColorScheme().getColor(marker.getColor());
1741 gc.setBackground(color);
1742 gc.setAlpha(color.getAlpha());
56b43ab2
PT
1743 gc.fillRectangle(rect);
1744 gc.setAlpha(255);
1745 String label = marker.getLabel();
1746 if (label != null && marker.getEntry() != null) {
1d012443 1747 label = label.substring(0, Math.min(label.indexOf('\n') != -1 ? label.indexOf('\n') : label.length(), MAX_LABEL_LENGTH));
8910dea2 1748 gc.setForeground(color);
1d012443 1749 Utils.drawText(gc, label, rect.x - gc.textExtent(label).x, rect.y, true);
56b43ab2 1750 }
55d1fc96
PT
1751 }
1752
837a2f8c
PT
1753 /**
1754 * Draw many items at once
1755 *
1756 * @param bounds
1c67fff3 1757 * The bounds of the control
837a2f8c
PT
1758 * @param timeProvider
1759 * The time provider
1760 * @param items
1761 * The array items to draw
1762 * @param topIndex
1763 * The index of the first element to draw
1764 * @param nameSpace
1c67fff3 1765 * The name space width
837a2f8c 1766 * @param gc
1c67fff3 1767 * Graphics context
837a2f8c
PT
1768 */
1769 public void drawItems(Rectangle bounds, ITimeDataProvider timeProvider,
1770 Item[] items, int topIndex, int nameSpace, GC gc) {
1771 for (int i = topIndex; i < items.length; i++) {
1772 Item item = items[i];
1773 drawItem(item, bounds, timeProvider, i, nameSpace, gc);
1774 }
837a2f8c
PT
1775 }
1776
1777 /**
1778 * Draws the item
1779 *
1c67fff3
PT
1780 * @param item
1781 * The item to draw
1782 * @param bounds
1783 * The bounds of the control
1784 * @param timeProvider
1785 * The time provider
1786 * @param i
1787 * The expanded item index
1788 * @param nameSpace
1789 * The name space width
1790 * @param gc
1791 * Graphics context
837a2f8c
PT
1792 */
1793 protected void drawItem(Item item, Rectangle bounds, ITimeDataProvider timeProvider, int i, int nameSpace, GC gc) {
83b36bb4
PT
1794 Rectangle itemRect = getItemRect(bounds, i);
1795 if (itemRect.y >= bounds.y + bounds.height) {
1796 return;
1797 }
1798
70e10acc 1799 ITimeGraphEntry entry = item.fEntry;
837a2f8c
PT
1800 long time0 = timeProvider.getTime0();
1801 long time1 = timeProvider.getTime1();
33fa1fc7 1802 long selectedTime = fTimeProvider.getSelectionEnd();
837a2f8c 1803
83b36bb4 1804 Rectangle rect = new Rectangle(nameSpace, itemRect.y, itemRect.width - nameSpace, itemRect.height);
1c67fff3 1805 if (rect.isEmpty() || (time1 <= time0)) {
837a2f8c
PT
1806 fTimeGraphProvider.postDrawEntry(entry, rect, gc);
1807 return;
1808 }
1809
f1fae91f 1810 boolean selected = item.fSelected;
837a2f8c
PT
1811 // K pixels per second
1812 double pixelsPerNanoSec = (rect.width <= RIGHT_MARGIN) ? 0 : (double) (rect.width - RIGHT_MARGIN) / (time1 - time0);
1813
70e10acc 1814 if (item.fEntry.hasTimeEvents()) {
ab0a54d5 1815 gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height));
837a2f8c 1816 fillSpace(rect, gc, selected);
83d0971d
PT
1817
1818 int margins = getMarginForHeight(rect.height);
1819 int height = rect.height - margins;
1820 int topMargin = (margins + 1) / 2;
1821 Rectangle stateRect = new Rectangle(rect.x, rect.y + topMargin, rect.width, height);
1822
1823 /* Set the font for this item */
1824 setFontForHeight(height, gc);
837a2f8c
PT
1825
1826 long maxDuration = (timeProvider.getTimeSpace() == 0) ? Long.MAX_VALUE : 1 * (time1 - time0) / timeProvider.getTimeSpace();
1827 Iterator<ITimeEvent> iterator = entry.getTimeEventsIterator(time0, time1, maxDuration);
1828
1829 int lastX = -1;
1830 while (iterator.hasNext()) {
1831 ITimeEvent event = iterator.next();
1832 int x = rect.x + (int) ((event.getTime() - time0) * pixelsPerNanoSec);
1833 int xEnd = rect.x + (int) ((event.getTime() + event.getDuration() - time0) * pixelsPerNanoSec);
1834 if (x >= rect.x + rect.width || xEnd < rect.x) {
1835 // event is out of bounds
1836 continue;
1837 }
1838 xEnd = Math.min(rect.x + rect.width, xEnd);
1839 stateRect.x = Math.max(rect.x, x);
1840 stateRect.width = Math.max(0, xEnd - stateRect.x + 1);
1841 if (stateRect.x == lastX) {
1842 stateRect.width -= 1;
1843 if (stateRect.width > 0) {
1844 gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
1845 gc.drawPoint(stateRect.x, stateRect.y - 2);
1846 stateRect.x += 1;
1847 }
837a2f8c
PT
1848 }
1849 boolean timeSelected = selectedTime >= event.getTime() && selectedTime < event.getTime() + event.getDuration();
f1fae91f 1850 if (drawState(getColorScheme(), event, stateRect, gc, selected, timeSelected)) {
b052914f 1851 lastX = stateRect.x;
ad128fd8 1852 }
837a2f8c 1853 }
ab0a54d5 1854 gc.setClipping((Rectangle) null);
837a2f8c
PT
1855 }
1856 fTimeGraphProvider.postDrawEntry(entry, rect, gc);
1857 }
1858
bec1f1ac
GB
1859 /**
1860 * Draw the links
1861 *
1862 * @param bounds
1c67fff3 1863 * The bounds of the control
bec1f1ac
GB
1864 * @param timeProvider
1865 * The time provider
1866 * @param links
6ae6c5bd 1867 * The list of link events
bec1f1ac 1868 * @param nameSpace
1c67fff3 1869 * The name space width
bec1f1ac 1870 * @param gc
1c67fff3 1871 * Graphics context
bec1f1ac
GB
1872 */
1873 public void drawLinks(Rectangle bounds, ITimeDataProvider timeProvider,
1874 List<ILinkEvent> links, int nameSpace, GC gc) {
79ec0b89
PT
1875 if (fHideArrows) {
1876 return;
1877 }
ab0a54d5 1878 gc.setClipping(new Rectangle(nameSpace, 0, bounds.width - nameSpace, bounds.height));
6ae6c5bd
PT
1879 /* the list can grow concurrently but cannot shrink */
1880 for (int i = 0; i < links.size(); i++) {
1881 drawLink(links.get(i), bounds, timeProvider, nameSpace, gc);
bec1f1ac 1882 }
ab0a54d5 1883 gc.setClipping((Rectangle) null);
bec1f1ac
GB
1884 }
1885
1886 /**
1c67fff3 1887 * Draws a link type event
bec1f1ac
GB
1888 *
1889 * @param event
1c67fff3 1890 * The link event to draw
bec1f1ac 1891 * @param bounds
1c67fff3 1892 * The bounds of the control
bec1f1ac 1893 * @param timeProvider
1c67fff3 1894 * The time provider
bec1f1ac 1895 * @param nameSpace
1c67fff3 1896 * The name space width
bec1f1ac
GB
1897 * @param gc
1898 * Graphics context
bec1f1ac
GB
1899 */
1900 protected void drawLink(ILinkEvent event, Rectangle bounds, ITimeDataProvider timeProvider, int nameSpace, GC gc) {
1053eaa6
PT
1901 drawArrow(getColorScheme(), event, getArrowRectangle(bounds, event), gc);
1902 }
1903
1904 private Rectangle getArrowRectangle(Rectangle bounds, ILinkEvent event) {
bec1f1ac
GB
1905 int srcIndex = fItemData.findItemIndex(event.getEntry());
1906 int destIndex = fItemData.findItemIndex(event.getDestinationEntry());
1907
1908 if ((srcIndex == -1) || (destIndex == -1)) {
1053eaa6 1909 return null;
bec1f1ac
GB
1910 }
1911
1053eaa6
PT
1912 Rectangle src = getStatesRect(bounds, srcIndex, fTimeProvider.getNameSpace());
1913 Rectangle dst = getStatesRect(bounds, destIndex, fTimeProvider.getNameSpace());
bec1f1ac
GB
1914
1915 int x0 = getXForTime(event.getTime());
1916 int x1 = getXForTime(event.getTime() + event.getDuration());
03ab8eeb
PT
1917
1918 // limit the x-coordinates to prevent integer overflow in calculations
1919 // and also GC.drawLine doesn't draw properly with large coordinates
1920 final int limit = Integer.MAX_VALUE / 1024;
1921 x0 = Math.max(-limit, Math.min(x0, limit));
1922 x1 = Math.max(-limit, Math.min(x1, limit));
1923
bec1f1ac
GB
1924 int y0 = src.y + src.height / 2;
1925 int y1 = dst.y + dst.height / 2;
1053eaa6 1926 return new Rectangle(x0, y0, x1 - x0, y1 - y0);
bec1f1ac
GB
1927 }
1928
1929 /**
1c67fff3 1930 * Draw an arrow
bec1f1ac
GB
1931 *
1932 * @param colors
1933 * Color scheme
1934 * @param event
1c67fff3 1935 * Time event for which we're drawing the arrow
bec1f1ac 1936 * @param rect
1c67fff3 1937 * The arrow rectangle
bec1f1ac
GB
1938 * @param gc
1939 * Graphics context
1c67fff3 1940 * @return true if the arrow was drawn
bec1f1ac
GB
1941 */
1942 protected boolean drawArrow(TimeGraphColorScheme colors, ITimeEvent event,
1943 Rectangle rect, GC gc) {
1944
a5e66312
PT
1945 if (rect == null) {
1946 return false;
1947 }
bec1f1ac
GB
1948 int colorIdx = fTimeGraphProvider.getStateTableIndex(event);
1949 if (colorIdx < 0) {
1950 return false;
1951 }
1952 boolean visible = ((rect.height == 0) && (rect.width == 0)) ? false : true;
1953
1954 if (visible) {
1955 Color stateColor = null;
1956 if (colorIdx < fEventColorMap.length) {
1957 stateColor = fEventColorMap[colorIdx];
1958 } else {
1959 stateColor = Display.getDefault().getSystemColor(SWT.COLOR_BLACK);
1960 }
1961
1962 gc.setForeground(stateColor);
1963 gc.setBackground(stateColor);
1964
1965 /* Draw the arrow */
1966 gc.drawLine(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
1967 drawArrowHead(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, gc);
1968
1969 }
1970 fTimeGraphProvider.postDrawEvent(event, rect, gc);
1971 return visible;
1972 }
1973
1974 /*
1975 * @author Francis Giraldeau
1976 *
1977 * Inspiration:
1978 * http://stackoverflow.com/questions/3010803/draw-arrow-on-line-algorithm
1979 *
1980 * The algorithm was taken from this site, not the code itself
1981 */
1982 private static void drawArrowHead(int x0, int y0, int x1, int y1, GC gc)
1983 {
1984 int factor = 10;
1985 double cos = 0.9510;
1986 double sin = 0.3090;
03ab8eeb
PT
1987 long lenx = x1 - x0;
1988 long leny = y1 - y0;
bec1f1ac
GB
1989 double len = Math.sqrt(lenx * lenx + leny * leny);
1990
1991 double dx = factor * lenx / len;
1992 double dy = factor * leny / len;
1993 int end1X = (int) Math.round((x1 - (dx * cos + dy * -sin)));
1994 int end1Y = (int) Math.round((y1 - (dx * sin + dy * cos)));
1995 int end2X = (int) Math.round((x1 - (dx * cos + dy * sin)));
1996 int end2Y = (int) Math.round((y1 - (dx * -sin + dy * cos)));
1997 int[] arrow = new int[] { x1, y1, end1X, end1Y, end2X, end2Y, x1, y1 };
1998 gc.fillPolygon(arrow);
1999 }
2000
a0a88f65
AM
2001 /**
2002 * Draw the name of an item.
2003 *
2004 * @param item
2005 * Item object
2006 * @param bounds
1c67fff3 2007 * The bounds of the item's name space
a0a88f65
AM
2008 * @param gc
2009 * Graphics context
2010 */
837a2f8c 2011 protected void drawName(Item item, Rectangle bounds, GC gc) {
70e10acc 2012 boolean hasTimeEvents = item.fEntry.hasTimeEvents();
837a2f8c
PT
2013
2014 // No name to be drawn
f1fae91f 2015 if (fTimeProvider.getNameSpace() == 0) {
837a2f8c
PT
2016 return;
2017 }
2018
83d0971d
PT
2019 int height = bounds.height - getMarginForHeight(bounds.height);
2020 setFontForHeight(height, gc);
2021
f1fae91f
PT
2022 int leftMargin = MARGIN + item.fLevel * EXPAND_SIZE;
2023 if (item.fHasChildren) {
2024 gc.setForeground(getColorScheme().getFgColorGroup(false, false));
2025 gc.setBackground(getColorScheme().getBkColor(false, false, false));
837a2f8c
PT
2026 Rectangle rect = Utils.clone(bounds);
2027 rect.x += leftMargin;
2028 rect.y += (bounds.height - EXPAND_SIZE) / 2;
2029 rect.width = EXPAND_SIZE;
2030 rect.height = EXPAND_SIZE;
2031 gc.fillRectangle(rect);
2032 gc.drawRectangle(rect.x, rect.y, rect.width - 1, rect.height - 1);
2033 int midy = rect.y + rect.height / 2;
2034 gc.drawLine(rect.x + 2, midy, rect.x + rect.width - 3, midy);
f1fae91f 2035 if (!item.fExpanded) {
837a2f8c
PT
2036 int midx = rect.x + rect.width / 2;
2037 gc.drawLine(midx, rect.y + 2, midx, rect.y + rect.height - 3);
2038 }
2039 }
2040 leftMargin += EXPAND_SIZE + MARGIN;
2041
70e10acc 2042 Image img = fTimeGraphProvider.getItemImage(item.fEntry);
837a2f8c
PT
2043 if (img != null) {
2044 // draw icon
2045 int imgHeight = img.getImageData().height;
2046 int imgWidth = img.getImageData().width;
2047 int x = leftMargin;
2048 int y = bounds.y + (bounds.height - imgHeight) / 2;
2049 gc.drawImage(img, x, y);
2050 leftMargin += imgWidth + MARGIN;
2051 }
f1fae91f 2052 String name = item.fName;
837a2f8c 2053 Point size = gc.stringExtent(name);
f1fae91f
PT
2054 if (fIdealNameSpace < leftMargin + size.x + MARGIN) {
2055 fIdealNameSpace = leftMargin + size.x + MARGIN;
837a2f8c
PT
2056 }
2057 if (hasTimeEvents) {
2058 // cut long string with "..."
2059 int width = bounds.width - leftMargin;
2060 int cuts = 0;
2061 while (size.x > width && name.length() > 1) {
2062 cuts++;
2063 name = name.substring(0, name.length() - 1);
2064 size = gc.stringExtent(name + "..."); //$NON-NLS-1$
2065 }
2066 if (cuts > 0) {
2067 name += "..."; //$NON-NLS-1$
2068 }
2069 }
2070 Rectangle rect = Utils.clone(bounds);
2071 rect.x += leftMargin;
2072 rect.width -= leftMargin;
2073 // draw text
2074 if (rect.width > 0) {
2075 rect.y += (bounds.height - gc.stringExtent(name).y) / 2;
f1fae91f 2076 gc.setForeground(getColorScheme().getFgColor(item.fSelected, fIsInFocus));
837a2f8c
PT
2077 int textWidth = Utils.drawText(gc, name, rect, true);
2078 leftMargin += textWidth + MARGIN;
837a2f8c
PT
2079
2080 if (hasTimeEvents) {
2081 // draw middle line
1c67fff3
PT
2082 rect.x = bounds.x + leftMargin;
2083 rect.y = bounds.y;
2084 rect.width = bounds.width - rect.x;
2085 drawMidLine(rect, gc);
837a2f8c
PT
2086 }
2087 }
2088 }
2089
a0a88f65
AM
2090 /**
2091 * Draw the state (color fill)
2092 *
2093 * @param colors
2094 * Color scheme
2095 * @param event
2096 * Time event for which we're drawing the state
2097 * @param rect
1c67fff3 2098 * The state rectangle
a0a88f65
AM
2099 * @param gc
2100 * Graphics context
2101 * @param selected
2102 * Is this time event currently selected (so it appears
2103 * highlighted)
2104 * @param timeSelected
2105 * Is the timestamp currently selected
ad128fd8 2106 * @return true if the state was drawn
a0a88f65 2107 */
ad128fd8 2108 protected boolean drawState(TimeGraphColorScheme colors, ITimeEvent event,
837a2f8c
PT
2109 Rectangle rect, GC gc, boolean selected, boolean timeSelected) {
2110
2111 int colorIdx = fTimeGraphProvider.getStateTableIndex(event);
ad128fd8
PT
2112 if (colorIdx < 0 && colorIdx != ITimeGraphPresentationProvider.TRANSPARENT) {
2113 return false;
837a2f8c
PT
2114 }
2115 boolean visible = rect.width == 0 ? false : true;
b052914f 2116 rect.width = Math.max(1, rect.width);
351a2391
XR
2117 Color black = Display.getDefault().getSystemColor(SWT.COLOR_BLACK);
2118 gc.setForeground(black);
837a2f8c 2119
b052914f
PT
2120 if (colorIdx == ITimeGraphPresentationProvider.TRANSPARENT) {
2121 if (visible) {
ad128fd8 2122 // Only draw the top and bottom borders
ad128fd8
PT
2123 gc.drawLine(rect.x, rect.y, rect.x + rect.width - 1, rect.y);
2124 gc.drawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width - 1, rect.y + rect.height - 1);
2125 if (rect.width == 1) {
ad128fd8
PT
2126 gc.drawPoint(rect.x, rect.y - 2);
2127 }
837a2f8c 2128 }
b052914f
PT
2129 fTimeGraphProvider.postDrawEvent(event, rect, gc);
2130 return false;
2131 }
2132 Color stateColor = null;
2133 if (colorIdx < fEventColorMap.length) {
2134 stateColor = fEventColorMap[colorIdx];
2135 } else {
2136 stateColor = black;
2137 }
837a2f8c 2138
b052914f
PT
2139 boolean reallySelected = timeSelected && selected;
2140 // fill all rect area
2141 gc.setBackground(stateColor);
2142 if (visible) {
837a2f8c 2143 gc.fillRectangle(rect);
b052914f
PT
2144 } else if (fBlendSubPixelEvents) {
2145 gc.setAlpha(128);
2146 gc.fillRectangle(rect);
2147 gc.setAlpha(255);
2148 }
837a2f8c 2149
b052914f
PT
2150 if (reallySelected) {
2151 gc.drawLine(rect.x, rect.y - 1, rect.x + rect.width - 1, rect.y - 1);
2152 gc.drawLine(rect.x, rect.y + rect.height, rect.x + rect.width - 1, rect.y + rect.height);
2153 }
2154 if (!visible) {
837a2f8c 2155 gc.drawPoint(rect.x, rect.y - 2);
837a2f8c
PT
2156 }
2157 fTimeGraphProvider.postDrawEvent(event, rect, gc);
ad128fd8 2158 return visible;
837a2f8c
PT
2159 }
2160
a0a88f65 2161 /**
83b36bb4 2162 * Fill an item's states rectangle
a0a88f65
AM
2163 *
2164 * @param rect
1c67fff3 2165 * The states rectangle
a0a88f65
AM
2166 * @param gc
2167 * Graphics context
2168 * @param selected
83b36bb4 2169 * true if the item is selected
a0a88f65 2170 */
837a2f8c 2171 protected void fillSpace(Rectangle rect, GC gc, boolean selected) {
1c67fff3
PT
2172 /* Nothing to draw */
2173 }
2174
2175 /**
2176 * Draw a line at the middle height of a rectangle
2177 *
2178 * @param rect
2179 * The rectangle
2180 * @param gc
2181 * Graphics context
2182 */
2183 private void drawMidLine(Rectangle rect, GC gc) {
f1fae91f 2184 gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.MID_LINE));
837a2f8c
PT
2185 int midy = rect.y + rect.height / 2;
2186 gc.drawLine(rect.x, midy, rect.x + rect.width, midy);
2187 }
2188
83d0971d
PT
2189 private static int getMarginForHeight(int height) {
2190 /*
2191 * State rectangle is smaller than the item bounds when height is > 4.
2192 * Don't use any margin if the height is below or equal that threshold.
2193 * Use a maximum of 6 pixels for both margins, otherwise try to use 13
2194 * pixels for the state height, but with a minimum margin of 1.
2195 */
2196 final int MARGIN_THRESHOLD = 4;
2197 final int PREFERRED_HEIGHT = 13;
2198 final int MIN_MARGIN = 1;
2199 final int MAX_MARGIN = 6;
2200 return height <= MARGIN_THRESHOLD ? 0 :
2201 Math.max(Math.min(height - PREFERRED_HEIGHT, MAX_MARGIN), MIN_MARGIN);
2202 }
2203
2204 private void setFontForHeight(int pixels, GC gc) {
2205 /* convert font height from pixels to points */
2206 int height = Math.max(pixels * PPI / DPI, 1);
2207 Font font = fFonts.get(height);
2208 if (font == null) {
2209 FontData fontData = gc.getFont().getFontData()[0];
2210 fontData.setHeight(height);
2211 font = new Font(gc.getDevice(), fontData);
2212 fFonts.put(height, font);
2213 }
2214 gc.setFont(font);
2215 }
2216
837a2f8c
PT
2217 @Override
2218 public void keyTraversed(TraverseEvent e) {
2219 if ((e.detail == SWT.TRAVERSE_TAB_NEXT) || (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)) {
2220 e.doit = true;
2221 }
2222 }
2223
2224 @Override
2225 public void keyPressed(KeyEvent e) {
2226 int idx = -1;
f1fae91f 2227 if (fItemData.fExpandedItems.length == 0) {
837a2f8c
PT
2228 return;
2229 }
2230 if (SWT.HOME == e.keyCode) {
2231 idx = 0;
2232 } else if (SWT.END == e.keyCode) {
f1fae91f 2233 idx = fItemData.fExpandedItems.length - 1;
837a2f8c
PT
2234 } else if (SWT.ARROW_DOWN == e.keyCode) {
2235 idx = getSelectedIndex();
2236 if (idx < 0) {
2237 idx = 0;
f1fae91f 2238 } else if (idx < fItemData.fExpandedItems.length - 1) {
837a2f8c
PT
2239 idx++;
2240 }
2241 } else if (SWT.ARROW_UP == e.keyCode) {
2242 idx = getSelectedIndex();
2243 if (idx < 0) {
2244 idx = 0;
2245 } else if (idx > 0) {
2246 idx--;
2247 }
33fa1fc7
PT
2248 } else if (SWT.ARROW_LEFT == e.keyCode && fDragState == DRAG_NONE) {
2249 boolean extend = (e.stateMask & SWT.SHIFT) != 0;
2250 selectPrevEvent(extend);
2251 } else if (SWT.ARROW_RIGHT == e.keyCode && fDragState == DRAG_NONE) {
2252 boolean extend = (e.stateMask & SWT.SHIFT) != 0;
2253 selectNextEvent(extend);
837a2f8c
PT
2254 } else if (SWT.PAGE_DOWN == e.keyCode) {
2255 int page = countPerPage();
2256 idx = getSelectedIndex();
2257 if (idx < 0) {
2258 idx = 0;
2259 }
2260 idx += page;
f1fae91f
PT
2261 if (idx >= fItemData.fExpandedItems.length) {
2262 idx = fItemData.fExpandedItems.length - 1;
837a2f8c
PT
2263 }
2264 } else if (SWT.PAGE_UP == e.keyCode) {
2265 int page = countPerPage();
2266 idx = getSelectedIndex();
2267 if (idx < 0) {
2268 idx = 0;
2269 }
2270 idx -= page;
2271 if (idx < 0) {
2272 idx = 0;
2273 }
2274 } else if (SWT.CR == e.keyCode) {
2275 idx = getSelectedIndex();
2276 if (idx >= 0) {
f1fae91f 2277 if (fItemData.fExpandedItems[idx].fHasChildren) {
837a2f8c
PT
2278 toggle(idx);
2279 } else {
2280 fireDefaultSelection();
2281 }
2282 }
2283 idx = -1;
3bd20aa6 2284 } else if ((e.character == '+' || e.character == '=') && ((e.stateMask & SWT.CTRL) != 0)) {
48072ae3 2285 fVerticalZoomAlignEntry = getVerticalZoomAlignSelection();
83d0971d 2286 verticalZoom(true);
48072ae3
PT
2287 if (fVerticalZoomAlignEntry != null) {
2288 setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
2289 }
3bd20aa6 2290 } else if (e.character == '-' && ((e.stateMask & SWT.CTRL) != 0)) {
48072ae3 2291 fVerticalZoomAlignEntry = getVerticalZoomAlignSelection();
83d0971d 2292 verticalZoom(false);
48072ae3
PT
2293 if (fVerticalZoomAlignEntry != null) {
2294 setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
2295 }
3bd20aa6 2296 } else if (e.character == '0' && ((e.stateMask & SWT.CTRL) != 0)) {
48072ae3 2297 fVerticalZoomAlignEntry = getVerticalZoomAlignSelection();
83d0971d 2298 resetVerticalZoom();
48072ae3
PT
2299 if (fVerticalZoomAlignEntry != null) {
2300 setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
2301 }
837a2f8c
PT
2302 }
2303 if (idx >= 0) {
2304 selectItem(idx, false);
2305 fireSelectionChanged();
2306 }
0fcf3b09
PT
2307 int x = toControl(e.display.getCursorLocation()).x;
2308 updateCursor(x, e.stateMask | e.keyCode);
837a2f8c
PT
2309 }
2310
2311 @Override
2312 public void keyReleased(KeyEvent e) {
0fcf3b09
PT
2313 int x = toControl(e.display.getCursorLocation()).x;
2314 updateCursor(x, e.stateMask & ~e.keyCode);
837a2f8c
PT
2315 }
2316
2317 @Override
2318 public void focusGained(FocusEvent e) {
f1fae91f 2319 fIsInFocus = true;
837a2f8c 2320 redraw();
33fa1fc7 2321 updateStatusLine(STATUS_WITHOUT_CURSOR_TIME);
837a2f8c
PT
2322 }
2323
2324 @Override
2325 public void focusLost(FocusEvent e) {
f1fae91f 2326 fIsInFocus = false;
f1fae91f 2327 if (DRAG_NONE != fDragState) {
837a2f8c 2328 setCapture(false);
f1fae91f 2329 fDragState = DRAG_NONE;
837a2f8c
PT
2330 }
2331 redraw();
0fcf3b09 2332 updateStatusLine(NO_STATUS);
837a2f8c
PT
2333 }
2334
2335 /**
2336 * @return If the current view is focused
2337 */
2338 public boolean isInFocus() {
f1fae91f 2339 return fIsInFocus;
837a2f8c
PT
2340 }
2341
2342 /**
2343 * Provide the possibility to control the wait cursor externally e.g. data
2344 * requests in progress
2345 *
2346 * @param waitInd Should we wait indefinitely?
2347 */
2348 public void waitCursor(boolean waitInd) {
2349 // Update cursor as indicated
2350 if (waitInd) {
f1fae91f 2351 setCursor(fWaitCursor);
837a2f8c
PT
2352 } else {
2353 setCursor(null);
837a2f8c 2354 }
0fcf3b09 2355 }
837a2f8c 2356
0fcf3b09
PT
2357 private void updateCursor(int x, int stateMask) {
2358 // if Wait cursor not active, check for the need to change the cursor
2359 if (getCursor() == fWaitCursor) {
2360 return;
2361 }
2362 Cursor cursor = null;
2363 if (fDragState == DRAG_SPLIT_LINE) {
2364 } else if (fDragState == DRAG_SELECTION) {
2365 cursor = fResizeCursor;
2366 } else if (fDragState == DRAG_TRACE_ITEM) {
2367 cursor = fDragCursor;
2368 } else if (fDragState == DRAG_ZOOM) {
2369 cursor = fZoomCursor;
2370 } else if ((stateMask & SWT.MODIFIER_MASK) == SWT.CTRL) {
2371 cursor = fDragCursor;
2372 } else if ((stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) {
2373 cursor = fResizeCursor;
baf92cac
AM
2374 } else if (!isOverSplitLine(x)) {
2375 long selectionBegin = fTimeProvider.getSelectionBegin();
2376 long selectionEnd = fTimeProvider.getSelectionEnd();
2377 int xBegin = getXForTime(selectionBegin);
2378 int xEnd = getXForTime(selectionEnd);
2379 if (Math.abs(x - xBegin) < SNAP_WIDTH || Math.abs(x - xEnd) < SNAP_WIDTH) {
2380 cursor = fResizeCursor;
0fcf3b09
PT
2381 }
2382 }
2383 if (getCursor() != cursor) {
2384 setCursor(cursor);
2385 }
837a2f8c
PT
2386 }
2387
4aa2593c
PT
2388 /**
2389 * Update the status line following a change of selection.
2390 *
2391 * @since 2.0
2392 */
2393 public void updateStatusLine() {
2394 updateStatusLine(STATUS_WITHOUT_CURSOR_TIME);
2395 }
2396
0fcf3b09 2397 private void updateStatusLine(int x) {
0fab12b0
PT
2398 // use the time provider of the time graph scale for the status line
2399 ITimeDataProvider tdp = fTimeGraphScale.getTimeProvider();
2400 if (fStatusLineManager == null || null == tdp ||
2401 tdp.getTime0() == tdp.getTime1()) {
0fcf3b09
PT
2402 return;
2403 }
0fab12b0
PT
2404 TimeFormat tf = tdp.getTimeFormat();
2405 Resolution res = Resolution.NANOSEC;
0fcf3b09 2406 StringBuilder message = new StringBuilder();
33fa1fc7
PT
2407 if ((x >= 0 || x == STATUS_WITHOUT_CURSOR_TIME) && fDragState == DRAG_NONE) {
2408 if (x != STATUS_WITHOUT_CURSOR_TIME) {
2409 long time = getTimeAtX(x);
2410 if (time >= 0) {
2411 if (tdp instanceof ITimeDataProviderConverter) {
2412 time = ((ITimeDataProviderConverter) tdp).convertTime(time);
2413 }
2414 message.append(NLS.bind("T: {0}{1} ", //$NON-NLS-1$
0fab12b0 2415 new Object[] {
33fa1fc7
PT
2416 tf == TimeFormat.CALENDAR ? Utils.formatDate(time) + ' ' : "", //$NON-NLS-1$
2417 Utils.formatTime(time, tf, res)
0fab12b0 2418 }));
0fcf3b09
PT
2419 }
2420 }
33fa1fc7
PT
2421 long selectionBegin = tdp.getSelectionBegin();
2422 long selectionEnd = tdp.getSelectionEnd();
2423 message.append(NLS.bind("T1: {0}{1}", //$NON-NLS-1$
2424 new Object[] {
2425 tf == TimeFormat.CALENDAR ? Utils.formatDate(selectionBegin) + ' ' : "", //$NON-NLS-1$
2426 Utils.formatTime(selectionBegin, tf, res)
2427 }));
2428 if (selectionBegin != selectionEnd) {
2429 message.append(NLS.bind(" T2: {0}{1} \u0394: {2}", //$NON-NLS-1$
2430 new Object[] {
2431 tf == TimeFormat.CALENDAR ? Utils.formatDate(selectionEnd) + ' ' : "", //$NON-NLS-1$
2432 Utils.formatTime(selectionEnd, tf, res),
2433 Utils.formatDelta(selectionEnd - selectionBegin, tf, res)
2434 }));
2435 }
0fcf3b09 2436 } else if (fDragState == DRAG_SELECTION || fDragState == DRAG_ZOOM) {
85203d74
PT
2437 long time0 = fDragBeginMarker ? getTimeAtX(fDragX0) : fDragTime0;
2438 long time = fDragBeginMarker ? fDragTime0 : getTimeAtX(fDragX);
0fab12b0
PT
2439 if (tdp instanceof ITimeDataProviderConverter) {
2440 time0 = ((ITimeDataProviderConverter) tdp).convertTime(time0);
2441 time = ((ITimeDataProviderConverter) tdp).convertTime(time);
837a2f8c 2442 }
0fab12b0
PT
2443 message.append(NLS.bind("T1: {0}{1} T2: {2}{3} \u0394: {4}", //$NON-NLS-1$
2444 new Object[] {
85203d74
PT
2445 tf == TimeFormat.CALENDAR ? Utils.formatDate(time0) + ' ' : "", //$NON-NLS-1$
2446 Utils.formatTime(time0, tf, res),
2447 tf == TimeFormat.CALENDAR ? Utils.formatDate(time) + ' ' : "", //$NON-NLS-1$
2448 Utils.formatTime(time, tf, res),
2449 Utils.formatDelta(time - time0, tf, res)
0fab12b0 2450 }));
837a2f8c 2451 }
0fcf3b09 2452 fStatusLineManager.setMessage(message.toString());
837a2f8c
PT
2453 }
2454
2455 @Override
2456 public void mouseMove(MouseEvent e) {
f1fae91f 2457 if (null == fTimeProvider) {
837a2f8c
PT
2458 return;
2459 }
b698ec63 2460 Point size = getSize();
f1fae91f
PT
2461 if (DRAG_TRACE_ITEM == fDragState) {
2462 int nameWidth = fTimeProvider.getNameSpace();
0fcf3b09
PT
2463 if (e.x > nameWidth && size.x > nameWidth && fDragX != e.x) {
2464 fDragX = e.x;
f1fae91f
PT
2465 double pixelsPerNanoSec = (size.x - nameWidth <= RIGHT_MARGIN) ? 0 : (double) (size.x - nameWidth - RIGHT_MARGIN) / (fTime1bak - fTime0bak);
2466 long timeDelta = (long) ((pixelsPerNanoSec == 0) ? 0 : ((fDragX - fDragX0) / pixelsPerNanoSec));
2467 long time1 = fTime1bak - timeDelta;
2468 long maxTime = fTimeProvider.getMaxTime();
837a2f8c
PT
2469 if (time1 > maxTime) {
2470 time1 = maxTime;
2471 }
f1fae91f
PT
2472 long time0 = time1 - (fTime1bak - fTime0bak);
2473 if (time0 < fTimeProvider.getMinTime()) {
2474 time0 = fTimeProvider.getMinTime();
2475 time1 = time0 + (fTime1bak - fTime0bak);
837a2f8c 2476 }
88de10c6 2477 fTimeProvider.setStartFinishTimeNotify(time0, time1);
837a2f8c 2478 }
f1fae91f
PT
2479 } else if (DRAG_SPLIT_LINE == fDragState) {
2480 fDragX = e.x;
2481 fTimeProvider.setNameSpace(e.x);
d2e4afa7 2482 TmfSignalManager.dispatchSignal(new TmfTimeViewAlignmentSignal(this, getTimeViewAlignmentInfo()));
0fcf3b09 2483 } else if (DRAG_SELECTION == fDragState) {
85203d74
PT
2484 if (fDragBeginMarker) {
2485 fDragX0 = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), size.x - RIGHT_MARGIN);
2486 } else {
2487 fDragX = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), size.x - RIGHT_MARGIN);
2488 }
baf92cac
AM
2489 redraw();
2490 fTimeGraphScale.setDragRange(fDragX0, fDragX);
f2ca0f69 2491 fireDragSelectionChanged(getTimeAtX(fDragX0), getTimeAtX(fDragX));
f1fae91f
PT
2492 } else if (DRAG_ZOOM == fDragState) {
2493 fDragX = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), size.x - RIGHT_MARGIN);
5b2b9bd7 2494 redraw();
0fcf3b09 2495 fTimeGraphScale.setDragRange(fDragX0, fDragX);
f1fae91f 2496 } else if (DRAG_NONE == fDragState) {
837a2f8c 2497 boolean mouseOverSplitLine = isOverSplitLine(e.x);
f1fae91f 2498 if (fMouseOverSplitLine != mouseOverSplitLine) {
837a2f8c
PT
2499 redraw();
2500 }
f1fae91f 2501 fMouseOverSplitLine = mouseOverSplitLine;
837a2f8c 2502 }
0fcf3b09
PT
2503 updateCursor(e.x, e.stateMask);
2504 updateStatusLine(e.x);
837a2f8c
PT
2505 }
2506
2507 @Override
2508 public void mouseDoubleClick(MouseEvent e) {
f1fae91f 2509 if (null == fTimeProvider) {
837a2f8c
PT
2510 return;
2511 }
5b2b9bd7 2512 if (1 == e.button && (e.stateMask & SWT.BUTTON_MASK) == 0) {
f1fae91f
PT
2513 if (isOverSplitLine(e.x) && fTimeProvider.getNameSpace() != 0) {
2514 fTimeProvider.setNameSpace(fIdealNameSpace);
837a2f8c 2515 boolean mouseOverSplitLine = isOverSplitLine(e.x);
f1fae91f 2516 if (fMouseOverSplitLine != mouseOverSplitLine) {
837a2f8c
PT
2517 redraw();
2518 }
f1fae91f 2519 fMouseOverSplitLine = mouseOverSplitLine;
d2e4afa7 2520 TmfSignalManager.dispatchSignal(new TmfTimeViewAlignmentSignal(this, getTimeViewAlignmentInfo()));
837a2f8c
PT
2521 return;
2522 }
2523 int idx = getItemIndexAtY(e.y);
2524 if (idx >= 0) {
2525 selectItem(idx, false);
2526 fireDefaultSelection();
2527 }
2528 }
2529 }
2530
2531 @Override
2532 public void mouseDown(MouseEvent e) {
407bfdd5
PT
2533 if (fDragState != DRAG_NONE || null == fTimeProvider ||
2534 fTimeProvider.getTime0() == fTimeProvider.getTime1() ||
b698ec63 2535 getSize().x - fTimeProvider.getNameSpace() <= 0) {
837a2f8c
PT
2536 return;
2537 }
2538 int idx;
0fcf3b09 2539 if (1 == e.button && (e.stateMask & SWT.MODIFIER_MASK) == 0) {
f1fae91f
PT
2540 int nameSpace = fTimeProvider.getNameSpace();
2541 if (nameSpace != 0 && isOverSplitLine(e.x)) {
2542 fDragState = DRAG_SPLIT_LINE;
0fcf3b09 2543 fDragButton = e.button;
f1fae91f
PT
2544 fDragX = e.x;
2545 fDragX0 = fDragX;
2546 fTime0bak = fTimeProvider.getTime0();
2547 fTime1bak = fTimeProvider.getTime1();
2548 redraw();
0fcf3b09 2549 updateCursor(e.x, e.stateMask);
f1fae91f 2550 return;
837a2f8c 2551 }
0fcf3b09
PT
2552 }
2553 if (1 == e.button && ((e.stateMask & SWT.MODIFIER_MASK) == 0 || (e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT)) {
2554 int nameSpace = fTimeProvider.getNameSpace();
837a2f8c
PT
2555 idx = getItemIndexAtY(e.y);
2556 if (idx >= 0) {
f1fae91f
PT
2557 Item item = fItemData.fExpandedItems[idx];
2558 if (item.fHasChildren && e.x < nameSpace && e.x < MARGIN + (item.fLevel + 1) * EXPAND_SIZE) {
837a2f8c 2559 toggle(idx);
0fcf3b09 2560 return;
837a2f8c
PT
2561 }
2562 selectItem(idx, false);
2563 fireSelectionChanged();
2564 } else {
2565 selectItem(idx, false); // clear selection
837a2f8c
PT
2566 fireSelectionChanged();
2567 }
0fcf3b09
PT
2568 long hitTime = getTimeAtX(e.x);
2569 if (hitTime >= 0) {
2570 setCapture(true);
2571
2572 fDragState = DRAG_SELECTION;
85203d74 2573 fDragBeginMarker = false;
0fcf3b09
PT
2574 fDragButton = e.button;
2575 fDragX = e.x;
2576 fDragX0 = fDragX;
2577 fDragTime0 = getTimeAtX(fDragX0);
baf92cac
AM
2578 long selectionBegin = fTimeProvider.getSelectionBegin();
2579 long selectionEnd = fTimeProvider.getSelectionEnd();
2580 int xBegin = getXForTime(selectionBegin);
2581 int xEnd = getXForTime(selectionEnd);
2582 if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) {
2583 long time = getTimeAtX(e.x);
2584 if (Math.abs(time - selectionBegin) < Math.abs(time - selectionEnd)) {
85203d74
PT
2585 fDragBeginMarker = true;
2586 fDragX = xEnd;
2587 fDragX0 = e.x;
baf92cac 2588 fDragTime0 = selectionEnd;
0fcf3b09 2589 } else {
baf92cac
AM
2590 fDragX0 = xBegin;
2591 fDragTime0 = selectionBegin;
2592 }
2593 } else {
2594 long time = getTimeAtX(e.x);
2595 if (Math.abs(e.x - xBegin) < SNAP_WIDTH && Math.abs(time - selectionBegin) <= Math.abs(time - selectionEnd)) {
85203d74
PT
2596 fDragBeginMarker = true;
2597 fDragX = xEnd;
2598 fDragX0 = e.x;
baf92cac
AM
2599 fDragTime0 = selectionEnd;
2600 } else if (Math.abs(e.x - xEnd) < SNAP_WIDTH && Math.abs(time - selectionEnd) <= Math.abs(time - selectionBegin)) {
2601 fDragX0 = xBegin;
2602 fDragTime0 = selectionBegin;
0fcf3b09
PT
2603 }
2604 }
2605 fTime0bak = fTimeProvider.getTime0();
2606 fTime1bak = fTimeProvider.getTime1();
2607 redraw();
2608 updateCursor(e.x, e.stateMask);
2609 fTimeGraphScale.setDragRange(fDragX0, fDragX);
2610 }
2611 } else if (2 == e.button || (1 == e.button && (e.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL)) {
2612 long hitTime = getTimeAtX(e.x);
2613 if (hitTime > 0) {
2614 setCapture(true);
2615 fDragState = DRAG_TRACE_ITEM;
2616 fDragButton = e.button;
2617 fDragX = e.x;
2618 fDragX0 = fDragX;
2619 fTime0bak = fTimeProvider.getTime0();
2620 fTime1bak = fTimeProvider.getTime1();
2621 updateCursor(e.x, e.stateMask);
2622 }
5b2b9bd7 2623 } else if (3 == e.button) {
14b7dd60
MK
2624 if (e.x >= fTimeProvider.getNameSpace()) {
2625 setCapture(true);
2626 fDragX = Math.min(Math.max(e.x, fTimeProvider.getNameSpace()), getSize().x - RIGHT_MARGIN);
2627 fDragX0 = fDragX;
2628 fDragTime0 = getTimeAtX(fDragX0);
2629 fDragState = DRAG_ZOOM;
2630 fDragButton = e.button;
2631 redraw();
2632 updateCursor(e.x, e.stateMask);
2633 fTimeGraphScale.setDragRange(fDragX0, fDragX);
2989c909
BH
2634 } else {
2635 idx = getItemIndexAtY(e.y);
2636 selectItem(idx, false);
2637 fireSelectionChanged();
14b7dd60 2638 }
837a2f8c
PT
2639 }
2640 }
2641
2642 @Override
2643 public void mouseUp(MouseEvent e) {
0b5a90a0
PT
2644 if (fPendingMenuDetectEvent != null && e.button == 3) {
2645 menuDetected(fPendingMenuDetectEvent);
2646 }
f1fae91f 2647 if (DRAG_NONE != fDragState) {
837a2f8c 2648 setCapture(false);
0fcf3b09
PT
2649 if (e.button == fDragButton && DRAG_TRACE_ITEM == fDragState) {
2650 if (fDragX != fDragX0) {
2651 fTimeProvider.notifyStartFinishTime();
2652 }
2653 fDragState = DRAG_NONE;
2654 } else if (e.button == fDragButton && DRAG_SPLIT_LINE == fDragState) {
2655 fDragState = DRAG_NONE;
2656 redraw();
2657 } else if (e.button == fDragButton && DRAG_SELECTION == fDragState) {
2658 if (fDragX == fDragX0) { // click without selecting anything
837a2f8c 2659 long time = getTimeAtX(e.x);
f1fae91f 2660 fTimeProvider.setSelectedTimeNotify(time, false);
e1725a05 2661 } else {
85203d74
PT
2662 long time0 = fDragBeginMarker ? getTimeAtX(fDragX0) : fDragTime0;
2663 long time1 = fDragBeginMarker ? fDragTime0 : getTimeAtX(fDragX);
84c8aef7 2664 fTimeProvider.setSelectionRangeNotify(time0, time1, false);
837a2f8c 2665 }
f1fae91f 2666 fDragState = DRAG_NONE;
837a2f8c 2667 redraw();
0fcf3b09
PT
2668 fTimeGraphScale.setDragRange(-1, -1);
2669 } else if (e.button == fDragButton && DRAG_ZOOM == fDragState) {
f1fae91f
PT
2670 int nameWidth = fTimeProvider.getNameSpace();
2671 if (Math.max(fDragX, fDragX0) > nameWidth && fDragX != fDragX0) {
2672 long time0 = getTimeAtX(fDragX0);
2673 long time1 = getTimeAtX(fDragX);
5b2b9bd7 2674 if (time0 < time1) {
f1fae91f 2675 fTimeProvider.setStartFinishTimeNotify(time0, time1);
5b2b9bd7 2676 } else {
f1fae91f 2677 fTimeProvider.setStartFinishTimeNotify(time1, time0);
5b2b9bd7
PT
2678 }
2679 } else {
2680 redraw();
2681 }
f1fae91f 2682 fDragState = DRAG_NONE;
0fcf3b09 2683 fTimeGraphScale.setDragRange(-1, -1);
837a2f8c 2684 }
837a2f8c 2685 }
0fcf3b09
PT
2686 updateCursor(e.x, e.stateMask);
2687 updateStatusLine(e.x);
837a2f8c
PT
2688 }
2689
2690 @Override
2691 public void mouseEnter(MouseEvent e) {
2692 }
2693
2694 @Override
2695 public void mouseExit(MouseEvent e) {
f1fae91f
PT
2696 if (fMouseOverSplitLine) {
2697 fMouseOverSplitLine = false;
837a2f8c
PT
2698 redraw();
2699 }
33fa1fc7 2700 updateStatusLine(STATUS_WITHOUT_CURSOR_TIME);
837a2f8c
PT
2701 }
2702
2703 @Override
2704 public void mouseHover(MouseEvent e) {
2705 }
2706
2707 @Override
2708 public void mouseScrolled(MouseEvent e) {
93cfc823 2709 if (fDragState != DRAG_NONE || e.count == 0) {
837a2f8c
PT
2710 return;
2711 }
494c9b22
PT
2712
2713 /*
2714 * On some platforms the mouse scroll event is sent to the
2715 * control that has focus even if it is not under the cursor.
2716 * Handle the event only if over the time graph control.
2717 */
2718 Point size = getSize();
2719 Rectangle bounds = new Rectangle(0, 0, size.x, size.y);
2720 if (!bounds.contains(e.x, e.y)) {
2721 return;
2722 }
2723
494d213d 2724 boolean horizontalZoom = false;
b698ec63 2725 boolean horizontalScroll = false;
494d213d 2726 boolean verticalZoom = false;
494c9b22
PT
2727 boolean verticalScroll = false;
2728
2729 // over the time graph control
2730 if ((e.stateMask & SWT.MODIFIER_MASK) == (SWT.SHIFT | SWT.CTRL)) {
2731 verticalZoom = true;
2732 } else if (e.x < fTimeProvider.getNameSpace()) {
2733 // over the name space
2734 verticalScroll = true;
2735 } else {
2736 // over the state area
2737 if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL) {
2738 // over the state area, CTRL pressed
dc4fa715 2739 horizontalZoom = true;
494c9b22
PT
2740 } else if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) {
2741 // over the state area, SHIFT pressed
2742 horizontalScroll = true;
e5552166 2743 } else {
494c9b22
PT
2744 // over the state area, no modifier pressed
2745 verticalScroll = true;
837a2f8c
PT
2746 }
2747 }
494d213d 2748 if (verticalZoom) {
48072ae3 2749 fVerticalZoomAlignEntry = getVerticalZoomAlignCursor(e.y);
494c9b22 2750 verticalZoom(e.count > 0);
48072ae3
PT
2751 if (fVerticalZoomAlignEntry != null) {
2752 setElementPosition(fVerticalZoomAlignEntry.getKey(), fVerticalZoomAlignEntry.getValue());
2753 }
494d213d 2754 } else if (horizontalZoom && fTimeProvider.getTime0() != fTimeProvider.getTime1()) {
494c9b22 2755 zoom(e.count > 0);
b698ec63
PT
2756 } else if (horizontalScroll) {
2757 horizontalScroll(e.count > 0);
494c9b22 2758 } else if (verticalScroll){
837a2f8c
PT
2759 setTopIndex(getTopIndex() - e.count);
2760 }
2761 }
2762
48072ae3
PT
2763 /**
2764 * Get the vertical zoom alignment entry and position based on the current
2765 * selection. If there is no selection or if the selection is not visible,
2766 * return an alignment entry with a null time graph entry.
2767 *
2768 * @return a map entry where the key is the selection's time graph entry and
2769 * the value is the center y-coordinate of that entry, or null
2770 */
2771 private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignSelection() {
2772 Entry<ITimeGraphEntry, Integer> alignEntry = getVerticalZoomAlignOngoing();
2773 if (alignEntry != null) {
2774 return alignEntry;
2775 }
2776 int index = getSelectedIndex();
2777 if (index == -1 || index >= getExpandedElementCount()) {
2778 return new SimpleEntry<>(null, 0);
2779 }
2780 Rectangle bounds = getClientArea();
2781 Rectangle itemRect = getItemRect(bounds, index);
2782 if (itemRect.y < bounds.y || itemRect.y > bounds.y + bounds.height) {
2783 /* selection is not visible */
2784 return new SimpleEntry<>(null, 0);
2785 }
2786 ITimeGraphEntry entry = getExpandedElement(index);
2787 int y = itemRect.y + itemRect.height / 2;
2788 return new SimpleEntry<>(entry, y);
2789 }
2790
2791 /**
2792 * Get the vertical zoom alignment entry and position at the specified
2793 * cursor position.
2794 *
2795 * @param y
2796 * the cursor y-coordinate
2797 * @return a map entry where the key is the time graph entry under the
2798 * cursor and the value is the cursor y-coordinate
2799 */
2800 private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignCursor(int y) {
2801 Entry<ITimeGraphEntry, Integer> alignEntry = getVerticalZoomAlignOngoing();
2802 if (alignEntry != null) {
2803 return alignEntry;
2804 }
2805 int index = getItemIndexAtY(y);
2806 if (index == -1) {
2807 index = getExpandedElementCount() - 1;
2808 }
2809 ITimeGraphEntry entry = getExpandedElement(index);
2810 return new SimpleEntry<>(entry, y);
2811 }
2812
2813 /**
2814 * Get the vertical zoom alignment entry and position if there is an ongoing
2815 * one and we are within the vertical zoom delay, or otherwise return null.
2816 *
2817 * @return a map entry where the key is a time graph entry and the value is
2818 * a y-coordinate, or null
2819 */
2820 private Entry<ITimeGraphEntry, Integer> getVerticalZoomAlignOngoing() {
2821 long currentTimeMillis = System.currentTimeMillis();
2822 if (currentTimeMillis < fVerticalZoomAlignTime + VERTICAL_ZOOM_DELAY) {
2823 /*
2824 * If the vertical zoom is triggered repeatedly in a short amount of
2825 * time, use the initial event's entry and position.
2826 */
2827 fVerticalZoomAlignTime = currentTimeMillis;
2828 return fVerticalZoomAlignEntry;
2829 }
2830 fVerticalZoomAlignTime = currentTimeMillis;
2831 return null;
2832 }
2833
6b11be52
PT
2834 @Override
2835 public void handleEvent(Event event) {
2836 if (event.type == SWT.MouseWheel) {
2837 // prevent horizontal scrolling when the mouse wheel is used to
2838 // scroll vertically or zoom
2839 event.doit = false;
2840 }
2841 }
2842
837a2f8c
PT
2843 @Override
2844 public int getBorderWidth() {
f1fae91f 2845 return fBorderWidth;
837a2f8c
PT
2846 }
2847
2848 /**
2849 * Set the border width
2850 *
2851 * @param borderWidth
2852 * The width
2853 */
2854 public void setBorderWidth(int borderWidth) {
f1fae91f 2855 this.fBorderWidth = borderWidth;
837a2f8c
PT
2856 }
2857
2858 /**
2859 * @return The current height of the header row
2860 */
2861 public int getHeaderHeight() {
f1fae91f 2862 return fHeaderHeight;
837a2f8c
PT
2863 }
2864
2865 /**
2866 * Set the height of the header row
2867 *
2868 * @param headerHeight
2869 * The height
2870 */
2871 public void setHeaderHeight(int headerHeight) {
f1fae91f 2872 this.fHeaderHeight = headerHeight;
837a2f8c
PT
2873 }
2874
2875 /**
c004295c 2876 * @return The default height of regular item rows
837a2f8c
PT
2877 */
2878 public int getItemHeight() {
f1fae91f 2879 return fGlobalItemHeight;
837a2f8c
PT
2880 }
2881
2882 /**
c004295c 2883 * Set the default height of regular item rows.
837a2f8c
PT
2884 *
2885 * @param rowHeight
2886 * The height
2887 */
2888 public void setItemHeight(int rowHeight) {
f1fae91f 2889 this.fGlobalItemHeight = rowHeight;
63b93cd6
PT
2890 for (Item item : fItemData.fItems) {
2891 item.fItemHeight = rowHeight;
2892 }
837a2f8c
PT
2893 }
2894
c004295c
PT
2895 /**
2896 * Set the height of a specific item. Overrides the default item height.
2897 *
2898 * @param entry
2899 * A time graph entry
2900 * @param rowHeight
2901 * The height
2902 * @return true if the height is successfully stored, false otherwise
c004295c
PT
2903 */
2904 public boolean setItemHeight(ITimeGraphEntry entry, int rowHeight) {
2905 Item item = fItemData.findItem(entry);
2906 if (item != null) {
2907 item.fItemHeight = rowHeight;
2908 return true;
2909 }
2910 return false;
2911 }
2912
837a2f8c
PT
2913 /**
2914 * Set the minimum item width
2915 *
2916 * @param width The minimum width
2917 */
2918 public void setMinimumItemWidth(int width) {
f1fae91f 2919 this.fMinimumItemWidth = width;
837a2f8c
PT
2920 }
2921
2922 /**
2923 * @return The minimum item width
2924 */
2925 public int getMinimumItemWidth() {
f1fae91f 2926 return fMinimumItemWidth;
837a2f8c
PT
2927 }
2928
b052914f
PT
2929 /**
2930 * Set whether all time events with a duration shorter than one pixel should
2931 * be blended in. If false, only the first such time event will be drawn and
2932 * the subsequent time events in the same pixel will be discarded. The
2933 * default value is false.
2934 *
2935 * @param blend
2936 * true if sub-pixel events should be blended, false otherwise.
0336f981 2937 * @since 1.1
b052914f
PT
2938 */
2939 public void setBlendSubPixelEvents(boolean blend) {
2940 fBlendSubPixelEvents = blend;
2941 }
2942
837a2f8c
PT
2943 @Override
2944 public void addSelectionChangedListener(ISelectionChangedListener listener) {
f1fae91f
PT
2945 if (listener != null && !fSelectionChangedListeners.contains(listener)) {
2946 fSelectionChangedListeners.add(listener);
837a2f8c
PT
2947 }
2948 }
2949
837a2f8c
PT
2950 @Override
2951 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2952 if (listener != null) {
f1fae91f 2953 fSelectionChangedListeners.remove(listener);
837a2f8c
PT
2954 }
2955 }
2956
837a2f8c
PT
2957 @Override
2958 public void setSelection(ISelection selection) {
2959 if (selection instanceof TimeGraphSelection) {
2960 TimeGraphSelection sel = (TimeGraphSelection) selection;
2961 Object ob = sel.getFirstElement();
2962 if (ob instanceof ITimeGraphEntry) {
2963 ITimeGraphEntry trace = (ITimeGraphEntry) ob;
2964 selectItem(trace, false);
2965 }
2966 }
2967
2968 }
2969
6ac5a950
AM
2970 /**
2971 * @param filter The filter object to be attached to the view
6ac5a950 2972 */
367e2932 2973 public void addFilter(@NonNull ViewerFilter filter) {
f1fae91f
PT
2974 if (!fFilters.contains(filter)) {
2975 fFilters.add(filter);
6ac5a950
AM
2976 }
2977 }
2978
2979 /**
2980 * @param filter The filter object to be attached to the view
6ac5a950 2981 */
367e2932 2982 public void removeFilter(@NonNull ViewerFilter filter) {
f1fae91f 2983 fFilters.remove(filter);
6ac5a950
AM
2984 }
2985
4923d7b9
PT
2986 /**
2987 * Returns this control's filters.
2988 *
2989 * @return an array of viewer filters
2990 * @since 2.0
2991 */
367e2932 2992 public @NonNull ViewerFilter[] getFilters() {
4923d7b9
PT
2993 return Iterables.toArray(fFilters, ViewerFilter.class);
2994 }
2995
2996 /**
2997 * Sets the filters, replacing any previous filters.
2998 *
2999 * @param filters
3000 * an array of viewer filters, or null
3001 * @since 2.0
3002 */
367e2932 3003 public void setFilters(@NonNull ViewerFilter[] filters) {
4923d7b9
PT
3004 fFilters.clear();
3005 if (filters != null) {
3006 fFilters.addAll(Arrays.asList(filters));
3007 }
3008 }
3009
496f76d3
GB
3010 @Override
3011 public void colorSettingsChanged(StateItem[] stateItems) {
3012 /* Destroy previous colors from the resource manager */
517fe01b
EB
3013 if (fEventColorMap != null) {
3014 for (Color color : fEventColorMap) {
3015 fResourceManager.destroyColor(color.getRGB());
3016 }
496f76d3
GB
3017 }
3018 if (stateItems != null) {
3019 fEventColorMap = new Color[stateItems.length];
3020 for (int i = 0; i < stateItems.length; i++) {
3021 fEventColorMap[i] = fResourceManager.createColor(stateItems[i].getStateColor());
3022 }
3023 } else {
3024 fEventColorMap = new Color[] { };
3025 }
3026 redraw();
3027 }
3028
837a2f8c 3029 private class ItemData {
df0e3d5f 3030 private Map<ITimeGraphEntry, Item> fItemMap = new LinkedHashMap<>();
f1fae91f
PT
3031 private Item[] fExpandedItems = new Item[0];
3032 private Item[] fItems = new Item[0];
70e10acc 3033 private ITimeGraphEntry fRootEntries[] = new ITimeGraphEntry[0];
507b1336 3034 private List<ILinkEvent> fLinks = new ArrayList<>();
837a2f8c
PT
3035
3036 public ItemData() {
3037 }
3038
70e10acc
PT
3039 public Item findItem(ITimeGraphEntry entry) {
3040 return fItemMap.get(entry);
837a2f8c
PT
3041 }
3042
70e10acc
PT
3043 public int findItemIndex(ITimeGraphEntry entry) {
3044 Item item = fItemMap.get(entry);
3045 if (item == null) {
837a2f8c
PT
3046 return -1;
3047 }
70e10acc 3048 return item.fExpandedIndex;
837a2f8c
PT
3049 }
3050
3051 public void refreshData() {
3e9a3685 3052 ITimeGraphEntry selection = getSelectedTrace();
df0e3d5f 3053 Map<ITimeGraphEntry, Item> itemMap = new LinkedHashMap<>();
70e10acc
PT
3054 for (int i = 0; i < fRootEntries.length; i++) {
3055 ITimeGraphEntry entry = fRootEntries[i];
df0e3d5f 3056 refreshData(itemMap, null, 0, entry);
837a2f8c 3057 }
df0e3d5f 3058 fItemMap = itemMap;
70e10acc 3059 fItems = fItemMap.values().toArray(new Item[0]);
837a2f8c 3060 updateExpandedItems();
3e9a3685 3061 if (selection != null) {
f1fae91f 3062 for (Item item : fExpandedItems) {
70e10acc 3063 if (item.fEntry == selection) {
f1fae91f 3064 item.fSelected = true;
3e9a3685
PT
3065 break;
3066 }
3067 }
3068 }
837a2f8c
PT
3069 }
3070
70e10acc 3071 private void refreshData(Map<ITimeGraphEntry, Item> itemMap, Item parent, int level, ITimeGraphEntry entry) {
837a2f8c
PT
3072 Item item = new Item(entry, entry.getName(), level);
3073 if (parent != null) {
f1fae91f 3074 parent.fChildren.add(item);
837a2f8c 3075 }
c004295c
PT
3076 if (fGlobalItemHeight == CUSTOM_ITEM_HEIGHT) {
3077 item.fItemHeight = fTimeGraphProvider.getItemHeight(entry);
3078 } else {
3079 item.fItemHeight = fGlobalItemHeight;
3080 }
3bd20aa6 3081 item.fItemHeight = Math.max(1, item.fItemHeight + fHeightAdjustment);
70e10acc 3082 itemMap.put(entry, item);
837a2f8c 3083 if (entry.hasChildren()) {
df0e3d5f
PT
3084 Item oldItem = fItemMap.get(entry);
3085 if (oldItem != null && oldItem.fHasChildren && level == oldItem.fLevel && entry.getParent() == oldItem.fEntry.getParent()) {
3086 /* existing items keep their old expanded state */
3087 item.fExpanded = oldItem.fExpanded;
3088 } else {
3089 /* new items set the expanded state according to auto-expand level */
3090 item.fExpanded = fAutoExpandLevel == ALL_LEVELS || level < fAutoExpandLevel;
3091 }
f1fae91f 3092 item.fHasChildren = true;
837a2f8c 3093 for (ITimeGraphEntry child : entry.getChildren()) {
70e10acc 3094 refreshData(itemMap, item, level + 1, child);
837a2f8c
PT
3095 }
3096 }
3097 }
3098
3099 public void updateExpandedItems() {
70e10acc
PT
3100 for (Item item : fItems) {
3101 item.fExpandedIndex = -1;
3102 }
507b1336 3103 List<Item> expandedItemList = new ArrayList<>();
70e10acc
PT
3104 for (int i = 0; i < fRootEntries.length; i++) {
3105 ITimeGraphEntry entry = fRootEntries[i];
837a2f8c
PT
3106 Item item = findItem(entry);
3107 refreshExpanded(expandedItemList, item);
3108 }
f1fae91f 3109 fExpandedItems = expandedItemList.toArray(new Item[0]);
19ed1845 3110 fTopIndex = Math.min(fTopIndex, Math.max(0, fExpandedItems.length - 1));
837a2f8c
PT
3111 }
3112
3113 private void refreshExpanded(List<Item> expandedItemList, Item item) {
6ac5a950
AM
3114 // Check for filters
3115 boolean display = true;
f1fae91f 3116 for (ViewerFilter filter : fFilters) {
70e10acc 3117 if (!filter.select(null, item.fEntry.getParent(), item.fEntry)) {
6ac5a950
AM
3118 display = false;
3119 break;
3120 }
3121 }
3122 if (display) {
70e10acc 3123 item.fExpandedIndex = expandedItemList.size();
6ac5a950 3124 expandedItemList.add(item);
f1fae91f
PT
3125 if (item.fHasChildren && item.fExpanded) {
3126 for (Item child : item.fChildren) {
6ac5a950
AM
3127 refreshExpanded(expandedItemList, child);
3128 }
837a2f8c
PT
3129 }
3130 }
3131 }
3132
70e10acc
PT
3133 public void refreshData(ITimeGraphEntry[] entries) {
3134 if (entries == null) {
70e10acc 3135 fRootEntries = null;
f1fae91f 3136 } else {
70e10acc 3137 fRootEntries = Arrays.copyOf(entries, entries.length);
837a2f8c
PT
3138 }
3139
837a2f8c
PT
3140 refreshData();
3141 }
3142
bec1f1ac
GB
3143 public void refreshArrows(List<ILinkEvent> events) {
3144 /* If links are null, reset the list */
3145 if (events != null) {
3146 fLinks = events;
3147 } else {
507b1336 3148 fLinks = new ArrayList<>();
bec1f1ac
GB
3149 }
3150 }
3151
70e10acc
PT
3152 public ITimeGraphEntry[] getEntries() {
3153 return fRootEntries;
837a2f8c 3154 }
837a2f8c
PT
3155 }
3156
3157 private class Item {
f1fae91f 3158 private boolean fExpanded;
70e10acc 3159 private int fExpandedIndex;
f1fae91f
PT
3160 private boolean fSelected;
3161 private boolean fHasChildren;
3162 private int fItemHeight;
70e10acc
PT
3163 private final int fLevel;
3164 private final List<Item> fChildren;
3165 private final String fName;
3166 private final ITimeGraphEntry fEntry;
837a2f8c 3167
70e10acc
PT
3168 public Item(ITimeGraphEntry entry, String name, int level) {
3169 this.fEntry = entry;
f1fae91f
PT
3170 this.fName = name;
3171 this.fLevel = level;
507b1336 3172 this.fChildren = new ArrayList<>();
837a2f8c
PT
3173 }
3174
3175 @Override
3176 public String toString() {
f1fae91f 3177 return fName;
837a2f8c
PT
3178 }
3179 }
3180
27df1564
XR
3181 @Override
3182 public void menuDetected(MenuDetectEvent e) {
f1fae91f 3183 if (null == fTimeProvider) {
27df1564
XR
3184 return;
3185 }
024658d1 3186 Point p = toControl(e.x, e.y);
0b5a90a0
PT
3187 if (e.detail == SWT.MENU_MOUSE) {
3188 if (fPendingMenuDetectEvent == null) {
3189 /* Feature in Linux. The MenuDetectEvent is received before mouseDown.
3190 * Store the event and trigger it later just before handling mouseUp.
3191 * This allows for the method to detect if mouse is used to drag zoom.
3192 */
3193 fPendingMenuDetectEvent = e;
024658d1
BH
3194 /*
3195 * Prevent the platform to show the menu when returning. The
3196 * menu will be shown (see below) when this method is called
3197 * again during mouseup().
3198 */
3199 e.doit = false;
0b5a90a0
PT
3200 return;
3201 }
3202 fPendingMenuDetectEvent = null;
024658d1 3203 if ((p.x >= fTimeProvider.getNameSpace()) && (fDragState != DRAG_ZOOM || fDragX != fDragX0)) {
0b5a90a0
PT
3204 return;
3205 }
3206 } else {
f1fae91f 3207 if (fDragState != DRAG_NONE) {
0b5a90a0
PT
3208 return;
3209 }
3210 }
27df1564 3211 int idx = getItemIndexAtY(p.y);
f1fae91f 3212 if (idx >= 0 && idx < fItemData.fExpandedItems.length) {
024658d1 3213 e.doit = true;
f1fae91f 3214 Item item = fItemData.fExpandedItems[idx];
70e10acc 3215 ITimeGraphEntry entry = item.fEntry;
9bdf1671
BH
3216
3217 /* Send menu event for the time graph entry */
3218 e.doit = true;
3219 e.data = entry;
3220 fireMenuEventOnTimeGraphEntry(e);
3221 Menu menu = getMenu();
3222 if (e.doit && (menu != null)) {
3223 menu.setVisible(true);
3224 }
3225
3226 /* Send menu event for time event */
27df1564
XR
3227 if (entry.hasTimeEvents()) {
3228 ITimeEvent event = Utils.findEvent(entry, getTimeAtX(p.x), 2);
3229 if (event != null) {
9bdf1671 3230 e.doit = true;
27df1564
XR
3231 e.data = event;
3232 fireMenuEventOnTimeEvent(e);
9bdf1671 3233 menu = getMenu();
024658d1
BH
3234 if (e.doit && (menu != null)) {
3235 menu.setVisible(true);
3236 }
27df1564
XR
3237 }
3238 }
27df1564
XR
3239 }
3240 }
3241
d2e4afa7
MAL
3242 /**
3243 * Perform the alignment operation.
3244 *
3245 * @param offset
3246 * the alignment offset
3247 *
3248 * @see ITmfTimeAligned
3249 *
3250 * @since 1.0
3251 */
3252 public void performAlign(int offset) {
3253 fTimeProvider.setNameSpace(offset);
3254 }
3255
3256 /**
3257 * Return the time alignment information
3258 *
3259 * @return the time alignment information
3260 *
3261 * @see ITmfTimeAligned
3262 *
3263 * @since 1.0
3264 */
3265 public TmfTimeViewAlignmentInfo getTimeViewAlignmentInfo() {
3266 return new TmfTimeViewAlignmentInfo(getShell(), toDisplay(0, 0), fTimeProvider.getNameSpace());
3267 }
837a2f8c 3268}
This page took 0.264301 seconds and 5 git commands to generate.