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