Java tutorial
/* * Debrief - the Open Source Maritime Analysis Application * http://debrief.info * * (C) 2000-2014, PlanetMayo Ltd * * This library is free software; you can redistribute it and/or * modify it under the terms of the Eclipse Public License v1.0 * (http://www.eclipse.org/legal/epl-v10.html) * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ package org.mwc.cmap.xyplot.views; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Toolkit; import java.awt.datatransfer.ClipboardOwner; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import javax.swing.JComponent; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.RTFTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IMemento; import org.eclipse.ui.IPartListener; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.part.ViewPart; import org.jfree.chart.JFreeChart; import org.jfree.chart.annotations.XYTextAnnotation; import org.jfree.chart.axis.DateAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.event.ChartChangeEvent; import org.jfree.chart.event.ChartChangeListener; import org.jfree.chart.event.ChartProgressEvent; import org.jfree.chart.event.ChartProgressListener; import org.jfree.chart.labels.StandardXYToolTipGenerator; import org.jfree.chart.labels.XYToolTipGenerator; import org.jfree.chart.plot.PlotOrientation; import org.jfree.data.general.AbstractSeriesDataset; import org.jfree.data.time.TimeSeries; import org.jfree.data.time.TimeSeriesCollection; import org.jfree.data.time.TimeSeriesDataItem; import org.jfree.data.xy.XYDataset; import org.jfree.experimental.chart.swt.ChartComposite; import org.jfree.ui.TextAnchor; import org.mwc.cmap.core.CorePlugin; import org.mwc.cmap.core.DataTypes.Temporal.TimeProvider; import org.mwc.cmap.core.preferences.SelectionHelper; import org.mwc.cmap.core.preferences.WMFExportPrefsPage.PreferenceConstants; import org.mwc.cmap.core.property_support.EditableWrapper; import org.mwc.cmap.plotViewer.PlotViewerPlugin; import org.mwc.cmap.plotViewer.actions.RTFWriter; import org.mwc.cmap.xyplot.XYPlotPlugin; import Debrief.GUI.Tote.StepControl; import MWC.Algorithms.Projections.FlatProjection; import MWC.GUI.Layer; import MWC.GUI.Layers; import MWC.GUI.Layers.DataListener; import MWC.GUI.Canvas.MetafileCanvasGraphics2d; import MWC.GUI.JFreeChart.ColourStandardXYItemRenderer; import MWC.GUI.JFreeChart.ColouredDataItem; import MWC.GUI.JFreeChart.DateAxisEditor; import MWC.GUI.JFreeChart.DateAxisEditor.MWCDateTickUnitWrapper; import MWC.GUI.JFreeChart.DatedToolTipGenerator; import MWC.GUI.JFreeChart.NewFormattedJFreeChart; import MWC.GUI.JFreeChart.RelativeDateAxis; import MWC.GUI.JFreeChart.StepperChartPanel; import MWC.GUI.JFreeChart.StepperXYPlot; import MWC.GUI.JFreeChart.formattingOperation; import MWC.GenericData.Duration; import MWC.GenericData.HiResDate; import com.pietjonas.wmfwriter2d.ClipboardCopy; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; public class XYPlotView extends ViewPart { /** * hide the implemetnation of something that can provide a dataset. * * @author ian * */ public static interface DatasetProvider { /** * get (recalculate) the dataset * * @return */ public AbstractSeriesDataset getDataset(); /** * get the layers that this data comes from * * @return */ Layers getLayers(); } // ////////////////////////////////////////////////// // data we store between sessions // ////////////////////////////////////////////////// private static final String FIXED_DURATION = "FixedDuration"; private static final String DISPLAY_FIXED_DURATION = "DisplayFixedDuration"; /** * data-type names */ private static final String PLOT_ID = "PlotId"; private static final String DO_WATERFALL = "DO_WATERFALL"; private final String TITLE = "XYPlot_Title"; private final String UNITS = "XYPlot_Units"; private final String FORMATTER = "XYPlot_Formatter"; private final String DATA = "XYPlot_Data"; private static interface PLOT_ATTRIBUTES { final String AxisFont = "AxisFont"; final String TickFont = "TickFont"; final String TitleFont = "TitleFont"; final String LineWidth = "LineWidth"; final String Title = "Title"; final String X_Title = "X_Title"; final String Y_Title = "Y_Title"; final String DateUnits = "DateUnits"; final String RelativeTimes = "RelativeTimes"; final String ShowSymbols = "ShowSymbols"; } /** * title of plot */ private String _myTitle = "Empty"; /** * the name of the units on the y axis */ private String _myUnits; /** * how to format the data */ private formattingOperation _theFormatter; /** * the data we're storing */ private AbstractSeriesDataset _dataset; /** * resize the data to fill the window */ private Action _fitToWindow; /** * whether to show/hide the crosshairs */ private Action _hideCrosshairs; /** * resize the data to fill the window */ private Action _switchAxes; /** * make the plot grow in real time */ private Action _growPlot; /** * output the plot as a WMF */ private Action _exportToWMF; /** * put the plot on the clipboard */ private Action _exportToClipboard; /** * put the plot on the clipboard as String matrix */ private Action _copyToClipboard; /** * the SWT control we insert the plot into */ private ChartComposite _plotControl; /** * the data-area of the plot */ StepperXYPlot _thePlot; /** * the area surrounding the plot */ private NewFormattedJFreeChart _thePlotArea; /** * object to tie the plot to the step control */ StepperChartPanel _chartInPanel; /** * somebody to listen to the time changes */ private PropertyChangeListener _timeListener; private IPartListener editorListener = new IPartListener() { @Override public void partOpened(IWorkbenchPart part) { } @Override public void partDeactivated(IWorkbenchPart part) { } @Override public void partBroughtToTop(IWorkbenchPart part) { } @Override public void partActivated(IWorkbenchPart part) { } @Override public void partClosed(IWorkbenchPart part) { if (part == editor) { getSite().getPage().hideView(XYPlotView.this); } } }; /** * helper - to let the user edit us */ private SelectionHelper _selectionHelper; private Action _editMyProperties; private ShowSymbolAction _showSymbols; /** * store the plot information when we're reloading a plot in a fresh session */ private IMemento _myMemento = null; private String _myId; /** * the special action that indicates if we should listen to our data changing, * but only if we have a data provider */ private Action _listenForDataChanges; /** * a helper that's able to generate a dataset * */ private transient DatasetProvider _provider; /** * our pre-generated layer listener * */ protected DataListener _modifiedListener; private IEditorPart editor; private XYTextAnnotation _crosshairValueText; /** * The constructor. */ public XYPlotView() { } /** * put some data into the view * * @param title * - the title for the plot * @param dataset * - the dataset to plot * @param units * - the units (for the y axis) * @param theFormatter * - an object capable of applying formatting to the plot * @param thePlotId */ public void showPlot(final String title, final AbstractSeriesDataset dataset, final String units, final formattingOperation theFormatter, final String thePlotId) { _listenForDataChanges.setEnabled(false); // right, store the incoming data, so we can save it when/if // Eclipse closes with this view still open _myTitle = title; _myUnits = units; _theFormatter = theFormatter; _dataset = dataset; _myId = thePlotId; // ok, update the plot. this.setPartName(_myTitle); if (dataset != null) { // ok, fill in the plot fillThePlot(title, units, theFormatter, dataset); } } /** * This is a callback that will allow us to create the viewer and initialize * it. */ public void createPartControl(final Composite parent) { _plotControl = new ChartComposite(parent, SWT.NONE, null, 400, 600, 300, 200, 1800, 1800, true, true, true, true, true, true) { @Override public void mouseUp(MouseEvent event) { super.mouseUp(event); JFreeChart c = getChart(); if (c != null) { c.setNotify(true); // force redraw } } }; // and lastly do the remaining bits... makeActions(); hookContextMenu(); hookDoubleClickAction(); contributeToActionBars(); // put in the plot-copy support final IActionBars actionBars = getViewSite().getActionBars(); actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), _exportToClipboard); // and the selection provider bits _selectionHelper = new SelectionHelper(); getSite().setSelectionProvider(_selectionHelper); // hey, have we got our data? if (_myMemento != null) { // yup, better restore it then. restorePreviousPlot(); } _modifiedListener = new DataListener() { @Override public void dataModified(final Layers theData, final Layer changedLayer) { regenerateData(); } @Override public void dataExtended(final Layers theData) { regenerateData(); } @Override public void dataReformatted(final Layers theData, final Layer changedLayer) { try { regenerateData(); } catch (org.eclipse.swt.SWTException se) { CorePlugin.logError(Status.WARNING, "Trouble redrawing XT Plot", se); } } }; getSite().getWorkbenchWindow().getPartService().addPartListener(editorListener); } /** * we're restoring a previous plot. retrieve the data from the memento, and * stick it back into the plot * */ private void restorePreviousPlot() { try { // retrieve the obvious stuff _myTitle = _myMemento.getString(TITLE); _myUnits = _myMemento.getString(UNITS); _myId = _myMemento.getString(PLOT_ID); // get our special streaming library ready final XStream xs = new XStream(new DomDriver()); // formatter first final String theFormatterStr = _myMemento.getString(FORMATTER); // hey, do we have a formatter? if (theFormatterStr != null) _theFormatter = (formattingOperation) xs.fromXML(theFormatterStr); // and the data final String dataStr = _myMemento.getString(DATA); // hmm, is there anything in it? if (dataStr == null) return; _dataset = (AbstractSeriesDataset) xs.fromXML(dataStr); // right, that's the essential bits, now open the plot showPlot(_myTitle, _dataset, _myUnits, _theFormatter, _myId); // sort out the fixed duration bits - now we've got our plot final String theDur = _myMemento.getString(FIXED_DURATION); Duration someDur = null; if (theDur != null) { final long dur = Long.parseLong(theDur); someDur = new Duration(dur, Duration.MILLISECONDS); } final Boolean doFixed = _myMemento.getBoolean(DISPLAY_FIXED_DURATION); if (doFixed != null) { this._thePlotArea.setFixedDuration(someDur); this._thePlotArea.setDisplayFixedDuration(doFixed); } // right the plot's done, put back in our fancy formatting bits String str; str = _myMemento.getString(PLOT_ATTRIBUTES.Title); if (str != null) _thePlotArea.setTitle((String) xs.fromXML(str)); Font theF = getFont(_myMemento, PLOT_ATTRIBUTES.AxisFont); if (theF != null) _thePlotArea.setAxisFont(theF); theF = getFont(_myMemento, PLOT_ATTRIBUTES.TickFont); if (theF != null) _thePlotArea.setTickFont(theF); theF = getFont(_myMemento, PLOT_ATTRIBUTES.TitleFont); if (theF != null) _thePlotArea.setTitleFont(theF); str = _myMemento.getString(PLOT_ATTRIBUTES.LineWidth); if (str != null) _thePlotArea.setDataLineWidth(((Integer) xs.fromXML(str)).intValue()); str = _myMemento.getString(PLOT_ATTRIBUTES.X_Title); if (str != null) _thePlotArea.setX_AxisTitle((String) xs.fromXML(str)); str = _myMemento.getString(PLOT_ATTRIBUTES.Y_Title); if (str != null) _thePlotArea.setY_AxisTitle((String) xs.fromXML(str)); str = _myMemento.getString(PLOT_ATTRIBUTES.DateUnits); if (str != null) _thePlotArea.setDateTickUnits((MWCDateTickUnitWrapper) xs.fromXML(str)); str = _myMemento.getString(PLOT_ATTRIBUTES.RelativeTimes); if (str != null) _thePlotArea.setRelativeTimes(((Boolean) xs.fromXML(str)).booleanValue()); str = _myMemento.getString(PLOT_ATTRIBUTES.ShowSymbols); if (str != null) _thePlotArea.setShowSymbols(((Boolean) xs.fromXML(str)).booleanValue()); // and the axis orientation final String doWaterTxt = _myMemento.getString(DO_WATERFALL); if (doWaterTxt != null) { _switchAxes.setChecked(Boolean.parseBoolean(doWaterTxt)); _switchAxes.run(); } } catch (final Exception e) { CorePlugin.logError(Status.ERROR, "Failed to read in saved XY Plot data", e); } } @SuppressWarnings("deprecation") private void fillThePlot(final String title, final String units, final formattingOperation theFormatter, final AbstractSeriesDataset dataset) { final StepControl _theStepper = null; // the working variables we rely on later _thePlotArea = null; ValueAxis xAxis = null; XYToolTipGenerator tooltipGenerator = null; // the y axis is common to hi & lo res. Format it here final NumberAxis yAxis = new NumberAxis(units); final Font tickFont = new Font("SansSerif", Font.PLAIN, 14); Font labelFont = new Font("SansSerif", Font.PLAIN, 16); yAxis.setLabelFont(labelFont); yAxis.setTickLabelFont(tickFont); // hmm, see if we are in hi-res mode. If we are, don't use a formatted // y-axis, just use the plain long microseconds // value if (HiResDate.inHiResProcessingMode()) { // final SimpleDateFormat _secFormat = new SimpleDateFormat("ss"); // ok, simple enough for us... final NumberAxis nAxis = new NumberAxis("time (secs.micros)") { /** * */ private static final long serialVersionUID = 1L; // public String getTickLabel(double currentTickValue) // { // long time = (long) currentTickValue; // Date dtg = new HiResDate(0, time).getDate(); // String res = _secFormat.format(dtg) + "." // + DebriefFormatDateTime.formatMicros(new HiResDate(0, time)); // return res; // } }; nAxis.setAutoRangeIncludesZero(false); xAxis = nAxis; // just show the raw data values tooltipGenerator = new StandardXYToolTipGenerator(); } else { // create a date-formatting axis final DateAxis dAxis = new RelativeDateAxis(); dAxis.setStandardTickUnits(DateAxisEditor.createStandardDateTickUnitsAsTickUnits()); xAxis = dAxis; // also create the date-knowledgable tooltip writer tooltipGenerator = new DatedToolTipGenerator(); } xAxis.setTickLabelFont(tickFont); xAxis.setLabelFont(labelFont); // create the special stepper plot final ColourStandardXYItemRenderer theRenderer = new ColourStandardXYItemRenderer(tooltipGenerator, null, null); _thePlot = new StepperXYPlot(null, (RelativeDateAxis) xAxis, yAxis, _theStepper, theRenderer); theRenderer.setPlot(_thePlot); theRenderer.setStroke(new BasicStroke(3.0f)); _thePlot.setRangeGridlineStroke(new BasicStroke(1f)); _thePlot.setDomainGridlineStroke(new BasicStroke(1f)); _thePlot.setRangeGridlinePaint(Color.LIGHT_GRAY); xAxis.setTickMarkStroke(new BasicStroke(1f)); yAxis.setTickMarkStroke(new BasicStroke(1f)); _thePlot.setOutlineStroke(new BasicStroke(2f)); // loop through the datasets, setting the color of each series to the first // color in that series if (dataset instanceof TimeSeriesCollection) { Color seriesCol = null; final TimeSeriesCollection tsc = (TimeSeriesCollection) dataset; for (int i = 0; i < dataset.getSeriesCount(); i++) { final TimeSeries ts = tsc.getSeries(i); if (ts.getItemCount() > 0) { final TimeSeriesDataItem dataItem = ts.getDataItem(0); if (dataItem instanceof ColouredDataItem) { final ColouredDataItem cd = (ColouredDataItem) dataItem; seriesCol = cd.getColor(); _thePlot.getRenderer().setSeriesPaint(i, seriesCol); } } } } // apply any formatting for this choice if (theFormatter != null) { theFormatter.format(_thePlot); } boolean createLegend = dataset.getSeriesCount() > 1; _thePlotArea = new NewFormattedJFreeChart(title, null, _thePlot, createLegend, _theStepper); // set the color of the area surrounding the plot // - naah, don't bother. leave it in the application background color. _thePlotArea.setBackgroundPaint(Color.white); // //////////////////////////////////////////////// // put the holder into one of our special items // //////////////////////////////////////////////// _chartInPanel = new StepperChartPanel(_thePlotArea, true, _theStepper); // ok - we need to fire time-changes to the chart setupFiringChangesToChart(); // format the chart _chartInPanel.setName(title); _chartInPanel.setMouseZoomable(true, true); // and insert into the composite _plotControl.setChart(_thePlotArea); // get the cross hairs ready _thePlot.setDomainCrosshairVisible(true); _thePlot.setRangeCrosshairVisible(true); _thePlot.setDomainCrosshairPaint(Color.GRAY); _thePlot.setRangeCrosshairPaint(Color.GRAY); _thePlot.setDomainCrosshairStroke(new BasicStroke(2)); _thePlot.setRangeCrosshairStroke(new BasicStroke(2)); // and the plot object to display the cross hair value _crosshairValueText = new XYTextAnnotation(" ", 0, 0); _crosshairValueText.setTextAnchor(TextAnchor.TOP_LEFT); _crosshairValueText.setFont(new Font("SansSerif", Font.BOLD, 15)); _crosshairValueText.setPaint(Color.black); _crosshairValueText.setBackgroundPaint(Color.white); _thePlot.addAnnotation(_crosshairValueText); _thePlotArea.addChangeListener(new ChartChangeListener() { @Override public void chartChanged(ChartChangeEvent event) { if (_showSymbols.isShowSymbols() != _thePlotArea.isShowSymbols()) { _showSymbols.updateAction(); } } }); _showSymbols.updateAction(); _thePlotArea.addProgressListener(new ChartProgressListener() { public void chartProgress(final ChartProgressEvent cpe) { if (cpe.getType() != ChartProgressEvent.DRAWING_FINISHED) return; // double-check our label is still in the right place final double xVal = _thePlot.getRangeAxis().getUpperBound(); final double yVal = _thePlot.getDomainAxis().getLowerBound(); boolean annotChanged = false; if (_crosshairValueText.getX() != yVal) { _crosshairValueText.setX(yVal); annotChanged = true; } if (_crosshairValueText.getY() != xVal) { _crosshairValueText.setY(xVal); annotChanged = true; } // and write the text final String numA = MWC.Utilities.TextFormatting.GeneralFormat .formatOneDecimalPlace(_thePlot.getRangeCrosshairValue()); final Date newDate = new Date((long) _thePlot.getDomainCrosshairValue()); final SimpleDateFormat _df = new SimpleDateFormat("HHmm:ss"); _df.setTimeZone(TimeZone.getTimeZone("GMT")); final String dateVal = _df.format(newDate); final String theMessage = " [" + dateVal + "," + numA + "]"; if (!theMessage.equals(_crosshairValueText.getText())) { _crosshairValueText.setText(theMessage); annotChanged = true; } if (annotChanged) { _plotControl.getChart().setNotify(true); } } }); // //////////////////////////////////////////////////// // put the time series into the plot // //////////////////////////////////////////////////// _thePlot.setDataset((XYDataset) dataset); } private void setupFiringChangesToChart() { // see if we've alreay been configured if (_timeListener != null) return; // get the document being edited final IWorkbench wb = PlatformUI.getWorkbench(); final IWorkbenchWindow win = wb.getActiveWorkbenchWindow(); final IWorkbenchPage page = win.getActivePage(); editor = null; // the page might not yet be open... if (page != null) { editor = page.getActiveEditor(); // do we have an active editor? if (editor == null) { // see if there are any editors at all open final IEditorReference[] theEditors = page.getEditorReferences(); for (int i = 0; i < theEditors.length; i++) { final IEditorReference thisE = theEditors[i]; editor = thisE.getEditor(false); // right, see if it has a time manager final TimeProvider tp = (TimeProvider) editor.getAdapter(TimeProvider.class); if (tp != null) { final String hisId = tp.getId(); if (hisId == _myId) break; } } // nope, drop out. return; } } TimeProvider prov = null; if (editor != null) { // get it's time-provider interface prov = (TimeProvider) editor.getAdapter(TimeProvider.class); } else CorePlugin.logError(Status.WARNING, "Failed to identify time provider", null); if (prov != null) { // create our listener _timeListener = new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent evt) { // ok - fire the time change to the chart final HiResDate newDTG = (HiResDate) evt.getNewValue(); // right tell the plot it's new time _thePlot.newTime(null, newDTG, null); // and tell the plot holder to redraw everything _chartInPanel.newTime(null, newDTG, null); refreshPlot(); } }; // add our listener to the time object prov.addListener(_timeListener, TimeProvider.TIME_CHANGED_PROPERTY_NAME); // fire the current time to our chart (just to start us off) _chartInPanel.newTime(null, prov.getTime(), null); refreshPlot(); } } final void wmfToFile() { // create the metafile graphics final String dir; // retrieve the prefs location for writing WMF String tmpDir = CorePlugin.getDefault().getPreferenceStore().getString(PreferenceConstants.WMF_DIRECTORY); // did we find the preference for the WMF location? if (tmpDir != null) { dir = tmpDir; } else { dir = System.getProperty("java.io.tmpdir"); } if (_plotControl == null || _plotControl.isDisposed()) { return; } // ok, setup the export Point size = _plotControl.getSize(); BufferedImage image = new BufferedImage(size.x, size.x, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = image.createGraphics(); _chartInPanel.setSize(size.x, size.y); _chartInPanel.paint(g2d); final MetafileCanvasGraphics2d mf = new MetafileCanvasGraphics2d(dir, (Graphics2D) g2d); doWMF(mf); } final void wmfToClipboard() { // create the metafile graphics final String dir = System.getProperty("java.io.tmpdir"); final MetafileCanvasGraphics2d mf = new MetafileCanvasGraphics2d(dir, (Graphics2D) _chartInPanel.getGraphics()); doWMF(mf); // try to get the filename final String fName = MetafileCanvasGraphics2d.getLastFileName(); // get the dimensions of the last plot operation final Dimension dim = MetafileCanvasGraphics2d.getLastScreenSize(); // try to copy the wmf to the clipboard if (Platform.OS_WIN32.equals(Platform.getOS()) && Platform.ARCH_X86.equals(Platform.getOSArch())) { try { // create the clipboard final ClipboardCopy cc = new ClipboardCopy(); cc.copyWithPixelSize(fName, dim.width, dim.height, false); } catch (final Exception e) { MWC.Utilities.Errors.Trace.trace(e, "Whilst writing WMF to clipboard"); } } else { rtfToClipboard(fName, dim); } } private void rtfToClipboard(final String fName, final Dimension dim) { // Issue #520 - Copy WMF embedded in RTF ByteArrayOutputStream os = null; DataInputStream dis = null; try { os = new ByteArrayOutputStream(); RTFWriter writer = new RTFWriter(os); File file = new File(fName); byte[] data = new byte[(int) file.length()]; dis = new DataInputStream(new FileInputStream(file)); dis.readFully(data); writer.writeHeader(); writer.writeEmfPicture(data, dim.getWidth(), dim.getHeight()); writer.writeTail(); RTFTransfer rtfTransfer = RTFTransfer.getInstance(); Clipboard clipboard = new Clipboard(Display.getDefault()); Object[] rtfData = new Object[] { os.toString() }; clipboard.setContents(rtfData, new Transfer[] { rtfTransfer }); } catch (final Exception e1) { IStatus status = new Status(IStatus.ERROR, PlotViewerPlugin.PLUGIN_ID, e1.getLocalizedMessage(), e1); XYPlotPlugin.getDefault().getLog().log(status); } finally { if (os != null) { try { os.close(); } catch (IOException e1) { // ignore } } if (dis != null) { try { dis.close(); } catch (IOException e1) { // ignore } } } } private final void doWMF(final MetafileCanvasGraphics2d mf) { // get the old background colour final Paint oldColor = _thePlot.getBackgroundPaint(); final Paint oldAreaColor = _thePlotArea.getBackgroundPaint(); // set the background to clear _thePlotArea.setBackgroundPaint(Color.white); _thePlot.setBackgroundPaint(Color.white); // copy the projection final MWC.Algorithms.Projections.FlatProjection fp = new FlatProjection(); Point size = _plotControl.getSize(); Dimension dim = new Dimension(size.x, size.y); fp.setScreenArea(dim); mf.setProjection(fp); // start drawing mf.startDraw(null); // sort out the background colour //final Dimension dim = _plotControl.getSize(); mf.setBackgroundColor(java.awt.Color.white); mf.setColor(mf.getBackgroundColor()); mf.fillRect(0, 0, dim.width, dim.height); try { // ask the canvas to paint the image _chartInPanel.paintWMFComponent(mf); } catch (final Exception e) { CorePlugin.logError(Status.ERROR, "Problem writing WMF", e); } // and finish mf.endDraw(null); // and restore the background colour _thePlot.setBackgroundPaint(oldColor); _thePlotArea.setBackgroundPaint(oldAreaColor); } private void hookContextMenu() { final MenuManager menuMgr = new MenuManager("#PopupMenu"); menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(new IMenuListener() { public void menuAboutToShow(final IMenuManager manager) { XYPlotView.this.fillContextMenu(manager); } }); // Menu menu = menuMgr.createContextMenu(viewer.getControl()); // viewer.getControl().setMenu(menu); // getSite().registerContextMenu(menuMgr, viewer); } private void contributeToActionBars() { final IActionBars bars = getViewSite().getActionBars(); fillLocalPullDown(bars.getMenuManager()); fillLocalToolBar(bars.getToolBarManager()); } private void fillLocalPullDown(final IMenuManager manager) { manager.add(_fitToWindow); manager.add(_switchAxes); manager.add(_hideCrosshairs); manager.add(_growPlot); manager.add(new Separator()); manager.add(_exportToWMF); manager.add(_exportToClipboard); manager.add(_editMyProperties); manager.add(_showSymbols); } void fillContextMenu(final IMenuManager manager) { manager.add(_fitToWindow); manager.add(_exportToWMF); // Other plug-ins can contribute there actions here manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); } private void fillLocalToolBar(final IToolBarManager manager) { manager.add(_fitToWindow); manager.add(_switchAxes); manager.add(_hideCrosshairs); manager.add(_growPlot); manager.add(_exportToWMF); manager.add(_exportToClipboard); manager.add(_copyToClipboard); manager.add(_editMyProperties); manager.add(_showSymbols); manager.add(_listenForDataChanges); } private void makeActions() { _editMyProperties = new Action() { public void run() { editMeInProperties(); } }; _editMyProperties.setText("Configure plot"); _editMyProperties.setToolTipText("Change editable properties for this chart"); _editMyProperties.setImageDescriptor(CorePlugin.getImageDescriptor("icons/16/properties.png")); _listenForDataChanges = new Action("Listen for data changes", SWT.TOGGLE) { public void run() { super.run(); doListenStatusUpdate(); } }; _listenForDataChanges.setToolTipText("Auto-sync with calculated track data."); _listenForDataChanges.setImageDescriptor(CorePlugin.getImageDescriptor("icons/16/follow_time.png")); _switchAxes = new Action("Plot as waterfall", SWT.TOGGLE) { public void run() { try { if (_switchAxes.isChecked()) _thePlot.setOrientation(PlotOrientation.HORIZONTAL); else _thePlot.setOrientation(PlotOrientation.VERTICAL); } catch (final Exception e) { MWC.Utilities.Errors.Trace.trace(e, "whilst performing resize after loading new plot"); } } }; _switchAxes.setToolTipText("Switch axes"); _switchAxes.setImageDescriptor(CorePlugin.getImageDescriptor("icons/16/swap_axis.png")); _growPlot = new Action("Grow times", SWT.TOGGLE) { public void run() { try { _thePlot.setGrowWithTime(_growPlot.isChecked()); // may aswell trigger a redraw _chartInPanel.invalidate(); } catch (final Exception e) { MWC.Utilities.Errors.Trace.trace(e, "whilst performing resize after loading new plot"); } } }; _growPlot.setToolTipText("Expand period covered in sync with scenario time"); _growPlot.setImageDescriptor(CorePlugin.getImageDescriptor("icons/16/clock.png")); _fitToWindow = new Action() { public void run() { try { _thePlot.zoom(0.0); } catch (final Exception e) { MWC.Utilities.Errors.Trace.trace(e, "whilst performing resize after loading new plot"); } } }; _fitToWindow.setText("Fit to window"); _fitToWindow.setToolTipText("Scale the graph to show all data"); _fitToWindow.setImageDescriptor(CorePlugin.getImageDescriptor("icons/16/fit_to_win.png")); _exportToWMF = new Action() { public void run() { wmfToFile(); } }; _exportToWMF.setText("Export to WMF file"); _exportToWMF.setToolTipText("Produce a WMF file of the graph"); _exportToWMF.setImageDescriptor(CorePlugin.getImageDescriptor("icons/16/ex_2word.png")); _exportToClipboard = new Action() { public void run() { bitmapToClipBoard(_chartInPanel); } }; _exportToClipboard.setText("Copy bitmap to Clipboard"); _exportToClipboard.setToolTipText("Place a bitmap image of the graph on the clipboard"); _exportToClipboard.setImageDescriptor(CorePlugin.getImageDescriptor("icons/16/copy.png")); _hideCrosshairs = new Action("Hide crosshairs", SWT.TOGGLE) { public void run() { try { _thePlot.setDomainCrosshairVisible(!_hideCrosshairs.isChecked()); _thePlot.setRangeCrosshairVisible(!_hideCrosshairs.isChecked()); if (_hideCrosshairs.isChecked()) _thePlot.removeAnnotation(_crosshairValueText); else _thePlot.addAnnotation(_crosshairValueText); // may aswell trigger a redraw _chartInPanel.invalidate(); } catch (final Exception e) { MWC.Utilities.Errors.Trace.trace(e, "whilst changing crosshair visibility"); } } }; _hideCrosshairs.setChecked(false); _hideCrosshairs.setToolTipText("Hide the crosshair from the graph (for printing)"); _hideCrosshairs.setImageDescriptor(CorePlugin.getImageDescriptor("icons/16/fix.png")); _copyToClipboard = new Action() { public void run() { if (_thePlot != null) { final TimeSeriesCollection dataset = (TimeSeriesCollection) _thePlot.getDataset(); XYPlotUtilities.copyToClipboard(_chartInPanel.getName(), dataset); } } }; _copyToClipboard.setText("Copy to Clipboard"); _copyToClipboard.setToolTipText("Copies the graph as a text matrix to the clipboard"); _copyToClipboard.setImageDescriptor(CorePlugin.getImageDescriptor("icons/16/export.png")); _showSymbols = new ShowSymbolAction(); } protected void bitmapToClipBoard(JComponent component) { Point size = _plotControl.getSize(); final BufferedImage img = new BufferedImage(size.x, size.y, BufferedImage.TYPE_INT_ARGB); Graphics g = img.getGraphics(); g.setColor(component.getForeground()); g.setFont(component.getFont()); component.setSize(size.x, size.y); component.paint(g); Transferable t = new Transferable() { public DataFlavor[] getTransferDataFlavors() { return new DataFlavor[] { DataFlavor.imageFlavor }; } public boolean isDataFlavorSupported(DataFlavor flavor) { if (flavor == DataFlavor.imageFlavor) return true; return false; } public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { if (isDataFlavorSupported(flavor)) { return img; } return null; } }; ClipboardOwner co = new ClipboardOwner() { public void lostOwnership(java.awt.datatransfer.Clipboard clipboard, Transferable contents) { } }; java.awt.datatransfer.Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard(); cb.setContents(t, co); } protected void doListenStatusUpdate() { final boolean doListen = _listenForDataChanges.isChecked(); if (_provider != null) { final Layers layers = _provider.getLayers(); if (layers != null) { if (doListen) { layers.addDataModifiedListener(_modifiedListener); layers.addDataReformattedListener(_modifiedListener); layers.addDataExtendedListener(_modifiedListener); regenerateData(); } else { layers.removeDataReformattedListener(_modifiedListener); layers.removeDataExtendedListener(_modifiedListener); layers.removeDataModifiedListener(_modifiedListener); } } } } @Override public void dispose() { super.dispose(); if (editorListener != null) { getSite().getWorkbenchWindow().getPartService().removePartListener(editorListener); editorListener = null; } // closing, ditch the listenesr ditchListeners(); } protected void ditchListeners() { if (_provider != null) { final Layers layers = _provider.getLayers(); if (layers != null) { layers.removeDataExtendedListener(_modifiedListener); layers.removeDataReformattedListener(_modifiedListener); layers.removeDataModifiedListener(_modifiedListener); } } } protected void regenerateData() { if (_provider != null) { final AbstractSeriesDataset ds = _provider.getDataset(); if (ds != null) { // store the dataset _dataset = ds; // if we failed in plot creation (maybe a track was empty), // then _thePlot may not have been created. if (_thePlot != null) _thePlot.setDataset((XYDataset) _dataset); } } } private void hookDoubleClickAction() { // viewer.addDoubleClickListener(new IDoubleClickListener() { // public void doubleClick(DoubleClickEvent event) { // doubleClickAction.run(); // } // }); } /** * Passing the focus request to the viewer's control. */ public void setFocus() { // viewer.getControl().setFocus(); if ((_timeListener == null) && (_chartInPanel != null)) { setupFiringChangesToChart(); } } /** * Passing the focus request to the viewer's control. */ public void editMeInProperties() { // do we have any data? if (_thePlotArea != null) { final EditableWrapper wrappedEditable = new EditableWrapper(_thePlotArea); final StructuredSelection _propsAsSelection = new StructuredSelection(wrappedEditable); _selectionHelper.fireNewSelection(_propsAsSelection); } else { System.out.println("we haven't got any properties yet"); } } /** * right, load ourselves from the supplied dataset * * @param site * @param memento * @throws PartInitException */ public void init(final IViewSite site, final IMemento memento) throws PartInitException { // let our parent go for it first super.init(site, memento); // is there any data waiting? We get an empty memento if this is a fresh // view if (memento != null) { _myMemento = memento; } } /** * right - store ourselves into the supplied memento object * * @param memento */ public void saveState(final IMemento memento) { // check we have some data if (_thePlotArea == null) return; // let our parent go for it first super.saveState(memento); memento.putString(TITLE, _myTitle); memento.putString(UNITS, _myUnits); memento.putString(PLOT_ID, _myId); // sort out the fixed duration bits memento.putBoolean(DISPLAY_FIXED_DURATION, this._thePlotArea.getDisplayFixedDuration()); memento.putString(FIXED_DURATION, "" + this._thePlotArea.getFixedDuration().getMillis()); // store whether the axes are switched memento.putString(DO_WATERFALL, Boolean.toString(_switchAxes.isChecked())); final XStream xs = new XStream(new DomDriver()); String str; // String str = xs.toXML(_theFormatter); if (_theFormatter != null) { str = xs.toXML(_theFormatter); memento.putString(FORMATTER, str); } str = xs.toXML(_dataset); memento.putString(DATA, str); // now the other plot bits // @@ storeFont(memento, PLOT_ATTRIBUTES.AxisFont, _thePlotArea.getAxisFont()); storeFont(memento, PLOT_ATTRIBUTES.TickFont, _thePlotArea.getTickFont()); storeFont(memento, PLOT_ATTRIBUTES.TitleFont, _thePlotArea.getTitleFont()); str = xs.toXML(new Integer(_thePlotArea.getDataLineWidth())); memento.putString(PLOT_ATTRIBUTES.LineWidth, str); str = xs.toXML(_thePlotArea.getTitle().getText()); memento.putString(PLOT_ATTRIBUTES.Title, str); str = xs.toXML(_thePlotArea.getX_AxisTitle()); memento.putString(PLOT_ATTRIBUTES.X_Title, str); str = xs.toXML(_thePlotArea.getY_AxisTitle()); memento.putString(PLOT_ATTRIBUTES.Y_Title, str); str = xs.toXML(_thePlotArea.getDateTickUnits()); memento.putString(PLOT_ATTRIBUTES.DateUnits, str); str = xs.toXML(new Boolean(_thePlotArea.getRelativeTimes())); memento.putString(PLOT_ATTRIBUTES.RelativeTimes, str); str = xs.toXML(new Boolean(_thePlotArea.isShowSymbols())); memento.putString(PLOT_ATTRIBUTES.ShowSymbols, str); } private void storeFont(final IMemento memento, final String entryHeader, final Font theFont) { // write elements memento.putInteger(entryHeader + "_SIZE", theFont.getSize()); memento.putString(entryHeader + "_FAMILY", theFont.getFamily()); memento.putInteger(entryHeader + "_STYLE", theFont.getStyle()); } private Font getFont(final IMemento memento, final String entryHeader) { Font res = null; final String family = memento.getString(entryHeader + "_FAMILY"); final Integer size = memento.getInteger(entryHeader + "_SIZE"); final Integer style = memento.getInteger(entryHeader + "_STYLE"); if (family != null) res = new Font(family, style, size); return res; } public static class StringHolder { private String _myString; public StringHolder() { } public StringHolder(final String theString) { _myString = theString; } public String get_myString() { return _myString; } public void set_myString(final String string) { _myString = string; } } public void showPlot(final String theTitle, final DatasetProvider prov, final String units2, final formattingOperation theFormatter, final String thePlotId) { // right, store the incoming data, so we can save it when/if // Eclipse closes with this view still open _myTitle = theTitle; _myUnits = units2; _theFormatter = theFormatter; _myId = thePlotId; // ok, update the plot. this.setPartName(_myTitle); _provider = prov; if (_provider != null) { final AbstractSeriesDataset ds = _provider.getDataset(); if (ds != null) { // store the dataset _dataset = ds; // ok, fill in the plot fillThePlot(_myTitle, _myUnits, _theFormatter, ds); } } // and set the right value _listenForDataChanges.setEnabled(true); // and tell it to start listening _listenForDataChanges.setChecked(true); // and process the new state doListenStatusUpdate(); } private void refreshPlot() { Runnable runnable = new Runnable() { @Override public void run() { if (_plotControl != null && !_plotControl.isDisposed()) { _plotControl.setSize(0, 0); _plotControl.getParent().layout(true, true); _plotControl.redraw(); _plotControl.update(); } } }; if (Display.getCurrent() != null) { runnable.run(); } else { Display.getDefault().syncExec(runnable); } } private class ShowSymbolAction extends Action { private static final String SYMBOL_ON = "icons/16/symbol_on.png"; private static final String SYMBOL_OFF = "icons/16/symbol_off.png"; private boolean showSymbols; public ShowSymbolAction() { super(); showSymbols = false; setText("Show symbols"); setToolTipText("Show symbols"); setImageDescriptor(CorePlugin.getImageDescriptor(SYMBOL_OFF)); } @Override public void run() { if (_thePlotArea != null) { if (!_thePlotArea.isShowSymbols()) { _thePlotArea.setShowSymbols(true); setText("Hide symbols"); setToolTipText("Hide symbols"); setImageDescriptor(CorePlugin.getImageDescriptor(SYMBOL_ON)); } else { _thePlotArea.setShowSymbols(false); setText("Show symbols"); setToolTipText("Show symbols"); setImageDescriptor(CorePlugin.getImageDescriptor(SYMBOL_OFF)); } showSymbols = _thePlotArea.isShowSymbols(); } } public void updateAction() { if (_thePlotArea != null) { if (_thePlotArea.isShowSymbols()) { setText("Hide symbols"); setToolTipText("Hide symbols"); setImageDescriptor(CorePlugin.getImageDescriptor(SYMBOL_ON)); } else { setText("Show symbols"); setToolTipText("Show symbols"); setImageDescriptor(CorePlugin.getImageDescriptor(SYMBOL_OFF)); } showSymbols = _thePlotArea.isShowSymbols(); } } public boolean isShowSymbols() { return showSymbols; } } }