183000045aed17cd3f2f6b44ed60fe834e1f8292
[simantics/platform.git] / bundles / org.simantics.charts / src / org / simantics / charts / editor / ChartCopyHandler.java
1 /*******************************************************************************
2  * Copyright (c) 2011,2020 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *     Semantum Oy - #501
12  *******************************************************************************/
13 package org.simantics.charts.editor;
14
15 import java.awt.Toolkit;
16 import java.awt.datatransfer.Clipboard;
17 import java.awt.datatransfer.StringSelection;
18 import java.io.BufferedWriter;
19 import java.io.File;
20 import java.io.FileOutputStream;
21 import java.io.IOException;
22 import java.io.OutputStreamWriter;
23 import java.lang.reflect.InvocationTargetException;
24 import java.nio.charset.Charset;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.concurrent.atomic.AtomicBoolean;
28
29 import org.eclipse.core.commands.AbstractHandler;
30 import org.eclipse.core.commands.ExecutionEvent;
31 import org.eclipse.core.commands.ExecutionException;
32 import org.eclipse.core.runtime.IProgressMonitor;
33 import org.eclipse.core.runtime.preferences.InstanceScope;
34 import org.eclipse.jface.action.IStatusLineManager;
35 import org.eclipse.jface.operation.IRunnableWithProgress;
36 import org.eclipse.jface.preference.IPreferenceStore;
37 import org.eclipse.jface.preference.PreferenceDialog;
38 import org.eclipse.jface.window.Window;
39 import org.eclipse.swt.SWT;
40 import org.eclipse.swt.widgets.FileDialog;
41 import org.eclipse.swt.widgets.Shell;
42 import org.eclipse.ui.IEditorPart;
43 import org.eclipse.ui.PlatformUI;
44 import org.eclipse.ui.dialogs.PreferencesUtil;
45 import org.eclipse.ui.handlers.HandlerUtil;
46 import org.eclipse.ui.preferences.ScopedPreferenceStore;
47 import org.simantics.charts.ui.CSVProgressMonitor;
48 import org.simantics.databoard.binding.error.BindingException;
49 import org.simantics.databoard.parser.StringEscapeUtils;
50 import org.simantics.databoard.util.Bean;
51 import org.simantics.databoard.util.StreamUtil;
52 import org.simantics.history.HistoryException;
53 import org.simantics.history.ItemManager;
54 import org.simantics.history.csv.CSVFormatter;
55 import org.simantics.history.csv.ColumnSeparator;
56 import org.simantics.history.csv.DecimalSeparator;
57 import org.simantics.history.csv.ExportInterpolation;
58 import org.simantics.history.util.subscription.SamplingFormat;
59 import org.simantics.modeling.preferences.CSVPreferences;
60 import org.simantics.trend.configuration.TrendItem;
61 import org.simantics.trend.impl.TrendNode;
62 import org.simantics.utils.format.FormattingUtils;
63 import org.simantics.utils.ui.ErrorLogger;
64
65 /**
66  * @author Tuukka Lehtonen
67  */
68 public class ChartCopyHandler extends AbstractHandler {
69
70         String lastFile;
71         
72     @Override
73     public Object execute(ExecutionEvent event) throws ExecutionException {
74         IEditorPart ep = HandlerUtil.getActiveEditor(event);
75         if (ep instanceof TimeSeriesEditor == false) return null;
76         TimeSeriesEditor editor = (TimeSeriesEditor) ep;
77         final TrendNode trendNode = editor.trendNode;
78         IStatusLineManager status = editor.getEditorSite().getActionBars().getStatusLineManager();
79         final Shell shell = HandlerUtil.getActiveShell(event);
80
81         // Find a good value to use for START_TIME based on the current horizontal ruler
82         // time range and the data start time.
83         IPreferenceStore csvnode = new ScopedPreferenceStore( InstanceScope.INSTANCE, CSVPreferences.P_NODE );
84         double oldStartTime = csvnode.getDouble(CSVPreferences.P_CSV_START_TIME);
85         double timeStep = csvnode.getDouble(CSVPreferences.P_CSV_TIME_STEP);
86         double visibleChartMinTime = trendNode.horizRuler.from;
87         double dataStartTime = trendNode.horizRuler.getItemFromTime();
88         // Find the first sample time that contains data if startTime < _from 
89         double n = Math.max(0, Math.ceil((visibleChartMinTime-dataStartTime) / timeStep));
90         double temporaryStartTime = dataStartTime + n*timeStep;
91         csvnode.setValue(CSVPreferences.P_CSV_START_TIME, temporaryStartTime);
92
93         try {
94             PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(
95                     shell, "org.simantics.modeling.csv.preferences",
96                     new String[] { "org.simantics.modeling.csv.preferences" },
97                     null);
98             dialog.setMessage("Select Used CSV Export Settings");
99             if (dialog.open() != Window.OK)
100                 return null;
101
102             AtomicBoolean result = new AtomicBoolean(false);
103             PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() {
104                 @Override
105                 public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
106                     result.set( copyDataToClipboard(monitor, trendNode, Format.JOINED_TIME, shell) );
107                 }
108             });
109             if (!result.get()) {
110                 status.setMessage("No data to copy");
111             } else {
112                 status.setMessage("Copied chart data to clipboard");
113             }
114             status.setErrorMessage(null);
115         } catch (InvocationTargetException e) {
116             ErrorLogger.defaultLogError(e.getCause());
117         } catch (InterruptedException e) {
118             ErrorLogger.defaultLogError(e);
119         } finally {
120             csvnode.setValue(CSVPreferences.P_CSV_START_TIME, oldStartTime);
121         }
122
123         return null;
124     }
125
126     static enum Format {
127         TIME_VALUE_PAIRS,
128         JOINED_TIME
129     }
130
131     public boolean copyDataToClipboard(IProgressMonitor monitor, TrendNode t, Format format, final Shell shell) {
132         Charset UTF8        = Charset.forName("UTF-8");
133         try {
134                 // String builder can be really slow when it is extended many times over. 
135                 // Instead stream to file with buffering
136 //            StringBuilder sb = new StringBuilder();
137                 IPreferenceStore csvnode = new ScopedPreferenceStore( InstanceScope.INSTANCE, CSVPreferences.P_NODE );
138                 String ext = csvnode.getString(CSVPreferences.P_CSV_FILE_EXTENSION);
139                 File tmpFile = File.createTempFile("clipboard", ext);
140                 tmpFile.deleteOnExit();
141                 FileOutputStream fos = new FileOutputStream(tmpFile); 
142                 BufferedWriter w = new BufferedWriter(new OutputStreamWriter(fos, UTF8));
143                 try {
144                         ItemManager im = new ItemManager( t.historian.getItems() );
145                     CSVFormatter formatter = new CSVFormatter();            
146                     formatter.setTimeRange(t.horizRuler.from, t.horizRuler.end);
147                     
148                     // Write preferences
149                     formatter.setStartTime( csvnode.getDouble(CSVPreferences.P_CSV_START_TIME) );
150                     formatter.setTimeStep( csvnode.getDouble(CSVPreferences.P_CSV_TIME_STEP) );
151                     formatter.setDecimalSeparator( DecimalSeparator.fromPreference(csvnode.getString(CSVPreferences.P_CSV_DECIMAL_SEPARATOR) ) );
152                     formatter.setColumnSeparator( ColumnSeparator.fromPreference(StringEscapeUtils.unescape( csvnode.getString(CSVPreferences.P_CSV_COLUMN_SEPARATOR) ) ) );
153                     formatter.setResample( csvnode.getBoolean(CSVPreferences.P_CSV_RESAMPLE) );
154                     formatter.setNumberInterpolation( ExportInterpolation.fromPreference (csvnode.getString(CSVPreferences.P_CSV_SAMPLING_MODE) ) );
155                     formatter.setTimeFormat( FormattingUtils.significantDigitFormat( csvnode.getInt(CSVPreferences.P_CSV_TIME_DIGITS) ) );
156                     formatter.setFloatFormat( FormattingUtils.significantDigitFormat( csvnode.getInt(CSVPreferences.P_CSV_FLOAT_DIGITS) ) );
157                     formatter.setNumberFormat( FormattingUtils.significantDigitFormat( csvnode.getInt(CSVPreferences.P_CSV_DOUBLE_DIGITS) ) );
158                     
159                     for (TrendItem i : t.spec.items) {
160                         if (i.hidden) continue;
161                         List<Bean> items = im.search("variableId", i.variableId);
162                         Collections.sort(items, SamplingFormat.INTERVAL_COMPARATOR);
163                         if (items.isEmpty()) continue;
164                         Bean config = items.get(0);
165                         String historyId = (String) config.getFieldUnchecked("id");
166                         formatter.addItem( t.historian, historyId, i.simpleLabel, i.variableReference, i.unit);
167                     }
168                     formatter.sort();
169                     switch (format) {
170                         case TIME_VALUE_PAIRS: 
171 //                                              formatter.formulate1(new CSVProgressMonitor(monitor), w);
172                             break;
173                         case JOINED_TIME:
174                             formatter.formulate2(new CSVProgressMonitor(monitor), w);
175                             break;
176                         default:
177                             throw new UnsupportedOperationException("unsupported format " + format);
178                     }
179                     w.flush();
180         
181                     if (tmpFile.length()==0) return false;
182         
183                     Toolkit toolkit = Toolkit.getDefaultToolkit();
184                     Clipboard clipboard = toolkit.getSystemClipboard();
185                     w.flush();
186                     fos.close();
187                     fos = null;
188                         
189                     System.out.println("Exported to "+tmpFile+" size: "+tmpFile.length());
190                     if ( tmpFile.length() > 10*1024*1024 ) {
191 //                      String msg = "The data has been written to temporary file:\n"+tmpFile.getCanonicalPath();
192 //                      ShowMessage.showInformation( shell.getDisplay(), "Too much data for clipboard.", msg);
193                         final File csvFile = tmpFile;
194                         tmpFile = null;
195                         shell.getDisplay().asyncExec( new Runnable() {
196                                                 @Override
197                                                 public void run() {
198                                         FileDialog fd = new FileDialog(shell, SWT.SAVE);
199                                         fd.setText("Write CSV to File");
200                                         fd.setFileName( lastFile!=null ? lastFile : csvFile.getAbsolutePath() );
201                                         String newFile = fd.open();
202                                         if ( newFile != null ) {
203                                                 lastFile = newFile;
204                                                 File ff = new File( newFile );
205                                                 ff.delete();
206                                                 csvFile.renameTo( ff );
207                                         } else {
208                                                 csvFile.delete();
209                                         }
210                                                 }} ); 
211                     } else {
212                             String str = StreamUtil.readString(tmpFile, UTF8);
213                             
214                             StringSelection strSel = new StringSelection(str);
215                             clipboard.setContents(strSel, null);
216                     }
217                     
218                         } catch (BindingException e1) {
219                                 ErrorLogger.defaultLogError(e1);
220                                 return false;
221                         } catch (IOException e) {
222                                 ErrorLogger.defaultLogError(e);
223                         } finally {
224                         if ( fos != null ) try { fos.close(); } catch (IOException e) { ErrorLogger.defaultLogError(e); }
225                         if ( tmpFile != null ) tmpFile.delete();
226                         }                       
227
228             return true;
229         } catch (HistoryException e) {
230             ErrorLogger.defaultLogError(e);
231         } catch (IOException e) {
232             ErrorLogger.defaultLogError(e);
233                 }
234         return false;
235     }
236
237 }