1 /*******************************************************************************
2 * Copyright (c) 2012 Ericsson
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
10 * Patrick Tasse - Initial API and implementation
11 *******************************************************************************/
13 package org
.eclipse
.linuxtools
.tmf
.ui
.widgets
.timegraph
;
15 import java
.util
.ArrayList
;
16 import java
.util
.Arrays
;
17 import java
.util
.HashMap
;
19 import org
.eclipse
.jface
.viewers
.ILabelProviderListener
;
20 import org
.eclipse
.jface
.viewers
.ISelectionChangedListener
;
21 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
22 import org
.eclipse
.jface
.viewers
.ITableLabelProvider
;
23 import org
.eclipse
.jface
.viewers
.ITreeContentProvider
;
24 import org
.eclipse
.jface
.viewers
.ITreeViewerListener
;
25 import org
.eclipse
.jface
.viewers
.SelectionChangedEvent
;
26 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
27 import org
.eclipse
.jface
.viewers
.TreeExpansionEvent
;
28 import org
.eclipse
.jface
.viewers
.TreeViewer
;
29 import org
.eclipse
.jface
.viewers
.Viewer
;
30 import org
.eclipse
.linuxtools
.tmf
.ui
.widgets
.timegraph
.model
.ITimeGraphEntry
;
31 import org
.eclipse
.swt
.SWT
;
32 import org
.eclipse
.swt
.custom
.SashForm
;
33 import org
.eclipse
.swt
.events
.ControlAdapter
;
34 import org
.eclipse
.swt
.events
.ControlEvent
;
35 import org
.eclipse
.swt
.events
.MouseEvent
;
36 import org
.eclipse
.swt
.events
.MouseWheelListener
;
37 import org
.eclipse
.swt
.events
.PaintEvent
;
38 import org
.eclipse
.swt
.events
.PaintListener
;
39 import org
.eclipse
.swt
.events
.SelectionAdapter
;
40 import org
.eclipse
.swt
.events
.SelectionEvent
;
41 import org
.eclipse
.swt
.graphics
.Image
;
42 import org
.eclipse
.swt
.graphics
.Point
;
43 import org
.eclipse
.swt
.layout
.FillLayout
;
44 import org
.eclipse
.swt
.widgets
.Composite
;
45 import org
.eclipse
.swt
.widgets
.Display
;
46 import org
.eclipse
.swt
.widgets
.Event
;
47 import org
.eclipse
.swt
.widgets
.Listener
;
48 import org
.eclipse
.swt
.widgets
.Slider
;
49 import org
.eclipse
.swt
.widgets
.Tree
;
50 import org
.eclipse
.swt
.widgets
.TreeColumn
;
51 import org
.eclipse
.swt
.widgets
.TreeItem
;
53 public class TimeGraphCombo
extends Composite
{
55 // ------------------------------------------------------------------------
57 // ------------------------------------------------------------------------
59 private static final Object FILLER
= new Object();
61 // ------------------------------------------------------------------------
63 // ------------------------------------------------------------------------
66 private TreeViewer fTreeViewer
;
69 private TimeGraphViewer fTimeGraphViewer
;
71 // The selection listener map
72 private HashMap
<ITimeGraphSelectionListener
, SelectionListenerWrapper
> fSelectionListenerMap
= new HashMap
<ITimeGraphSelectionListener
, SelectionListenerWrapper
>();
74 // Flag to block the tree selection changed listener when triggered by the time graph combo
75 private boolean fInhibitTreeSelection
= false;
77 // Number of filler rows used by the tree content provider
78 private int fNumFillerRows
;
80 // Calculated item height for Linux workaround
81 private int fLinuxItemHeight
= 0;
83 // ------------------------------------------------------------------------
85 // ------------------------------------------------------------------------
88 * The TreeContentProviderWrapper is used to insert filler items after
89 * the elements of the tree's real content provider.
91 private class TreeContentProviderWrapper
implements ITreeContentProvider
{
92 private ITreeContentProvider contentProvider
;
94 public TreeContentProviderWrapper(ITreeContentProvider contentProvider
) {
95 this.contentProvider
= contentProvider
;
99 public void dispose() {
100 contentProvider
.dispose();
104 public void inputChanged(Viewer viewer
, Object oldInput
, Object newInput
) {
105 contentProvider
.inputChanged(viewer
, oldInput
, newInput
);
109 public Object
[] getElements(Object inputElement
) {
110 Object
[] elements
= contentProvider
.getElements(inputElement
);
111 // add filler elements to ensure alignment with time analysis viewer
112 Object
[] oElements
= Arrays
.copyOf(elements
, elements
.length
+ fNumFillerRows
, new Object
[0].getClass());
113 for (int i
= 0; i
< fNumFillerRows
; i
++) {
114 oElements
[elements
.length
+ i
] = FILLER
;
120 public Object
[] getChildren(Object parentElement
) {
121 if (parentElement
instanceof ITimeGraphEntry
) {
122 return contentProvider
.getChildren(parentElement
);
124 return new Object
[0];
129 public Object
getParent(Object element
) {
130 if (element
instanceof ITimeGraphEntry
) {
131 return contentProvider
.getParent(element
);
138 public boolean hasChildren(Object element
) {
139 if (element
instanceof ITimeGraphEntry
) {
140 return contentProvider
.hasChildren(element
);
148 * The TreeLabelProviderWrapper is used to intercept the filler items
149 * from the calls to the tree's real label provider.
151 private class TreeLabelProviderWrapper
implements ITableLabelProvider
{
152 private ITableLabelProvider labelProvider
;
154 public TreeLabelProviderWrapper(ITableLabelProvider labelProvider
) {
155 this.labelProvider
= labelProvider
;
159 public void addListener(ILabelProviderListener listener
) {
160 labelProvider
.addListener(listener
);
164 public void dispose() {
165 labelProvider
.dispose();
169 public boolean isLabelProperty(Object element
, String property
) {
170 if (element
instanceof ITimeGraphEntry
) {
171 return labelProvider
.isLabelProperty(element
, property
);
178 public void removeListener(ILabelProviderListener listener
) {
179 labelProvider
.removeListener(listener
);
183 public Image
getColumnImage(Object element
, int columnIndex
) {
184 if (element
instanceof ITimeGraphEntry
) {
185 return labelProvider
.getColumnImage(element
, columnIndex
);
192 public String
getColumnText(Object element
, int columnIndex
) {
193 if (element
instanceof ITimeGraphEntry
) {
194 return labelProvider
.getColumnText(element
, columnIndex
);
203 * The SelectionListenerWrapper is used to intercept the filler items from
204 * the time graph combo's real selection listener, and to prevent double
205 * notifications from being sent when selection changes in both tree and
206 * time graph at the same time.
208 private class SelectionListenerWrapper
implements ISelectionChangedListener
, ITimeGraphSelectionListener
{
209 private ITimeGraphSelectionListener listener
;
210 private ITimeGraphEntry selection
= null;
212 public SelectionListenerWrapper(ITimeGraphSelectionListener listener
) {
213 this.listener
= listener
;
217 public void selectionChanged(SelectionChangedEvent event
) {
218 if (fInhibitTreeSelection
) {
221 Object element
= ((IStructuredSelection
) event
.getSelection()).getFirstElement();
222 if (element
instanceof ITimeGraphEntry
) {
223 ITimeGraphEntry entry
= (ITimeGraphEntry
) element
;
224 if (entry
!= selection
) {
226 listener
.selectionChanged(new TimeGraphSelectionEvent(event
.getSource(), selection
));
232 public void selectionChanged(TimeGraphSelectionEvent event
) {
233 ITimeGraphEntry entry
= event
.getSelection();
234 if (entry
!= selection
) {
236 listener
.selectionChanged(new TimeGraphSelectionEvent(event
.getSource(), selection
));
241 // ------------------------------------------------------------------------
243 // ------------------------------------------------------------------------
246 * Constructs a new instance of this class given its parent
247 * and a style value describing its behavior and appearance.
249 * @param parent a widget which will be the parent of the new instance (cannot be null)
250 * @param style the style of widget to construct
252 public TimeGraphCombo(Composite parent
, int style
) {
253 super(parent
, style
);
254 setLayout(new FillLayout());
256 final SashForm sash
= new SashForm(this, SWT
.NONE
);
258 fTreeViewer
= new TreeViewer(sash
, SWT
.FULL_SELECTION
| SWT
.H_SCROLL
);
259 final Tree tree
= fTreeViewer
.getTree();
260 tree
.setHeaderVisible(true);
261 tree
.setLinesVisible(true);
263 fTimeGraphViewer
= new TimeGraphViewer(sash
, SWT
.NONE
);
264 fTimeGraphViewer
.setItemHeight(getItemHeight(tree
));
265 fTimeGraphViewer
.setHeaderHeight(tree
.getHeaderHeight());
266 fTimeGraphViewer
.setBorderWidth(tree
.getBorderWidth());
267 fTimeGraphViewer
.setNameWidthPref(0);
269 // Feature in Windows. The tree vertical bar reappears when
270 // the control is resized so we need to hide it again.
271 // Bug in Linux. The tree header height is 0 in constructor,
272 // so we need to reset it later when the control is resized.
273 tree
.addControlListener(new ControlAdapter() {
275 public void controlResized(ControlEvent e
) {
276 fTreeViewer
.getTree().getVerticalBar().setEnabled(false);
277 fTreeViewer
.getTree().getVerticalBar().setVisible(false);
278 fTimeGraphViewer
.setHeaderHeight(tree
.getHeaderHeight());
282 // ensure synchronization of expanded items between tree and time graph
283 fTreeViewer
.addTreeListener(new ITreeViewerListener() {
285 public void treeCollapsed(TreeExpansionEvent event
) {
286 fTimeGraphViewer
.setExpandedState((ITimeGraphEntry
) event
.getElement(), false);
290 public void treeExpanded(TreeExpansionEvent event
) {
291 fTimeGraphViewer
.setExpandedState((ITimeGraphEntry
) event
.getElement(), true);
295 // ensure synchronization of expanded items between tree and time graph
296 fTimeGraphViewer
.addTreeListener(new ITimeGraphTreeListener() {
298 public void treeCollapsed(TimeGraphTreeExpansionEvent event
) {
299 fTreeViewer
.setExpandedState(event
.getEntry(), false);
303 public void treeExpanded(TimeGraphTreeExpansionEvent event
) {
304 fTreeViewer
.setExpandedState(event
.getEntry(), true);
308 // prevent mouse button from selecting a filler tree item
309 tree
.addListener(SWT
.MouseDown
, new Listener() {
311 public void handleEvent(Event event
) {
312 TreeItem treeItem
= tree
.getItem(new Point(event
.x
, event
.y
));
313 if (treeItem
== null || treeItem
.getData() == FILLER
) {
315 ArrayList
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
);
316 if (treeItems
.size() == 0) {
317 fTreeViewer
.setSelection(new StructuredSelection());
318 fTimeGraphViewer
.setSelection(null);
321 // this prevents from scrolling up when selecting
322 // the partially visible tree item at the bottom
323 tree
.select(treeItems
.get(treeItems
.size() - 1));
324 fTreeViewer
.setSelection(new StructuredSelection());
325 fTimeGraphViewer
.setSelection(null);
330 // prevent mouse wheel from scrolling down into filler tree items
331 tree
.addListener(SWT
.MouseWheel
, new Listener() {
333 public void handleEvent(Event event
) {
335 Slider scrollBar
= fTimeGraphViewer
.getVerticalBar();
336 fTimeGraphViewer
.setTopIndex(scrollBar
.getSelection() - event
.count
);
337 ArrayList
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
);
338 if (treeItems
.size() == 0) {
341 TreeItem treeItem
= treeItems
.get(fTimeGraphViewer
.getTopIndex());
342 tree
.setTopItem(treeItem
);
346 // prevent key stroke from selecting a filler tree item
347 tree
.addListener(SWT
.KeyDown
, new Listener() {
349 public void handleEvent(Event event
) {
350 ArrayList
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
);
351 if (treeItems
.size() == 0) {
352 fTreeViewer
.setSelection(new StructuredSelection());
356 if (event
.keyCode
== SWT
.ARROW_DOWN
) {
357 int index
= Math
.min(fTimeGraphViewer
.getSelectionIndex() + 1, treeItems
.size() - 1);
358 fTimeGraphViewer
.setSelection((ITimeGraphEntry
) treeItems
.get(index
).getData());
360 } else if (event
.keyCode
== SWT
.PAGE_DOWN
) {
361 int height
= tree
.getSize().y
- tree
.getHeaderHeight() - tree
.getHorizontalBar().getSize().y
;
362 int countPerPage
= height
/ getItemHeight(tree
);
363 int index
= Math
.min(fTimeGraphViewer
.getSelectionIndex() + countPerPage
- 1, treeItems
.size() - 1);
364 fTimeGraphViewer
.setSelection((ITimeGraphEntry
) treeItems
.get(index
).getData());
366 } else if (event
.keyCode
== SWT
.END
) {
367 fTimeGraphViewer
.setSelection((ITimeGraphEntry
) treeItems
.get(treeItems
.size() - 1).getData());
370 TreeItem treeItem
= treeItems
.get(fTimeGraphViewer
.getTopIndex());
371 tree
.setTopItem(treeItem
);
372 if (fTimeGraphViewer
.getSelectionIndex() >= 0) {
373 fTreeViewer
.setSelection(new StructuredSelection(fTimeGraphViewer
.getSelection()));
375 fTreeViewer
.setSelection(new StructuredSelection());
380 // ensure alignment of top item between tree and time graph
381 fTimeGraphViewer
.getTimeGraphControl().addControlListener(new ControlAdapter() {
383 public void controlResized(ControlEvent e
) {
384 ArrayList
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
);
385 if (treeItems
.size() == 0) {
388 TreeItem treeItem
= treeItems
.get(fTimeGraphViewer
.getTopIndex());
389 tree
.setTopItem(treeItem
);
393 // ensure synchronization of selected item between tree and time graph
394 fTreeViewer
.addSelectionChangedListener(new ISelectionChangedListener() {
396 public void selectionChanged(SelectionChangedEvent event
) {
397 if (fInhibitTreeSelection
) {
400 if (event
.getSelection() instanceof IStructuredSelection
) {
401 Object selection
= ((IStructuredSelection
) event
.getSelection()).getFirstElement();
402 if (selection
instanceof ITimeGraphEntry
) {
403 fTimeGraphViewer
.setSelection((ITimeGraphEntry
) selection
);
405 ArrayList
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
);
406 if (treeItems
.size() == 0) {
409 TreeItem treeItem
= treeItems
.get(fTimeGraphViewer
.getTopIndex());
410 tree
.setTopItem(treeItem
);
415 // ensure synchronization of selected item between tree and time graph
416 fTimeGraphViewer
.addSelectionListener(new ITimeGraphSelectionListener() {
418 public void selectionChanged(TimeGraphSelectionEvent event
) {
419 ITimeGraphEntry entry
= fTimeGraphViewer
.getSelection();
420 fInhibitTreeSelection
= true; // block the tree selection changed listener
422 StructuredSelection selection
= new StructuredSelection(entry
);
423 fTreeViewer
.setSelection(selection
);
425 fTreeViewer
.setSelection(new StructuredSelection());
427 fInhibitTreeSelection
= false;
428 ArrayList
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
);
429 if (treeItems
.size() == 0) {
432 TreeItem treeItem
= treeItems
.get(fTimeGraphViewer
.getTopIndex());
433 tree
.setTopItem(treeItem
);
437 // ensure alignment of top item between tree and time graph
438 fTimeGraphViewer
.getVerticalBar().addSelectionListener(new SelectionAdapter() {
440 public void widgetSelected(SelectionEvent e
) {
441 ArrayList
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
);
442 if (treeItems
.size() == 0) {
445 TreeItem treeItem
= treeItems
.get(fTimeGraphViewer
.getTopIndex());
446 tree
.setTopItem(treeItem
);
450 // ensure alignment of top item between tree and time graph
451 fTimeGraphViewer
.getTimeGraphControl().addMouseWheelListener(new MouseWheelListener() {
453 public void mouseScrolled(MouseEvent e
) {
454 ArrayList
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
);
455 if (treeItems
.size() == 0) {
458 TreeItem treeItem
= treeItems
.get(fTimeGraphViewer
.getTopIndex());
459 tree
.setTopItem(treeItem
);
463 // The filler rows are required to ensure alignment when the tree does not have a
464 // visible horizontal scroll bar. The tree does not allow its top item to be set
465 // to a value that would cause blank space to be drawn at the bottom of the tree.
466 fNumFillerRows
= Display
.getDefault().getBounds().height
/ getItemHeight(tree
);
468 sash
.setWeights(new int[] { 1, 1 });
471 // ------------------------------------------------------------------------
473 // ------------------------------------------------------------------------
476 * Returns this time graph combo's tree viewer.
478 * @return the tree viewer
480 public TreeViewer
getTreeViewer() {
485 * Returns this time graph combo's time graph viewer.
487 * @return the time graph viewer
489 public TimeGraphViewer
getTimeGraphViewer() {
490 return fTimeGraphViewer
;
493 // ------------------------------------------------------------------------
495 // ------------------------------------------------------------------------
498 * @see org.eclipse.swt.widgets.Control#redraw()
501 public void redraw() {
502 fTimeGraphViewer
.getControl().redraw();
506 // ------------------------------------------------------------------------
508 // ------------------------------------------------------------------------
511 * Sets the tree content provider used by this time graph combo.
513 * @param contentProvider the tree content provider
515 public void setTreeContentProvider(ITreeContentProvider contentProvider
) {
516 fTreeViewer
.setContentProvider(new TreeContentProviderWrapper(contentProvider
));
520 * Sets the tree label provider used by this time graph combo.
522 * @param treeLabelProvider the tree label provider
524 public void setTreeLabelProvider(ITableLabelProvider labelProvider
) {
525 fTreeViewer
.setLabelProvider(new TreeLabelProviderWrapper(labelProvider
));
529 * Sets the tree columns for this time graph combo.
531 * @param columnNames the tree column names
533 public void setTreeColumns(String
[] columnNames
) {
534 final Tree tree
= fTreeViewer
.getTree();
535 for (String columnName
: columnNames
) {
536 TreeColumn column
= new TreeColumn(tree
, SWT
.LEFT
);
537 column
.setText(columnName
);
544 * Sets the time graph provider used by this time graph combo.
546 * @param timeGraphProvider the time graph provider
548 public void setTimeGraphProvider(ITimeGraphPresentationProvider timeGraphProvider
) {
549 fTimeGraphViewer
.setTimeGraphProvider(timeGraphProvider
);
553 * Sets or clears the input for this time graph combo.
554 * The input array should only contain top-level elements.
556 * @param input the input of this time graph combo, or <code>null</code> if none
558 public void setInput(ITimeGraphEntry
[] input
) {
559 fInhibitTreeSelection
= true;
560 fTreeViewer
.setInput(input
);
561 fInhibitTreeSelection
= true;
562 fTreeViewer
.expandAll();
563 fTreeViewer
.getTree().getVerticalBar().setEnabled(false);
564 fTreeViewer
.getTree().getVerticalBar().setVisible(false);
565 fTimeGraphViewer
.setItemHeight(getItemHeight(fTreeViewer
.getTree()));
566 fTimeGraphViewer
.setInput(input
);
570 * Refreshes this time graph completely with information freshly obtained from its model.
572 public void refresh() {
573 fTreeViewer
.refresh();
574 fTimeGraphViewer
.refresh();
578 * Adds a listener for selection changes in this time graph combo.
580 * @param listener a selection listener
582 public void addSelectionListener(ITimeGraphSelectionListener listener
) {
583 SelectionListenerWrapper listenerWrapper
= new SelectionListenerWrapper(listener
);
584 fTreeViewer
.addSelectionChangedListener(listenerWrapper
);
585 fSelectionListenerMap
.put(listener
, listenerWrapper
);
586 fTimeGraphViewer
.addSelectionListener(listenerWrapper
);
590 * Removes the given selection listener from this time graph combo.
592 * @param listener a selection changed listener
594 public void removeSelectionListener(ITimeGraphSelectionListener listener
) {
595 SelectionListenerWrapper listenerWrapper
= fSelectionListenerMap
.remove(listener
);
596 fTreeViewer
.removeSelectionChangedListener(listenerWrapper
);
597 fTimeGraphViewer
.removeSelectionListener(listenerWrapper
);
601 * Sets the current selection for this time graph combo.
603 * @param selection the new selection
605 public void setSelection(ITimeGraphEntry selection
) {
606 fTimeGraphViewer
.setSelection(selection
);
607 fInhibitTreeSelection
= true; // block the tree selection changed listener
608 if (selection
!= null) {
609 StructuredSelection structuredSelection
= new StructuredSelection(selection
);
610 fTreeViewer
.setSelection(structuredSelection
);
612 fTreeViewer
.setSelection(new StructuredSelection());
614 fInhibitTreeSelection
= false;
615 ArrayList
<TreeItem
> treeItems
= getVisibleExpandedItems(fTreeViewer
.getTree());
616 if (treeItems
.size() == 0) {
619 TreeItem treeItem
= treeItems
.get(fTimeGraphViewer
.getTopIndex());
620 fTreeViewer
.getTree().setTopItem(treeItem
);
623 // ------------------------------------------------------------------------
625 // ------------------------------------------------------------------------
627 private ArrayList
<TreeItem
> getVisibleExpandedItems(Tree tree
) {
628 ArrayList
<TreeItem
> items
= new ArrayList
<TreeItem
>();
629 for (TreeItem item
: tree
.getItems()) {
630 if (item
.getData() == FILLER
) {
634 if (item
.getExpanded()) {
635 items
.addAll(getVisibleExpandedItems(item
));
641 private ArrayList
<TreeItem
> getVisibleExpandedItems(TreeItem treeItem
) {
642 ArrayList
<TreeItem
> items
= new ArrayList
<TreeItem
>();
643 for (TreeItem item
: treeItem
.getItems()) {
645 if (item
.getExpanded()) {
646 items
.addAll(getVisibleExpandedItems(item
));
652 private int getItemHeight(final Tree tree
) {
654 * Bug in Linux. The method getItemHeight doesn't always return the correct value.
656 if (fLinuxItemHeight
>= 0 && System
.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$
657 if (fLinuxItemHeight
!= 0) {
658 return fLinuxItemHeight
;
660 ArrayList
<TreeItem
> treeItems
= getVisibleExpandedItems(tree
);
661 if (treeItems
.size() > 1) {
662 final TreeItem treeItem0
= treeItems
.get(0);
663 final TreeItem treeItem1
= treeItems
.get(1);
664 PaintListener paintListener
= new PaintListener() {
666 public void paintControl(PaintEvent e
) {
667 tree
.removePaintListener(this);
668 int y0
= treeItem0
.getBounds().y
;
669 int y1
= treeItem1
.getBounds().y
;
670 int itemHeight
= y1
- y0
;
671 if (itemHeight
> 0) {
672 fLinuxItemHeight
= itemHeight
;
673 fTimeGraphViewer
.setItemHeight(itemHeight
);
677 tree
.addPaintListener(paintListener
);
680 fLinuxItemHeight
= -1; // Not Linux, don't perform os.name check anymore
682 return tree
.getItemHeight();