--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2014 École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Geneviève Bastien - Initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.ui.viewers.tree;
+
+import java.util.List;
+
+import org.eclipse.jface.viewers.AbstractTreeViewer;
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.ITableColorProvider;
+import org.eclipse.jface.viewers.ITableFontProvider;
+import org.eclipse.jface.viewers.ITableLabelProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.linuxtools.tmf.core.signal.TmfRangeSynchSignal;
+import org.eclipse.linuxtools.tmf.core.signal.TmfSignalHandler;
+import org.eclipse.linuxtools.tmf.core.signal.TmfTimeSynchSignal;
+import org.eclipse.linuxtools.tmf.core.trace.ITmfTrace;
+import org.eclipse.linuxtools.tmf.ui.viewers.TmfTimeViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+
+/**
+ * Abstract class for viewers who will display data using a TreeViewer. It
+ * automatically synchronizes with time information of the UI. It also
+ * implements some common functionalities for all tree viewer, such as managing
+ * the column data, content initialization and update. The viewer implementing
+ * this does not have to worry about whether some code runs in the UI thread or
+ * not.
+ *
+ * @author Geneviève Bastien
+ * @since 3.0
+ */
+public abstract class AbstractTmfTreeViewer extends TmfTimeViewer {
+
+ private final TreeViewer fTreeViewer;
+
+ // ------------------------------------------------------------------------
+ // Internal classes
+ // ------------------------------------------------------------------------
+
+ /* The elements of the tree viewer are of type ITmfTreeViewerEntry */
+ private class TreeContentProvider implements ITreeContentProvider {
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ if (inputElement != null) {
+ try {
+ return ((List<?>) inputElement).toArray(new ITmfTreeViewerEntry[0]);
+ } catch (ClassCastException e) {
+ }
+ }
+ return new ITmfTreeViewerEntry[0];
+ }
+
+ @Override
+ public Object[] getChildren(Object parentElement) {
+ ITmfTreeViewerEntry entry = (ITmfTreeViewerEntry) parentElement;
+ List<? extends ITmfTreeViewerEntry> children = entry.getChildren();
+ return children.toArray(new ITmfTreeViewerEntry[children.size()]);
+ }
+
+ @Override
+ public Object getParent(Object element) {
+ ITmfTreeViewerEntry entry = (ITmfTreeViewerEntry) element;
+ return entry.getParent();
+ }
+
+ @Override
+ public boolean hasChildren(Object element) {
+ ITmfTreeViewerEntry entry = (ITmfTreeViewerEntry) element;
+ return entry.hasChildren();
+ }
+
+ }
+
+ /**
+ * Base class to provide the labels for the tree viewer. Views extending
+ * this class typically need to override the getColumnText method if they
+ * have more than one column to display. It also allows to change the font
+ * and colors of the cells.
+ */
+ protected static class TreeLabelProvider implements ITableLabelProvider, ITableFontProvider, ITableColorProvider {
+
+ @Override
+ public void addListener(ILabelProviderListener listener) {
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public boolean isLabelProperty(Object element, String property) {
+ return false;
+ }
+
+ @Override
+ public void removeListener(ILabelProviderListener listener) {
+ }
+
+ @Override
+ public Image getColumnImage(Object element, int columnIndex) {
+ return null;
+ }
+
+ @Override
+ public String getColumnText(Object element, int columnIndex) {
+ if ((element instanceof ITmfTreeViewerEntry) && (columnIndex == 0)) {
+ ITmfTreeViewerEntry entry = (ITmfTreeViewerEntry) element;
+ return entry.getName();
+ }
+ return new String();
+ }
+
+ @Override
+ public Color getForeground(Object element, int columnIndex) {
+ return Display.getCurrent().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
+ }
+
+ @Override
+ public Color getBackground(Object element, int columnIndex) {
+ return Display.getCurrent().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
+ }
+
+ @Override
+ public Font getFont(Object element, int columnIndex) {
+ return null;
+ }
+
+ }
+
+ // ------------------------------------------------------------------------
+ // Constructors and initialization methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructor
+ *
+ * @param parent
+ * The parent composite that holds this viewer
+ * @param allowMultiSelect
+ * Whether multiple selections are allowed
+ */
+ public AbstractTmfTreeViewer(Composite parent, boolean allowMultiSelect) {
+ super(parent);
+
+ int flags = SWT.FULL_SELECTION | SWT.H_SCROLL;
+ if (allowMultiSelect) {
+ flags |= SWT.MULTI;
+ }
+
+ /* Build the tree viewer part of the view */
+ fTreeViewer = new TreeViewer(parent, flags);
+ fTreeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
+ final Tree tree = fTreeViewer.getTree();
+ tree.setHeaderVisible(true);
+ tree.setLinesVisible(true);
+ fTreeViewer.setContentProvider(new TreeContentProvider());
+ fTreeViewer.setLabelProvider(new TreeLabelProvider());
+ List<TmfTreeColumnData> columns = getColumnDataProvider().getColumnData();
+ this.setTreeColumns(columns);
+ }
+
+ /**
+ * Get the column data provider that will contain the list of columns to be
+ * part of this viewer. It is called once during the constructor.
+ *
+ * @return The tree column data provider for this viewer.
+ */
+ protected abstract ITmfTreeColumnDataProvider getColumnDataProvider();
+
+ /**
+ * Sets the tree columns for this tree viewer
+ *
+ * @param columns
+ * The tree column data
+ */
+ public void setTreeColumns(final List<TmfTreeColumnData> columns) {
+ boolean hasPercentProvider = false;
+ for (final TmfTreeColumnData columnData : columns) {
+ columnData.createColumn(fTreeViewer);
+ hasPercentProvider |= (columnData.getPercentageProvider() != null);
+ }
+
+ if (hasPercentProvider) {
+ /*
+ * Handler that will draw bar charts in the cell using a percentage
+ * value.
+ */
+ fTreeViewer.getTree().addListener(SWT.EraseItem, new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ if (columns.get(event.index).getPercentageProvider() != null) {
+
+ double percentage = columns.get(event.index).getPercentageProvider().getPercentage(event.item.getData());
+ if (percentage == 0) { // No bar to draw
+ return;
+ }
+
+ if ((event.detail & SWT.SELECTED) > 0) {
+ /*
+ * The item is selected. Draw our own background to
+ * avoid overwriting the bar.
+ */
+ event.gc.fillRectangle(event.x, event.y, event.width, event.height);
+ event.detail &= ~SWT.SELECTED;
+ }
+
+ int barWidth = (int) ((fTreeViewer.getTree().getColumn(event.index).getWidth() - 8) * percentage);
+ int oldAlpha = event.gc.getAlpha();
+ Color oldForeground = event.gc.getForeground();
+ Color oldBackground = event.gc.getBackground();
+ /*
+ * Draws a transparent gradient rectangle from the color
+ * of foreground and background.
+ */
+ event.gc.setAlpha(64);
+ event.gc.setForeground(event.item.getDisplay().getSystemColor(SWT.COLOR_BLUE));
+ event.gc.setBackground(event.item.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
+ event.gc.fillGradientRectangle(event.x, event.y, barWidth, event.height, true);
+ event.gc.drawRectangle(event.x, event.y, barWidth, event.height);
+ /* Restores old values */
+ event.gc.setForeground(oldForeground);
+ event.gc.setBackground(oldBackground);
+ event.gc.setAlpha(oldAlpha);
+ event.detail &= ~SWT.BACKGROUND;
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Set the label provider that will fill the columns of the tree viewer
+ *
+ * @param labelProvider
+ * The label provider to fill the columns
+ */
+ protected void setLabelProvider(IBaseLabelProvider labelProvider) {
+ fTreeViewer.setLabelProvider(labelProvider);
+ }
+
+ /**
+ * Get the tree viewer object
+ *
+ * @return The tree viewer object displayed by this viewer
+ */
+ protected TreeViewer getTreeViewer() {
+ return fTreeViewer;
+ }
+
+ // ------------------------------------------------------------------------
+ // ITmfViewer
+ // ------------------------------------------------------------------------
+
+ @Override
+ public Control getControl() {
+ return fTreeViewer.getControl();
+ }
+
+ @Override
+ public void refresh() {
+ Tree tree = fTreeViewer.getTree();
+ tree.setRedraw(false);
+ fTreeViewer.refresh();
+ fTreeViewer.expandAll();
+ tree.setRedraw(true);
+ }
+
+ @Override
+ public void loadTrace(ITmfTrace trace) {
+ super.loadTrace(trace);
+ Thread thread = new Thread() {
+ @Override
+ public void run() {
+ initializeDataSource();
+ Display.getDefault().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ clearContent();
+ updateContent(getWindowStartTime(), getWindowEndTime(), false);
+ }
+ });
+ }
+ };
+ thread.start();
+ }
+
+ // ------------------------------------------------------------------------
+ // Operations
+ // ------------------------------------------------------------------------
+
+ /**
+ * Add a selection listener to the tree viewer. This will be called when the
+ * selection changes and contain all the selected items.
+ *
+ * The selection change listener can be used like this:
+ *
+ * <pre>
+ * getTreeViewer().addSelectionChangeListener(new ISelectionChangedListener() {
+ * @Override
+ * public void selectionChanged(SelectionChangedEvent event) {
+ * if (event.getSelection() instanceof IStructuredSelection) {
+ * Object selection = ((IStructuredSelection) event.getSelection()).getFirstElement();
+ * if (selection instanceof ITmfTreeViewerEntry) {
+ * // Do something
+ * }
+ * }
+ * }
+ * });
+ * </pre>
+ *
+ * @param listener
+ * The {@link ISelectionChangedListener}
+ */
+ public void addSelectionChangeListener(ISelectionChangedListener listener) {
+ fTreeViewer.addSelectionChangedListener(listener);
+ }
+
+ /**
+ * Method called when the trace is loaded, to initialize any data once the
+ * trace has been set, but before the first call to update the content of
+ * the viewer.
+ */
+ protected void initializeDataSource() {
+
+ }
+
+ /**
+ * Clears the current content of the viewer.
+ */
+ protected void clearContent() {
+ fTreeViewer.setInput(null);
+ }
+
+ /**
+ * Requests an update of the viewer's content in a given time range or
+ * selection time range. An extra parameter defines whether these times
+ * correspond to the selection or the visible range, as the viewer may
+ * update differently in those cases.
+ *
+ * @param start
+ * The start time of the requested content
+ * @param end
+ * The end time of the requested content
+ * @param isSelection
+ * <code>true</code> if this time range is for a selection,
+ * <code>false</code> for the visible time range
+ */
+ protected void updateContent(final long start, final long end, final boolean isSelection) {
+ Thread thread = new Thread() {
+ @Override
+ public void run() {
+ final List<ITmfTreeViewerEntry> entries = updateElements(start, end, isSelection);
+ /* Set the input in main thread only if it didn't change */
+ if (entries != null) {
+ Display.getDefault().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ if (entries != fTreeViewer.getInput()) {
+ fTreeViewer.setInput(entries);
+ } else {
+ fTreeViewer.refresh();
+ }
+ // FIXME should add a bit of padding
+ for (TreeColumn column : fTreeViewer.getTree().getColumns()) {
+ column.pack();
+ }
+ }
+ });
+ }
+ }
+ };
+ thread.start();
+ }
+
+ /**
+ * Update the entries to the given start/end time. An extra parameter
+ * defines whether these times correspond to the selection or the visible
+ * range, as the viewer may update differently in those cases. If no update
+ * is necessary, the method should return <code>null</code>. To empty the
+ * tree, an empty list should be returned.
+ *
+ * This method is not called in the UI thread when using the default viewer
+ * content update. Resource-intensive calculations here should not block the
+ * UI.
+ *
+ * @param start
+ * The start time of the requested content
+ * @param end
+ * The end time of the requested content
+ * @param isSelection
+ * <code>true</code> if this time range is for a selection,
+ * <code>false</code> for the visible time range
+ * @return The list of entries to display or <code>null</code> if no update
+ * necessary
+ */
+ protected abstract List<ITmfTreeViewerEntry> updateElements(long start, long end, boolean isSelection);
+
+ /**
+ * Get the current input displayed by the viewer
+ *
+ * @return The input of the tree viewer
+ */
+ protected Object getInput() {
+ return fTreeViewer.getInput();
+ }
+
+ // ------------------------------------------------------------------------
+ // Signal Handler
+ // ------------------------------------------------------------------------
+
+ /**
+ * Signal handler for handling of the time synch signal. The times
+ * correspond to the selection by the user, not the visible time range.
+ *
+ * @param signal
+ * The time synch signal {@link TmfTimeSynchSignal}
+ */
+ @Override
+ @TmfSignalHandler
+ public void selectionRangeUpdated(TmfTimeSynchSignal signal) {
+ super.selectionRangeUpdated(signal);
+ if ((signal.getSource() != this) && (getTrace() != null)) {
+ updateContent(this.getSelectionBeginTime(), this.getSelectionEndTime(), true);
+ }
+ }
+
+ /**
+ * Signal handler for handling of the time range synch signal. This time
+ * range is the visible zone of the view.
+ *
+ * @param signal
+ * The time range synch signal {@link TmfRangeSynchSignal}
+ */
+ @Override
+ @TmfSignalHandler
+ public void timeRangeUpdated(TmfRangeSynchSignal signal) {
+ super.timeRangeUpdated(signal);
+ updateContent(this.getWindowStartTime(), this.getWindowEndTime(), false);
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2011, 2014 Ericsson, École Polytechnique de Montréal
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Mathieu Denis - Initial Implementation and API (in TmfBaseColumnData of
+ * statistics framework)
+ * Bernd Hufmann - Added Annotations
+ * Geneviève Bastien - Moved TmfBaseColumnData to this class and adapted
+ * it for the abstract tree viewer
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.ui.viewers.tree;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.TreeViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerComparator;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.widgets.Tree;
+
+/**
+ * Represents a column in an abstract tree viewer. It allows to define the
+ * column's characteristics: text, width, alignment, tooltip, comparators,
+ * percent providers, whether the column is movable, etc.
+ *
+ * @author Geneviève Bastien
+ * @since 3.0
+ */
+public class TmfTreeColumnData {
+ /** Name of the column. */
+ private final String fText;
+ /** Width of the column. */
+ private int fWidth = -1;
+ /** Alignment of the column. */
+ private int fAlignment = SWT.LEAD;
+ /** Tooltip of the column. */
+ private String fTooltip = null;
+ /** Used to sort elements of this column. If null, column is not sortable. */
+ private ViewerComparator fComparator = null;
+ /** Whether the column is movable */
+ private boolean fMovable = false;
+ /** Used to draw bar charts in this column. Can be null. */
+ private ITmfColumnPercentageProvider fPercentageProvider = null;
+
+ /** Used to draw bar charts in columns. */
+ public interface ITmfColumnPercentageProvider {
+
+ /**
+ * Percentage provider. Returns a percentage (between 0 and 100) from
+ * the given object. The object is usually an entry (a line of the tree
+ * viewer).
+ *
+ * @param data
+ * The data object corresponding to a line in the tree.
+ * @return The value as a percentage (between 0 and 100)
+ */
+ public double getPercentage(Object data);
+ }
+
+ /**
+ * Constructor with parameters
+ *
+ * @param text
+ * Text of the column. The name will be shown at the top of the
+ * column.
+ */
+ public TmfTreeColumnData(String text) {
+ fText = text;
+ }
+
+ /**
+ * Get the header text of a column
+ *
+ * @return The header text
+ */
+ public String getText() {
+ return fText;
+ }
+
+ /**
+ * Get the width of the column
+ *
+ * @return The column width
+ */
+ public int getWidth() {
+ return fWidth;
+ }
+
+ /**
+ * Get the alignment of the column
+ *
+ * @return The alignment (for example SWT.LEAD, SWT.RIGHT, etc)
+ */
+ public int getAlignment() {
+ return fAlignment;
+ }
+
+ /**
+ * Get the tooltip text to go with this column
+ *
+ * @return The tooltip text
+ */
+ public String getTooltip() {
+ return fTooltip;
+ }
+
+ /**
+ * Get the comparator used to sort columns. If <code>null</code>, then the
+ * column is not sortable
+ *
+ * @return The column comparator
+ */
+ public ViewerComparator getComparator() {
+ return fComparator;
+ }
+
+ /**
+ * Get the percentage provider for this column. This will allow to draw a
+ * bar chart inside the cells of this columns
+ *
+ * @return The percentage provider
+ */
+ public ITmfColumnPercentageProvider getPercentageProvider() {
+ return fPercentageProvider;
+ }
+
+ /**
+ * Return whether the column is movable or not
+ *
+ * @return True if column can be moved, false otherwise.
+ */
+ public boolean isMovable() {
+ return fMovable;
+ }
+
+ /**
+ * Set the width of the column. If not set, -1 is used.
+ *
+ * @param width
+ * Width of the column. Use -1 for tree viewer's default
+ * behavior.
+ */
+ public void setWidth(int width) {
+ fWidth = width;
+ }
+
+ /**
+ * Set the alignment of this column. If not set, default value is SWT.LEAD.
+ *
+ * @param alignment
+ * Alignment of the column. For example, SWT.LEAD, SWT.RIGHT,
+ * SWT.LEFT
+ */
+ public void setAlignment(int alignment) {
+ fAlignment = alignment;
+ }
+
+ /**
+ * Set the tooltip associated with this column
+ *
+ * @param tooltip
+ * the tooltip text
+ */
+ public void setTooltip(String tooltip) {
+ fTooltip = tooltip;
+ }
+
+ /**
+ * Set the comparator used to sort the column
+ *
+ * @param comparator
+ * The comparator. Use <code>null</code> to not sort the column.
+ */
+ public void setComparator(ViewerComparator comparator) {
+ fComparator = comparator;
+ }
+
+ /**
+ * Set the percentage provider that will provide a percentage value to draw
+ * a bar chart inside the cells of this column
+ *
+ * @param percentProvider
+ * The percentage provider
+ */
+ public void setPercentageProvider(ITmfColumnPercentageProvider percentProvider) {
+ fPercentageProvider = percentProvider;
+ }
+
+ /**
+ * Set whether the column can be moved in the tree viewer. Default is false.
+ *
+ * @param movable
+ * true if the column can be moved, false otherwise
+ */
+ public void setMovable(boolean movable) {
+ fMovable = movable;
+ }
+
+ /**
+ * Create a TreeColumn with this column's data and adds it to a {@link Tree}
+ *
+ * @param treeViewer
+ * The {@link TreeViewer} object to add the column to
+ * @return The newly created {@link TreeViewerColumn}
+ */
+ @NonNull
+ public TreeViewerColumn createColumn(final TreeViewer treeViewer) {
+ final TreeViewerColumn column = new TreeViewerColumn(treeViewer, getAlignment());
+ final TmfTreeColumnData columnData = this;
+ column.getColumn().setText(getText());
+ if (getWidth() != -1) {
+ column.getColumn().setWidth(getWidth());
+ }
+ if (getTooltip() != null) {
+ column.getColumn().setToolTipText(getTooltip());
+ }
+ column.getColumn().setMoveable(isMovable());
+
+ /* Add the comparator to sort the column */
+ if (getComparator() != null) {
+ column.getColumn().addSelectionListener(new SelectionAdapter() {
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+
+ if (treeViewer.getTree().getSortDirection() == SWT.UP || treeViewer.getTree().getSortColumn() != column.getColumn()) {
+ /*
+ * Puts the descendant order if the old order was up
+ * or if the selected column has changed.
+ */
+ treeViewer.setComparator(columnData.getComparator());
+ treeViewer.getTree().setSortDirection(SWT.DOWN);
+ } else {
+ ViewerComparator reverseComparator;
+ /* Initializes the reverse comparator. */
+ reverseComparator = new ViewerComparator() {
+ @Override
+ public int compare(Viewer viewer, Object e1, Object
+ e2) {
+ return -1 * columnData.getComparator().compare(viewer, e1, e2);
+ }
+ };
+
+ /*
+ * Puts the ascendant ordering if the selected
+ * column hasn't changed.
+ */
+ treeViewer.setComparator(reverseComparator);
+ treeViewer.getTree().setSortDirection(SWT.UP);
+ }
+ treeViewer.getTree().setSortColumn(column.getColumn());
+ }
+ });
+ }
+
+ return column;
+ }
+}