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