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