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