]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.charts/src/org/simantics/charts/editor/e4/TimeSeriesEditor.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.charts / src / org / simantics / charts / editor / e4 / TimeSeriesEditor.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2011 Association for Decentralized Information Management in\r
3  * Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.charts.editor.e4;\r
13 \r
14 import java.awt.geom.Point2D;\r
15 import java.util.Collections;\r
16 import java.util.List;\r
17 import java.util.Set;\r
18 import java.util.UUID;\r
19 import java.util.logging.Level;\r
20 import java.util.logging.Logger;\r
21 \r
22 import javax.inject.Inject;\r
23 \r
24 import org.eclipse.core.runtime.IAdaptable;\r
25 import org.eclipse.core.runtime.IStatus;\r
26 import org.eclipse.core.runtime.Status;\r
27 import org.eclipse.core.runtime.preferences.IEclipsePreferences;\r
28 import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;\r
29 import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;\r
30 import org.eclipse.core.runtime.preferences.InstanceScope;\r
31 import org.eclipse.e4.ui.model.application.MApplication;\r
32 import org.eclipse.e4.ui.model.application.MApplicationElement;\r
33 import org.eclipse.e4.ui.model.application.ui.MUIElement;\r
34 import org.eclipse.e4.ui.model.application.ui.basic.MPart;\r
35 import org.eclipse.e4.ui.model.application.ui.menu.MToolBar;\r
36 import org.eclipse.e4.ui.services.EContextService;\r
37 import org.eclipse.e4.ui.workbench.modeling.EModelService;\r
38 import org.eclipse.e4.ui.workbench.modeling.ISelectionListener;\r
39 import org.eclipse.jface.action.IMenuListener;\r
40 import org.eclipse.jface.action.IMenuManager;\r
41 import org.eclipse.jface.action.MenuManager;\r
42 import org.eclipse.jface.action.Separator;\r
43 import org.eclipse.jface.resource.ImageDescriptor;\r
44 import org.eclipse.jface.viewers.StructuredSelection;\r
45 import org.eclipse.swt.SWT;\r
46 import org.eclipse.swt.graphics.Point;\r
47 import org.eclipse.swt.widgets.Composite;\r
48 import org.eclipse.swt.widgets.Display;\r
49 import org.eclipse.swt.widgets.Menu;\r
50 import org.eclipse.swt.widgets.Text;\r
51 import org.eclipse.ui.IEditorInput;\r
52 import org.eclipse.ui.IEditorSite;\r
53 import org.simantics.Simantics;\r
54 import org.simantics.browsing.ui.model.browsecontexts.BrowseContext;\r
55 import org.simantics.charts.Activator;\r
56 import org.simantics.charts.ITrendSupport;\r
57 import org.simantics.charts.editor.ActiveRunQuery;\r
58 import org.simantics.charts.editor.ChartData;\r
59 import org.simantics.charts.editor.ChartKeyBindings;\r
60 import org.simantics.charts.editor.ChartKeys;\r
61 import org.simantics.charts.editor.ChartPasteHandler2;\r
62 import org.simantics.charts.editor.HideItemsAction;\r
63 import org.simantics.charts.editor.MoveHairlineAction;\r
64 import org.simantics.charts.editor.PropertiesAction;\r
65 import org.simantics.charts.editor.SendCommandAction;\r
66 import org.simantics.charts.editor.SubscriptionDropParticipant;\r
67 import org.simantics.charts.editor.TrackExperimentTimeAction;\r
68 import org.simantics.charts.ontology.ChartResource;\r
69 import org.simantics.charts.preference.ChartPreferences;\r
70 import org.simantics.charts.query.FindChartItemForTrendItem;\r
71 import org.simantics.charts.query.MilestoneSpecQuery;\r
72 import org.simantics.charts.query.SetProperty;\r
73 import org.simantics.charts.query.TrendSpecQuery;\r
74 import org.simantics.charts.ui.ChartLinkData;\r
75 import org.simantics.databoard.Bindings;\r
76 import org.simantics.databoard.util.ObjectUtils;\r
77 import org.simantics.db.AsyncReadGraph;\r
78 import org.simantics.db.ReadGraph;\r
79 import org.simantics.db.Resource;\r
80 import org.simantics.db.Session;\r
81 import org.simantics.db.common.request.ParametrizedRead;\r
82 import org.simantics.db.common.request.UniqueRead;\r
83 import org.simantics.db.exception.DatabaseException;\r
84 import org.simantics.db.layer0.request.combinations.Combinators;\r
85 import org.simantics.db.layer0.variable.RVI;\r
86 import org.simantics.db.layer0.variable.RVIBuilder;\r
87 import org.simantics.db.layer0.variable.Variable;\r
88 import org.simantics.db.layer0.variable.Variables;\r
89 import org.simantics.db.layer0.variable.Variables.Role;\r
90 import org.simantics.db.procedure.AsyncListener;\r
91 import org.simantics.db.procedure.SyncListener;\r
92 import org.simantics.diagram.participant.SGFocusParticipant;\r
93 import org.simantics.diagram.participant.e4.ContextUtil;\r
94 import org.simantics.g2d.canvas.ICanvasContext;\r
95 import org.simantics.g2d.canvas.impl.CanvasContext;\r
96 import org.simantics.g2d.chassis.AWTChassis;\r
97 import org.simantics.g2d.chassis.ICanvasChassis;\r
98 import org.simantics.g2d.chassis.IChassisListener;\r
99 import org.simantics.g2d.chassis.SWTChassis;\r
100 import org.simantics.g2d.participant.KeyToCommand;\r
101 import org.simantics.g2d.participant.TimeParticipant;\r
102 import org.simantics.g2d.utils.CanvasUtils;\r
103 import org.simantics.history.Collector;\r
104 import org.simantics.history.HistoryManager;\r
105 import org.simantics.history.impl.FileHistory;\r
106 import org.simantics.project.IProject;\r
107 import org.simantics.scenegraph.INode;\r
108 import org.simantics.scenegraph.g2d.events.Event;\r
109 import org.simantics.scenegraph.g2d.events.EventTypes;\r
110 import org.simantics.scenegraph.g2d.events.IEventHandler;\r
111 import org.simantics.scenegraph.g2d.events.MouseEvent;\r
112 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
113 import org.simantics.scenegraph.g2d.events.command.Commands;\r
114 import org.simantics.selectionview.StandardPropertyPage;\r
115 import org.simantics.simulation.data.Datasource;\r
116 import org.simantics.simulation.experiment.ExperimentState;\r
117 import org.simantics.simulation.experiment.IExperiment;\r
118 import org.simantics.simulation.experiment.IExperimentListener;\r
119 import org.simantics.simulation.ontology.SimulationResource;\r
120 import org.simantics.trend.TrendInitializer;\r
121 import org.simantics.trend.TrendInitializer.StepListener;\r
122 import org.simantics.trend.configuration.ItemPlacement;\r
123 import org.simantics.trend.configuration.LineQuality;\r
124 import org.simantics.trend.configuration.TimeFormat;\r
125 import org.simantics.trend.configuration.TrendSpec;\r
126 import org.simantics.trend.impl.HorizRuler;\r
127 import org.simantics.trend.impl.ItemNode;\r
128 import org.simantics.trend.impl.MilestoneSpec;\r
129 import org.simantics.trend.impl.TrendNode;\r
130 import org.simantics.trend.impl.TrendParticipant;\r
131 import org.simantics.ui.workbench.IPropertyPage;\r
132 import org.simantics.ui.workbench.IResourceEditorInput;\r
133 import org.simantics.ui.workbench.ResourceEditorInput;\r
134 import org.simantics.ui.workbench.action.PerformDefaultAction;\r
135 import org.simantics.ui.workbench.e4.E4ResourceEditorBase;\r
136 import org.simantics.ui.workbench.editor.input.InputValidationCombinators;\r
137 import org.simantics.utils.datastructures.hints.HintListenerAdapter;\r
138 import org.simantics.utils.datastructures.hints.IHintContext;\r
139 import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
140 import org.simantics.utils.datastructures.hints.IHintObservable;\r
141 import org.simantics.utils.format.ValueFormat;\r
142 import org.simantics.utils.threads.AWTThread;\r
143 import org.simantics.utils.threads.IThreadWorkQueue;\r
144 import org.simantics.utils.threads.SWTThread;\r
145 import org.simantics.utils.threads.ThreadUtils;\r
146 import org.simantics.utils.ui.BundleUtils;\r
147 import org.simantics.utils.ui.ErrorLogger;\r
148 import org.simantics.utils.ui.ExceptionUtils;\r
149 import org.simantics.utils.ui.SWTUtils;\r
150 import org.simantics.utils.ui.dialogs.ShowMessage;\r
151 import org.simantics.utils.ui.jface.ActiveSelectionProvider;\r
152 \r
153 /**\r
154  * TimeSeriesEditor is an interactive part that draws a time series chart.\r
155  * \r
156  * The configuration model is {@link TrendSpec} which is read through\r
157  * {@link TrendSpecQuery}. In Simantics Environment the\r
158  * editor input is {@link ResourceEditorInput}.\r
159  * \r
160  * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
161  * @author Tuukka Lehtonen\r
162  */\r
163 public class TimeSeriesEditor extends E4ResourceEditorBase implements IAdaptable {\r
164 \r
165     ParametrizedRead<IResourceEditorInput, Boolean> INPUT_VALIDATOR =\r
166         Combinators.compose(\r
167                 InputValidationCombinators.hasURI(),\r
168                 InputValidationCombinators.extractInputResource()\r
169         );\r
170 \r
171     protected ParametrizedRead<IResourceEditorInput, Boolean> getInputValidator() {\r
172         return INPUT_VALIDATOR;\r
173     }\r
174 \r
175     /**\r
176      * The root property browse context of the time series editor. A transitive\r
177      * closure is calculated for this context.\r
178      */\r
179     private static String       ROOT_PROPERTY_BROWSE_CONTEXT = ChartResource.URIs.ChartBrowseContext;\r
180 \r
181     /**\r
182      * ID of the this editor part extension.\r
183      */\r
184     public static final String  ID                  = "org.simantics.charts.editor.timeseries";\r
185 \r
186     public static final String CONTRIBUTION_URI     = "bundleclass://org.simantics.charts/org.simantics.charts.editor.e4.TimeSeriesEditor";\r
187 \r
188     private static final String CONTEXT_MENU_ID     = "#timeSeriesChart";\r
189 \r
190     private IEclipsePreferences chartPreferenceNode;\r
191 \r
192     private final ImageDescriptor IMG_ZOOM_TO_FIT = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/horizAndVert16.png");\r
193     private final ImageDescriptor IMG_ZOOM_TO_FIT_HORIZ = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/horiz16.png");\r
194     private final ImageDescriptor IMG_ZOOM_TO_FIT_VERT = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/vert16.png");\r
195     private final ImageDescriptor IMG_AUTOSCALE = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/autoscale16.png");\r
196 \r
197     IPreferenceChangeListener preferenceListener = new IPreferenceChangeListener() {\r
198 \r
199         @Override\r
200         public void preferenceChange(PreferenceChangeEvent event) {\r
201             if (isDisposed()) {\r
202                 System.err.println("Warning: pref change to disposed TimeSeriesEditor");\r
203                 return;\r
204             }\r
205 \r
206             if ( event.getKey().equals(ChartPreferences.P_REDRAW_INTERVAL ) || \r
207                     event.getKey().equals(ChartPreferences.P_AUTOSCALE_INTERVAL )) {\r
208                 long redraw_interval = chartPreferenceNode.getLong(ChartPreferences.P_REDRAW_INTERVAL, ChartPreferences.DEFAULT_REDRAW_INTERVAL);\r
209                 long autoscale_interval = chartPreferenceNode.getLong(ChartPreferences.P_AUTOSCALE_INTERVAL, ChartPreferences.DEFAULT_AUTOSCALE_INTERVAL);\r
210                 setInterval( redraw_interval, autoscale_interval );\r
211             }\r
212             if ( event.getKey().equals(ChartPreferences.P_DRAW_SAMPLES )) {\r
213                 boolean draw_samples = chartPreferenceNode.getBoolean(ChartPreferences.P_DRAW_SAMPLES, ChartPreferences.DEFAULT_DRAW_SAMPLES);\r
214                 setDrawSamples( draw_samples );\r
215             }\r
216             if ( event.getKey().equals(ChartPreferences.P_TIMEFORMAT ) ) {\r
217                 String s = chartPreferenceNode.get(ChartPreferences.P_TIMEFORMAT, ChartPreferences.DEFAULT_TIMEFORMAT);\r
218                 TimeFormat tf = TimeFormat.valueOf( s );\r
219                 if (tf!=null) setTimeFormat( tf );\r
220             }\r
221             if ( event.getKey().equals(ChartPreferences.P_VALUEFORMAT ) ) {\r
222                 String s = chartPreferenceNode.get(ChartPreferences.P_VALUEFORMAT, ChartPreferences.DEFAULT_VALUEFORMAT);\r
223                 ValueFormat vf = ValueFormat.valueOf( s );\r
224                 if (vf!=null) setValueFormat( vf );\r
225             }\r
226             if ( event.getKey().equals(ChartPreferences.P_ITEMPLACEMENT)) {\r
227                 String s = chartPreferenceNode.get(ChartPreferences.P_ITEMPLACEMENT, ChartPreferences.DEFAULT_ITEMPLACEMENT);\r
228                 ItemPlacement ip = ItemPlacement.valueOf(s);\r
229                 if (trendNode!=null) trendNode.itemPlacement = ip;\r
230             }\r
231             if ( event.getKey().equals(ChartPreferences.P_TEXTQUALITY) || event.getKey().equals(ChartPreferences.P_LINEQUALITY) ) {\r
232                 String s1 = chartPreferenceNode.get(ChartPreferences.P_TEXTQUALITY, ChartPreferences.DEFAULT_TEXTQUALITY);\r
233                 String s2 = chartPreferenceNode.get(ChartPreferences.P_LINEQUALITY, ChartPreferences.DEFAULT_LINEQUALITY);\r
234                 LineQuality q1 = LineQuality.valueOf(s1);\r
235                 LineQuality q2 = LineQuality.valueOf(s2);\r
236                 if (trendNode!=null) trendNode.quality.textQuality = q1;\r
237                 if (trendNode!=null) trendNode.quality.lineQuality = q2;\r
238             }\r
239             \r
240         }\r
241     };\r
242 \r
243     /**\r
244      * The project which this editor is listening to for changes to\r
245      * {@link ChartKeys.ChartSourceKey keys}.\r
246      */\r
247     IProject                    project;\r
248 \r
249     /**\r
250      * The text widget shown only if there is no IProject available at the time\r
251      * of editor part creation.\r
252      */\r
253     Text                        errorText;\r
254 \r
255     /**\r
256      * A unique key for making DB requests chart editor specific without binding\r
257      * the requests to the editor object itself.\r
258      */\r
259     UUID                        uniqueChartEditorId = UUID.randomUUID();\r
260     Logger                      log;\r
261     Display                     display;\r
262     SWTChassis                  canvas;\r
263     CanvasContext               cvsCtx;\r
264     TrendParticipant            tp;\r
265     TrendNode                   trendNode;\r
266     StepListener                stepListener;\r
267     MilestoneSpecListener       milestoneListener;\r
268     MilestoneSpecQuery          milestoneQuery;\r
269 \r
270     /**\r
271      * The ChartData instance used by this editor for sourcing data at any given\r
272      * moment. Project hint instances are copied into this instance.\r
273      */\r
274     final ChartData             chartData = new ChartData(null, null, null, null, null, null);\r
275 \r
276     /**\r
277      * The ChartSourceKey to match the model this editor was opened for.\r
278      * @see #model\r
279      * @see #init(IEditorSite, IEditorInput)\r
280      */\r
281     ChartKeys.ChartSourceKey    chartDataKey;\r
282     \r
283     \r
284     /**\r
285      * Context management utils\r
286      */\r
287     protected IThreadWorkQueue           swt;\r
288     protected ContextUtil                contextUtil;\r
289 \r
290     class ExperimentStateListener implements IExperimentListener {\r
291         @Override\r
292         public void stateChanged(ExperimentState state) {\r
293             TrendSpec spec = trendNode.getTrendSpec();\r
294             spec.experimentIsRunning = state == ExperimentState.RUNNING;\r
295             if (spec.experimentIsRunning && spec.viewProfile.trackExperimentTime) {\r
296                 TrendParticipant t = tp;\r
297                 if (t != null)\r
298                     t.setDirty();\r
299             }\r
300         }\r
301     }\r
302 \r
303     ExperimentStateListener experimentStateListener = new ExperimentStateListener();\r
304 \r
305     class ChartDataListener extends HintListenerAdapter implements Runnable {\r
306         @Override\r
307         public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
308             if (key.equals(chartDataKey)) {\r
309                 // @Thread any\r
310                 if (!cvsCtx.isDisposed() && cvsCtx.isAlive()) {\r
311                     cvsCtx.getThreadAccess().asyncExec(this);\r
312                 }\r
313             }\r
314         }\r
315         @Override\r
316         public void run() {\r
317             // @Thread AWT\r
318             if (cvsCtx.isDisposed() || !cvsCtx.isAlive()) return;\r
319             ChartData data = Simantics.getProject().getHint(chartDataKey);\r
320             setInput( data, trendNode.getTrendSpec() );\r
321         }\r
322     }\r
323 \r
324     ChartDataListener chartDataListener = new ChartDataListener();\r
325 \r
326     class ValueTipBoxPositionListener extends HintListenerAdapter {\r
327         @Override\r
328         public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
329             if (key.equals(TrendParticipant.KEY_VALUE_TIP_BOX_RELATIVE_POS) && newValue != null) {\r
330                 Session s = Simantics.getSession();\r
331                 ChartResource CHART = s.getService(ChartResource.class);\r
332                 Point2D p = (Point2D) newValue;\r
333                 double[] value = { p.getX(), p.getY() };\r
334                 s.asyncRequest(new SetProperty(getInputResource(), CHART.Chart_valueViewPosition, value, Bindings.DOUBLE_ARRAY));\r
335             }\r
336         }\r
337     }\r
338 \r
339     ValueTipBoxPositionListener valueTipBoxPositionListener = new ValueTipBoxPositionListener();\r
340 \r
341     \r
342     private void handleLinkDataChange() {\r
343         final ChartLinkData newData = linkData;\r
344         trendNode.autoscaletime = newData == null || newData.sender == TimeSeriesEditor.this;\r
345         \r
346         if ( newData == null || newData.sender==TimeSeriesEditor.this ) return;\r
347         TrendNode tn = trendNode;\r
348         HorizRuler hr = tn!=null ? tn.horizRuler : null;\r
349 \r
350         ChartLinkData oldData = new ChartLinkData();\r
351         getFromEnd(oldData);\r
352 \r
353         if ( hr != null && !ObjectUtils.objectEquals(tn.valueTipTime, newData.valueTipTime)) {\r
354             tn.valueTipTime = newData.valueTipTime;\r
355             tp.setDirty();\r
356         }\r
357         \r
358         if ( hr != null && (oldData.from!=newData.from || oldData.sx!=newData.sx)) {\r
359             \r
360             cvsCtx.getThreadAccess().asyncExec( new Runnable() {\r
361                 @Override\r
362                 public void run() {\r
363                     boolean b = trendNode.horizRuler.setFromScale(newData.from, newData.sx);\r
364                     trendNode.horizRuler.autoscroll = false;\r
365                     if (b) {\r
366                         trendNode.layout();\r
367                         tp.setDirty();\r
368                     }\r
369                 }});\r
370         }\r
371     }\r
372     \r
373     // Link-Time\r
374 //    State linkTimeState;\r
375 //    IStateListener linkTimeStateListener = new IStateListener() {\r
376 //              @Override\r
377 //              public void handleStateChange(State state, Object oldValue) {\r
378 //                      \r
379 //                      \r
380 //              }\r
381 //    };\r
382     \r
383     HorizRuler.TimeWindowListener horizRulerListener = new HorizRuler.TimeWindowListener() {\r
384                 @Override\r
385                 public void onNewWindow(double from, double end, double scalex) {\r
386                         final ChartLinkData oldData = linkData;\r
387                         if (oldData != null) {\r
388                                 ChartLinkData data = new ChartLinkData(TimeSeriesEditor.this, from, end, scalex);\r
389                                 data.valueTipTime = trendNode.valueTipTime;\r
390                                 linkData = data;\r
391                                 handleLinkDataChange();\r
392                         }\r
393                 }\r
394         };\r
395 \r
396     class ChassisListener implements IChassisListener {\r
397         @Override\r
398         public void chassisClosed(ICanvasChassis sender) {\r
399             // Prevent deadlock while disposing which using syncExec would result in. \r
400             final ICanvasContext ctx = cvsCtx;\r
401             ThreadUtils.asyncExec(ctx.getThreadAccess(), new Runnable() {\r
402                 @Override\r
403                 public void run() {\r
404                     if (ctx != null) {\r
405                         AWTChassis awt = canvas.getAWTComponent();\r
406                         if (awt != null)\r
407                             awt.setCanvasContext(null);\r
408                         ctx.dispose();\r
409                     }\r
410                 }\r
411             });\r
412             canvas.removeChassisListener(ChassisListener.this);\r
413         }\r
414     }\r
415 \r
416     ActiveSelectionProvider     selectionProvider   = new ActiveSelectionProvider();\r
417     MenuManager                 menuManager;\r
418 \r
419     @Inject\r
420     EContextService contextService;\r
421 \r
422     private ChartLinkData linkData;\r
423 \r
424     public TimeSeriesEditor() {\r
425         log = Logger.getLogger( this.getClass().getName() );\r
426     }\r
427 \r
428     @Override\r
429     public void initImpl(MPart part) {\r
430         EModelService modelService = getContext().get(EModelService.class);\r
431         MApplication app = getContext().get(MApplication.class);\r
432         MUIElement element = modelService.cloneSnippet(app, "org.simantics.charts.toolbar.default.snippet", null);\r
433         if (element != null) {\r
434             element.setVisible(true);\r
435             element.setToBeRendered(true);\r
436             part.setToolbar((MToolBar)element);\r
437         }\r
438         \r
439 //        MBindingContext dialogAndWindowContext = findMAppElementById(app.getRootContext(), "org.eclipse.ui.contexts.dialogAndWindow");\r
440 //        MBindingContext windowContext = findMAppElementById(dialogAndWindowContext.getChildren(), "org.eclipse.ui.contexts.window");\r
441 //        MBindingContext chartContext = findMAppElementById(windowContext.getChildren(), "org.simantics.charts.editor.context");\r
442 //        if (chartContext == null) {\r
443 //            chartContext = MCommandsFactory.INSTANCE.createBindingContext();\r
444 //            chartContext.setContributorURI("bundle://test.uri");\r
445 //            chartContext.setElementId("org.simantics.charts.editor.context");\r
446 //            chartContext.setName("In Chart Editor");\r
447 //            chartContext.setDescription("Chart Editor Binding Context");\r
448 //            windowContext.getChildren().add(chartContext);\r
449 //        }\r
450 //        MBindingTable chartBindingTable = findMAppElementById(app.getBindingTables(), "org.simantics.charts.editor.context");\r
451 //        if (chartBindingTable == null) {\r
452 //            chartBindingTable = MCommandsFactory.INSTANCE.createBindingTable();\r
453 //            chartBindingTable.setContributorURI("bundle://test.test.uri");\r
454 //            chartBindingTable.setElementId("org.simantics.charts.editor.context");\r
455 //            chartBindingTable.setBindingContext(chartContext);\r
456 //            \r
457 //            for (MCommand command : app.getCommands()) {\r
458 //                if (command.getElementId().equals("org.simantics.chart.canvasCommandDelegateCommand")) {\r
459 //                    \r
460 //                    MParameter param = MCommandsFactory.INSTANCE.createParameter();\r
461 //                    param.setElementId("id");\r
462 //                    param.setName("org.simantics.charts.commandparameter.canvasCommandDelegateParameter");\r
463 //                    param.setValue("zoomToFit");\r
464 //                    MKeyBinding keyBinding = MCommandsFactory.INSTANCE.createKeyBinding();\r
465 //                    keyBinding.setKeySequence("1");\r
466 //                    keyBinding.setCommand(command);\r
467 //                    keyBinding.getParameters().add(param);\r
468 //                    chartBindingTable.getBindings().add(keyBinding);\r
469 //                    \r
470 //                    break;\r
471 //                }\r
472 //            }\r
473 //            \r
474 //            app.getBindingTables().add(chartBindingTable);\r
475 //        }\r
476 //        if (!part.getBindingContexts().contains(chartContext))\r
477 //            part.getBindingContexts().add(chartContext);\r
478     }\r
479 \r
480     private <T extends MApplicationElement> T findMAppElementById(List<T> appElements, String elementId) {\r
481         for (T element : appElements) {\r
482             if (element.getElementId() != null && element.getElementId().equals(elementId)) {\r
483                 return element;\r
484             }\r
485         }\r
486         return null;\r
487     }\r
488 \r
489 \r
490     /**\r
491      * Invoke this only from the AWT thread.\r
492      * @param context\r
493      */\r
494     protected void setCanvasContext(final SWTChassis chassis, final ICanvasContext context) {\r
495         // Cannot directly invoke SWTChassis.setCanvasContext only because it\r
496         // needs to be invoked in the SWT thread and AWTChassis.setCanvasContext in the\r
497         // AWT thread, but directly invoking SWTChassis.setCanvasContext would call both\r
498         // in the SWT thread which would cause synchronous scheduling of AWT\r
499         // runnables which is always a potential source of deadlocks.\r
500         chassis.getAWTComponent().setCanvasContext(context);\r
501         SWTUtils.asyncExec(chassis, new Runnable() {\r
502             @Override\r
503             public void run() {\r
504                 if (!chassis.isDisposed())\r
505                     // For AWT, this is a no-operation.\r
506                     chassis.setCanvasContext(context);\r
507             }\r
508         });\r
509     }\r
510 \r
511     @Override\r
512     public void createPartControl(Composite parent) {\r
513         this.chartDataKey = ChartKeys.chartSourceKey(assertInputModelResource());\r
514         display = parent.getDisplay();\r
515         swt = SWTThread.getThreadAccess(display);\r
516 \r
517         // Must have a project to attach to, otherwise the editor is useless.\r
518         project = Simantics.peekProject();\r
519         if (project == null) {\r
520             errorText = new Text(parent, SWT.NONE);\r
521             errorText.setText("No project is open.");\r
522             errorText.setEditable(false);\r
523             return;\r
524         }\r
525 \r
526         // Create the canvas context here before finishing createPartControl\r
527         // to give anybody requiring access to this editor's ICanvasContext\r
528         // a chance to do their work.\r
529         // The context can be created in SWT thread without scheduling\r
530         // to the context thread and having potential deadlocks.\r
531         // The context is locked here and unlocked after it has been\r
532         // initialized in the AWT thread.\r
533         IThreadWorkQueue thread = AWTThread.getThreadAccess();\r
534         cvsCtx = new CanvasContext(thread);\r
535         cvsCtx.setLocked(true);\r
536 \r
537         canvas = new SWTChassis(parent, SWT.NONE);\r
538         canvas.populate(parameter -> {\r
539             if (!isDisposed()) {\r
540                 canvas.addChassisListener(new ChassisListener());\r
541                 initializeCanvas(canvas, cvsCtx);\r
542             }\r
543         });\r
544 \r
545         // Link time\r
546 //        ICommandService service = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);\r
547 //        Command command = service.getCommand( LinkTimeHandler.COMMAND_ID );\r
548 //        linkTimeState = command.getState( LinkTimeHandler.STATE_ID );\r
549 //        if ( linkTimeState != null ) linkTimeState.addListener( linkTimeStateListener );\r
550         \r
551         // TODO: Link time E4 way!\r
552 \r
553         \r
554         linkData = new ChartLinkData();\r
555         \r
556         addPopupMenu();\r
557         \r
558         // Start tracking editor input validity. \r
559         activateValidation();\r
560 \r
561         // Provide input as selection for property page.\r
562 //        selectionProvider.setSelection( new StructuredSelection(getInputResource()) );\r
563         \r
564         getSelectionService().setSelection(new StructuredSelection(getInputResource()));\r
565         getSelectionService().addSelectionListener(getPart().getElementId(), new ISelectionListener() {\r
566             \r
567             @Override\r
568             public void selectionChanged(MPart part, Object selection) {\r
569                 System.out.println("selection changed!");\r
570             }\r
571         });\r
572         \r
573 //        getSite().setSelectionProvider( selectionProvider );\r
574     }\r
575 \r
576     protected void initializeCanvas(final SWTChassis chassis, CanvasContext cvsCtx) {\r
577         // Initialize canvas context\r
578         TrendSpec nodata = new TrendSpec();\r
579         nodata.init();\r
580         cvsCtx = TrendInitializer.defaultInitializeCanvas(cvsCtx, null, null, null, nodata);\r
581 \r
582         tp = cvsCtx.getAtMostOneItemOfClass(TrendParticipant.class);\r
583 \r
584         \r
585 //        IContextService contextService = (IContextService) getSite().getService(IContextService.class);\r
586         contextUtil = new ContextUtil(contextService, swt);\r
587         \r
588         \r
589         cvsCtx.add( new SubscriptionDropParticipant( getInputResource() ) );\r
590         cvsCtx.add( new KeyToCommand( ChartKeyBindings.DEFAULT_BINDINGS ) );\r
591         cvsCtx.add( new ChartPasteHandler2(getInputResource()) );\r
592         cvsCtx.add(contextUtil);\r
593         \r
594         // Context management\r
595         cvsCtx.add(new SGFocusParticipant(canvas, "org.simantics.charts.editor.context"));\r
596 \r
597         stepListener = new StepListener( tp );\r
598         trendNode = tp.getTrend();\r
599         trendNode.titleNode.remove();\r
600         trendNode.titleNode = null;\r
601 \r
602         // Link time\r
603         trendNode.horizRuler.listener = horizRulerListener;\r
604         \r
605         final ChartLinkData linkTime = linkData;\r
606         if (linkTime!=null) trendNode.horizRuler.setFromEnd(linkTime.from, linkTime.sx);\r
607         \r
608         // Handle mouse moved event after TrendParticipant.\r
609         // This handler forwards trend.mouseHoverTime to linkTimeState\r
610         cvsCtx.getEventHandlerStack().add( new IEventHandler() {\r
611 \r
612                         @Override\r
613                         public int getEventMask() {\r
614                                 return EventTypes.MouseMovedMask | EventTypes.MouseClickMask | EventTypes.CommandMask | EventTypes.KeyPressed;\r
615                         }\r
616 \r
617                         @Override\r
618                         public boolean handleEvent(Event e) {\r
619 \r
620 //                              System.out.println("LinkEventHandler: "+e);\r
621                                 ChartLinkData oldData = linkData;\r
622                                 if (oldData!=null) {\r
623                                         ChartLinkData newData = new ChartLinkData();\r
624                                         getFromEnd(newData);\r
625                                         if (!newData.equals(oldData)) {\r
626 //                                              System.out.println("Sending new link-data");\r
627                                                 linkData = newData;\r
628                                                 handleLinkDataChange();\r
629                                         }\r
630                                 }\r
631                                 return false;\r
632                         }}, -1);\r
633 \r
634         // Canvas context is initialized, unlock it now to allow rendering.\r
635         cvsCtx.setLocked(false);\r
636 \r
637         setCanvasContext(chassis, cvsCtx);\r
638 \r
639         cvsCtx.getEventHandlerStack().add(new IEventHandler() {\r
640             @Override\r
641             public boolean handleEvent(Event e) {\r
642                 MouseButtonReleasedEvent event = (MouseButtonReleasedEvent) e;\r
643                 if (event.button != MouseEvent.RIGHT_BUTTON)\r
644                     return false;\r
645 \r
646                 final Point p = new Point((int) event.screenPosition.getX(), (int) event.screenPosition.getY());\r
647                 SWTUtils.asyncExec(chassis, new Runnable() {\r
648                     @Override\r
649                     public void run() {\r
650                         if (!canvas.isDisposed())\r
651                             showPopup(p);\r
652                     }\r
653                 });\r
654                 return true;\r
655             }\r
656             @Override\r
657             public int getEventMask() {\r
658                 return EventTypes.MouseButtonReleasedMask;\r
659             }\r
660         }, 1000000);\r
661 \r
662         // Track data source and preinitialize chartData\r
663         project.addHintListener(chartDataListener);\r
664         chartData.readFrom( (ChartData) project.getHint( chartDataKey ) );\r
665 \r
666         if (chartData.run != null) {\r
667             milestoneListener = new MilestoneSpecListener();\r
668             milestoneQuery = new MilestoneSpecQuery( chartData.run );\r
669             Simantics.getSession().asyncRequest( milestoneQuery, milestoneListener );\r
670         }\r
671 \r
672         // IMPORTANT: Only after preinitializing chartData, start tracking chart configuration\r
673         trackChartConfiguration();\r
674         trackPreferences();\r
675 \r
676         // Write changes to TrendSpec.viewProfile.valueViewPosition[XY]\r
677         // back to the graph database.\r
678         cvsCtx.getHintStack().addHintListener(valueTipBoxPositionListener);\r
679     }\r
680 \r
681     private void addPopupMenu() {\r
682 \r
683 //          #TODO: FINISH THIS WHEN E4 WORKBENCH\r
684 //          MPopupMenu popupMenu = MMenuFactory.INSTANCE.createPopupMenu();\r
685 //          popupMenu.setLabel("Time Series Editor");\r
686 //          popupMenu.setElementId(CONTEXT_MENU_ID);\r
687 //          \r
688 //        for (MCommand command : getPart().getContext().get(MApplication.class).getCommands()) {\r
689 //            if (command.getElementId().equals("org.simantics.chart.canvasCommandDelegateCommand")) {\r
690 //                MParameter param = MCommandsFactory.INSTANCE.createParameter();\r
691 //                param.setElementId("id");\r
692 //                param.setName("org.simantics.charts.commandparameter.canvasCommandDelegateParameter");\r
693 //                param.setValue("zoomToFit");\r
694 //                MHandledMenuItem mmenuItem = MMenuFactory.INSTANCE.createHandledMenuItem();\r
695 //                mmenuItem.setLabel("Zoom to Fit");\r
696 //                mmenuItem.setCommand(command);\r
697 //                mmenuItem.getParameters().add(param);\r
698 //            }\r
699 //        }\r
700 //          \r
701 //          getPart().getContext().get(EMenuService.class).registerContextMenu(canvas, CONTEXT_MENU_ID);\r
702             \r
703             \r
704             \r
705         menuManager = new MenuManager("Time Series Editor", CONTEXT_MENU_ID);\r
706         menuManager.setRemoveAllWhenShown(true);\r
707         Menu menu = menuManager.createContextMenu(canvas);\r
708         canvas.setMenu(menu);\r
709         // TODO: link to modeled menu in E4 way\r
710 //        getEditorSite().registerContextMenu(menuManager.getId(), menuManager, selectionProvider);\r
711 \r
712         // Add support for some built-in actions in the context menu.\r
713         menuManager.addMenuListener(new IMenuListener() {\r
714             @Override\r
715             public void menuAboutToShow(IMenuManager manager) {\r
716                 // Not initialized yet, prevent NPE.\r
717                 TrendNode trendNode = TimeSeriesEditor.this.trendNode;\r
718                 TrendParticipant tp = TimeSeriesEditor.this.tp;\r
719                 if (trendNode == null || tp == null)\r
720                     return;\r
721 \r
722                 TrendSpec trendSpec = trendNode.getTrendSpec();\r
723                 ItemNode hoverItem = tp.hoveringItem;\r
724                 if (hoverItem != null && hoverItem.item != null) {\r
725                     Resource component = resolveReferencedComponent(hoverItem.item.variableId);\r
726                     if (component != null) {\r
727                         manager.add(new PerformDefaultAction("Show Referenced Component", canvas, component));\r
728                     }\r
729 \r
730                     Resource chart = TimeSeriesEditor.this.getInputResource();\r
731                     if ( chart != null ) {\r
732                         try {\r
733                             Resource chartItem = Simantics.getSession().sync( new FindChartItemForTrendItem(chart, hoverItem.item) );\r
734                             if (chartItem != null) {\r
735                                 manager.add(new HideItemsAction("Hide Item", true, Collections.singletonList(chartItem)));\r
736                                 manager.add(new Separator());\r
737                                 manager.add(new PropertiesAction("Item Properties", canvas, chartItem));\r
738                                 manager.add(new PropertiesAction("Chart Properties", canvas, chart));\r
739                             }\r
740                         } catch (DatabaseException e) {\r
741                             Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to resolve context menu items.", e));\r
742                         }\r
743                     }\r
744                 } else {\r
745                     boolean hairlineMovementAllowed =\r
746                             !(trendSpec.experimentIsRunning &&\r
747                               trendSpec.viewProfile.trackExperimentTime);\r
748 \r
749                     Resource chart = TimeSeriesEditor.this.getInputResource();\r
750                     manager.add(new Separator());\r
751                     manager.add(new MoveHairlineAction(\r
752                             "Move Hairline Here",\r
753                             chart,\r
754                             hairlineMovementAllowed,\r
755                             trendNode,\r
756                             trendNode.mouseHoverTime\r
757                             ));\r
758                     manager.add(new MoveHairlineAction(\r
759                             "Move Hairline To Current Time",\r
760                             chart,\r
761                             hairlineMovementAllowed,\r
762                             trendNode,\r
763                             trendNode.horizRuler.getItemEndTime(),\r
764                             Boolean.FALSE\r
765                             ));\r
766                     manager.add(new TrackExperimentTimeAction(\r
767                             "Hairline Tracks Current Time",\r
768                             chart,\r
769                             trendSpec.viewProfile.trackExperimentTime));\r
770                     manager.add(new Separator());\r
771                     manager.add(new SendCommandAction("Zoom to Fit", IMG_ZOOM_TO_FIT, cvsCtx, Commands.ZOOM_TO_FIT));\r
772                     manager.add(new SendCommandAction("Zoom to Fit Horizontally", IMG_ZOOM_TO_FIT_HORIZ, cvsCtx, Commands.ZOOM_TO_FIT_HORIZ));\r
773                     manager.add(new SendCommandAction("Zoom to Fit Vertically", IMG_ZOOM_TO_FIT_VERT, cvsCtx, Commands.ZOOM_TO_FIT_VERT));\r
774                     manager.add(new SendCommandAction("Autoscale Chart", IMG_AUTOSCALE, cvsCtx, Commands.AUTOSCALE));\r
775                     manager.add(new Separator());\r
776                     manager.add(new PropertiesAction("Chart Properties", canvas, chart));\r
777                 }\r
778             }\r
779         });\r
780     }\r
781 \r
782     protected Resource resolveReferencedComponent(final String variableId) {\r
783         try {\r
784             return Simantics.getSession().sync(new UniqueRead<Resource>() {\r
785                 @Override\r
786                 public Resource perform(ReadGraph graph) throws DatabaseException {\r
787                     Variable configuration = Variables.getConfigurationContext(graph, getInputResource());\r
788                     RVI rvi = RVI.fromResourceFormat(graph, variableId);\r
789                     rvi = new RVIBuilder(rvi).removeFromFirstRole(Role.PROPERTY).toRVI();\r
790                     if (rvi.isEmpty())\r
791                         return null;\r
792                     Variable var = rvi.resolve(graph, configuration);\r
793                     return var.getPossibleRepresents(graph);\r
794                 }\r
795             });\r
796         } catch (DatabaseException e) {\r
797             ErrorLogger.defaultLogError(e);\r
798         }\r
799         return null;\r
800     }\r
801 \r
802     private void showPopup(Point p) {\r
803         menuManager.getMenu().setLocation(p);\r
804         menuManager.getMenu().setVisible(true);\r
805     }\r
806 \r
807     private void trackChartConfiguration() {\r
808         Simantics.getSession().asyncRequest(new TrendSpecQuery( uniqueChartEditorId, getInputResource() ), new TrendSpecListener());\r
809         Simantics.getSession().asyncRequest(new ActiveRunQuery( uniqueChartEditorId, getInputResource() ), new ActiveRunListener());\r
810     }\r
811 \r
812     @Override\r
813     public void setFocus() {\r
814         if (errorText != null)\r
815             errorText.setFocus();\r
816         else\r
817             canvas.setFocus();\r
818     }\r
819 \r
820     @Override\r
821     public void dispose() {\r
822         if (isDisposed() == true) return;\r
823 \r
824         if (trendNode!=null && trendNode.horizRuler!=null) {\r
825             trendNode.horizRuler.listener = null;\r
826         }\r
827 \r
828 //        if ( linkTimeState != null ) linkTimeState.removeListener( linkTimeStateListener );\r
829 \r
830         canvas.getHintContext().removeHint( SWTChassis.KEY_EDITORPART );\r
831         canvas.getHintContext().removeHint( SWTChassis.KEY_WORKBENCHPAGE );\r
832         canvas.getHintContext().removeHint( SWTChassis.KEY_WORKBENCHWINDOW );\r
833 \r
834         if ( chartPreferenceNode!= null ) {\r
835             chartPreferenceNode.removePreferenceChangeListener( preferenceListener );\r
836         }\r
837 \r
838         MilestoneSpecListener ml = milestoneListener;\r
839         if (ml!=null) ml.dispose();\r
840 \r
841         if (project != null) {\r
842             project.removeHintListener(chartDataListener);\r
843         }\r
844 \r
845         if (chartData != null) {\r
846             if (chartData.datasource!=null)\r
847                 chartData.datasource.removeListener( stepListener );\r
848             if (chartData.experiment!=null)\r
849                 chartData.experiment.removeListener( experimentStateListener );\r
850             chartData.readFrom( null );\r
851         }\r
852     }\r
853 \r
854     /**\r
855      * @param data new data or null\r
856      * @param newSpec new spec or null\r
857      * @thread AWT\r
858      */\r
859     @SuppressWarnings("unused")\r
860     public void setInput(ChartData data, TrendSpec newSpec) {\r
861         boolean doLayout = false;\r
862 \r
863         // Disregard input if it is not for this chart's containing model.\r
864         if (data != null && data.model != null && !data.model.equals(assertInputModelResource()))\r
865             data = null;\r
866 \r
867         // Accommodate Datasource changes\r
868         Datasource: {\r
869                 Datasource oldDatasource = chartData==null?null:chartData.datasource;\r
870                 Datasource newDatasource = data==null?null:data.datasource;\r
871                 //if ( !ObjectUtils.objectEquals(oldDatasource, newDatasource) ) \r
872                 {\r
873                         if (oldDatasource!=null) oldDatasource.removeListener( stepListener );\r
874                         if (newDatasource!=null) newDatasource.addListener( stepListener );\r
875                 }\r
876         }\r
877 \r
878         Experiment: {\r
879             IExperiment oldExperiment = chartData==null?null:chartData.experiment;\r
880             IExperiment newExperiment = data==null?null:data.experiment;\r
881             //if ( !ObjectUtils.objectEquals(oldExperiment, newExperiment) ) \r
882             {\r
883                 if (oldExperiment!=null) oldExperiment.removeListener( experimentStateListener );\r
884                 if (newExperiment!=null) newExperiment.addListener( experimentStateListener );\r
885             }\r
886         }\r
887 \r
888         // Accommodate Historian changes\r
889         Historian: {\r
890                 HistoryManager oldHistorian = trendNode.historian==null?null:trendNode.historian;\r
891                 HistoryManager newHistorian = data==null?null:data.history;\r
892                 Collector newCollector = data==null?null:data.collector;\r
893         //      if ( !ObjectUtils.objectEquals(oldHistorian, newHistorian) ) \r
894                 {\r
895                         if (newHistorian instanceof FileHistory) {\r
896                                 FileHistory fh = (FileHistory) newHistorian;\r
897                                 System.out.println("History = "+fh.getWorkarea());\r
898                         }\r
899                         trendNode.setHistorian( newHistorian, newCollector );\r
900                         doLayout |= trendNode.autoscale(true, true) | !ObjectUtils.objectEquals(oldHistorian, newHistorian);\r
901                 }\r
902 \r
903                 // Accommodate TrendSpec changes\r
904                 TrendSpec oldSpec = trendNode.getTrendSpec();\r
905                 if ( !newSpec.equals(oldSpec) ) {\r
906                     trendNode.setTrendSpec( newSpec==null?TrendSpec.EMPTY:newSpec );\r
907                     doLayout = true;\r
908                 }\r
909                 \r
910         }\r
911 \r
912         Resource newExperimentResource = data==null ? null : data.run;\r
913         Resource oldExperimentResource = this.chartData == null ? null : this.chartData.run;\r
914         \r
915         // Track milestones\r
916         Milestones: {\r
917                 if (!ObjectUtils.objectEquals(oldExperimentResource, newExperimentResource)) {\r
918 \r
919                         // Dispose old listener & Query\r
920                         if (milestoneListener!=null) {\r
921                                 milestoneListener.dispose();\r
922                                 milestoneListener = null;\r
923                         }\r
924                         if (milestoneQuery!=null) {\r
925                                 milestoneQuery = null;\r
926                         }\r
927 \r
928                         trendNode.setMilestones( MilestoneSpec.EMPTY );\r
929                         \r
930                         if (newExperimentResource != null) {\r
931                                 milestoneListener = new MilestoneSpecListener();\r
932                                 milestoneQuery = new MilestoneSpecQuery( newExperimentResource );\r
933                                 Simantics.getSession().asyncRequest( milestoneQuery, milestoneListener );\r
934                         }\r
935                 }\r
936                 \r
937         }\r
938 \r
939         if (doLayout) trendNode.layout();\r
940         this.chartData.readFrom( data );\r
941         tp.setDirty();\r
942         \r
943         if (!ObjectUtils.objectEquals(oldExperimentResource, newExperimentResource)) {\r
944                 resetViewAfterDataChange();\r
945         }\r
946         \r
947     }\r
948 \r
949     class ActiveRunListener implements SyncListener<Resource> {\r
950         @Override\r
951         public void exception(ReadGraph graph, Throwable throwable) {\r
952             ErrorLogger.defaultLogError(throwable);\r
953             ShowMessage.showError(throwable.getClass().getSimpleName(), throwable.getMessage());\r
954         }\r
955         @Override\r
956         public void execute(ReadGraph graph, final Resource run) throws DatabaseException {\r
957                 if(run != null) {\r
958                         SimulationResource SIMU = SimulationResource.getInstance(graph);\r
959                         Variable var = Variables.getVariable(graph, run);\r
960                         IExperiment exp = var.getPossiblePropertyValue(graph, SIMU.Run_iExperiment);\r
961                         ITrendSupport ts = exp.getService(ITrendSupport.class);\r
962                         ts.setChartData(graph);\r
963                 }\r
964         }\r
965         @Override\r
966         public boolean isDisposed() {\r
967             return TimeSeriesEditor.this.isDisposed();\r
968         }\r
969     }\r
970     \r
971     class TrendSpecListener implements AsyncListener<TrendSpec> {\r
972         @Override\r
973         public void exception(AsyncReadGraph graph, Throwable throwable) {\r
974                 \r
975             ErrorLogger.defaultLogError(throwable);\r
976             ShowMessage.showError(throwable.getClass().getSimpleName(), throwable.getMessage());\r
977         }\r
978         @Override\r
979         public void execute(AsyncReadGraph graph, final TrendSpec result) {\r
980             if (result == null) {\r
981                 log.log(Level.INFO, "Chart configuration removed");\r
982             } else {\r
983                 log.log(Level.INFO, "Chart configuration updated: " + result);\r
984             }\r
985 \r
986             // Reload chart in AWT Thread\r
987             AWTThread.getThreadAccess().asyncExec(new Runnable() {\r
988                 @Override\r
989                 public void run() {\r
990                     if (!isDisposed())\r
991                         setInput( chartData, result );\r
992                 }\r
993             });\r
994         }\r
995         @Override\r
996         public boolean isDisposed() {\r
997             return TimeSeriesEditor.this.isDisposed();\r
998         }\r
999     }\r
1000     \r
1001     class MilestoneSpecListener implements AsyncListener<MilestoneSpec> {\r
1002         boolean disposed = false;\r
1003                 @Override\r
1004                 public void execute(AsyncReadGraph graph, final MilestoneSpec result) {\r
1005                         AWTThread.INSTANCE.asyncExec(new Runnable() {\r
1006                                 public void run() {\r
1007                                         trendNode.setMilestones(result);\r
1008                                 }});\r
1009                 }\r
1010 \r
1011                 @Override\r
1012                 public void exception(AsyncReadGraph graph, Throwable throwable) {\r
1013                         \r
1014                 }\r
1015 \r
1016                 @Override\r
1017                 public boolean isDisposed() {\r
1018                         return disposed;\r
1019                 }\r
1020                 \r
1021                 public void dispose() {\r
1022                         disposed = true;\r
1023                 }\r
1024         \r
1025     }\r
1026 \r
1027     private void trackPreferences() {\r
1028         chartPreferenceNode = InstanceScope.INSTANCE.getNode( "org.simantics.charts" );\r
1029         chartPreferenceNode.addPreferenceChangeListener( preferenceListener );\r
1030         long redrawInterval = chartPreferenceNode.getLong(ChartPreferences.P_REDRAW_INTERVAL, ChartPreferences.DEFAULT_REDRAW_INTERVAL);\r
1031         long autoscaleInterval = chartPreferenceNode.getLong(ChartPreferences.P_AUTOSCALE_INTERVAL, ChartPreferences.DEFAULT_AUTOSCALE_INTERVAL);\r
1032         setInterval(redrawInterval, autoscaleInterval);\r
1033         \r
1034         String timeFormat = chartPreferenceNode.get(ChartPreferences.P_TIMEFORMAT, ChartPreferences.DEFAULT_TIMEFORMAT); \r
1035         TimeFormat tf = TimeFormat.valueOf( timeFormat );\r
1036         if (tf!=null) setTimeFormat( tf );\r
1037         \r
1038         Boolean drawSamples = chartPreferenceNode.getBoolean(ChartPreferences.P_DRAW_SAMPLES, ChartPreferences.DEFAULT_DRAW_SAMPLES);\r
1039         setDrawSamples(drawSamples);\r
1040 \r
1041         String valueFormat = chartPreferenceNode.get(ChartPreferences.P_VALUEFORMAT, ChartPreferences.DEFAULT_VALUEFORMAT);\r
1042         ValueFormat vf = ValueFormat.valueOf( valueFormat );\r
1043         if (vf!=null) setValueFormat( vf );\r
1044         \r
1045         String s = chartPreferenceNode.get(ChartPreferences.P_ITEMPLACEMENT, ChartPreferences.DEFAULT_ITEMPLACEMENT);\r
1046         ItemPlacement ip = ItemPlacement.valueOf(s);\r
1047         if (trendNode!=null) trendNode.itemPlacement = ip;\r
1048         \r
1049         String s1 = chartPreferenceNode.get(ChartPreferences.P_TEXTQUALITY, ChartPreferences.DEFAULT_TEXTQUALITY);\r
1050         String s2 = chartPreferenceNode.get(ChartPreferences.P_LINEQUALITY, ChartPreferences.DEFAULT_LINEQUALITY);\r
1051         LineQuality q1 = LineQuality.valueOf(s1);\r
1052         LineQuality q2 = LineQuality.valueOf(s2);\r
1053         if (trendNode!=null) trendNode.quality.textQuality = q1;\r
1054         if (trendNode!=null) trendNode.quality.lineQuality = q2;\r
1055         \r
1056     }\r
1057 \r
1058     private void setInterval(long redrawInterval, long autoscaleInterval) {\r
1059         redrawInterval = Math.max(1, redrawInterval);\r
1060         long pulse = Math.min(50, redrawInterval);\r
1061         pulse = Math.min(pulse, autoscaleInterval);\r
1062         IHintContext h = canvas.getCanvasContext().getDefaultHintContext();\r
1063         h.setHint(TimeParticipant.KEY_TIME_PULSE_INTERVAL, pulse);\r
1064         h.setHint(TrendParticipant.KEY_TREND_DRAW_INTERVAL, redrawInterval);\r
1065         h.setHint(TrendParticipant.KEY_TREND_AUTOSCALE_INTERVAL, autoscaleInterval);        \r
1066     }\r
1067 \r
1068     private void setDrawSamples(boolean value) {\r
1069         trendNode.drawSamples = value;\r
1070         trendNode.layout();\r
1071         tp.setDirty();\r
1072     }\r
1073 \r
1074     private void setTimeFormat( TimeFormat tf ) {\r
1075         if (trendNode.timeFormat == tf) return;\r
1076         trendNode.timeFormat = tf;\r
1077         trendNode.layout();\r
1078         tp.setDirty();\r
1079     }\r
1080 \r
1081     private void setValueFormat( ValueFormat vf ) {\r
1082         if (trendNode.valueFormat == vf) return;\r
1083         trendNode.valueFormat = vf;\r
1084         trendNode.layout();\r
1085         tp.setDirty();\r
1086     }\r
1087 \r
1088     @SuppressWarnings("unchecked")\r
1089     @Override\r
1090     public <T> T getAdapter(Class<T> adapter) {\r
1091         if (adapter == INode.class) {\r
1092             ICanvasContext ctx = cvsCtx;\r
1093             if (ctx != null)\r
1094                 return (T) ctx.getSceneGraph();\r
1095         }\r
1096         if (adapter == IPropertyPage.class)\r
1097             // TODO: pure e4 property page without sites\r
1098             return (T) new StandardPropertyPage(null, getPropertyPageContexts());\r
1099         if (adapter == ICanvasContext.class)\r
1100             return (T) cvsCtx;\r
1101         return null;\r
1102     }\r
1103 \r
1104     protected Set<String> getPropertyPageContexts() {\r
1105         try {\r
1106             return BrowseContext.getBrowseContextClosure(Simantics.getSession(), Collections.singleton(ROOT_PROPERTY_BROWSE_CONTEXT));\r
1107         } catch (DatabaseException e) {\r
1108             ExceptionUtils.logAndShowError("Failed to load modeled browse contexts for property page, see exception for details.", e);\r
1109             return Collections.singleton(ROOT_PROPERTY_BROWSE_CONTEXT);\r
1110         }\r
1111     }\r
1112 \r
1113     /**\r
1114      * Add from, end, (scale x) to argument array\r
1115      * @param fromEnd array of 2 or 3\r
1116      */\r
1117     public void getFromEnd(ChartLinkData data) {\r
1118         data.sender = this;\r
1119         TrendNode tn = trendNode;\r
1120         data.valueTipTime = tn.valueTipTime;\r
1121         HorizRuler hr = tn!=null ? tn.horizRuler : null;\r
1122         if ( hr != null ) {\r
1123                 data.from = hr.from;\r
1124                 data.end = hr.end;\r
1125                         double len = hr.end-hr.from; \r
1126                         double wid = tn.plot.getWidth();\r
1127                         if ( wid==0.0 ) wid = 0.1;\r
1128                         data.sx = len/wid;\r
1129         }\r
1130     }\r
1131 \r
1132     @SuppressWarnings("unused")\r
1133     private static boolean doubleEquals(double a, double b) {\r
1134         if (Double.isNaN(a) && Double.isNaN(b)) return true;\r
1135         return a==b;\r
1136     }\r
1137 \r
1138     protected void resetViewAfterDataChange() {\r
1139         \r
1140         CanvasUtils.sendCommand(cvsCtx, Commands.CANCEL);\r
1141         CanvasUtils.sendCommand(cvsCtx, Commands.AUTOSCALE);\r
1142         \r
1143     }\r
1144 \r
1145 //    private Resource getInputResource() {\r
1146 //        return resource;\r
1147 //    }\r
1148 }\r