/*******************************************************************************
* Copyright (c) 2012-2015 INRIA.
* 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:
* Generoso Pagano - initial API and implementation
******************************************************************************/
package fr.inria.soctrace.framesoc.ui.histogram.view;
import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.wb.swt.ResourceManager;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.event.AxisChangeEvent;
import org.jfree.chart.event.AxisChangeListener;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.labels.XYToolTipGenerator;
import org.jfree.chart.plot.IntervalMarker;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.Range;
import org.jfree.data.statistics.HistogramDataset;
import org.jfree.experimental.chart.swt.ChartComposite;
import org.jfree.ui.RectangleInsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fr.inria.soctrace.framesoc.core.bus.FramesocBusTopic;
import fr.inria.soctrace.framesoc.ui.Activator;
import fr.inria.soctrace.framesoc.ui.histogram.loaders.DensityHistogramLoader;
import fr.inria.soctrace.framesoc.ui.histogram.model.HistogramLoaderDataset;
import fr.inria.soctrace.framesoc.ui.histogram.snapshot.HistogramSnapshotDialog;
import fr.inria.soctrace.framesoc.ui.model.ColorsChangeDescriptor;
import fr.inria.soctrace.framesoc.ui.model.EventProducerNode;
import fr.inria.soctrace.framesoc.ui.model.EventTypeNode;
import fr.inria.soctrace.framesoc.ui.model.GanttTraceIntervalAction;
import fr.inria.soctrace.framesoc.ui.model.PieTraceIntervalAction;
import fr.inria.soctrace.framesoc.ui.model.SynchronizeTraceIntervalAction;
import fr.inria.soctrace.framesoc.ui.model.TableTraceIntervalAction;
import fr.inria.soctrace.framesoc.ui.model.TimeInterval;
import fr.inria.soctrace.framesoc.ui.model.TraceConfigurationDescriptor;
import fr.inria.soctrace.framesoc.ui.model.TraceIntervalDescriptor;
import fr.inria.soctrace.framesoc.ui.perspective.FramesocPart;
import fr.inria.soctrace.framesoc.ui.perspective.FramesocViews;
import fr.inria.soctrace.framesoc.ui.providers.SquareIconLabelProvider;
import fr.inria.soctrace.framesoc.ui.treefilter.FilterDataManager;
import fr.inria.soctrace.framesoc.ui.treefilter.FilterDimension;
import fr.inria.soctrace.framesoc.ui.treefilter.FilterDimensionData;
import fr.inria.soctrace.framesoc.ui.treefilter.ProducerFilterData;
import fr.inria.soctrace.framesoc.ui.treefilter.TypeFilterData;
import fr.inria.soctrace.framesoc.ui.utils.TimeBar;
import fr.inria.soctrace.lib.model.Trace;
import fr.inria.soctrace.lib.model.utils.ModelConstants.TimeUnit;
import fr.inria.soctrace.lib.model.utils.SoCTraceException;
import fr.inria.soctrace.lib.model.utils.TimestampFormat;
import fr.inria.soctrace.lib.model.utils.TimestampFormat.TickDescriptor;
import fr.inria.soctrace.lib.utils.Configuration;
import fr.inria.soctrace.lib.utils.DeltaManager;
import fr.inria.soctrace.lib.utils.Configuration.SoCTraceProperty;
/**
* Framesoc Bar Chart view.
*
* @author "Generoso Pagano <generoso.pagano@inria.fr>"
*/
public class HistogramView extends FramesocPart {
private class HistogramFilterData extends FilterDataManager {
public HistogramFilterData(FilterDimensionData dimension) {
super(dimension);
}
@Override
public void reloadAfterChange() {
loadHistogram(currentShownTrace, loadedInterval);
}
}
/**
* The ID of the view as specified by the extension.
*/
public static final String ID = FramesocViews.HISTOGRAM_VIEW_ID;
/**
* Logger
*/
public static final Logger logger = LoggerFactory.getLogger(HistogramView.class);
/*
* Constants
*/
private static final String TOOLTIP_FORMAT = "bin central timestamp: {1}, events: {2}";
private static final String HISTOGRAM_TITLE = "";
private static final String X_LABEL = "";
private static final String Y_LABEL = "";
private static final boolean HAS_LEGEND = false;
private static final boolean HAS_TOOLTIPS = true;
private static final boolean HAS_URLS = true;
private static final boolean USE_BUFFER = true;
private static final Color BACKGROUND_PAINT = new Color(255, 255, 255);
private static final Color DOMAIN_GRIDLINE_PAINT = new Color(230, 230, 230);
private static final Color RANGE_GRIDLINE_PAINT = new Color(200, 200, 200);
private static final Color MARKER_OUTLINE_PAINT = new Color(0, 0, 255);
private static final int TIMESTAMP_MAX_SIZE = 130;
private static final long BUILD_UPDATE_TIMEOUT = 300;
private static final int TOTAL_WORK = 1000;
private static final int NO_STATUS = -1;
private static final Cursor ARROW_CURSOR = new Cursor(Display.getDefault(), SWT.CURSOR_ARROW);
private static final Cursor IBEAM_CURSOR = new Cursor(Display.getDefault(), SWT.CURSOR_IBEAM);
private static final int NO_SELECTED_VALUE = -1;
private final Font TICK_LABEL_FONT = new Font("Tahoma", 0, 11);
private final Font LABEL_FONT = new Font("Tahoma", 0, 12);
private final TimestampFormat X_FORMAT = new TimestampFormat();
private final DecimalFormat Y_FORMAT = new DecimalFormat("0");
private final XYToolTipGenerator TOOLTIP_GENERATOR = new StandardXYToolTipGenerator(
TOOLTIP_FORMAT, X_FORMAT, Y_FORMAT);
/*
* UI components
*/
private Composite compositeChart;
private ChartComposite chartFrame;
protected XYPlot plot;
private TimeBar timeBar;
private IntervalMarker marker;
private List<SquareIconLabelProvider> labelProviders = new ArrayList<>();
/*
* Loading data and configuration
*/
private TimeInterval requestedInterval;
private TimeInterval loadedInterval;
private Map<FilterDimension, HistogramFilterData> filterMap;
private HistogramLoaderDataset dataset;
/*
* Timestamp management
*/
private int numberOfTicks = 10;
private IStatusLineManager statusLineManager;
/*
* Selection
*/
private boolean activeSelection = false;
private boolean dragInProgress = false;
private long selectedTs0 = NO_SELECTED_VALUE;
private long selectedTs1 = NO_SELECTED_VALUE;
public HistogramView() {
super();
topics.addTopic(FramesocBusTopic.TOPIC_UI_COLORS_CHANGED);
topics.registerAll();
filterMap = new HashMap<>();
filterMap.put(FilterDimension.PRODUCERS, new HistogramFilterData(new ProducerFilterData()));
filterMap.put(FilterDimension.TYPE, new HistogramFilterData(new TypeFilterData()));
}
/* Uncomment this to use the window builder */
// public void createPartControl(Composite parent) {
// createFramesocPartControl(parent);
// }
@Override
public void createFramesocPartControl(Composite parent) {
statusLineManager = getViewSite().getActionBars().getStatusLineManager();
// parent layout
GridLayout gl_parent = new GridLayout(1, false);
gl_parent.verticalSpacing = 2;
gl_parent.marginWidth = 0;
gl_parent.horizontalSpacing = 0;
gl_parent.marginHeight = 0;
parent.setLayout(gl_parent);
// Chart Composite
compositeChart = new Composite(parent, SWT.BORDER);
compositeChart.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
FillLayout fl_compositeChart = new FillLayout(SWT.HORIZONTAL);
compositeChart.setLayout(fl_compositeChart);
compositeChart.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
int width = Math.max(compositeChart.getSize().x - 40, 1);
numberOfTicks = Math.max(width / TIMESTAMP_MAX_SIZE, 1);
refresh(false, false, true);
}
});
// Time management bar
Composite timeComposite = new Composite(parent, SWT.BORDER);
timeComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
GridLayout gl_timeComposite = new GridLayout(1, false);
gl_timeComposite.horizontalSpacing = 0;
timeComposite.setLayout(gl_timeComposite);
// time manager
timeBar = new TimeBar(timeComposite, SWT.NONE, true, true);
timeBar.setEnabled(false);
IStatusLineManager statusLineManager = getViewSite().getActionBars().getStatusLineManager();
timeBar.setStatusLineManager(statusLineManager);
timeBar.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
TimeInterval barInterval = timeBar.getSelection();
if (marker == null) {
addNewMarker(barInterval.startTimestamp, barInterval.endTimestamp);
} else {
marker.setStartValue(barInterval.startTimestamp);
marker.setEndValue(barInterval.endTimestamp);
}
selectedTs0 = barInterval.startTimestamp;
selectedTs1 = barInterval.endTimestamp;
}
});
// button to synch the timebar, producers and type with the current loaded data
timeBar.getSynchButton().addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (loadedInterval != null) {
timeBar.setSelection(loadedInterval);
if (marker != null && plot != null) {
plot.removeDomainMarker(marker);
marker = null;
}
}
}
});
// load button
timeBar.getLoadButton().addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (!timeBar.getSelection().equals(loadedInterval)) {
loadHistogram(currentShownTrace, timeBar.getSelection());
}
}
});
// ----------
// TOOL BAR
// ----------
// filters and actions
initFilterDialogs();
createActions();
}
private void initFilterData(Trace t) {
for (HistogramFilterData data : filterMap.values()) {
try {
data.setFilterRoots(DensityHistogramLoader.loadDimension(data.getDimension(), t));
} catch (SoCTraceException e) {
e.printStackTrace();
}
}
}
private void initFilterDialogs() {
for (HistogramFilterData data : filterMap.values()) {
data.initFilterDialog(getSite().getShell());
}
}
private void createActions() {
IToolBarManager manager = getViewSite().getActionBars().getToolBarManager();
// Filters actions
manager.add(filterMap.get(FilterDimension.PRODUCERS).initFilterAction());
manager.add(filterMap.get(FilterDimension.TYPE).initFilterAction());
// Separator
manager.add(new Separator());
// Snapshot
manager.add(createSnapshotAction());
// Separator
manager.add(new Separator());
// Framesoc Actions
TableTraceIntervalAction.add(manager, createTableAction());
GanttTraceIntervalAction.add(manager, createGanttAction());
PieTraceIntervalAction.add(manager, createPieAction());
SynchronizeTraceIntervalAction.add(manager, createSynchronizeAction());
// disable all actions
enableActions(false);
}
@Override
public void setFocus() {
super.setFocus();
}
@Override
public void dispose() {
plot = null;
super.dispose();
}
protected TraceIntervalDescriptor getIntervalDescriptor() {
if (currentShownTrace == null || loadedInterval == null)
return null;
TraceConfigurationDescriptor des = new TraceConfigurationDescriptor();
des.setTrace(currentShownTrace);
des.setTimeInterval(loadedInterval);
// Set event type and event prod
des.setEventTypes(filterMap.get(FilterDimension.TYPE).getChecked());
des.setEventProducers(filterMap.get(FilterDimension.PRODUCERS)
.getChecked());
return des;
}
@Override
public String getId() {
return ID;
}
@Override
public void showTrace(final Trace trace, Object data) {
boolean reload = false;
if (trace == null)
return;
if (currentShownTrace == null || !currentShownTrace.equals(trace)) {
initFilterData(trace);
}
if (data == null) {
// called after right click on trace tree menu
loadHistogram(trace, new TimeInterval(trace.getMinTimestamp(), trace.getMaxTimestamp()));
} else {
// called after double click on trace tree or a Framesoc bus message
// coming from a "show in" action
TraceIntervalDescriptor des = (TraceIntervalDescriptor) data;
TimeInterval desInterval = des.getTimeInterval();
if (desInterval.equals(TimeInterval.NOT_SPECIFIED)) {
// double click
if (currentShownTrace != null && currentShownTrace.equals(trace)) {
// same trace: keep interval and configuration
return;
}
if (currentShownTrace == null) {
// load the whole trace
Trace t = des.getTrace();
desInterval = new TimeInterval(t.getMinTimestamp(), t.getMaxTimestamp());
}
}
// Check if we apply event producers and event types filters
if (des instanceof TraceConfigurationDescriptor) {
TraceConfigurationDescriptor tcd = (TraceConfigurationDescriptor) des;
if (!tcd.getEventTypes().isEmpty()
&& Boolean.valueOf(Configuration.getInstance().get(
SoCTraceProperty.type_filter_synchronization))) {
filterMap.get(FilterDimension.TYPE).setChecked(
tcd.getEventTypes());
reload = true;
}
if (!tcd.getEventProducers().isEmpty()
&& Boolean
.valueOf(Configuration
.getInstance()
.get(SoCTraceProperty.producer_filter_synhronization))) {
filterMap.get(FilterDimension.PRODUCERS).setChecked(
tcd.getEventProducers());
reload = true;
}
}
if (loadedInterval == null || (!loadedInterval.equals(desInterval)) || reload) {
loadHistogram(des.getTrace(), desInterval);
}
}
}
/**
* Load the histogram using the current trace and the information in the type and producer
* trees.
*
* @param trace
* trace to load
* @param interval
* time interval to load
*/
public void loadHistogram(final Trace trace, final TimeInterval interval) {
currentShownTrace = trace;
// set time unit, extrema and displayed range
timeBar.setTimeUnit(TimeUnit.getTimeUnit(trace.getTimeUnit()));
timeBar.setExtrema(trace.getMinTimestamp(), trace.getMaxTimestamp());
timeBar.setDisplayInterval(interval);
// reset selection
selectedTs0 = selectedTs1 = NO_SELECTED_VALUE;
// nothing is loaded so far, so the interval is [start, start] (duration 0)
loadedInterval = new TimeInterval(interval.startTimestamp, interval.startTimestamp);
requestedInterval = new TimeInterval(interval);
Thread showThread = new Thread() {
@Override
public void run() {
// prepare the configuration for the loader
Map<FilterDimension, List<Integer>> confMap = new HashMap<>();
for (HistogramFilterData data : filterMap.values()) {
confMap.put(data.getDimension(), data.getCheckedId());
}
// create a new loader dataset
dataset = new HistogramLoaderDataset();
// create loader thread and drawer job
LoaderThread loaderThread = new LoaderThread(interval,
confMap.get(FilterDimension.TYPE), confMap.get(FilterDimension.PRODUCERS));
DrawerJob drawerJob = new DrawerJob("Event Density Chart Job", loaderThread);
loaderThread.start();
drawerJob.schedule();
}
};
showThread.start();
}
/**
* Loader thread.
*/
private class LoaderThread extends Thread {
private final TimeInterval interval;
private final IProgressMonitor monitor;
private List<Integer> types;
private List<Integer> producer;
public LoaderThread(TimeInterval interval, List<Integer> types, List<Integer> producers) {
this.interval = interval;
this.types = types;
this.producer = producers;
this.monitor = new NullProgressMonitor();
}
@Override
public void run() {
DensityHistogramLoader.load(currentShownTrace, interval, types, producer, dataset,
monitor);
}
public void cancel() {
monitor.setCanceled(true);
}
}
/**
* Drawer job.
*/
private class DrawerJob extends Job {
private final LoaderThread loaderThread;
public DrawerJob(String name, LoaderThread loaderThread) {
super(name);
this.loaderThread = loaderThread;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
DeltaManager dm = new DeltaManager();
dm.start();
monitor.beginTask("Loading trace " + currentShownTrace.getAlias(), TOTAL_WORK);
try {
disableButtons();
boolean done = false;
boolean first = true;
long oldLoadedEnd = 0;
while (!done) {
done = dataset.waitUntilDone(BUILD_UPDATE_TIMEOUT);
if (!dataset.isDirty()) {
continue;
}
if (monitor.isCanceled()) {
loaderThread.cancel();
logger.debug("Drawer job cancelled");
// refresh one last time
refresh(first, true, false);
first = false;
return Status.CANCEL_STATUS;
}
oldLoadedEnd = loadedInterval.endTimestamp;
refresh(first, false, false);
first = false;
double delta = loadedInterval.endTimestamp - oldLoadedEnd;
if (delta > 0) {
monitor.worked((int) ((delta / requestedInterval.getDuration()) * TOTAL_WORK));
}
}
if (first) {
// refresh at least once when there is no data.
refresh(first, false, false);
}
return Status.OK_STATUS;
} finally {
enableButtons();
logger.debug(dm.endMessage("finished drawing"));
}
}
private void disableButtons() {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
timeBar.setEnabled(false);
enableActions(false);
}
});
}
private void enableButtons() {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
enableActions(true);
timeBar.setEnabled(true);
}
});
}
}
/**
* Refresh the UI using the current dataset
*
* @param first
* flag indicating if it is the first refresh for a given load
*/
private void refresh(final boolean first, final boolean cancelled, final boolean keepZoom) {
if (dataset == null) {
return;
}
/*
* Prepare data
*/
DeltaManager dm = new DeltaManager();
dm.start();
// get the last snapshot
HistogramDataset hdataset = dataset.getSnapshot(loadedInterval);
// if we have not been cancelled, the x range corresponds to the requested interval
final TimeInterval histogramInterval = new TimeInterval(requestedInterval);
if (cancelled) {
// we have been cancelled, the x range corresponds to the actual loaded interval
histogramInterval.copy(loadedInterval);
}
if (keepZoom && plot != null) {
long min = (long) plot.getDomainAxis().getRange().getLowerBound();
long max = (long) plot.getDomainAxis().getRange().getUpperBound();
TimeInterval displayed = new TimeInterval(min, max);
histogramInterval.copy(displayed);
}
/*
* Prepare chart
*/
final JFreeChart chart = ChartFactory.createHistogram(HISTOGRAM_TITLE, X_LABEL, Y_LABEL,
hdataset, PlotOrientation.VERTICAL, HAS_LEGEND, HAS_TOOLTIPS, HAS_URLS);
// customize plot
preparePlot(first, chart, histogramInterval);
// display chart in UI
displayChart(chart, histogramInterval, first);
// Is there a selection ?
if (marker == null && selectedTs0 > 0 && selectedTs1 > 0) {
// Redraw selection
addNewMarker(selectedTs0, selectedTs1);
timeBar.setSelection(selectedTs0, selectedTs1);
}
logger.debug(dm.endMessage("Finished refreshing"));
}
/**
* Display the chart in the UI thread, using the loaded interval.
*
* @param chart
* jfreechart chart
* @param histogramInterval
* displayed interval
* @param first
* flag indicating if it is the first refresh for a given load
*/
private void displayChart(final JFreeChart chart, final TimeInterval displayed,
final boolean first) {
// prepare the new histogram UI
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
// Clean parent
for (Control c : compositeChart.getChildren()) {
c.dispose();
}
// histogram chart
chartFrame = new ChartComposite(compositeChart, SWT.NONE, chart, USE_BUFFER) {
@Override
public void mouseMove(MouseEvent e) {
super.mouseMove(e);
// update cursor
if (!isInDataArea(e.x, e.y)) {
getShell().setCursor(ARROW_CURSOR);
} else {
if (dragInProgress
|| (activeSelection && (isNear(e.x, selectedTs0)) || isNear(
e.x, selectedTs1))) {
getShell().setCursor(IBEAM_CURSOR);
} else {
getShell().setCursor(ARROW_CURSOR);
}
}
// update marker
long v = getTimestampAt(e.x);
if (dragInProgress) {
// when drag is in progress, the moving side is always Ts1
selectedTs1 = v;
long min = Math.min(selectedTs0, selectedTs1);
long max = Math.max(selectedTs0, selectedTs1);
if (marker != null) {
marker.setStartValue(min);
marker.setEndValue(max);
}
timeBar.setSelection(min, max);
}
// update status line
updateStatusLine(v);
}
@Override
public void mouseUp(MouseEvent e) {
super.mouseUp(e);
dragInProgress = false;
selectedTs1 = getTimestampAt(e.x);
if (selectedTs0 > selectedTs1) {
// reorder Ts0 and Ts1
long tmp = selectedTs1;
selectedTs1 = selectedTs0;
selectedTs0 = tmp;
} else if (selectedTs0 == selectedTs1) {
marker.setStartValue(selectedTs0);
marker.setEndValue(selectedTs0);
activeSelection = false;
timeBar.setSelection(loadedInterval);
updateStatusLine(selectedTs0);
}
}
@Override
public void mouseDown(MouseEvent e) {
super.mouseDown(e);
if (activeSelection) {
if (isNear(e.x, selectedTs0)) {
// swap in order to have Ts1 as moving side
long tmp = selectedTs0;
selectedTs0 = selectedTs1;
selectedTs1 = tmp;
} else if (isNear(e.x, selectedTs1)) {
// nothing to do if the moving side is already Ts1
} else {
// near to no one: remove marker and add a new one
removeMarker();
selectedTs0 = getTimestampAt(e.x);
addNewMarker(selectedTs0, selectedTs0);
}
} else {
removeMarker();
selectedTs0 = getTimestampAt(e.x);
addNewMarker(selectedTs0, selectedTs0);
}
activeSelection = true;
dragInProgress = true;
}
private boolean isNear(int pos, long value) {
final int RANGE = 3;
int vPos = getPosAt(value);
if (Math.abs(vPos - pos) <= RANGE) {
return true;
}
return false;
}
boolean isInDataArea(int x, int y) {
if (chartFrame != null) {
org.eclipse.swt.graphics.Rectangle swtRect = chartFrame
.getScreenDataArea();
Rectangle2D screenDataArea = new Rectangle();
screenDataArea.setRect(swtRect.x, swtRect.y, swtRect.width,
swtRect.height);
return swtRect.contains(x, y);
}
return false;
}
};
chartFrame.addMouseWheelListener(new MouseWheelListener() {
@Override
public void mouseScrolled(MouseEvent e) {
if ((e.stateMask & SWT.CTRL) == SWT.CTRL) {
if (e.count > 0) {
// zoom in
zoomChartAxis(true, e.x, e.y);
} else {
// zoom out
zoomChartAxis(false, e.x, e.y);
}
}
}
private void zoomChartAxis(boolean increase, int x, int y) {
double min = plot.getDomainAxis().getRange().getLowerBound();
double max = plot.getDomainAxis().getRange().getUpperBound();
X_FORMAT.setContext((long) min, (long) max);
Point2D p = chartFrame.translateScreenToJava2D(new Point(x, y));
PlotRenderingInfo plotInfo = chartFrame.getChartRenderingInfo()
.getPlotInfo();
if (increase) {
double dmin = min;
double dmax = max;
if (dmin <= 0) {
double inc = -2 * dmin + 1;
dmin += inc;
dmax += inc;
}
double diff = (dmax - dmin) / dmin;
if (diff >= 0.01) {
// zoom only if the (max - min) is at least 1% of the min
plot.zoomDomainAxes(0.5, plotInfo, p, true);
}
} else {
// XXX On Fedora 17 this always dezoom all
plot.zoomDomainAxes(-0.5, plotInfo, p, true);
}
// adjust
min = plot.getDomainAxis().getRange().getLowerBound();
max = plot.getDomainAxis().getRange().getUpperBound();
Range maxRange = new Range(Math.max(loadedInterval.startTimestamp, min),
Math.min(loadedInterval.endTimestamp, max));
plot.getDomainAxis().setRange(maxRange);
}
});
// - size
chartFrame.setSize(compositeChart.getSize());
// - prevent y zooming
chartFrame.setRangeZoomable(false);
// - prevent x zooming (we do it manually with wheel)
chartFrame.setDomainZoomable(false);
// - workaround for last xaxis tick not shown (jfreechart bug)
RectangleInsets insets = plot.getInsets();
plot.setInsets(new RectangleInsets(insets.getTop(), insets.getLeft(), insets
.getBottom(), 25));
// - time bounds
plot.getDomainAxis().setLowerBound(displayed.startTimestamp);
plot.getDomainAxis().setUpperBound(displayed.endTimestamp);
// timebar
timeBar.setSelection(loadedInterval);
timeBar.setDisplayInterval(loadedInterval);
}
});
}
private void removeMarker() {
if (marker != null) {
plot.removeDomainMarker(marker);
marker = null;
}
}
private void addNewMarker(long start, long end) {
marker = new IntervalMarker(selectedTs0, selectedTs0);
marker.setPaint(BACKGROUND_PAINT);
marker.setOutlinePaint(MARKER_OUTLINE_PAINT);
marker.setAlpha(0.5f);
marker.setStartValue(start);
marker.setEndValue(end);
plot.addDomainMarker(marker);
activeSelection = true;
}
public ChartComposite getChartFrame() {
return chartFrame;
}
/**
* Prepare the plot
*
* @param chart
* jfreechart chart
* @param displayed
* displayed time interval
*/
private void preparePlot(boolean first, JFreeChart chart, TimeInterval displayed) {
// Plot customization
plot = chart.getXYPlot();
// Grid and background colors
plot.setBackgroundPaint(BACKGROUND_PAINT);
plot.setDomainGridlinePaint(DOMAIN_GRIDLINE_PAINT);
plot.setRangeGridlinePaint(RANGE_GRIDLINE_PAINT);
// Tooltip
XYItemRenderer renderer = plot.getRenderer();
renderer.setBaseToolTipGenerator(TOOLTIP_GENERATOR);
// Disable bar white stripes
XYBarRenderer barRenderer = (XYBarRenderer) plot.getRenderer();
barRenderer.setBarPainter(new StandardXYBarPainter());
// X axis
X_FORMAT.setTimeUnit(TimeUnit.getTimeUnit(currentShownTrace.getTimeUnit()));
X_FORMAT.setContext(displayed.startTimestamp, displayed.endTimestamp);
NumberAxis xaxis = (NumberAxis) plot.getDomainAxis();
xaxis.setTickLabelFont(TICK_LABEL_FONT);
xaxis.setLabelFont(LABEL_FONT);
xaxis.setNumberFormatOverride(X_FORMAT);
TickDescriptor des = X_FORMAT.getTickDescriptor(displayed.startTimestamp,
displayed.endTimestamp, numberOfTicks);
xaxis.setTickUnit(new NumberTickUnit(des.delta));
xaxis.addChangeListener(new AxisChangeListener() {
@Override
public void axisChanged(AxisChangeEvent arg) {
long max = ((Double) plot.getDomainAxis().getRange().getUpperBound()).longValue();
long min = ((Double) plot.getDomainAxis().getRange().getLowerBound()).longValue();
TickDescriptor des = X_FORMAT.getTickDescriptor(min, max, numberOfTicks);
NumberTickUnit newUnit = new NumberTickUnit(des.delta);
NumberTickUnit currentUnit = ((NumberAxis) arg.getAxis()).getTickUnit();
// ensure we don't loop
if (!currentUnit.equals(newUnit)) {
((NumberAxis) arg.getAxis()).setTickUnit(newUnit);
}
}
});
// Y axis
NumberAxis yaxis = (NumberAxis) plot.getRangeAxis();
yaxis.setTickLabelFont(TICK_LABEL_FONT);
yaxis.setLabelFont(LABEL_FONT);
// remove the marker, if any
if (marker != null) {
plot.removeDomainMarker(marker);
marker = null;
}
}
private int getPosAt(long timestamp) {
if (chartFrame != null && plot != null) {
org.eclipse.swt.graphics.Rectangle swtRect = chartFrame.getScreenDataArea();
Rectangle2D screenDataArea = new Rectangle();
screenDataArea.setRect(swtRect.x, swtRect.y, swtRect.width, swtRect.height);
int pos = (int) plot.getDomainAxis().valueToJava2D(timestamp, screenDataArea,
plot.getDomainAxisEdge());
return pos;
}
return 0;
}
private long getTimestampAt(int pos) {
if (chartFrame != null && plot != null && loadedInterval != null) {
org.eclipse.swt.graphics.Rectangle swtRect = chartFrame.getScreenDataArea();
Rectangle2D screenDataArea = new Rectangle();
screenDataArea.setRect(swtRect.x, swtRect.y, swtRect.width, swtRect.height);
long v = (long) plot.getDomainAxis().java2DToValue(pos, screenDataArea,
plot.getDomainAxisEdge());
if (v < loadedInterval.startTimestamp) {
v = loadedInterval.startTimestamp;
} else if (v > loadedInterval.endTimestamp) {
v = loadedInterval.endTimestamp;
}
return v;
}
return 0;
}
@Override
public void partHandle(FramesocBusTopic topic, Object data) {
if (topic.equals(FramesocBusTopic.TOPIC_UI_COLORS_CHANGED)) {
if (currentShownTrace == null)
return;
ColorsChangeDescriptor des = (ColorsChangeDescriptor) data;
logger.debug("Colors changed: {}", des);
for (SquareIconLabelProvider p : labelProviders) {
p.disposeImages();
}
refresh(false, false, true);
}
}
private void updateStatusLine(long timestamp) {
if (statusLineManager == null || currentShownTrace == null) {
return;
}
if (timestamp == NO_STATUS) {
statusLineManager.setMessage("");
return;
}
long ts0 = selectedTs0;
long ts1 = selectedTs1;
if (ts0 > ts1) {
long tmp = ts1;
ts1 = ts0;
ts0 = tmp;
}
ts0 = Math.max(ts0, currentShownTrace.getMinTimestamp());
ts1 = Math.min(ts1, currentShownTrace.getMaxTimestamp());
StringBuilder message = new StringBuilder();
if (!dragInProgress) {
message.append("T: "); //$NON-NLS-1$
message.append(X_FORMAT.format(timestamp));
message.append(" ");
}
message.append("T1: "); //$NON-NLS-1$
message.append(X_FORMAT.format(ts0));
if (activeSelection) {
message.append(" T2: "); //$NON-NLS-1$
message.append(X_FORMAT.format(ts1));
message.append(" \u0394: "); //$NON-NLS-1$
message.append(X_FORMAT.format(Math.abs(ts1 - ts0)));
}
statusLineManager.setMessage(message.toString());
}
/**
* Initialize the snapshot action
*
* @return the action
*/
public IAction createSnapshotAction() {
SnapshotAction snapshotAction = new SnapshotAction("",
IAction.AS_PUSH_BUTTON);
snapshotAction.histoView = this;
snapshotAction.setImageDescriptor(ResourceManager
.getPluginImageDescriptor(Activator.PLUGIN_ID,
"icons/snapshot.png"));
snapshotAction.setToolTipText("Take a snapshot");
return snapshotAction;
}
private class SnapshotAction extends Action {
public HistogramView histoView;
public SnapshotAction(String string, int asPushButton) {
super(string, asPushButton);
}
@Override
public void run() {
new HistogramSnapshotDialog(getSite().getShell(), histoView).open();
}
}
public String getSnapshotInfo() {
StringBuffer output = new StringBuffer();
output.append("\nLoaded start timestamp: ");
output.append(loadedInterval.startTimestamp);
output.append("\nLoaded end timestamp: ");
output.append(loadedInterval.endTimestamp);
output.append("\nSelected start timestamp: ");
output.append(Math.min(selectedTs0, selectedTs1));
output.append("\nSelected end timestamp: ");
output.append(Math.max(selectedTs0, selectedTs1));
// Filtered types
String filteredTypes = "";
List<Object> filteredType = filterMap.get(FilterDimension.TYPE).getChecked();
List<Object> allTypes = filterMap.get(FilterDimension.TYPE).getAllElements();
for (Object typeObject : allTypes) {
if (!filteredType.contains(typeObject)) {
if (typeObject instanceof EventTypeNode) {
filteredTypes = filteredTypes
+ ((EventTypeNode) typeObject).getEventType().getName() + ", ";
}
}
}
// If there was some filtered event producers
if (!filteredTypes.isEmpty()) {
// Remove last separator
filteredTypes = filteredTypes.substring(0, filteredTypes.length() - 2);
output.append("\nFiltered event types: ");
output.append(filteredTypes);
}
// Filtered event producers
String filteredEP = "";
List<Object> filteredProducers = filterMap.get(FilterDimension.PRODUCERS).getChecked();
List<Object> allProducers = filterMap.get(FilterDimension.PRODUCERS).getAllElements();
for (Object producer : allProducers) {
if (!filteredProducers.contains(producer)) {
if (producer instanceof EventProducerNode) {
filteredEP = filteredEP
+ ((EventProducerNode) producer).getName() + ", ";
}
}
}
// If there was some filtered event producers
if (!filteredEP.isEmpty()) {
// Remove last separator
filteredEP = filteredEP.substring(0, filteredEP.length() - 2);
output.append("\nFiltered event producers: ");
output.append(filteredEP);
}
return output.toString();
}
}