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