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