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.e4;
14 import java.awt.geom.Point2D;
15 import java.util.Collections;
16 import java.util.List;
18 import java.util.UUID;
19 import java.util.logging.Level;
20 import java.util.logging.Logger;
22 import javax.inject.Inject;
24 import org.eclipse.core.runtime.IAdaptable;
25 import org.eclipse.core.runtime.IStatus;
26 import org.eclipse.core.runtime.Status;
27 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
28 import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
29 import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
30 import org.eclipse.core.runtime.preferences.InstanceScope;
31 import org.eclipse.e4.ui.model.application.MApplication;
32 import org.eclipse.e4.ui.model.application.MApplicationElement;
33 import org.eclipse.e4.ui.model.application.ui.MUIElement;
34 import org.eclipse.e4.ui.model.application.ui.basic.MPart;
35 import org.eclipse.e4.ui.model.application.ui.menu.MToolBar;
36 import org.eclipse.e4.ui.services.EContextService;
37 import org.eclipse.e4.ui.workbench.modeling.EModelService;
38 import org.eclipse.e4.ui.workbench.modeling.ISelectionListener;
39 import org.eclipse.jface.action.IMenuListener;
40 import org.eclipse.jface.action.IMenuManager;
41 import org.eclipse.jface.action.MenuManager;
42 import org.eclipse.jface.action.Separator;
43 import org.eclipse.jface.resource.ImageDescriptor;
44 import org.eclipse.jface.viewers.StructuredSelection;
45 import org.eclipse.swt.SWT;
46 import org.eclipse.swt.graphics.Point;
47 import org.eclipse.swt.widgets.Composite;
48 import org.eclipse.swt.widgets.Display;
49 import org.eclipse.swt.widgets.Menu;
50 import org.eclipse.swt.widgets.Text;
51 import org.eclipse.ui.IEditorInput;
52 import org.eclipse.ui.IEditorSite;
53 import org.simantics.Simantics;
54 import org.simantics.browsing.ui.model.browsecontexts.BrowseContext;
55 import org.simantics.charts.Activator;
56 import org.simantics.charts.ITrendSupport;
57 import org.simantics.charts.editor.ActiveRunQuery;
58 import org.simantics.charts.editor.ChartData;
59 import org.simantics.charts.editor.ChartKeyBindings;
60 import org.simantics.charts.editor.ChartKeys;
61 import org.simantics.charts.editor.ChartPasteHandler2;
62 import org.simantics.charts.editor.HideItemsAction;
63 import org.simantics.charts.editor.MoveHairlineAction;
64 import org.simantics.charts.editor.PropertiesAction;
65 import org.simantics.charts.editor.SendCommandAction;
66 import org.simantics.charts.editor.SubscriptionDropParticipant;
67 import org.simantics.charts.editor.TrackExperimentTimeAction;
68 import org.simantics.charts.ontology.ChartResource;
69 import org.simantics.charts.preference.ChartPreferences;
70 import org.simantics.charts.query.FindChartItemForTrendItem;
71 import org.simantics.charts.query.MilestoneSpecQuery;
72 import org.simantics.charts.query.SetProperty;
73 import org.simantics.charts.query.TrendSpecQuery;
74 import org.simantics.charts.ui.ChartLinkData;
75 import org.simantics.databoard.Bindings;
76 import org.simantics.databoard.util.ObjectUtils;
77 import org.simantics.db.AsyncReadGraph;
78 import org.simantics.db.ReadGraph;
79 import org.simantics.db.Resource;
80 import org.simantics.db.Session;
81 import org.simantics.db.common.request.ParametrizedRead;
82 import org.simantics.db.common.request.UniqueRead;
83 import org.simantics.db.exception.DatabaseException;
84 import org.simantics.db.layer0.request.combinations.Combinators;
85 import org.simantics.db.layer0.variable.RVI;
86 import org.simantics.db.layer0.variable.RVIBuilder;
87 import org.simantics.db.layer0.variable.Variable;
88 import org.simantics.db.layer0.variable.Variables;
89 import org.simantics.db.layer0.variable.Variables.Role;
90 import org.simantics.db.procedure.AsyncListener;
91 import org.simantics.db.procedure.SyncListener;
92 import org.simantics.diagram.participant.SGFocusParticipant;
93 import org.simantics.diagram.participant.e4.ContextUtil;
94 import org.simantics.g2d.canvas.ICanvasContext;
95 import org.simantics.g2d.canvas.impl.CanvasContext;
96 import org.simantics.g2d.chassis.AWTChassis;
97 import org.simantics.g2d.chassis.ICanvasChassis;
98 import org.simantics.g2d.chassis.IChassisListener;
99 import org.simantics.g2d.chassis.SWTChassis;
100 import org.simantics.g2d.participant.KeyToCommand;
101 import org.simantics.g2d.participant.TimeParticipant;
102 import org.simantics.g2d.utils.CanvasUtils;
103 import org.simantics.history.Collector;
104 import org.simantics.history.HistoryManager;
105 import org.simantics.history.impl.FileHistory;
106 import org.simantics.project.IProject;
107 import org.simantics.scenegraph.INode;
108 import org.simantics.scenegraph.g2d.events.Event;
109 import org.simantics.scenegraph.g2d.events.EventTypes;
110 import org.simantics.scenegraph.g2d.events.IEventHandler;
111 import org.simantics.scenegraph.g2d.events.MouseEvent;
112 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
113 import org.simantics.scenegraph.g2d.events.command.Commands;
114 import org.simantics.selectionview.StandardPropertyPage;
115 import org.simantics.simulation.data.Datasource;
116 import org.simantics.simulation.experiment.ExperimentState;
117 import org.simantics.simulation.experiment.IExperiment;
118 import org.simantics.simulation.experiment.IExperimentListener;
119 import org.simantics.simulation.ontology.SimulationResource;
120 import org.simantics.trend.TrendInitializer;
121 import org.simantics.trend.TrendInitializer.StepListener;
122 import org.simantics.trend.configuration.ItemPlacement;
123 import org.simantics.trend.configuration.LineQuality;
124 import org.simantics.trend.configuration.TimeFormat;
125 import org.simantics.trend.configuration.TrendSpec;
126 import org.simantics.trend.impl.HorizRuler;
127 import org.simantics.trend.impl.ItemNode;
128 import org.simantics.trend.impl.MilestoneSpec;
129 import org.simantics.trend.impl.TrendNode;
130 import org.simantics.trend.impl.TrendParticipant;
131 import org.simantics.ui.workbench.IPropertyPage;
132 import org.simantics.ui.workbench.IResourceEditorInput;
133 import org.simantics.ui.workbench.ResourceEditorInput;
134 import org.simantics.ui.workbench.action.PerformDefaultAction;
135 import org.simantics.ui.workbench.e4.E4ResourceEditorBase;
136 import org.simantics.ui.workbench.editor.input.InputValidationCombinators;
137 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
138 import org.simantics.utils.datastructures.hints.IHintContext;
139 import org.simantics.utils.datastructures.hints.IHintContext.Key;
140 import org.simantics.utils.datastructures.hints.IHintObservable;
141 import org.simantics.utils.format.ValueFormat;
142 import org.simantics.utils.threads.AWTThread;
143 import org.simantics.utils.threads.IThreadWorkQueue;
144 import org.simantics.utils.threads.SWTThread;
145 import org.simantics.utils.threads.ThreadUtils;
146 import org.simantics.utils.ui.BundleUtils;
147 import org.simantics.utils.ui.ErrorLogger;
148 import org.simantics.utils.ui.ExceptionUtils;
149 import org.simantics.utils.ui.SWTUtils;
150 import org.simantics.utils.ui.dialogs.ShowMessage;
151 import org.simantics.utils.ui.jface.ActiveSelectionProvider;
154 * TimeSeriesEditor is an interactive part that draws a time series chart.
156 * The configuration model is {@link TrendSpec} which is read through
157 * {@link TrendSpecQuery}. In Simantics Environment the
158 * editor input is {@link ResourceEditorInput}.
160 * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
161 * @author Tuukka Lehtonen
163 public class TimeSeriesEditor extends E4ResourceEditorBase implements IAdaptable {
165 ParametrizedRead<IResourceEditorInput, Boolean> INPUT_VALIDATOR =
167 InputValidationCombinators.hasURI(),
168 InputValidationCombinators.extractInputResource()
171 protected ParametrizedRead<IResourceEditorInput, Boolean> getInputValidator() {
172 return INPUT_VALIDATOR;
176 * The root property browse context of the time series editor. A transitive
177 * closure is calculated for this context.
179 private static String ROOT_PROPERTY_BROWSE_CONTEXT = ChartResource.URIs.ChartBrowseContext;
182 * ID of the this editor part extension.
184 public static final String ID = "org.simantics.charts.editor.timeseries";
186 public static final String CONTRIBUTION_URI = "bundleclass://org.simantics.charts/org.simantics.charts.editor.e4.TimeSeriesEditor";
188 private static final String CONTEXT_MENU_ID = "#timeSeriesChart";
190 private IEclipsePreferences chartPreferenceNode;
192 private final ImageDescriptor IMG_ZOOM_TO_FIT = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/horizAndVert16.png");
193 private final ImageDescriptor IMG_ZOOM_TO_FIT_HORIZ = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/horiz16.png");
194 private final ImageDescriptor IMG_ZOOM_TO_FIT_VERT = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/vert16.png");
195 private final ImageDescriptor IMG_AUTOSCALE = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/autoscale16.png");
197 IPreferenceChangeListener preferenceListener = new IPreferenceChangeListener() {
200 public void preferenceChange(PreferenceChangeEvent event) {
202 System.err.println("Warning: pref change to disposed TimeSeriesEditor");
206 if ( event.getKey().equals(ChartPreferences.P_REDRAW_INTERVAL ) ||
207 event.getKey().equals(ChartPreferences.P_AUTOSCALE_INTERVAL )) {
208 long redraw_interval = chartPreferenceNode.getLong(ChartPreferences.P_REDRAW_INTERVAL, ChartPreferences.DEFAULT_REDRAW_INTERVAL);
209 long autoscale_interval = chartPreferenceNode.getLong(ChartPreferences.P_AUTOSCALE_INTERVAL, ChartPreferences.DEFAULT_AUTOSCALE_INTERVAL);
210 setInterval( redraw_interval, autoscale_interval );
212 if ( event.getKey().equals(ChartPreferences.P_DRAW_SAMPLES )) {
213 boolean draw_samples = chartPreferenceNode.getBoolean(ChartPreferences.P_DRAW_SAMPLES, ChartPreferences.DEFAULT_DRAW_SAMPLES);
214 setDrawSamples( draw_samples );
216 if ( event.getKey().equals(ChartPreferences.P_TIMEFORMAT ) ) {
217 String s = chartPreferenceNode.get(ChartPreferences.P_TIMEFORMAT, ChartPreferences.DEFAULT_TIMEFORMAT);
218 TimeFormat tf = TimeFormat.valueOf( s );
219 if (tf!=null) setTimeFormat( tf );
221 if ( event.getKey().equals(ChartPreferences.P_VALUEFORMAT ) ) {
222 String s = chartPreferenceNode.get(ChartPreferences.P_VALUEFORMAT, ChartPreferences.DEFAULT_VALUEFORMAT);
223 ValueFormat vf = ValueFormat.valueOf( s );
224 if (vf!=null) setValueFormat( vf );
226 if ( event.getKey().equals(ChartPreferences.P_ITEMPLACEMENT)) {
227 String s = chartPreferenceNode.get(ChartPreferences.P_ITEMPLACEMENT, ChartPreferences.DEFAULT_ITEMPLACEMENT);
228 ItemPlacement ip = ItemPlacement.valueOf(s);
229 if (trendNode!=null) trendNode.itemPlacement = ip;
231 if ( event.getKey().equals(ChartPreferences.P_TEXTQUALITY) || event.getKey().equals(ChartPreferences.P_LINEQUALITY) ) {
232 String s1 = chartPreferenceNode.get(ChartPreferences.P_TEXTQUALITY, ChartPreferences.DEFAULT_TEXTQUALITY);
233 String s2 = chartPreferenceNode.get(ChartPreferences.P_LINEQUALITY, ChartPreferences.DEFAULT_LINEQUALITY);
234 LineQuality q1 = LineQuality.valueOf(s1);
235 LineQuality q2 = LineQuality.valueOf(s2);
236 if (trendNode!=null) trendNode.quality.textQuality = q1;
237 if (trendNode!=null) trendNode.quality.lineQuality = q2;
244 * The project which this editor is listening to for changes to
245 * {@link ChartKeys.ChartSourceKey keys}.
250 * The text widget shown only if there is no IProject available at the time
251 * of editor part creation.
256 * A unique key for making DB requests chart editor specific without binding
257 * the requests to the editor object itself.
259 UUID uniqueChartEditorId = UUID.randomUUID();
263 CanvasContext cvsCtx;
266 StepListener stepListener;
267 MilestoneSpecListener milestoneListener;
268 MilestoneSpecQuery milestoneQuery;
271 * The ChartData instance used by this editor for sourcing data at any given
272 * moment. Project hint instances are copied into this instance.
274 final ChartData chartData = new ChartData(null, null, null, null, null, null);
277 * The ChartSourceKey to match the model this editor was opened for.
279 * @see #init(IEditorSite, IEditorInput)
281 ChartKeys.ChartSourceKey chartDataKey;
285 * Context management utils
287 protected IThreadWorkQueue swt;
288 protected ContextUtil contextUtil;
290 class ExperimentStateListener implements IExperimentListener {
292 public void stateChanged(ExperimentState state) {
293 TrendSpec spec = trendNode.getTrendSpec();
294 spec.experimentIsRunning = state == ExperimentState.RUNNING;
295 if (spec.experimentIsRunning && spec.viewProfile.trackExperimentTime) {
296 TrendParticipant t = tp;
303 ExperimentStateListener experimentStateListener = new ExperimentStateListener();
305 class ChartDataListener extends HintListenerAdapter implements Runnable {
307 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
308 if (key.equals(chartDataKey)) {
310 if (!cvsCtx.isDisposed() && cvsCtx.isAlive()) {
311 cvsCtx.getThreadAccess().asyncExec(this);
318 if (cvsCtx.isDisposed() || !cvsCtx.isAlive()) return;
319 ChartData data = Simantics.getProject().getHint(chartDataKey);
320 setInput( data, trendNode.getTrendSpec() );
324 ChartDataListener chartDataListener = new ChartDataListener();
326 class ValueTipBoxPositionListener extends HintListenerAdapter {
328 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
329 if (key.equals(TrendParticipant.KEY_VALUE_TIP_BOX_RELATIVE_POS) && newValue != null) {
330 Session s = Simantics.getSession();
331 ChartResource CHART = s.getService(ChartResource.class);
332 Point2D p = (Point2D) newValue;
333 double[] value = { p.getX(), p.getY() };
334 s.asyncRequest(new SetProperty(getInputResource(), CHART.Chart_valueViewPosition, value, Bindings.DOUBLE_ARRAY));
339 ValueTipBoxPositionListener valueTipBoxPositionListener = new ValueTipBoxPositionListener();
342 private void handleLinkDataChange() {
343 final ChartLinkData newData = linkData;
344 trendNode.autoscaletime = newData == null || newData.sender == TimeSeriesEditor.this;
346 if ( newData == null || newData.sender==TimeSeriesEditor.this ) return;
347 TrendNode tn = trendNode;
348 HorizRuler hr = tn!=null ? tn.horizRuler : null;
350 ChartLinkData oldData = new ChartLinkData();
353 if ( hr != null && !ObjectUtils.objectEquals(tn.valueTipTime, newData.valueTipTime)) {
354 tn.valueTipTime = newData.valueTipTime;
358 if ( hr != null && (oldData.from!=newData.from || oldData.sx!=newData.sx)) {
360 cvsCtx.getThreadAccess().asyncExec( new Runnable() {
363 boolean b = trendNode.horizRuler.setFromScale(newData.from, newData.sx);
364 trendNode.horizRuler.autoscroll = false;
374 // State linkTimeState;
375 // IStateListener linkTimeStateListener = new IStateListener() {
377 // public void handleStateChange(State state, Object oldValue) {
383 HorizRuler.TimeWindowListener horizRulerListener = new HorizRuler.TimeWindowListener() {
385 public void onNewWindow(double from, double end, double scalex) {
386 final ChartLinkData oldData = linkData;
387 if (oldData != null) {
388 ChartLinkData data = new ChartLinkData(TimeSeriesEditor.this, from, end, scalex);
389 data.valueTipTime = trendNode.valueTipTime;
391 handleLinkDataChange();
396 class ChassisListener implements IChassisListener {
398 public void chassisClosed(ICanvasChassis sender) {
399 // Prevent deadlock while disposing which using syncExec would result in.
400 final ICanvasContext ctx = cvsCtx;
401 ThreadUtils.asyncExec(ctx.getThreadAccess(), new Runnable() {
405 AWTChassis awt = canvas.getAWTComponent();
407 awt.setCanvasContext(null);
412 canvas.removeChassisListener(ChassisListener.this);
416 ActiveSelectionProvider selectionProvider = new ActiveSelectionProvider();
417 MenuManager menuManager;
420 EContextService contextService;
422 private ChartLinkData linkData;
424 public TimeSeriesEditor() {
425 log = Logger.getLogger( this.getClass().getName() );
429 public void initImpl(MPart part) {
430 EModelService modelService = getContext().get(EModelService.class);
431 MApplication app = getContext().get(MApplication.class);
432 MUIElement element = modelService.cloneSnippet(app, "org.simantics.charts.toolbar.default.snippet", null);
433 if (element != null) {
434 element.setVisible(true);
435 element.setToBeRendered(true);
436 part.setToolbar((MToolBar)element);
439 // MBindingContext dialogAndWindowContext = findMAppElementById(app.getRootContext(), "org.eclipse.ui.contexts.dialogAndWindow");
440 // MBindingContext windowContext = findMAppElementById(dialogAndWindowContext.getChildren(), "org.eclipse.ui.contexts.window");
441 // MBindingContext chartContext = findMAppElementById(windowContext.getChildren(), "org.simantics.charts.editor.context");
442 // if (chartContext == null) {
443 // chartContext = MCommandsFactory.INSTANCE.createBindingContext();
444 // chartContext.setContributorURI("bundle://test.uri");
445 // chartContext.setElementId("org.simantics.charts.editor.context");
446 // chartContext.setName("In Chart Editor");
447 // chartContext.setDescription("Chart Editor Binding Context");
448 // windowContext.getChildren().add(chartContext);
450 // MBindingTable chartBindingTable = findMAppElementById(app.getBindingTables(), "org.simantics.charts.editor.context");
451 // if (chartBindingTable == null) {
452 // chartBindingTable = MCommandsFactory.INSTANCE.createBindingTable();
453 // chartBindingTable.setContributorURI("bundle://test.test.uri");
454 // chartBindingTable.setElementId("org.simantics.charts.editor.context");
455 // chartBindingTable.setBindingContext(chartContext);
457 // for (MCommand command : app.getCommands()) {
458 // if (command.getElementId().equals("org.simantics.chart.canvasCommandDelegateCommand")) {
460 // MParameter param = MCommandsFactory.INSTANCE.createParameter();
461 // param.setElementId("id");
462 // param.setName("org.simantics.charts.commandparameter.canvasCommandDelegateParameter");
463 // param.setValue("zoomToFit");
464 // MKeyBinding keyBinding = MCommandsFactory.INSTANCE.createKeyBinding();
465 // keyBinding.setKeySequence("1");
466 // keyBinding.setCommand(command);
467 // keyBinding.getParameters().add(param);
468 // chartBindingTable.getBindings().add(keyBinding);
474 // app.getBindingTables().add(chartBindingTable);
476 // if (!part.getBindingContexts().contains(chartContext))
477 // part.getBindingContexts().add(chartContext);
480 private <T extends MApplicationElement> T findMAppElementById(List<T> appElements, String elementId) {
481 for (T element : appElements) {
482 if (element.getElementId() != null && element.getElementId().equals(elementId)) {
491 * Invoke this only from the AWT thread.
494 protected void setCanvasContext(final SWTChassis chassis, final ICanvasContext context) {
495 // Cannot directly invoke SWTChassis.setCanvasContext only because it
496 // needs to be invoked in the SWT thread and AWTChassis.setCanvasContext in the
497 // AWT thread, but directly invoking SWTChassis.setCanvasContext would call both
498 // in the SWT thread which would cause synchronous scheduling of AWT
499 // runnables which is always a potential source of deadlocks.
500 chassis.getAWTComponent().setCanvasContext(context);
501 SWTUtils.asyncExec(chassis, new Runnable() {
504 if (!chassis.isDisposed())
505 // For AWT, this is a no-operation.
506 chassis.setCanvasContext(context);
512 public void createPartControl(Composite parent) {
513 this.chartDataKey = ChartKeys.chartSourceKey(assertInputModelResource());
514 display = parent.getDisplay();
515 swt = SWTThread.getThreadAccess(display);
517 // Must have a project to attach to, otherwise the editor is useless.
518 project = Simantics.peekProject();
519 if (project == null) {
520 errorText = new Text(parent, SWT.NONE);
521 errorText.setText("No project is open.");
522 errorText.setEditable(false);
526 // Create the canvas context here before finishing createPartControl
527 // to give anybody requiring access to this editor's ICanvasContext
528 // a chance to do their work.
529 // The context can be created in SWT thread without scheduling
530 // to the context thread and having potential deadlocks.
531 // The context is locked here and unlocked after it has been
532 // initialized in the AWT thread.
533 IThreadWorkQueue thread = AWTThread.getThreadAccess();
534 cvsCtx = new CanvasContext(thread);
535 cvsCtx.setLocked(true);
537 canvas = new SWTChassis(parent, SWT.NONE);
538 canvas.populate(parameter -> {
540 canvas.addChassisListener(new ChassisListener());
541 initializeCanvas(canvas, cvsCtx);
546 // ICommandService service = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);
547 // Command command = service.getCommand( LinkTimeHandler.COMMAND_ID );
548 // linkTimeState = command.getState( LinkTimeHandler.STATE_ID );
549 // if ( linkTimeState != null ) linkTimeState.addListener( linkTimeStateListener );
551 // TODO: Link time E4 way!
554 linkData = new ChartLinkData();
558 // Start tracking editor input validity.
559 activateValidation();
561 // Provide input as selection for property page.
562 // selectionProvider.setSelection( new StructuredSelection(getInputResource()) );
564 getSelectionService().setSelection(new StructuredSelection(getInputResource()));
565 getSelectionService().addSelectionListener(getPart().getElementId(), new ISelectionListener() {
568 public void selectionChanged(MPart part, Object selection) {
569 System.out.println("selection changed!");
573 // getSite().setSelectionProvider( selectionProvider );
576 protected void initializeCanvas(final SWTChassis chassis, CanvasContext cvsCtx) {
577 // Initialize canvas context
578 TrendSpec nodata = new TrendSpec();
580 cvsCtx = TrendInitializer.defaultInitializeCanvas(cvsCtx, null, null, null, nodata);
582 tp = cvsCtx.getAtMostOneItemOfClass(TrendParticipant.class);
585 // IContextService contextService = (IContextService) getSite().getService(IContextService.class);
586 contextUtil = new ContextUtil(contextService, swt);
589 cvsCtx.add( new SubscriptionDropParticipant( getInputResource() ) );
590 cvsCtx.add( new KeyToCommand( ChartKeyBindings.DEFAULT_BINDINGS ) );
591 cvsCtx.add( new ChartPasteHandler2(getInputResource()) );
592 cvsCtx.add(contextUtil);
594 // Context management
595 cvsCtx.add(new SGFocusParticipant(canvas, "org.simantics.charts.editor.context"));
597 stepListener = new StepListener( tp );
598 trendNode = tp.getTrend();
599 trendNode.titleNode.remove();
600 trendNode.titleNode = null;
603 trendNode.horizRuler.listener = horizRulerListener;
605 final ChartLinkData linkTime = linkData;
606 if (linkTime!=null) trendNode.horizRuler.setFromEnd(linkTime.from, linkTime.sx);
608 // Handle mouse moved event after TrendParticipant.
609 // This handler forwards trend.mouseHoverTime to linkTimeState
610 cvsCtx.getEventHandlerStack().add( new IEventHandler() {
613 public int getEventMask() {
614 return EventTypes.MouseMovedMask | EventTypes.MouseClickMask | EventTypes.CommandMask | EventTypes.KeyPressed;
618 public boolean handleEvent(Event e) {
620 // System.out.println("LinkEventHandler: "+e);
621 ChartLinkData oldData = linkData;
623 ChartLinkData newData = new ChartLinkData();
625 if (!newData.equals(oldData)) {
626 // System.out.println("Sending new link-data");
628 handleLinkDataChange();
634 // Canvas context is initialized, unlock it now to allow rendering.
635 cvsCtx.setLocked(false);
637 setCanvasContext(chassis, cvsCtx);
639 cvsCtx.getEventHandlerStack().add(new IEventHandler() {
641 public boolean handleEvent(Event e) {
642 MouseButtonReleasedEvent event = (MouseButtonReleasedEvent) e;
643 if (event.button != MouseEvent.RIGHT_BUTTON)
646 final Point p = new Point((int) event.screenPosition.getX(), (int) event.screenPosition.getY());
647 SWTUtils.asyncExec(chassis, new Runnable() {
650 if (!canvas.isDisposed())
657 public int getEventMask() {
658 return EventTypes.MouseButtonReleasedMask;
662 // Track data source and preinitialize chartData
663 project.addHintListener(chartDataListener);
664 chartData.readFrom( (ChartData) project.getHint( chartDataKey ) );
666 if (chartData.run != null) {
667 milestoneListener = new MilestoneSpecListener();
668 milestoneQuery = new MilestoneSpecQuery( chartData.run );
669 Simantics.getSession().asyncRequest( milestoneQuery, milestoneListener );
672 // IMPORTANT: Only after preinitializing chartData, start tracking chart configuration
673 trackChartConfiguration();
676 // Write changes to TrendSpec.viewProfile.valueViewPosition[XY]
677 // back to the graph database.
678 cvsCtx.getHintStack().addHintListener(valueTipBoxPositionListener);
681 private void addPopupMenu() {
683 // #TODO: FINISH THIS WHEN E4 WORKBENCH
684 // MPopupMenu popupMenu = MMenuFactory.INSTANCE.createPopupMenu();
685 // popupMenu.setLabel("Time Series Editor");
686 // popupMenu.setElementId(CONTEXT_MENU_ID);
688 // for (MCommand command : getPart().getContext().get(MApplication.class).getCommands()) {
689 // if (command.getElementId().equals("org.simantics.chart.canvasCommandDelegateCommand")) {
690 // MParameter param = MCommandsFactory.INSTANCE.createParameter();
691 // param.setElementId("id");
692 // param.setName("org.simantics.charts.commandparameter.canvasCommandDelegateParameter");
693 // param.setValue("zoomToFit");
694 // MHandledMenuItem mmenuItem = MMenuFactory.INSTANCE.createHandledMenuItem();
695 // mmenuItem.setLabel("Zoom to Fit");
696 // mmenuItem.setCommand(command);
697 // mmenuItem.getParameters().add(param);
701 // getPart().getContext().get(EMenuService.class).registerContextMenu(canvas, CONTEXT_MENU_ID);
705 menuManager = new MenuManager("Time Series Editor", CONTEXT_MENU_ID);
706 menuManager.setRemoveAllWhenShown(true);
707 Menu menu = menuManager.createContextMenu(canvas);
708 canvas.setMenu(menu);
709 // TODO: link to modeled menu in E4 way
710 // getEditorSite().registerContextMenu(menuManager.getId(), menuManager, selectionProvider);
712 // Add support for some built-in actions in the context menu.
713 menuManager.addMenuListener(new IMenuListener() {
715 public void menuAboutToShow(IMenuManager manager) {
716 // Not initialized yet, prevent NPE.
717 TrendNode trendNode = TimeSeriesEditor.this.trendNode;
718 TrendParticipant tp = TimeSeriesEditor.this.tp;
719 if (trendNode == null || tp == null)
722 TrendSpec trendSpec = trendNode.getTrendSpec();
723 ItemNode hoverItem = tp.hoveringItem;
724 if (hoverItem != null && hoverItem.item != null) {
725 Resource component = resolveReferencedComponent(hoverItem.item.variableId);
726 if (component != null) {
727 manager.add(new PerformDefaultAction("Show Referenced Component", canvas, component));
730 Resource chart = TimeSeriesEditor.this.getInputResource();
731 if ( chart != null ) {
733 Resource chartItem = Simantics.getSession().sync( new FindChartItemForTrendItem(chart, hoverItem.item) );
734 if (chartItem != null) {
735 manager.add(new HideItemsAction("Hide Item", true, Collections.singletonList(chartItem)));
736 manager.add(new Separator());
737 manager.add(new PropertiesAction("Item Properties", canvas, chartItem));
738 manager.add(new PropertiesAction("Chart Properties", canvas, chart));
740 } catch (DatabaseException e) {
741 Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to resolve context menu items.", e));
745 boolean hairlineMovementAllowed =
746 !(trendSpec.experimentIsRunning &&
747 trendSpec.viewProfile.trackExperimentTime);
749 Resource chart = TimeSeriesEditor.this.getInputResource();
750 manager.add(new Separator());
751 manager.add(new MoveHairlineAction(
752 "Move Hairline Here",
754 hairlineMovementAllowed,
756 trendNode.mouseHoverTime
758 manager.add(new MoveHairlineAction(
759 "Move Hairline To Current Time",
761 hairlineMovementAllowed,
763 trendNode.horizRuler.getItemEndTime(),
766 manager.add(new TrackExperimentTimeAction(
767 "Hairline Tracks Current Time",
769 trendSpec.viewProfile.trackExperimentTime));
770 manager.add(new Separator());
771 manager.add(new SendCommandAction("Zoom to Fit", IMG_ZOOM_TO_FIT, cvsCtx, Commands.ZOOM_TO_FIT));
772 manager.add(new SendCommandAction("Zoom to Fit Horizontally", IMG_ZOOM_TO_FIT_HORIZ, cvsCtx, Commands.ZOOM_TO_FIT_HORIZ));
773 manager.add(new SendCommandAction("Zoom to Fit Vertically", IMG_ZOOM_TO_FIT_VERT, cvsCtx, Commands.ZOOM_TO_FIT_VERT));
774 manager.add(new SendCommandAction("Autoscale Chart", IMG_AUTOSCALE, cvsCtx, Commands.AUTOSCALE));
775 manager.add(new Separator());
776 manager.add(new PropertiesAction("Chart Properties", canvas, chart));
782 protected Resource resolveReferencedComponent(final String variableId) {
784 return Simantics.getSession().sync(new UniqueRead<Resource>() {
786 public Resource perform(ReadGraph graph) throws DatabaseException {
787 Variable configuration = Variables.getConfigurationContext(graph, getInputResource());
788 RVI rvi = RVI.fromResourceFormat(graph, variableId);
789 rvi = new RVIBuilder(rvi).removeFromFirstRole(Role.PROPERTY).toRVI();
792 Variable var = rvi.resolve(graph, configuration);
793 return var.getPossibleRepresents(graph);
796 } catch (DatabaseException e) {
797 ErrorLogger.defaultLogError(e);
802 private void showPopup(Point p) {
803 menuManager.getMenu().setLocation(p);
804 menuManager.getMenu().setVisible(true);
807 private void trackChartConfiguration() {
808 Simantics.getSession().asyncRequest(new TrendSpecQuery( uniqueChartEditorId, getInputResource() ), new TrendSpecListener());
809 Simantics.getSession().asyncRequest(new ActiveRunQuery( uniqueChartEditorId, getInputResource() ), new ActiveRunListener());
813 public void setFocus() {
814 if (errorText != null)
815 errorText.setFocus();
821 public void dispose() {
822 if (isDisposed() == true) return;
824 if (trendNode!=null && trendNode.horizRuler!=null) {
825 trendNode.horizRuler.listener = null;
828 // if ( linkTimeState != null ) linkTimeState.removeListener( linkTimeStateListener );
830 canvas.getHintContext().removeHint( SWTChassis.KEY_EDITORPART );
831 canvas.getHintContext().removeHint( SWTChassis.KEY_WORKBENCHPAGE );
832 canvas.getHintContext().removeHint( SWTChassis.KEY_WORKBENCHWINDOW );
834 if ( chartPreferenceNode!= null ) {
835 chartPreferenceNode.removePreferenceChangeListener( preferenceListener );
838 MilestoneSpecListener ml = milestoneListener;
839 if (ml!=null) ml.dispose();
841 if (project != null) {
842 project.removeHintListener(chartDataListener);
845 if (chartData != null) {
846 if (chartData.datasource!=null)
847 chartData.datasource.removeListener( stepListener );
848 if (chartData.experiment!=null)
849 chartData.experiment.removeListener( experimentStateListener );
850 chartData.readFrom( null );
855 * @param data new data or null
856 * @param newSpec new spec or null
859 @SuppressWarnings("unused")
860 public void setInput(ChartData data, TrendSpec newSpec) {
861 boolean doLayout = false;
863 // Disregard input if it is not for this chart's containing model.
864 if (data != null && data.model != null && !data.model.equals(assertInputModelResource()))
867 // Accommodate Datasource changes
869 Datasource oldDatasource = chartData==null?null:chartData.datasource;
870 Datasource newDatasource = data==null?null:data.datasource;
871 //if ( !ObjectUtils.objectEquals(oldDatasource, newDatasource) )
873 if (oldDatasource!=null) oldDatasource.removeListener( stepListener );
874 if (newDatasource!=null) newDatasource.addListener( stepListener );
879 IExperiment oldExperiment = chartData==null?null:chartData.experiment;
880 IExperiment newExperiment = data==null?null:data.experiment;
881 //if ( !ObjectUtils.objectEquals(oldExperiment, newExperiment) )
883 if (oldExperiment!=null) oldExperiment.removeListener( experimentStateListener );
884 if (newExperiment!=null) newExperiment.addListener( experimentStateListener );
888 // Accommodate Historian changes
890 HistoryManager oldHistorian = trendNode.historian==null?null:trendNode.historian;
891 HistoryManager newHistorian = data==null?null:data.history;
892 Collector newCollector = data==null?null:data.collector;
893 // if ( !ObjectUtils.objectEquals(oldHistorian, newHistorian) )
895 if (newHistorian instanceof FileHistory) {
896 FileHistory fh = (FileHistory) newHistorian;
897 System.out.println("History = "+fh.getWorkarea());
899 trendNode.setHistorian( newHistorian, newCollector );
900 doLayout |= trendNode.autoscale(true, true) | !ObjectUtils.objectEquals(oldHistorian, newHistorian);
903 // Accommodate TrendSpec changes
904 TrendSpec oldSpec = trendNode.getTrendSpec();
905 if ( !newSpec.equals(oldSpec) ) {
906 trendNode.setTrendSpec( newSpec==null?TrendSpec.EMPTY:newSpec );
912 Resource newExperimentResource = data==null ? null : data.run;
913 Resource oldExperimentResource = this.chartData == null ? null : this.chartData.run;
917 if (!ObjectUtils.objectEquals(oldExperimentResource, newExperimentResource)) {
919 // Dispose old listener & Query
920 if (milestoneListener!=null) {
921 milestoneListener.dispose();
922 milestoneListener = null;
924 if (milestoneQuery!=null) {
925 milestoneQuery = null;
928 trendNode.setMilestones( MilestoneSpec.EMPTY );
930 if (newExperimentResource != null) {
931 milestoneListener = new MilestoneSpecListener();
932 milestoneQuery = new MilestoneSpecQuery( newExperimentResource );
933 Simantics.getSession().asyncRequest( milestoneQuery, milestoneListener );
939 if (doLayout) trendNode.layout();
940 this.chartData.readFrom( data );
943 if (!ObjectUtils.objectEquals(oldExperimentResource, newExperimentResource)) {
944 resetViewAfterDataChange();
949 class ActiveRunListener implements SyncListener<Resource> {
951 public void exception(ReadGraph graph, Throwable throwable) {
952 ErrorLogger.defaultLogError(throwable);
953 ShowMessage.showError(throwable.getClass().getSimpleName(), throwable.getMessage());
956 public void execute(ReadGraph graph, final Resource run) throws DatabaseException {
958 SimulationResource SIMU = SimulationResource.getInstance(graph);
959 Variable var = Variables.getVariable(graph, run);
960 IExperiment exp = var.getPossiblePropertyValue(graph, SIMU.Run_iExperiment);
961 ITrendSupport ts = exp.getService(ITrendSupport.class);
962 ts.setChartData(graph);
966 public boolean isDisposed() {
967 return TimeSeriesEditor.this.isDisposed();
971 class TrendSpecListener implements AsyncListener<TrendSpec> {
973 public void exception(AsyncReadGraph graph, Throwable throwable) {
975 ErrorLogger.defaultLogError(throwable);
976 ShowMessage.showError(throwable.getClass().getSimpleName(), throwable.getMessage());
979 public void execute(AsyncReadGraph graph, final TrendSpec result) {
980 if (result == null) {
981 log.log(Level.INFO, "Chart configuration removed");
983 log.log(Level.INFO, "Chart configuration updated: " + result);
986 // Reload chart in AWT Thread
987 AWTThread.getThreadAccess().asyncExec(new Runnable() {
991 setInput( chartData, result );
996 public boolean isDisposed() {
997 return TimeSeriesEditor.this.isDisposed();
1001 class MilestoneSpecListener implements AsyncListener<MilestoneSpec> {
1002 boolean disposed = false;
1004 public void execute(AsyncReadGraph graph, final MilestoneSpec result) {
1005 AWTThread.INSTANCE.asyncExec(new Runnable() {
1007 trendNode.setMilestones(result);
1012 public void exception(AsyncReadGraph graph, Throwable throwable) {
1017 public boolean isDisposed() {
1021 public void dispose() {
1027 private void trackPreferences() {
1028 chartPreferenceNode = InstanceScope.INSTANCE.getNode( "org.simantics.charts" );
1029 chartPreferenceNode.addPreferenceChangeListener( preferenceListener );
1030 long redrawInterval = chartPreferenceNode.getLong(ChartPreferences.P_REDRAW_INTERVAL, ChartPreferences.DEFAULT_REDRAW_INTERVAL);
1031 long autoscaleInterval = chartPreferenceNode.getLong(ChartPreferences.P_AUTOSCALE_INTERVAL, ChartPreferences.DEFAULT_AUTOSCALE_INTERVAL);
1032 setInterval(redrawInterval, autoscaleInterval);
1034 String timeFormat = chartPreferenceNode.get(ChartPreferences.P_TIMEFORMAT, ChartPreferences.DEFAULT_TIMEFORMAT);
1035 TimeFormat tf = TimeFormat.valueOf( timeFormat );
1036 if (tf!=null) setTimeFormat( tf );
1038 Boolean drawSamples = chartPreferenceNode.getBoolean(ChartPreferences.P_DRAW_SAMPLES, ChartPreferences.DEFAULT_DRAW_SAMPLES);
1039 setDrawSamples(drawSamples);
1041 String valueFormat = chartPreferenceNode.get(ChartPreferences.P_VALUEFORMAT, ChartPreferences.DEFAULT_VALUEFORMAT);
1042 ValueFormat vf = ValueFormat.valueOf( valueFormat );
1043 if (vf!=null) setValueFormat( vf );
1045 String s = chartPreferenceNode.get(ChartPreferences.P_ITEMPLACEMENT, ChartPreferences.DEFAULT_ITEMPLACEMENT);
1046 ItemPlacement ip = ItemPlacement.valueOf(s);
1047 if (trendNode!=null) trendNode.itemPlacement = ip;
1049 String s1 = chartPreferenceNode.get(ChartPreferences.P_TEXTQUALITY, ChartPreferences.DEFAULT_TEXTQUALITY);
1050 String s2 = chartPreferenceNode.get(ChartPreferences.P_LINEQUALITY, ChartPreferences.DEFAULT_LINEQUALITY);
1051 LineQuality q1 = LineQuality.valueOf(s1);
1052 LineQuality q2 = LineQuality.valueOf(s2);
1053 if (trendNode!=null) trendNode.quality.textQuality = q1;
1054 if (trendNode!=null) trendNode.quality.lineQuality = q2;
1058 private void setInterval(long redrawInterval, long autoscaleInterval) {
1059 redrawInterval = Math.max(1, redrawInterval);
1060 long pulse = Math.min(50, redrawInterval);
1061 pulse = Math.min(pulse, autoscaleInterval);
1062 IHintContext h = canvas.getCanvasContext().getDefaultHintContext();
1063 h.setHint(TimeParticipant.KEY_TIME_PULSE_INTERVAL, pulse);
1064 h.setHint(TrendParticipant.KEY_TREND_DRAW_INTERVAL, redrawInterval);
1065 h.setHint(TrendParticipant.KEY_TREND_AUTOSCALE_INTERVAL, autoscaleInterval);
1068 private void setDrawSamples(boolean value) {
1069 trendNode.drawSamples = value;
1074 private void setTimeFormat( TimeFormat tf ) {
1075 if (trendNode.timeFormat == tf) return;
1076 trendNode.timeFormat = tf;
1081 private void setValueFormat( ValueFormat vf ) {
1082 if (trendNode.valueFormat == vf) return;
1083 trendNode.valueFormat = vf;
1088 @SuppressWarnings("unchecked")
1090 public <T> T getAdapter(Class<T> adapter) {
1091 if (adapter == INode.class) {
1092 ICanvasContext ctx = cvsCtx;
1094 return (T) ctx.getSceneGraph();
1096 if (adapter == IPropertyPage.class)
1097 // TODO: pure e4 property page without sites
1098 return (T) new StandardPropertyPage(null, getPropertyPageContexts());
1099 if (adapter == ICanvasContext.class)
1104 protected Set<String> getPropertyPageContexts() {
1106 return BrowseContext.getBrowseContextClosure(Simantics.getSession(), Collections.singleton(ROOT_PROPERTY_BROWSE_CONTEXT));
1107 } catch (DatabaseException e) {
1108 ExceptionUtils.logAndShowError("Failed to load modeled browse contexts for property page, see exception for details.", e);
1109 return Collections.singleton(ROOT_PROPERTY_BROWSE_CONTEXT);
1114 * Add from, end, (scale x) to argument array
1115 * @param fromEnd array of 2 or 3
1117 public void getFromEnd(ChartLinkData data) {
1119 TrendNode tn = trendNode;
1120 data.valueTipTime = tn.valueTipTime;
1121 HorizRuler hr = tn!=null ? tn.horizRuler : null;
1123 data.from = hr.from;
1125 double len = hr.end-hr.from;
1126 double wid = tn.plot.getWidth();
1127 if ( wid==0.0 ) wid = 0.1;
1132 @SuppressWarnings("unused")
1133 private static boolean doubleEquals(double a, double b) {
1134 if (Double.isNaN(a) && Double.isNaN(b)) return true;
1138 protected void resetViewAfterDataChange() {
1140 CanvasUtils.sendCommand(cvsCtx, Commands.CANCEL);
1141 CanvasUtils.sendCommand(cvsCtx, Commands.AUTOSCALE);
1145 // private Resource getInputResource() {