1 /*******************************************************************************
2 * Copyright (c) 2012, 2015 Ericsson, others
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * Patrick Tasse - Initial API and implementation
11 * François Rajotte - Filter implementation
12 * Geneviève Bastien - Add event links between entries
13 * Christian Mansky - Add check active / uncheck inactive buttons
14 *******************************************************************************/
16 package org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
;
18 import java
.util
.ArrayList
;
19 import java
.util
.Arrays
;
20 import java
.util
.HashMap
;
21 import java
.util
.HashSet
;
22 import java
.util
.List
;
26 import org
.eclipse
.jdt
.annotation
.NonNull
;
27 import org
.eclipse
.jface
.viewers
.AbstractTreeViewer
;
28 import org
.eclipse
.jface
.viewers
.ILabelProviderListener
;
29 import org
.eclipse
.jface
.viewers
.ISelectionChangedListener
;
30 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
31 import org
.eclipse
.jface
.viewers
.ITableLabelProvider
;
32 import org
.eclipse
.jface
.viewers
.ITreeContentProvider
;
33 import org
.eclipse
.jface
.viewers
.ITreeViewerListener
;
34 import org
.eclipse
.jface
.viewers
.SelectionChangedEvent
;
35 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
36 import org
.eclipse
.jface
.viewers
.TreeExpansionEvent
;
37 import org
.eclipse
.jface
.viewers
.TreeViewer
;
38 import org
.eclipse
.jface
.viewers
.Viewer
;
39 import org
.eclipse
.jface
.viewers
.ViewerFilter
;
40 import org
.eclipse
.swt
.SWT
;
41 import org
.eclipse
.swt
.custom
.SashForm
;
42 import org
.eclipse
.swt
.events
.ControlAdapter
;
43 import org
.eclipse
.swt
.events
.ControlEvent
;
44 import org
.eclipse
.swt
.events
.DisposeEvent
;
45 import org
.eclipse
.swt
.events
.DisposeListener
;
46 import org
.eclipse
.swt
.events
.MouseEvent
;
47 import org
.eclipse
.swt
.events
.MouseTrackAdapter
;
48 import org
.eclipse
.swt
.events
.MouseWheelListener
;
49 import org
.eclipse
.swt
.events
.PaintEvent
;
50 import org
.eclipse
.swt
.events
.PaintListener
;
51 import org
.eclipse
.swt
.events
.SelectionAdapter
;
52 import org
.eclipse
.swt
.events
.SelectionEvent
;
53 import org
.eclipse
.swt
.graphics
.Font
;
54 import org
.eclipse
.swt
.graphics
.FontData
;
55 import org
.eclipse
.swt
.graphics
.Image
;
56 import org
.eclipse
.swt
.graphics
.Point
;
57 import org
.eclipse
.swt
.graphics
.Rectangle
;
58 import org
.eclipse
.swt
.layout
.FillLayout
;
59 import org
.eclipse
.swt
.layout
.GridLayout
;
60 import org
.eclipse
.swt
.widgets
.Composite
;
61 import org
.eclipse
.swt
.widgets
.Control
;
62 import org
.eclipse
.swt
.widgets
.Display
;
63 import org
.eclipse
.swt
.widgets
.Event
;
64 import org
.eclipse
.swt
.widgets
.Listener
;
65 import org
.eclipse
.swt
.widgets
.Sash
;
66 import org
.eclipse
.swt
.widgets
.Slider
;
67 import org
.eclipse
.swt
.widgets
.Tree
;
68 import org
.eclipse
.swt
.widgets
.TreeColumn
;
69 import org
.eclipse
.swt
.widgets
.TreeItem
;
70 import org
.eclipse
.tracecompass
.tmf
.core
.signal
.TmfSignalManager
;
71 import org
.eclipse
.tracecompass
.tmf
.ui
.signal
.TmfTimeViewAlignmentInfo
;
72 import org
.eclipse
.tracecompass
.tmf
.ui
.signal
.TmfTimeViewAlignmentSignal
;
73 import org
.eclipse
.tracecompass
.tmf
.ui
.views
.ITmfTimeAligned
;
74 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.dialogs
.ITimeGraphEntryActiveProvider
;
75 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.dialogs
.ShowFilterDialogAction
;
76 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.ILinkEvent
;
77 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.ITimeGraphEntry
;
78 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.widgets
.TimeGraphColorScheme
;
79 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.widgets
.TimeGraphControl
;
81 import com
.google
.common
.collect
.Iterables
;
84 * Time graph "combo" view (with the list/tree on the left and the gantt chart
87 * @author Patrick Tasse
89 public class TimeGraphCombo
extends Composite
{
91 // ------------------------------------------------------------------------
93 // ------------------------------------------------------------------------
95 /** Constant indicating that all levels of the time graph should be expanded */
96 public static final int ALL_LEVELS
= AbstractTreeViewer
.ALL_LEVELS
;
98 private static final Object FILLER
= new Object();
100 // ------------------------------------------------------------------------
102 // ------------------------------------------------------------------------
104 /** The tree viewer */
105 private TreeViewer fTreeViewer
;
107 /** The time viewer */
108 private @NonNull TimeGraphViewer fTimeGraphViewer
;
110 /** The selection listener map */
111 private final Map
<ITimeGraphSelectionListener
, SelectionListenerWrapper
> fSelectionListenerMap
= new HashMap
<>();
113 /** The map of viewer filters to viewer filter wrappers */
114 private final Map
<@NonNull ViewerFilter
, @NonNull ViewerFilter
> fViewerFilterMap
= new HashMap
<>();
117 * Flag to block the tree selection changed listener when triggered by the
120 private boolean fInhibitTreeSelection
= false;
122 /** Number of filler rows used by the tree content provider */
123 private int fNumFillerRows
;
125 /** Calculated item height for Linux workaround */
126 private int fLinuxItemHeight
= 0;
128 /** The action that opens the filter dialog */
129 private ShowFilterDialogAction fShowFilterDialogAction
;
131 /** Default weight of each part of the sash */
132 private static final int[] DEFAULT_WEIGHTS
= { 1, 1 };
134 /** List of all expanded items whose parents are also expanded */
135 private List
<TreeItem
> fVisibleExpandedItems
= null;
137 private Listener fSashDragListener
;
138 private SashForm fSashForm
;
140 private final boolean fScrollBarsInTreeWorkaround
;
142 private Font fTreeFont
;
144 // ------------------------------------------------------------------------
146 // ------------------------------------------------------------------------
149 * The TimeGraphViewerExtension is used to set appropriate values and to
150 * override methods that could be called directly by the user and that must
151 * be handled by the time graph combo.
153 private class TimeGraphViewerExtension
extends TimeGraphViewer
{
155 private TimeGraphViewerExtension(Composite parent
, int style
, Tree tree
) {
156 super(parent
, style
);
157 setItemHeight(TimeGraphCombo
.this.getItemHeight(tree
, true));
158 setHeaderHeight(tree
.getHeaderHeight());
159 setBorderWidth(tree
.getBorderWidth());
164 public ShowFilterDialogAction
getShowFilterDialogAction() {
165 return TimeGraphCombo
.this.getShowFilterDialogAction();
169 protected TimeGraphControl
createTimeGraphControl(Composite composite
, TimeGraphColorScheme colors
) {
170 return new TimeGraphControl(composite
, colors
) {
172 public void verticalZoom(boolean zoomIn
, boolean adjustItems
) {
173 boolean changed
= TimeGraphCombo
.this.verticalZoom(zoomIn
);
176 * The time graph combo takes care of adjusting item
177 * heights, only adjust the font in the time graph
178 * control. Only do it if the time graph combo's tree
179 * font has actually changed.
181 super.verticalZoom(zoomIn
, false);
186 public void resetVerticalZoom(boolean adjustItems
) {
187 TimeGraphCombo
.this.resetVerticalZoom();
189 * The time graph combo takes care of resetting item
190 * heights, only reset the font in the time graph control.
192 super.resetVerticalZoom(false);
199 * The TreeContentProviderWrapper is used to insert filler items after
200 * the elements of the tree's real content provider.
202 private class TreeContentProviderWrapper
implements ITreeContentProvider
{
203 private final ITreeContentProvider contentProvider
;
205 public TreeContentProviderWrapper(ITreeContentProvider contentProvider
) {
206 this.contentProvider
= contentProvider
;
210 public void dispose() {
211 contentProvider
.dispose();
215 public void inputChanged(Viewer viewer
, Object oldInput
, Object newInput
) {
216 contentProvider
.inputChanged(viewer
, oldInput
, newInput
);
220 public Object
[] getElements(Object inputElement
) {
221 Object
[] elements
= contentProvider
.getElements(inputElement
);
222 // add filler elements to ensure alignment with time analysis viewer
223 Object
[] oElements
= Arrays
.copyOf(elements
, elements
.length
+ fNumFillerRows
, Object
[].class);
224 for (int i
= 0; i
< fNumFillerRows
; i
++) {
225 oElements
[elements
.length
+ i
] = FILLER
;
231 public Object
[] getChildren(Object parentElement
) {
232 if (parentElement
instanceof ITimeGraphEntry
) {
233 return contentProvider
.getChildren(parentElement
);
235 return new Object
[0];
239 public Object
getParent(Object element
) {
240 if (element
instanceof ITimeGraphEntry
) {
241 return contentProvider
.getParent(element
);
247 public boolean hasChildren(Object element
) {
248 if (element
instanceof ITimeGraphEntry
) {
249 return contentProvider
.hasChildren(element
);
256 * The TreeLabelProviderWrapper is used to intercept the filler items
257 * from the calls to the tree's real label provider.
259 private class TreeLabelProviderWrapper
implements ITableLabelProvider
{
260 private final ITableLabelProvider labelProvider
;
262 public TreeLabelProviderWrapper(ITableLabelProvider labelProvider
) {
263 this.labelProvider
= labelProvider
;
267 public void addListener(ILabelProviderListener listener
) {
268 labelProvider
.addListener(listener
);
272 public void dispose() {
273 labelProvider
.dispose();
277 public boolean isLabelProperty(Object element
, String property
) {
278 if (element
instanceof ITimeGraphEntry
) {
279 return labelProvider
.isLabelProperty(element
, property
);
285 public void removeListener(ILabelProviderListener listener
) {
286 labelProvider
.removeListener(listener
);
290 public Image
getColumnImage(Object element
, int columnIndex
) {
291 if (element
instanceof ITimeGraphEntry
) {
292 return labelProvider
.getColumnImage(element
, columnIndex
);
298 public String
getColumnText(Object element
, int columnIndex
) {
299 if (element
instanceof ITimeGraphEntry
) {
300 return labelProvider
.getColumnText(element
, columnIndex
);
308 * The SelectionListenerWrapper is used to intercept the filler items from
309 * the time graph combo's real selection listener, and to prevent double
310 * notifications from being sent when selection changes in both tree and
311 * time graph at the same time.
313 private class SelectionListenerWrapper
implements ISelectionChangedListener
, ITimeGraphSelectionListener
{
314 private final ITimeGraphSelectionListener listener
;
315 private ITimeGraphEntry selection
= null;
317 public SelectionListenerWrapper(ITimeGraphSelectionListener listener
) {
318 this.listener
= listener
;
322 public void selectionChanged(SelectionChangedEvent event
) {
323 if (fInhibitTreeSelection
) {
326 Object element
= ((IStructuredSelection
) event
.getSelection()).getFirstElement();
327 if (element
instanceof ITimeGraphEntry
) {
328 ITimeGraphEntry entry
= (ITimeGraphEntry
) element
;
329 if (entry
!= selection
) {
331 listener
.selectionChanged(new TimeGraphSelectionEvent(event
.getSource(), selection
));
337 public void selectionChanged(TimeGraphSelectionEvent event
) {
338 ITimeGraphEntry entry
= event
.getSelection();
339 if (entry
!= selection
) {
341 listener
.selectionChanged(new TimeGraphSelectionEvent(event
.getSource(), selection
));
347 * The ViewerFilterWrapper is used to intercept the filler items from
348 * the time graph combo's real ViewerFilters. These filler items should
351 private class ViewerFilterWrapper
extends ViewerFilter
{
353 private ViewerFilter fWrappedFilter
;
355 ViewerFilterWrapper(ViewerFilter filter
) {
357 this.fWrappedFilter
= filter
;
361 public boolean select(Viewer viewer
, Object parentElement
, Object element
) {
362 if (element
instanceof ITimeGraphEntry
) {
363 return fWrappedFilter
.select(viewer
, parentElement
, element
);
370 // ------------------------------------------------------------------------
372 // ------------------------------------------------------------------------
375 * Constructs a new instance of this class given its parent
376 * and a style value describing its behavior and appearance.
378 * @param parent a widget which will be the parent of the new instance (cannot be null)
379 * @param style the style of widget to construct
381 public TimeGraphCombo(Composite parent
, int style
) {
382 this(parent
, style
, DEFAULT_WEIGHTS
);
386 * Constructs a new instance of this class given its parent and a style
387 * value describing its behavior and appearance.
390 * a widget which will be the parent of the new instance (cannot
393 * the style of widget to construct
395 * The array (length 2) of relative weights of each side of the sash form
397 public TimeGraphCombo(Composite parent
, int style
, int[] weights
) {
398 super(parent
, style
);
399 setLayout(new FillLayout());
401 fSashForm
= new SashForm(this, SWT
.NONE
);
404 * In Windows, SWT.H_SCROLL | SWT.NO_SCROLL is not properly supported,
405 * both scroll bars are always created. See Tree.checkStyle: "Even when
406 * WS_HSCROLL or WS_VSCROLL is not specified, Windows creates trees and
407 * tables with scroll bars."
409 fScrollBarsInTreeWorkaround
= "win32".equals(SWT
.getPlatform()); //$NON-NLS-1$
411 int scrollBarStyle
= fScrollBarsInTreeWorkaround ? SWT
.H_SCROLL
: SWT
.H_SCROLL
| SWT
.NO_SCROLL
;
413 fTreeViewer
= new TreeViewer(fSashForm
, SWT
.FULL_SELECTION
| scrollBarStyle
);
414 fTreeViewer
.setAutoExpandLevel(AbstractTreeViewer
.ALL_LEVELS
);
415 final Tree tree
= fTreeViewer
.getTree();
416 tree
.setHeaderVisible(true);
417 tree
.setLinesVisible(true);
419 fTimeGraphViewer
= new TimeGraphViewerExtension(fSashForm
, SWT
.NONE
, tree
);
421 if (fScrollBarsInTreeWorkaround
) {
422 // Feature in Windows. The tree vertical bar reappears when
423 // the control is resized so we need to hide it again.
424 tree
.addControlListener(new ControlAdapter() {
425 private int depth
= 0;
428 public void controlResized(ControlEvent e
) {
431 tree
.getVerticalBar().setEnabled(false);
432 // this can trigger controlResized recursively
433 tree
.getVerticalBar().setVisible(false);
439 // Bug in Linux. The tree header height is 0 in constructor,
440 // so we need to reset it later when the control is painted.
441 // This work around used to be done on control resized but the header
442 // height was not initialized on the initial resize on GTK3.
443 tree
.addPaintListener(new PaintListener() {
445 public void paintControl(PaintEvent e
) {
446 int headerHeight
= tree
.getHeaderHeight();
447 if (headerHeight
> 0) {
448 fTimeGraphViewer
.setHeaderHeight(headerHeight
);
449 tree
.removePaintListener(this);
454 tree
.addDisposeListener(new DisposeListener() {
456 public void widgetDisposed(DisposeEvent e
) {
457 if (fTreeFont
!= null) {
463 // ensure synchronization of expanded items between tree and time graph
464 fTreeViewer
.addTreeListener(new ITreeViewerListener() {
466 public void treeCollapsed(TreeExpansionEvent event
) {
467 fTimeGraphViewer
.setExpandedState((ITimeGraphEntry
) event
.getElement(), false);
468 // queue the alignment update because the tree items may only be
469 // actually collapsed after the listeners have been notified
470 fVisibleExpandedItems
= null; // invalidate the cache
471 getDisplay().asyncExec(new Runnable() {
474 alignTreeItems(true);
479 public void treeExpanded(TreeExpansionEvent event
) {
480 ITimeGraphEntry entry
= (ITimeGraphEntry
) event
.getElement();
481 fTimeGraphViewer
.setExpandedState(entry
, true);
482 Set
<Object
> expandedElements
= new HashSet
<>(Arrays
.asList(fTreeViewer
.getExpandedElements()));
483 for (ITimeGraphEntry child
: entry
.getChildren()) {
484 if (child
.hasChildren()) {
485 boolean expanded
= expandedElements
.contains(child
);
486 fTimeGraphViewer
.setExpandedState(child
, expanded
);
489 // queue the alignment update because the tree items may only be
490 // actually expanded after the listeners have been notified
491 fVisibleExpandedItems
= null; // invalidate the cache
492 getDisplay().asyncExec(new Runnable() {
495 alignTreeItems(true);
500 // ensure synchronization of expanded items between tree and time graph
501 fTimeGraphViewer
.addTreeListener(new ITimeGraphTreeListener() {
503 public void treeCollapsed(TimeGraphTreeExpansionEvent event
) {
504 fTreeViewer
.setExpandedState(event
.getEntry(), false);
505 alignTreeItems(true);
509 public void treeExpanded(TimeGraphTreeExpansionEvent event
) {
510 ITimeGraphEntry entry
= event
.getEntry();
511 fTreeViewer
.setExpandedState(entry
, true);
512 Set
<Object
> expandedElements
= new HashSet
<>(Arrays
.asList(fTreeViewer
.getExpandedElements()));
513 for (ITimeGraphEntry child
: entry
.getChildren()) {
514 if (child
.hasChildren()) {
515 boolean expanded
= expandedElements
.contains(child
);
516 fTimeGraphViewer
.setExpandedState(child
, expanded
);
519 alignTreeItems(true);
523 // prevent mouse button from selecting a filler tree item
524 tree
.addListener(SWT
.MouseDown
, new Listener() {
526 public void handleEvent(Event event
) {
527 TreeItem treeItem
= tree
.getItem(new Point(event
.x
, event
.y
));
528 if (treeItem
== null || treeItem
.getData() == FILLER
) {
530 List
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
, false);
531 if (treeItems
.size() == 0) {
532 fTreeViewer
.setSelection(new StructuredSelection());
533 fTimeGraphViewer
.setSelection(null);
536 // this prevents from scrolling up when selecting
537 // the partially visible tree item at the bottom
538 tree
.select(treeItems
.get(treeItems
.size() - 1));
539 fTreeViewer
.setSelection(new StructuredSelection());
540 fTimeGraphViewer
.setSelection(null);
545 // prevent mouse wheel from scrolling down into filler tree items
546 tree
.addListener(SWT
.MouseWheel
, new Listener() {
548 public void handleEvent(Event event
) {
550 Slider scrollBar
= fTimeGraphViewer
.getVerticalBar();
551 fTimeGraphViewer
.setTopIndex(scrollBar
.getSelection() - event
.count
);
552 alignTreeItems(false);
556 // prevent key stroke from selecting a filler tree item
557 tree
.addListener(SWT
.KeyDown
, new Listener() {
559 public void handleEvent(Event event
) {
560 List
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
, false);
561 if (treeItems
.size() == 0) {
562 fTreeViewer
.setSelection(new StructuredSelection());
566 if (event
.keyCode
== SWT
.ARROW_DOWN
) {
567 int index
= Math
.min(fTimeGraphViewer
.getSelectionIndex() + 1, treeItems
.size() - 1);
568 fTimeGraphViewer
.setSelection((ITimeGraphEntry
) treeItems
.get(index
).getData());
570 } else if (event
.keyCode
== SWT
.PAGE_DOWN
) {
571 int height
= tree
.getSize().y
- tree
.getHeaderHeight() - tree
.getHorizontalBar().getSize().y
;
572 int countPerPage
= height
/ getItemHeight(tree
, false);
573 int index
= Math
.min(fTimeGraphViewer
.getSelectionIndex() + countPerPage
- 1, treeItems
.size() - 1);
574 fTimeGraphViewer
.setSelection((ITimeGraphEntry
) treeItems
.get(index
).getData());
576 } else if (event
.keyCode
== SWT
.END
) {
577 fTimeGraphViewer
.setSelection((ITimeGraphEntry
) treeItems
.get(treeItems
.size() - 1).getData());
579 } else if ((event
.character
== '+' || event
.character
== '=') && ((event
.stateMask
& SWT
.CTRL
) != 0)) {
581 } else if (event
.character
== '-' && ((event
.stateMask
& SWT
.CTRL
) != 0)) {
583 } else if (event
.character
== '0' && ((event
.stateMask
& SWT
.CTRL
) != 0)) {
588 if (fTimeGraphViewer
.getSelectionIndex() >= 0) {
589 fTreeViewer
.setSelection(new StructuredSelection(fTimeGraphViewer
.getSelection()));
591 fTreeViewer
.setSelection(new StructuredSelection());
593 alignTreeItems(false);
597 // ensure alignment of top item between tree and time graph
598 fTimeGraphViewer
.getTimeGraphControl().addControlListener(new ControlAdapter() {
600 public void controlResized(ControlEvent e
) {
601 alignTreeItems(false);
605 // ensure synchronization of selected item between tree and time graph
606 fTreeViewer
.addSelectionChangedListener(new ISelectionChangedListener() {
608 public void selectionChanged(SelectionChangedEvent event
) {
609 if (fInhibitTreeSelection
) {
612 if (event
.getSelection() instanceof IStructuredSelection
) {
613 Object selection
= ((IStructuredSelection
) event
.getSelection()).getFirstElement();
614 if (selection
instanceof ITimeGraphEntry
) {
615 fTimeGraphViewer
.setSelection((ITimeGraphEntry
) selection
);
617 alignTreeItems(false);
622 // ensure synchronization of selected item between tree and time graph
623 fTimeGraphViewer
.addSelectionListener(new ITimeGraphSelectionListener() {
625 public void selectionChanged(TimeGraphSelectionEvent event
) {
626 ITimeGraphEntry entry
= fTimeGraphViewer
.getSelection();
627 fInhibitTreeSelection
= true; // block the tree selection changed listener
629 StructuredSelection selection
= new StructuredSelection(entry
);
630 fTreeViewer
.setSelection(selection
);
632 fTreeViewer
.setSelection(new StructuredSelection());
634 fInhibitTreeSelection
= false;
635 alignTreeItems(false);
639 // ensure alignment of top item between tree and time graph
640 fTimeGraphViewer
.getVerticalBar().addSelectionListener(new SelectionAdapter() {
642 public void widgetSelected(SelectionEvent e
) {
643 alignTreeItems(false);
647 // ensure alignment of top item between tree and time graph
648 fTimeGraphViewer
.getTimeGraphControl().addMouseWheelListener(new MouseWheelListener() {
650 public void mouseScrolled(MouseEvent e
) {
651 alignTreeItems(false);
655 // ensure the tree has focus control when mouse is over it if the time graph had control
656 fTreeViewer
.getControl().addMouseTrackListener(new MouseTrackAdapter() {
658 public void mouseEnter(MouseEvent e
) {
659 if (fTimeGraphViewer
.getTimeGraphControl().isFocusControl()) {
660 fTreeViewer
.getControl().setFocus();
665 // ensure the time graph has focus control when mouse is over it if the tree had control
666 fTimeGraphViewer
.getTimeGraphControl().addMouseTrackListener(new MouseTrackAdapter() {
668 public void mouseEnter(MouseEvent e
) {
669 if (fTreeViewer
.getControl().isFocusControl()) {
670 fTimeGraphViewer
.getTimeGraphControl().setFocus();
674 fTimeGraphViewer
.getTimeGraphScale().addMouseTrackListener(new MouseTrackAdapter() {
676 public void mouseEnter(MouseEvent e
) {
677 if (fTreeViewer
.getControl().isFocusControl()) {
678 fTimeGraphViewer
.getTimeGraphControl().setFocus();
683 // The filler rows are required to ensure alignment when the tree does not have a
684 // visible horizontal scroll bar. The tree does not allow its top item to be set
685 // to a value that would cause blank space to be drawn at the bottom of the tree.
686 fNumFillerRows
= Display
.getDefault().getBounds().height
/ getItemHeight(tree
, false);
688 fSashForm
.setWeights(weights
);
690 fTimeGraphViewer
.getTimeGraphControl().addPaintListener(new PaintListener() {
692 public void paintControl(PaintEvent e
) {
693 // Sashes in a SashForm are being created on layout so add the
694 // drag listener here
695 if (fSashDragListener
== null) {
696 for (Control control
: fSashForm
.getChildren()) {
697 if (control
instanceof Sash
) {
698 fSashDragListener
= new Listener() {
701 public void handleEvent(Event event
) {
702 sendTimeViewAlignmentChanged();
706 control
.removePaintListener(this);
707 control
.addListener(SWT
.Selection
, fSashDragListener
);
708 // There should be only one sash
717 private boolean verticalZoom(boolean zoomIn
) {
718 Tree tree
= fTreeViewer
.getTree();
719 FontData fontData
= tree
.getFont().getFontData()[0];
720 int height
= fontData
.getHeight() + (zoomIn ?
1 : -1);
724 fontData
.setHeight(height
);
725 if (fTreeFont
!= null) {
728 fTreeFont
= new Font(tree
.getDisplay(), fontData
);
729 tree
.setFont(fTreeFont
);
732 fTimeGraphViewer
.setHeaderHeight(tree
.getHeaderHeight());
733 fTimeGraphViewer
.setItemHeight(getItemHeight(tree
, true));
734 alignTreeItems(false);
738 private void resetVerticalZoom() {
739 Tree tree
= fTreeViewer
.getTree();
740 if (fTreeFont
!= null) {
747 fTimeGraphViewer
.setHeaderHeight(tree
.getHeaderHeight());
748 fTimeGraphViewer
.setItemHeight(getItemHeight(tree
, true));
749 alignTreeItems(false);
752 private void sendTimeViewAlignmentChanged() {
753 TmfSignalManager
.dispatchSignal(new TmfTimeViewAlignmentSignal(fSashForm
, getTimeViewAlignmentInfo()));
756 // ------------------------------------------------------------------------
758 // ------------------------------------------------------------------------
761 * Returns this time graph combo's tree viewer.
763 * @return the tree viewer
765 public TreeViewer
getTreeViewer() {
770 * Returns this time graph combo's time graph viewer.
772 * @return the time graph viewer
774 public @NonNull TimeGraphViewer
getTimeGraphViewer() {
775 return fTimeGraphViewer
;
779 * Get the show filter dialog action.
781 * @return The Action object
784 public ShowFilterDialogAction
getShowFilterDialogAction() {
785 if (fShowFilterDialogAction
== null) {
786 fShowFilterDialogAction
= new ShowFilterDialogAction(fTimeGraphViewer
) {
788 protected void addFilter(ViewerFilter filter
) {
789 /* add filter to the combo instead of the viewer */
790 TimeGraphCombo
.this.addFilter(filter
);
794 protected void removeFilter(ViewerFilter filter
) {
795 /* remove filter from the combo instead of the viewer */
796 TimeGraphCombo
.this.removeFilter(filter
);
800 protected void refresh() {
801 /* refresh the combo instead of the viewer */
802 TimeGraphCombo
.this.refresh();
806 return fShowFilterDialogAction
;
809 // ------------------------------------------------------------------------
811 // ------------------------------------------------------------------------
814 public void redraw() {
815 fTimeGraphViewer
.getControl().redraw();
820 public void update() {
821 fTimeGraphViewer
.getControl().update();
825 // ------------------------------------------------------------------------
827 // ------------------------------------------------------------------------
830 * Sets the tree content provider used by this time graph combo.
832 * @param contentProvider the tree content provider
834 public void setTreeContentProvider(ITreeContentProvider contentProvider
) {
835 fTreeViewer
.setContentProvider(new TreeContentProviderWrapper(contentProvider
));
839 * Sets the tree label provider used by this time graph combo.
841 * @param labelProvider the tree label provider
843 public void setTreeLabelProvider(ITableLabelProvider labelProvider
) {
844 fTreeViewer
.setLabelProvider(new TreeLabelProviderWrapper(labelProvider
));
848 * Sets the tree content provider used by the filter dialog
850 * @param contentProvider the tree content provider
852 public void setFilterContentProvider(ITreeContentProvider contentProvider
) {
853 getShowFilterDialogAction().getFilterDialog().setContentProvider(contentProvider
);
857 * Sets the tree label provider used by the filter dialog
859 * @param labelProvider the tree label provider
861 public void setFilterLabelProvider(ITableLabelProvider labelProvider
) {
862 getShowFilterDialogAction().getFilterDialog().setLabelProvider(labelProvider
);
866 * Adds a "check active" button used by the filter dialog
868 * @param activeProvider
869 * Additional button info specific to a certain view.
872 public void addTimeGraphFilterCheckActiveButton(ITimeGraphEntryActiveProvider activeProvider
) {
873 getShowFilterDialogAction().getFilterDialog().addTimeGraphFilterCheckActiveButton(activeProvider
);
877 * Adds an "uncheck inactive" button used by the filter dialog
879 * @param inactiveProvider
880 * Additional button info specific to a certain view.
883 public void addTimeGraphFilterUncheckInactiveButton(ITimeGraphEntryActiveProvider inactiveProvider
) {
884 getShowFilterDialogAction().getFilterDialog().addTimeGraphFilterUncheckInactiveButton(inactiveProvider
);
888 * Sets the tree columns for this time graph combo.
890 * @param columnNames the tree column names
892 public void setTreeColumns(String
[] columnNames
) {
893 final Tree tree
= fTreeViewer
.getTree();
894 for (String columnName
: columnNames
) {
895 TreeColumn column
= new TreeColumn(tree
, SWT
.LEFT
);
896 column
.setText(columnName
);
902 * Sets the tree columns for this time graph combo's filter dialog.
904 * @param columnNames the tree column names
906 public void setFilterColumns(String
[] columnNames
) {
907 getShowFilterDialogAction().getFilterDialog().setColumnNames(columnNames
);
911 * Sets the time graph content provider used by this time graph combo.
913 * @param timeGraphContentProvider
914 * the time graph content provider
916 public void setTimeGraphContentProvider(ITimeGraphContentProvider timeGraphContentProvider
) {
917 fTimeGraphViewer
.setTimeGraphContentProvider(timeGraphContentProvider
);
921 * Sets the time graph presentation provider used by this time graph combo.
923 * @param timeGraphProvider the time graph provider
925 public void setTimeGraphProvider(ITimeGraphPresentationProvider timeGraphProvider
) {
926 fTimeGraphViewer
.setTimeGraphProvider(timeGraphProvider
);
930 * Sets or clears the input for this time graph combo.
932 * @param input the input of this time graph combo, or <code>null</code> if none
934 public void setInput(Object input
) {
935 fInhibitTreeSelection
= true;
936 fTreeViewer
.setInput(input
);
937 for (SelectionListenerWrapper listenerWrapper
: fSelectionListenerMap
.values()) {
938 listenerWrapper
.selection
= null;
940 fInhibitTreeSelection
= false;
941 if (fScrollBarsInTreeWorkaround
) {
942 fTreeViewer
.getTree().getVerticalBar().setEnabled(false);
943 fTreeViewer
.getTree().getVerticalBar().setVisible(false);
945 fTimeGraphViewer
.setInput(input
);
946 fTimeGraphViewer
.setItemHeight(getItemHeight(fTreeViewer
.getTree(), false));
947 // queue the alignment update because in Linux the item bounds are not
948 // set properly until the tree has been painted at least once
949 fVisibleExpandedItems
= null; // invalidate the cache
950 getDisplay().asyncExec(new Runnable() {
953 alignTreeItems(true);
958 * Gets the input for this time graph combo.
960 * @return The input of this time graph combo, or <code>null</code> if none
962 public Object
getInput() {
963 return fTreeViewer
.getInput();
967 * Sets or clears the list of links to display on this combo
969 * @param links the links to display in this time graph combo
971 public void setLinks(List
<ILinkEvent
> links
) {
972 fTimeGraphViewer
.setLinks(links
);
976 * @param filter The filter object to be attached to the view
978 public void addFilter(@NonNull ViewerFilter filter
) {
979 fInhibitTreeSelection
= true;
980 ViewerFilter wrapper
= new ViewerFilterWrapper(filter
);
981 fTreeViewer
.addFilter(wrapper
);
982 fTimeGraphViewer
.addFilter(filter
);
983 fViewerFilterMap
.put(filter
, wrapper
);
984 alignTreeItems(true);
985 fInhibitTreeSelection
= false;
989 * @param filter The filter object to be removed from the view
991 public void removeFilter(@NonNull ViewerFilter filter
) {
992 fInhibitTreeSelection
= true;
993 ViewerFilter wrapper
= fViewerFilterMap
.get(filter
);
994 fTreeViewer
.removeFilter(wrapper
);
995 fTimeGraphViewer
.removeFilter(filter
);
996 fViewerFilterMap
.remove(filter
);
997 alignTreeItems(true);
998 fInhibitTreeSelection
= false;
1002 * Returns this viewer's filters.
1004 * @return an array of viewer filters
1007 public @NonNull ViewerFilter
[] getFilters() {
1008 return fTimeGraphViewer
.getFilters();
1012 * Sets the filters, replacing any previous filters, and triggers
1013 * refiltering of the elements.
1016 * an array of viewer filters, or null
1019 public void setFilters(@NonNull ViewerFilter
[] filters
) {
1020 fInhibitTreeSelection
= true;
1021 fViewerFilterMap
.clear();
1022 if (filters
== null) {
1023 fTreeViewer
.resetFilters();
1025 for (ViewerFilter filter
: filters
) {
1026 ViewerFilter wrapper
= new ViewerFilterWrapper(filter
);
1027 fViewerFilterMap
.put(filter
, wrapper
);
1029 ViewerFilter
[] wrappers
= Iterables
.toArray(fViewerFilterMap
.values(), ViewerFilter
.class);
1030 fTreeViewer
.setFilters(wrappers
);
1032 fTimeGraphViewer
.setFilters(filters
);
1033 alignTreeItems(true);
1034 fInhibitTreeSelection
= false;
1038 * Refreshes this time graph completely with information freshly obtained from its model.
1040 public void refresh() {
1041 fInhibitTreeSelection
= true;
1042 Tree tree
= fTreeViewer
.getTree();
1044 tree
.setRedraw(false);
1045 fTreeViewer
.refresh();
1047 tree
.setRedraw(true);
1049 fTimeGraphViewer
.refresh();
1050 alignTreeItems(true);
1051 fInhibitTreeSelection
= false;
1055 * Adds a listener for selection changes in this time graph combo.
1057 * @param listener a selection listener
1059 public void addSelectionListener(ITimeGraphSelectionListener listener
) {
1060 SelectionListenerWrapper listenerWrapper
= new SelectionListenerWrapper(listener
);
1061 fTreeViewer
.addSelectionChangedListener(listenerWrapper
);
1062 fSelectionListenerMap
.put(listener
, listenerWrapper
);
1063 fTimeGraphViewer
.addSelectionListener(listenerWrapper
);
1067 * Removes the given selection listener from this time graph combo.
1069 * @param listener a selection changed listener
1071 public void removeSelectionListener(ITimeGraphSelectionListener listener
) {
1072 SelectionListenerWrapper listenerWrapper
= fSelectionListenerMap
.remove(listener
);
1073 fTreeViewer
.removeSelectionChangedListener(listenerWrapper
);
1074 fTimeGraphViewer
.removeSelectionListener(listenerWrapper
);
1078 * Sets the current selection for this time graph combo.
1080 * @param selection the new selection
1082 public void setSelection(ITimeGraphEntry selection
) {
1083 fTimeGraphViewer
.setSelection(selection
);
1084 fInhibitTreeSelection
= true; // block the tree selection changed listener
1085 if (selection
!= null) {
1086 StructuredSelection structuredSelection
= new StructuredSelection(selection
);
1087 fTreeViewer
.setSelection(structuredSelection
);
1089 fTreeViewer
.setSelection(new StructuredSelection());
1091 fInhibitTreeSelection
= false;
1092 alignTreeItems(false);
1096 * Sets the auto-expand level to be used for new entries discovered when
1097 * calling {@link #setInput(Object)} or {@link #refresh()}. The value 0
1098 * means that there is no auto-expand; 1 means that top-level entries are
1099 * expanded, but not their children; 2 means that top-level entries are
1100 * expanded, and their children, but not grand-children; and so on.
1102 * The value {@link #ALL_LEVELS} means that all subtrees should be expanded.
1106 * non-negative level, or <code>ALL_LEVELS</code> to expand all
1107 * levels of the tree
1109 public void setAutoExpandLevel(int level
) {
1110 fTimeGraphViewer
.setAutoExpandLevel(level
);
1112 fTreeViewer
.setAutoExpandLevel(level
);
1114 fTreeViewer
.setAutoExpandLevel(level
+ 1);
1119 * Returns the auto-expand level.
1121 * @return non-negative level, or <code>ALL_LEVELS</code> if all levels of
1122 * the tree are expanded automatically
1123 * @see #setAutoExpandLevel
1125 public int getAutoExpandLevel() {
1126 return fTimeGraphViewer
.getAutoExpandLevel();
1130 * Set the expanded state of an entry
1133 * The entry to expand/collapse
1135 * True for expanded, false for collapsed
1137 public void setExpandedState(ITimeGraphEntry entry
, boolean expanded
) {
1138 fTimeGraphViewer
.setExpandedState(entry
, expanded
);
1139 fTreeViewer
.setExpandedState(entry
, expanded
);
1140 alignTreeItems(true);
1144 * Collapses all nodes of the viewer's tree, starting with the root.
1146 public void collapseAll() {
1147 fTimeGraphViewer
.collapseAll();
1148 fTreeViewer
.collapseAll();
1149 alignTreeItems(true);
1153 * Expands all nodes of the viewer's tree, starting with the root.
1155 public void expandAll() {
1156 fTimeGraphViewer
.expandAll();
1157 fTreeViewer
.expandAll();
1158 alignTreeItems(true);
1161 // ------------------------------------------------------------------------
1163 // ------------------------------------------------------------------------
1165 private List
<TreeItem
> getVisibleExpandedItems(Tree tree
, boolean refresh
) {
1166 if (fVisibleExpandedItems
== null || refresh
) {
1167 List
<TreeItem
> visibleExpandedItems
= new ArrayList
<>();
1168 addVisibleExpandedItems(visibleExpandedItems
, tree
.getItems());
1169 fVisibleExpandedItems
= visibleExpandedItems
;
1171 return fVisibleExpandedItems
;
1174 private void addVisibleExpandedItems(List
<TreeItem
> visibleExpandedItems
, TreeItem
[] items
) {
1175 for (TreeItem item
: items
) {
1176 Object data
= item
.getData();
1177 if (data
== FILLER
) {
1180 visibleExpandedItems
.add(item
);
1181 boolean expandedState
= fTimeGraphViewer
.getExpandedState((ITimeGraphEntry
) data
);
1182 if (item
.getExpanded() != expandedState
) {
1183 /* synchronize the expanded state of both viewers */
1184 fTreeViewer
.setExpandedState(data
, expandedState
);
1186 if (expandedState
) {
1187 addVisibleExpandedItems(visibleExpandedItems
, item
.getItems());
1192 private int getItemHeight(final Tree tree
, boolean force
) {
1194 * Bug in Linux. The method getItemHeight doesn't always return the correct value.
1196 if (fLinuxItemHeight
>= 0 && System
.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$
1197 if (fLinuxItemHeight
!= 0 && !force
) {
1198 return fLinuxItemHeight
;
1201 if (getVisibleExpandedItems(tree
, true).size() > 1) {
1202 PaintListener paintListener
= new PaintListener() {
1204 public void paintControl(PaintEvent e
) {
1205 // get the treeItems here to have all items
1206 List
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
, true);
1207 if (treeItems
.size() < 2) {
1210 final TreeItem treeItem0
= treeItems
.get(0);
1211 final TreeItem treeItem1
= treeItems
.get(1);
1212 tree
.removePaintListener(this);
1213 int y0
= treeItem0
.getBounds().y
;
1214 int y1
= treeItem1
.getBounds().y
;
1215 int itemHeight
= y1
- y0
;
1216 if (itemHeight
> 0) {
1217 fLinuxItemHeight
= itemHeight
;
1218 fTimeGraphViewer
.setItemHeight(itemHeight
);
1222 tree
.addPaintListener(paintListener
);
1225 fLinuxItemHeight
= -1; // Not Linux, don't perform os.name check anymore
1227 return tree
.getItemHeight();
1230 private void alignTreeItems(boolean refreshExpandedItems
) {
1231 // align the tree top item with the time graph top item
1232 Tree tree
= fTreeViewer
.getTree();
1233 List
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
, refreshExpandedItems
);
1234 int topIndex
= fTimeGraphViewer
.getTopIndex();
1235 if (topIndex
>= treeItems
.size()) {
1238 TreeItem item
= treeItems
.get(topIndex
);
1239 tree
.setTopItem(item
);
1242 * In GTK3, the bounds of the tree items are only sure to be correct
1243 * after the tree has been painted.
1245 tree
.addPaintListener(new PaintListener() {
1247 public void paintControl(PaintEvent e
) {
1248 tree
.removePaintListener(this);
1253 /* Make sure the paint event is triggered. */
1257 private void doAlignTreeItems() {
1258 Tree tree
= fTreeViewer
.getTree();
1259 List
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
, false);
1260 int topIndex
= fTimeGraphViewer
.getTopIndex();
1261 if (topIndex
>= treeItems
.size()) {
1264 TreeItem item
= treeItems
.get(topIndex
);
1266 // get the first filler item so we can calculate the last item's height
1267 TreeItem fillerItem
= null;
1268 for (TreeItem treeItem
: fTreeViewer
.getTree().getItems()) {
1269 if (treeItem
.getData() == FILLER
) {
1270 fillerItem
= treeItem
;
1275 // ensure the time graph item heights are equal to the tree item heights
1276 int treeHeight
= fTreeViewer
.getTree().getBounds().height
;
1277 int index
= topIndex
;
1278 Rectangle bounds
= item
.getBounds();
1279 while (index
< treeItems
.size()) {
1280 if (bounds
.y
> treeHeight
) {
1283 TreeItem nextItem
= (index
+ 1 == treeItems
.size()) ? fillerItem
: treeItems
.get(index
+ 1);
1284 Rectangle nextBounds
= alignTreeItem(item
, bounds
, nextItem
);
1287 bounds
= nextBounds
;
1291 * When an item's height in the time graph changes, it is possible that
1292 * the time graph readjusts its top index to fill empty space at the
1293 * bottom of the viewer. Calling method setTopIndex() triggers this
1294 * adjustment, if needed. In that case, we need to make sure that the
1295 * newly visible items at the top of the viewer are also aligned.
1297 fTimeGraphViewer
.setTopIndex(topIndex
);
1298 if (fTimeGraphViewer
.getTopIndex() != topIndex
) {
1299 alignTreeItems(false);
1303 private Rectangle
alignTreeItem(TreeItem item
, Rectangle bounds
, TreeItem nextItem
) {
1305 * Bug in Linux. The method getBounds doesn't always return the correct height.
1306 * Use the difference of y position between items to calculate the height.
1308 Rectangle nextBounds
= nextItem
.getBounds();
1309 Integer itemHeight
= nextBounds
.y
- bounds
.y
;
1310 if (itemHeight
> 0) {
1311 ITimeGraphEntry entry
= (ITimeGraphEntry
) item
.getData();
1312 fTimeGraphViewer
.getTimeGraphControl().setItemHeight(entry
, itemHeight
);
1318 * Return the time alignment information
1320 * @return the time alignment information
1322 * @see ITmfTimeAligned
1326 public TmfTimeViewAlignmentInfo
getTimeViewAlignmentInfo() {
1327 Point location
= fSashForm
.toDisplay(0, 0);
1328 int timeAxisOffset
= fTreeViewer
.getControl().getSize().x
+ fSashForm
.getSashWidth();
1329 return new TmfTimeViewAlignmentInfo(fSashForm
.getShell(), location
, timeAxisOffset
);
1333 * Return the available width for the time-axis.
1335 * @see ITmfTimeAligned
1337 * @param requestedOffset
1338 * the requested offset
1339 * @return the available width for the time-axis
1343 public int getAvailableWidth(int requestedOffset
) {
1344 int vBarWidth
= ((fTimeGraphViewer
.getVerticalBar() != null) && (fTimeGraphViewer
.getVerticalBar().isVisible())) ? fTimeGraphViewer
.getVerticalBar().getSize().x
: 0;
1345 int totalWidth
= fSashForm
.getBounds().width
;
1346 return Math
.min(totalWidth
, Math
.max(0, totalWidth
- requestedOffset
- vBarWidth
));
1350 * Perform the alignment operation.
1353 * the alignment offset
1355 * the alignment width
1357 * @see ITmfTimeAligned
1361 public void performAlign(int offset
, int width
) {
1362 int total
= fSashForm
.getBounds().width
;
1363 int timeAxisOffset
= Math
.min(offset
, total
);
1364 int width1
= Math
.max(0, timeAxisOffset
- fSashForm
.getSashWidth());
1365 int width2
= total
- timeAxisOffset
;
1366 if (width1
>= 0 && width2
> 0 || width1
> 0 && width2
>= 0) {
1367 fSashForm
.setWeights(new int[] { width1
, width2
});
1371 Composite composite
= fTimeGraphViewer
.getTimeAlignedComposite();
1372 GridLayout layout
= (GridLayout
) composite
.getLayout();
1373 int timeBasedControlsWidth
= composite
.getSize().x
;
1374 int marginSize
= timeBasedControlsWidth
- width
;
1375 layout
.marginRight
= Math
.max(0, marginSize
);