/******************************************************************************* * 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.ui; import java.awt.Color; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.jface.fieldassist.FieldDecorationRegistry; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.preference.ColorSelector; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Spinner; import org.eclipse.swt.widgets.Text; import org.simantics.charts.preference.ChartPreferences; import org.simantics.charts.query.ChartAndSubscriptionItemData; import org.simantics.databoard.util.URIStringUtils; import org.simantics.modeling.preferences.SubscriptionPreferences; import org.simantics.trend.configuration.Scale; import org.simantics.trend.configuration.TrendItem.DrawMode; import org.simantics.trend.impl.JarisPaints; /** * ChartAndSubscriptionItemDialog is a dialog that has the following property * queries: * Chart name (Read only) * Variable (Read only) * Label * Draw Mode (Enumeration) * Scale, min, max ( Enum, int, int) * Subscription ( Text ) * Deadband, Interval, Gain, Bias, Unit (Double) * Stroke settings (width, color) * * If the dialog is opened in binary mode the content is more stripped. * Chart name * Variable * Label * Subscription * Stroke settings * * @author toni.kalajainen */ public class ChartAndSubscriptionItemDialog extends Dialog { private static final String MANUAL = "Manual"; private static final String AUTO = "Auto"; // UI Label lChart, lRVI, lLabel, lDrawMode, lScale, lMin, lMax, lSubscription, lDeadband, lInterval, lGain, lBias, lUnit; Text tRVI, tLabel, tDeadband, tInterval, tGain, tBias, tUnit; Combo cSubscription; Button saveAsPrefs; // chart specific, not initialized, if includeChartSpecificProperties=false Text tChart, tMin, tMax; Combo cDrawMode, cScale; Spinner sStrokeWidth; ColorSelector colorSelector; // Input error decorations ControlDecoration tMinDecor; ControlDecoration tMaxDecor; ControlDecoration tDeadbandDecor; ControlDecoration tIntervalDecor; ControlDecoration tGainDecor; ControlDecoration tBiasDecor; // ChartAndSubscriptionItemData baseData = new ChartAndSubscriptionItemData(); ChartAndSubscriptionItemData data; final Runnable applyAction; IEclipsePreferences chartnode; // chart specific, not initialized, if includeChartSpecificProperties=false IEclipsePreferences subscriptionnode; boolean includeChartSpecificProperties; public ChartAndSubscriptionItemDialog(Shell parentShell, ChartAndSubscriptionItemData data, boolean includeChartSpecificProperties) { this(parentShell, data, includeChartSpecificProperties, null); } public ChartAndSubscriptionItemDialog(Shell parentShell, ChartAndSubscriptionItemData data, boolean includeChartSpecificProperties, Runnable applyAction) { super(parentShell); this.data = data; this.baseData.readFrom(data); this.includeChartSpecificProperties = includeChartSpecificProperties; this.applyAction = applyAction; if(includeChartSpecificProperties) chartnode = InstanceScope.INSTANCE.getNode( ChartPreferences.P_NODE ); subscriptionnode = InstanceScope.INSTANCE.getNode( SubscriptionPreferences.P_NODE ); setShellStyle(SWT.RESIZE | SWT.TITLE | SWT.CLOSE | SWT.BORDER); } @Override protected Control createContents(Composite parent) { Control c = super.createContents(parent); validate(); return c; } @Override protected void createButtonsForButtonBar(Composite parent) { if (applyAction != null) createButton(parent, ChartDialogConstants.APPLY_ID, ChartDialogConstants.APPLY_LABEL, true); super.createButtonsForButtonBar(parent); } public void setInput(ChartAndSubscriptionItemData data) { // TODO: implement so that this reinitializes the UI of the dialog according to the new specified data } protected void disposeDialogAreaContents() { Composite area = (Composite) dialogArea; for (Control child : area.getChildren()) child.dispose(); } @Override protected Control createDialogArea(Composite parent) { Composite c = (Composite) super.createDialogArea(parent); GridLayoutFactory.fillDefaults().margins(8, 8).numColumns(9).applyTo(c); createDialogAreaContents(c); return c; } protected void createDialogAreaContents(Composite c) { GridDataFactory gd1 = GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(1, 1); GridDataFactory gd2 = GridDataFactory.fillDefaults().grab(true, false).indent(5, 0).span(8, 1); // 1st line - Chart: [chart name] if(includeChartSpecificProperties) { lChart = new Label(c, 0); lChart.setText("Chart:"); gd1.applyTo(lChart); tChart = new Text(c, SWT.BORDER | SWT.READ_ONLY); //tChart.setEnabled( false ); if ( data.chartName != null ) tChart.setText( data.chartName ); gd2.applyTo(tChart); } // 2nd line - Variable Reference: [chart item label] lRVI = new Label(c, 0); lRVI.setText("Variable: "); gd1.applyTo(lRVI); tRVI = new Text(c, SWT.BORDER | SWT.READ_ONLY); //tRVI.setEnabled(false); if ( data.variableReference!=null ) tRVI.setText(URIStringUtils.unescape( data.variableReference )); gd2.applyTo(tRVI); // 3rd line - Label: [chart item label] lLabel = new Label(c, 0); lLabel.setText("&Label: "); gd1.applyTo(lLabel); tLabel = new Text(c, SWT.BORDER); if ( data.label!=null ) tLabel.setText(data.label); gd2.applyTo(tLabel); // if (!data.binaryMode) { // // 4th line - DrawMode: [ drop-down-list V ] // lDrawMode = new Label(c, 0); // lDrawMode.setText("DrawMode :"); // gd1.applyTo(lDrawMode); // cDrawMode = new Combo(c, SWT.DROP_DOWN | SWT.BORDER | SWT.READ_ONLY); // for (DrawMode dm : DrawMode.values()) cDrawMode.add( dm.name() ); // String _drawmode = data.drawmode!=null ? data.drawmode.toString() : chartnode.get( ChartPreferences.P_DRAWMODE, ChartPreferences.DEFAULT_DRAWMODE ); // cDrawMode.setText( _drawmode ); // gd2.applyTo(cDrawMode); // } if (!data.binaryMode && includeChartSpecificProperties) { // 5th line - Scale: [ Auto/Manual ] Min: [ ## ] Max: [ ## ] lScale = new Label(c, 0); lScale.setText("&Scale: "); gd1.applyTo(lScale); Composite scaleComposite = new Composite(c, 0); gd2.applyTo(scaleComposite); GridLayoutFactory.fillDefaults().numColumns(8).applyTo(scaleComposite); cScale = new Combo(scaleComposite, SWT.DROP_DOWN | SWT.BORDER | SWT.READ_ONLY); cScale.add(AUTO); cScale.add(MANUAL); String _scale = data.scale != null ? data.scale.toString() : chartnode.get( ChartPreferences.P_SCALEMODE, ChartPreferences.DEFAULT_SCALEMODE ); cScale.setText( _scale ); GridDataFactory.fillDefaults().span(2, 1).applyTo(cScale); lMin = new Label(scaleComposite, 0); lMin.setText("&Min: "); GridDataFactory.fillDefaults().span(1, 1).align(SWT.FILL, SWT.CENTER).applyTo(lMin); tMin = new Text(scaleComposite, SWT.BORDER); Double _min = data.min; if ( _min == null ) _min = chartnode.getDouble( ChartPreferences.P_SCALE_MIN, 0.0); tMin.setText( Double.toString(_min) ); GridDataFactory.fillDefaults().hint(80, SWT.DEFAULT).indent(5, 0).grab(true, false).span(2, 1).applyTo(tMin); tMin.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { data.min = parseDouble(tMin, false, 0.0); if (data.min != null) cScale.setText(MANUAL); validate(); } }); lMax = new Label(scaleComposite, 0); lMax.setText("Ma&x: "); GridDataFactory.fillDefaults().span(1, 1).align(SWT.FILL, SWT.CENTER).applyTo(lMax); tMax = new Text(scaleComposite, SWT.BORDER); Double _max = data.max; if ( _max==null ) _max = chartnode.getDouble( ChartPreferences.P_SCALE_MAX, 100.0); tMax.setText( Double.toString(_max) ); GridDataFactory.fillDefaults().hint(80, SWT.DEFAULT).indent(5, 0).grab(true, false).span(2, 1).applyTo(tMax); tMax.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { data.max = parseDouble(tMax, false, 100.0); if (data.max != null) cScale.setText(MANUAL); validate(); } }); } // 6th line - Subscription: [ Default / others ] [New] lSubscription = new Label(c, 0); lSubscription.setText("S&ubscription: "); gd1.applyTo(lSubscription); cSubscription = new Combo(c, SWT.BORDER); for (String s : data.subscriptions) cSubscription.add(s); gd2.applyTo(cSubscription); String _subscription = data.subscription; if ( _subscription == null ) _subscription = subscriptionnode.get( SubscriptionPreferences.P_SUBSCRIPTION_DEFAULT, data.subscriptions.length==0 ? "Default" : cSubscription.getItem(0) ); cSubscription.setText( _subscription ); if (!data.binaryMode) { // 7th line - Deadband: [deadband] Interval: [interval] lDeadband = new Label(c, 0); lDeadband.setText("&Deadband: "); gd1.applyTo(lDeadband); tDeadband = new Text(c, SWT.BORDER); Double _deadband = data.deadband; if ( _deadband == null ) _deadband = subscriptionnode.getDouble( SubscriptionPreferences.P_SUBSCRIPTION_DEADBAND, SubscriptionPreferences.DEFAULT_SUBSCRIPTION_DEADBAND ); tDeadband.setText( Double.toString(_deadband) ); tDeadband.setEditable(data.mutableCollectionSettings); GridDataFactory.fillDefaults().hint(110, SWT.DEFAULT).indent(5, 0).span(3, 1).applyTo(tDeadband); tDeadband.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { data.deadband = parseDouble(tDeadband, false, 0.0); validate(); } }); lInterval = new Label(c, 0); lInterval.setText("&Interval: "); lInterval.setToolTipText("Sampling Interval"); GridDataFactory.fillDefaults().span(2, 1).applyTo(lInterval); tInterval = new Text(c, SWT.BORDER); Double _interval = data.interval; if ( _interval == null ) _interval = subscriptionnode.getDouble( SubscriptionPreferences.P_SUBSCRIPTION_INTERVAL, SubscriptionPreferences.DEFAULT_SUBSCRIPTION_INTERVAL ); tInterval.setText( Double.toString(_interval) ); tInterval.setEditable(data.mutableCollectionSettings); GridDataFactory.fillDefaults().hint(110, SWT.DEFAULT).indent(5, 0).span(3, 1).applyTo(tInterval); tInterval.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { data.interval = parseDouble(tInterval, false, 0.0); validate(); } }); } // 8th line - Gain: [gain] Bias: [bias] if (!data.binaryMode) { lGain = new Label(c, 0); lGain.setText("&Gain: "); gd1.applyTo(lGain); tGain = new Text(c, SWT.BORDER); Double _gain = data.gain; if (_gain == null) _gain = subscriptionnode.getDouble( SubscriptionPreferences.P_SUBSCRIPTION_GAIN, 1.0); tGain.setText( Double.toString(_gain) ); GridDataFactory.fillDefaults().hint(110, SWT.DEFAULT).indent(5, 0).span(3, 1).applyTo(tGain); tGain.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { data.gain = parseDouble(tGain, false, 1.0); validate(); } }); lBias = new Label(c, 0); lBias.setText("&Bias: "); GridDataFactory.fillDefaults().span(2, 1).applyTo(lBias); tBias = new Text(c, SWT.BORDER); Double _bias = data.bias; if (_bias == null) _bias = subscriptionnode.getDouble( SubscriptionPreferences.P_SUBSCRIPTION_BIAS, 0.0); tBias.setText( Double.toString(_bias) ); GridDataFactory.fillDefaults().hint(110, SWT.DEFAULT).indent(5, 0).span(3, 1).applyTo(tBias); tBias.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { data.bias = parseDouble(tBias, false, 0.0); validate(); } }); } // 9th line - Unit: [unit] if (!data.binaryMode) { lUnit = new Label(c, 0); lUnit.setText("U&nit: "); gd1.applyTo(lUnit); tUnit = new Text(c, SWT.BORDER); String _unit = data.unit; if (_unit==null) _unit = subscriptionnode.get( SubscriptionPreferences.P_SUBSCRIPTION_UNIT, ""); tUnit.setText( _unit ); GridDataFactory.fillDefaults().hint(100, SWT.DEFAULT).indent(5, 0).span(3, 1).applyTo(tUnit); // Fill line GridDataFactory.fillDefaults().span(5, 1).applyTo(new Label(c, 0)); } // Disable fields that edit subscription item if (!data.hasSubscriptionItem) { tLabel.setEnabled( false ); cSubscription.setEnabled( false ); tDeadband.setEnabled( false ); tInterval.setEnabled( false ); tGain.setEnabled( false ); tBias.setEnabled( false ); tUnit.setEnabled( false ); } // 10th line - Color stroke / color if (includeChartSpecificProperties) { createStrokeGroup(c); } saveAsPrefs = new Button(c, SWT.CHECK); saveAsPrefs.setText("Sav&e as new defaults"); saveAsPrefs.setToolTipText("Sav&e these settings as new defaults"); // Always unchecked by default. saveAsPrefs.setSelection(false); GridDataFactory.fillDefaults().hint(100, SWT.DEFAULT).span(9, 1).applyTo(saveAsPrefs); // Error decorations Image image = FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_ERROR) .getImage(); if (!data.binaryMode) { if (includeChartSpecificProperties) { tMinDecor = createDecoration(tMin, SWT.LEFT | SWT.CENTER, image); tMaxDecor = createDecoration(tMax, SWT.LEFT | SWT.CENTER, image); } tDeadbandDecor = createDecoration(tDeadband, SWT.LEFT | SWT.CENTER, image); tIntervalDecor = createDecoration(tInterval, SWT.LEFT | SWT.CENTER, image); tGainDecor = createDecoration(tGain, SWT.LEFT | SWT.CENTER, image); tBiasDecor = createDecoration(tBias, SWT.LEFT | SWT.CENTER, image); } // Ensure that the data structure contains all the values that were // initialized into the UI. parseUiToData(); tLabel.setFocus(); } private void createStrokeGroup(Composite c) { GridDataFactory gd1 = GridDataFactory.fillDefaults().indent(10, 0).align(SWT.FILL, SWT.CENTER).span(1, 1); Group gStroke = new Group(c, 0); gStroke.setText("Trend Line Stroke"); GridLayoutFactory.fillDefaults().margins(8, 8).spacing(10, 10).numColumns(5).applyTo(gStroke); GridDataFactory.fillDefaults().span(9, 1).applyTo(gStroke); if (!data.binaryMode) { // Custom stroke Label lStrokeWidth = new Label(gStroke, 0); lStrokeWidth.setText("&Width:"); gd1.applyTo(lStrokeWidth); sStrokeWidth = new Spinner(gStroke, SWT.BORDER); sStrokeWidth.setDigits(1); sStrokeWidth.setIncrement(5); sStrokeWidth.setMinimum(10); sStrokeWidth.setMaximum(50); GridDataFactory.fillDefaults().indent(10, 0).grab(false, false).span(1, 1).applyTo(sStrokeWidth); if (data.strokeWidth != null) { sStrokeWidth.setSelection((int) (data.strokeWidth * 10)); } sStrokeWidth.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { float newWidth = ((float) sStrokeWidth.getSelection()) * 0.1f; data.strokeWidth = newWidth; } }); } Label lColor = new Label(gStroke, 0); lColor.setText("&Color: "); gd1.applyTo(lColor); colorSelector = new ColorSelector(gStroke); GridDataFactory.fillDefaults().indent(10, 0).span(1, 1).grab(false, false).applyTo(colorSelector.getButton()); final RGB indexColor = toRGB(JarisPaints.getColor(data.index)); RGB customColor = data.color != null ? toRGB(data.color) : indexColor; colorSelector.setColorValue(customColor); final Button bResetColor = new Button(gStroke, SWT.PUSH); bResetColor.setText("Reset Color"); bResetColor.setToolTipText("Reset Color to Item Index Default"); bResetColor.setEnabled(!indexColor.equals(customColor)); colorSelector.addListener(new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { RGB rgb = colorSelector.getColorValue(); data.color = new Color(rgb.red, rgb.green, rgb.blue).getColorComponents(new float[4]); data.color[3] = 1; bResetColor.setEnabled(!indexColor.equals(rgb)); } }); bResetColor.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { data.color = null; colorSelector.setColorValue(toRGB(JarisPaints.getColor(data.index))); bResetColor.setEnabled(false); } }); } protected RGB toRGB(float[] color) { float r = color[0]; float g = color[1]; float b = color[2]; return new RGB( (int) (r*255+0.5), (int) (g*255+0.5), (int) (b*255+0.5) ); } protected RGB toRGB(Color color) { return new RGB(color.getRed(), color.getGreen(), color.getBlue()); } protected ControlDecoration createDecoration(Control control, int position, Image image) { ControlDecoration d = new ControlDecoration(control, position); d.setMarginWidth(2); d.setImage(image); d.hide(); return d; } protected Double parseDouble(Text text, boolean returnDefault, double defaultValue) { if (text == null) return null; try { String s = text.getText(); s = s.replace(',', '.'); return Double.valueOf(s); } catch ( NumberFormatException nfe ) { return returnDefault ? defaultValue : null; } } protected void parseUiToData() { data.label = tLabel.getText(); if (!data.binaryMode) { if (includeChartSpecificProperties) { data.min = parseDouble(tMin, true, 0.0); data.max = parseDouble(tMax, true, 100.0); } data.interval = parseDouble(tInterval, true, 0.0); data.deadband = parseDouble(tDeadband, true, 0.0); data.gain = parseDouble(tGain, true, 1.0); data.bias = parseDouble(tBias, true, 0.0); data.unit = tUnit.getText(); if (includeChartSpecificProperties) { // data.drawmode = DrawMode.valueOf( cDrawMode.getText() ); data.drawmode = DrawMode.Line; if (cScale.getText().equals("Manual")) { data.scale = new Scale.Manual(data.min, data.max); } else if (cScale.getText().equals("Auto")) { data.scale = new Scale.Auto(); } } } data.subscription = cSubscription.getText(); } @Override protected void buttonPressed(int buttonId) { if (ChartDialogConstants.APPLY_ID == buttonId) { applyPressed(); } else { super.buttonPressed(buttonId); } } private void applyPressed() { if (applyAction != null) { parseUiToData(); if (!baseData.equals(data)) { baseData.readFrom(data); applyAction.run(); } } } @Override protected void okPressed() { boolean savePrefs = saveAsPrefs.getSelection(); parseUiToData(); if (!data.binaryMode && savePrefs) { if (includeChartSpecificProperties) { chartnode.put( ChartPreferences.P_DRAWMODE, data.drawmode.name() ); chartnode.put( ChartPreferences.P_SCALEMODE, data.scale instanceof Scale.Manual ? "Manual" : "Auto" ); chartnode.putDouble( ChartPreferences.P_SCALE_MIN, data.min ); chartnode.putDouble( ChartPreferences.P_SCALE_MAX, data.max ); } subscriptionnode.putDouble( SubscriptionPreferences.P_SUBSCRIPTION_DEADBAND, data.deadband ); subscriptionnode.putDouble( SubscriptionPreferences.P_SUBSCRIPTION_INTERVAL, data.interval ); subscriptionnode.putDouble( SubscriptionPreferences.P_SUBSCRIPTION_GAIN, data.gain ); subscriptionnode.putDouble( SubscriptionPreferences.P_SUBSCRIPTION_BIAS, data.bias ); subscriptionnode.put( SubscriptionPreferences.P_SUBSCRIPTION_UNIT, data.unit ); } if (savePrefs) subscriptionnode.put( SubscriptionPreferences.P_SUBSCRIPTION_DEFAULT, data.subscription ); if (applyAction != null && !baseData.equals(data)) applyAction.run(); super.okPressed(); } @Override protected void configureShell(Shell newShell) { super.configureShell(newShell); newShell.setText( includeChartSpecificProperties ? "Edit Chart Item" : "Edit Subscription Item" ); } public void validate() { boolean ok = true; if (!data.binaryMode) { if (includeChartSpecificProperties) { setDecoration(tMinDecor, null); setDecoration(tMaxDecor, null); if (tMin != null && tMax != null) { if (data.min == null || data.max == null) { ok = false; if (data.min == null) setDecoration(tMinDecor, "Invalid non-numeric value"); if (data.max == null) setDecoration(tMaxDecor, "Invalid non-numeric value"); } else { if (data.min >= data.max) { setDecoration(tMinDecor, "Minimum value must be smaller than maximum value"); setDecoration(tMaxDecor, "Maximum value must be larger than minimum value"); ok = false; } } } } ok &= data.interval != null && data.deadband != null && data.bias != null && data.gain != null; setDecoration(tIntervalDecor, data.interval == null ? "Invalid non-numeric value" : null); if (data.interval != null && data.interval < 0) setDecoration(tIntervalDecor, "Sampling interval cannot be negative"); setDecoration(tDeadbandDecor, data.deadband == null ? "Invalid non-numeric value" : null); if (data.deadband != null && data.deadband < 0) setDecoration(tDeadbandDecor, "Deadband cannot be negative"); setDecoration(tGainDecor, data.gain == null ? "Invalid non-numeric value" : null); setDecoration(tBiasDecor, data.bias == null ? "Invalid non-numeric value" : null); } setFinishable(ok); } private void setDecoration(ControlDecoration decor, String desc) { if (decor == null) return; if (desc != null) { decor.setDescriptionText(desc); decor.show(); } else { decor.hide(); } } protected void setFinishable(boolean ok) { getButton(OK).setEnabled(ok); } }