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