-/*******************************************************************************\r
- * Copyright (c) 2007, 2011 Association for Decentralized Information Management in\r
- * Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- * VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.charts.editor.e4;\r
-\r
-import java.awt.geom.Point2D;\r
-import java.util.Collections;\r
-import java.util.List;\r
-import java.util.Set;\r
-import java.util.UUID;\r
-import java.util.logging.Level;\r
-import java.util.logging.Logger;\r
-\r
-import javax.inject.Inject;\r
-\r
-import org.eclipse.core.runtime.IAdaptable;\r
-import org.eclipse.core.runtime.IStatus;\r
-import org.eclipse.core.runtime.Status;\r
-import org.eclipse.core.runtime.preferences.IEclipsePreferences;\r
-import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;\r
-import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;\r
-import org.eclipse.core.runtime.preferences.InstanceScope;\r
-import org.eclipse.e4.ui.model.application.MApplication;\r
-import org.eclipse.e4.ui.model.application.MApplicationElement;\r
-import org.eclipse.e4.ui.model.application.ui.MUIElement;\r
-import org.eclipse.e4.ui.model.application.ui.basic.MPart;\r
-import org.eclipse.e4.ui.model.application.ui.menu.MToolBar;\r
-import org.eclipse.e4.ui.services.EContextService;\r
-import org.eclipse.e4.ui.workbench.modeling.EModelService;\r
-import org.eclipse.e4.ui.workbench.modeling.ISelectionListener;\r
-import org.eclipse.jface.action.IMenuListener;\r
-import org.eclipse.jface.action.IMenuManager;\r
-import org.eclipse.jface.action.MenuManager;\r
-import org.eclipse.jface.action.Separator;\r
-import org.eclipse.jface.resource.ImageDescriptor;\r
-import org.eclipse.jface.viewers.StructuredSelection;\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.graphics.Point;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Display;\r
-import org.eclipse.swt.widgets.Menu;\r
-import org.eclipse.swt.widgets.Text;\r
-import org.eclipse.ui.IEditorInput;\r
-import org.eclipse.ui.IEditorSite;\r
-import org.simantics.Simantics;\r
-import org.simantics.browsing.ui.model.browsecontexts.BrowseContext;\r
-import org.simantics.charts.Activator;\r
-import org.simantics.charts.ITrendSupport;\r
-import org.simantics.charts.editor.ActiveRunQuery;\r
-import org.simantics.charts.editor.ChartData;\r
-import org.simantics.charts.editor.ChartKeyBindings;\r
-import org.simantics.charts.editor.ChartKeys;\r
-import org.simantics.charts.editor.ChartPasteHandler2;\r
-import org.simantics.charts.editor.HideItemsAction;\r
-import org.simantics.charts.editor.MoveHairlineAction;\r
-import org.simantics.charts.editor.PropertiesAction;\r
-import org.simantics.charts.editor.SendCommandAction;\r
-import org.simantics.charts.editor.SubscriptionDropParticipant;\r
-import org.simantics.charts.editor.TrackExperimentTimeAction;\r
-import org.simantics.charts.ontology.ChartResource;\r
-import org.simantics.charts.preference.ChartPreferences;\r
-import org.simantics.charts.query.FindChartItemForTrendItem;\r
-import org.simantics.charts.query.MilestoneSpecQuery;\r
-import org.simantics.charts.query.SetProperty;\r
-import org.simantics.charts.query.TrendSpecQuery;\r
-import org.simantics.charts.ui.ChartLinkData;\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.databoard.util.ObjectUtils;\r
-import org.simantics.db.AsyncReadGraph;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.Session;\r
-import org.simantics.db.common.request.ParametrizedRead;\r
-import org.simantics.db.common.request.UniqueRead;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.layer0.request.combinations.Combinators;\r
-import org.simantics.db.layer0.variable.RVI;\r
-import org.simantics.db.layer0.variable.RVIBuilder;\r
-import org.simantics.db.layer0.variable.Variable;\r
-import org.simantics.db.layer0.variable.Variables;\r
-import org.simantics.db.layer0.variable.Variables.Role;\r
-import org.simantics.db.procedure.AsyncListener;\r
-import org.simantics.db.procedure.SyncListener;\r
-import org.simantics.diagram.participant.SGFocusParticipant;\r
-import org.simantics.diagram.participant.e4.ContextUtil;\r
-import org.simantics.g2d.canvas.ICanvasContext;\r
-import org.simantics.g2d.canvas.impl.CanvasContext;\r
-import org.simantics.g2d.chassis.AWTChassis;\r
-import org.simantics.g2d.chassis.ICanvasChassis;\r
-import org.simantics.g2d.chassis.IChassisListener;\r
-import org.simantics.g2d.chassis.SWTChassis;\r
-import org.simantics.g2d.participant.KeyToCommand;\r
-import org.simantics.g2d.participant.TimeParticipant;\r
-import org.simantics.g2d.utils.CanvasUtils;\r
-import org.simantics.history.Collector;\r
-import org.simantics.history.HistoryManager;\r
-import org.simantics.history.impl.FileHistory;\r
-import org.simantics.project.IProject;\r
-import org.simantics.scenegraph.INode;\r
-import org.simantics.scenegraph.g2d.events.Event;\r
-import org.simantics.scenegraph.g2d.events.EventTypes;\r
-import org.simantics.scenegraph.g2d.events.IEventHandler;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
-import org.simantics.scenegraph.g2d.events.command.Commands;\r
-import org.simantics.selectionview.StandardPropertyPage;\r
-import org.simantics.simulation.data.Datasource;\r
-import org.simantics.simulation.experiment.ExperimentState;\r
-import org.simantics.simulation.experiment.IExperiment;\r
-import org.simantics.simulation.experiment.IExperimentListener;\r
-import org.simantics.simulation.ontology.SimulationResource;\r
-import org.simantics.trend.TrendInitializer;\r
-import org.simantics.trend.TrendInitializer.StepListener;\r
-import org.simantics.trend.configuration.ItemPlacement;\r
-import org.simantics.trend.configuration.LineQuality;\r
-import org.simantics.trend.configuration.TimeFormat;\r
-import org.simantics.trend.configuration.TrendSpec;\r
-import org.simantics.trend.impl.HorizRuler;\r
-import org.simantics.trend.impl.ItemNode;\r
-import org.simantics.trend.impl.MilestoneSpec;\r
-import org.simantics.trend.impl.TrendNode;\r
-import org.simantics.trend.impl.TrendParticipant;\r
-import org.simantics.ui.workbench.IPropertyPage;\r
-import org.simantics.ui.workbench.IResourceEditorInput;\r
-import org.simantics.ui.workbench.ResourceEditorInput;\r
-import org.simantics.ui.workbench.action.PerformDefaultAction;\r
-import org.simantics.ui.workbench.e4.E4ResourceEditorBase;\r
-import org.simantics.ui.workbench.editor.input.InputValidationCombinators;\r
-import org.simantics.utils.datastructures.hints.HintListenerAdapter;\r
-import org.simantics.utils.datastructures.hints.IHintContext;\r
-import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
-import org.simantics.utils.datastructures.hints.IHintObservable;\r
-import org.simantics.utils.format.ValueFormat;\r
-import org.simantics.utils.threads.AWTThread;\r
-import org.simantics.utils.threads.IThreadWorkQueue;\r
-import org.simantics.utils.threads.SWTThread;\r
-import org.simantics.utils.threads.ThreadUtils;\r
-import org.simantics.utils.ui.BundleUtils;\r
-import org.simantics.utils.ui.ErrorLogger;\r
-import org.simantics.utils.ui.ExceptionUtils;\r
-import org.simantics.utils.ui.SWTUtils;\r
-import org.simantics.utils.ui.dialogs.ShowMessage;\r
-import org.simantics.utils.ui.jface.ActiveSelectionProvider;\r
-\r
-/**\r
- * TimeSeriesEditor is an interactive part that draws a time series chart.\r
- * \r
- * The configuration model is {@link TrendSpec} which is read through\r
- * {@link TrendSpecQuery}. In Simantics Environment the\r
- * editor input is {@link ResourceEditorInput}.\r
- * \r
- * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
- * @author Tuukka Lehtonen\r
- */\r
-public class TimeSeriesEditor extends E4ResourceEditorBase implements IAdaptable {\r
-\r
- ParametrizedRead<IResourceEditorInput, Boolean> INPUT_VALIDATOR =\r
- Combinators.compose(\r
- InputValidationCombinators.hasURI(),\r
- InputValidationCombinators.extractInputResource()\r
- );\r
-\r
- protected ParametrizedRead<IResourceEditorInput, Boolean> getInputValidator() {\r
- return INPUT_VALIDATOR;\r
- }\r
-\r
- /**\r
- * The root property browse context of the time series editor. A transitive\r
- * closure is calculated for this context.\r
- */\r
- private static String ROOT_PROPERTY_BROWSE_CONTEXT = ChartResource.URIs.ChartBrowseContext;\r
-\r
- /**\r
- * ID of the this editor part extension.\r
- */\r
- public static final String ID = "org.simantics.charts.editor.timeseries";\r
-\r
- public static final String CONTRIBUTION_URI = "bundleclass://org.simantics.charts/org.simantics.charts.editor.e4.TimeSeriesEditor";\r
-\r
- private static final String CONTEXT_MENU_ID = "#timeSeriesChart";\r
-\r
- private IEclipsePreferences chartPreferenceNode;\r
-\r
- private final ImageDescriptor IMG_ZOOM_TO_FIT = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/horizAndVert16.png");\r
- private final ImageDescriptor IMG_ZOOM_TO_FIT_HORIZ = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/horiz16.png");\r
- private final ImageDescriptor IMG_ZOOM_TO_FIT_VERT = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/vert16.png");\r
- private final ImageDescriptor IMG_AUTOSCALE = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/autoscale16.png");\r
-\r
- IPreferenceChangeListener preferenceListener = new IPreferenceChangeListener() {\r
-\r
- @Override\r
- public void preferenceChange(PreferenceChangeEvent event) {\r
- if (isDisposed()) {\r
- System.err.println("Warning: pref change to disposed TimeSeriesEditor");\r
- return;\r
- }\r
-\r
- if ( event.getKey().equals(ChartPreferences.P_REDRAW_INTERVAL ) || \r
- event.getKey().equals(ChartPreferences.P_AUTOSCALE_INTERVAL )) {\r
- long redraw_interval = chartPreferenceNode.getLong(ChartPreferences.P_REDRAW_INTERVAL, ChartPreferences.DEFAULT_REDRAW_INTERVAL);\r
- long autoscale_interval = chartPreferenceNode.getLong(ChartPreferences.P_AUTOSCALE_INTERVAL, ChartPreferences.DEFAULT_AUTOSCALE_INTERVAL);\r
- setInterval( redraw_interval, autoscale_interval );\r
- }\r
- if ( event.getKey().equals(ChartPreferences.P_DRAW_SAMPLES )) {\r
- boolean draw_samples = chartPreferenceNode.getBoolean(ChartPreferences.P_DRAW_SAMPLES, ChartPreferences.DEFAULT_DRAW_SAMPLES);\r
- setDrawSamples( draw_samples );\r
- }\r
- if ( event.getKey().equals(ChartPreferences.P_TIMEFORMAT ) ) {\r
- String s = chartPreferenceNode.get(ChartPreferences.P_TIMEFORMAT, ChartPreferences.DEFAULT_TIMEFORMAT);\r
- TimeFormat tf = TimeFormat.valueOf( s );\r
- if (tf!=null) setTimeFormat( tf );\r
- }\r
- if ( event.getKey().equals(ChartPreferences.P_VALUEFORMAT ) ) {\r
- String s = chartPreferenceNode.get(ChartPreferences.P_VALUEFORMAT, ChartPreferences.DEFAULT_VALUEFORMAT);\r
- ValueFormat vf = ValueFormat.valueOf( s );\r
- if (vf!=null) setValueFormat( vf );\r
- }\r
- if ( event.getKey().equals(ChartPreferences.P_ITEMPLACEMENT)) {\r
- String s = chartPreferenceNode.get(ChartPreferences.P_ITEMPLACEMENT, ChartPreferences.DEFAULT_ITEMPLACEMENT);\r
- ItemPlacement ip = ItemPlacement.valueOf(s);\r
- if (trendNode!=null) trendNode.itemPlacement = ip;\r
- }\r
- if ( event.getKey().equals(ChartPreferences.P_TEXTQUALITY) || event.getKey().equals(ChartPreferences.P_LINEQUALITY) ) {\r
- String s1 = chartPreferenceNode.get(ChartPreferences.P_TEXTQUALITY, ChartPreferences.DEFAULT_TEXTQUALITY);\r
- String s2 = chartPreferenceNode.get(ChartPreferences.P_LINEQUALITY, ChartPreferences.DEFAULT_LINEQUALITY);\r
- LineQuality q1 = LineQuality.valueOf(s1);\r
- LineQuality q2 = LineQuality.valueOf(s2);\r
- if (trendNode!=null) trendNode.quality.textQuality = q1;\r
- if (trendNode!=null) trendNode.quality.lineQuality = q2;\r
- }\r
- \r
- }\r
- };\r
-\r
- /**\r
- * The project which this editor is listening to for changes to\r
- * {@link ChartKeys.ChartSourceKey keys}.\r
- */\r
- IProject project;\r
-\r
- /**\r
- * The text widget shown only if there is no IProject available at the time\r
- * of editor part creation.\r
- */\r
- Text errorText;\r
-\r
- /**\r
- * A unique key for making DB requests chart editor specific without binding\r
- * the requests to the editor object itself.\r
- */\r
- UUID uniqueChartEditorId = UUID.randomUUID();\r
- Logger log;\r
- Display display;\r
- SWTChassis canvas;\r
- CanvasContext cvsCtx;\r
- TrendParticipant tp;\r
- TrendNode trendNode;\r
- StepListener stepListener;\r
- MilestoneSpecListener milestoneListener;\r
- MilestoneSpecQuery milestoneQuery;\r
-\r
- /**\r
- * The ChartData instance used by this editor for sourcing data at any given\r
- * moment. Project hint instances are copied into this instance.\r
- */\r
- final ChartData chartData = new ChartData(null, null, null, null, null, null);\r
-\r
- /**\r
- * The ChartSourceKey to match the model this editor was opened for.\r
- * @see #model\r
- * @see #init(IEditorSite, IEditorInput)\r
- */\r
- ChartKeys.ChartSourceKey chartDataKey;\r
- \r
- \r
- /**\r
- * Context management utils\r
- */\r
- protected IThreadWorkQueue swt;\r
- protected ContextUtil contextUtil;\r
-\r
- class ExperimentStateListener implements IExperimentListener {\r
- @Override\r
- public void stateChanged(ExperimentState state) {\r
- TrendSpec spec = trendNode.getTrendSpec();\r
- spec.experimentIsRunning = state == ExperimentState.RUNNING;\r
- if (spec.experimentIsRunning && spec.viewProfile.trackExperimentTime) {\r
- TrendParticipant t = tp;\r
- if (t != null)\r
- t.setDirty();\r
- }\r
- }\r
- }\r
-\r
- ExperimentStateListener experimentStateListener = new ExperimentStateListener();\r
-\r
- class ChartDataListener extends HintListenerAdapter implements Runnable {\r
- @Override\r
- public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
- if (key.equals(chartDataKey)) {\r
- // @Thread any\r
- if (!cvsCtx.isDisposed() && cvsCtx.isAlive()) {\r
- cvsCtx.getThreadAccess().asyncExec(this);\r
- }\r
- }\r
- }\r
- @Override\r
- public void run() {\r
- // @Thread AWT\r
- if (cvsCtx.isDisposed() || !cvsCtx.isAlive()) return;\r
- ChartData data = Simantics.getProject().getHint(chartDataKey);\r
- setInput( data, trendNode.getTrendSpec() );\r
- }\r
- }\r
-\r
- ChartDataListener chartDataListener = new ChartDataListener();\r
-\r
- class ValueTipBoxPositionListener extends HintListenerAdapter {\r
- @Override\r
- public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
- if (key.equals(TrendParticipant.KEY_VALUE_TIP_BOX_RELATIVE_POS) && newValue != null) {\r
- Session s = Simantics.getSession();\r
- ChartResource CHART = s.getService(ChartResource.class);\r
- Point2D p = (Point2D) newValue;\r
- double[] value = { p.getX(), p.getY() };\r
- s.asyncRequest(new SetProperty(getInputResource(), CHART.Chart_valueViewPosition, value, Bindings.DOUBLE_ARRAY));\r
- }\r
- }\r
- }\r
-\r
- ValueTipBoxPositionListener valueTipBoxPositionListener = new ValueTipBoxPositionListener();\r
-\r
- \r
- private void handleLinkDataChange() {\r
- final ChartLinkData newData = linkData;\r
- trendNode.autoscaletime = newData == null || newData.sender == TimeSeriesEditor.this;\r
- \r
- if ( newData == null || newData.sender==TimeSeriesEditor.this ) return;\r
- TrendNode tn = trendNode;\r
- HorizRuler hr = tn!=null ? tn.horizRuler : null;\r
-\r
- ChartLinkData oldData = new ChartLinkData();\r
- getFromEnd(oldData);\r
-\r
- if ( hr != null && !ObjectUtils.objectEquals(tn.valueTipTime, newData.valueTipTime)) {\r
- tn.valueTipTime = newData.valueTipTime;\r
- tp.setDirty();\r
- }\r
- \r
- if ( hr != null && (oldData.from!=newData.from || oldData.sx!=newData.sx)) {\r
- \r
- cvsCtx.getThreadAccess().asyncExec( new Runnable() {\r
- @Override\r
- public void run() {\r
- boolean b = trendNode.horizRuler.setFromScale(newData.from, newData.sx);\r
- trendNode.horizRuler.autoscroll = false;\r
- if (b) {\r
- trendNode.layout();\r
- tp.setDirty();\r
- }\r
- }});\r
- }\r
- }\r
- \r
- // Link-Time\r
-// State linkTimeState;\r
-// IStateListener linkTimeStateListener = new IStateListener() {\r
-// @Override\r
-// public void handleStateChange(State state, Object oldValue) {\r
-// \r
-// \r
-// }\r
-// };\r
- \r
- HorizRuler.TimeWindowListener horizRulerListener = new HorizRuler.TimeWindowListener() {\r
- @Override\r
- public void onNewWindow(double from, double end, double scalex) {\r
- final ChartLinkData oldData = linkData;\r
- if (oldData != null) {\r
- ChartLinkData data = new ChartLinkData(TimeSeriesEditor.this, from, end, scalex);\r
- data.valueTipTime = trendNode.valueTipTime;\r
- linkData = data;\r
- handleLinkDataChange();\r
- }\r
- }\r
- };\r
-\r
- class ChassisListener implements IChassisListener {\r
- @Override\r
- public void chassisClosed(ICanvasChassis sender) {\r
- // Prevent deadlock while disposing which using syncExec would result in. \r
- final ICanvasContext ctx = cvsCtx;\r
- ThreadUtils.asyncExec(ctx.getThreadAccess(), new Runnable() {\r
- @Override\r
- public void run() {\r
- if (ctx != null) {\r
- AWTChassis awt = canvas.getAWTComponent();\r
- if (awt != null)\r
- awt.setCanvasContext(null);\r
- ctx.dispose();\r
- }\r
- }\r
- });\r
- canvas.removeChassisListener(ChassisListener.this);\r
- }\r
- }\r
-\r
- ActiveSelectionProvider selectionProvider = new ActiveSelectionProvider();\r
- MenuManager menuManager;\r
-\r
- @Inject\r
- EContextService contextService;\r
-\r
- private ChartLinkData linkData;\r
-\r
- public TimeSeriesEditor() {\r
- log = Logger.getLogger( this.getClass().getName() );\r
- }\r
-\r
- @Override\r
- public void initImpl(MPart part) {\r
- EModelService modelService = getContext().get(EModelService.class);\r
- MApplication app = getContext().get(MApplication.class);\r
- MUIElement element = modelService.cloneSnippet(app, "org.simantics.charts.toolbar.default.snippet", null);\r
- if (element != null) {\r
- element.setVisible(true);\r
- element.setToBeRendered(true);\r
- part.setToolbar((MToolBar)element);\r
- }\r
- \r
-// MBindingContext dialogAndWindowContext = findMAppElementById(app.getRootContext(), "org.eclipse.ui.contexts.dialogAndWindow");\r
-// MBindingContext windowContext = findMAppElementById(dialogAndWindowContext.getChildren(), "org.eclipse.ui.contexts.window");\r
-// MBindingContext chartContext = findMAppElementById(windowContext.getChildren(), "org.simantics.charts.editor.context");\r
-// if (chartContext == null) {\r
-// chartContext = MCommandsFactory.INSTANCE.createBindingContext();\r
-// chartContext.setContributorURI("bundle://test.uri");\r
-// chartContext.setElementId("org.simantics.charts.editor.context");\r
-// chartContext.setName("In Chart Editor");\r
-// chartContext.setDescription("Chart Editor Binding Context");\r
-// windowContext.getChildren().add(chartContext);\r
-// }\r
-// MBindingTable chartBindingTable = findMAppElementById(app.getBindingTables(), "org.simantics.charts.editor.context");\r
-// if (chartBindingTable == null) {\r
-// chartBindingTable = MCommandsFactory.INSTANCE.createBindingTable();\r
-// chartBindingTable.setContributorURI("bundle://test.test.uri");\r
-// chartBindingTable.setElementId("org.simantics.charts.editor.context");\r
-// chartBindingTable.setBindingContext(chartContext);\r
-// \r
-// for (MCommand command : app.getCommands()) {\r
-// if (command.getElementId().equals("org.simantics.chart.canvasCommandDelegateCommand")) {\r
-// \r
-// MParameter param = MCommandsFactory.INSTANCE.createParameter();\r
-// param.setElementId("id");\r
-// param.setName("org.simantics.charts.commandparameter.canvasCommandDelegateParameter");\r
-// param.setValue("zoomToFit");\r
-// MKeyBinding keyBinding = MCommandsFactory.INSTANCE.createKeyBinding();\r
-// keyBinding.setKeySequence("1");\r
-// keyBinding.setCommand(command);\r
-// keyBinding.getParameters().add(param);\r
-// chartBindingTable.getBindings().add(keyBinding);\r
-// \r
-// break;\r
-// }\r
-// }\r
-// \r
-// app.getBindingTables().add(chartBindingTable);\r
-// }\r
-// if (!part.getBindingContexts().contains(chartContext))\r
-// part.getBindingContexts().add(chartContext);\r
- }\r
-\r
- private <T extends MApplicationElement> T findMAppElementById(List<T> appElements, String elementId) {\r
- for (T element : appElements) {\r
- if (element.getElementId() != null && element.getElementId().equals(elementId)) {\r
- return element;\r
- }\r
- }\r
- return null;\r
- }\r
-\r
-\r
- /**\r
- * Invoke this only from the AWT thread.\r
- * @param context\r
- */\r
- protected void setCanvasContext(final SWTChassis chassis, final ICanvasContext context) {\r
- // Cannot directly invoke SWTChassis.setCanvasContext only because it\r
- // needs to be invoked in the SWT thread and AWTChassis.setCanvasContext in the\r
- // AWT thread, but directly invoking SWTChassis.setCanvasContext would call both\r
- // in the SWT thread which would cause synchronous scheduling of AWT\r
- // runnables which is always a potential source of deadlocks.\r
- chassis.getAWTComponent().setCanvasContext(context);\r
- SWTUtils.asyncExec(chassis, new Runnable() {\r
- @Override\r
- public void run() {\r
- if (!chassis.isDisposed())\r
- // For AWT, this is a no-operation.\r
- chassis.setCanvasContext(context);\r
- }\r
- });\r
- }\r
-\r
- @Override\r
- public void createPartControl(Composite parent) {\r
- this.chartDataKey = ChartKeys.chartSourceKey(assertInputModelResource());\r
- display = parent.getDisplay();\r
- swt = SWTThread.getThreadAccess(display);\r
-\r
- // Must have a project to attach to, otherwise the editor is useless.\r
- project = Simantics.peekProject();\r
- if (project == null) {\r
- errorText = new Text(parent, SWT.NONE);\r
- errorText.setText("No project is open.");\r
- errorText.setEditable(false);\r
- return;\r
- }\r
-\r
- // Create the canvas context here before finishing createPartControl\r
- // to give anybody requiring access to this editor's ICanvasContext\r
- // a chance to do their work.\r
- // The context can be created in SWT thread without scheduling\r
- // to the context thread and having potential deadlocks.\r
- // The context is locked here and unlocked after it has been\r
- // initialized in the AWT thread.\r
- IThreadWorkQueue thread = AWTThread.getThreadAccess();\r
- cvsCtx = new CanvasContext(thread);\r
- cvsCtx.setLocked(true);\r
-\r
- canvas = new SWTChassis(parent, SWT.NONE);\r
- canvas.populate(parameter -> {\r
- if (!isDisposed()) {\r
- canvas.addChassisListener(new ChassisListener());\r
- initializeCanvas(canvas, cvsCtx);\r
- }\r
- });\r
-\r
- // Link time\r
-// ICommandService service = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);\r
-// Command command = service.getCommand( LinkTimeHandler.COMMAND_ID );\r
-// linkTimeState = command.getState( LinkTimeHandler.STATE_ID );\r
-// if ( linkTimeState != null ) linkTimeState.addListener( linkTimeStateListener );\r
- \r
- // TODO: Link time E4 way!\r
-\r
- \r
- linkData = new ChartLinkData();\r
- \r
- addPopupMenu();\r
- \r
- // Start tracking editor input validity. \r
- activateValidation();\r
-\r
- // Provide input as selection for property page.\r
-// selectionProvider.setSelection( new StructuredSelection(getInputResource()) );\r
- \r
- getSelectionService().setSelection(new StructuredSelection(getInputResource()));\r
- getSelectionService().addSelectionListener(getPart().getElementId(), new ISelectionListener() {\r
- \r
- @Override\r
- public void selectionChanged(MPart part, Object selection) {\r
- System.out.println("selection changed!");\r
- }\r
- });\r
- \r
-// getSite().setSelectionProvider( selectionProvider );\r
- }\r
-\r
- protected void initializeCanvas(final SWTChassis chassis, CanvasContext cvsCtx) {\r
- // Initialize canvas context\r
- TrendSpec nodata = new TrendSpec();\r
- nodata.init();\r
- cvsCtx = TrendInitializer.defaultInitializeCanvas(cvsCtx, null, null, null, nodata);\r
-\r
- tp = cvsCtx.getAtMostOneItemOfClass(TrendParticipant.class);\r
-\r
- \r
-// IContextService contextService = (IContextService) getSite().getService(IContextService.class);\r
- contextUtil = new ContextUtil(contextService, swt);\r
- \r
- \r
- cvsCtx.add( new SubscriptionDropParticipant( getInputResource() ) );\r
- cvsCtx.add( new KeyToCommand( ChartKeyBindings.DEFAULT_BINDINGS ) );\r
- cvsCtx.add( new ChartPasteHandler2(getInputResource()) );\r
- cvsCtx.add(contextUtil);\r
- \r
- // Context management\r
- cvsCtx.add(new SGFocusParticipant(canvas, "org.simantics.charts.editor.context"));\r
-\r
- stepListener = new StepListener( tp );\r
- trendNode = tp.getTrend();\r
- trendNode.titleNode.remove();\r
- trendNode.titleNode = null;\r
-\r
- // Link time\r
- trendNode.horizRuler.listener = horizRulerListener;\r
- \r
- final ChartLinkData linkTime = linkData;\r
- if (linkTime!=null) trendNode.horizRuler.setFromEnd(linkTime.from, linkTime.sx);\r
- \r
- // Handle mouse moved event after TrendParticipant.\r
- // This handler forwards trend.mouseHoverTime to linkTimeState\r
- cvsCtx.getEventHandlerStack().add( new IEventHandler() {\r
-\r
- @Override\r
- public int getEventMask() {\r
- return EventTypes.MouseMovedMask | EventTypes.MouseClickMask | EventTypes.CommandMask | EventTypes.KeyPressed;\r
- }\r
-\r
- @Override\r
- public boolean handleEvent(Event e) {\r
-\r
-// System.out.println("LinkEventHandler: "+e);\r
- ChartLinkData oldData = linkData;\r
- if (oldData!=null) {\r
- ChartLinkData newData = new ChartLinkData();\r
- getFromEnd(newData);\r
- if (!newData.equals(oldData)) {\r
-// System.out.println("Sending new link-data");\r
- linkData = newData;\r
- handleLinkDataChange();\r
- }\r
- }\r
- return false;\r
- }}, -1);\r
-\r
- // Canvas context is initialized, unlock it now to allow rendering.\r
- cvsCtx.setLocked(false);\r
-\r
- setCanvasContext(chassis, cvsCtx);\r
-\r
- cvsCtx.getEventHandlerStack().add(new IEventHandler() {\r
- @Override\r
- public boolean handleEvent(Event e) {\r
- MouseButtonReleasedEvent event = (MouseButtonReleasedEvent) e;\r
- if (event.button != MouseEvent.RIGHT_BUTTON)\r
- return false;\r
-\r
- final Point p = new Point((int) event.screenPosition.getX(), (int) event.screenPosition.getY());\r
- SWTUtils.asyncExec(chassis, new Runnable() {\r
- @Override\r
- public void run() {\r
- if (!canvas.isDisposed())\r
- showPopup(p);\r
- }\r
- });\r
- return true;\r
- }\r
- @Override\r
- public int getEventMask() {\r
- return EventTypes.MouseButtonReleasedMask;\r
- }\r
- }, 1000000);\r
-\r
- // Track data source and preinitialize chartData\r
- project.addHintListener(chartDataListener);\r
- chartData.readFrom( (ChartData) project.getHint( chartDataKey ) );\r
-\r
- if (chartData.run != null) {\r
- milestoneListener = new MilestoneSpecListener();\r
- milestoneQuery = new MilestoneSpecQuery( chartData.run );\r
- Simantics.getSession().asyncRequest( milestoneQuery, milestoneListener );\r
- }\r
-\r
- // IMPORTANT: Only after preinitializing chartData, start tracking chart configuration\r
- trackChartConfiguration();\r
- trackPreferences();\r
-\r
- // Write changes to TrendSpec.viewProfile.valueViewPosition[XY]\r
- // back to the graph database.\r
- cvsCtx.getHintStack().addHintListener(valueTipBoxPositionListener);\r
- }\r
-\r
- private void addPopupMenu() {\r
-\r
-// #TODO: FINISH THIS WHEN E4 WORKBENCH\r
-// MPopupMenu popupMenu = MMenuFactory.INSTANCE.createPopupMenu();\r
-// popupMenu.setLabel("Time Series Editor");\r
-// popupMenu.setElementId(CONTEXT_MENU_ID);\r
-// \r
-// for (MCommand command : getPart().getContext().get(MApplication.class).getCommands()) {\r
-// if (command.getElementId().equals("org.simantics.chart.canvasCommandDelegateCommand")) {\r
-// MParameter param = MCommandsFactory.INSTANCE.createParameter();\r
-// param.setElementId("id");\r
-// param.setName("org.simantics.charts.commandparameter.canvasCommandDelegateParameter");\r
-// param.setValue("zoomToFit");\r
-// MHandledMenuItem mmenuItem = MMenuFactory.INSTANCE.createHandledMenuItem();\r
-// mmenuItem.setLabel("Zoom to Fit");\r
-// mmenuItem.setCommand(command);\r
-// mmenuItem.getParameters().add(param);\r
-// }\r
-// }\r
-// \r
-// getPart().getContext().get(EMenuService.class).registerContextMenu(canvas, CONTEXT_MENU_ID);\r
- \r
- \r
- \r
- menuManager = new MenuManager("Time Series Editor", CONTEXT_MENU_ID);\r
- menuManager.setRemoveAllWhenShown(true);\r
- Menu menu = menuManager.createContextMenu(canvas);\r
- canvas.setMenu(menu);\r
- // TODO: link to modeled menu in E4 way\r
-// getEditorSite().registerContextMenu(menuManager.getId(), menuManager, selectionProvider);\r
-\r
- // Add support for some built-in actions in the context menu.\r
- menuManager.addMenuListener(new IMenuListener() {\r
- @Override\r
- public void menuAboutToShow(IMenuManager manager) {\r
- // Not initialized yet, prevent NPE.\r
- TrendNode trendNode = TimeSeriesEditor.this.trendNode;\r
- TrendParticipant tp = TimeSeriesEditor.this.tp;\r
- if (trendNode == null || tp == null)\r
- return;\r
-\r
- TrendSpec trendSpec = trendNode.getTrendSpec();\r
- ItemNode hoverItem = tp.hoveringItem;\r
- if (hoverItem != null && hoverItem.item != null) {\r
- Resource component = resolveReferencedComponent(hoverItem.item.variableId);\r
- if (component != null) {\r
- manager.add(new PerformDefaultAction("Show Referenced Component", canvas, component));\r
- }\r
-\r
- Resource chart = TimeSeriesEditor.this.getInputResource();\r
- if ( chart != null ) {\r
- try {\r
- Resource chartItem = Simantics.getSession().sync( new FindChartItemForTrendItem(chart, hoverItem.item) );\r
- if (chartItem != null) {\r
- manager.add(new HideItemsAction("Hide Item", true, Collections.singletonList(chartItem)));\r
- manager.add(new Separator());\r
- manager.add(new PropertiesAction("Item Properties", canvas, chartItem));\r
- manager.add(new PropertiesAction("Chart Properties", canvas, chart));\r
- }\r
- } catch (DatabaseException e) {\r
- Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to resolve context menu items.", e));\r
- }\r
- }\r
- } else {\r
- boolean hairlineMovementAllowed =\r
- !(trendSpec.experimentIsRunning &&\r
- trendSpec.viewProfile.trackExperimentTime);\r
-\r
- Resource chart = TimeSeriesEditor.this.getInputResource();\r
- manager.add(new Separator());\r
- manager.add(new MoveHairlineAction(\r
- "Move Hairline Here",\r
- chart,\r
- hairlineMovementAllowed,\r
- trendNode,\r
- trendNode.mouseHoverTime\r
- ));\r
- manager.add(new MoveHairlineAction(\r
- "Move Hairline To Current Time",\r
- chart,\r
- hairlineMovementAllowed,\r
- trendNode,\r
- trendNode.horizRuler.getItemEndTime(),\r
- Boolean.FALSE\r
- ));\r
- manager.add(new TrackExperimentTimeAction(\r
- "Hairline Tracks Current Time",\r
- chart,\r
- trendSpec.viewProfile.trackExperimentTime));\r
- manager.add(new Separator());\r
- manager.add(new SendCommandAction("Zoom to Fit", IMG_ZOOM_TO_FIT, cvsCtx, Commands.ZOOM_TO_FIT));\r
- manager.add(new SendCommandAction("Zoom to Fit Horizontally", IMG_ZOOM_TO_FIT_HORIZ, cvsCtx, Commands.ZOOM_TO_FIT_HORIZ));\r
- manager.add(new SendCommandAction("Zoom to Fit Vertically", IMG_ZOOM_TO_FIT_VERT, cvsCtx, Commands.ZOOM_TO_FIT_VERT));\r
- manager.add(new SendCommandAction("Autoscale Chart", IMG_AUTOSCALE, cvsCtx, Commands.AUTOSCALE));\r
- manager.add(new Separator());\r
- manager.add(new PropertiesAction("Chart Properties", canvas, chart));\r
- }\r
- }\r
- });\r
- }\r
-\r
- protected Resource resolveReferencedComponent(final String variableId) {\r
- try {\r
- return Simantics.getSession().sync(new UniqueRead<Resource>() {\r
- @Override\r
- public Resource perform(ReadGraph graph) throws DatabaseException {\r
- Variable configuration = Variables.getConfigurationContext(graph, getInputResource());\r
- RVI rvi = RVI.fromResourceFormat(graph, variableId);\r
- rvi = new RVIBuilder(rvi).removeFromFirstRole(Role.PROPERTY).toRVI();\r
- if (rvi.isEmpty())\r
- return null;\r
- Variable var = rvi.resolve(graph, configuration);\r
- return var.getPossibleRepresents(graph);\r
- }\r
- });\r
- } catch (DatabaseException e) {\r
- ErrorLogger.defaultLogError(e);\r
- }\r
- return null;\r
- }\r
-\r
- private void showPopup(Point p) {\r
- menuManager.getMenu().setLocation(p);\r
- menuManager.getMenu().setVisible(true);\r
- }\r
-\r
- private void trackChartConfiguration() {\r
- Simantics.getSession().asyncRequest(new TrendSpecQuery( uniqueChartEditorId, getInputResource() ), new TrendSpecListener());\r
- Simantics.getSession().asyncRequest(new ActiveRunQuery( uniqueChartEditorId, getInputResource() ), new ActiveRunListener());\r
- }\r
-\r
- @Override\r
- public void setFocus() {\r
- if (errorText != null)\r
- errorText.setFocus();\r
- else\r
- canvas.setFocus();\r
- }\r
-\r
- @Override\r
- public void dispose() {\r
- if (isDisposed() == true) return;\r
-\r
- if (trendNode!=null && trendNode.horizRuler!=null) {\r
- trendNode.horizRuler.listener = null;\r
- }\r
-\r
-// if ( linkTimeState != null ) linkTimeState.removeListener( linkTimeStateListener );\r
-\r
- canvas.getHintContext().removeHint( SWTChassis.KEY_EDITORPART );\r
- canvas.getHintContext().removeHint( SWTChassis.KEY_WORKBENCHPAGE );\r
- canvas.getHintContext().removeHint( SWTChassis.KEY_WORKBENCHWINDOW );\r
-\r
- if ( chartPreferenceNode!= null ) {\r
- chartPreferenceNode.removePreferenceChangeListener( preferenceListener );\r
- }\r
-\r
- MilestoneSpecListener ml = milestoneListener;\r
- if (ml!=null) ml.dispose();\r
-\r
- if (project != null) {\r
- project.removeHintListener(chartDataListener);\r
- }\r
-\r
- if (chartData != null) {\r
- if (chartData.datasource!=null)\r
- chartData.datasource.removeListener( stepListener );\r
- if (chartData.experiment!=null)\r
- chartData.experiment.removeListener( experimentStateListener );\r
- chartData.readFrom( null );\r
- }\r
- }\r
-\r
- /**\r
- * @param data new data or null\r
- * @param newSpec new spec or null\r
- * @thread AWT\r
- */\r
- @SuppressWarnings("unused")\r
- public void setInput(ChartData data, TrendSpec newSpec) {\r
- boolean doLayout = false;\r
-\r
- // Disregard input if it is not for this chart's containing model.\r
- if (data != null && data.model != null && !data.model.equals(assertInputModelResource()))\r
- data = null;\r
-\r
- // Accommodate Datasource changes\r
- Datasource: {\r
- Datasource oldDatasource = chartData==null?null:chartData.datasource;\r
- Datasource newDatasource = data==null?null:data.datasource;\r
- //if ( !ObjectUtils.objectEquals(oldDatasource, newDatasource) ) \r
- {\r
- if (oldDatasource!=null) oldDatasource.removeListener( stepListener );\r
- if (newDatasource!=null) newDatasource.addListener( stepListener );\r
- }\r
- }\r
-\r
- Experiment: {\r
- IExperiment oldExperiment = chartData==null?null:chartData.experiment;\r
- IExperiment newExperiment = data==null?null:data.experiment;\r
- //if ( !ObjectUtils.objectEquals(oldExperiment, newExperiment) ) \r
- {\r
- if (oldExperiment!=null) oldExperiment.removeListener( experimentStateListener );\r
- if (newExperiment!=null) newExperiment.addListener( experimentStateListener );\r
- }\r
- }\r
-\r
- // Accommodate Historian changes\r
- Historian: {\r
- HistoryManager oldHistorian = trendNode.historian==null?null:trendNode.historian;\r
- HistoryManager newHistorian = data==null?null:data.history;\r
- Collector newCollector = data==null?null:data.collector;\r
- // if ( !ObjectUtils.objectEquals(oldHistorian, newHistorian) ) \r
- {\r
- if (newHistorian instanceof FileHistory) {\r
- FileHistory fh = (FileHistory) newHistorian;\r
- System.out.println("History = "+fh.getWorkarea());\r
- }\r
- trendNode.setHistorian( newHistorian, newCollector );\r
- doLayout |= trendNode.autoscale(true, true) | !ObjectUtils.objectEquals(oldHistorian, newHistorian);\r
- }\r
-\r
- // Accommodate TrendSpec changes\r
- TrendSpec oldSpec = trendNode.getTrendSpec();\r
- if ( !newSpec.equals(oldSpec) ) {\r
- trendNode.setTrendSpec( newSpec==null?TrendSpec.EMPTY:newSpec );\r
- doLayout = true;\r
- }\r
- \r
- }\r
-\r
- Resource newExperimentResource = data==null ? null : data.run;\r
- Resource oldExperimentResource = this.chartData == null ? null : this.chartData.run;\r
- \r
- // Track milestones\r
- Milestones: {\r
- if (!ObjectUtils.objectEquals(oldExperimentResource, newExperimentResource)) {\r
-\r
- // Dispose old listener & Query\r
- if (milestoneListener!=null) {\r
- milestoneListener.dispose();\r
- milestoneListener = null;\r
- }\r
- if (milestoneQuery!=null) {\r
- milestoneQuery = null;\r
- }\r
-\r
- trendNode.setMilestones( MilestoneSpec.EMPTY );\r
- \r
- if (newExperimentResource != null) {\r
- milestoneListener = new MilestoneSpecListener();\r
- milestoneQuery = new MilestoneSpecQuery( newExperimentResource );\r
- Simantics.getSession().asyncRequest( milestoneQuery, milestoneListener );\r
- }\r
- }\r
- \r
- }\r
-\r
- if (doLayout) trendNode.layout();\r
- this.chartData.readFrom( data );\r
- tp.setDirty();\r
- \r
- if (!ObjectUtils.objectEquals(oldExperimentResource, newExperimentResource)) {\r
- resetViewAfterDataChange();\r
- }\r
- \r
- }\r
-\r
- class ActiveRunListener implements SyncListener<Resource> {\r
- @Override\r
- public void exception(ReadGraph graph, Throwable throwable) {\r
- ErrorLogger.defaultLogError(throwable);\r
- ShowMessage.showError(throwable.getClass().getSimpleName(), throwable.getMessage());\r
- }\r
- @Override\r
- public void execute(ReadGraph graph, final Resource run) throws DatabaseException {\r
- if(run != null) {\r
- SimulationResource SIMU = SimulationResource.getInstance(graph);\r
- Variable var = Variables.getVariable(graph, run);\r
- IExperiment exp = var.getPossiblePropertyValue(graph, SIMU.Run_iExperiment);\r
- ITrendSupport ts = exp.getService(ITrendSupport.class);\r
- ts.setChartData(graph);\r
- }\r
- }\r
- @Override\r
- public boolean isDisposed() {\r
- return TimeSeriesEditor.this.isDisposed();\r
- }\r
- }\r
- \r
- class TrendSpecListener implements AsyncListener<TrendSpec> {\r
- @Override\r
- public void exception(AsyncReadGraph graph, Throwable throwable) {\r
- \r
- ErrorLogger.defaultLogError(throwable);\r
- ShowMessage.showError(throwable.getClass().getSimpleName(), throwable.getMessage());\r
- }\r
- @Override\r
- public void execute(AsyncReadGraph graph, final TrendSpec result) {\r
- if (result == null) {\r
- log.log(Level.INFO, "Chart configuration removed");\r
- } else {\r
- log.log(Level.INFO, "Chart configuration updated: " + result);\r
- }\r
-\r
- // Reload chart in AWT Thread\r
- AWTThread.getThreadAccess().asyncExec(new Runnable() {\r
- @Override\r
- public void run() {\r
- if (!isDisposed())\r
- setInput( chartData, result );\r
- }\r
- });\r
- }\r
- @Override\r
- public boolean isDisposed() {\r
- return TimeSeriesEditor.this.isDisposed();\r
- }\r
- }\r
- \r
- class MilestoneSpecListener implements AsyncListener<MilestoneSpec> {\r
- boolean disposed = false;\r
- @Override\r
- public void execute(AsyncReadGraph graph, final MilestoneSpec result) {\r
- AWTThread.INSTANCE.asyncExec(new Runnable() {\r
- public void run() {\r
- trendNode.setMilestones(result);\r
- }});\r
- }\r
-\r
- @Override\r
- public void exception(AsyncReadGraph graph, Throwable throwable) {\r
- \r
- }\r
-\r
- @Override\r
- public boolean isDisposed() {\r
- return disposed;\r
- }\r
- \r
- public void dispose() {\r
- disposed = true;\r
- }\r
- \r
- }\r
-\r
- private void trackPreferences() {\r
- chartPreferenceNode = InstanceScope.INSTANCE.getNode( "org.simantics.charts" );\r
- chartPreferenceNode.addPreferenceChangeListener( preferenceListener );\r
- long redrawInterval = chartPreferenceNode.getLong(ChartPreferences.P_REDRAW_INTERVAL, ChartPreferences.DEFAULT_REDRAW_INTERVAL);\r
- long autoscaleInterval = chartPreferenceNode.getLong(ChartPreferences.P_AUTOSCALE_INTERVAL, ChartPreferences.DEFAULT_AUTOSCALE_INTERVAL);\r
- setInterval(redrawInterval, autoscaleInterval);\r
- \r
- String timeFormat = chartPreferenceNode.get(ChartPreferences.P_TIMEFORMAT, ChartPreferences.DEFAULT_TIMEFORMAT); \r
- TimeFormat tf = TimeFormat.valueOf( timeFormat );\r
- if (tf!=null) setTimeFormat( tf );\r
- \r
- Boolean drawSamples = chartPreferenceNode.getBoolean(ChartPreferences.P_DRAW_SAMPLES, ChartPreferences.DEFAULT_DRAW_SAMPLES);\r
- setDrawSamples(drawSamples);\r
-\r
- String valueFormat = chartPreferenceNode.get(ChartPreferences.P_VALUEFORMAT, ChartPreferences.DEFAULT_VALUEFORMAT);\r
- ValueFormat vf = ValueFormat.valueOf( valueFormat );\r
- if (vf!=null) setValueFormat( vf );\r
- \r
- String s = chartPreferenceNode.get(ChartPreferences.P_ITEMPLACEMENT, ChartPreferences.DEFAULT_ITEMPLACEMENT);\r
- ItemPlacement ip = ItemPlacement.valueOf(s);\r
- if (trendNode!=null) trendNode.itemPlacement = ip;\r
- \r
- String s1 = chartPreferenceNode.get(ChartPreferences.P_TEXTQUALITY, ChartPreferences.DEFAULT_TEXTQUALITY);\r
- String s2 = chartPreferenceNode.get(ChartPreferences.P_LINEQUALITY, ChartPreferences.DEFAULT_LINEQUALITY);\r
- LineQuality q1 = LineQuality.valueOf(s1);\r
- LineQuality q2 = LineQuality.valueOf(s2);\r
- if (trendNode!=null) trendNode.quality.textQuality = q1;\r
- if (trendNode!=null) trendNode.quality.lineQuality = q2;\r
- \r
- }\r
-\r
- private void setInterval(long redrawInterval, long autoscaleInterval) {\r
- redrawInterval = Math.max(1, redrawInterval);\r
- long pulse = Math.min(50, redrawInterval);\r
- pulse = Math.min(pulse, autoscaleInterval);\r
- IHintContext h = canvas.getCanvasContext().getDefaultHintContext();\r
- h.setHint(TimeParticipant.KEY_TIME_PULSE_INTERVAL, pulse);\r
- h.setHint(TrendParticipant.KEY_TREND_DRAW_INTERVAL, redrawInterval);\r
- h.setHint(TrendParticipant.KEY_TREND_AUTOSCALE_INTERVAL, autoscaleInterval); \r
- }\r
-\r
- private void setDrawSamples(boolean value) {\r
- trendNode.drawSamples = value;\r
- trendNode.layout();\r
- tp.setDirty();\r
- }\r
-\r
- private void setTimeFormat( TimeFormat tf ) {\r
- if (trendNode.timeFormat == tf) return;\r
- trendNode.timeFormat = tf;\r
- trendNode.layout();\r
- tp.setDirty();\r
- }\r
-\r
- private void setValueFormat( ValueFormat vf ) {\r
- if (trendNode.valueFormat == vf) return;\r
- trendNode.valueFormat = vf;\r
- trendNode.layout();\r
- tp.setDirty();\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public <T> T getAdapter(Class<T> adapter) {\r
- if (adapter == INode.class) {\r
- ICanvasContext ctx = cvsCtx;\r
- if (ctx != null)\r
- return (T) ctx.getSceneGraph();\r
- }\r
- if (adapter == IPropertyPage.class)\r
- // TODO: pure e4 property page without sites\r
- return (T) new StandardPropertyPage(null, getPropertyPageContexts());\r
- if (adapter == ICanvasContext.class)\r
- return (T) cvsCtx;\r
- return null;\r
- }\r
-\r
- protected Set<String> getPropertyPageContexts() {\r
- try {\r
- return BrowseContext.getBrowseContextClosure(Simantics.getSession(), Collections.singleton(ROOT_PROPERTY_BROWSE_CONTEXT));\r
- } catch (DatabaseException e) {\r
- ExceptionUtils.logAndShowError("Failed to load modeled browse contexts for property page, see exception for details.", e);\r
- return Collections.singleton(ROOT_PROPERTY_BROWSE_CONTEXT);\r
- }\r
- }\r
-\r
- /**\r
- * Add from, end, (scale x) to argument array\r
- * @param fromEnd array of 2 or 3\r
- */\r
- public void getFromEnd(ChartLinkData data) {\r
- data.sender = this;\r
- TrendNode tn = trendNode;\r
- data.valueTipTime = tn.valueTipTime;\r
- HorizRuler hr = tn!=null ? tn.horizRuler : null;\r
- if ( hr != null ) {\r
- data.from = hr.from;\r
- data.end = hr.end;\r
- double len = hr.end-hr.from; \r
- double wid = tn.plot.getWidth();\r
- if ( wid==0.0 ) wid = 0.1;\r
- data.sx = len/wid;\r
- }\r
- }\r
-\r
- @SuppressWarnings("unused")\r
- private static boolean doubleEquals(double a, double b) {\r
- if (Double.isNaN(a) && Double.isNaN(b)) return true;\r
- return a==b;\r
- }\r
-\r
- protected void resetViewAfterDataChange() {\r
- \r
- CanvasUtils.sendCommand(cvsCtx, Commands.CANCEL);\r
- CanvasUtils.sendCommand(cvsCtx, Commands.AUTOSCALE);\r
- \r
- }\r
-\r
-// private Resource getInputResource() {\r
-// return resource;\r
-// }\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
+ * Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.charts.editor.e4;
+
+import java.awt.geom.Point2D;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
+import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.e4.ui.model.application.MApplication;
+import org.eclipse.e4.ui.model.application.MApplicationElement;
+import org.eclipse.e4.ui.model.application.ui.MUIElement;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.model.application.ui.menu.MToolBar;
+import org.eclipse.e4.ui.services.EContextService;
+import org.eclipse.e4.ui.workbench.modeling.EModelService;
+import org.eclipse.e4.ui.workbench.modeling.ISelectionListener;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorSite;
+import org.simantics.Simantics;
+import org.simantics.browsing.ui.model.browsecontexts.BrowseContext;
+import org.simantics.charts.Activator;
+import org.simantics.charts.ITrendSupport;
+import org.simantics.charts.editor.ActiveRunQuery;
+import org.simantics.charts.editor.ChartData;
+import org.simantics.charts.editor.ChartKeyBindings;
+import org.simantics.charts.editor.ChartKeys;
+import org.simantics.charts.editor.ChartPasteHandler2;
+import org.simantics.charts.editor.HideItemsAction;
+import org.simantics.charts.editor.MoveHairlineAction;
+import org.simantics.charts.editor.PropertiesAction;
+import org.simantics.charts.editor.SendCommandAction;
+import org.simantics.charts.editor.SubscriptionDropParticipant;
+import org.simantics.charts.editor.TrackExperimentTimeAction;
+import org.simantics.charts.ontology.ChartResource;
+import org.simantics.charts.preference.ChartPreferences;
+import org.simantics.charts.query.FindChartItemForTrendItem;
+import org.simantics.charts.query.MilestoneSpecQuery;
+import org.simantics.charts.query.SetProperty;
+import org.simantics.charts.query.TrendSpecQuery;
+import org.simantics.charts.ui.ChartLinkData;
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.util.ObjectUtils;
+import org.simantics.db.AsyncReadGraph;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Session;
+import org.simantics.db.common.request.ParametrizedRead;
+import org.simantics.db.common.request.UniqueRead;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.request.combinations.Combinators;
+import org.simantics.db.layer0.variable.RVI;
+import org.simantics.db.layer0.variable.RVIBuilder;
+import org.simantics.db.layer0.variable.Variable;
+import org.simantics.db.layer0.variable.Variables;
+import org.simantics.db.layer0.variable.Variables.Role;
+import org.simantics.db.procedure.AsyncListener;
+import org.simantics.db.procedure.SyncListener;
+import org.simantics.diagram.participant.SGFocusParticipant;
+import org.simantics.diagram.participant.e4.ContextUtil;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.impl.CanvasContext;
+import org.simantics.g2d.chassis.AWTChassis;
+import org.simantics.g2d.chassis.ICanvasChassis;
+import org.simantics.g2d.chassis.IChassisListener;
+import org.simantics.g2d.chassis.SWTChassis;
+import org.simantics.g2d.participant.KeyToCommand;
+import org.simantics.g2d.participant.TimeParticipant;
+import org.simantics.g2d.utils.CanvasUtils;
+import org.simantics.history.Collector;
+import org.simantics.history.HistoryManager;
+import org.simantics.history.impl.FileHistory;
+import org.simantics.project.IProject;
+import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.g2d.events.Event;
+import org.simantics.scenegraph.g2d.events.EventTypes;
+import org.simantics.scenegraph.g2d.events.IEventHandler;
+import org.simantics.scenegraph.g2d.events.MouseEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
+import org.simantics.scenegraph.g2d.events.command.Commands;
+import org.simantics.selectionview.StandardPropertyPage;
+import org.simantics.simulation.data.Datasource;
+import org.simantics.simulation.experiment.ExperimentState;
+import org.simantics.simulation.experiment.IExperiment;
+import org.simantics.simulation.experiment.IExperimentListener;
+import org.simantics.simulation.ontology.SimulationResource;
+import org.simantics.trend.TrendInitializer;
+import org.simantics.trend.TrendInitializer.StepListener;
+import org.simantics.trend.configuration.ItemPlacement;
+import org.simantics.trend.configuration.LineQuality;
+import org.simantics.trend.configuration.TimeFormat;
+import org.simantics.trend.configuration.TrendSpec;
+import org.simantics.trend.impl.HorizRuler;
+import org.simantics.trend.impl.ItemNode;
+import org.simantics.trend.impl.MilestoneSpec;
+import org.simantics.trend.impl.TrendNode;
+import org.simantics.trend.impl.TrendParticipant;
+import org.simantics.ui.workbench.IPropertyPage;
+import org.simantics.ui.workbench.IResourceEditorInput;
+import org.simantics.ui.workbench.ResourceEditorInput;
+import org.simantics.ui.workbench.action.PerformDefaultAction;
+import org.simantics.ui.workbench.e4.E4ResourceEditorBase;
+import org.simantics.ui.workbench.editor.input.InputValidationCombinators;
+import org.simantics.utils.datastructures.hints.HintListenerAdapter;
+import org.simantics.utils.datastructures.hints.IHintContext;
+import org.simantics.utils.datastructures.hints.IHintContext.Key;
+import org.simantics.utils.datastructures.hints.IHintObservable;
+import org.simantics.utils.format.ValueFormat;
+import org.simantics.utils.threads.AWTThread;
+import org.simantics.utils.threads.IThreadWorkQueue;
+import org.simantics.utils.threads.SWTThread;
+import org.simantics.utils.threads.ThreadUtils;
+import org.simantics.utils.ui.BundleUtils;
+import org.simantics.utils.ui.ErrorLogger;
+import org.simantics.utils.ui.ExceptionUtils;
+import org.simantics.utils.ui.SWTUtils;
+import org.simantics.utils.ui.dialogs.ShowMessage;
+import org.simantics.utils.ui.jface.ActiveSelectionProvider;
+
+/**
+ * TimeSeriesEditor is an interactive part that draws a time series chart.
+ *
+ * The configuration model is {@link TrendSpec} which is read through
+ * {@link TrendSpecQuery}. In Simantics Environment the
+ * editor input is {@link ResourceEditorInput}.
+ *
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
+ * @author Tuukka Lehtonen
+ */
+public class TimeSeriesEditor extends E4ResourceEditorBase implements IAdaptable {
+
+ ParametrizedRead<IResourceEditorInput, Boolean> INPUT_VALIDATOR =
+ Combinators.compose(
+ InputValidationCombinators.hasURI(),
+ InputValidationCombinators.extractInputResource()
+ );
+
+ protected ParametrizedRead<IResourceEditorInput, Boolean> getInputValidator() {
+ return INPUT_VALIDATOR;
+ }
+
+ /**
+ * The root property browse context of the time series editor. A transitive
+ * closure is calculated for this context.
+ */
+ private static String ROOT_PROPERTY_BROWSE_CONTEXT = ChartResource.URIs.ChartBrowseContext;
+
+ /**
+ * ID of the this editor part extension.
+ */
+ public static final String ID = "org.simantics.charts.editor.timeseries";
+
+ public static final String CONTRIBUTION_URI = "bundleclass://org.simantics.charts/org.simantics.charts.editor.e4.TimeSeriesEditor";
+
+ private static final String CONTEXT_MENU_ID = "#timeSeriesChart";
+
+ private IEclipsePreferences chartPreferenceNode;
+
+ private final ImageDescriptor IMG_ZOOM_TO_FIT = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/horizAndVert16.png");
+ private final ImageDescriptor IMG_ZOOM_TO_FIT_HORIZ = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/horiz16.png");
+ private final ImageDescriptor IMG_ZOOM_TO_FIT_VERT = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/vert16.png");
+ private final ImageDescriptor IMG_AUTOSCALE = BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/autoscale16.png");
+
+ IPreferenceChangeListener preferenceListener = new IPreferenceChangeListener() {
+
+ @Override
+ public void preferenceChange(PreferenceChangeEvent event) {
+ if (isDisposed()) {
+ System.err.println("Warning: pref change to disposed TimeSeriesEditor");
+ return;
+ }
+
+ if ( event.getKey().equals(ChartPreferences.P_REDRAW_INTERVAL ) ||
+ event.getKey().equals(ChartPreferences.P_AUTOSCALE_INTERVAL )) {
+ long redraw_interval = chartPreferenceNode.getLong(ChartPreferences.P_REDRAW_INTERVAL, ChartPreferences.DEFAULT_REDRAW_INTERVAL);
+ long autoscale_interval = chartPreferenceNode.getLong(ChartPreferences.P_AUTOSCALE_INTERVAL, ChartPreferences.DEFAULT_AUTOSCALE_INTERVAL);
+ setInterval( redraw_interval, autoscale_interval );
+ }
+ if ( event.getKey().equals(ChartPreferences.P_DRAW_SAMPLES )) {
+ boolean draw_samples = chartPreferenceNode.getBoolean(ChartPreferences.P_DRAW_SAMPLES, ChartPreferences.DEFAULT_DRAW_SAMPLES);
+ setDrawSamples( draw_samples );
+ }
+ if ( event.getKey().equals(ChartPreferences.P_TIMEFORMAT ) ) {
+ String s = chartPreferenceNode.get(ChartPreferences.P_TIMEFORMAT, ChartPreferences.DEFAULT_TIMEFORMAT);
+ TimeFormat tf = TimeFormat.valueOf( s );
+ if (tf!=null) setTimeFormat( tf );
+ }
+ if ( event.getKey().equals(ChartPreferences.P_VALUEFORMAT ) ) {
+ String s = chartPreferenceNode.get(ChartPreferences.P_VALUEFORMAT, ChartPreferences.DEFAULT_VALUEFORMAT);
+ ValueFormat vf = ValueFormat.valueOf( s );
+ if (vf!=null) setValueFormat( vf );
+ }
+ if ( event.getKey().equals(ChartPreferences.P_ITEMPLACEMENT)) {
+ String s = chartPreferenceNode.get(ChartPreferences.P_ITEMPLACEMENT, ChartPreferences.DEFAULT_ITEMPLACEMENT);
+ ItemPlacement ip = ItemPlacement.valueOf(s);
+ if (trendNode!=null) trendNode.itemPlacement = ip;
+ }
+ if ( event.getKey().equals(ChartPreferences.P_TEXTQUALITY) || event.getKey().equals(ChartPreferences.P_LINEQUALITY) ) {
+ String s1 = chartPreferenceNode.get(ChartPreferences.P_TEXTQUALITY, ChartPreferences.DEFAULT_TEXTQUALITY);
+ String s2 = chartPreferenceNode.get(ChartPreferences.P_LINEQUALITY, ChartPreferences.DEFAULT_LINEQUALITY);
+ LineQuality q1 = LineQuality.valueOf(s1);
+ LineQuality q2 = LineQuality.valueOf(s2);
+ if (trendNode!=null) trendNode.quality.textQuality = q1;
+ if (trendNode!=null) trendNode.quality.lineQuality = q2;
+ }
+
+ }
+ };
+
+ /**
+ * The project which this editor is listening to for changes to
+ * {@link ChartKeys.ChartSourceKey keys}.
+ */
+ IProject project;
+
+ /**
+ * The text widget shown only if there is no IProject available at the time
+ * of editor part creation.
+ */
+ Text errorText;
+
+ /**
+ * A unique key for making DB requests chart editor specific without binding
+ * the requests to the editor object itself.
+ */
+ UUID uniqueChartEditorId = UUID.randomUUID();
+ Logger log;
+ Display display;
+ SWTChassis canvas;
+ CanvasContext cvsCtx;
+ TrendParticipant tp;
+ TrendNode trendNode;
+ StepListener stepListener;
+ MilestoneSpecListener milestoneListener;
+ MilestoneSpecQuery milestoneQuery;
+
+ /**
+ * The ChartData instance used by this editor for sourcing data at any given
+ * moment. Project hint instances are copied into this instance.
+ */
+ final ChartData chartData = new ChartData(null, null, null, null, null, null);
+
+ /**
+ * The ChartSourceKey to match the model this editor was opened for.
+ * @see #model
+ * @see #init(IEditorSite, IEditorInput)
+ */
+ ChartKeys.ChartSourceKey chartDataKey;
+
+
+ /**
+ * Context management utils
+ */
+ protected IThreadWorkQueue swt;
+ protected ContextUtil contextUtil;
+
+ class ExperimentStateListener implements IExperimentListener {
+ @Override
+ public void stateChanged(ExperimentState state) {
+ TrendSpec spec = trendNode.getTrendSpec();
+ spec.experimentIsRunning = state == ExperimentState.RUNNING;
+ if (spec.experimentIsRunning && spec.viewProfile.trackExperimentTime) {
+ TrendParticipant t = tp;
+ if (t != null)
+ t.setDirty();
+ }
+ }
+ }
+
+ ExperimentStateListener experimentStateListener = new ExperimentStateListener();
+
+ class ChartDataListener extends HintListenerAdapter implements Runnable {
+ @Override
+ public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
+ if (key.equals(chartDataKey)) {
+ // @Thread any
+ if (!cvsCtx.isDisposed() && cvsCtx.isAlive()) {
+ cvsCtx.getThreadAccess().asyncExec(this);
+ }
+ }
+ }
+ @Override
+ public void run() {
+ // @Thread AWT
+ if (cvsCtx.isDisposed() || !cvsCtx.isAlive()) return;
+ ChartData data = Simantics.getProject().getHint(chartDataKey);
+ setInput( data, trendNode.getTrendSpec() );
+ }
+ }
+
+ ChartDataListener chartDataListener = new ChartDataListener();
+
+ class ValueTipBoxPositionListener extends HintListenerAdapter {
+ @Override
+ public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
+ if (key.equals(TrendParticipant.KEY_VALUE_TIP_BOX_RELATIVE_POS) && newValue != null) {
+ Session s = Simantics.getSession();
+ ChartResource CHART = s.getService(ChartResource.class);
+ Point2D p = (Point2D) newValue;
+ double[] value = { p.getX(), p.getY() };
+ s.asyncRequest(new SetProperty(getInputResource(), CHART.Chart_valueViewPosition, value, Bindings.DOUBLE_ARRAY));
+ }
+ }
+ }
+
+ ValueTipBoxPositionListener valueTipBoxPositionListener = new ValueTipBoxPositionListener();
+
+
+ private void handleLinkDataChange() {
+ final ChartLinkData newData = linkData;
+ trendNode.autoscaletime = newData == null || newData.sender == TimeSeriesEditor.this;
+
+ if ( newData == null || newData.sender==TimeSeriesEditor.this ) return;
+ TrendNode tn = trendNode;
+ HorizRuler hr = tn!=null ? tn.horizRuler : null;
+
+ ChartLinkData oldData = new ChartLinkData();
+ getFromEnd(oldData);
+
+ if ( hr != null && !ObjectUtils.objectEquals(tn.valueTipTime, newData.valueTipTime)) {
+ tn.valueTipTime = newData.valueTipTime;
+ tp.setDirty();
+ }
+
+ if ( hr != null && (oldData.from!=newData.from || oldData.sx!=newData.sx)) {
+
+ cvsCtx.getThreadAccess().asyncExec( new Runnable() {
+ @Override
+ public void run() {
+ boolean b = trendNode.horizRuler.setFromScale(newData.from, newData.sx);
+ trendNode.horizRuler.autoscroll = false;
+ if (b) {
+ trendNode.layout();
+ tp.setDirty();
+ }
+ }});
+ }
+ }
+
+ // Link-Time
+// State linkTimeState;
+// IStateListener linkTimeStateListener = new IStateListener() {
+// @Override
+// public void handleStateChange(State state, Object oldValue) {
+//
+//
+// }
+// };
+
+ HorizRuler.TimeWindowListener horizRulerListener = new HorizRuler.TimeWindowListener() {
+ @Override
+ public void onNewWindow(double from, double end, double scalex) {
+ final ChartLinkData oldData = linkData;
+ if (oldData != null) {
+ ChartLinkData data = new ChartLinkData(TimeSeriesEditor.this, from, end, scalex);
+ data.valueTipTime = trendNode.valueTipTime;
+ linkData = data;
+ handleLinkDataChange();
+ }
+ }
+ };
+
+ class ChassisListener implements IChassisListener {
+ @Override
+ public void chassisClosed(ICanvasChassis sender) {
+ // Prevent deadlock while disposing which using syncExec would result in.
+ final ICanvasContext ctx = cvsCtx;
+ ThreadUtils.asyncExec(ctx.getThreadAccess(), new Runnable() {
+ @Override
+ public void run() {
+ if (ctx != null) {
+ AWTChassis awt = canvas.getAWTComponent();
+ if (awt != null)
+ awt.setCanvasContext(null);
+ ctx.dispose();
+ }
+ }
+ });
+ canvas.removeChassisListener(ChassisListener.this);
+ }
+ }
+
+ ActiveSelectionProvider selectionProvider = new ActiveSelectionProvider();
+ MenuManager menuManager;
+
+ @Inject
+ EContextService contextService;
+
+ private ChartLinkData linkData;
+
+ public TimeSeriesEditor() {
+ log = Logger.getLogger( this.getClass().getName() );
+ }
+
+ @Override
+ public void initImpl(MPart part) {
+ EModelService modelService = getContext().get(EModelService.class);
+ MApplication app = getContext().get(MApplication.class);
+ MUIElement element = modelService.cloneSnippet(app, "org.simantics.charts.toolbar.default.snippet", null);
+ if (element != null) {
+ element.setVisible(true);
+ element.setToBeRendered(true);
+ part.setToolbar((MToolBar)element);
+ }
+
+// MBindingContext dialogAndWindowContext = findMAppElementById(app.getRootContext(), "org.eclipse.ui.contexts.dialogAndWindow");
+// MBindingContext windowContext = findMAppElementById(dialogAndWindowContext.getChildren(), "org.eclipse.ui.contexts.window");
+// MBindingContext chartContext = findMAppElementById(windowContext.getChildren(), "org.simantics.charts.editor.context");
+// if (chartContext == null) {
+// chartContext = MCommandsFactory.INSTANCE.createBindingContext();
+// chartContext.setContributorURI("bundle://test.uri");
+// chartContext.setElementId("org.simantics.charts.editor.context");
+// chartContext.setName("In Chart Editor");
+// chartContext.setDescription("Chart Editor Binding Context");
+// windowContext.getChildren().add(chartContext);
+// }
+// MBindingTable chartBindingTable = findMAppElementById(app.getBindingTables(), "org.simantics.charts.editor.context");
+// if (chartBindingTable == null) {
+// chartBindingTable = MCommandsFactory.INSTANCE.createBindingTable();
+// chartBindingTable.setContributorURI("bundle://test.test.uri");
+// chartBindingTable.setElementId("org.simantics.charts.editor.context");
+// chartBindingTable.setBindingContext(chartContext);
+//
+// for (MCommand command : app.getCommands()) {
+// if (command.getElementId().equals("org.simantics.chart.canvasCommandDelegateCommand")) {
+//
+// MParameter param = MCommandsFactory.INSTANCE.createParameter();
+// param.setElementId("id");
+// param.setName("org.simantics.charts.commandparameter.canvasCommandDelegateParameter");
+// param.setValue("zoomToFit");
+// MKeyBinding keyBinding = MCommandsFactory.INSTANCE.createKeyBinding();
+// keyBinding.setKeySequence("1");
+// keyBinding.setCommand(command);
+// keyBinding.getParameters().add(param);
+// chartBindingTable.getBindings().add(keyBinding);
+//
+// break;
+// }
+// }
+//
+// app.getBindingTables().add(chartBindingTable);
+// }
+// if (!part.getBindingContexts().contains(chartContext))
+// part.getBindingContexts().add(chartContext);
+ }
+
+ private <T extends MApplicationElement> T findMAppElementById(List<T> appElements, String elementId) {
+ for (T element : appElements) {
+ if (element.getElementId() != null && element.getElementId().equals(elementId)) {
+ return element;
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Invoke this only from the AWT thread.
+ * @param context
+ */
+ protected void setCanvasContext(final SWTChassis chassis, final ICanvasContext context) {
+ // Cannot directly invoke SWTChassis.setCanvasContext only because it
+ // needs to be invoked in the SWT thread and AWTChassis.setCanvasContext in the
+ // AWT thread, but directly invoking SWTChassis.setCanvasContext would call both
+ // in the SWT thread which would cause synchronous scheduling of AWT
+ // runnables which is always a potential source of deadlocks.
+ chassis.getAWTComponent().setCanvasContext(context);
+ SWTUtils.asyncExec(chassis, new Runnable() {
+ @Override
+ public void run() {
+ if (!chassis.isDisposed())
+ // For AWT, this is a no-operation.
+ chassis.setCanvasContext(context);
+ }
+ });
+ }
+
+ @Override
+ public void createPartControl(Composite parent) {
+ this.chartDataKey = ChartKeys.chartSourceKey(assertInputModelResource());
+ display = parent.getDisplay();
+ swt = SWTThread.getThreadAccess(display);
+
+ // Must have a project to attach to, otherwise the editor is useless.
+ project = Simantics.peekProject();
+ if (project == null) {
+ errorText = new Text(parent, SWT.NONE);
+ errorText.setText("No project is open.");
+ errorText.setEditable(false);
+ return;
+ }
+
+ // Create the canvas context here before finishing createPartControl
+ // to give anybody requiring access to this editor's ICanvasContext
+ // a chance to do their work.
+ // The context can be created in SWT thread without scheduling
+ // to the context thread and having potential deadlocks.
+ // The context is locked here and unlocked after it has been
+ // initialized in the AWT thread.
+ IThreadWorkQueue thread = AWTThread.getThreadAccess();
+ cvsCtx = new CanvasContext(thread);
+ cvsCtx.setLocked(true);
+
+ canvas = new SWTChassis(parent, SWT.NONE);
+ canvas.populate(parameter -> {
+ if (!isDisposed()) {
+ canvas.addChassisListener(new ChassisListener());
+ initializeCanvas(canvas, cvsCtx);
+ }
+ });
+
+ // Link time
+// ICommandService service = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);
+// Command command = service.getCommand( LinkTimeHandler.COMMAND_ID );
+// linkTimeState = command.getState( LinkTimeHandler.STATE_ID );
+// if ( linkTimeState != null ) linkTimeState.addListener( linkTimeStateListener );
+
+ // TODO: Link time E4 way!
+
+
+ linkData = new ChartLinkData();
+
+ addPopupMenu();
+
+ // Start tracking editor input validity.
+ activateValidation();
+
+ // Provide input as selection for property page.
+// selectionProvider.setSelection( new StructuredSelection(getInputResource()) );
+
+ getSelectionService().setSelection(new StructuredSelection(getInputResource()));
+ getSelectionService().addSelectionListener(getPart().getElementId(), new ISelectionListener() {
+
+ @Override
+ public void selectionChanged(MPart part, Object selection) {
+ System.out.println("selection changed!");
+ }
+ });
+
+// getSite().setSelectionProvider( selectionProvider );
+ }
+
+ protected void initializeCanvas(final SWTChassis chassis, CanvasContext cvsCtx) {
+ // Initialize canvas context
+ TrendSpec nodata = new TrendSpec();
+ nodata.init();
+ cvsCtx = TrendInitializer.defaultInitializeCanvas(cvsCtx, null, null, null, nodata);
+
+ tp = cvsCtx.getAtMostOneItemOfClass(TrendParticipant.class);
+
+
+// IContextService contextService = (IContextService) getSite().getService(IContextService.class);
+ contextUtil = new ContextUtil(contextService, swt);
+
+
+ cvsCtx.add( new SubscriptionDropParticipant( getInputResource() ) );
+ cvsCtx.add( new KeyToCommand( ChartKeyBindings.DEFAULT_BINDINGS ) );
+ cvsCtx.add( new ChartPasteHandler2(getInputResource()) );
+ cvsCtx.add(contextUtil);
+
+ // Context management
+ cvsCtx.add(new SGFocusParticipant(canvas, "org.simantics.charts.editor.context"));
+
+ stepListener = new StepListener( tp );
+ trendNode = tp.getTrend();
+ trendNode.titleNode.remove();
+ trendNode.titleNode = null;
+
+ // Link time
+ trendNode.horizRuler.listener = horizRulerListener;
+
+ final ChartLinkData linkTime = linkData;
+ if (linkTime!=null) trendNode.horizRuler.setFromEnd(linkTime.from, linkTime.sx);
+
+ // Handle mouse moved event after TrendParticipant.
+ // This handler forwards trend.mouseHoverTime to linkTimeState
+ cvsCtx.getEventHandlerStack().add( new IEventHandler() {
+
+ @Override
+ public int getEventMask() {
+ return EventTypes.MouseMovedMask | EventTypes.MouseClickMask | EventTypes.CommandMask | EventTypes.KeyPressed;
+ }
+
+ @Override
+ public boolean handleEvent(Event e) {
+
+// System.out.println("LinkEventHandler: "+e);
+ ChartLinkData oldData = linkData;
+ if (oldData!=null) {
+ ChartLinkData newData = new ChartLinkData();
+ getFromEnd(newData);
+ if (!newData.equals(oldData)) {
+// System.out.println("Sending new link-data");
+ linkData = newData;
+ handleLinkDataChange();
+ }
+ }
+ return false;
+ }}, -1);
+
+ // Canvas context is initialized, unlock it now to allow rendering.
+ cvsCtx.setLocked(false);
+
+ setCanvasContext(chassis, cvsCtx);
+
+ cvsCtx.getEventHandlerStack().add(new IEventHandler() {
+ @Override
+ public boolean handleEvent(Event e) {
+ MouseButtonReleasedEvent event = (MouseButtonReleasedEvent) e;
+ if (event.button != MouseEvent.RIGHT_BUTTON)
+ return false;
+
+ final Point p = new Point((int) event.screenPosition.getX(), (int) event.screenPosition.getY());
+ SWTUtils.asyncExec(chassis, new Runnable() {
+ @Override
+ public void run() {
+ if (!canvas.isDisposed())
+ showPopup(p);
+ }
+ });
+ return true;
+ }
+ @Override
+ public int getEventMask() {
+ return EventTypes.MouseButtonReleasedMask;
+ }
+ }, 1000000);
+
+ // Track data source and preinitialize chartData
+ project.addHintListener(chartDataListener);
+ chartData.readFrom( (ChartData) project.getHint( chartDataKey ) );
+
+ if (chartData.run != null) {
+ milestoneListener = new MilestoneSpecListener();
+ milestoneQuery = new MilestoneSpecQuery( chartData.run );
+ Simantics.getSession().asyncRequest( milestoneQuery, milestoneListener );
+ }
+
+ // IMPORTANT: Only after preinitializing chartData, start tracking chart configuration
+ trackChartConfiguration();
+ trackPreferences();
+
+ // Write changes to TrendSpec.viewProfile.valueViewPosition[XY]
+ // back to the graph database.
+ cvsCtx.getHintStack().addHintListener(valueTipBoxPositionListener);
+ }
+
+ private void addPopupMenu() {
+
+// #TODO: FINISH THIS WHEN E4 WORKBENCH
+// MPopupMenu popupMenu = MMenuFactory.INSTANCE.createPopupMenu();
+// popupMenu.setLabel("Time Series Editor");
+// popupMenu.setElementId(CONTEXT_MENU_ID);
+//
+// for (MCommand command : getPart().getContext().get(MApplication.class).getCommands()) {
+// if (command.getElementId().equals("org.simantics.chart.canvasCommandDelegateCommand")) {
+// MParameter param = MCommandsFactory.INSTANCE.createParameter();
+// param.setElementId("id");
+// param.setName("org.simantics.charts.commandparameter.canvasCommandDelegateParameter");
+// param.setValue("zoomToFit");
+// MHandledMenuItem mmenuItem = MMenuFactory.INSTANCE.createHandledMenuItem();
+// mmenuItem.setLabel("Zoom to Fit");
+// mmenuItem.setCommand(command);
+// mmenuItem.getParameters().add(param);
+// }
+// }
+//
+// getPart().getContext().get(EMenuService.class).registerContextMenu(canvas, CONTEXT_MENU_ID);
+
+
+
+ menuManager = new MenuManager("Time Series Editor", CONTEXT_MENU_ID);
+ menuManager.setRemoveAllWhenShown(true);
+ Menu menu = menuManager.createContextMenu(canvas);
+ canvas.setMenu(menu);
+ // TODO: link to modeled menu in E4 way
+// getEditorSite().registerContextMenu(menuManager.getId(), menuManager, selectionProvider);
+
+ // Add support for some built-in actions in the context menu.
+ menuManager.addMenuListener(new IMenuListener() {
+ @Override
+ public void menuAboutToShow(IMenuManager manager) {
+ // Not initialized yet, prevent NPE.
+ TrendNode trendNode = TimeSeriesEditor.this.trendNode;
+ TrendParticipant tp = TimeSeriesEditor.this.tp;
+ if (trendNode == null || tp == null)
+ return;
+
+ TrendSpec trendSpec = trendNode.getTrendSpec();
+ ItemNode hoverItem = tp.hoveringItem;
+ if (hoverItem != null && hoverItem.item != null) {
+ Resource component = resolveReferencedComponent(hoverItem.item.variableId);
+ if (component != null) {
+ manager.add(new PerformDefaultAction("Show Referenced Component", canvas, component));
+ }
+
+ Resource chart = TimeSeriesEditor.this.getInputResource();
+ if ( chart != null ) {
+ try {
+ Resource chartItem = Simantics.getSession().sync( new FindChartItemForTrendItem(chart, hoverItem.item) );
+ if (chartItem != null) {
+ manager.add(new HideItemsAction("Hide Item", true, Collections.singletonList(chartItem)));
+ manager.add(new Separator());
+ manager.add(new PropertiesAction("Item Properties", canvas, chartItem));
+ manager.add(new PropertiesAction("Chart Properties", canvas, chart));
+ }
+ } catch (DatabaseException e) {
+ Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to resolve context menu items.", e));
+ }
+ }
+ } else {
+ boolean hairlineMovementAllowed =
+ !(trendSpec.experimentIsRunning &&
+ trendSpec.viewProfile.trackExperimentTime);
+
+ Resource chart = TimeSeriesEditor.this.getInputResource();
+ manager.add(new Separator());
+ manager.add(new MoveHairlineAction(
+ "Move Hairline Here",
+ chart,
+ hairlineMovementAllowed,
+ trendNode,
+ trendNode.mouseHoverTime
+ ));
+ manager.add(new MoveHairlineAction(
+ "Move Hairline To Current Time",
+ chart,
+ hairlineMovementAllowed,
+ trendNode,
+ trendNode.horizRuler.getItemEndTime(),
+ Boolean.FALSE
+ ));
+ manager.add(new TrackExperimentTimeAction(
+ "Hairline Tracks Current Time",
+ chart,
+ trendSpec.viewProfile.trackExperimentTime));
+ manager.add(new Separator());
+ manager.add(new SendCommandAction("Zoom to Fit", IMG_ZOOM_TO_FIT, cvsCtx, Commands.ZOOM_TO_FIT));
+ manager.add(new SendCommandAction("Zoom to Fit Horizontally", IMG_ZOOM_TO_FIT_HORIZ, cvsCtx, Commands.ZOOM_TO_FIT_HORIZ));
+ manager.add(new SendCommandAction("Zoom to Fit Vertically", IMG_ZOOM_TO_FIT_VERT, cvsCtx, Commands.ZOOM_TO_FIT_VERT));
+ manager.add(new SendCommandAction("Autoscale Chart", IMG_AUTOSCALE, cvsCtx, Commands.AUTOSCALE));
+ manager.add(new Separator());
+ manager.add(new PropertiesAction("Chart Properties", canvas, chart));
+ }
+ }
+ });
+ }
+
+ protected Resource resolveReferencedComponent(final String variableId) {
+ try {
+ return Simantics.getSession().sync(new UniqueRead<Resource>() {
+ @Override
+ public Resource perform(ReadGraph graph) throws DatabaseException {
+ Variable configuration = Variables.getConfigurationContext(graph, getInputResource());
+ RVI rvi = RVI.fromResourceFormat(graph, variableId);
+ rvi = new RVIBuilder(rvi).removeFromFirstRole(Role.PROPERTY).toRVI();
+ if (rvi.isEmpty())
+ return null;
+ Variable var = rvi.resolve(graph, configuration);
+ return var.getPossibleRepresents(graph);
+ }
+ });
+ } catch (DatabaseException e) {
+ ErrorLogger.defaultLogError(e);
+ }
+ return null;
+ }
+
+ private void showPopup(Point p) {
+ menuManager.getMenu().setLocation(p);
+ menuManager.getMenu().setVisible(true);
+ }
+
+ private void trackChartConfiguration() {
+ Simantics.getSession().asyncRequest(new TrendSpecQuery( uniqueChartEditorId, getInputResource() ), new TrendSpecListener());
+ Simantics.getSession().asyncRequest(new ActiveRunQuery( uniqueChartEditorId, getInputResource() ), new ActiveRunListener());
+ }
+
+ @Override
+ public void setFocus() {
+ if (errorText != null)
+ errorText.setFocus();
+ else
+ canvas.setFocus();
+ }
+
+ @Override
+ public void dispose() {
+ if (isDisposed() == true) return;
+
+ if (trendNode!=null && trendNode.horizRuler!=null) {
+ trendNode.horizRuler.listener = null;
+ }
+
+// if ( linkTimeState != null ) linkTimeState.removeListener( linkTimeStateListener );
+
+ canvas.getHintContext().removeHint( SWTChassis.KEY_EDITORPART );
+ canvas.getHintContext().removeHint( SWTChassis.KEY_WORKBENCHPAGE );
+ canvas.getHintContext().removeHint( SWTChassis.KEY_WORKBENCHWINDOW );
+
+ if ( chartPreferenceNode!= null ) {
+ chartPreferenceNode.removePreferenceChangeListener( preferenceListener );
+ }
+
+ MilestoneSpecListener ml = milestoneListener;
+ if (ml!=null) ml.dispose();
+
+ if (project != null) {
+ project.removeHintListener(chartDataListener);
+ }
+
+ if (chartData != null) {
+ if (chartData.datasource!=null)
+ chartData.datasource.removeListener( stepListener );
+ if (chartData.experiment!=null)
+ chartData.experiment.removeListener( experimentStateListener );
+ chartData.readFrom( null );
+ }
+ }
+
+ /**
+ * @param data new data or null
+ * @param newSpec new spec or null
+ * @thread AWT
+ */
+ @SuppressWarnings("unused")
+ public void setInput(ChartData data, TrendSpec newSpec) {
+ boolean doLayout = false;
+
+ // Disregard input if it is not for this chart's containing model.
+ if (data != null && data.model != null && !data.model.equals(assertInputModelResource()))
+ data = null;
+
+ // Accommodate Datasource changes
+ Datasource: {
+ Datasource oldDatasource = chartData==null?null:chartData.datasource;
+ Datasource newDatasource = data==null?null:data.datasource;
+ //if ( !ObjectUtils.objectEquals(oldDatasource, newDatasource) )
+ {
+ if (oldDatasource!=null) oldDatasource.removeListener( stepListener );
+ if (newDatasource!=null) newDatasource.addListener( stepListener );
+ }
+ }
+
+ Experiment: {
+ IExperiment oldExperiment = chartData==null?null:chartData.experiment;
+ IExperiment newExperiment = data==null?null:data.experiment;
+ //if ( !ObjectUtils.objectEquals(oldExperiment, newExperiment) )
+ {
+ if (oldExperiment!=null) oldExperiment.removeListener( experimentStateListener );
+ if (newExperiment!=null) newExperiment.addListener( experimentStateListener );
+ }
+ }
+
+ // Accommodate Historian changes
+ Historian: {
+ HistoryManager oldHistorian = trendNode.historian==null?null:trendNode.historian;
+ HistoryManager newHistorian = data==null?null:data.history;
+ Collector newCollector = data==null?null:data.collector;
+ // if ( !ObjectUtils.objectEquals(oldHistorian, newHistorian) )
+ {
+ if (newHistorian instanceof FileHistory) {
+ FileHistory fh = (FileHistory) newHistorian;
+ System.out.println("History = "+fh.getWorkarea());
+ }
+ trendNode.setHistorian( newHistorian, newCollector );
+ doLayout |= trendNode.autoscale(true, true) | !ObjectUtils.objectEquals(oldHistorian, newHistorian);
+ }
+
+ // Accommodate TrendSpec changes
+ TrendSpec oldSpec = trendNode.getTrendSpec();
+ if ( !newSpec.equals(oldSpec) ) {
+ trendNode.setTrendSpec( newSpec==null?TrendSpec.EMPTY:newSpec );
+ doLayout = true;
+ }
+
+ }
+
+ Resource newExperimentResource = data==null ? null : data.run;
+ Resource oldExperimentResource = this.chartData == null ? null : this.chartData.run;
+
+ // Track milestones
+ Milestones: {
+ if (!ObjectUtils.objectEquals(oldExperimentResource, newExperimentResource)) {
+
+ // Dispose old listener & Query
+ if (milestoneListener!=null) {
+ milestoneListener.dispose();
+ milestoneListener = null;
+ }
+ if (milestoneQuery!=null) {
+ milestoneQuery = null;
+ }
+
+ trendNode.setMilestones( MilestoneSpec.EMPTY );
+
+ if (newExperimentResource != null) {
+ milestoneListener = new MilestoneSpecListener();
+ milestoneQuery = new MilestoneSpecQuery( newExperimentResource );
+ Simantics.getSession().asyncRequest( milestoneQuery, milestoneListener );
+ }
+ }
+
+ }
+
+ if (doLayout) trendNode.layout();
+ this.chartData.readFrom( data );
+ tp.setDirty();
+
+ if (!ObjectUtils.objectEquals(oldExperimentResource, newExperimentResource)) {
+ resetViewAfterDataChange();
+ }
+
+ }
+
+ class ActiveRunListener implements SyncListener<Resource> {
+ @Override
+ public void exception(ReadGraph graph, Throwable throwable) {
+ ErrorLogger.defaultLogError(throwable);
+ ShowMessage.showError(throwable.getClass().getSimpleName(), throwable.getMessage());
+ }
+ @Override
+ public void execute(ReadGraph graph, final Resource run) throws DatabaseException {
+ if(run != null) {
+ SimulationResource SIMU = SimulationResource.getInstance(graph);
+ Variable var = Variables.getVariable(graph, run);
+ IExperiment exp = var.getPossiblePropertyValue(graph, SIMU.Run_iExperiment);
+ ITrendSupport ts = exp.getService(ITrendSupport.class);
+ ts.setChartData(graph);
+ }
+ }
+ @Override
+ public boolean isDisposed() {
+ return TimeSeriesEditor.this.isDisposed();
+ }
+ }
+
+ class TrendSpecListener implements AsyncListener<TrendSpec> {
+ @Override
+ public void exception(AsyncReadGraph graph, Throwable throwable) {
+
+ ErrorLogger.defaultLogError(throwable);
+ ShowMessage.showError(throwable.getClass().getSimpleName(), throwable.getMessage());
+ }
+ @Override
+ public void execute(AsyncReadGraph graph, final TrendSpec result) {
+ if (result == null) {
+ log.log(Level.INFO, "Chart configuration removed");
+ } else {
+ log.log(Level.INFO, "Chart configuration updated: " + result);
+ }
+
+ // Reload chart in AWT Thread
+ AWTThread.getThreadAccess().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ if (!isDisposed())
+ setInput( chartData, result );
+ }
+ });
+ }
+ @Override
+ public boolean isDisposed() {
+ return TimeSeriesEditor.this.isDisposed();
+ }
+ }
+
+ class MilestoneSpecListener implements AsyncListener<MilestoneSpec> {
+ boolean disposed = false;
+ @Override
+ public void execute(AsyncReadGraph graph, final MilestoneSpec result) {
+ AWTThread.INSTANCE.asyncExec(new Runnable() {
+ public void run() {
+ trendNode.setMilestones(result);
+ }});
+ }
+
+ @Override
+ public void exception(AsyncReadGraph graph, Throwable throwable) {
+
+ }
+
+ @Override
+ public boolean isDisposed() {
+ return disposed;
+ }
+
+ public void dispose() {
+ disposed = true;
+ }
+
+ }
+
+ private void trackPreferences() {
+ chartPreferenceNode = InstanceScope.INSTANCE.getNode( "org.simantics.charts" );
+ chartPreferenceNode.addPreferenceChangeListener( preferenceListener );
+ long redrawInterval = chartPreferenceNode.getLong(ChartPreferences.P_REDRAW_INTERVAL, ChartPreferences.DEFAULT_REDRAW_INTERVAL);
+ long autoscaleInterval = chartPreferenceNode.getLong(ChartPreferences.P_AUTOSCALE_INTERVAL, ChartPreferences.DEFAULT_AUTOSCALE_INTERVAL);
+ setInterval(redrawInterval, autoscaleInterval);
+
+ String timeFormat = chartPreferenceNode.get(ChartPreferences.P_TIMEFORMAT, ChartPreferences.DEFAULT_TIMEFORMAT);
+ TimeFormat tf = TimeFormat.valueOf( timeFormat );
+ if (tf!=null) setTimeFormat( tf );
+
+ Boolean drawSamples = chartPreferenceNode.getBoolean(ChartPreferences.P_DRAW_SAMPLES, ChartPreferences.DEFAULT_DRAW_SAMPLES);
+ setDrawSamples(drawSamples);
+
+ String valueFormat = chartPreferenceNode.get(ChartPreferences.P_VALUEFORMAT, ChartPreferences.DEFAULT_VALUEFORMAT);
+ ValueFormat vf = ValueFormat.valueOf( valueFormat );
+ if (vf!=null) setValueFormat( vf );
+
+ String s = chartPreferenceNode.get(ChartPreferences.P_ITEMPLACEMENT, ChartPreferences.DEFAULT_ITEMPLACEMENT);
+ ItemPlacement ip = ItemPlacement.valueOf(s);
+ if (trendNode!=null) trendNode.itemPlacement = ip;
+
+ String s1 = chartPreferenceNode.get(ChartPreferences.P_TEXTQUALITY, ChartPreferences.DEFAULT_TEXTQUALITY);
+ String s2 = chartPreferenceNode.get(ChartPreferences.P_LINEQUALITY, ChartPreferences.DEFAULT_LINEQUALITY);
+ LineQuality q1 = LineQuality.valueOf(s1);
+ LineQuality q2 = LineQuality.valueOf(s2);
+ if (trendNode!=null) trendNode.quality.textQuality = q1;
+ if (trendNode!=null) trendNode.quality.lineQuality = q2;
+
+ }
+
+ private void setInterval(long redrawInterval, long autoscaleInterval) {
+ redrawInterval = Math.max(1, redrawInterval);
+ long pulse = Math.min(50, redrawInterval);
+ pulse = Math.min(pulse, autoscaleInterval);
+ IHintContext h = canvas.getCanvasContext().getDefaultHintContext();
+ h.setHint(TimeParticipant.KEY_TIME_PULSE_INTERVAL, pulse);
+ h.setHint(TrendParticipant.KEY_TREND_DRAW_INTERVAL, redrawInterval);
+ h.setHint(TrendParticipant.KEY_TREND_AUTOSCALE_INTERVAL, autoscaleInterval);
+ }
+
+ private void setDrawSamples(boolean value) {
+ trendNode.drawSamples = value;
+ trendNode.layout();
+ tp.setDirty();
+ }
+
+ private void setTimeFormat( TimeFormat tf ) {
+ if (trendNode.timeFormat == tf) return;
+ trendNode.timeFormat = tf;
+ trendNode.layout();
+ tp.setDirty();
+ }
+
+ private void setValueFormat( ValueFormat vf ) {
+ if (trendNode.valueFormat == vf) return;
+ trendNode.valueFormat = vf;
+ trendNode.layout();
+ tp.setDirty();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T getAdapter(Class<T> adapter) {
+ if (adapter == INode.class) {
+ ICanvasContext ctx = cvsCtx;
+ if (ctx != null)
+ return (T) ctx.getSceneGraph();
+ }
+ if (adapter == IPropertyPage.class)
+ // TODO: pure e4 property page without sites
+ return (T) new StandardPropertyPage(null, getPropertyPageContexts());
+ if (adapter == ICanvasContext.class)
+ return (T) cvsCtx;
+ return null;
+ }
+
+ protected Set<String> getPropertyPageContexts() {
+ try {
+ return BrowseContext.getBrowseContextClosure(Simantics.getSession(), Collections.singleton(ROOT_PROPERTY_BROWSE_CONTEXT));
+ } catch (DatabaseException e) {
+ ExceptionUtils.logAndShowError("Failed to load modeled browse contexts for property page, see exception for details.", e);
+ return Collections.singleton(ROOT_PROPERTY_BROWSE_CONTEXT);
+ }
+ }
+
+ /**
+ * Add from, end, (scale x) to argument array
+ * @param fromEnd array of 2 or 3
+ */
+ public void getFromEnd(ChartLinkData data) {
+ data.sender = this;
+ TrendNode tn = trendNode;
+ data.valueTipTime = tn.valueTipTime;
+ HorizRuler hr = tn!=null ? tn.horizRuler : null;
+ if ( hr != null ) {
+ data.from = hr.from;
+ data.end = hr.end;
+ double len = hr.end-hr.from;
+ double wid = tn.plot.getWidth();
+ if ( wid==0.0 ) wid = 0.1;
+ data.sx = len/wid;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static boolean doubleEquals(double a, double b) {
+ if (Double.isNaN(a) && Double.isNaN(b)) return true;
+ return a==b;
+ }
+
+ protected void resetViewAfterDataChange() {
+
+ CanvasUtils.sendCommand(cvsCtx, Commands.CANCEL);
+ CanvasUtils.sendCommand(cvsCtx, Commands.AUTOSCALE);
+
+ }
+
+// private Resource getInputResource() {
+// return resource;
+// }
+}