1 /*******************************************************************************
2 * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.charts.editor;
14 import java.awt.geom.Point2D;
15 import java.util.Collections;
17 import java.util.UUID;
18 import java.util.logging.Level;
19 import java.util.logging.Logger;
21 import org.eclipse.core.commands.Command;
22 import org.eclipse.core.commands.IStateListener;
23 import org.eclipse.core.commands.State;
24 import org.eclipse.core.runtime.IStatus;
25 import org.eclipse.core.runtime.Status;
26 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
27 import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
28 import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
29 import org.eclipse.core.runtime.preferences.InstanceScope;
30 import org.eclipse.jface.action.IMenuListener;
31 import org.eclipse.jface.action.IMenuManager;
32 import org.eclipse.jface.action.MenuManager;
33 import org.eclipse.jface.action.Separator;
34 import org.eclipse.jface.resource.ImageDescriptor;
35 import org.eclipse.jface.viewers.StructuredSelection;
36 import org.eclipse.swt.SWT;
37 import org.eclipse.swt.graphics.Point;
38 import org.eclipse.swt.widgets.Composite;
39 import org.eclipse.swt.widgets.Display;
40 import org.eclipse.swt.widgets.Menu;
41 import org.eclipse.swt.widgets.Text;
42 import org.eclipse.ui.IEditorInput;
43 import org.eclipse.ui.IEditorSite;
44 import org.eclipse.ui.IWorkbenchPage;
45 import org.eclipse.ui.IWorkbenchWindow;
46 import org.eclipse.ui.PartInitException;
47 import org.eclipse.ui.PlatformUI;
48 import org.eclipse.ui.commands.ICommandService;
49 import org.eclipse.ui.contexts.IContextService;
50 import org.simantics.Simantics;
51 import org.simantics.browsing.ui.model.browsecontexts.BrowseContext;
52 import org.simantics.charts.Activator;
53 import org.simantics.charts.ITrendSupport;
54 import org.simantics.charts.ontology.ChartResource;
55 import org.simantics.charts.preference.ChartPreferences;
56 import org.simantics.charts.query.FindChartItemForTrendItem;
57 import org.simantics.charts.query.MilestoneSpecQuery;
58 import org.simantics.charts.query.SetProperty;
59 import org.simantics.charts.query.TrendSpecQuery;
60 import org.simantics.charts.ui.ChartLinkData;
61 import org.simantics.charts.ui.LinkTimeHandler;
62 import org.simantics.databoard.Bindings;
63 import org.simantics.databoard.util.ObjectUtils;
64 import org.simantics.db.AsyncReadGraph;
65 import org.simantics.db.ReadGraph;
66 import org.simantics.db.Resource;
67 import org.simantics.db.Session;
68 import org.simantics.db.common.request.ParametrizedRead;
69 import org.simantics.db.common.request.UniqueRead;
70 import org.simantics.db.exception.DatabaseException;
71 import org.simantics.db.layer0.request.Model;
72 import org.simantics.db.layer0.request.combinations.Combinators;
73 import org.simantics.db.layer0.variable.RVI;
74 import org.simantics.db.layer0.variable.RVIBuilder;
75 import org.simantics.db.layer0.variable.Variable;
76 import org.simantics.db.layer0.variable.Variables;
77 import org.simantics.db.layer0.variable.Variables.Role;
78 import org.simantics.db.procedure.AsyncListener;
79 import org.simantics.db.procedure.SyncListener;
80 import org.simantics.diagram.participant.ContextUtil;
81 import org.simantics.diagram.participant.SGFocusParticipant;
82 import org.simantics.g2d.canvas.ICanvasContext;
83 import org.simantics.g2d.canvas.impl.CanvasContext;
84 import org.simantics.g2d.chassis.AWTChassis;
85 import org.simantics.g2d.chassis.ICanvasChassis;
86 import org.simantics.g2d.chassis.IChassisListener;
87 import org.simantics.g2d.chassis.SWTChassis;
88 import org.simantics.g2d.participant.KeyToCommand;
89 import org.simantics.g2d.participant.TimeParticipant;
90 import org.simantics.g2d.utils.CanvasUtils;
91 import org.simantics.history.Collector;
92 import org.simantics.history.HistoryManager;
93 import org.simantics.history.impl.FileHistory;
94 import org.simantics.project.IProject;
95 import org.simantics.scenegraph.INode;
96 import org.simantics.scenegraph.g2d.events.Event;
97 import org.simantics.scenegraph.g2d.events.EventTypes;
98 import org.simantics.scenegraph.g2d.events.IEventHandler;
99 import org.simantics.scenegraph.g2d.events.MouseEvent;
100 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
101 import org.simantics.scenegraph.g2d.events.command.Commands;
102 import org.simantics.selectionview.StandardPropertyPage;
103 import org.simantics.simulation.data.Datasource;
104 import org.simantics.simulation.experiment.ExperimentState;
105 import org.simantics.simulation.experiment.IExperiment;
106 import org.simantics.simulation.experiment.IExperimentListener;
107 import org.simantics.simulation.ontology.SimulationResource;
108 import org.simantics.trend.TrendInitializer;
109 import org.simantics.trend.TrendInitializer.StepListener;
110 import org.simantics.trend.configuration.ItemPlacement;
111 import org.simantics.trend.configuration.LineQuality;
112 import org.simantics.trend.configuration.TimeFormat;
113 import org.simantics.trend.configuration.TrendSpec;
114 import org.simantics.trend.impl.HorizRuler;
115 import org.simantics.trend.impl.ItemNode;
116 import org.simantics.trend.impl.MilestoneSpec;
117 import org.simantics.trend.impl.TrendNode;
118 import org.simantics.trend.impl.TrendParticipant;
119 import org.simantics.ui.workbench.IPropertyPage;
120 import org.simantics.ui.workbench.IResourceEditorInput;
121 import org.simantics.ui.workbench.ResourceEditorInput;
122 import org.simantics.ui.workbench.ResourceEditorPart;
123 import org.simantics.ui.workbench.action.PerformDefaultAction;
124 import org.simantics.ui.workbench.editor.input.InputValidationCombinators;
125 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
126 import org.simantics.utils.datastructures.hints.IHintContext;
127 import org.simantics.utils.datastructures.hints.IHintContext.Key;
128 import org.simantics.utils.datastructures.hints.IHintObservable;
129 import org.simantics.utils.format.ValueFormat;
130 import org.simantics.utils.threads.AWTThread;
131 import org.simantics.utils.threads.IThreadWorkQueue;
132 import org.simantics.utils.threads.SWTThread;
133 import org.simantics.utils.threads.ThreadUtils;
134 import org.simantics.utils.ui.BundleUtils;
135 import org.simantics.utils.ui.ErrorLogger;
136 import org.simantics.utils.ui.ExceptionUtils;
137 import org.simantics.utils.ui.SWTDPIUtil;
138 import org.simantics.utils.ui.SWTUtils;
139 import org.simantics.utils.ui.dialogs.ShowMessage;
140 import org.simantics.utils.ui.jface.ActiveSelectionProvider;
143 * TimeSeriesEditor is an interactive part that draws a time series chart.
145 * The configuration model is {@link TrendSpec} which is read through
146 * {@link TrendSpecQuery}. In Simantics Environment the
147 * editor input is {@link ResourceEditorInput}.
149 * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
150 * @author Tuukka Lehtonen
152 public class TimeSeriesEditor extends ResourceEditorPart {
154 ParametrizedRead<IResourceEditorInput, Boolean> INPUT_VALIDATOR =
156 InputValidationCombinators.hasURI(),
157 InputValidationCombinators.extractInputResource()
161 protected ParametrizedRead<IResourceEditorInput, Boolean> getInputValidator() {
162 return INPUT_VALIDATOR;
166 * The root property browse context of the time series editor. A transitive
167 * closure is calculated for this context.
169 private static String ROOT_PROPERTY_BROWSE_CONTEXT = ChartResource.URIs.ChartBrowseContext;
172 * ID of the this editor part extension.
174 public static final String ID = "org.simantics.charts.editor.timeseries";
176 private static final String CONTEXT_MENU_ID = "#timeSeriesChart";
178 private IEclipsePreferences chartPreferenceNode;
180 private final ImageDescriptor IMG_ZOOM_TO_FIT = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/horizAndVert16.png");
181 private final ImageDescriptor IMG_ZOOM_TO_FIT_HORIZ = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/horiz16.png");
182 private final ImageDescriptor IMG_ZOOM_TO_FIT_VERT = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/vert16.png");
183 private final ImageDescriptor IMG_AUTOSCALE = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/autoscale16.png");
185 IPreferenceChangeListener preferenceListener = new IPreferenceChangeListener() {
187 public void preferenceChange(PreferenceChangeEvent event) {
189 System.err.println("Warning: pref change to disposed TimeSeriesEditor");
193 if ( event.getKey().equals(ChartPreferences.P_REDRAW_INTERVAL ) ||
194 event.getKey().equals(ChartPreferences.P_AUTOSCALE_INTERVAL )) {
195 long redraw_interval = chartPreferenceNode.getLong(ChartPreferences.P_REDRAW_INTERVAL, ChartPreferences.DEFAULT_REDRAW_INTERVAL);
196 long autoscale_interval = chartPreferenceNode.getLong(ChartPreferences.P_AUTOSCALE_INTERVAL, ChartPreferences.DEFAULT_AUTOSCALE_INTERVAL);
197 setInterval( redraw_interval, autoscale_interval );
199 if ( event.getKey().equals(ChartPreferences.P_DRAW_SAMPLES )) {
200 boolean draw_samples = chartPreferenceNode.getBoolean(ChartPreferences.P_DRAW_SAMPLES, ChartPreferences.DEFAULT_DRAW_SAMPLES);
201 setDrawSamples( draw_samples );
203 if ( event.getKey().equals(ChartPreferences.P_TIMEFORMAT ) ) {
204 String s = chartPreferenceNode.get(ChartPreferences.P_TIMEFORMAT, ChartPreferences.DEFAULT_TIMEFORMAT);
205 TimeFormat tf = TimeFormat.valueOf( s );
206 if (tf!=null) setTimeFormat( tf );
208 if ( event.getKey().equals(ChartPreferences.P_VALUEFORMAT ) ) {
209 String s = chartPreferenceNode.get(ChartPreferences.P_VALUEFORMAT, ChartPreferences.DEFAULT_VALUEFORMAT);
210 ValueFormat vf = ValueFormat.valueOf( s );
211 if (vf!=null) setValueFormat( vf );
213 if ( event.getKey().equals(ChartPreferences.P_ITEMPLACEMENT)) {
214 String s = chartPreferenceNode.get(ChartPreferences.P_ITEMPLACEMENT, ChartPreferences.DEFAULT_ITEMPLACEMENT);
215 ItemPlacement ip = ItemPlacement.valueOf(s);
216 if (trendNode!=null) trendNode.itemPlacement = ip;
218 if ( event.getKey().equals(ChartPreferences.P_TEXTQUALITY) || event.getKey().equals(ChartPreferences.P_LINEQUALITY) ) {
219 String s1 = chartPreferenceNode.get(ChartPreferences.P_TEXTQUALITY, ChartPreferences.DEFAULT_TEXTQUALITY);
220 String s2 = chartPreferenceNode.get(ChartPreferences.P_LINEQUALITY, ChartPreferences.DEFAULT_LINEQUALITY);
221 LineQuality q1 = LineQuality.valueOf(s1);
222 LineQuality q2 = LineQuality.valueOf(s2);
223 if (trendNode!=null) trendNode.quality.textQuality = q1;
224 if (trendNode!=null) trendNode.quality.lineQuality = q2;
231 * The project which this editor is listening to for changes to
232 * {@link ChartKeys.ChartSourceKey keys}.
237 * The model resource containing the input chart resource.
242 * The text widget shown only if there is no IProject available at the time
243 * of editor part creation.
248 * A unique key for making DB requests chart editor specific without binding
249 * the requests to the editor object itself.
251 UUID uniqueChartEditorId = UUID.randomUUID();
255 CanvasContext cvsCtx;
258 StepListener stepListener;
259 MilestoneSpecListener milestoneListener;
260 MilestoneSpecQuery milestoneQuery;
263 * The ChartData instance used by this editor for sourcing data at any given
264 * moment. Project hint instances are copied into this instance.
266 final ChartData chartData = new ChartData(null, null, null, null, null, null);
269 * The ChartSourceKey to match the model this editor was opened for.
271 * @see #init(IEditorSite, IEditorInput)
273 ChartKeys.ChartSourceKey chartDataKey;
277 * Context management utils
279 protected IThreadWorkQueue swt;
280 protected ContextUtil contextUtil;
282 class ExperimentStateListener implements IExperimentListener {
284 public void stateChanged(ExperimentState state) {
285 TrendSpec spec = trendNode.getTrendSpec();
286 spec.experimentIsRunning = state == ExperimentState.RUNNING;
287 if (spec.experimentIsRunning && spec.viewProfile.trackExperimentTime) {
288 TrendParticipant t = tp;
295 ExperimentStateListener experimentStateListener = new ExperimentStateListener();
297 class ChartDataListener extends HintListenerAdapter implements Runnable {
299 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
300 if (key.equals(chartDataKey)) {
302 if (!cvsCtx.isDisposed() && cvsCtx.isAlive()) {
303 cvsCtx.getThreadAccess().asyncExec(this);
310 if (cvsCtx.isDisposed() || !cvsCtx.isAlive()) return;
311 ChartData data = Simantics.getProject().getHint(chartDataKey);
312 setInput( data, trendNode.getTrendSpec() );
316 ChartDataListener chartDataListener = new ChartDataListener();
318 class ValueTipBoxPositionListener extends HintListenerAdapter {
320 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
321 if (key.equals(TrendParticipant.KEY_VALUE_TIP_BOX_RELATIVE_POS) && newValue != null) {
322 Session s = Simantics.getSession();
323 ChartResource CHART = s.getService(ChartResource.class);
324 Point2D p = (Point2D) newValue;
325 double[] value = { p.getX(), p.getY() };
326 s.asyncRequest(new SetProperty(getInputResource(), CHART.Chart_valueViewPosition, value, Bindings.DOUBLE_ARRAY));
331 ValueTipBoxPositionListener valueTipBoxPositionListener = new ValueTipBoxPositionListener();
335 IStateListener linkTimeStateListener = new IStateListener() {
337 public void handleStateChange(State state, Object oldValue) {
338 final ChartLinkData newData = (ChartLinkData) linkTimeState.getValue();
339 trendNode.autoscaletime = newData == null || newData.sender == TimeSeriesEditor.this;
341 if ( newData == null || newData.sender==TimeSeriesEditor.this ) return;
342 TrendNode tn = trendNode;
343 HorizRuler hr = tn!=null ? tn.horizRuler : null;
345 ChartLinkData oldData = new ChartLinkData();
348 if ( hr != null && !ObjectUtils.objectEquals(tn.valueTipTime, newData.valueTipTime)) {
349 tn.valueTipTime = newData.valueTipTime;
353 if ( hr != null && (oldData.from!=newData.from || oldData.sx!=newData.sx)) {
355 cvsCtx.getThreadAccess().asyncExec( new Runnable() {
358 boolean b = trendNode.horizRuler.setFromScale(newData.from, newData.sx);
359 trendNode.horizRuler.autoscroll = false;
369 HorizRuler.TimeWindowListener horizRulerListener = new HorizRuler.TimeWindowListener() {
371 public void onNewWindow(double from, double end, double scalex) {
372 final ChartLinkData oldData = (ChartLinkData) linkTimeState.getValue();
373 if (oldData != null) {
374 ChartLinkData data = new ChartLinkData(TimeSeriesEditor.this, from, end, scalex);
375 data.valueTipTime = trendNode.valueTipTime;
376 linkTimeState.setValue( data );
381 class ChassisListener implements IChassisListener {
383 public void chassisClosed(ICanvasChassis sender) {
384 // Prevent deadlock while disposing which using syncExec would result in.
385 final ICanvasContext ctx = cvsCtx;
386 ThreadUtils.asyncExec(ctx.getThreadAccess(), new Runnable() {
390 AWTChassis awt = canvas.getAWTComponent();
392 awt.setCanvasContext(null);
397 canvas.removeChassisListener(ChassisListener.this);
401 ActiveSelectionProvider selectionProvider = new ActiveSelectionProvider();
402 MenuManager menuManager;
404 public TimeSeriesEditor() {
405 log = Logger.getLogger( this.getClass().getName() );
408 boolean isTimeLinked() {
409 if (linkTimeState==null) return false;
410 Boolean isLinked = (Boolean) linkTimeState.getValue();
411 return isLinked != null && isLinked;
415 public void init(IEditorSite site, IEditorInput input) throws PartInitException {
416 super.init(site, input);
418 this.model = Simantics.getSession().syncRequest( new Model( getInputResource() ) );
419 this.chartDataKey = ChartKeys.chartSourceKey(model);
420 } catch (DatabaseException e) {
421 throw new PartInitException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Input " + getInputResource() + " is not part of a model.", e));
426 * Invoke this only from the AWT thread.
429 protected void setCanvasContext(final SWTChassis chassis, final ICanvasContext context) {
430 // Cannot directly invoke SWTChassis.setCanvasContext only because it
431 // needs to be invoked in the SWT thread and AWTChassis.setCanvasContext in the
432 // AWT thread, but directly invoking SWTChassis.setCanvasContext would call both
433 // in the SWT thread which would cause synchronous scheduling of AWT
434 // runnables which is always a potential source of deadlocks.
435 chassis.getAWTComponent().setCanvasContext(context);
436 SWTUtils.asyncExec(chassis, new Runnable() {
439 if (!chassis.isDisposed())
440 // For AWT, this is a no-operation.
441 chassis.setCanvasContext(context);
447 public void createPartControl(Composite parent) {
448 display = parent.getDisplay();
449 swt = SWTThread.getThreadAccess(display);
451 // Must have a project to attach to, otherwise the editor is useless.
452 project = Simantics.peekProject();
453 if (project == null) {
454 errorText = new Text(parent, SWT.NONE);
455 errorText.setText("No project is open.");
456 errorText.setEditable(false);
460 // Create the canvas context here before finishing createPartControl
461 // to give anybody requiring access to this editor's ICanvasContext
462 // a chance to do their work.
463 // The context can be created in SWT thread without scheduling
464 // to the context thread and having potential deadlocks.
465 // The context is locked here and unlocked after it has been
466 // initialized in the AWT thread.
467 IThreadWorkQueue thread = AWTThread.getThreadAccess();
468 cvsCtx = new CanvasContext(thread);
469 cvsCtx.setLocked(true);
471 final IWorkbenchWindow win = getEditorSite().getWorkbenchWindow();
472 final IWorkbenchPage page = getEditorSite().getPage();
474 canvas = new SWTChassis(parent, SWT.NONE);
475 canvas.populate(parameter -> {
477 canvas.addChassisListener(new ChassisListener());
478 initializeCanvas(canvas, cvsCtx, win, page);
483 ICommandService service = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);
484 Command command = service.getCommand( LinkTimeHandler.COMMAND_ID );
485 linkTimeState = command.getState( LinkTimeHandler.STATE_ID );
486 if ( linkTimeState != null ) linkTimeState.addListener( linkTimeStateListener );
490 // Start tracking editor input validity.
491 activateValidation();
493 // Provide input as selection for property page.
494 selectionProvider.setSelection( new StructuredSelection(getInputResource()) );
495 getSite().setSelectionProvider( selectionProvider );
498 protected void initializeCanvas(final SWTChassis chassis, CanvasContext cvsCtx, IWorkbenchWindow window, IWorkbenchPage page) {
499 // Initialize canvas context
500 TrendSpec nodata = new TrendSpec();
502 cvsCtx = TrendInitializer.defaultInitializeCanvas(cvsCtx, null, null, null, nodata);
504 tp = cvsCtx.getAtMostOneItemOfClass(TrendParticipant.class);
507 IContextService contextService = (IContextService) getSite().getService(IContextService.class);
508 contextUtil = new ContextUtil(contextService, swt);
511 cvsCtx.add( new SubscriptionDropParticipant( getInputResource() ) );
512 cvsCtx.add( new KeyToCommand( ChartKeyBindings.DEFAULT_BINDINGS ) );
513 cvsCtx.add( new ChartPasteHandler2(getInputResource().get()) );
514 cvsCtx.add(contextUtil);
516 // Context management
517 cvsCtx.add(new SGFocusParticipant(canvas, "org.simantics.charts.editor.context"));
519 stepListener = new StepListener( tp );
520 trendNode = tp.getTrend();
521 trendNode.titleNode.remove();
522 trendNode.titleNode = null;
525 trendNode.horizRuler.listener = horizRulerListener;
527 final ChartLinkData linkTime = (ChartLinkData) linkTimeState.getValue();
528 if (linkTime!=null) trendNode.horizRuler.setFromEnd(linkTime.from, linkTime.sx);
530 // Handle mouse moved event after TrendParticipant.
531 // This handler forwards trend.mouseHoverTime to linkTimeState
532 cvsCtx.getEventHandlerStack().add( new IEventHandler() {
535 public int getEventMask() {
536 return EventTypes.MouseMovedMask | EventTypes.MouseClickMask | EventTypes.CommandMask | EventTypes.KeyPressed;
540 public boolean handleEvent(Event e) {
542 // System.out.println("LinkEventHandler: "+e);
543 ChartLinkData oldData = (ChartLinkData) linkTimeState.getValue();
545 ChartLinkData newData = new ChartLinkData();
547 if (!newData.equals(oldData)) {
548 // System.out.println("Sending new link-data");
549 linkTimeState.setValue( newData );
555 canvas.getHintContext().setHint( SWTChassis.KEY_EDITORPART, this);
556 canvas.getHintContext().setHint( SWTChassis.KEY_WORKBENCHPAGE, page);
557 canvas.getHintContext().setHint( SWTChassis.KEY_WORKBENCHWINDOW, window);
559 // Canvas context is initialized, unlock it now to allow rendering.
560 cvsCtx.setLocked(false);
562 setCanvasContext(chassis, cvsCtx);
564 cvsCtx.getEventHandlerStack().add(new IEventHandler() {
566 public boolean handleEvent(Event e) {
567 MouseButtonReleasedEvent event = (MouseButtonReleasedEvent) e;
568 if (event.button != MouseEvent.RIGHT_BUTTON)
572 SWTDPIUtil.downscaleSwt((int) event.screenPosition.getX()),
573 SWTDPIUtil.downscaleSwt((int) event.screenPosition.getY()));
574 SWTUtils.asyncExec(chassis, () -> {
575 if (!canvas.isDisposed())
581 public int getEventMask() {
582 return EventTypes.MouseButtonReleasedMask;
586 // Track data source and preinitialize chartData
587 project.addHintListener(chartDataListener);
588 chartData.readFrom( (ChartData) project.getHint( chartDataKey ) );
589 chartData.reference();
591 if (chartData.run != null) {
592 milestoneListener = new MilestoneSpecListener();
593 milestoneQuery = new MilestoneSpecQuery( chartData.run );
594 getSession().asyncRequest( milestoneQuery, milestoneListener );
597 // IMPORTANT: Only after preinitializing chartData, start tracking chart configuration
598 trackChartConfiguration();
601 // Write changes to TrendSpec.viewProfile.valueViewPosition[XY]
602 // back to the graph database.
603 cvsCtx.getHintStack().addHintListener(valueTipBoxPositionListener);
606 private void addPopupMenu() {
607 menuManager = new MenuManager("Time Series Editor", CONTEXT_MENU_ID);
608 menuManager.setRemoveAllWhenShown(true);
609 Menu menu = menuManager.createContextMenu(canvas);
610 canvas.setMenu(menu);
611 getEditorSite().registerContextMenu(menuManager.getId(), menuManager, selectionProvider);
613 // Add support for some built-in actions in the context menu.
614 menuManager.addMenuListener(new IMenuListener() {
616 public void menuAboutToShow(IMenuManager manager) {
617 // Not initialized yet, prevent NPE.
618 TrendNode trendNode = TimeSeriesEditor.this.trendNode;
619 TrendParticipant tp = TimeSeriesEditor.this.tp;
620 if (trendNode == null || tp == null)
623 TrendSpec trendSpec = trendNode.getTrendSpec();
624 ItemNode hoverItem = tp.hoveringItem;
625 if (hoverItem != null && hoverItem.item != null) {
626 Resource component = resolveReferencedComponent(getResourceInput(), hoverItem.item.variableId);
627 if (component != null) {
628 manager.add(new PerformDefaultAction("Show Referenced Component", canvas, component));
631 Resource chart = TimeSeriesEditor.this.getInputResource();
632 if ( chart != null ) {
634 Resource chartItem = getSession().sync( new FindChartItemForTrendItem(chart, hoverItem.item) );
635 if (chartItem != null) {
636 manager.add(new HideItemsAction("Hide Item", true, Collections.singletonList(chartItem)));
637 manager.add(new Separator());
638 manager.add(new ChartPreferencesAction(getSite()));
639 manager.add(new Separator());
640 manager.add(new PropertiesAction("Item Properties", canvas, chartItem));
641 manager.add(new PropertiesAction("Chart Properties", canvas, chart));
643 } catch (DatabaseException e) {
644 Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to resolve context menu items.", e));
648 boolean hairlineMovementAllowed =
649 !(trendSpec.experimentIsRunning &&
650 trendSpec.viewProfile.trackExperimentTime);
652 Resource chart = TimeSeriesEditor.this.getInputResource();
653 manager.add(new Separator());
654 manager.add(new MoveHairlineAction(
655 "Move Hairline Here",
657 hairlineMovementAllowed,
659 trendNode.mouseHoverTime
661 manager.add(new MoveHairlineAction(
662 "Move Hairline To Current Time",
664 hairlineMovementAllowed,
666 trendNode.horizRuler.getItemEndTime(),
669 manager.add(new TrackExperimentTimeAction(
670 "Hairline Tracks Current Time",
672 trendSpec.viewProfile.trackExperimentTime));
673 manager.add(new Separator());
674 manager.add(new SendCommandAction("Zoom to Fit", IMG_ZOOM_TO_FIT, cvsCtx, Commands.ZOOM_TO_FIT));
675 manager.add(new SendCommandAction("Zoom to Fit Horizontally", IMG_ZOOM_TO_FIT_HORIZ, cvsCtx, Commands.ZOOM_TO_FIT_HORIZ));
676 manager.add(new SendCommandAction("Zoom to Fit Vertically", IMG_ZOOM_TO_FIT_VERT, cvsCtx, Commands.ZOOM_TO_FIT_VERT));
677 manager.add(new SendCommandAction("Autoscale Chart", IMG_AUTOSCALE, cvsCtx, Commands.AUTOSCALE));
678 manager.add(new Separator());
679 manager.add(new ChartPreferencesAction(getSite()));
680 manager.add(new Separator());
681 manager.add(new PropertiesAction("Chart Properties", canvas, chart));
687 protected Resource resolveReferencedComponent(IResourceEditorInput resourceInput, final String variableId) {
689 return getSession().sync(new UniqueRead<Resource>() {
691 public Resource perform(ReadGraph graph) throws DatabaseException {
692 Variable configuration = Variables.getConfigurationContext(graph, getInputResource());
693 RVI rvi = RVI.fromResourceFormat(graph, variableId);
694 rvi = new RVIBuilder(rvi).removeFromFirstRole(Role.PROPERTY).toRVI();
697 Variable var = rvi.resolve(graph, configuration);
698 return var.getPossibleRepresents(graph);
701 } catch (DatabaseException e) {
702 ErrorLogger.defaultLogError(e);
707 private void showPopup(Point p) {
708 menuManager.getMenu().setLocation(p);
709 menuManager.getMenu().setVisible(true);
712 private void trackChartConfiguration() {
713 getSession().asyncRequest(new TrendSpecQuery( uniqueChartEditorId, getInputResource() ), new TrendSpecListener());
714 getSession().asyncRequest(new ActiveRunQuery( uniqueChartEditorId, getInputResource() ), new ActiveRunListener());
718 public void setFocus() {
719 if (errorText != null)
720 errorText.setFocus();
726 public void dispose() {
727 if (disposed == true) return;
730 if (trendNode!=null && trendNode.horizRuler!=null) {
731 trendNode.horizRuler.listener = null;
734 if ( linkTimeState != null ) linkTimeState.removeListener( linkTimeStateListener );
736 canvas.getHintContext().removeHint( SWTChassis.KEY_EDITORPART );
737 canvas.getHintContext().removeHint( SWTChassis.KEY_WORKBENCHPAGE );
738 canvas.getHintContext().removeHint( SWTChassis.KEY_WORKBENCHWINDOW );
740 if ( chartPreferenceNode!= null ) {
741 chartPreferenceNode.removePreferenceChangeListener( preferenceListener );
744 MilestoneSpecListener ml = milestoneListener;
745 if (ml!=null) ml.dispose();
747 if (project != null) {
748 project.removeHintListener(chartDataListener);
751 if (chartData != null) {
752 if (chartData.datasource!=null)
753 chartData.datasource.removeListener( stepListener );
754 if (chartData.experiment!=null)
755 chartData.experiment.removeListener( experimentStateListener );
756 chartData.dereference();
757 chartData.readFrom( null );
764 * @param data new data or null
765 * @param newSpec new spec or null
768 @SuppressWarnings("unused")
769 public void setInput(ChartData data, TrendSpec newSpec) {
770 boolean doLayout = false;
772 // Disregard input if it is not for this chart's containing model.
773 if (data != null && data.model != null && !data.model.equals(model))
776 // Accommodate Datasource changes
778 Datasource oldDatasource = chartData==null?null:chartData.datasource;
779 Datasource newDatasource = data==null?null:data.datasource;
780 //if ( !ObjectUtils.objectEquals(oldDatasource, newDatasource) )
782 if (oldDatasource!=null) oldDatasource.removeListener( stepListener );
783 if (newDatasource!=null) newDatasource.addListener( stepListener );
788 IExperiment oldExperiment = chartData==null?null:chartData.experiment;
789 IExperiment newExperiment = data==null?null:data.experiment;
790 //if ( !ObjectUtils.objectEquals(oldExperiment, newExperiment) )
792 if (oldExperiment!=null) oldExperiment.removeListener( experimentStateListener );
793 if (newExperiment!=null) newExperiment.addListener( experimentStateListener );
797 // Accommodate Historian changes
799 HistoryManager oldHistorian = trendNode.historian==null?null:trendNode.historian;
800 HistoryManager newHistorian = data==null?null:data.history;
801 Collector newCollector = data==null?null:data.collector;
802 // if ( !ObjectUtils.objectEquals(oldHistorian, newHistorian) )
804 if (newHistorian instanceof FileHistory) {
805 FileHistory fh = (FileHistory) newHistorian;
806 System.out.println("History = "+fh.getWorkarea());
808 trendNode.setHistorian( newHistorian, newCollector );
809 doLayout |= trendNode.autoscale(true, true) | !ObjectUtils.objectEquals(oldHistorian, newHistorian);
812 // Accommodate TrendSpec changes
813 TrendSpec oldSpec = trendNode.getTrendSpec();
814 if ( !newSpec.equals(oldSpec) ) {
815 trendNode.setTrendSpec( newSpec==null?TrendSpec.EMPTY:newSpec );
821 Resource newExperimentResource = data==null ? null : data.run;
822 Resource oldExperimentResource = this.chartData == null ? null : this.chartData.run;
826 if (!ObjectUtils.objectEquals(oldExperimentResource, newExperimentResource)) {
828 // Dispose old listener & Query
829 if (milestoneListener!=null) {
830 milestoneListener.dispose();
831 milestoneListener = null;
833 if (milestoneQuery!=null) {
834 milestoneQuery = null;
837 trendNode.setMilestones( MilestoneSpec.EMPTY );
839 if (newExperimentResource != null) {
840 milestoneListener = new MilestoneSpecListener();
841 milestoneQuery = new MilestoneSpecQuery( newExperimentResource );
842 Simantics.getSession().asyncRequest( milestoneQuery, milestoneListener );
848 if (doLayout) trendNode.layout();
849 this.chartData.dereference();
850 this.chartData.readFrom( data );
851 this.chartData.reference();
854 if (!ObjectUtils.objectEquals(oldExperimentResource, newExperimentResource)) {
855 resetViewAfterDataChange();
860 class ActiveRunListener implements SyncListener<Resource> {
862 public void exception(ReadGraph graph, Throwable throwable) {
863 ErrorLogger.defaultLogError(throwable);
864 ShowMessage.showError(throwable.getClass().getSimpleName(), throwable.getMessage());
867 public void execute(ReadGraph graph, final Resource run) throws DatabaseException {
869 SimulationResource SIMU = SimulationResource.getInstance(graph);
870 Variable var = Variables.getPossibleVariable(graph, run);
871 IExperiment exp = var != null ? var.getPossiblePropertyValue(graph, SIMU.Run_iExperiment) : null;
872 ITrendSupport ts = exp != null ? exp.getService(ITrendSupport.class) : null;
874 ts.setChartData(graph);
878 public boolean isDisposed() {
879 return TimeSeriesEditor.this.disposed;
883 class TrendSpecListener implements AsyncListener<TrendSpec> {
885 public void exception(AsyncReadGraph graph, Throwable throwable) {
887 ErrorLogger.defaultLogError(throwable);
888 ShowMessage.showError(throwable.getClass().getSimpleName(), throwable.getMessage());
891 public void execute(AsyncReadGraph graph, final TrendSpec result) {
892 if (result == null) {
893 log.log(Level.INFO, "Chart configuration removed");
895 log.log(Level.INFO, "Chart configuration updated: " + result);
898 // Reload chart in AWT Thread
899 AWTThread.getThreadAccess().asyncExec(new Runnable() {
903 setInput( chartData, result );
908 public boolean isDisposed() {
909 return TimeSeriesEditor.this.disposed;
913 class MilestoneSpecListener implements AsyncListener<MilestoneSpec> {
914 boolean disposed = false;
916 public void execute(AsyncReadGraph graph, final MilestoneSpec result) {
917 AWTThread.INSTANCE.asyncExec(new Runnable() {
919 trendNode.setMilestones(result);
924 public void exception(AsyncReadGraph graph, Throwable throwable) {
929 public boolean isDisposed() {
933 public void dispose() {
939 private void trackPreferences() {
940 chartPreferenceNode = InstanceScope.INSTANCE.getNode( "org.simantics.charts" );
941 chartPreferenceNode.addPreferenceChangeListener( preferenceListener );
942 long redrawInterval = chartPreferenceNode.getLong(ChartPreferences.P_REDRAW_INTERVAL, ChartPreferences.DEFAULT_REDRAW_INTERVAL);
943 long autoscaleInterval = chartPreferenceNode.getLong(ChartPreferences.P_AUTOSCALE_INTERVAL, ChartPreferences.DEFAULT_AUTOSCALE_INTERVAL);
944 setInterval(redrawInterval, autoscaleInterval);
946 String timeFormat = chartPreferenceNode.get(ChartPreferences.P_TIMEFORMAT, ChartPreferences.DEFAULT_TIMEFORMAT);
947 TimeFormat tf = TimeFormat.valueOf( timeFormat );
948 if (tf!=null) setTimeFormat( tf );
950 Boolean drawSamples = chartPreferenceNode.getBoolean(ChartPreferences.P_DRAW_SAMPLES, ChartPreferences.DEFAULT_DRAW_SAMPLES);
951 setDrawSamples(drawSamples);
953 String valueFormat = chartPreferenceNode.get(ChartPreferences.P_VALUEFORMAT, ChartPreferences.DEFAULT_VALUEFORMAT);
954 ValueFormat vf = ValueFormat.valueOf( valueFormat );
955 if (vf!=null) setValueFormat( vf );
957 String s = chartPreferenceNode.get(ChartPreferences.P_ITEMPLACEMENT, ChartPreferences.DEFAULT_ITEMPLACEMENT);
958 ItemPlacement ip = ItemPlacement.valueOf(s);
959 if (trendNode!=null) trendNode.itemPlacement = ip;
961 String s1 = chartPreferenceNode.get(ChartPreferences.P_TEXTQUALITY, ChartPreferences.DEFAULT_TEXTQUALITY);
962 String s2 = chartPreferenceNode.get(ChartPreferences.P_LINEQUALITY, ChartPreferences.DEFAULT_LINEQUALITY);
963 LineQuality q1 = LineQuality.valueOf(s1);
964 LineQuality q2 = LineQuality.valueOf(s2);
965 if (trendNode!=null) trendNode.quality.textQuality = q1;
966 if (trendNode!=null) trendNode.quality.lineQuality = q2;
970 private void setInterval(long redrawInterval, long autoscaleInterval) {
971 redrawInterval = Math.max(1, redrawInterval);
972 long pulse = Math.min(50, redrawInterval);
973 pulse = Math.min(pulse, autoscaleInterval);
974 IHintContext h = canvas.getCanvasContext().getDefaultHintContext();
975 h.setHint(TimeParticipant.KEY_TIME_PULSE_INTERVAL, pulse);
976 h.setHint(TrendParticipant.KEY_TREND_DRAW_INTERVAL, redrawInterval);
977 h.setHint(TrendParticipant.KEY_TREND_AUTOSCALE_INTERVAL, autoscaleInterval);
980 private void setDrawSamples(boolean value) {
981 trendNode.drawSamples = value;
986 private void setTimeFormat( TimeFormat tf ) {
987 if (trendNode.timeFormat == tf) return;
988 trendNode.timeFormat = tf;
993 private void setValueFormat( ValueFormat vf ) {
994 if (trendNode.valueFormat == vf) return;
995 trendNode.valueFormat = vf;
1000 @SuppressWarnings("unchecked")
1002 public <T> T getAdapter(Class<T> adapter) {
1003 if (adapter == INode.class) {
1004 ICanvasContext ctx = cvsCtx;
1006 return (T) ctx.getSceneGraph();
1008 if (adapter == IPropertyPage.class)
1009 return (T) new StandardPropertyPage(getSite(), getPropertyPageContexts());
1010 if (adapter == ICanvasContext.class)
1012 return super.getAdapter(adapter);
1015 protected Set<String> getPropertyPageContexts() {
1017 return BrowseContext.getBrowseContextClosure(Simantics.getSession(), Collections.singleton(ROOT_PROPERTY_BROWSE_CONTEXT));
1018 } catch (DatabaseException e) {
1019 ExceptionUtils.logAndShowError("Failed to load modeled browse contexts for property page, see exception for details.", e);
1020 return Collections.singleton(ROOT_PROPERTY_BROWSE_CONTEXT);
1025 * Add from, end, (scale x) to argument array
1026 * @param fromEnd array of 2 or 3
1028 public void getFromEnd(ChartLinkData data) {
1030 TrendNode tn = trendNode;
1031 data.valueTipTime = tn.valueTipTime;
1032 HorizRuler hr = tn!=null ? tn.horizRuler : null;
1034 data.from = hr.from;
1036 double len = hr.end-hr.from;
1037 double wid = tn.plot.getWidth();
1038 if ( wid==0.0 ) wid = 0.1;
1043 @SuppressWarnings("unused")
1044 private static boolean doubleEquals(double a, double b) {
1045 if (Double.isNaN(a) && Double.isNaN(b)) return true;
1049 protected void resetViewAfterDataChange() {
1051 CanvasUtils.sendCommand(cvsCtx, Commands.CANCEL);
1052 CanvasUtils.sendCommand(cvsCtx, Commands.AUTOSCALE);