1 /*******************************************************************************
2 * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.charts.ui;
14 import java.awt.Color;
16 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
17 import org.eclipse.core.runtime.preferences.InstanceScope;
18 import org.eclipse.jface.dialogs.Dialog;
19 import org.eclipse.jface.fieldassist.ControlDecoration;
20 import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
21 import org.eclipse.jface.layout.GridDataFactory;
22 import org.eclipse.jface.layout.GridLayoutFactory;
23 import org.eclipse.jface.preference.ColorSelector;
24 import org.eclipse.jface.util.IPropertyChangeListener;
25 import org.eclipse.jface.util.PropertyChangeEvent;
26 import org.eclipse.swt.SWT;
27 import org.eclipse.swt.events.ModifyEvent;
28 import org.eclipse.swt.events.ModifyListener;
29 import org.eclipse.swt.events.SelectionAdapter;
30 import org.eclipse.swt.events.SelectionEvent;
31 import org.eclipse.swt.graphics.Image;
32 import org.eclipse.swt.graphics.RGB;
33 import org.eclipse.swt.widgets.Button;
34 import org.eclipse.swt.widgets.Combo;
35 import org.eclipse.swt.widgets.Composite;
36 import org.eclipse.swt.widgets.Control;
37 import org.eclipse.swt.widgets.Group;
38 import org.eclipse.swt.widgets.Label;
39 import org.eclipse.swt.widgets.Shell;
40 import org.eclipse.swt.widgets.Spinner;
41 import org.eclipse.swt.widgets.Text;
42 import org.simantics.charts.preference.ChartPreferences;
43 import org.simantics.charts.query.ChartAndSubscriptionItemData;
44 import org.simantics.databoard.util.URIStringUtils;
45 import org.simantics.modeling.preferences.SubscriptionPreferences;
46 import org.simantics.trend.configuration.Scale;
47 import org.simantics.trend.configuration.TrendItem.DrawMode;
48 import org.simantics.trend.impl.JarisPaints;
51 * ChartAndSubscriptionItemDialog is a dialog that has the following property
53 * Chart name (Read only)
54 * Variable (Read only)
56 * Draw Mode (Enumeration)
57 * Scale, min, max ( Enum, int, int)
58 * Subscription ( Text )
59 * Deadband, Interval, Gain, Bias, Unit (Double)
60 * Stroke settings (width, color)
62 * If the dialog is opened in binary mode the content is more stripped.
69 * @author toni.kalajainen
71 public class ChartAndSubscriptionItemDialog extends Dialog {
73 private static final String MANUAL = "Manual";
74 private static final String AUTO = "Auto";
77 Label lChart, lRVI, lLabel, lDrawMode, lScale, lMin, lMax, lSubscription, lDeadband, lInterval, lGain, lBias, lUnit;
78 Text tRVI, tLabel, tDeadband, tInterval, tGain, tBias, tUnit;
82 // chart specific, not initialized, if includeChartSpecificProperties=false
83 Text tChart, tMin, tMax;
84 Combo cDrawMode, cScale;
86 ColorSelector colorSelector;
88 // Input error decorations
89 ControlDecoration tMinDecor;
90 ControlDecoration tMaxDecor;
91 ControlDecoration tDeadbandDecor;
92 ControlDecoration tIntervalDecor;
93 ControlDecoration tGainDecor;
94 ControlDecoration tBiasDecor;
97 ChartAndSubscriptionItemData baseData = new ChartAndSubscriptionItemData();
98 ChartAndSubscriptionItemData data;
100 final Runnable applyAction;
102 IEclipsePreferences chartnode; // chart specific, not initialized, if includeChartSpecificProperties=false
103 IEclipsePreferences subscriptionnode;
104 boolean includeChartSpecificProperties;
106 public ChartAndSubscriptionItemDialog(Shell parentShell, ChartAndSubscriptionItemData data, boolean includeChartSpecificProperties) {
107 this(parentShell, data, includeChartSpecificProperties, null);
110 public ChartAndSubscriptionItemDialog(Shell parentShell, ChartAndSubscriptionItemData data, boolean includeChartSpecificProperties, Runnable applyAction) {
113 this.baseData.readFrom(data);
114 this.includeChartSpecificProperties = includeChartSpecificProperties;
115 this.applyAction = applyAction;
117 if(includeChartSpecificProperties)
118 chartnode = InstanceScope.INSTANCE.getNode( ChartPreferences.P_NODE );
119 subscriptionnode = InstanceScope.INSTANCE.getNode( SubscriptionPreferences.P_NODE );
121 setShellStyle(SWT.RESIZE | SWT.TITLE | SWT.CLOSE | SWT.BORDER);
125 protected Control createContents(Composite parent) {
126 Control c = super.createContents(parent);
132 protected void createButtonsForButtonBar(Composite parent) {
133 if (applyAction != null)
134 createButton(parent, ChartDialogConstants.APPLY_ID, ChartDialogConstants.APPLY_LABEL, true);
135 super.createButtonsForButtonBar(parent);
138 public void setInput(ChartAndSubscriptionItemData data) {
139 // TODO: implement so that this reinitializes the UI of the dialog according to the new specified data
142 protected void disposeDialogAreaContents() {
143 Composite area = (Composite) dialogArea;
144 for (Control child : area.getChildren())
149 protected Control createDialogArea(Composite parent) {
150 Composite c = (Composite) super.createDialogArea(parent);
151 GridLayoutFactory.fillDefaults().margins(8, 8).numColumns(9).applyTo(c);
152 createDialogAreaContents(c);
156 protected void createDialogAreaContents(Composite c) {
157 GridDataFactory gd1 = GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(1, 1);
158 GridDataFactory gd2 = GridDataFactory.fillDefaults().grab(true, false).indent(5, 0).span(8, 1);
160 // 1st line - Chart: [chart name]
161 if(includeChartSpecificProperties) {
162 lChart = new Label(c, 0);
163 lChart.setText("Chart:");
165 tChart = new Text(c, SWT.BORDER | SWT.READ_ONLY);
166 //tChart.setEnabled( false );
167 if ( data.chartName != null ) tChart.setText( data.chartName );
171 // 2nd line - Variable Reference: [chart item label]
172 lRVI = new Label(c, 0);
173 lRVI.setText("Variable: ");
175 tRVI = new Text(c, SWT.BORDER | SWT.READ_ONLY);
176 //tRVI.setEnabled(false);
177 if ( data.variableReference!=null ) tRVI.setText(URIStringUtils.unescape( data.variableReference ));
180 // 3rd line - Label: [chart item label]
181 lLabel = new Label(c, 0);
182 lLabel.setText("&Label: ");
184 tLabel = new Text(c, SWT.BORDER);
185 if ( data.label!=null ) tLabel.setText(data.label);
188 // if (!data.binaryMode) {
189 // // 4th line - DrawMode: [ drop-down-list V ]
190 // lDrawMode = new Label(c, 0);
191 // lDrawMode.setText("DrawMode :");
192 // gd1.applyTo(lDrawMode);
193 // cDrawMode = new Combo(c, SWT.DROP_DOWN | SWT.BORDER | SWT.READ_ONLY);
194 // for (DrawMode dm : DrawMode.values()) cDrawMode.add( dm.name() );
195 // String _drawmode = data.drawmode!=null ? data.drawmode.toString() : chartnode.get( ChartPreferences.P_DRAWMODE, ChartPreferences.DEFAULT_DRAWMODE );
196 // cDrawMode.setText( _drawmode );
197 // gd2.applyTo(cDrawMode);
200 if (!data.binaryMode && includeChartSpecificProperties) {
201 // 5th line - Scale: [ Auto/Manual ] Min: [ ## ] Max: [ ## ]
202 lScale = new Label(c, 0);
203 lScale.setText("&Scale: ");
206 Composite scaleComposite = new Composite(c, 0);
207 gd2.applyTo(scaleComposite);
208 GridLayoutFactory.fillDefaults().numColumns(8).applyTo(scaleComposite);
210 cScale = new Combo(scaleComposite, SWT.DROP_DOWN | SWT.BORDER | SWT.READ_ONLY);
213 String _scale = data.scale != null ? data.scale.toString() : chartnode.get( ChartPreferences.P_SCALEMODE, ChartPreferences.DEFAULT_SCALEMODE );
214 cScale.setText( _scale );
215 GridDataFactory.fillDefaults().span(2, 1).applyTo(cScale);
217 lMin = new Label(scaleComposite, 0);
218 lMin.setText("&Min: ");
219 GridDataFactory.fillDefaults().span(1, 1).align(SWT.FILL, SWT.CENTER).applyTo(lMin);
220 tMin = new Text(scaleComposite, SWT.BORDER);
221 Double _min = data.min;
222 if ( _min == null ) _min = chartnode.getDouble( ChartPreferences.P_SCALE_MIN, 0.0);
223 tMin.setText( Double.toString(_min) );
224 GridDataFactory.fillDefaults().hint(80, SWT.DEFAULT).indent(5, 0).grab(true, false).span(2, 1).applyTo(tMin);
225 tMin.addModifyListener(new ModifyListener() {
227 public void modifyText(ModifyEvent e) {
228 data.min = parseDouble(tMin, false, 0.0);
229 if (data.min != null)
230 cScale.setText(MANUAL);
235 lMax = new Label(scaleComposite, 0);
236 lMax.setText("Ma&x: ");
237 GridDataFactory.fillDefaults().span(1, 1).align(SWT.FILL, SWT.CENTER).applyTo(lMax);
238 tMax = new Text(scaleComposite, SWT.BORDER);
239 Double _max = data.max;
240 if ( _max==null ) _max = chartnode.getDouble( ChartPreferences.P_SCALE_MAX, 100.0);
241 tMax.setText( Double.toString(_max) );
242 GridDataFactory.fillDefaults().hint(80, SWT.DEFAULT).indent(5, 0).grab(true, false).span(2, 1).applyTo(tMax);
243 tMax.addModifyListener(new ModifyListener() {
245 public void modifyText(ModifyEvent e) {
246 data.max = parseDouble(tMax, false, 100.0);
247 if (data.max != null)
248 cScale.setText(MANUAL);
254 // 6th line - Subscription: [ Default / others ] [New]
255 lSubscription = new Label(c, 0);
256 lSubscription.setText("S&ubscription: ");
257 gd1.applyTo(lSubscription);
258 cSubscription = new Combo(c, SWT.BORDER);
259 for (String s : data.subscriptions) cSubscription.add(s);
260 gd2.applyTo(cSubscription);
261 String _subscription = data.subscription;
262 if ( _subscription == null ) _subscription = subscriptionnode.get( SubscriptionPreferences.P_SUBSCRIPTION_DEFAULT, data.subscriptions.length==0 ? "Default" : cSubscription.getItem(0) );
263 cSubscription.setText( _subscription );
265 if (!data.binaryMode) {
266 // 7th line - Deadband: [deadband] Interval: [interval]
267 lDeadband = new Label(c, 0);
268 lDeadband.setText("&Deadband: ");
269 gd1.applyTo(lDeadband);
270 tDeadband = new Text(c, SWT.BORDER);
271 Double _deadband = data.deadband;
272 if ( _deadband == null ) _deadband = subscriptionnode.getDouble( SubscriptionPreferences.P_SUBSCRIPTION_DEADBAND, SubscriptionPreferences.DEFAULT_SUBSCRIPTION_DEADBAND );
273 tDeadband.setText( Double.toString(_deadband) );
274 tDeadband.setEditable(data.mutableCollectionSettings);
275 GridDataFactory.fillDefaults().hint(110, SWT.DEFAULT).indent(5, 0).span(3, 1).applyTo(tDeadband);
276 tDeadband.addModifyListener(new ModifyListener() {
278 public void modifyText(ModifyEvent e) {
279 data.deadband = parseDouble(tDeadband, false, 0.0);
284 lInterval = new Label(c, 0);
285 lInterval.setText("&Interval: ");
286 lInterval.setToolTipText("Sampling Interval");
287 GridDataFactory.fillDefaults().span(2, 1).applyTo(lInterval);
288 tInterval = new Text(c, SWT.BORDER);
289 Double _interval = data.interval;
290 if ( _interval == null ) _interval = subscriptionnode.getDouble( SubscriptionPreferences.P_SUBSCRIPTION_INTERVAL, SubscriptionPreferences.DEFAULT_SUBSCRIPTION_INTERVAL );
291 tInterval.setText( Double.toString(_interval) );
292 tInterval.setEditable(data.mutableCollectionSettings);
293 GridDataFactory.fillDefaults().hint(110, SWT.DEFAULT).indent(5, 0).span(3, 1).applyTo(tInterval);
294 tInterval.addModifyListener(new ModifyListener() {
296 public void modifyText(ModifyEvent e) {
297 data.interval = parseDouble(tInterval, false, 0.0);
303 // 8th line - Gain: [gain] Bias: [bias]
304 if (!data.binaryMode) {
305 lGain = new Label(c, 0);
306 lGain.setText("&Gain: ");
308 tGain = new Text(c, SWT.BORDER);
309 Double _gain = data.gain;
310 if (_gain == null) _gain = subscriptionnode.getDouble( SubscriptionPreferences.P_SUBSCRIPTION_GAIN, 1.0);
311 tGain.setText( Double.toString(_gain) );
312 GridDataFactory.fillDefaults().hint(110, SWT.DEFAULT).indent(5, 0).span(3, 1).applyTo(tGain);
313 tGain.addModifyListener(new ModifyListener() {
315 public void modifyText(ModifyEvent e) {
316 data.gain = parseDouble(tGain, false, 1.0);
321 lBias = new Label(c, 0);
322 lBias.setText("&Bias: ");
323 GridDataFactory.fillDefaults().span(2, 1).applyTo(lBias);
324 tBias = new Text(c, SWT.BORDER);
325 Double _bias = data.bias;
326 if (_bias == null) _bias = subscriptionnode.getDouble( SubscriptionPreferences.P_SUBSCRIPTION_BIAS, 0.0);
327 tBias.setText( Double.toString(_bias) );
328 GridDataFactory.fillDefaults().hint(110, SWT.DEFAULT).indent(5, 0).span(3, 1).applyTo(tBias);
329 tBias.addModifyListener(new ModifyListener() {
331 public void modifyText(ModifyEvent e) {
332 data.bias = parseDouble(tBias, false, 0.0);
338 // 9th line - Unit: [unit]
339 if (!data.binaryMode) {
340 lUnit = new Label(c, 0);
341 lUnit.setText("U&nit: ");
343 tUnit = new Text(c, SWT.BORDER);
344 String _unit = data.unit;
345 if (_unit==null) _unit = subscriptionnode.get( SubscriptionPreferences.P_SUBSCRIPTION_UNIT, "");
346 tUnit.setText( _unit );
347 GridDataFactory.fillDefaults().hint(100, SWT.DEFAULT).indent(5, 0).span(3, 1).applyTo(tUnit);
349 GridDataFactory.fillDefaults().span(5, 1).applyTo(new Label(c, 0));
352 // Disable fields that edit subscription item
353 if (!data.hasSubscriptionItem) {
354 tLabel.setEnabled( false );
355 cSubscription.setEnabled( false );
356 tDeadband.setEnabled( false );
357 tInterval.setEnabled( false );
358 tGain.setEnabled( false );
359 tBias.setEnabled( false );
360 tUnit.setEnabled( false );
363 // 10th line - Color stroke / color
364 if (includeChartSpecificProperties) {
365 createStrokeGroup(c);
368 saveAsPrefs = new Button(c, SWT.CHECK);
369 saveAsPrefs.setText("Sav&e as new defaults");
370 saveAsPrefs.setToolTipText("Sav&e these settings as new defaults");
371 // Always unchecked by default.
372 saveAsPrefs.setSelection(false);
373 GridDataFactory.fillDefaults().hint(100, SWT.DEFAULT).span(9, 1).applyTo(saveAsPrefs);
376 Image image = FieldDecorationRegistry.getDefault()
377 .getFieldDecoration(FieldDecorationRegistry.DEC_ERROR)
380 if (!data.binaryMode) {
381 if (includeChartSpecificProperties) {
382 tMinDecor = createDecoration(tMin, SWT.LEFT | SWT.CENTER, image);
383 tMaxDecor = createDecoration(tMax, SWT.LEFT | SWT.CENTER, image);
385 tDeadbandDecor = createDecoration(tDeadband, SWT.LEFT | SWT.CENTER, image);
386 tIntervalDecor = createDecoration(tInterval, SWT.LEFT | SWT.CENTER, image);
387 tGainDecor = createDecoration(tGain, SWT.LEFT | SWT.CENTER, image);
388 tBiasDecor = createDecoration(tBias, SWT.LEFT | SWT.CENTER, image);
391 // Ensure that the data structure contains all the values that were
392 // initialized into the UI.
398 private void createStrokeGroup(Composite c) {
399 GridDataFactory gd1 = GridDataFactory.fillDefaults().indent(10, 0).align(SWT.FILL, SWT.CENTER).span(1, 1);
401 Group gStroke = new Group(c, 0);
402 gStroke.setText("Trend Line Stroke");
403 GridLayoutFactory.fillDefaults().margins(8, 8).spacing(10, 10).numColumns(5).applyTo(gStroke);
404 GridDataFactory.fillDefaults().span(9, 1).applyTo(gStroke);
406 if (!data.binaryMode) {
408 Label lStrokeWidth = new Label(gStroke, 0);
409 lStrokeWidth.setText("&Width:");
410 gd1.applyTo(lStrokeWidth);
411 sStrokeWidth = new Spinner(gStroke, SWT.BORDER);
412 sStrokeWidth.setDigits(1);
413 sStrokeWidth.setIncrement(5);
414 sStrokeWidth.setMinimum(10);
415 sStrokeWidth.setMaximum(50);
416 GridDataFactory.fillDefaults().indent(10, 0).grab(false, false).span(1, 1).applyTo(sStrokeWidth);
418 if (data.strokeWidth != null) {
419 sStrokeWidth.setSelection((int) (data.strokeWidth * 10));
422 sStrokeWidth.addSelectionListener(new SelectionAdapter() {
424 public void widgetSelected(SelectionEvent e) {
425 float newWidth = ((float) sStrokeWidth.getSelection()) * 0.1f;
426 data.strokeWidth = newWidth;
431 Label lColor = new Label(gStroke, 0);
432 lColor.setText("&Color: ");
434 colorSelector = new ColorSelector(gStroke);
435 GridDataFactory.fillDefaults().indent(10, 0).span(1, 1).grab(false, false).applyTo(colorSelector.getButton());
436 final RGB indexColor = toRGB(JarisPaints.getColor(data.index));
437 RGB customColor = data.color != null ? toRGB(data.color) : indexColor;
438 colorSelector.setColorValue(customColor);
440 final Button bResetColor = new Button(gStroke, SWT.PUSH);
441 bResetColor.setText("Reset Color");
442 bResetColor.setToolTipText("Reset Color to Item Index Default");
443 bResetColor.setEnabled(!indexColor.equals(customColor));
445 colorSelector.addListener(new IPropertyChangeListener() {
447 public void propertyChange(PropertyChangeEvent event) {
448 RGB rgb = colorSelector.getColorValue();
449 data.color = new Color(rgb.red, rgb.green, rgb.blue).getColorComponents(new float[4]);
451 bResetColor.setEnabled(!indexColor.equals(rgb));
455 bResetColor.addSelectionListener(new SelectionAdapter() {
457 public void widgetSelected(SelectionEvent e) {
459 colorSelector.setColorValue(toRGB(JarisPaints.getColor(data.index)));
460 bResetColor.setEnabled(false);
465 protected RGB toRGB(float[] color) {
469 return new RGB( (int) (r*255+0.5), (int) (g*255+0.5), (int) (b*255+0.5) );
472 protected RGB toRGB(Color color) {
473 return new RGB(color.getRed(), color.getGreen(), color.getBlue());
476 protected ControlDecoration createDecoration(Control control, int position, Image image) {
477 ControlDecoration d = new ControlDecoration(control, position);
484 protected Double parseDouble(Text text, boolean returnDefault, double defaultValue) {
488 String s = text.getText();
489 s = s.replace(',', '.');
490 return Double.valueOf(s);
491 } catch ( NumberFormatException nfe ) {
492 return returnDefault ? defaultValue : null;
496 protected void parseUiToData() {
497 data.label = tLabel.getText();
498 if (!data.binaryMode) {
499 if (includeChartSpecificProperties) {
500 data.min = parseDouble(tMin, true, 0.0);
501 data.max = parseDouble(tMax, true, 100.0);
503 data.interval = parseDouble(tInterval, true, 0.0);
504 data.deadband = parseDouble(tDeadband, true, 0.0);
505 data.gain = parseDouble(tGain, true, 1.0);
506 data.bias = parseDouble(tBias, true, 0.0);
507 data.unit = tUnit.getText();
509 if (includeChartSpecificProperties) {
510 // data.drawmode = DrawMode.valueOf( cDrawMode.getText() );
511 data.drawmode = DrawMode.Line;
513 if (cScale.getText().equals("Manual")) {
514 data.scale = new Scale.Manual(data.min, data.max);
515 } else if (cScale.getText().equals("Auto")) {
516 data.scale = new Scale.Auto();
520 data.subscription = cSubscription.getText();
524 protected void buttonPressed(int buttonId) {
525 if (ChartDialogConstants.APPLY_ID == buttonId) {
528 super.buttonPressed(buttonId);
532 private void applyPressed() {
533 if (applyAction != null) {
535 if (!baseData.equals(data)) {
536 baseData.readFrom(data);
543 protected void okPressed() {
544 boolean savePrefs = saveAsPrefs.getSelection();
547 if (!data.binaryMode && savePrefs) {
548 if (includeChartSpecificProperties) {
549 chartnode.put( ChartPreferences.P_DRAWMODE, data.drawmode.name() );
550 chartnode.put( ChartPreferences.P_SCALEMODE, data.scale instanceof Scale.Manual ? "Manual" : "Auto" );
551 chartnode.putDouble( ChartPreferences.P_SCALE_MIN, data.min );
552 chartnode.putDouble( ChartPreferences.P_SCALE_MAX, data.max );
554 subscriptionnode.putDouble( SubscriptionPreferences.P_SUBSCRIPTION_DEADBAND, data.deadband );
555 subscriptionnode.putDouble( SubscriptionPreferences.P_SUBSCRIPTION_INTERVAL, data.interval );
556 subscriptionnode.putDouble( SubscriptionPreferences.P_SUBSCRIPTION_GAIN, data.gain );
557 subscriptionnode.putDouble( SubscriptionPreferences.P_SUBSCRIPTION_BIAS, data.bias );
558 subscriptionnode.put( SubscriptionPreferences.P_SUBSCRIPTION_UNIT, data.unit );
562 subscriptionnode.put( SubscriptionPreferences.P_SUBSCRIPTION_DEFAULT, data.subscription );
564 if (applyAction != null && !baseData.equals(data))
570 protected void configureShell(Shell newShell) {
571 super.configureShell(newShell);
573 includeChartSpecificProperties ? "Edit Chart Item"
574 : "Edit Subscription Item"
578 public void validate() {
580 if (!data.binaryMode) {
581 if (includeChartSpecificProperties) {
582 setDecoration(tMinDecor, null);
583 setDecoration(tMaxDecor, null);
584 if (tMin != null && tMax != null) {
585 if (data.min == null || data.max == null) {
587 if (data.min == null)
588 setDecoration(tMinDecor, "Invalid non-numeric value");
589 if (data.max == null)
590 setDecoration(tMaxDecor, "Invalid non-numeric value");
592 if (data.min >= data.max) {
593 setDecoration(tMinDecor, "Minimum value must be smaller than maximum value");
594 setDecoration(tMaxDecor, "Maximum value must be larger than minimum value");
601 ok &= data.interval != null && data.deadband != null && data.bias != null && data.gain != null;
602 setDecoration(tIntervalDecor, data.interval == null ? "Invalid non-numeric value" : null);
603 if (data.interval != null && data.interval < 0)
604 setDecoration(tIntervalDecor, "Sampling interval cannot be negative");
605 setDecoration(tDeadbandDecor, data.deadband == null ? "Invalid non-numeric value" : null);
606 if (data.deadband != null && data.deadband < 0)
607 setDecoration(tDeadbandDecor, "Deadband cannot be negative");
608 setDecoration(tGainDecor, data.gain == null ? "Invalid non-numeric value" : null);
609 setDecoration(tBiasDecor, data.bias == null ? "Invalid non-numeric value" : null);
614 private void setDecoration(ControlDecoration decor, String desc) {
618 decor.setDescriptionText(desc);
625 protected void setFinishable(boolean ok) {
626 getButton(OK).setEnabled(ok);