Bug 378401: Implementation of time graph widget.
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.ui / src / org / eclipse / linuxtools / tmf / ui / widgets / timegraph / TimeGraphCombo.java
1 /*******************************************************************************
2 * Copyright (c) 2012 Ericsson
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 *******************************************************************************/
12
13 package org.eclipse.linuxtools.tmf.ui.widgets.timegraph;
14
15 import java.util.ArrayList;
16 import java.util.Arrays;
17 import java.util.HashMap;
18
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;
52
53 public class TimeGraphCombo extends Composite {
54
55 // ------------------------------------------------------------------------
56 // Constants
57 // ------------------------------------------------------------------------
58
59 private static final Object FILLER = new Object();
60
61 // ------------------------------------------------------------------------
62 // Fields
63 // ------------------------------------------------------------------------
64
65 // The tree viewer
66 private TreeViewer fTreeViewer;
67
68 // The time viewer
69 private TimeGraphViewer fTimeGraphViewer;
70
71 // The selection listener map
72 private HashMap<ITimeGraphSelectionListener, SelectionListenerWrapper> fSelectionListenerMap = new HashMap<ITimeGraphSelectionListener, SelectionListenerWrapper>();
73
74 // Flag to block the tree selection changed listener when triggered by the time graph combo
75 private boolean fInhibitTreeSelection = false;
76
77 // Number of filler rows used by the tree content provider
78 private static int fNumFillerRows;
79
80 // Calculated item height for Linux workaround
81 private int fLinuxItemHeight = 0;
82
83 // ------------------------------------------------------------------------
84 // Classes
85 // ------------------------------------------------------------------------
86
87 private class TreeContentProviderWrapper implements ITreeContentProvider {
88 private ITreeContentProvider contentProvider;
89
90 public TreeContentProviderWrapper(ITreeContentProvider contentProvider) {
91 this.contentProvider = contentProvider;
92 }
93
94 @Override
95 public void dispose() {
96 contentProvider.dispose();
97 }
98
99 @Override
100 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
101 contentProvider.inputChanged(viewer, oldInput, newInput);
102 }
103
104 @Override
105 public Object[] getElements(Object inputElement) {
106 Object[] elements = contentProvider.getElements(inputElement);
107 // add filler elements to ensure alignment with time analysis viewer
108 Object[] oElements = Arrays.copyOf(elements, elements.length + fNumFillerRows, new Object[0].getClass());
109 for (int i = 0; i < fNumFillerRows; i++) {
110 oElements[elements.length + i] = FILLER;
111 }
112 return oElements;
113 }
114
115 @Override
116 public Object[] getChildren(Object parentElement) {
117 if (parentElement instanceof ITimeGraphEntry) {
118 return contentProvider.getChildren(parentElement);
119 } else {
120 return new Object[0];
121 }
122 }
123
124 @Override
125 public Object getParent(Object element) {
126 if (element instanceof ITimeGraphEntry) {
127 return contentProvider.getParent(element);
128 } else {
129 return null;
130 }
131 }
132
133 @Override
134 public boolean hasChildren(Object element) {
135 if (element instanceof ITimeGraphEntry) {
136 return contentProvider.hasChildren(element);
137 } else {
138 return false;
139 }
140 }
141 }
142
143 private class TreeLabelProviderWrapper implements ITableLabelProvider {
144 private ITableLabelProvider labelProvider;
145
146 public TreeLabelProviderWrapper(ITableLabelProvider labelProvider) {
147 this.labelProvider = labelProvider;
148 }
149
150 @Override
151 public void addListener(ILabelProviderListener listener) {
152 labelProvider.addListener(listener);
153 }
154
155 @Override
156 public void dispose() {
157 labelProvider.dispose();
158 }
159
160 @Override
161 public boolean isLabelProperty(Object element, String property) {
162 if (element instanceof ITimeGraphEntry) {
163 return labelProvider.isLabelProperty(element, property);
164 } else {
165 return false;
166 }
167 }
168
169 @Override
170 public void removeListener(ILabelProviderListener listener) {
171 labelProvider.removeListener(listener);
172 }
173
174 @Override
175 public Image getColumnImage(Object element, int columnIndex) {
176 if (element instanceof ITimeGraphEntry) {
177 return labelProvider.getColumnImage(element, columnIndex);
178 } else {
179 return null;
180 }
181 }
182
183 @Override
184 public String getColumnText(Object element, int columnIndex) {
185 if (element instanceof ITimeGraphEntry) {
186 return labelProvider.getColumnText(element, columnIndex);
187 } else {
188 return null;
189 }
190 }
191
192 }
193
194 private class SelectionListenerWrapper implements ISelectionChangedListener, ITimeGraphSelectionListener {
195 private ITimeGraphSelectionListener listener;
196 private ITimeGraphEntry selection = null;
197
198 public SelectionListenerWrapper(ITimeGraphSelectionListener listener) {
199 this.listener = listener;
200 }
201
202 @Override
203 public void selectionChanged(SelectionChangedEvent event) {
204 if (fInhibitTreeSelection) {
205 return;
206 }
207 Object element = ((IStructuredSelection) event.getSelection()).getFirstElement();
208 if (element instanceof ITimeGraphEntry) {
209 ITimeGraphEntry entry = (ITimeGraphEntry) element;
210 if (entry != selection) {
211 selection = entry;
212 listener.selectionChanged(new TimeGraphSelectionEvent(event.getSource(), selection));
213 }
214 }
215 }
216
217 @Override
218 public void selectionChanged(TimeGraphSelectionEvent event) {
219 ITimeGraphEntry entry = event.getSelection();
220 if (entry != selection) {
221 selection = entry;
222 listener.selectionChanged(new TimeGraphSelectionEvent(event.getSource(), selection));
223 }
224 }
225 }
226
227 // ------------------------------------------------------------------------
228 // Constructors
229 // ------------------------------------------------------------------------
230
231 public TimeGraphCombo(Composite parent, int style) {
232 super(parent, style);
233 setLayout(new FillLayout());
234
235 final SashForm sash = new SashForm(this, SWT.NONE);
236
237 fTreeViewer = new TreeViewer(sash, SWT.FULL_SELECTION | SWT.H_SCROLL);
238 final Tree tree = fTreeViewer.getTree();
239 tree.setHeaderVisible(true);
240 tree.setLinesVisible(true);
241
242 fTimeGraphViewer = new TimeGraphViewer(sash, SWT.NONE);
243 fTimeGraphViewer.setItemHeight(getItemHeight(tree));
244 fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
245 fTimeGraphViewer.setBorderWidth(tree.getBorderWidth());
246 fTimeGraphViewer.setNameWidthPref(0);
247
248 // Bug in Linux. The tree header height is 0 in constructor,
249 // so we need to reset it later when the control is resized.
250 tree.addControlListener(new ControlAdapter() {
251 @Override
252 public void controlResized(ControlEvent e) {
253 fTreeViewer.getTree().getVerticalBar().setEnabled(false);
254 fTreeViewer.getTree().getVerticalBar().setVisible(false);
255 fTimeGraphViewer.setHeaderHeight(tree.getHeaderHeight());
256 }
257 });
258
259 fTreeViewer.addTreeListener(new ITreeViewerListener() {
260 @Override
261 public void treeCollapsed(TreeExpansionEvent event) {
262 fTimeGraphViewer.setExpandedState((ITimeGraphEntry) event.getElement(), false);
263 }
264
265 @Override
266 public void treeExpanded(TreeExpansionEvent event) {
267 fTimeGraphViewer.setExpandedState((ITimeGraphEntry) event.getElement(), true);
268 }
269 });
270
271 fTimeGraphViewer.addTreeListener(new ITimeGraphTreeListener() {
272 @Override
273 public void treeCollapsed(TimeGraphTreeExpansionEvent event) {
274 fTreeViewer.setExpandedState(event.getEntry(), false);
275 }
276
277 @Override
278 public void treeExpanded(TimeGraphTreeExpansionEvent event) {
279 fTreeViewer.setExpandedState(event.getEntry(), true);
280 }
281 });
282
283 // prevent mouse button from selecting a filler tree item
284 tree.addListener(SWT.MouseDown, new Listener() {
285 @Override
286 public void handleEvent(Event event) {
287 TreeItem treeItem = tree.getItem(new Point(event.x, event.y));
288 if (treeItem == null || treeItem.getData() == FILLER) {
289 event.doit = false;
290 ArrayList<TreeItem> treeItems = getVisibleExpandedItems(tree);
291 if (treeItems.size() == 0) {
292 fTreeViewer.setSelection(new StructuredSelection());
293 fTimeGraphViewer.setSelection(null);
294 return;
295 }
296 // this prevents from scrolling up when selecting
297 // the partially visible tree item at the bottom
298 tree.select(treeItems.get(treeItems.size() - 1));
299 fTreeViewer.setSelection(new StructuredSelection());
300 fTimeGraphViewer.setSelection(null);
301 }
302 }
303 });
304
305 tree.addListener(SWT.MouseWheel, new Listener() {
306 @Override
307 public void handleEvent(Event event) {
308 event.doit = false;
309 Slider scrollBar = fTimeGraphViewer.getVerticalBar();
310 fTimeGraphViewer.setTopIndex(scrollBar.getSelection() - event.count);
311 ArrayList<TreeItem> treeItems = getVisibleExpandedItems(tree);
312 if (treeItems.size() == 0) {
313 return;
314 }
315 TreeItem treeItem = treeItems.get(fTimeGraphViewer.getTopIndex());
316 tree.setTopItem(treeItem);
317 }
318 });
319
320 // prevent key stroke from selecting a filler tree item
321 tree.addListener(SWT.KeyDown, new Listener() {
322 @Override
323 public void handleEvent(Event event) {
324 ArrayList<TreeItem> treeItems = getVisibleExpandedItems(tree);
325 if (treeItems.size() == 0) {
326 fTreeViewer.setSelection(new StructuredSelection());
327 event.doit = false;
328 return;
329 }
330 if (event.keyCode == SWT.ARROW_DOWN) {
331 int index = Math.min(fTimeGraphViewer.getSelectionIndex() + 1, treeItems.size() - 1);
332 fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(index).getData());
333 event.doit = false;
334 } else if (event.keyCode == SWT.PAGE_DOWN) {
335 int height = tree.getSize().y - tree.getHeaderHeight() - tree.getHorizontalBar().getSize().y;
336 int countPerPage = height / getItemHeight(tree);
337 int index = Math.min(fTimeGraphViewer.getSelectionIndex() + countPerPage - 1, treeItems.size() - 1);
338 fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(index).getData());
339 event.doit = false;
340 } else if (event.keyCode == SWT.END) {
341 fTimeGraphViewer.setSelection((ITimeGraphEntry) treeItems.get(treeItems.size() - 1).getData());
342 event.doit = false;
343 }
344 TreeItem treeItem = treeItems.get(fTimeGraphViewer.getTopIndex());
345 tree.setTopItem(treeItem);
346 if (fTimeGraphViewer.getSelectionIndex() >= 0) {
347 fTreeViewer.setSelection(new StructuredSelection(fTimeGraphViewer.getSelection()));
348 } else {
349 fTreeViewer.setSelection(new StructuredSelection());
350 }
351 }
352 });
353
354 fTimeGraphViewer.getTimeGraphControl().addControlListener(new ControlAdapter() {
355 @Override
356 public void controlResized(ControlEvent e) {
357 ArrayList<TreeItem> treeItems = getVisibleExpandedItems(tree);
358 if (treeItems.size() == 0) {
359 return;
360 }
361 TreeItem treeItem = treeItems.get(fTimeGraphViewer.getTopIndex());
362 tree.setTopItem(treeItem);
363 }
364 });
365
366 fTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
367 @Override
368 public void selectionChanged(SelectionChangedEvent event) {
369 if (fInhibitTreeSelection) {
370 return;
371 }
372 if (event.getSelection() instanceof IStructuredSelection) {
373 Object selection = ((IStructuredSelection) event.getSelection()).getFirstElement();
374 if (selection instanceof ITimeGraphEntry) {
375 fTimeGraphViewer.setSelection((ITimeGraphEntry) selection);
376 }
377 ArrayList<TreeItem> treeItems = getVisibleExpandedItems(tree);
378 if (treeItems.size() == 0) {
379 return;
380 }
381 TreeItem treeItem = treeItems.get(fTimeGraphViewer.getTopIndex());
382 tree.setTopItem(treeItem);
383 }
384 }
385 });
386
387 fTimeGraphViewer.addSelectionListener(new ITimeGraphSelectionListener() {
388 @Override
389 public void selectionChanged(TimeGraphSelectionEvent event) {
390 ITimeGraphEntry entry = fTimeGraphViewer.getSelection();
391 fInhibitTreeSelection = true; // block the tree selection changed listener
392 if (entry != null) {
393 StructuredSelection selection = new StructuredSelection(entry);
394 fTreeViewer.setSelection(selection);
395 } else {
396 fTreeViewer.setSelection(new StructuredSelection());
397 }
398 fInhibitTreeSelection = false;
399 ArrayList<TreeItem> treeItems = getVisibleExpandedItems(tree);
400 if (treeItems.size() == 0) {
401 return;
402 }
403 TreeItem treeItem = treeItems.get(fTimeGraphViewer.getTopIndex());
404 tree.setTopItem(treeItem);
405 }
406 });
407
408 fTimeGraphViewer.getVerticalBar().addSelectionListener(new SelectionAdapter() {
409 @Override
410 public void widgetSelected(SelectionEvent e) {
411 ArrayList<TreeItem> treeItems = getVisibleExpandedItems(tree);
412 if (treeItems.size() == 0) {
413 return;
414 }
415 TreeItem treeItem = treeItems.get(fTimeGraphViewer.getTopIndex());
416 tree.setTopItem(treeItem);
417 }
418 });
419
420 fTimeGraphViewer.getTimeGraphControl().addMouseWheelListener(new MouseWheelListener() {
421 @Override
422 public void mouseScrolled(MouseEvent e) {
423 ArrayList<TreeItem> treeItems = getVisibleExpandedItems(tree);
424 if (treeItems.size() == 0) {
425 return;
426 }
427 TreeItem treeItem = treeItems.get(fTimeGraphViewer.getTopIndex());
428 tree.setTopItem(treeItem);
429 }
430 });
431
432 fNumFillerRows = Display.getDefault().getBounds().height / getItemHeight(tree);
433
434 sash.setWeights(new int[] { 1, 1 });
435 }
436
437 private ArrayList<TreeItem> getVisibleExpandedItems(Tree tree) {
438 ArrayList<TreeItem> items = new ArrayList<TreeItem>();
439 for (TreeItem item : tree.getItems()) {
440 if (item.getData() == FILLER) {
441 break;
442 }
443 items.add(item);
444 if (item.getExpanded()) {
445 items.addAll(getVisibleExpandedItems(item));
446 }
447 }
448 return items;
449 }
450
451 private ArrayList<TreeItem> getVisibleExpandedItems(TreeItem treeItem) {
452 ArrayList<TreeItem> items = new ArrayList<TreeItem>();
453 for (TreeItem item : treeItem.getItems()) {
454 items.add(item);
455 if (item.getExpanded()) {
456 items.addAll(getVisibleExpandedItems(item));
457 }
458 }
459 return items;
460 }
461
462 // ------------------------------------------------------------------------
463 // Accessors
464 // ------------------------------------------------------------------------
465
466 /**
467 * Returns this time graph combo's tree viewer.
468 *
469 * @return the tree viewer
470 */
471 public TreeViewer getTreeViewer() {
472 return fTreeViewer;
473 }
474
475 /**
476 * Returns this time graph combo's time graph viewer.
477 *
478 * @return the time graph viewer
479 */
480 public TimeGraphViewer getTimeGraphViewer() {
481 return fTimeGraphViewer;
482 }
483
484 // ------------------------------------------------------------------------
485 // Control
486 // ------------------------------------------------------------------------
487
488 /* (non-Javadoc)
489 * @see org.eclipse.swt.widgets.Control#redraw()
490 */
491 @Override
492 public void redraw() {
493 fTimeGraphViewer.getControl().redraw();
494 super.redraw();
495 }
496
497 // ------------------------------------------------------------------------
498 // Internal
499 // ------------------------------------------------------------------------
500
501 public int getItemHeight(final Tree tree) {
502 /*
503 * Bug in Linux. The method getItemHeight doesn't always return the correct value.
504 */
505 if (fLinuxItemHeight >= 0 && System.getProperty("os.name").contains("Linux")) { //$NON-NLS-1$ //$NON-NLS-2$
506 if (fLinuxItemHeight != 0) {
507 return fLinuxItemHeight;
508 }
509 ArrayList<TreeItem> treeItems = getVisibleExpandedItems(tree);
510 if (treeItems.size() > 1) {
511 final TreeItem treeItem0 = treeItems.get(0);
512 final TreeItem treeItem1 = treeItems.get(1);
513 PaintListener paintListener = new PaintListener() {
514 @Override
515 public void paintControl(PaintEvent e) {
516 tree.removePaintListener(this);
517 int y0 = treeItem0.getBounds().y;
518 int y1 = treeItem1.getBounds().y;
519 int itemHeight = y1 - y0;
520 if (itemHeight > 0) {
521 fLinuxItemHeight = itemHeight;
522 fTimeGraphViewer.setItemHeight(itemHeight);
523 }
524 }
525 };
526 tree.addPaintListener(paintListener);
527 }
528 } else {
529 fLinuxItemHeight = -1; // Not Linux, don't perform os.name check anymore
530 }
531 return tree.getItemHeight();
532 }
533
534 // ------------------------------------------------------------------------
535 // Operations
536 // ------------------------------------------------------------------------
537
538 /**
539 * Sets the tree content provider used by this time graph combo.
540 *
541 * @param contentProvider the tree content provider
542 */
543 public void setTreeContentProvider(ITreeContentProvider contentProvider) {
544 fTreeViewer.setContentProvider(new TreeContentProviderWrapper(contentProvider));
545 }
546
547 /**
548 * Sets the tree label provider used by this time graph combo.
549 *
550 * @param treeLabelProvider the tree label provider
551 */
552 public void setTreeLabelProvider(ITableLabelProvider labelProvider) {
553 fTreeViewer.setLabelProvider(new TreeLabelProviderWrapper(labelProvider));
554 }
555
556 /**
557 * Sets the tree columns for this time graph combo.
558 *
559 * @param columnNames the tree column names
560 */
561 public void setTreeColumns(String[] columnNames) {
562 final Tree tree = fTreeViewer.getTree();
563 for (String columnName : columnNames) {
564 TreeColumn column = new TreeColumn(tree, SWT.LEFT);
565 column.setText(columnName);
566 column.pack();
567 }
568 }
569
570
571 /**
572 * Sets the time graph provider used by this time graph combo.
573 *
574 * @param timeGraphProvider the time graph provider
575 */
576 public void setTimeGraphProvider(ITimeGraphPresentationProvider timeGraphProvider) {
577 fTimeGraphViewer.setTimeGraphProvider(timeGraphProvider);
578 }
579
580 /**
581 * Sets or clears the input for this time graph combo.
582 * The input array should only contain top-level elements.
583 *
584 * @param input the input of this time graph combo, or <code>null</code> if none
585 */
586 public void setInput(ITimeGraphEntry[] input) {
587 fTreeViewer.setInput(input);
588 fTreeViewer.expandAll();
589 fTreeViewer.getTree().getVerticalBar().setEnabled(false);
590 fTreeViewer.getTree().getVerticalBar().setVisible(false);
591 fTimeGraphViewer.setItemHeight(getItemHeight(fTreeViewer.getTree()));
592 fTimeGraphViewer.setInput(input);
593 }
594
595 /**
596 * Refreshes this time graph completely with information freshly obtained from its model.
597 */
598 public void refresh() {
599 fTreeViewer.refresh();
600 fTimeGraphViewer.refresh();
601 }
602
603 /**
604 * Adds a listener for selection changes in this time graph combo.
605 *
606 * @param listener a selection listener
607 */
608 public void addSelectionListener(ITimeGraphSelectionListener listener) {
609 SelectionListenerWrapper listenerWrapper = new SelectionListenerWrapper(listener);
610 fTreeViewer.addSelectionChangedListener(listenerWrapper);
611 fSelectionListenerMap.put(listener, listenerWrapper);
612 fTimeGraphViewer.addSelectionListener(listenerWrapper);
613 }
614
615 /**
616 * Removes the given selection listener from this time graph combo.
617 *
618 * @param listener a selection changed listener
619 */
620 public void removeSelectionListener(ITimeGraphSelectionListener listener) {
621 SelectionListenerWrapper listenerWrapper = fSelectionListenerMap.remove(listener);
622 fTreeViewer.removeSelectionChangedListener(listenerWrapper);
623 fTimeGraphViewer.removeSelectionListener(listenerWrapper);
624 }
625 }
This page took 0.060339 seconds and 6 git commands to generate.