tmf.ui: bug 505695 fix time graph views with GTK
[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 List<TreeItem> treeItems = getVisibleExpandedItems(tree, false);
615 if (treeItems.isEmpty()) {
616 event.doit = false;
617 fTreeViewer.setSelection(new StructuredSelection());
618 fTimeGraphViewer.setSelection(null);
619 return;
620 }
621 TreeItem lastTreeItem = treeItems.get(treeItems.size() - 1);
622 if (event.y >= lastTreeItem.getBounds().y + lastTreeItem.getBounds().height) {
623 event.doit = false;
624 // this prevents from scrolling up when selecting
625 // the partially visible tree item at the bottom
626 tree.select(treeItems.get(treeItems.size() - 1));
627 fTreeViewer.setSelection(new StructuredSelection());
628 fTimeGraphViewer.setSelection(null);
629 }
630 });
631
632 // prevent mouse wheel from scrolling down into filler tree items
633 tree.addListener(SWT.MouseWheel, event -> {
634 event.doit = false;
635 if (event.count == 0) {
636 return;
637 }
638 Slider scrollBar = fTimeGraphViewer.getVerticalBar();
639 fTimeGraphViewer.setTopIndex(scrollBar.getSelection() - event.count);
640 alignTreeItems(false);
641 });
642
643 // prevent key stroke from selecting a filler tree item
644 tree.addListener(SWT.KeyDown, event -> {
645 List<TreeItem> treeItems = getVisibleExpandedItems(tree, false);
646 if (treeItems.size() == 0) {
647 fTreeViewer.setSelection(new StructuredSelection());
648 event.doit = false;
649 return;
650 }
651 if (event.keyCode == SWT.ARROW_DOWN) {
652 int index = Math.min(fTimeGraphViewer.getSelectionIndex() + 1, treeItems.size() - 1);
653 fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(index).getData());
654 event.doit = false;
655 } else if (event.keyCode == SWT.PAGE_DOWN) {
656 int height = tree.getSize().y - tree.getHeaderHeight() - tree.getHorizontalBar().getSize().y;
657 int countPerPage = height / getItemHeight(tree, false);
658 int index = Math.min(fTimeGraphViewer.getSelectionIndex() + countPerPage - 1, treeItems.size() - 1);
659 fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(index).getData());
660 event.doit = false;
661 } else if (event.keyCode == SWT.END) {
662 fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(treeItems.size() - 1).getData());
663 event.doit = false;
664 } else if ((event.character == '+' || event.character == '=') && ((event.stateMask & SWT.CTRL) != 0)) {
665 fTimeGraphViewer.getTimeGraphControl().keyPressed(new KeyEvent(event));
666 return;
667 } else if (event.character == '-' && ((event.stateMask & SWT.CTRL) != 0)) {
668 fTimeGraphViewer.getTimeGraphControl().keyPressed(new KeyEvent(event));
669 return;
670 } else if (event.character == '0' && ((event.stateMask & SWT.CTRL) != 0)) {
671 fTimeGraphViewer.getTimeGraphControl().keyPressed(new KeyEvent(event));
672 return;
673 } else {
674 return;
675 }
676 if (fTimeGraphViewer.getSelectionIndex() >= 0) {
677 fTreeViewer.setSelection(new StructuredSelection(fTimeGraphViewer.getSelection()));
678 } else {
679 fTreeViewer.setSelection(new StructuredSelection());
680 }
681 alignTreeItems(false);
682 });
683
684 // ensure alignment of top item between tree and time graph
685 fTimeGraphViewer.getTimeGraphControl().addControlListener(new ControlAdapter() {
686 @Override
687 public void controlResized(ControlEvent e) {
688 alignTreeItems(false);
689 }
690 });
691
692 // ensure synchronization of selected item between tree and time graph
693 fTreeViewer.addSelectionChangedListener(event -> {
694 if (fInhibitTreeSelection) {
695 return;
696 }
697 if (event.getSelection() instanceof IStructuredSelection) {
698 Object selection = ((IStructuredSelection) event.getSelection()).getFirstElement();
699 if (selection instanceof ITimeGraphEntry) {
700 fTimeGraphViewer.setSelection((ITimeGraphEntry) selection);
701 }
702 alignTreeItems(false);
703 }
704 });
705
706 // ensure synchronization of selected item between tree and time graph
707 fTimeGraphViewer.addSelectionListener(event -> {
708 ITimeGraphEntry entry = fTimeGraphViewer.getSelection();
709 setSelectionInTree(entry);
710 });
711
712 // ensure alignment of top item between tree and time graph
713 fTimeGraphViewer.getVerticalBar().addSelectionListener(new SelectionAdapter() {
714
715 @Override
716 public void widgetSelected(SelectionEvent e) {
717 alignTreeItems(false);
718 }
719 });
720
721 // ensure alignment of top item between tree and time graph
722 fTimeGraphViewer.getTimeGraphControl().addMouseWheelListener(e -> {
723 if (e.count == 0) {
724 return;
725 }
726 alignTreeItems(false);
727 });
728
729 // ensure the tree has focus control when mouse is over it if the time graph had control
730 fTreeViewer.getControl().addMouseTrackListener(new MouseTrackAdapter() {
731 @Override
732 public void mouseEnter(MouseEvent e) {
733 if (fTimeGraphViewer.getTimeGraphControl().isFocusControl()) {
734 fTreeViewer.getControl().setFocus();
735 }
736 }
737 });
738
739 // ensure the time graph has focus control when mouse is over it if the tree had control
740 fTimeGraphViewer.getTimeGraphControl().addMouseTrackListener(new MouseTrackAdapter() {
741 @Override
742 public void mouseEnter(MouseEvent e) {
743 if (fTreeViewer.getControl().isFocusControl()) {
744 fTimeGraphViewer.getTimeGraphControl().setFocus();
745 }
746 }
747 });
748 fTimeGraphViewer.getTimeGraphScale().addMouseTrackListener(new MouseTrackAdapter() {
749 @Override
750 public void mouseEnter(MouseEvent e) {
751 if (fTreeViewer.getControl().isFocusControl()) {
752 fTimeGraphViewer.getTimeGraphControl().setFocus();
753 }
754 }
755 });
756
757 // The filler rows are required to ensure alignment when the tree does not have a
758 // visible horizontal scroll bar. The tree does not allow its top item to be set
759 // to a value that would cause blank space to be drawn at the bottom of the tree.
760 fNumFillerRows = Display.getDefault().getBounds().height / getItemHeight(tree, false);
761
762 fSashForm.setWeights(weights);
763
764 fTimeGraphViewer.getTimeGraphControl().addPaintListener(new PaintListener() {
765 @Override
766 public void paintControl(PaintEvent e) {
767 // Sashes in a SashForm are being created on layout so add the
768 // drag listener here
769 if (fSashDragListener == null) {
770 for (Control control : fSashForm.getChildren()) {
771 if (control instanceof Sash) {
772 fSashDragListener = new Listener() {
773
774 @Override
775 public void handleEvent(Event event) {
776 sendTimeViewAlignmentChanged();
777
778 }
779 };
780 control.removePaintListener(this);
781 control.addListener(SWT.Selection, fSashDragListener);
782 // There should be only one sash
783 break;
784 }
785 }
786 }
787 }
788 });
789 }
790
791 private void verticalZoom(boolean zoomIn) {
792 Tree tree = fTreeViewer.getTree();
793 FontData fontData = tree.getFont().getFontData()[0];
794 int height = fontData.getHeight() + (zoomIn ? 1 : -1);
795 if (height <= 0) {
796 return;
797 }
798 fontData.setHeight(height);
799 if (fTreeFont != null) {
800 fTreeFont.dispose();
801 }
802 fTreeFont = new Font(tree.getDisplay(), fontData);
803 tree.setFont(fTreeFont);
804 redraw();
805 update();
806 fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
807 fTimeGraphViewer.setItemHeight(getItemHeight(tree, true));
808 alignTreeItems(false);
809 }
810
811 private void resetVerticalZoom() {
812 Tree tree = fTreeViewer.getTree();
813 if (fTreeFont != null) {
814 fTreeFont.dispose();
815 fTreeFont = null;
816 }
817 tree.setFont(null);
818 redraw();
819 update();
820 fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
821 fTimeGraphViewer.setItemHeight(getItemHeight(tree, true));
822 alignTreeItems(false);
823 }
824
825 private void sendTimeViewAlignmentChanged() {
826 TmfSignalManager.dispatchSignal(new TmfTimeViewAlignmentSignal(fSashForm, getTimeViewAlignmentInfo()));
827 }
828
829 // ------------------------------------------------------------------------
830 // Accessors
831 // ------------------------------------------------------------------------
832
833 /**
834 * Returns this time graph combo's tree viewer.
835 *
836 * @return the tree viewer
837 */
838 public TreeViewer getTreeViewer() {
839 return fTreeViewer;
840 }
841
842 /**
843 * Returns this time graph combo's time graph viewer.
844 *
845 * @return the time graph viewer
846 */
847 public @NonNull TimeGraphViewer getTimeGraphViewer() {
848 return fTimeGraphViewer;
849 }
850
851 /**
852 * Get the show filter dialog action.
853 *
854 * @return The Action object
855 * @since 1.2
856 */
857 public ShowFilterDialogAction getShowFilterDialogAction() {
858 if (fShowFilterDialogAction == null) {
859 fShowFilterDialogAction = new ShowFilterDialogAction(fTimeGraphViewer) {
860 @Override
861 protected void addFilter(ViewerFilter filter) {
862 /* add filter to the combo instead of the viewer */
863 TimeGraphCombo.this.addFilter(filter);
864 }
865
866 @Override
867 protected void removeFilter(ViewerFilter filter) {
868 /* remove filter from the combo instead of the viewer */
869 TimeGraphCombo.this.removeFilter(filter);
870 }
871
872 @Override
873 protected void refresh() {
874 /* refresh the combo instead of the viewer */
875 TimeGraphCombo.this.refresh();
876 }
877 };
878 }
879 return fShowFilterDialogAction;
880 }
881
882 // ------------------------------------------------------------------------
883 // Control
884 // ------------------------------------------------------------------------
885
886 @Override
887 public void redraw() {
888 fTimeGraphViewer.getControl().redraw();
889 super.redraw();
890 }
891
892 @Override
893 public void update() {
894 fTimeGraphViewer.getControl().update();
895 super.update();
896 }
897
898 // ------------------------------------------------------------------------
899 // Operations
900 // ------------------------------------------------------------------------
901
902 /**
903 * Sets the tree content provider used by this time graph combo.
904 *
905 * @param contentProvider
906 * the tree content provider
907 */
908 public void setTreeContentProvider(ITreeContentProvider contentProvider) {
909 fTreeViewer.setContentProvider(new TreeContentProviderWrapper(contentProvider));
910 }
911
912 /**
913 * Sets the tree label provider used by this time graph combo.
914 *
915 * @param labelProvider
916 * the tree label provider
917 */
918 public void setTreeLabelProvider(ITableLabelProvider labelProvider) {
919 fTreeViewer.setLabelProvider(new TreeLabelProviderWrapper(labelProvider));
920 }
921
922 /**
923 * Sets the tree content provider used by the filter dialog
924 *
925 * @param contentProvider
926 * the tree content provider
927 */
928 public void setFilterContentProvider(ITreeContentProvider contentProvider) {
929 getShowFilterDialogAction().getFilterDialog().setContentProvider(contentProvider);
930 }
931
932 /**
933 * Sets the tree label provider used by the filter dialog
934 *
935 * @param labelProvider
936 * the tree label provider
937 */
938 public void setFilterLabelProvider(ITableLabelProvider labelProvider) {
939 getShowFilterDialogAction().getFilterDialog().setLabelProvider(labelProvider);
940 }
941
942 /**
943 * Adds a "check active" button used by the filter dialog
944 *
945 * @param activeProvider
946 * Additional button info specific to a certain view.
947 * @since 1.0
948 */
949 public void addTimeGraphFilterCheckActiveButton(ITimeGraphEntryActiveProvider activeProvider) {
950 getShowFilterDialogAction().getFilterDialog().addTimeGraphFilterCheckActiveButton(activeProvider);
951 }
952
953 /**
954 * Adds an "uncheck inactive" button used by the filter dialog
955 *
956 * @param inactiveProvider
957 * Additional button info specific to a certain view.
958 * @since 1.0
959 */
960 public void addTimeGraphFilterUncheckInactiveButton(ITimeGraphEntryActiveProvider inactiveProvider) {
961 getShowFilterDialogAction().getFilterDialog().addTimeGraphFilterUncheckInactiveButton(inactiveProvider);
962 }
963
964 /**
965 * Sets the tree columns for this time graph combo.
966 *
967 * @param columnNames
968 * the tree column names
969 */
970 public void setTreeColumns(String[] columnNames) {
971 final Tree tree = fTreeViewer.getTree();
972 for (String columnName : columnNames) {
973 TreeColumn column = new TreeColumn(tree, SWT.LEFT);
974 column.setMoveable(true);
975 column.setText(columnName);
976 column.pack();
977 }
978 }
979
980 /**
981 * Sets the tree columns for this time graph combo's filter dialog.
982 *
983 * @param columnNames
984 * the tree column names
985 */
986 public void setFilterColumns(String[] columnNames) {
987 getShowFilterDialogAction().getFilterDialog().setColumnNames(columnNames);
988 }
989
990 /**
991 * Sets the time graph content provider used by this time graph combo.
992 *
993 * @param timeGraphContentProvider
994 * the time graph content provider
995 */
996 public void setTimeGraphContentProvider(ITimeGraphContentProvider timeGraphContentProvider) {
997 fTimeGraphViewer.setTimeGraphContentProvider(timeGraphContentProvider);
998 }
999
1000 /**
1001 * Sets the time graph presentation provider used by this time graph combo.
1002 *
1003 * @param timeGraphProvider
1004 * the time graph provider
1005 */
1006 public void setTimeGraphProvider(ITimeGraphPresentationProvider timeGraphProvider) {
1007 fTimeGraphViewer.setTimeGraphProvider(timeGraphProvider);
1008 }
1009
1010 /**
1011 * Sets or clears the input for this time graph combo.
1012 *
1013 * @param input
1014 * the input of this time graph combo, or <code>null</code> if
1015 * none
1016 */
1017 public void setInput(Object input) {
1018 fInhibitTreeSelection = true;
1019 fTreeViewer.setInput(input);
1020 for (SelectionListenerWrapper listenerWrapper : fSelectionListenerMap.values()) {
1021 listenerWrapper.selection = null;
1022 }
1023 fInhibitTreeSelection = false;
1024 if (fScrollBarsInTreeWorkaround) {
1025 fTreeViewer.getTree().getVerticalBar().setEnabled(false);
1026 fTreeViewer.getTree().getVerticalBar().setVisible(false);
1027 }
1028 fTimeGraphViewer.setInput(input);
1029 fTimeGraphViewer.setItemHeight(getItemHeight(fTreeViewer.getTree(), false));
1030 // queue the alignment update because in Linux the item bounds are not
1031 // set properly until the tree has been painted at least once
1032 fVisibleExpandedItems = null; // invalidate the cache
1033 getDisplay().asyncExec(new Runnable() {
1034 @Override
1035 public void run() {
1036 if (isDisposed()) {
1037 return;
1038 }
1039 alignTreeItems(true);
1040 }
1041 });
1042 }
1043
1044 /**
1045 * Gets the input for this time graph combo.
1046 *
1047 * @return The input of this time graph combo, or <code>null</code> if none
1048 */
1049 public Object getInput() {
1050 return fTreeViewer.getInput();
1051 }
1052
1053 /**
1054 * Sets or clears the list of links to display on this combo
1055 *
1056 * @param links
1057 * the links to display in this time graph combo
1058 */
1059 public void setLinks(List<ILinkEvent> links) {
1060 fTimeGraphViewer.setLinks(links);
1061 }
1062
1063 /**
1064 * @param filter
1065 * The filter object to be attached to the view
1066 */
1067 public void addFilter(@NonNull ViewerFilter filter) {
1068 fInhibitTreeSelection = true;
1069 ViewerFilter wrapper = new ViewerFilterWrapper(filter);
1070 fTreeViewer.addFilter(wrapper);
1071 fTimeGraphViewer.addFilter(filter);
1072 fViewerFilterMap.put(filter, wrapper);
1073 alignTreeItems(true);
1074 fInhibitTreeSelection = false;
1075 }
1076
1077 /**
1078 * @param filter
1079 * The filter object to be removed from the view
1080 */
1081 public void removeFilter(@NonNull ViewerFilter filter) {
1082 fInhibitTreeSelection = true;
1083 ViewerFilter wrapper = fViewerFilterMap.get(filter);
1084 fTreeViewer.removeFilter(wrapper);
1085 fTimeGraphViewer.removeFilter(filter);
1086 fViewerFilterMap.remove(filter);
1087 alignTreeItems(true);
1088 fInhibitTreeSelection = false;
1089 }
1090
1091 /**
1092 * Returns this viewer's filters.
1093 *
1094 * @return an array of viewer filters
1095 * @since 1.2
1096 */
1097 public @NonNull ViewerFilter[] getFilters() {
1098 return fTimeGraphViewer.getFilters();
1099 }
1100
1101 /**
1102 * Sets the filters, replacing any previous filters, and triggers
1103 * refiltering of the elements.
1104 *
1105 * @param filters
1106 * an array of viewer filters, or null
1107 * @since 1.2
1108 */
1109 public void setFilters(@NonNull ViewerFilter[] filters) {
1110 fInhibitTreeSelection = true;
1111 fViewerFilterMap.clear();
1112 if (filters == null) {
1113 fTreeViewer.resetFilters();
1114 } else {
1115 for (ViewerFilter filter : filters) {
1116 ViewerFilter wrapper = new ViewerFilterWrapper(filter);
1117 fViewerFilterMap.put(filter, wrapper);
1118 }
1119 ViewerFilter[] wrappers = Iterables.toArray(fViewerFilterMap.values(), ViewerFilter.class);
1120 fTreeViewer.setFilters(wrappers);
1121 }
1122 fTimeGraphViewer.setFilters(filters);
1123 alignTreeItems(true);
1124 fInhibitTreeSelection = false;
1125 }
1126
1127 /**
1128 * Refreshes this time graph completely with information freshly obtained
1129 * from its model.
1130 */
1131 public void refresh() {
1132 fInhibitTreeSelection = true;
1133 Tree tree = fTreeViewer.getTree();
1134 try {
1135 tree.setRedraw(false);
1136 fTreeViewer.refresh();
1137 } finally {
1138 tree.setRedraw(true);
1139 }
1140 fTimeGraphViewer.refresh();
1141 alignTreeItems(true);
1142 fInhibitTreeSelection = false;
1143 }
1144
1145 /**
1146 * Adds a listener for selection changes in this time graph combo.
1147 *
1148 * @param listener
1149 * a selection listener
1150 */
1151 public void addSelectionListener(ITimeGraphSelectionListener listener) {
1152 SelectionListenerWrapper listenerWrapper = new SelectionListenerWrapper(listener);
1153 fTreeViewer.addSelectionChangedListener(listenerWrapper);
1154 fSelectionListenerMap.put(listener, listenerWrapper);
1155 fTimeGraphViewer.addSelectionListener(listenerWrapper);
1156 }
1157
1158 /**
1159 * Removes the given selection listener from this time graph combo.
1160 *
1161 * @param listener
1162 * a selection changed listener
1163 */
1164 public void removeSelectionListener(ITimeGraphSelectionListener listener) {
1165 SelectionListenerWrapper listenerWrapper = fSelectionListenerMap.remove(listener);
1166 fTreeViewer.removeSelectionChangedListener(listenerWrapper);
1167 fTimeGraphViewer.removeSelectionListener(listenerWrapper);
1168 }
1169
1170 /**
1171 * Sets the current selection for this time graph combo.
1172 *
1173 * @param selection
1174 * the new selection
1175 */
1176 public void setSelection(ITimeGraphEntry selection) {
1177 fTimeGraphViewer.setSelection(selection);
1178 setSelectionInTree(selection);
1179 }
1180
1181 /**
1182 * Sets the current selection for this time graph combo and reveal it if
1183 * needed.
1184 *
1185 * @param selection
1186 * The new selection
1187 * @since 2.0
1188 */
1189 public void selectAndReveal(@NonNull ITimeGraphEntry selection) {
1190 fTimeGraphViewer.selectAndReveal(selection);
1191 setSelectionInTree(selection);
1192 }
1193
1194 /**
1195 * Select the entry in the tree structure
1196 *
1197 * @param selection
1198 * The new selection
1199 */
1200 private void setSelectionInTree(ITimeGraphEntry selection) {
1201 fInhibitTreeSelection = true; // block the tree selection changed
1202 // listener
1203 if (selection != null) {
1204 StructuredSelection structuredSelection = new StructuredSelection(selection);
1205 fTreeViewer.setSelection(structuredSelection);
1206 } else {
1207 fTreeViewer.setSelection(new StructuredSelection());
1208 }
1209 fInhibitTreeSelection = false;
1210 alignTreeItems(false);
1211 }
1212
1213 /**
1214 * Sets the auto-expand level to be used for new entries discovered when
1215 * calling {@link #setInput(Object)} or {@link #refresh()}. The value 0
1216 * means that there is no auto-expand; 1 means that top-level entries are
1217 * expanded, but not their children; 2 means that top-level entries are
1218 * expanded, and their children, but not grand-children; and so on.
1219 * <p>
1220 * The value {@link #ALL_LEVELS} means that all subtrees should be expanded.
1221 * </p>
1222 *
1223 * @param level
1224 * non-negative level, or <code>ALL_LEVELS</code> to expand all
1225 * levels of the tree
1226 */
1227 public void setAutoExpandLevel(int level) {
1228 fTimeGraphViewer.setAutoExpandLevel(level);
1229 if (level <= 0) {
1230 fTreeViewer.setAutoExpandLevel(level);
1231 } else {
1232 fTreeViewer.setAutoExpandLevel(level + 1);
1233 }
1234 }
1235
1236 /**
1237 * Returns the auto-expand level.
1238 *
1239 * @return non-negative level, or <code>ALL_LEVELS</code> if all levels of
1240 * the tree are expanded automatically
1241 * @see #setAutoExpandLevel
1242 */
1243 public int getAutoExpandLevel() {
1244 return fTimeGraphViewer.getAutoExpandLevel();
1245 }
1246
1247 /**
1248 * Get the expanded state of an entry.
1249 *
1250 * @param entry
1251 * The entry
1252 * @return true if the entry is expanded, false if collapsed
1253 * @since 2.0
1254 */
1255 public boolean getExpandedState(ITimeGraphEntry entry) {
1256 return fTimeGraphViewer.getExpandedState(entry);
1257 }
1258
1259 /**
1260 * Set the expanded state of an entry
1261 *
1262 * @param entry
1263 * The entry to expand/collapse
1264 * @param expanded
1265 * True for expanded, false for collapsed
1266 */
1267 public void setExpandedState(ITimeGraphEntry entry, boolean expanded) {
1268 fTimeGraphViewer.setExpandedState(entry, expanded);
1269 fTreeViewer.setExpandedState(entry, expanded);
1270 alignTreeItems(true);
1271 }
1272
1273 /**
1274 * Collapses all nodes of the viewer's tree, starting with the root.
1275 */
1276 public void collapseAll() {
1277 fTimeGraphViewer.collapseAll();
1278 fTreeViewer.collapseAll();
1279 alignTreeItems(true);
1280 }
1281
1282 /**
1283 * Expands all nodes of the viewer's tree, starting with the root.
1284 */
1285 public void expandAll() {
1286 fTimeGraphViewer.expandAll();
1287 fTreeViewer.expandAll();
1288 alignTreeItems(true);
1289 }
1290
1291 // ------------------------------------------------------------------------
1292 // Internal
1293 // ------------------------------------------------------------------------
1294
1295 private List<TreeItem> getVisibleExpandedItems(Tree tree, boolean refresh) {
1296 if (fVisibleExpandedItems == null || refresh) {
1297 List<TreeItem> visibleExpandedItems = new ArrayList<>();
1298 addVisibleExpandedItems(visibleExpandedItems, tree.getItems());
1299 fVisibleExpandedItems = visibleExpandedItems;
1300 }
1301 return fVisibleExpandedItems;
1302 }
1303
1304 private void addVisibleExpandedItems(List<TreeItem> visibleExpandedItems, TreeItem[] items) {
1305 for (TreeItem item : items) {
1306 Object data = item.getData();
1307 if (data == FILLER) {
1308 break;
1309 }
1310 visibleExpandedItems.add(item);
1311 boolean expandedState = fTimeGraphViewer.getExpandedState((ITimeGraphEntry) data);
1312 if (item.getExpanded() != expandedState) {
1313 /* synchronize the expanded state of both viewers */
1314 fTreeViewer.setExpandedState(data, expandedState);
1315 }
1316 if (expandedState) {
1317 addVisibleExpandedItems(visibleExpandedItems, item.getItems());
1318 }
1319 }
1320 }
1321
1322 private int getItemHeight(final Tree tree, boolean force) {
1323 /*
1324 * Bug in Linux. The method getItemHeight doesn't always return the
1325 * correct value.
1326 */
1327 if (fLinuxItemHeight >= 0 && System.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$
1328 if (fLinuxItemHeight != 0 && !force) {
1329 return fLinuxItemHeight;
1330 }
1331
1332 if (getVisibleExpandedItems(tree, true).size() > 1) {
1333 PaintListener paintListener = new PaintListener() {
1334 @Override
1335 public void paintControl(PaintEvent e) {
1336 // get the treeItems here to have all items
1337 List<TreeItem> treeItems = getVisibleExpandedItems(tree, true);
1338 if (treeItems.size() < 2) {
1339 return;
1340 }
1341 final TreeItem treeItem0 = treeItems.get(0);
1342 final TreeItem treeItem1 = treeItems.get(1);
1343 tree.removePaintListener(this);
1344 int y0 = treeItem0.getBounds().y;
1345 int y1 = treeItem1.getBounds().y;
1346 int itemHeight = y1 - y0;
1347 if (itemHeight > 0) {
1348 fLinuxItemHeight = itemHeight;
1349 fTimeGraphViewer.setItemHeight(itemHeight);
1350 }
1351 }
1352 };
1353 tree.addPaintListener(paintListener);
1354 }
1355 } else {
1356 fLinuxItemHeight = -1; // Not Linux, don't perform os.name check
1357 // anymore
1358 }
1359 return tree.getItemHeight();
1360 }
1361
1362 private void alignTreeItems(boolean refreshExpandedItems) {
1363
1364 // align the tree top item with the time graph top item
1365 Tree tree = fTreeViewer.getTree();
1366 List<TreeItem> treeItems = getVisibleExpandedItems(tree, refreshExpandedItems);
1367 int topIndex = fTimeGraphViewer.getTopIndex();
1368 if (topIndex >= treeItems.size()) {
1369 return;
1370 }
1371 TreeItem item = treeItems.get(topIndex);
1372 tree.setTopItem(item);
1373 /*
1374 * In GTK3, the bounds of the tree items are only sure to be correct
1375 * after the tree has been painted.
1376 */
1377 tree.addPaintListener(new PaintListener() {
1378 @Override
1379 public void paintControl(PaintEvent e) {
1380 tree.removePaintListener(this);
1381 doAlignTreeItems();
1382 redraw();
1383 }
1384 });
1385 /* Make sure the paint event is triggered. */
1386 tree.redraw();
1387 }
1388
1389 private void doAlignTreeItems() {
1390 Tree tree = fTreeViewer.getTree();
1391 List<TreeItem> treeItems = getVisibleExpandedItems(tree, false);
1392 int topIndex = fTimeGraphViewer.getTopIndex();
1393 if (topIndex >= treeItems.size()) {
1394 return;
1395 }
1396 TreeItem item = treeItems.get(topIndex);
1397
1398 // get the first filler item so we can calculate the last item's height
1399 TreeItem fillerItem = null;
1400 for (TreeItem treeItem : fTreeViewer.getTree().getItems()) {
1401 if (treeItem.getData() == FILLER) {
1402 fillerItem = treeItem;
1403 break;
1404 }
1405 }
1406
1407 // ensure the time graph item heights are equal to the tree item heights
1408 int treeHeight = fTreeViewer.getTree().getBounds().height;
1409 int index = topIndex;
1410 Rectangle bounds = item.getBounds();
1411 while (index < treeItems.size()) {
1412 if (bounds.y > treeHeight) {
1413 break;
1414 }
1415 TreeItem nextItem = (index + 1 == treeItems.size()) ? fillerItem : treeItems.get(index + 1);
1416 Rectangle nextBounds = alignTreeItem(item, bounds, nextItem);
1417 index++;
1418 item = nextItem;
1419 bounds = nextBounds;
1420 }
1421
1422 /*
1423 * When an item's height in the time graph changes, it is possible that
1424 * the time graph readjusts its top index to fill empty space at the
1425 * bottom of the viewer. Calling method setTopIndex() triggers this
1426 * adjustment, if needed. In that case, we need to make sure that the
1427 * newly visible items at the top of the viewer are also aligned.
1428 */
1429 fTimeGraphViewer.setTopIndex(topIndex);
1430 if (fTimeGraphViewer.getTopIndex() != topIndex) {
1431 alignTreeItems(false);
1432 }
1433 }
1434
1435 private Rectangle alignTreeItem(TreeItem item, Rectangle bounds, TreeItem nextItem) {
1436 /*
1437 * Bug in Linux. The method getBounds doesn't always return the correct
1438 * height. Use the difference of y position between items to calculate
1439 * the height.
1440 */
1441 Rectangle nextBounds = nextItem.getBounds();
1442 Integer itemHeight = nextBounds.y - bounds.y;
1443 if (itemHeight > 0) {
1444 ITimeGraphEntry entry = (ITimeGraphEntry) item.getData();
1445 fTimeGraphViewer.getTimeGraphControl().setItemHeight(entry, itemHeight);
1446 }
1447 return nextBounds;
1448 }
1449
1450 /**
1451 * Return the time alignment information
1452 *
1453 * @return the time alignment information
1454 *
1455 * @see ITmfTimeAligned
1456 *
1457 * @since 1.0
1458 */
1459 public TmfTimeViewAlignmentInfo getTimeViewAlignmentInfo() {
1460 Point location = fSashForm.toDisplay(0, 0);
1461 int timeAxisOffset = fTreeViewer.getControl().getSize().x + fSashForm.getSashWidth();
1462 return new TmfTimeViewAlignmentInfo(fSashForm.getShell(), location, timeAxisOffset);
1463 }
1464
1465 /**
1466 * Return the available width for the time-axis.
1467 *
1468 * @see ITmfTimeAligned
1469 *
1470 * @param requestedOffset
1471 * the requested offset
1472 * @return the available width for the time-axis
1473 *
1474 * @since 1.0
1475 */
1476 public int getAvailableWidth(int requestedOffset) {
1477 int vBarWidth = ((fTimeGraphViewer.getVerticalBar() != null) && (fTimeGraphViewer.getVerticalBar().isVisible())) ? fTimeGraphViewer.getVerticalBar().getSize().x : 0;
1478 int totalWidth = fSashForm.getBounds().width;
1479 return Math.min(totalWidth, Math.max(0, totalWidth - requestedOffset - vBarWidth));
1480 }
1481
1482 /**
1483 * Perform the alignment operation.
1484 *
1485 * @param offset
1486 * the alignment offset
1487 * @param width
1488 * the alignment width
1489 *
1490 * @see ITmfTimeAligned
1491 *
1492 * @since 1.0
1493 */
1494 public void performAlign(int offset, int width) {
1495 int total = fSashForm.getBounds().width;
1496 int timeAxisOffset = Math.min(offset, total);
1497 int width1 = Math.max(0, timeAxisOffset - fSashForm.getSashWidth());
1498 int width2 = total - timeAxisOffset;
1499 if (width1 >= 0 && width2 > 0 || width1 > 0 && width2 >= 0) {
1500 fSashForm.setWeights(new int[] { width1, width2 });
1501 fSashForm.layout();
1502 }
1503
1504 Composite composite = fTimeGraphViewer.getTimeAlignedComposite();
1505 GridLayout layout = (GridLayout) composite.getLayout();
1506 int timeBasedControlsWidth = composite.getSize().x;
1507 int marginSize = timeBasedControlsWidth - width;
1508 layout.marginRight = Math.max(0, marginSize);
1509 composite.layout();
1510 }
1511 }
This page took 0.064991 seconds and 5 git commands to generate.