From: Alexandre Montplaisir Date: Thu, 5 May 2016 01:05:56 +0000 (-0400) Subject: analysis.lami: Show one view per report X-Git-Url: http://git.efficios.com/?a=commitdiff_plain;h=f95c93454d70abf864521c8b9415528f45da30bc;p=deliverable%2Ftracecompass.git analysis.lami: Show one view per report Multiple tables in a report will be shown as multiple tabs in the view. Each tab will have its own graphs. This will allow naming the views with the report names, so it will be easier to match a given view to its report. Change-Id: Ieeef337079d385dfd79f3cc26a3574b800e7754c Signed-off-by: Alexandre Montplaisir Reviewed-on: https://git.eclipse.org/r/72243 Reviewed-by: Patrick Tasse Tested-by: Patrick Tasse Reviewed-by: Hudson CI --- diff --git a/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/handler/OpenReportHandler.java b/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/handler/OpenReportHandler.java index 9bceec72e6..5dec51f923 100644 --- a/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/handler/OpenReportHandler.java +++ b/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/handler/OpenReportHandler.java @@ -57,7 +57,7 @@ public class OpenReportHandler extends AbstractHandler { Display.getDefault().syncExec(() -> { try { - LamiReportViewFactory.createNewViews(lamiReport); + LamiReportViewFactory.createNewView(lamiReport); } catch (PartInitException e) { } }); diff --git a/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/handler/RunAnalysisHandler.java b/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/handler/RunAnalysisHandler.java index 10458b1793..e99c7cd605 100644 --- a/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/handler/RunAnalysisHandler.java +++ b/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/handler/RunAnalysisHandler.java @@ -131,7 +131,7 @@ public class RunAnalysisHandler extends AbstractHandler { /* Automatically open the report for convenience */ Display.getDefault().syncExec(() -> { try { - LamiReportViewFactory.createNewViews(report); + LamiReportViewFactory.createNewView(report); } catch (PartInitException e) { } }); diff --git a/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/viewers/LamiTableViewer.java b/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/viewers/LamiTableViewer.java index 6bc712d05f..0c745961fe 100644 --- a/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/viewers/LamiTableViewer.java +++ b/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/viewers/LamiTableViewer.java @@ -194,6 +194,10 @@ public final class LamiTableViewer extends TmfSimpleTableViewer implements ILami } }); Display.getDefault().asyncExec(() -> { + if (tableViewer.getTable().isDisposed()) { + return; + } + TableColumn[] cols = tableViewer.getTable().getColumns(); for (int i = 0; i < cols.length; i++) { cols[i].pack(); diff --git a/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/views/LamiReportView.java b/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/views/LamiReportView.java index ad7e68db50..ed38dd6c26 100644 --- a/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/views/LamiReportView.java +++ b/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/views/LamiReportView.java @@ -10,52 +10,27 @@ package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views; import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; -import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.Separator; -import org.eclipse.jface.viewers.ArrayContentProvider; -import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.LabelProvider; -import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CTabFolder; +import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiEmptyAspect; -import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect; -import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel; +import org.eclipse.tracecompass.internal.analysis.lami.ui.Activator; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiAnalysisReport; import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel.ChartType; import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable; -import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry; -import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiXYSeriesDescription; -import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimeRange; -import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.signals.LamiSelectionUpdateSignal; -import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal; -import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler; -import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager; -import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp; -import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; -import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; -import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; import org.eclipse.tracecompass.tmf.ui.views.TmfView; -import com.google.common.collect.Iterables; - /** * Base view showing output of Babeltrace scripts. * @@ -73,13 +48,43 @@ public final class LamiReportView extends TmfView { /** View ID */ public static final String VIEW_ID = "org.eclipse.tracecompass.analysis.lami.views.reportview"; //$NON-NLS-1$ - private final @Nullable LamiResultTable fResultTable; + private final @Nullable LamiAnalysisReport fReport; + private final List fTabPages; + + private @Nullable CTabFolder fTabFolder; + + // ------------------------------------------------------------------------ + // Actions + // ------------------------------------------------------------------------ + + private class ToggleTableAction extends Action { + @Override + public void run() { + LamiReportViewTabPage page = getCurrentSelectedPage(); + if (page == null) { + return; + } + page.toggleTableViewer(); + } + } + + private class NewChartAction extends Action { + + private final ChartType fChartType; - private @Nullable LamiViewerControl fTableViewerControl; - private final Set fPredefGraphViewerControls = new LinkedHashSet<>(); - private final Set fCustomGraphViewerControls = new LinkedHashSet<>(); - private @Nullable SashForm fSashForm; - private Set fSelectionIndexes; + public NewChartAction(ChartType chartType) { + fChartType = chartType; + } + + @Override + public void run() { + LamiReportViewTabPage page = getCurrentSelectedPage(); + if (page == null) { + return; + } + page.createNewCustomChart(fChartType); + } + } // ------------------------------------------------------------------------ // Constructor @@ -90,11 +95,8 @@ public final class LamiReportView extends TmfView { */ public LamiReportView() { super(VIEW_ID); - fResultTable = LamiReportViewFactory.getCurrentResultTable(); - fSelectionIndexes = new HashSet<>(); - if (fResultTable != null) { - fSelectionIndexes = getIndexOfEntriesIntersectingTimerange(checkNotNull(fResultTable), TmfTraceManager.getInstance().getCurrentTraceContext().getSelectionRange()); - } + fReport = LamiReportViewFactory.getCurrentReport(); + fTabPages = new ArrayList<>(); } // ------------------------------------------------------------------------ @@ -103,36 +105,39 @@ public final class LamiReportView extends TmfView { @Override public void createPartControl(@Nullable Composite parent) { - LamiResultTable resultTable = fResultTable; - if (resultTable == null || parent == null) { + LamiAnalysisReport report = fReport; + if (report == null || parent == null) { return; } - SashForm sf = new SashForm(parent, SWT.NONE); - fSashForm = sf; - setPartName(resultTable.getTableClass().getTableTitle()); + setPartName(report.getName()); - /* Prepare the table viewer, which is always present */ - LamiViewerControl tableViewerControl = new LamiViewerControl(sf, resultTable); - fTableViewerControl = tableViewerControl; + fTabFolder = new CTabFolder(parent, SWT.NONE); + fTabFolder.setSimple(false); - /* Prepare the predefined graph viewers, if any */ - resultTable.getTableClass().getPredefinedViews() - .forEach(graphModel -> fPredefGraphViewerControls.add(new LamiViewerControl(sf, resultTable, graphModel))); + for (LamiResultTable table : report.getTables()) { + String name = table.getTableClass().getTableTitle(); - /* Automatically open the table viewer initially */ - tableViewerControl.getToggleAction().run(); + CTabItem tabItem = new CTabItem(fTabFolder, SWT.NULL); + tabItem.setText(name); + + SashForm sf = new SashForm(fTabFolder, SWT.NONE); + fTabPages.add(new LamiReportViewTabPage(sf, table)); + tabItem.setControl(sf); + } /* Add toolbar buttons */ + Action toggleTableAction = new ToggleTableAction(); + toggleTableAction.setText(Messages.LamiReportView_ActivateTableAction_ButtonName); + toggleTableAction.setToolTipText(Messages.LamiReportView_ActivateTableAction_ButtonTooltip); + toggleTableAction.setImageDescriptor(Activator.getDefault().getImageDescripterFromPath("icons/table.gif")); //$NON-NLS-1$ + IToolBarManager toolbarMgr = getViewSite().getActionBars().getToolBarManager(); - toolbarMgr.add(tableViewerControl.getToggleAction()); - fPredefGraphViewerControls.stream() - .map(LamiViewerControl::getToggleAction) - .forEach(toolbarMgr::add); + toolbarMgr.add(toggleTableAction); IMenuManager menuMgr = getViewSite().getActionBars().getMenuManager(); - IAction newBarChartAction = new NewChartAction(checkNotNull(parent.getShell()), sf, resultTable, ChartType.BAR_CHART); - IAction newXYScatterAction = new NewChartAction(checkNotNull(parent.getShell()), sf, resultTable, ChartType.XY_SCATTER); + IAction newBarChartAction = new NewChartAction(ChartType.BAR_CHART); + IAction newXYScatterAction = new NewChartAction(ChartType.XY_SCATTER); newBarChartAction.setText(Messages.LamiReportView_NewCustomBarChart); newXYScatterAction.setText(Messages.LamiReportView_NewCustomScatterChart); @@ -141,10 +146,12 @@ public final class LamiReportView extends TmfView { IAction clearCustomViewsAction = new Action() { @Override public void run() { - fCustomGraphViewerControls.forEach(LamiViewerControl::dispose); - fCustomGraphViewerControls.clear(); - sf.layout(); - + LamiReportViewTabPage tabPage = getCurrentSelectedPage(); + if (tabPage == null) { + return; + } + tabPage.clearAllCustomViewers(); + tabPage.getControl().layout(); } }; clearCustomViewsAction.setText(Messages.LamiReportView_ClearAllCustomViews); @@ -154,9 +161,11 @@ public final class LamiReportView extends TmfView { menuMgr.add(new Separator()); menuMgr.add(clearCustomViewsAction); - /* Simulate a new external signal to the default viewer */ - LamiSelectionUpdateSignal signal = new LamiSelectionUpdateSignal(LamiReportView.this, fSelectionIndexes, checkNotNull(fResultTable).hashCode()); - TmfSignalManager.dispatchSignal(signal); + /* Select the first tab initially */ + CTabFolder tf = checkNotNull(fTabFolder); + if (tf.getItemCount() > 0) { + tf.setSelection(0); + } } // ------------------------------------------------------------------------ @@ -165,292 +174,18 @@ public final class LamiReportView extends TmfView { @Override public void setFocus() { - } - - @Override - public void dispose() { - super.dispose(); - if (fSashForm != null) { - fSashForm.dispose(); + if (fTabFolder != null) { + fTabFolder.setFocus(); } - if (fTableViewerControl != null) { - fTableViewerControl.dispose(); - } - fPredefGraphViewerControls.forEach(LamiViewerControl::dispose); - fCustomGraphViewerControls.forEach(LamiViewerControl::dispose); } - private class NewChartAction extends Action { - - private final Shell icfDialogParentShell; - private final Composite icfChartViewerParent; - private final LamiResultTable icfResultTable; - private boolean icfXLogScale; - private boolean icfYLogScale; - private final ChartType icfChartType; - - public NewChartAction(Shell parentShell, Composite chartViewerParent, - LamiResultTable resultTable, ChartType chartType) { - icfDialogParentShell = parentShell; - icfChartViewerParent = chartViewerParent; - icfResultTable = resultTable; - icfXLogScale = false; - icfYLogScale = false; - icfChartType = chartType; - } - - @Override - public void run() { - int xLogScaleOptionIndex = -1; - int yLogScaleOptionIndex = -1; - - List xStringColumn = icfResultTable.getTableClass().getAspects().stream() - .filter(aspect -> !(aspect instanceof LamiEmptyAspect)) - .collect(Collectors.toList()); - - /* Get the flattened aspects for Y since mapping an aggregate aspect to y series make no sense so far */ - List yStringColumn = icfResultTable.getTableClass().getAspects().stream() - .filter(aspect -> !(aspect instanceof LamiEmptyAspect)) - .collect(Collectors.toList()); - - switch (icfChartType) { - case BAR_CHART: - /* Y value must strictly continous and non timestamp */ - yStringColumn = yStringColumn.stream() - .filter(aspect -> !aspect.isTimeStamp() && aspect.isContinuous()) - .collect(Collectors.toList()); - break; - case PIE_CHART: - break; - case XY_SCATTER: - break; - default: - break; - } - - IStructuredContentProvider contentProvider = checkNotNull(ArrayContentProvider.getInstance()); - - LamiSeriesDialog dialog = new LamiSeriesDialog(icfDialogParentShell, - icfChartType, - xStringColumn, - yStringColumn, - contentProvider, - new LabelProvider() { - @Override - public String getText(@Nullable Object element) { - return ((LamiTableEntryAspect) checkNotNull(element)).getLabel(); - } - }, - contentProvider, - new LabelProvider() { - @Override - public String getText(@Nullable Object element) { - return ((LamiTableEntryAspect) checkNotNull(element)).getLabel(); - } - }); - dialog.setTitle(icfChartType.toString() + ' ' + Messages.LamiSeriesDialog_creation); - - /* X options per chart type */ - switch (icfChartType) { - case XY_SCATTER: - xLogScaleOptionIndex = dialog.addXCheckBoxOption( - Messages.LamiSeriesDialog_x_axis + ' ' + Messages.LamiReportView_LogScale, - false, new Predicate() { - @Override - public boolean test(@NonNull LamiTableEntryAspect t) { - return t.isContinuous() && !t.isTimeStamp(); - } - }); - break; - case BAR_CHART: - case PIE_CHART: - default: - break; - } - - /* Y options per chart type */ - switch (icfChartType) { - case BAR_CHART: - case XY_SCATTER: - yLogScaleOptionIndex = dialog.addYCheckBoxOption( - Messages.LamiSeriesDialog_y_axis + ' ' + Messages.LamiReportView_LogScale, - false, new Predicate() { - @Override - public boolean test(@NonNull LamiTableEntryAspect t) { - return t.isContinuous() && !t.isTimeStamp(); - } - }); - break; - - case PIE_CHART: - default: - break; - } - - if (dialog.open() != Window.OK) { - return; - } - - List results = Arrays.stream(dialog.getResult()) - .map(serie -> (LamiXYSeriesDescription) serie) - .collect(Collectors.toList()); - - boolean[] xCheckBoxOptionsResults = dialog.getXCheckBoxOptionValues(); - boolean[] yCheckBoxOptionsResults = dialog.getYCheckBoxOptionValues(); - - /* Get X log scale option */ - if (xLogScaleOptionIndex > -1 && xLogScaleOptionIndex < xCheckBoxOptionsResults.length) { - icfXLogScale = xCheckBoxOptionsResults[xLogScaleOptionIndex]; - } - /* Get Y log scale option */ - if (yLogScaleOptionIndex > -1 && yLogScaleOptionIndex < yCheckBoxOptionsResults.length) { - icfYLogScale = yCheckBoxOptionsResults[yLogScaleOptionIndex]; - } - - List xAxisColString = new ArrayList<>(); - List yAxisColString = new ArrayList<>(); - - /* Specific chart type result fetching */ - switch (icfChartType) { - case PIE_CHART: - case BAR_CHART: - /* Validate that we only have 1 X aspect */ - if (results.stream() - .map(element -> element.getXAspect().getLabel()) - .distinct() - .count() != 1) { - throw new IllegalStateException("No unique X axis label for results"); //$NON-NLS-1$ - } - xAxisColString = results.stream() - .map(element -> element.getXAspect().getLabel()) - .distinct() - .collect(Collectors.toList()); - break; - case XY_SCATTER: - xAxisColString = results.stream() - .map(element -> element.getXAspect().getLabel()) - .collect(Collectors.toList()); - break; - default: - break; - } - - yAxisColString = results.stream() - .map(element -> element.getYAspect().getLabel()) - .collect(Collectors.toList()); - - LamiChartModel model = new LamiChartModel(icfChartType, - nullToEmptyString(Messages.LamiReportView_Custom), - xAxisColString, - yAxisColString, - icfXLogScale, - icfYLogScale); - - LamiViewerControl viewerControl = new LamiViewerControl(icfChartViewerParent, icfResultTable, model); - fCustomGraphViewerControls.add(viewerControl); - viewerControl.getToggleAction().run(); - - /* Signal the current selection to the newly created graph */ - LamiSelectionUpdateSignal signal = new LamiSelectionUpdateSignal(LamiReportView.this, fSelectionIndexes, checkNotNull(fResultTable).hashCode()); - TmfSignalManager.dispatchSignal(signal); + private @Nullable LamiReportViewTabPage getCurrentSelectedPage() { + CTabFolder tf = fTabFolder; + if (tf == null) { + return null; } + int idx = tf.getSelectionIndex(); + return fTabPages.get(idx); } - // ------------------------------------------------------------------------ - // Signals - // ------------------------------------------------------------------------ - - /** - * Signal handler for selection update. - * Propagate a TmfSelectionRangeUpdatedSignal if possible. - * - * @param signal - * The selection update signal - */ - @TmfSignalHandler - public void updateSelection(LamiSelectionUpdateSignal signal) { - LamiResultTable table = fResultTable; - if (table == null) { - return; - } - - if (table.hashCode() != signal.getSignalHash() || equals(signal.getSource())) { - /* The signal is not for us */ - return; - } - - Set entryIndex = signal.getEntryIndex(); - - /* - * Since most of the external viewers deal only with continuous time - * ranges and do not allow multi-time range selection, simply signal - * only when one selection is present. - */ - - if (entryIndex.isEmpty()) { - /* - * In an ideal world we would send a null signal to reset all view - * and simply show no selection. But since this is Tracecompass - * there is no notion of "unselected state" in most of the viewers so - * we do not update/clear the last timerange and show false information to the user. - */ - return; - } - - if (entryIndex.size() == 1) { - int index = Iterables.getOnlyElement(entryIndex).intValue(); - LamiTimeRange timeRange = table.getEntries().get(index).getCorrespondingTimeRange(); - if (timeRange != null) { - /* Send Range update to other views */ - ITmfTimestamp start = TmfTimestamp.fromNanos(timeRange.getStart()); - ITmfTimestamp end = TmfTimestamp.fromNanos(timeRange.getEnd()); - TmfSignalManager.dispatchSignal(new TmfSelectionRangeUpdatedSignal(LamiReportView.this, start, end)); - } - } - - fSelectionIndexes = entryIndex; - } - - /** - * Signal handler for time range selections - * - * @param signal - * The received signal - */ - @TmfSignalHandler - public void externalUpdateSelection(TmfSelectionRangeUpdatedSignal signal) { - LamiResultTable table = fResultTable; - if (table == null) { - return; - } - - if (signal.getSource() == this) { - /* We are the source */ - return; - } - TmfTimeRange range = new TmfTimeRange(signal.getBeginTime(), signal.getEndTime()); - - Set selections = getIndexOfEntriesIntersectingTimerange(table, range); - - /* Update all LamiViewer */ - LamiSelectionUpdateSignal signal1 = new LamiSelectionUpdateSignal(LamiReportView.this, selections, table.hashCode()); - TmfSignalManager.dispatchSignal(signal1); - } - - private static Set getIndexOfEntriesIntersectingTimerange(LamiResultTable table, TmfTimeRange range) { - Set selections = new HashSet<>(); - for (LamiTableEntry entry : table.getEntries()) { - LamiTimeRange timerange = entry.getCorrespondingTimeRange(); - if (timerange == null) { - /* Return since the table have no timerange */ - return selections; - } - - TmfTimeRange tempTimeRange = new TmfTimeRange(TmfTimestamp.fromNanos(timerange.getStart()), TmfTimestamp.fromNanos(timerange.getEnd())); - if (tempTimeRange.getIntersection(range) != null) { - selections.add(table.getEntries().indexOf(entry)); - } - } - return selections; - } } diff --git a/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/views/LamiReportViewFactory.java b/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/views/LamiReportViewFactory.java index d768dc8872..a6f51a190d 100644 --- a/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/views/LamiReportViewFactory.java +++ b/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/views/LamiReportViewFactory.java @@ -11,7 +11,6 @@ package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiAnalysisReport; -import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; @@ -28,16 +27,17 @@ public final class LamiReportViewFactory { private LamiReportViewFactory() { } - private static @Nullable LamiResultTable currentTable; + private static @Nullable LamiAnalysisReport currentReport; private static int secondaryViewId = 1; /** - * Return the current result table + * Return the current report. Should be accessed by the view currently being + * built. * - * @return The current result table + * @return The current report */ - public static @Nullable LamiResultTable getCurrentResultTable() { - return currentTable; + public static @Nullable LamiAnalysisReport getCurrentReport() { + return currentReport; } /** @@ -48,21 +48,24 @@ public final class LamiReportViewFactory { * @throws PartInitException * If there was a problem initializing a view */ - public static synchronized void createNewViews(LamiAnalysisReport report) throws PartInitException { - boolean firstView = true; + public static synchronized void createNewView(LamiAnalysisReport report) + throws PartInitException { + currentReport = report; - for (LamiResultTable table : report.getTables()) { - currentTable = table; + final IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); - int mode = (firstView ? IWorkbenchPage.VIEW_ACTIVATE : IWorkbenchPage.VIEW_VISIBLE); + /* + * Doing this in two operations here, instead of using + * IWorkbenchPage.VIEW_ACTIVATE, works around a bug where the contextual + * menu would get "stuck" until the Project view is defocused and + * refocused. + */ + page.showView(LamiReportView.VIEW_ID, String.valueOf(secondaryViewId), IWorkbenchPage.VIEW_VISIBLE); + page.activate(page.findView(LamiReportView.VIEW_ID)); - final IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); - page.showView(LamiReportView.VIEW_ID, String.valueOf(secondaryViewId), mode); - secondaryViewId++; + secondaryViewId++; - currentTable = null; - firstView = false; - } + currentReport = null; } } diff --git a/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/views/LamiReportViewTabPage.java b/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/views/LamiReportViewTabPage.java new file mode 100644 index 0000000000..1d95830f36 --- /dev/null +++ b/analysis/org.eclipse.tracecompass.analysis.lami.ui/src/org/eclipse/tracecompass/internal/provisional/analysis/lami/ui/views/LamiReportViewTabPage.java @@ -0,0 +1,422 @@ +/******************************************************************************* + * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir + * + * 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 + *******************************************************************************/ + +package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views; + +import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; +import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiEmptyAspect; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel.ChartType; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiXYSeriesDescription; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.types.LamiTimeRange; +import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.signals.LamiSelectionUpdateSignal; +import org.eclipse.tracecompass.tmf.core.component.TmfComponent; +import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal; +import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler; +import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager; +import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp; +import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; +import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp; +import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; + +import com.google.common.collect.Iterables; + +/** + * Sub-view of a {@link LamiReportView} that shows the contents of one table of + * the analysis report. While it is not a View object directly, its + * responsibilities are the same. + * + * @author Alexandre Montplaisir + * @author Jonathan Rajotte-Julien + */ +public final class LamiReportViewTabPage extends TmfComponent { + + // ------------------------------------------------------------------------ + // Attributes + // ------------------------------------------------------------------------ + + private final LamiResultTable fResultTable; + + private final LamiViewerControl fTableViewerControl; + private final Set fCustomGraphViewerControls = new LinkedHashSet<>(); + private final Composite fControl; + + private Set fSelectionIndexes; + + // ------------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------------ + + /** + * Constructor + * + * @param parent + * Parent composite + * @param table + * The result table to display in this tab + */ + public LamiReportViewTabPage(Composite parent, LamiResultTable table) { + super(table.getTableClass().getTableTitle()); + fResultTable = table; + fSelectionIndexes = new HashSet<>(); + fSelectionIndexes = getIndexOfEntriesIntersectingTimerange(checkNotNull(fResultTable), TmfTraceManager.getInstance().getCurrentTraceContext().getSelectionRange()); + + fControl = parent; + + /* Prepare the table viewer, which is always present */ + LamiViewerControl tableViewerControl = new LamiViewerControl(fControl, fResultTable); + fTableViewerControl = tableViewerControl; + + /* Automatically open the table viewer initially */ + tableViewerControl.getToggleAction().run(); + + /* Simulate a new external signal to the default viewer */ + LamiSelectionUpdateSignal signal = new LamiSelectionUpdateSignal(LamiReportViewTabPage.this, fSelectionIndexes, checkNotNull(fResultTable).hashCode()); + TmfSignalManager.dispatchSignal(signal); + + fControl.addDisposeListener(e -> { + /* Dispose this class's resource */ + fTableViewerControl.dispose(); + clearAllCustomViewers(); + super.dispose(); + }); + } + + // ------------------------------------------------------------------------ + // Operations + // ------------------------------------------------------------------------ + + @Override + public void dispose() { + fControl.dispose(); + /* fControl's disposeListener will dispose the class's resources */ + } + + /** + * Get the SWT control associated with this tab page. + * + * @return The SWT control + */ + public Composite getControl() { + return fControl; + } + + /** + * Get the result table shown in this tab. + * + * @return The report result table + */ + public LamiResultTable getResultTable() { + return fResultTable; + } + + /** + * Clear all the custom graph viewers in this tab. + */ + public void clearAllCustomViewers() { + fCustomGraphViewerControls.forEach(LamiViewerControl::dispose); + fCustomGraphViewerControls.clear(); + } + + /** + * Toggle the display of the table viewer in this tab. This shows it if it + * is currently hidden, and vice versa. + */ + public void toggleTableViewer() { + fTableViewerControl.getToggleAction().run(); + } + + /** + * Add a new chart viewer to this tab. + * + * The method only needs a chart type (currently selected via separate + * actions), all other information will be found in the result table or in + * dialogs shown to the user as part of the execution of this method. + * + * @param chartType + * The type of chart to create + */ + public void createNewCustomChart(ChartType chartType) { + int xLogScaleOptionIndex = -1; + int yLogScaleOptionIndex = -1; + + List xStringColumn = fResultTable.getTableClass().getAspects().stream() + .filter(aspect -> !(aspect instanceof LamiEmptyAspect)) + .collect(Collectors.toList()); + + /* Get the flattened aspects for Y since mapping an aggregate aspect to y series make no sense so far */ + List yStringColumn = fResultTable.getTableClass().getAspects().stream() + .filter(aspect -> !(aspect instanceof LamiEmptyAspect)) + .collect(Collectors.toList()); + + switch (chartType) { + case BAR_CHART: + /* Y value must strictly continous and non timestamp */ + yStringColumn = yStringColumn.stream() + .filter(aspect -> !aspect.isTimeStamp() && aspect.isContinuous()) + .collect(Collectors.toList()); + break; + case PIE_CHART: + break; + case XY_SCATTER: + break; + default: + break; + } + + IStructuredContentProvider contentProvider = checkNotNull(ArrayContentProvider.getInstance()); + + LamiSeriesDialog dialog = new LamiSeriesDialog(getControl().getShell(), + chartType, + xStringColumn, + yStringColumn, + contentProvider, + new LabelProvider() { + @Override + public String getText(@Nullable Object element) { + return ((LamiTableEntryAspect) checkNotNull(element)).getLabel(); + } + }, + contentProvider, + new LabelProvider() { + @Override + public String getText(@Nullable Object element) { + return ((LamiTableEntryAspect) checkNotNull(element)).getLabel(); + } + }); + dialog.setTitle(chartType.toString() + ' ' + Messages.LamiSeriesDialog_creation); + + /* X options per chart type */ + switch (chartType) { + case XY_SCATTER: + xLogScaleOptionIndex = dialog.addXCheckBoxOption( + Messages.LamiSeriesDialog_x_axis + ' ' + Messages.LamiReportView_LogScale, + false, new Predicate() { + @Override + public boolean test(@NonNull LamiTableEntryAspect t) { + return t.isContinuous() && !t.isTimeStamp(); + } + }); + break; + case BAR_CHART: + case PIE_CHART: + default: + break; + } + + /* Y options per chart type */ + switch (chartType) { + case BAR_CHART: + case XY_SCATTER: + yLogScaleOptionIndex = dialog.addYCheckBoxOption( + Messages.LamiSeriesDialog_y_axis + ' ' + Messages.LamiReportView_LogScale, + false, new Predicate() { + @Override + public boolean test(@NonNull LamiTableEntryAspect t) { + return t.isContinuous() && !t.isTimeStamp(); + } + }); + break; + + case PIE_CHART: + default: + break; + } + + if (dialog.open() != Window.OK) { + return; + } + + List results = Arrays.stream(dialog.getResult()) + .map(serie -> (LamiXYSeriesDescription) serie) + .collect(Collectors.toList()); + + boolean[] xCheckBoxOptionsResults = dialog.getXCheckBoxOptionValues(); + boolean[] yCheckBoxOptionsResults = dialog.getYCheckBoxOptionValues(); + + boolean isXLogScale = false; + boolean isYLogScale = false; + + /* Get X log scale option */ + if (xLogScaleOptionIndex > -1 && xLogScaleOptionIndex < xCheckBoxOptionsResults.length) { + isXLogScale = xCheckBoxOptionsResults[xLogScaleOptionIndex]; + } + /* Get Y log scale option */ + if (yLogScaleOptionIndex > -1 && yLogScaleOptionIndex < yCheckBoxOptionsResults.length) { + isYLogScale = yCheckBoxOptionsResults[yLogScaleOptionIndex]; + } + + List xAxisColString = new ArrayList<>(); + List yAxisColString = new ArrayList<>(); + + /* Specific chart type result fetching */ + switch (chartType) { + case PIE_CHART: + case BAR_CHART: + /* Validate that we only have 1 X aspect */ + if (results.stream() + .map(element -> element.getXAspect().getLabel()) + .distinct() + .count() != 1) { + throw new IllegalStateException(); + } + xAxisColString = results.stream() + .map(element -> element.getXAspect().getLabel()) + .distinct() + .collect(Collectors.toList()); + break; + case XY_SCATTER: + xAxisColString = results.stream() + .map(element -> element.getXAspect().getLabel()) + .collect(Collectors.toList()); + break; + default: + break; + } + + yAxisColString = results.stream() + .map(element -> element.getYAspect().getLabel()) + .collect(Collectors.toList()); + + LamiChartModel model = new LamiChartModel(chartType, + nullToEmptyString(Messages.LamiReportView_Custom), + xAxisColString, + yAxisColString, + isXLogScale, + isYLogScale); + + LamiViewerControl viewerControl = new LamiViewerControl(fControl, fResultTable, model); + fCustomGraphViewerControls.add(viewerControl); + viewerControl.getToggleAction().run(); + + /* Signal the current selection to the newly created graph */ + LamiSelectionUpdateSignal signal = new LamiSelectionUpdateSignal(LamiReportViewTabPage.this, + fSelectionIndexes, checkNotNull(fResultTable).hashCode()); + TmfSignalManager.dispatchSignal(signal); + } + + // ------------------------------------------------------------------------ + // Signals + // ------------------------------------------------------------------------ + + /** + * Signal handler for selection update. + * Propagate a TmfSelectionRangeUpdatedSignal if possible. + * + * @param signal + * The selection update signal + */ + @TmfSignalHandler + public void updateSelection(LamiSelectionUpdateSignal signal) { + LamiResultTable table = fResultTable; + Object source = signal.getSource(); + + if (table.hashCode() != signal.getSignalHash() || + source == this || + /* + * Don't forward signals from other tab pages, especially those + * from other views. + */ + source instanceof LamiReportViewTabPage) { + /* The signal is not for us */ + return; + } + + Set entryIndex = signal.getEntryIndex(); + + /* + * Since most of the external viewer deal only with continuous timerange and do not allow multi time range + * selection simply signal only when only one selection is present. + */ + + if (entryIndex.isEmpty()) { + /* + * In an ideal world we would send a null signal to reset all view + * and simply show no selection. But since this is Tracecompass + * there is no notion of "unselected state" in most of the viewers so + * we do not update/clear the last timerange and show false information to the user. + */ + return; + } + + if (entryIndex.size() == 1) { + int index = Iterables.getOnlyElement(entryIndex).intValue(); + LamiTimeRange timeRange = table.getEntries().get(index).getCorrespondingTimeRange(); + if (timeRange != null) { + /* Send Range update to other views */ + ITmfTimestamp start = TmfTimestamp.fromNanos(timeRange.getStart()); + ITmfTimestamp end = TmfTimestamp.fromNanos(timeRange.getEnd()); + TmfSignalManager.dispatchSignal(new TmfSelectionRangeUpdatedSignal(LamiReportViewTabPage.this, start, end)); + } + } + + fSelectionIndexes = entryIndex; + } + + /** + * Signal handler for time range selections + * + * @param signal + * The received signal + */ + @TmfSignalHandler + public void externalUpdateSelection(TmfSelectionRangeUpdatedSignal signal) { + LamiResultTable table = fResultTable; + + if (signal.getSource() == this) { + /* We are the source */ + return; + } + TmfTimeRange range = new TmfTimeRange(signal.getBeginTime(), signal.getEndTime()); + + Set selections = getIndexOfEntriesIntersectingTimerange(table, range); + + /* Update all LamiViewer */ + LamiSelectionUpdateSignal signal1 = new LamiSelectionUpdateSignal(LamiReportViewTabPage.this, selections, table.hashCode()); + TmfSignalManager.dispatchSignal(signal1); + } + + private static Set getIndexOfEntriesIntersectingTimerange(LamiResultTable table, TmfTimeRange range) { + Set selections = new HashSet<>(); + for (LamiTableEntry entry : table.getEntries()) { + LamiTimeRange timerange = entry.getCorrespondingTimeRange(); + if (timerange == null) { + /* Return since the table have no timerange */ + return selections; + } + + TmfTimeRange tempTimeRange = new TmfTimeRange(TmfTimestamp.fromNanos(timerange.getStart()), TmfTimestamp.fromNanos(timerange.getEnd())); + if (tempTimeRange.getIntersection(range) != null) { + selections.add(table.getEntries().indexOf(entry)); + } + } + return selections; + } +}