8601b41e47fa2430be4b23f0402ee00695af796c
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / widgets / timegraph / TimeGraphCombo.java
1 /*******************************************************************************
2 * Copyright (c) 2012, 2015 Ericsson, others
3 *
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
8 *
9 * Contributors:
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 *******************************************************************************/
15
16 package org.eclipse.tracecompass.tmf.ui.widgets.timegraph;
17
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;
23 import java.util.Map;
24 import java.util.Set;
25
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.KeyEvent;
45 import org.eclipse.swt.events.MouseAdapter;
46 import org.eclipse.swt.events.MouseEvent;
47 import org.eclipse.swt.events.MouseTrackAdapter;
48 import org.eclipse.swt.events.PaintEvent;
49 import org.eclipse.swt.events.PaintListener;
50 import org.eclipse.swt.events.SelectionAdapter;
51 import org.eclipse.swt.events.SelectionEvent;
52 import org.eclipse.swt.graphics.Font;
53 import org.eclipse.swt.graphics.FontData;
54 import org.eclipse.swt.graphics.GC;
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.ITimeDataProvider;
79 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphColorScheme;
80 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphControl;
81 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphMarkerAxis;
82
83 import com.google.common.collect.Iterables;
84
85 /**
86 * Time graph "combo" view (with the list/tree on the left and the gantt chart
87 * on the right)
88 *
89 * @author Patrick Tasse
90 */
91 public class TimeGraphCombo extends Composite {
92
93 // ------------------------------------------------------------------------
94 // Constants
95 // ------------------------------------------------------------------------
96
97 /** Constant indicating that all levels of the time graph should be expanded */
98 public static final int ALL_LEVELS = AbstractTreeViewer.ALL_LEVELS;
99
100 private static final Object FILLER = new Object();
101
102 // ------------------------------------------------------------------------
103 // Fields
104 // ------------------------------------------------------------------------
105
106 /** The tree viewer */
107 private TreeViewer fTreeViewer;
108
109 /** The time viewer */
110 private @NonNull TimeGraphViewer fTimeGraphViewer;
111
112 /** The selection listener map */
113 private final Map<ITimeGraphSelectionListener, SelectionListenerWrapper> fSelectionListenerMap = new HashMap<>();
114
115 /** The map of viewer filters to viewer filter wrappers */
116 private final Map<@NonNull ViewerFilter, @NonNull ViewerFilter> fViewerFilterMap = new HashMap<>();
117
118 /**
119 * Flag to block the tree selection changed listener when triggered by the
120 * time graph combo
121 */
122 private boolean fInhibitTreeSelection = false;
123
124 /** Number of filler rows used by the tree content provider */
125 private int fNumFillerRows;
126
127 /** Calculated item height for Linux workaround */
128 private int fLinuxItemHeight = 0;
129
130 /** The action that opens the filter dialog */
131 private ShowFilterDialogAction fShowFilterDialogAction;
132
133 /** Default weight of each part of the sash */
134 private static final int[] DEFAULT_WEIGHTS = { 1, 1 };
135
136 /** List of all expanded items whose parents are also expanded */
137 private List<TreeItem> fVisibleExpandedItems = null;
138
139 private Listener fSashDragListener;
140 private SashForm fSashForm;
141
142 private final boolean fScrollBarsInTreeWorkaround;
143
144 private Font fTreeFont;
145
146 // ------------------------------------------------------------------------
147 // Classes
148 // ------------------------------------------------------------------------
149
150 /**
151 * The TimeGraphViewerExtension is used to set appropriate values and to
152 * override methods that could be called directly by the user and that must
153 * be handled by the time graph combo.
154 */
155 private class TimeGraphViewerExtension extends TimeGraphViewer {
156
157 private TimeGraphViewerExtension(Composite parent, int style, Tree tree) {
158 super(parent, style);
159 setItemHeight(TimeGraphCombo.this.getItemHeight(tree, true));
160 setHeaderHeight(tree.getHeaderHeight());
161 setBorderWidth(tree.getBorderWidth());
162 setNameWidthPref(0);
163 }
164
165 @Override
166 public ShowFilterDialogAction getShowFilterDialogAction() {
167 return TimeGraphCombo.this.getShowFilterDialogAction();
168 }
169
170 @Override
171 protected TimeGraphControl createTimeGraphControl(Composite composite, TimeGraphColorScheme colors) {
172 return new TimeGraphControl(composite, colors) {
173 @Override
174 public void verticalZoom(boolean zoomIn) {
175 TimeGraphCombo.this.verticalZoom(zoomIn);
176 }
177
178 @Override
179 public void resetVerticalZoom() {
180 TimeGraphCombo.this.resetVerticalZoom();
181 }
182
183 @Override
184 public void setElementPosition(ITimeGraphEntry entry, int y) {
185 /*
186 * Queue the update to make sure the time graph combo has finished
187 * updating the item heights.
188 */
189 getDisplay().asyncExec(() -> {
190 super.setElementPosition(entry, y);
191 alignTreeItems(false);
192 });
193 }
194 };
195 }
196
197 private class TimeGraphMarkerAxisExtension extends TimeGraphMarkerAxis {
198 private int fMargin = 0;
199
200 public TimeGraphMarkerAxisExtension(Composite parent, @NonNull TimeGraphColorScheme colorScheme, @NonNull ITimeDataProvider timeProvider) {
201 super(parent, colorScheme, timeProvider);
202 }
203
204 @Override
205 public Point computeSize(int wHint, int hHint, boolean changed) {
206 Point size = super.computeSize(wHint, hHint, changed);
207 if (size.y > 0) {
208 size.y += fMargin;
209 }
210 return size;
211 }
212
213 @Override
214 public void redraw() {
215 super.redraw();
216 fTreeViewer.getControl().redraw();
217 }
218
219 @Override
220 protected void drawMarkerAxis(Rectangle bounds, int nameSpace, GC gc) {
221 super.drawMarkerAxis(bounds, nameSpace, gc);
222 }
223
224 private Rectangle getAxisBounds() {
225 Tree tree = fTreeViewer.getTree();
226 Rectangle axisBounds = getBounds();
227 Rectangle treeClientArea = tree.getClientArea();
228 if (axisBounds.isEmpty()) {
229 treeClientArea.y += treeClientArea.height;
230 treeClientArea.height = 0;
231 return treeClientArea;
232 }
233 Composite axisParent = getParent();
234 Point axisDisplayCoordinates = axisParent.toDisplay(axisBounds.x, axisBounds.y);
235 Point axisTreeCoordinates = tree.toControl(axisDisplayCoordinates);
236 int height = treeClientArea.y + treeClientArea.height - axisTreeCoordinates.y;
237 int margin = Math.max(0, axisBounds.height - height);
238 if (fMargin != margin) {
239 fMargin = margin;
240 getParent().layout();
241 redraw();
242 axisTreeCoordinates.y -= margin;
243 height += margin;
244 }
245 return new Rectangle(treeClientArea.x, axisTreeCoordinates.y, treeClientArea.width, height);
246 }
247 }
248
249 @Override
250 protected TimeGraphMarkerAxis createTimeGraphMarkerAxis(Composite parent, @NonNull TimeGraphColorScheme colorScheme, @NonNull ITimeDataProvider timeProvider) {
251 TimeGraphMarkerAxisExtension timeGraphMarkerAxis = new TimeGraphMarkerAxisExtension(parent, colorScheme, timeProvider);
252 Tree tree = fTreeViewer.getTree();
253 tree.addPaintListener(e -> {
254 /*
255 * Draw the marker axis over the tree. Only the name area
256 * will be drawn, since the time area has zero width.
257 */
258 Rectangle bounds = timeGraphMarkerAxis.getAxisBounds();
259 e.gc.setBackground(timeGraphMarkerAxis.getBackground());
260 timeGraphMarkerAxis.drawMarkerAxis(bounds, bounds.width, e.gc);
261 });
262 tree.addMouseListener(new MouseAdapter() {
263 @Override
264 public void mouseDown(MouseEvent e) {
265 Rectangle bounds = timeGraphMarkerAxis.getAxisBounds();
266 if (bounds.contains(e.x, e.y)) {
267 timeGraphMarkerAxis.mouseDown(e, bounds, bounds.width);
268 }
269 }
270 });
271 tree.getHorizontalBar().addSelectionListener(new SelectionAdapter() {
272 @Override
273 public void widgetSelected(SelectionEvent e) {
274 tree.redraw();
275 }
276 });
277 return timeGraphMarkerAxis;
278 }
279 }
280
281 /**
282 * The TreeContentProviderWrapper is used to insert filler items after
283 * the elements of the tree's real content provider.
284 */
285 private class TreeContentProviderWrapper implements ITreeContentProvider {
286 private final ITreeContentProvider contentProvider;
287
288 public TreeContentProviderWrapper(ITreeContentProvider contentProvider) {
289 this.contentProvider = contentProvider;
290 }
291
292 @Override
293 public void dispose() {
294 contentProvider.dispose();
295 }
296
297 @Override
298 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
299 contentProvider.inputChanged(viewer, oldInput, newInput);
300 }
301
302 @Override
303 public Object[] getElements(Object inputElement) {
304 Object[] elements = contentProvider.getElements(inputElement);
305 // add filler elements to ensure alignment with time analysis viewer
306 Object[] oElements = Arrays.copyOf(elements, elements.length + fNumFillerRows, Object[].class);
307 for (int i = 0; i < fNumFillerRows; i++) {
308 oElements[elements.length + i] = FILLER;
309 }
310 return oElements;
311 }
312
313 @Override
314 public Object[] getChildren(Object parentElement) {
315 if (parentElement instanceof ITimeGraphEntry) {
316 return contentProvider.getChildren(parentElement);
317 }
318 return new Object[0];
319 }
320
321 @Override
322 public Object getParent(Object element) {
323 if (element instanceof ITimeGraphEntry) {
324 return contentProvider.getParent(element);
325 }
326 return null;
327 }
328
329 @Override
330 public boolean hasChildren(Object element) {
331 if (element instanceof ITimeGraphEntry) {
332 return contentProvider.hasChildren(element);
333 }
334 return false;
335 }
336 }
337
338 /**
339 * The TreeLabelProviderWrapper is used to intercept the filler items
340 * from the calls to the tree's real label provider.
341 */
342 private static class TreeLabelProviderWrapper implements ITableLabelProvider {
343 private final ITableLabelProvider labelProvider;
344
345 public TreeLabelProviderWrapper(ITableLabelProvider labelProvider) {
346 this.labelProvider = labelProvider;
347 }
348
349 @Override
350 public void addListener(ILabelProviderListener listener) {
351 labelProvider.addListener(listener);
352 }
353
354 @Override
355 public void dispose() {
356 labelProvider.dispose();
357 }
358
359 @Override
360 public boolean isLabelProperty(Object element, String property) {
361 if (element instanceof ITimeGraphEntry) {
362 return labelProvider.isLabelProperty(element, property);
363 }
364 return false;
365 }
366
367 @Override
368 public void removeListener(ILabelProviderListener listener) {
369 labelProvider.removeListener(listener);
370 }
371
372 @Override
373 public Image getColumnImage(Object element, int columnIndex) {
374 if (element instanceof ITimeGraphEntry) {
375 return labelProvider.getColumnImage(element, columnIndex);
376 }
377 return null;
378 }
379
380 @Override
381 public String getColumnText(Object element, int columnIndex) {
382 if (element instanceof ITimeGraphEntry) {
383 return labelProvider.getColumnText(element, columnIndex);
384 }
385 return null;
386 }
387
388 }
389
390 /**
391 * The SelectionListenerWrapper is used to intercept the filler items from
392 * the time graph combo's real selection listener, and to prevent double
393 * notifications from being sent when selection changes in both tree and
394 * time graph at the same time.
395 */
396 private class SelectionListenerWrapper implements ISelectionChangedListener, ITimeGraphSelectionListener {
397 private final ITimeGraphSelectionListener listener;
398 private ITimeGraphEntry selection = null;
399
400 public SelectionListenerWrapper(ITimeGraphSelectionListener listener) {
401 this.listener = listener;
402 }
403
404 @Override
405 public void selectionChanged(SelectionChangedEvent event) {
406 if (fInhibitTreeSelection) {
407 return;
408 }
409 Object element = ((IStructuredSelection) event.getSelection()).getFirstElement();
410 if (element instanceof ITimeGraphEntry) {
411 ITimeGraphEntry entry = (ITimeGraphEntry) element;
412 if (entry != selection) {
413 selection = entry;
414 listener.selectionChanged(new TimeGraphSelectionEvent(event.getSource(), selection));
415 }
416 }
417 }
418
419 @Override
420 public void selectionChanged(TimeGraphSelectionEvent event) {
421 ITimeGraphEntry entry = event.getSelection();
422 if (entry != selection) {
423 selection = entry;
424 listener.selectionChanged(new TimeGraphSelectionEvent(event.getSource(), selection));
425 }
426 }
427 }
428
429 /**
430 * The ViewerFilterWrapper is used to intercept the filler items from
431 * the time graph combo's real ViewerFilters. These filler items should
432 * always be visible.
433 */
434 private static class ViewerFilterWrapper extends ViewerFilter {
435
436 private ViewerFilter fWrappedFilter;
437
438 ViewerFilterWrapper(ViewerFilter filter) {
439 super();
440 this.fWrappedFilter = filter;
441 }
442
443 @Override
444 public boolean select(Viewer viewer, Object parentElement, Object element) {
445 if (element instanceof ITimeGraphEntry) {
446 return fWrappedFilter.select(viewer, parentElement, element);
447 }
448 return true;
449 }
450
451 }
452
453 // ------------------------------------------------------------------------
454 // Constructors
455 // ------------------------------------------------------------------------
456
457 /**
458 * Constructs a new instance of this class given its parent
459 * and a style value describing its behavior and appearance.
460 *
461 * @param parent a widget which will be the parent of the new instance (cannot be null)
462 * @param style the style of widget to construct
463 */
464 public TimeGraphCombo(Composite parent, int style) {
465 this(parent, style, DEFAULT_WEIGHTS);
466 }
467
468 /**
469 * Constructs a new instance of this class given its parent and a style
470 * value describing its behavior and appearance.
471 *
472 * @param parent
473 * a widget which will be the parent of the new instance (cannot
474 * be null)
475 * @param style
476 * the style of widget to construct
477 * @param weights
478 * The array (length 2) of relative weights of each side of the sash form
479 */
480 public TimeGraphCombo(Composite parent, int style, int[] weights) {
481 super(parent, style);
482 setLayout(new FillLayout());
483
484 fSashForm = new SashForm(this, SWT.NONE);
485
486 /*
487 * In Windows, SWT.H_SCROLL | SWT.NO_SCROLL is not properly supported,
488 * both scroll bars are always created. See Tree.checkStyle: "Even when
489 * WS_HSCROLL or WS_VSCROLL is not specified, Windows creates trees and
490 * tables with scroll bars."
491 */
492 fScrollBarsInTreeWorkaround = "win32".equals(SWT.getPlatform()); //$NON-NLS-1$
493
494 int scrollBarStyle = fScrollBarsInTreeWorkaround ? SWT.H_SCROLL : SWT.H_SCROLL | SWT.NO_SCROLL;
495
496 fTreeViewer = new TreeViewer(fSashForm, SWT.FULL_SELECTION | scrollBarStyle);
497 fTreeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
498 final Tree tree = fTreeViewer.getTree();
499 tree.setHeaderVisible(true);
500 tree.setLinesVisible(true);
501
502 fTimeGraphViewer = new TimeGraphViewerExtension(fSashForm, SWT.NONE, tree);
503
504 if (fScrollBarsInTreeWorkaround) {
505 // Feature in Windows. The tree vertical bar reappears when
506 // the control is resized so we need to hide it again.
507 tree.addControlListener(new ControlAdapter() {
508 private int depth = 0;
509
510 @Override
511 public void controlResized(ControlEvent e) {
512 if (depth == 0) {
513 depth++;
514 tree.getVerticalBar().setEnabled(false);
515 // this can trigger controlResized recursively
516 tree.getVerticalBar().setVisible(false);
517 depth--;
518 }
519 }
520 });
521 }
522 // Bug in Linux. The tree header height is 0 in constructor,
523 // so we need to reset it later when the control is painted.
524 // This work around used to be done on control resized but the header
525 // height was not initialized on the initial resize on GTK3.
526 tree.addPaintListener(new PaintListener() {
527 @Override
528 public void paintControl(PaintEvent e) {
529 int headerHeight = tree.getHeaderHeight();
530 if (headerHeight > 0) {
531 fTimeGraphViewer.setHeaderHeight(headerHeight);
532 tree.removePaintListener(this);
533 }
534 }
535 });
536
537 tree.addDisposeListener(e -> {
538 if (fTreeFont != null) {
539 fTreeFont.dispose();
540 }
541 });
542
543 // ensure synchronization of expanded items between tree and time graph
544 fTreeViewer.addTreeListener(new ITreeViewerListener() {
545 @Override
546 public void treeCollapsed(TreeExpansionEvent event) {
547 fTimeGraphViewer.setExpandedState((ITimeGraphEntry) event.getElement(), false);
548 // queue the alignment update because the tree items may only be
549 // actually collapsed after the listeners have been notified
550 fVisibleExpandedItems = null; // invalidate the cache
551 getDisplay().asyncExec(new Runnable() {
552 @Override
553 public void run() {
554 alignTreeItems(true);
555 }});
556 }
557
558 @Override
559 public void treeExpanded(TreeExpansionEvent event) {
560 ITimeGraphEntry entry = (ITimeGraphEntry) event.getElement();
561 fTimeGraphViewer.setExpandedState(entry, true);
562 Set<Object> expandedElements = new HashSet<>(Arrays.asList(fTreeViewer.getExpandedElements()));
563 for (ITimeGraphEntry child : entry.getChildren()) {
564 if (child.hasChildren()) {
565 boolean expanded = expandedElements.contains(child);
566 fTimeGraphViewer.setExpandedState(child, expanded);
567 }
568 }
569 // queue the alignment update because the tree items may only be
570 // actually expanded after the listeners have been notified
571 fVisibleExpandedItems = null; // invalidate the cache
572 getDisplay().asyncExec(new Runnable() {
573 @Override
574 public void run() {
575 alignTreeItems(true);
576 }});
577 }
578 });
579
580 // ensure synchronization of expanded items between tree and time graph
581 fTimeGraphViewer.addTreeListener(new ITimeGraphTreeListener() {
582 @Override
583 public void treeCollapsed(TimeGraphTreeExpansionEvent event) {
584 fTreeViewer.setExpandedState(event.getEntry(), false);
585 alignTreeItems(true);
586 }
587
588 @Override
589 public void treeExpanded(TimeGraphTreeExpansionEvent event) {
590 ITimeGraphEntry entry = event.getEntry();
591 fTreeViewer.setExpandedState(entry, true);
592 Set<Object> expandedElements = new HashSet<>(Arrays.asList(fTreeViewer.getExpandedElements()));
593 for (ITimeGraphEntry child : entry.getChildren()) {
594 if (child.hasChildren()) {
595 boolean expanded = expandedElements.contains(child);
596 fTimeGraphViewer.setExpandedState(child, expanded);
597 }
598 }
599 alignTreeItems(true);
600 }
601 });
602
603 // prevent mouse button from selecting a filler tree item
604 tree.addListener(SWT.MouseDown, event -> {
605 TreeItem treeItem = tree.getItem(new Point(event.x, event.y));
606 if (treeItem == null || treeItem.getData() == FILLER) {
607 event.doit = false;
608 List<TreeItem> treeItems = getVisibleExpandedItems(tree, false);
609 if (treeItems.size() == 0) {
610 fTreeViewer.setSelection(new StructuredSelection());
611 fTimeGraphViewer.setSelection(null);
612 return;
613 }
614 // this prevents from scrolling up when selecting
615 // the partially visible tree item at the bottom
616 tree.select(treeItems.get(treeItems.size() - 1));
617 fTreeViewer.setSelection(new StructuredSelection());
618 fTimeGraphViewer.setSelection(null);
619 }
620 });
621
622 // prevent mouse wheel from scrolling down into filler tree items
623 tree.addListener(SWT.MouseWheel, event -> {
624 event.doit = false;
625 if (event.count == 0) {
626 return;
627 }
628 Slider scrollBar = fTimeGraphViewer.getVerticalBar();
629 fTimeGraphViewer.setTopIndex(scrollBar.getSelection() - event.count);
630 alignTreeItems(false);
631 });
632
633 // prevent key stroke from selecting a filler tree item
634 tree.addListener(SWT.KeyDown, event -> {
635 List<TreeItem> treeItems = getVisibleExpandedItems(tree, false);
636 if (treeItems.size() == 0) {
637 fTreeViewer.setSelection(new StructuredSelection());
638 event.doit = false;
639 return;
640 }
641 if (event.keyCode == SWT.ARROW_DOWN) {
642 int index = Math.min(fTimeGraphViewer.getSelectionIndex() + 1, treeItems.size() - 1);
643 fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(index).getData());
644 event.doit = false;
645 } else if (event.keyCode == SWT.PAGE_DOWN) {
646 int height = tree.getSize().y - tree.getHeaderHeight() - tree.getHorizontalBar().getSize().y;
647 int countPerPage = height / getItemHeight(tree, false);
648 int index = Math.min(fTimeGraphViewer.getSelectionIndex() + countPerPage - 1, treeItems.size() - 1);
649 fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(index).getData());
650 event.doit = false;
651 } else if (event.keyCode == SWT.END) {
652 fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(treeItems.size() - 1).getData());
653 event.doit = false;
654 } else if ((event.character == '+' || event.character == '=') && ((event.stateMask & SWT.CTRL) != 0)) {
655 fTimeGraphViewer.getTimeGraphControl().keyPressed(new KeyEvent(event));
656 return;
657 } else if (event.character == '-' && ((event.stateMask & SWT.CTRL) != 0)) {
658 fTimeGraphViewer.getTimeGraphControl().keyPressed(new KeyEvent(event));
659 return;
660 } else if (event.character == '0' && ((event.stateMask & SWT.CTRL) != 0)) {
661 fTimeGraphViewer.getTimeGraphControl().keyPressed(new KeyEvent(event));
662 return;
663 } else {
664 return;
665 }
666 if (fTimeGraphViewer.getSelectionIndex() >= 0) {
667 fTreeViewer.setSelection(new StructuredSelection(fTimeGraphViewer.getSelection()));
668 } else {
669 fTreeViewer.setSelection(new StructuredSelection());
670 }
671 alignTreeItems(false);
672 });
673
674 // ensure alignment of top item between tree and time graph
675 fTimeGraphViewer.getTimeGraphControl().addControlListener(new ControlAdapter() {
676 @Override
677 public void controlResized(ControlEvent e) {
678 alignTreeItems(false);
679 }
680 });
681
682 // ensure synchronization of selected item between tree and time graph
683 fTreeViewer.addSelectionChangedListener(event -> {
684 if (fInhibitTreeSelection) {
685 return;
686 }
687 if (event.getSelection() instanceof IStructuredSelection) {
688 Object selection = ((IStructuredSelection) event.getSelection()).getFirstElement();
689 if (selection instanceof ITimeGraphEntry) {
690 fTimeGraphViewer.setSelection((ITimeGraphEntry) selection);
691 }
692 alignTreeItems(false);
693 }
694 });
695
696 // ensure synchronization of selected item between tree and time graph
697 fTimeGraphViewer.addSelectionListener(event -> {
698 ITimeGraphEntry entry = fTimeGraphViewer.getSelection();
699 fInhibitTreeSelection = true; // block the tree selection changed listener
700 if (entry != null) {
701 StructuredSelection selection = new StructuredSelection(entry);
702 fTreeViewer.setSelection(selection);
703 } else {
704 fTreeViewer.setSelection(new StructuredSelection());
705 }
706 fInhibitTreeSelection = false;
707 alignTreeItems(false);
708 });
709
710 // ensure alignment of top item between tree and time graph
711 fTimeGraphViewer.getVerticalBar().addSelectionListener(new SelectionAdapter() {
712 @Override
713 public void widgetSelected(SelectionEvent e) {
714 alignTreeItems(false);
715 }
716 });
717
718 // ensure alignment of top item between tree and time graph
719 fTimeGraphViewer.getTimeGraphControl().addMouseWheelListener(e -> {
720 if (e.count == 0) {
721 return;
722 }
723 alignTreeItems(false);
724 });
725
726 // ensure the tree has focus control when mouse is over it if the time graph had control
727 fTreeViewer.getControl().addMouseTrackListener(new MouseTrackAdapter() {
728 @Override
729 public void mouseEnter(MouseEvent e) {
730 if (fTimeGraphViewer.getTimeGraphControl().isFocusControl()) {
731 fTreeViewer.getControl().setFocus();
732 }
733 }
734 });
735
736 // ensure the time graph has focus control when mouse is over it if the tree had control
737 fTimeGraphViewer.getTimeGraphControl().addMouseTrackListener(new MouseTrackAdapter() {
738 @Override
739 public void mouseEnter(MouseEvent e) {
740 if (fTreeViewer.getControl().isFocusControl()) {
741 fTimeGraphViewer.getTimeGraphControl().setFocus();
742 }
743 }
744 });
745 fTimeGraphViewer.getTimeGraphScale().addMouseTrackListener(new MouseTrackAdapter() {
746 @Override
747 public void mouseEnter(MouseEvent e) {
748 if (fTreeViewer.getControl().isFocusControl()) {
749 fTimeGraphViewer.getTimeGraphControl().setFocus();
750 }
751 }
752 });
753
754 // The filler rows are required to ensure alignment when the tree does not have a
755 // visible horizontal scroll bar. The tree does not allow its top item to be set
756 // to a value that would cause blank space to be drawn at the bottom of the tree.
757 fNumFillerRows = Display.getDefault().getBounds().height / getItemHeight(tree, false);
758
759 fSashForm.setWeights(weights);
760
761 fTimeGraphViewer.getTimeGraphControl().addPaintListener(new PaintListener() {
762 @Override
763 public void paintControl(PaintEvent e) {
764 // Sashes in a SashForm are being created on layout so add the
765 // drag listener here
766 if (fSashDragListener == null) {
767 for (Control control : fSashForm.getChildren()) {
768 if (control instanceof Sash) {
769 fSashDragListener = new Listener() {
770
771 @Override
772 public void handleEvent(Event event) {
773 sendTimeViewAlignmentChanged();
774
775 }
776 };
777 control.removePaintListener(this);
778 control.addListener(SWT.Selection, fSashDragListener);
779 // There should be only one sash
780 break;
781 }
782 }
783 }
784 }
785 });
786 }
787
788 private void verticalZoom(boolean zoomIn) {
789 Tree tree = fTreeViewer.getTree();
790 FontData fontData = tree.getFont().getFontData()[0];
791 int height = fontData.getHeight() + (zoomIn ? 1 : -1);
792 if (height <= 0) {
793 return;
794 }
795 fontData.setHeight(height);
796 if (fTreeFont != null) {
797 fTreeFont.dispose();
798 }
799 fTreeFont = new Font(tree.getDisplay(), fontData);
800 tree.setFont(fTreeFont);
801 redraw();
802 update();
803 fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
804 fTimeGraphViewer.setItemHeight(getItemHeight(tree, true));
805 alignTreeItems(false);
806 }
807
808 private void resetVerticalZoom() {
809 Tree tree = fTreeViewer.getTree();
810 if (fTreeFont != null) {
811 fTreeFont.dispose();
812 fTreeFont = null;
813 }
814 tree.setFont(null);
815 redraw();
816 update();
817 fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
818 fTimeGraphViewer.setItemHeight(getItemHeight(tree, true));
819 alignTreeItems(false);
820 }
821
822 private void sendTimeViewAlignmentChanged() {
823 TmfSignalManager.dispatchSignal(new TmfTimeViewAlignmentSignal(fSashForm, getTimeViewAlignmentInfo()));
824 }
825
826 // ------------------------------------------------------------------------
827 // Accessors
828 // ------------------------------------------------------------------------
829
830 /**
831 * Returns this time graph combo's tree viewer.
832 *
833 * @return the tree viewer
834 */
835 public TreeViewer getTreeViewer() {
836 return fTreeViewer;
837 }
838
839 /**
840 * Returns this time graph combo's time graph viewer.
841 *
842 * @return the time graph viewer
843 */
844 public @NonNull TimeGraphViewer getTimeGraphViewer() {
845 return fTimeGraphViewer;
846 }
847
848 /**
849 * Get the show filter dialog action.
850 *
851 * @return The Action object
852 * @since 2.0
853 */
854 public ShowFilterDialogAction getShowFilterDialogAction() {
855 if (fShowFilterDialogAction == null) {
856 fShowFilterDialogAction = new ShowFilterDialogAction(fTimeGraphViewer) {
857 @Override
858 protected void addFilter(ViewerFilter filter) {
859 /* add filter to the combo instead of the viewer */
860 TimeGraphCombo.this.addFilter(filter);
861 }
862
863 @Override
864 protected void removeFilter(ViewerFilter filter) {
865 /* remove filter from the combo instead of the viewer */
866 TimeGraphCombo.this.removeFilter(filter);
867 }
868
869 @Override
870 protected void refresh() {
871 /* refresh the combo instead of the viewer */
872 TimeGraphCombo.this.refresh();
873 }
874 };
875 }
876 return fShowFilterDialogAction;
877 }
878
879 // ------------------------------------------------------------------------
880 // Control
881 // ------------------------------------------------------------------------
882
883 @Override
884 public void redraw() {
885 fTimeGraphViewer.getControl().redraw();
886 super.redraw();
887 }
888
889 @Override
890 public void update() {
891 fTimeGraphViewer.getControl().update();
892 super.update();
893 }
894
895 // ------------------------------------------------------------------------
896 // Operations
897 // ------------------------------------------------------------------------
898
899 /**
900 * Sets the tree content provider used by this time graph combo.
901 *
902 * @param contentProvider the tree content provider
903 */
904 public void setTreeContentProvider(ITreeContentProvider contentProvider) {
905 fTreeViewer.setContentProvider(new TreeContentProviderWrapper(contentProvider));
906 }
907
908 /**
909 * Sets the tree label provider used by this time graph combo.
910 *
911 * @param labelProvider the tree label provider
912 */
913 public void setTreeLabelProvider(ITableLabelProvider labelProvider) {
914 fTreeViewer.setLabelProvider(new TreeLabelProviderWrapper(labelProvider));
915 }
916
917 /**
918 * Sets the tree content provider used by the filter dialog
919 *
920 * @param contentProvider the tree content provider
921 */
922 public void setFilterContentProvider(ITreeContentProvider contentProvider) {
923 getShowFilterDialogAction().getFilterDialog().setContentProvider(contentProvider);
924 }
925
926 /**
927 * Sets the tree label provider used by the filter dialog
928 *
929 * @param labelProvider the tree label provider
930 */
931 public void setFilterLabelProvider(ITableLabelProvider labelProvider) {
932 getShowFilterDialogAction().getFilterDialog().setLabelProvider(labelProvider);
933 }
934
935 /**
936 * Adds a "check active" button used by the filter dialog
937 *
938 * @param activeProvider
939 * Additional button info specific to a certain view.
940 * @since 1.0
941 */
942 public void addTimeGraphFilterCheckActiveButton(ITimeGraphEntryActiveProvider activeProvider) {
943 getShowFilterDialogAction().getFilterDialog().addTimeGraphFilterCheckActiveButton(activeProvider);
944 }
945
946 /**
947 * Adds an "uncheck inactive" button used by the filter dialog
948 *
949 * @param inactiveProvider
950 * Additional button info specific to a certain view.
951 * @since 1.0
952 */
953 public void addTimeGraphFilterUncheckInactiveButton(ITimeGraphEntryActiveProvider inactiveProvider) {
954 getShowFilterDialogAction().getFilterDialog().addTimeGraphFilterUncheckInactiveButton(inactiveProvider);
955 }
956
957 /**
958 * Sets the tree columns for this time graph combo.
959 *
960 * @param columnNames the tree column names
961 */
962 public void setTreeColumns(String[] columnNames) {
963 final Tree tree = fTreeViewer.getTree();
964 for (String columnName : columnNames) {
965 TreeColumn column = new TreeColumn(tree, SWT.LEFT);
966 column.setMoveable(true);
967 column.setText(columnName);
968 column.pack();
969 }
970 }
971
972 /**
973 * Sets the tree columns for this time graph combo's filter dialog.
974 *
975 * @param columnNames the tree column names
976 */
977 public void setFilterColumns(String[] columnNames) {
978 getShowFilterDialogAction().getFilterDialog().setColumnNames(columnNames);
979 }
980
981 /**
982 * Sets the time graph content provider used by this time graph combo.
983 *
984 * @param timeGraphContentProvider
985 * the time graph content provider
986 */
987 public void setTimeGraphContentProvider(ITimeGraphContentProvider timeGraphContentProvider) {
988 fTimeGraphViewer.setTimeGraphContentProvider(timeGraphContentProvider);
989 }
990
991 /**
992 * Sets the time graph presentation provider used by this time graph combo.
993 *
994 * @param timeGraphProvider the time graph provider
995 */
996 public void setTimeGraphProvider(ITimeGraphPresentationProvider timeGraphProvider) {
997 fTimeGraphViewer.setTimeGraphProvider(timeGraphProvider);
998 }
999
1000 /**
1001 * Sets or clears the input for this time graph combo.
1002 *
1003 * @param input the input of this time graph combo, or <code>null</code> if none
1004 */
1005 public void setInput(Object input) {
1006 fInhibitTreeSelection = true;
1007 fTreeViewer.setInput(input);
1008 for (SelectionListenerWrapper listenerWrapper : fSelectionListenerMap.values()) {
1009 listenerWrapper.selection = null;
1010 }
1011 fInhibitTreeSelection = false;
1012 if (fScrollBarsInTreeWorkaround) {
1013 fTreeViewer.getTree().getVerticalBar().setEnabled(false);
1014 fTreeViewer.getTree().getVerticalBar().setVisible(false);
1015 }
1016 fTimeGraphViewer.setInput(input);
1017 fTimeGraphViewer.setItemHeight(getItemHeight(fTreeViewer.getTree(), false));
1018 // queue the alignment update because in Linux the item bounds are not
1019 // set properly until the tree has been painted at least once
1020 fVisibleExpandedItems = null; // invalidate the cache
1021 getDisplay().asyncExec(new Runnable() {
1022 @Override
1023 public void run() {
1024 alignTreeItems(true);
1025 }});
1026 }
1027
1028 /**
1029 * Gets the input for this time graph combo.
1030 *
1031 * @return The input of this time graph combo, or <code>null</code> if none
1032 */
1033 public Object getInput() {
1034 return fTreeViewer.getInput();
1035 }
1036
1037 /**
1038 * Sets or clears the list of links to display on this combo
1039 *
1040 * @param links the links to display in this time graph combo
1041 */
1042 public void setLinks(List<ILinkEvent> links) {
1043 fTimeGraphViewer.setLinks(links);
1044 }
1045
1046 /**
1047 * @param filter The filter object to be attached to the view
1048 */
1049 public void addFilter(@NonNull ViewerFilter filter) {
1050 fInhibitTreeSelection = true;
1051 ViewerFilter wrapper = new ViewerFilterWrapper(filter);
1052 fTreeViewer.addFilter(wrapper);
1053 fTimeGraphViewer.addFilter(filter);
1054 fViewerFilterMap.put(filter, wrapper);
1055 alignTreeItems(true);
1056 fInhibitTreeSelection = false;
1057 }
1058
1059 /**
1060 * @param filter The filter object to be removed from the view
1061 */
1062 public void removeFilter(@NonNull ViewerFilter filter) {
1063 fInhibitTreeSelection = true;
1064 ViewerFilter wrapper = fViewerFilterMap.get(filter);
1065 fTreeViewer.removeFilter(wrapper);
1066 fTimeGraphViewer.removeFilter(filter);
1067 fViewerFilterMap.remove(filter);
1068 alignTreeItems(true);
1069 fInhibitTreeSelection = false;
1070 }
1071
1072 /**
1073 * Returns this viewer's filters.
1074 *
1075 * @return an array of viewer filters
1076 * @since 2.0
1077 */
1078 public @NonNull ViewerFilter[] getFilters() {
1079 return fTimeGraphViewer.getFilters();
1080 }
1081
1082 /**
1083 * Sets the filters, replacing any previous filters, and triggers
1084 * refiltering of the elements.
1085 *
1086 * @param filters
1087 * an array of viewer filters, or null
1088 * @since 2.0
1089 */
1090 public void setFilters(@NonNull ViewerFilter[] filters) {
1091 fInhibitTreeSelection = true;
1092 fViewerFilterMap.clear();
1093 if (filters == null) {
1094 fTreeViewer.resetFilters();
1095 } else {
1096 for (ViewerFilter filter : filters) {
1097 ViewerFilter wrapper = new ViewerFilterWrapper(filter);
1098 fViewerFilterMap.put(filter, wrapper);
1099 }
1100 ViewerFilter[] wrappers = Iterables.toArray(fViewerFilterMap.values(), ViewerFilter.class);
1101 fTreeViewer.setFilters(wrappers);
1102 }
1103 fTimeGraphViewer.setFilters(filters);
1104 alignTreeItems(true);
1105 fInhibitTreeSelection = false;
1106 }
1107
1108 /**
1109 * Refreshes this time graph completely with information freshly obtained from its model.
1110 */
1111 public void refresh() {
1112 fInhibitTreeSelection = true;
1113 Tree tree = fTreeViewer.getTree();
1114 try {
1115 tree.setRedraw(false);
1116 fTreeViewer.refresh();
1117 } finally {
1118 tree.setRedraw(true);
1119 }
1120 fTimeGraphViewer.refresh();
1121 alignTreeItems(true);
1122 fInhibitTreeSelection = false;
1123 }
1124
1125 /**
1126 * Adds a listener for selection changes in this time graph combo.
1127 *
1128 * @param listener a selection listener
1129 */
1130 public void addSelectionListener(ITimeGraphSelectionListener listener) {
1131 SelectionListenerWrapper listenerWrapper = new SelectionListenerWrapper(listener);
1132 fTreeViewer.addSelectionChangedListener(listenerWrapper);
1133 fSelectionListenerMap.put(listener, listenerWrapper);
1134 fTimeGraphViewer.addSelectionListener(listenerWrapper);
1135 }
1136
1137 /**
1138 * Removes the given selection listener from this time graph combo.
1139 *
1140 * @param listener a selection changed listener
1141 */
1142 public void removeSelectionListener(ITimeGraphSelectionListener listener) {
1143 SelectionListenerWrapper listenerWrapper = fSelectionListenerMap.remove(listener);
1144 fTreeViewer.removeSelectionChangedListener(listenerWrapper);
1145 fTimeGraphViewer.removeSelectionListener(listenerWrapper);
1146 }
1147
1148 /**
1149 * Sets the current selection for this time graph combo.
1150 *
1151 * @param selection the new selection
1152 */
1153 public void setSelection(ITimeGraphEntry selection) {
1154 fTimeGraphViewer.setSelection(selection);
1155 fInhibitTreeSelection = true; // block the tree selection changed listener
1156 if (selection != null) {
1157 StructuredSelection structuredSelection = new StructuredSelection(selection);
1158 fTreeViewer.setSelection(structuredSelection);
1159 } else {
1160 fTreeViewer.setSelection(new StructuredSelection());
1161 }
1162 fInhibitTreeSelection = false;
1163 alignTreeItems(false);
1164 }
1165
1166 /**
1167 * Sets the auto-expand level to be used for new entries discovered when
1168 * calling {@link #setInput(Object)} or {@link #refresh()}. The value 0
1169 * means that there is no auto-expand; 1 means that top-level entries are
1170 * expanded, but not their children; 2 means that top-level entries are
1171 * expanded, and their children, but not grand-children; and so on.
1172 * <p>
1173 * The value {@link #ALL_LEVELS} means that all subtrees should be expanded.
1174 * </p>
1175 *
1176 * @param level
1177 * non-negative level, or <code>ALL_LEVELS</code> to expand all
1178 * levels of the tree
1179 */
1180 public void setAutoExpandLevel(int level) {
1181 fTimeGraphViewer.setAutoExpandLevel(level);
1182 if (level <= 0) {
1183 fTreeViewer.setAutoExpandLevel(level);
1184 } else {
1185 fTreeViewer.setAutoExpandLevel(level + 1);
1186 }
1187 }
1188
1189 /**
1190 * Returns the auto-expand level.
1191 *
1192 * @return non-negative level, or <code>ALL_LEVELS</code> if all levels of
1193 * the tree are expanded automatically
1194 * @see #setAutoExpandLevel
1195 */
1196 public int getAutoExpandLevel() {
1197 return fTimeGraphViewer.getAutoExpandLevel();
1198 }
1199
1200 /**
1201 * Get the expanded state of an entry.
1202 *
1203 * @param entry
1204 * The entry
1205 * @return true if the entry is expanded, false if collapsed
1206 * @since 2.0
1207 */
1208 public boolean getExpandedState(ITimeGraphEntry entry) {
1209 return fTimeGraphViewer.getExpandedState(entry);
1210 }
1211
1212 /**
1213 * Set the expanded state of an entry
1214 *
1215 * @param entry
1216 * The entry to expand/collapse
1217 * @param expanded
1218 * True for expanded, false for collapsed
1219 */
1220 public void setExpandedState(ITimeGraphEntry entry, boolean expanded) {
1221 fTimeGraphViewer.setExpandedState(entry, expanded);
1222 fTreeViewer.setExpandedState(entry, expanded);
1223 alignTreeItems(true);
1224 }
1225
1226 /**
1227 * Collapses all nodes of the viewer's tree, starting with the root.
1228 */
1229 public void collapseAll() {
1230 fTimeGraphViewer.collapseAll();
1231 fTreeViewer.collapseAll();
1232 alignTreeItems(true);
1233 }
1234
1235 /**
1236 * Expands all nodes of the viewer's tree, starting with the root.
1237 */
1238 public void expandAll() {
1239 fTimeGraphViewer.expandAll();
1240 fTreeViewer.expandAll();
1241 alignTreeItems(true);
1242 }
1243
1244 // ------------------------------------------------------------------------
1245 // Internal
1246 // ------------------------------------------------------------------------
1247
1248 private List<TreeItem> getVisibleExpandedItems(Tree tree, boolean refresh) {
1249 if (fVisibleExpandedItems == null || refresh) {
1250 List<TreeItem> visibleExpandedItems = new ArrayList<>();
1251 addVisibleExpandedItems(visibleExpandedItems, tree.getItems());
1252 fVisibleExpandedItems = visibleExpandedItems;
1253 }
1254 return fVisibleExpandedItems;
1255 }
1256
1257 private void addVisibleExpandedItems(List<TreeItem> visibleExpandedItems, TreeItem[] items) {
1258 for (TreeItem item : items) {
1259 Object data = item.getData();
1260 if (data == FILLER) {
1261 break;
1262 }
1263 visibleExpandedItems.add(item);
1264 boolean expandedState = fTimeGraphViewer.getExpandedState((ITimeGraphEntry) data);
1265 if (item.getExpanded() != expandedState) {
1266 /* synchronize the expanded state of both viewers */
1267 fTreeViewer.setExpandedState(data, expandedState);
1268 }
1269 if (expandedState) {
1270 addVisibleExpandedItems(visibleExpandedItems, item.getItems());
1271 }
1272 }
1273 }
1274
1275 private int getItemHeight(final Tree tree, boolean force) {
1276 /*
1277 * Bug in Linux. The method getItemHeight doesn't always return the correct value.
1278 */
1279 if (fLinuxItemHeight >= 0 && System.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$
1280 if (fLinuxItemHeight != 0 && !force) {
1281 return fLinuxItemHeight;
1282 }
1283
1284 if (getVisibleExpandedItems(tree, true).size() > 1) {
1285 PaintListener paintListener = new PaintListener() {
1286 @Override
1287 public void paintControl(PaintEvent e) {
1288 // get the treeItems here to have all items
1289 List<TreeItem> treeItems = getVisibleExpandedItems(tree, true);
1290 if (treeItems.size() < 2) {
1291 return;
1292 }
1293 final TreeItem treeItem0 = treeItems.get(0);
1294 final TreeItem treeItem1 = treeItems.get(1);
1295 tree.removePaintListener(this);
1296 int y0 = treeItem0.getBounds().y;
1297 int y1 = treeItem1.getBounds().y;
1298 int itemHeight = y1 - y0;
1299 if (itemHeight > 0) {
1300 fLinuxItemHeight = itemHeight;
1301 fTimeGraphViewer.setItemHeight(itemHeight);
1302 }
1303 }
1304 };
1305 tree.addPaintListener(paintListener);
1306 }
1307 } else {
1308 fLinuxItemHeight = -1; // Not Linux, don't perform os.name check anymore
1309 }
1310 return tree.getItemHeight();
1311 }
1312
1313 private void alignTreeItems(boolean refreshExpandedItems) {
1314
1315 // align the tree top item with the time graph top item
1316 Tree tree = fTreeViewer.getTree();
1317 List<TreeItem> treeItems = getVisibleExpandedItems(tree, refreshExpandedItems);
1318 int topIndex = fTimeGraphViewer.getTopIndex();
1319 if (topIndex >= treeItems.size()) {
1320 return;
1321 }
1322 TreeItem item = treeItems.get(topIndex);
1323 tree.setTopItem(item);
1324 /*
1325 * In GTK3, the bounds of the tree items are only sure to be correct
1326 * after the tree has been painted.
1327 */
1328 tree.addPaintListener(new PaintListener() {
1329 @Override
1330 public void paintControl(PaintEvent e) {
1331 tree.removePaintListener(this);
1332 doAlignTreeItems();
1333 redraw();
1334 /*
1335 * Bug in GTK. Calling setTopItem() can scroll to the wrong item
1336 * when the 'tree view' is dirty. Set it again once it is clean.
1337 */
1338 if (SWT.getPlatform().equals("gtk")) { //$NON-NLS-1$
1339 tree.getDisplay().asyncExec(() -> {
1340 TreeItem topItem = tree.getTopItem();
1341 if (!tree.isDisposed() && topItem != null && !topItem.isDisposed()) {
1342 tree.setTopItem(topItem);
1343 }
1344 });
1345 }
1346 }
1347 });
1348 /* Make sure the paint event is triggered. */
1349 tree.redraw();
1350 }
1351
1352 private void doAlignTreeItems() {
1353 Tree tree = fTreeViewer.getTree();
1354 List<TreeItem> treeItems = getVisibleExpandedItems(tree, false);
1355 int topIndex = fTimeGraphViewer.getTopIndex();
1356 if (topIndex >= treeItems.size()) {
1357 return;
1358 }
1359 TreeItem item = treeItems.get(topIndex);
1360
1361 // get the first filler item so we can calculate the last item's height
1362 TreeItem fillerItem = null;
1363 for (TreeItem treeItem : fTreeViewer.getTree().getItems()) {
1364 if (treeItem.getData() == FILLER) {
1365 fillerItem = treeItem;
1366 break;
1367 }
1368 }
1369
1370 // ensure the time graph item heights are equal to the tree item heights
1371 int treeHeight = fTreeViewer.getTree().getBounds().height;
1372 int index = topIndex;
1373 Rectangle bounds = item.getBounds();
1374 while (index < treeItems.size()) {
1375 if (bounds.y > treeHeight) {
1376 break;
1377 }
1378 TreeItem nextItem = (index + 1 == treeItems.size()) ? fillerItem : treeItems.get(index + 1);
1379 Rectangle nextBounds = alignTreeItem(item, bounds, nextItem);
1380 index++;
1381 item = nextItem;
1382 bounds = nextBounds;
1383 }
1384
1385 /*
1386 * When an item's height in the time graph changes, it is possible that
1387 * the time graph readjusts its top index to fill empty space at the
1388 * bottom of the viewer. Calling method setTopIndex() triggers this
1389 * adjustment, if needed. In that case, we need to make sure that the
1390 * newly visible items at the top of the viewer are also aligned.
1391 */
1392 fTimeGraphViewer.setTopIndex(topIndex);
1393 if (fTimeGraphViewer.getTopIndex() != topIndex) {
1394 alignTreeItems(false);
1395 }
1396 }
1397
1398 private Rectangle alignTreeItem(TreeItem item, Rectangle bounds, TreeItem nextItem) {
1399 /*
1400 * Bug in Linux. The method getBounds doesn't always return the correct height.
1401 * Use the difference of y position between items to calculate the height.
1402 */
1403 Rectangle nextBounds = nextItem.getBounds();
1404 Integer itemHeight = nextBounds.y - bounds.y;
1405 if (itemHeight > 0) {
1406 ITimeGraphEntry entry = (ITimeGraphEntry) item.getData();
1407 fTimeGraphViewer.getTimeGraphControl().setItemHeight(entry, itemHeight);
1408 }
1409 return nextBounds;
1410 }
1411
1412 /**
1413 * Return the time alignment information
1414 *
1415 * @return the time alignment information
1416 *
1417 * @see ITmfTimeAligned
1418 *
1419 * @since 1.0
1420 */
1421 public TmfTimeViewAlignmentInfo getTimeViewAlignmentInfo() {
1422 Point location = fSashForm.toDisplay(0, 0);
1423 int timeAxisOffset = fTreeViewer.getControl().getSize().x + fSashForm.getSashWidth();
1424 return new TmfTimeViewAlignmentInfo(fSashForm.getShell(), location, timeAxisOffset);
1425 }
1426
1427 /**
1428 * Return the available width for the time-axis.
1429 *
1430 * @see ITmfTimeAligned
1431 *
1432 * @param requestedOffset
1433 * the requested offset
1434 * @return the available width for the time-axis
1435 *
1436 * @since 1.0
1437 */
1438 public int getAvailableWidth(int requestedOffset) {
1439 int vBarWidth = ((fTimeGraphViewer.getVerticalBar() != null) && (fTimeGraphViewer.getVerticalBar().isVisible())) ? fTimeGraphViewer.getVerticalBar().getSize().x : 0;
1440 int totalWidth = fSashForm.getBounds().width;
1441 return Math.min(totalWidth, Math.max(0, totalWidth - requestedOffset - vBarWidth));
1442 }
1443
1444 /**
1445 * Perform the alignment operation.
1446 *
1447 * @param offset
1448 * the alignment offset
1449 * @param width
1450 * the alignment width
1451 *
1452 * @see ITmfTimeAligned
1453 *
1454 * @since 1.0
1455 */
1456 public void performAlign(int offset, int width) {
1457 int total = fSashForm.getBounds().width;
1458 int timeAxisOffset = Math.min(offset, total);
1459 int width1 = Math.max(0, timeAxisOffset - fSashForm.getSashWidth());
1460 int width2 = total - timeAxisOffset;
1461 if (width1 >= 0 && width2 > 0 || width1 > 0 && width2 >= 0) {
1462 fSashForm.setWeights(new int[] { width1, width2 });
1463 fSashForm.layout();
1464 }
1465
1466 Composite composite = fTimeGraphViewer.getTimeAlignedComposite();
1467 GridLayout layout = (GridLayout) composite.getLayout();
1468 int timeBasedControlsWidth = composite.getSize().x;
1469 int marginSize = timeBasedControlsWidth - width;
1470 layout.marginRight = Math.max(0, marginSize);
1471 composite.layout();
1472 }
1473 }
This page took 0.06405 seconds and 4 git commands to generate.