c44108b21bfb9555350ee500e8294bcd0a1bf321
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / viewers / tree / AbstractTmfTreeViewer.java
1 /*******************************************************************************
2 * Copyright (c) 2014 École Polytechnique de Montréal
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 * Geneviève Bastien - Initial API and implementation
11 *******************************************************************************/
12
13 package org.eclipse.tracecompass.tmf.ui.viewers.tree;
14
15 import java.util.List;
16
17 import org.eclipse.core.runtime.IProgressMonitor;
18 import org.eclipse.core.runtime.IStatus;
19 import org.eclipse.core.runtime.Status;
20 import org.eclipse.core.runtime.jobs.Job;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.eclipse.jface.viewers.AbstractTreeViewer;
23 import org.eclipse.jface.viewers.IBaseLabelProvider;
24 import org.eclipse.jface.viewers.ILabelProviderListener;
25 import org.eclipse.jface.viewers.ISelectionChangedListener;
26 import org.eclipse.jface.viewers.IStructuredSelection;
27 import org.eclipse.jface.viewers.ITableColorProvider;
28 import org.eclipse.jface.viewers.ITableFontProvider;
29 import org.eclipse.jface.viewers.ITableLabelProvider;
30 import org.eclipse.jface.viewers.ITreeContentProvider;
31 import org.eclipse.jface.viewers.StructuredSelection;
32 import org.eclipse.jface.viewers.TreeViewer;
33 import org.eclipse.jface.viewers.Viewer;
34 import org.eclipse.swt.SWT;
35 import org.eclipse.swt.graphics.Color;
36 import org.eclipse.swt.graphics.Font;
37 import org.eclipse.swt.graphics.Image;
38 import org.eclipse.swt.widgets.Composite;
39 import org.eclipse.swt.widgets.Control;
40 import org.eclipse.swt.widgets.Display;
41 import org.eclipse.swt.widgets.Event;
42 import org.eclipse.swt.widgets.Listener;
43 import org.eclipse.swt.widgets.Tree;
44 import org.eclipse.swt.widgets.TreeColumn;
45 import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal;
46 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
47 import org.eclipse.tracecompass.tmf.core.signal.TmfWindowRangeUpdatedSignal;
48 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
49 import org.eclipse.tracecompass.tmf.ui.viewers.TmfTimeViewer;
50
51 /**
52 * Abstract class for viewers who will display data using a TreeViewer. It
53 * automatically synchronizes with time information of the UI. It also
54 * implements some common functionalities for all tree viewer, such as managing
55 * the column data, content initialization and update. The viewer implementing
56 * this does not have to worry about whether some code runs in the UI thread or
57 * not.
58 *
59 * @author Geneviève Bastien
60 */
61 public abstract class AbstractTmfTreeViewer extends TmfTimeViewer {
62
63 private final TreeViewer fTreeViewer;
64
65 // ------------------------------------------------------------------------
66 // Internal classes
67 // ------------------------------------------------------------------------
68
69 /* The elements of the tree viewer are of type ITmfTreeViewerEntry */
70 private class TreeContentProvider implements ITreeContentProvider {
71
72 @Override
73 public void dispose() {
74 }
75
76 @Override
77 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
78 }
79
80 @Override
81 public Object[] getElements(Object inputElement) {
82 if (inputElement instanceof ITmfTreeViewerEntry) {
83 return ((ITmfTreeViewerEntry) inputElement).getChildren().toArray(new ITmfTreeViewerEntry[0]);
84 }
85 return new ITmfTreeViewerEntry[0];
86 }
87
88 @Override
89 public Object[] getChildren(Object parentElement) {
90 ITmfTreeViewerEntry entry = (ITmfTreeViewerEntry) parentElement;
91 List<? extends ITmfTreeViewerEntry> children = entry.getChildren();
92 return children.toArray(new ITmfTreeViewerEntry[children.size()]);
93 }
94
95 @Override
96 public Object getParent(Object element) {
97 ITmfTreeViewerEntry entry = (ITmfTreeViewerEntry) element;
98 return entry.getParent();
99 }
100
101 @Override
102 public boolean hasChildren(Object element) {
103 ITmfTreeViewerEntry entry = (ITmfTreeViewerEntry) element;
104 return entry.hasChildren();
105 }
106
107 }
108
109 /**
110 * Base class to provide the labels for the tree viewer. Views extending
111 * this class typically need to override the getColumnText method if they
112 * have more than one column to display. It also allows to change the font
113 * and colors of the cells.
114 */
115 protected static class TreeLabelProvider implements ITableLabelProvider, ITableFontProvider, ITableColorProvider {
116
117 @Override
118 public void addListener(ILabelProviderListener listener) {
119 }
120
121 @Override
122 public void dispose() {
123 }
124
125 @Override
126 public boolean isLabelProperty(Object element, String property) {
127 return false;
128 }
129
130 @Override
131 public void removeListener(ILabelProviderListener listener) {
132 }
133
134 @Override
135 public Image getColumnImage(Object element, int columnIndex) {
136 return null;
137 }
138
139 @Override
140 public String getColumnText(Object element, int columnIndex) {
141 if ((element instanceof ITmfTreeViewerEntry) && (columnIndex == 0)) {
142 ITmfTreeViewerEntry entry = (ITmfTreeViewerEntry) element;
143 return entry.getName();
144 }
145 return new String();
146 }
147
148 @Override
149 public Color getForeground(Object element, int columnIndex) {
150 return Display.getCurrent().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
151 }
152
153 @Override
154 public Color getBackground(Object element, int columnIndex) {
155 return Display.getCurrent().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
156 }
157
158 @Override
159 public Font getFont(Object element, int columnIndex) {
160 return null;
161 }
162
163 }
164
165 // ------------------------------------------------------------------------
166 // Constructors and initialization methods
167 // ------------------------------------------------------------------------
168
169 /**
170 * Constructor
171 *
172 * @param parent
173 * The parent composite that holds this viewer
174 * @param allowMultiSelect
175 * Whether multiple selections are allowed
176 */
177 public AbstractTmfTreeViewer(Composite parent, boolean allowMultiSelect) {
178 super(parent);
179
180 int flags = SWT.FULL_SELECTION | SWT.H_SCROLL;
181 if (allowMultiSelect) {
182 flags |= SWT.MULTI;
183 }
184
185 /* Build the tree viewer part of the view */
186 fTreeViewer = new TreeViewer(parent, flags);
187 fTreeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
188 final Tree tree = fTreeViewer.getTree();
189 tree.setHeaderVisible(true);
190 tree.setLinesVisible(true);
191 fTreeViewer.setContentProvider(new TreeContentProvider());
192 fTreeViewer.setLabelProvider(new TreeLabelProvider());
193 List<TmfTreeColumnData> columns = getColumnDataProvider().getColumnData();
194 this.setTreeColumns(columns);
195 }
196
197 /**
198 * Get the column data provider that will contain the list of columns to be
199 * part of this viewer. It is called once during the constructor.
200 *
201 * @return The tree column data provider for this viewer.
202 */
203 protected abstract ITmfTreeColumnDataProvider getColumnDataProvider();
204
205 /**
206 * Sets the tree columns for this tree viewer
207 *
208 * @param columns
209 * The tree column data
210 */
211 public void setTreeColumns(final List<TmfTreeColumnData> columns) {
212 boolean hasPercentProvider = false;
213 for (final TmfTreeColumnData columnData : columns) {
214 columnData.createColumn(fTreeViewer);
215 hasPercentProvider |= (columnData.getPercentageProvider() != null);
216 }
217
218 if (hasPercentProvider) {
219 /*
220 * Handler that will draw bar charts in the cell using a percentage
221 * value.
222 */
223 fTreeViewer.getTree().addListener(SWT.EraseItem, new Listener() {
224 @Override
225 public void handleEvent(Event event) {
226 if (columns.get(event.index).getPercentageProvider() != null) {
227
228 double percentage = columns.get(event.index).getPercentageProvider().getPercentage(event.item.getData());
229 if (percentage == 0) { // No bar to draw
230 return;
231 }
232
233 if ((event.detail & SWT.SELECTED) > 0) {
234 /*
235 * The item is selected. Draw our own background to
236 * avoid overwriting the bar.
237 */
238 event.gc.fillRectangle(event.x, event.y, event.width, event.height);
239 event.detail &= ~SWT.SELECTED;
240 }
241
242 int barWidth = (int) ((fTreeViewer.getTree().getColumn(event.index).getWidth() - 8) * percentage);
243 int oldAlpha = event.gc.getAlpha();
244 Color oldForeground = event.gc.getForeground();
245 Color oldBackground = event.gc.getBackground();
246 /*
247 * Draws a transparent gradient rectangle from the color
248 * of foreground and background.
249 */
250 event.gc.setAlpha(64);
251 event.gc.setForeground(event.item.getDisplay().getSystemColor(SWT.COLOR_BLUE));
252 event.gc.setBackground(event.item.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
253 event.gc.fillGradientRectangle(event.x, event.y, barWidth, event.height, true);
254 event.gc.drawRectangle(event.x, event.y, barWidth, event.height);
255 /* Restores old values */
256 event.gc.setForeground(oldForeground);
257 event.gc.setBackground(oldBackground);
258 event.gc.setAlpha(oldAlpha);
259 event.detail &= ~SWT.BACKGROUND;
260 }
261 }
262 });
263 }
264 }
265
266 /**
267 * Set the label provider that will fill the columns of the tree viewer
268 *
269 * @param labelProvider
270 * The label provider to fill the columns
271 */
272 protected void setLabelProvider(IBaseLabelProvider labelProvider) {
273 fTreeViewer.setLabelProvider(labelProvider);
274 }
275
276 /**
277 * Get the tree viewer object
278 *
279 * @return The tree viewer object displayed by this viewer
280 */
281 protected TreeViewer getTreeViewer() {
282 return fTreeViewer;
283 }
284
285 // ------------------------------------------------------------------------
286 // ITmfViewer
287 // ------------------------------------------------------------------------
288
289 @Override
290 public Control getControl() {
291 return fTreeViewer.getControl();
292 }
293
294 @Override
295 public void refresh() {
296 Tree tree = fTreeViewer.getTree();
297 tree.setRedraw(false);
298 fTreeViewer.refresh();
299 fTreeViewer.expandAll();
300 tree.setRedraw(true);
301 }
302
303 @Override
304 public void loadTrace(ITmfTrace trace) {
305 super.loadTrace(trace);
306 Thread thread = new Thread() {
307 @Override
308 public void run() {
309 initializeDataSource();
310 Display.getDefault().asyncExec(new Runnable() {
311 @Override
312 public void run() {
313 clearContent();
314 updateContent(getWindowStartTime(), getWindowEndTime(), false);
315 }
316 });
317 }
318 };
319 thread.start();
320 }
321
322 // ------------------------------------------------------------------------
323 // Operations
324 // ------------------------------------------------------------------------
325
326 /**
327 * Set the currently selected items in the treeviewer
328 *
329 * @param selection
330 * The list of selected items
331 */
332 public void setSelection(@NonNull List<ITmfTreeViewerEntry> selection) {
333 IStructuredSelection sel = new StructuredSelection(selection);
334 fTreeViewer.setSelection(sel, true);
335 }
336
337 /**
338 * Add a selection listener to the tree viewer. This will be called when the
339 * selection changes and contain all the selected items.
340 *
341 * The selection change listener can be used like this:
342 *
343 * <pre>
344 * getTreeViewer().addSelectionChangeListener(new ISelectionChangedListener() {
345 * &#064;Override
346 * public void selectionChanged(SelectionChangedEvent event) {
347 * if (event.getSelection() instanceof IStructuredSelection) {
348 * Object selection = ((IStructuredSelection) event.getSelection()).getFirstElement();
349 * if (selection instanceof ITmfTreeViewerEntry) {
350 * // Do something
351 * }
352 * }
353 * }
354 * });
355 * </pre>
356 *
357 * @param listener
358 * The {@link ISelectionChangedListener}
359 */
360 public void addSelectionChangeListener(ISelectionChangedListener listener) {
361 fTreeViewer.addSelectionChangedListener(listener);
362 }
363
364 /**
365 * Method called when the trace is loaded, to initialize any data once the
366 * trace has been set, but before the first call to update the content of
367 * the viewer.
368 */
369 protected void initializeDataSource() {
370
371 }
372
373 /**
374 * Clears the current content of the viewer.
375 */
376 protected void clearContent() {
377 fTreeViewer.setInput(null);
378 }
379
380 /**
381 * Method called after the content has been updated and the new input has
382 * been set on the tree.
383 *
384 * @param rootEntry
385 * The new input of this viewer, or null if none
386 */
387 protected void contentChanged(ITmfTreeViewerEntry rootEntry) {
388
389 }
390
391 /**
392 * Requests an update of the viewer's content in a given time range or
393 * selection time range. An extra parameter defines whether these times
394 * correspond to the selection or the visible range, as the viewer may
395 * update differently in those cases.
396 *
397 * @param start
398 * The start time of the requested content
399 * @param end
400 * The end time of the requested content
401 * @param isSelection
402 * <code>true</code> if this time range is for a selection,
403 * <code>false</code> for the visible time range
404 */
405 protected void updateContent(final long start, final long end, final boolean isSelection) {
406 Job thread = new Job("") { //$NON-NLS-1$
407 @Override
408 public IStatus run(IProgressMonitor monitor) {
409 final ITmfTreeViewerEntry rootEntry = updateElements(start, end, isSelection);
410 /* Set the input in main thread only if it didn't change */
411 if (rootEntry != null) {
412 Display.getDefault().asyncExec(new Runnable() {
413 @Override
414 public void run() {
415 if (fTreeViewer.getControl().isDisposed()) {
416 return;
417 }
418
419 if (rootEntry != fTreeViewer.getInput()) {
420 fTreeViewer.setInput(rootEntry);
421 contentChanged(rootEntry);
422 } else {
423 fTreeViewer.refresh();
424 fTreeViewer.expandToLevel(fTreeViewer.getAutoExpandLevel());
425 }
426 // FIXME should add a bit of padding
427 for (TreeColumn column : fTreeViewer.getTree().getColumns()) {
428 column.pack();
429 }
430 }
431 });
432 }
433 return Status.OK_STATUS;
434 }
435 };
436 thread.setSystem(true);
437 thread.schedule();
438 }
439
440 /**
441 * Update the entries to the given start/end time. An extra parameter
442 * defines whether these times correspond to the selection or the visible
443 * range, as the viewer may update differently in those cases. This methods
444 * returns a root node that is not meant to be visible. The children of this
445 * 'fake' root node are the first level of entries that will appear in the
446 * tree. If no update is necessary, the method should return
447 * <code>null</code>. To empty the tree, a root node containing an empty
448 * list of children should be returned.
449 *
450 * This method is not called in the UI thread when using the default viewer
451 * content update. Resource-intensive calculations here should not block the
452 * UI.
453 *
454 * @param start
455 * The start time of the requested content
456 * @param end
457 * The end time of the requested content
458 * @param isSelection
459 * <code>true</code> if this time range is for a selection,
460 * <code>false</code> for the visible time range
461 * @return The root entry of the list of entries to display or
462 * <code>null</code> if no update necessary
463 */
464 protected abstract ITmfTreeViewerEntry updateElements(long start, long end, boolean isSelection);
465
466 /**
467 * Get the current input displayed by the viewer
468 *
469 * @return The input of the tree viewer, the root entry
470 */
471 protected ITmfTreeViewerEntry getInput() {
472 return (ITmfTreeViewerEntry) fTreeViewer.getInput();
473 }
474
475 // ------------------------------------------------------------------------
476 // Signal Handler
477 // ------------------------------------------------------------------------
478
479 /**
480 * Signal handler for handling of the time synch signal. The times
481 * correspond to the selection by the user, not the visible time range.
482 *
483 * @param signal
484 * The time synch signal {@link TmfSelectionRangeUpdatedSignal}
485 */
486 @Override
487 @TmfSignalHandler
488 public void selectionRangeUpdated(TmfSelectionRangeUpdatedSignal signal) {
489 super.selectionRangeUpdated(signal);
490 if ((signal.getSource() != this) && (getTrace() != null)) {
491 updateContent(this.getSelectionBeginTime(), this.getSelectionEndTime(), true);
492 }
493 }
494
495 /**
496 * Signal handler for handling of the window range signal. This time range
497 * is the visible zone of the view.
498 *
499 * @param signal
500 * The {@link TmfWindowRangeUpdatedSignal}
501 */
502 @Override
503 @TmfSignalHandler
504 public void windowRangeUpdated(TmfWindowRangeUpdatedSignal signal) {
505 super.windowRangeUpdated(signal);
506 updateContent(this.getWindowStartTime(), this.getWindowEndTime(), false);
507 }
508
509 @Override
510 public void reset() {
511 super.reset();
512 clearContent();
513 }
514
515 }
This page took 0.041686 seconds and 4 git commands to generate.