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