+/*******************************************************************************\r
+ * Copyright (c) 2011 Association for Decentralized Information Management in\r
+ * Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.charts.editor;\r
+\r
+import java.awt.Toolkit;\r
+import java.awt.datatransfer.Clipboard;\r
+import java.awt.datatransfer.StringSelection;\r
+import java.io.BufferedWriter;\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.OutputStreamWriter;\r
+import java.lang.reflect.InvocationTargetException;\r
+import java.nio.charset.Charset;\r
+import java.util.Collections;\r
+import java.util.List;\r
+import java.util.concurrent.atomic.AtomicBoolean;\r
+\r
+import org.eclipse.core.commands.AbstractHandler;\r
+import org.eclipse.core.commands.ExecutionEvent;\r
+import org.eclipse.core.commands.ExecutionException;\r
+import org.eclipse.core.runtime.IProgressMonitor;\r
+import org.eclipse.core.runtime.preferences.InstanceScope;\r
+import org.eclipse.jface.action.IStatusLineManager;\r
+import org.eclipse.jface.operation.IRunnableWithProgress;\r
+import org.eclipse.jface.preference.IPreferenceStore;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.widgets.FileDialog;\r
+import org.eclipse.swt.widgets.Shell;\r
+import org.eclipse.ui.IEditorPart;\r
+import org.eclipse.ui.PlatformUI;\r
+import org.eclipse.ui.handlers.HandlerUtil;\r
+import org.eclipse.ui.preferences.ScopedPreferenceStore;\r
+import org.simantics.charts.ui.CSVProgressMonitor;\r
+import org.simantics.databoard.binding.error.BindingException;\r
+import org.simantics.databoard.parser.StringEscapeUtils;\r
+import org.simantics.databoard.util.Bean;\r
+import org.simantics.databoard.util.StreamUtil;\r
+import org.simantics.history.HistoryException;\r
+import org.simantics.history.ItemManager;\r
+import org.simantics.history.csv.CSVFormatter;\r
+import org.simantics.history.csv.ColumnSeparator;\r
+import org.simantics.history.csv.DecimalSeparator;\r
+import org.simantics.history.csv.ExportInterpolation;\r
+import org.simantics.history.util.subscription.SamplingFormat;\r
+import org.simantics.modeling.preferences.CSVPreferences;\r
+import org.simantics.trend.configuration.TrendItem;\r
+import org.simantics.trend.impl.TrendNode;\r
+import org.simantics.utils.format.FormattingUtils;\r
+import org.simantics.utils.ui.ErrorLogger;\r
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class ChartCopyHandler extends AbstractHandler {\r
+\r
+ String lastFile;\r
+ \r
+ @Override\r
+ public Object execute(ExecutionEvent event) throws ExecutionException {\r
+ IEditorPart ep = HandlerUtil.getActiveEditor(event);\r
+ if (ep instanceof TimeSeriesEditor == false) return null;\r
+ TimeSeriesEditor editor = (TimeSeriesEditor) ep;\r
+ final TrendNode trendNode = editor.trendNode;\r
+ IStatusLineManager status = editor.getEditorSite().getActionBars().getStatusLineManager();\r
+ final Shell shell = HandlerUtil.getActiveShell(event);\r
+\r
+ final AtomicBoolean result = new AtomicBoolean(false);\r
+ try {\r
+ PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() {\r
+ @Override\r
+ public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {\r
+ result.set( copyDataToClipboard(monitor, trendNode, Format.JOINED_TIME, shell) );\r
+ }\r
+ });\r
+ if (!result.get()) {\r
+ status.setMessage("No data to copy");\r
+ } else {\r
+ status.setMessage("Copied chart data to clipboard");\r
+ }\r
+ status.setErrorMessage(null);\r
+ } catch (InvocationTargetException e) {\r
+ ErrorLogger.defaultLogError(e.getCause());\r
+ } catch (InterruptedException e) {\r
+ ErrorLogger.defaultLogError(e);\r
+ }\r
+\r
+ return null;\r
+ }\r
+\r
+ static enum Format {\r
+ TIME_VALUE_PAIRS,\r
+ JOINED_TIME\r
+ }\r
+\r
+ public boolean copyDataToClipboard(IProgressMonitor monitor, TrendNode t, Format format, final Shell shell) {\r
+ Charset UTF8 = Charset.forName("UTF-8");\r
+ try {\r
+ // String builder can be really slow when it is extended many times over. \r
+ // Instead stream to file with buffering\r
+// StringBuilder sb = new StringBuilder();\r
+ IPreferenceStore csvnode = new ScopedPreferenceStore( InstanceScope.INSTANCE, CSVPreferences.P_NODE );\r
+ String ext = csvnode.getString(CSVPreferences.P_CSV_FILE_EXTENSION);\r
+ File tmpFile = File.createTempFile("clipboard", ext);\r
+ tmpFile.deleteOnExit();\r
+ FileOutputStream fos = new FileOutputStream(tmpFile); \r
+ BufferedWriter w = new BufferedWriter(new OutputStreamWriter(fos, UTF8));\r
+ try {\r
+ ItemManager im = new ItemManager( t.historian.getItems() );\r
+ CSVFormatter formatter = new CSVFormatter(); \r
+ formatter.setTimeRange(t.horizRuler.from, t.horizRuler.end);\r
+ \r
+ // Write preferences\r
+ formatter.setStartTime( csvnode.getDouble(CSVPreferences.P_CSV_START_TIME) );\r
+ formatter.setTimeStep( csvnode.getDouble(CSVPreferences.P_CSV_TIME_STEP) );\r
+ formatter.setDecimalSeparator( DecimalSeparator.fromPreference(csvnode.getString(CSVPreferences.P_CSV_DECIMAL_SEPARATOR) ) );\r
+ formatter.setColumnSeparator( ColumnSeparator.fromPreference(StringEscapeUtils.unescape( csvnode.getString(CSVPreferences.P_CSV_COLUMN_SEPARATOR) ) ) );\r
+ formatter.setResample( csvnode.getBoolean(CSVPreferences.P_CSV_RESAMPLE) );\r
+ formatter.setNumberInterpolation( ExportInterpolation.fromPreference (csvnode.getString(CSVPreferences.P_CSV_SAMPLING_MODE) ) );\r
+ formatter.setTimeFormat( FormattingUtils.significantDigitFormat( csvnode.getInt(CSVPreferences.P_CSV_TIME_DIGITS) ) );\r
+ formatter.setFloatFormat( FormattingUtils.significantDigitFormat( csvnode.getInt(CSVPreferences.P_CSV_FLOAT_DIGITS) ) );\r
+ formatter.setNumberFormat( FormattingUtils.significantDigitFormat( csvnode.getInt(CSVPreferences.P_CSV_DOUBLE_DIGITS) ) );\r
+ \r
+ for (TrendItem i : t.spec.items) {\r
+ if (i.hidden) continue;\r
+ List<Bean> items = im.search("variableId", i.variableId);\r
+ Collections.sort(items, SamplingFormat.INTERVAL_COMPARATOR);\r
+ if (items.isEmpty()) continue;\r
+ Bean config = items.get(0);\r
+ String historyId = (String) config.getFieldUnchecked("id");\r
+ formatter.addItem( t.historian, historyId, i.simpleLabel, i.variableReference, i.unit);\r
+ }\r
+ formatter.sort();\r
+ switch (format) {\r
+ case TIME_VALUE_PAIRS: \r
+// formatter.formulate1(new CSVProgressMonitor(monitor), w);\r
+ break;\r
+ case JOINED_TIME:\r
+ formatter.formulate2(new CSVProgressMonitor(monitor), w);\r
+ break;\r
+ default:\r
+ throw new UnsupportedOperationException("unsupported format " + format);\r
+ }\r
+ w.flush();\r
+ \r
+ if (tmpFile.length()==0) return false;\r
+ \r
+ Toolkit toolkit = Toolkit.getDefaultToolkit();\r
+ Clipboard clipboard = toolkit.getSystemClipboard();\r
+ w.flush();\r
+ fos.close();\r
+ fos = null;\r
+ \r
+ System.out.println("Exported to "+tmpFile+" size: "+tmpFile.length());\r
+ if ( tmpFile.length() > 10*1024*1024 ) {\r
+// String msg = "The data has been written to temporary file:\n"+tmpFile.getCanonicalPath();\r
+// ShowMessage.showInformation( shell.getDisplay(), "Too much data for clipboard.", msg);\r
+ final File csvFile = tmpFile;\r
+ tmpFile = null;\r
+ shell.getDisplay().asyncExec( new Runnable() {\r
+ @Override\r
+ public void run() {\r
+ FileDialog fd = new FileDialog(shell, SWT.SAVE);\r
+ fd.setText("Write CSV to File");\r
+ fd.setFileName( lastFile!=null ? lastFile : csvFile.getAbsolutePath() );\r
+ String newFile = fd.open();\r
+ if ( newFile != null ) {\r
+ lastFile = newFile;\r
+ File ff = new File( newFile );\r
+ ff.delete();\r
+ csvFile.renameTo( ff );\r
+ } else {\r
+ csvFile.delete();\r
+ }\r
+ }} ); \r
+ } else {\r
+ String str = StreamUtil.readString(tmpFile, UTF8);\r
+ \r
+ StringSelection strSel = new StringSelection(str);\r
+ clipboard.setContents(strSel, null);\r
+ }\r
+ \r
+ } catch (BindingException e1) {\r
+ ErrorLogger.defaultLogError(e1);\r
+ return false;\r
+ } catch (IOException e) {\r
+ ErrorLogger.defaultLogError(e);\r
+ } finally {\r
+ if ( fos != null ) try { fos.close(); } catch (IOException e) { ErrorLogger.defaultLogError(e); }\r
+ if ( tmpFile != null ) tmpFile.delete();\r
+ } \r
+\r
+ return true;\r
+ } catch (HistoryException e) {\r
+ ErrorLogger.defaultLogError(e);\r
+ } catch (IOException e) {\r
+ ErrorLogger.defaultLogError(e);\r
+ }\r
+ return false;\r
+ }\r
+\r
+}\r