This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new 264259f34d Support variables in Filter Rows values (#7686)
264259f34d is described below
commit 264259f34da859b568c73f1c89d2fd7bb9b1c634
Author: Wassim <[email protected]>
AuthorDate: Thu Jul 30 11:47:54 2026 +0200
Support variables in Filter Rows values (#7686)
---
.../pipeline/transforms/filterrows/FilterRows.java | 22 ++-
.../transforms/filterrows/FilterRowsData.java | 2 +
.../transforms/filterrows/FilterRowsDialog.java | 2 +-
.../transforms/filterrows/FilterRowsTest.java | 154 +++++++++++++++++++++
.../hop/ui/core/dialog/EnterValueDialog.java | 81 +++++++++--
.../apache/hop/ui/core/widget/ConditionEditor.java | 16 ++-
6 files changed, 257 insertions(+), 20 deletions(-)
diff --git
a/plugins/transforms/filterrows/src/main/java/org/apache/hop/pipeline/transforms/filterrows/FilterRows.java
b/plugins/transforms/filterrows/src/main/java/org/apache/hop/pipeline/transforms/filterrows/FilterRows.java
index e2e15ff50c..c28a9adde6 100644
---
a/plugins/transforms/filterrows/src/main/java/org/apache/hop/pipeline/transforms/filterrows/FilterRows.java
+++
b/plugins/transforms/filterrows/src/main/java/org/apache/hop/pipeline/transforms/filterrows/FilterRows.java
@@ -18,6 +18,7 @@
package org.apache.hop.pipeline.transforms.filterrows;
import java.util.List;
+import org.apache.hop.core.Condition;
import org.apache.hop.core.Const;
import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.row.IRowMeta;
@@ -47,7 +48,7 @@ public class FilterRows extends BaseTransform<FilterRowsMeta,
FilterRowsData> {
private synchronized boolean keepRow(IRowMeta rowMeta, Object[] row) throws
HopException {
try {
- return meta.getCondition().evaluate(rowMeta, row);
+ return data.condition.evaluate(rowMeta, row);
} catch (Exception e) {
String message =
BaseMessages.getString(
@@ -160,9 +161,9 @@ public class FilterRows extends
BaseTransform<FilterRowsMeta, FilterRowsData> {
public boolean init() {
if (super.init()) {
- // could it be a better idea to have a clone on the condition in data
and do this on the first
- // row?
- meta.getCondition().clearFieldPositions();
+ data.condition = meta.getCondition().clone();
+ resolveVariables(data.condition);
+ data.condition.clearFieldPositions();
List<IStream> targetStreams =
meta.getTransformIOMeta().getTargetStreams();
data.trueTransformName = targetStreams.get(0).getTransformName();
@@ -176,6 +177,19 @@ public class FilterRows extends
BaseTransform<FilterRowsMeta, FilterRowsData> {
return false;
}
+ void resolveVariables(Condition condition) {
+ if (condition.isAtomic()) {
+ Condition.CValue rightValue = condition.getRightValue();
+ if (rightValue != null && rightValue.getText() != null) {
+ rightValue.setText(resolve(rightValue.getText()));
+ }
+ } else {
+ for (int i = 0; i < condition.nrConditions(); i++) {
+ resolveVariables(condition.getCondition(i));
+ }
+ }
+ }
+
protected void checkNonExistingFields() throws HopException {
List<String> orphanFields = meta.getOrphanFields(meta.getCondition(),
getInputRowMeta());
if (!Utils.isEmpty(orphanFields)) {
diff --git
a/plugins/transforms/filterrows/src/main/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsData.java
b/plugins/transforms/filterrows/src/main/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsData.java
index db3254a7ae..f5ba59c5aa 100644
---
a/plugins/transforms/filterrows/src/main/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsData.java
+++
b/plugins/transforms/filterrows/src/main/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsData.java
@@ -17,6 +17,7 @@
package org.apache.hop.pipeline.transforms.filterrows;
+import org.apache.hop.core.Condition;
import org.apache.hop.core.IRowSet;
import org.apache.hop.core.row.IRowMeta;
import org.apache.hop.pipeline.transform.BaseTransformData;
@@ -31,6 +32,7 @@ public class FilterRowsData extends BaseTransformData
implements ITransformData
public boolean chosesTargetTransforms;
public String trueTransformName;
public String falseTransformName;
+ public Condition condition;
public FilterRowsData() {
super();
diff --git
a/plugins/transforms/filterrows/src/main/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsDialog.java
b/plugins/transforms/filterrows/src/main/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsDialog.java
index 914fff903c..eb9dc881b3 100644
---
a/plugins/transforms/filterrows/src/main/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsDialog.java
+++
b/plugins/transforms/filterrows/src/main/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsDialog.java
@@ -151,7 +151,7 @@ public class FilterRowsDialog extends BaseTransformDialog {
ke);
}
- wCondition = new ConditionEditor(shell, SWT.BORDER, condition,
inputfields);
+ wCondition = new ConditionEditor(shell, SWT.BORDER, condition,
inputfields, variables);
FormData fdCondition = new FormData();
fdCondition.left = new FormAttachment(0, 0);
diff --git
a/plugins/transforms/filterrows/src/test/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsTest.java
b/plugins/transforms/filterrows/src/test/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsTest.java
new file mode 100644
index 0000000000..1c6a38ac13
--- /dev/null
+++
b/plugins/transforms/filterrows/src/test/java/org/apache/hop/pipeline/transforms/filterrows/FilterRowsTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.filterrows;
+
+import static org.apache.hop.core.Condition.Function.EQUAL;
+import static org.apache.hop.core.Condition.Function.REGEXP;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.stream.Stream;
+import org.apache.hop.core.Condition;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.ValueMetaAndData;
+import org.apache.hop.core.row.value.ValueMetaBigNumber;
+import org.apache.hop.core.row.value.ValueMetaBoolean;
+import org.apache.hop.core.row.value.ValueMetaDate;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.core.row.value.ValueMetaNumber;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.row.value.ValueMetaTimestamp;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.engines.local.LocalPipelineEngine;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class FilterRowsTest {
+
+ @BeforeAll
+ static void setUp() throws Exception {
+ HopEnvironment.init();
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("variableValueTypes")
+ void resolvesAndConvertsVariableForSupportedScalarTypes(
+ String type,
+ IValueMeta valueMeta,
+ Object matchingValue,
+ Object differentValue,
+ String variable)
+ throws Exception {
+ Condition.CValue constant =
+ new Condition.CValue(new ValueMetaAndData(valueMeta, matchingValue));
+ constant.setText("${FILTER_VALUE}");
+ Condition metadataCondition = new Condition();
+ metadataCondition.setLeftValueName("value");
+ metadataCondition.setFunction(EQUAL);
+ metadataCondition.setRightValue(constant);
+
+ FilterRows transform = createTransform(metadataCondition);
+ transform.setVariable("FILTER_VALUE", variable);
+ Condition runtimeCondition = metadataCondition.clone();
+ transform.resolveVariables(runtimeCondition);
+
+ RowMeta rowMeta = new RowMeta();
+ rowMeta.addValueMeta(valueMeta.clone());
+ assertEquals(variable, runtimeCondition.getRightValueString());
+ assertEquals("${FILTER_VALUE}", metadataCondition.getRightValueString());
+ assertTrue(runtimeCondition.evaluate(rowMeta, new Object[]
{matchingValue}));
+ assertFalse(runtimeCondition.evaluate(rowMeta, new Object[]
{differentValue}));
+ }
+
+ @Test
+ void resolvesVariablesInNestedConditionValuesWithoutChangingMetadata()
throws Exception {
+ Condition stringCondition =
+ new Condition("name", EQUAL, null, new ValueMetaAndData("constant",
"${NAME}"));
+ Condition regexCondition =
+ new Condition("code", REGEXP, null, new ValueMetaAndData("constant",
"${CODE_REGEX}"));
+ Condition condition = new Condition();
+ condition.addCondition(stringCondition);
+ condition.addCondition(regexCondition);
+
+ FilterRows transform = createTransform(condition);
+ transform.setVariable("NAME", "Alice");
+ transform.setVariable("CODE_REGEX", "A-[0-9]+");
+
+ Condition runtimeCondition = condition.clone();
+ transform.resolveVariables(runtimeCondition);
+
+ assertEquals("Alice",
runtimeCondition.getCondition(0).getRightValueString());
+ assertEquals("A-[0-9]+",
runtimeCondition.getCondition(1).getRightValueString());
+ assertEquals("${NAME}", condition.getCondition(0).getRightValueString());
+ assertEquals("${CODE_REGEX}",
condition.getCondition(1).getRightValueString());
+
+ RowMeta rowMeta = new RowMeta();
+ rowMeta.addValueMeta(new ValueMetaString("name"));
+ rowMeta.addValueMeta(new ValueMetaString("code"));
+ assertTrue(runtimeCondition.evaluate(rowMeta, new Object[] {"Alice",
"A-123"}));
+ assertFalse(runtimeCondition.evaluate(rowMeta, new Object[] {"Alice",
"B-123"}));
+ }
+
+ private static Stream<Arguments> variableValueTypes() throws Exception {
+ ValueMetaDate dateMeta = new ValueMetaDate("value");
+ dateMeta.setConversionMask("yyyy-MM-dd");
+ Date date = new SimpleDateFormat("yyyy-MM-dd").parse("2026-07-29");
+ Date otherDate = new SimpleDateFormat("yyyy-MM-dd").parse("2026-07-30");
+
+ ValueMetaTimestamp timestampMeta = new ValueMetaTimestamp("value");
+ timestampMeta.setConversionMask("yyyy-MM-dd HH:mm:ss.SSS");
+ Timestamp timestamp = Timestamp.valueOf("2026-07-29 12:34:56.789");
+ Timestamp otherTimestamp = Timestamp.valueOf("2026-07-29 12:34:57.789");
+
+ return Stream.of(
+ Arguments.of("String", new ValueMetaString("value"), "Alice", "Bob",
"Alice"),
+ Arguments.of("Integer", new ValueMetaInteger("value"), 42L, 43L, "42"),
+ Arguments.of("Number", new ValueMetaNumber("value"), 12.5D, 13.5D,
"12.5"),
+ Arguments.of(
+ "BigNumber",
+ new ValueMetaBigNumber("value"),
+ new BigDecimal("123456789.0123"),
+ new BigDecimal("123456789.0124"),
+ "123456789.0123"),
+ Arguments.of("Boolean", new ValueMetaBoolean("value"), true, false,
"Y"),
+ Arguments.of("Date", dateMeta, date, otherDate, "2026-07-29"),
+ Arguments.of(
+ "Timestamp", timestampMeta, timestamp, otherTimestamp, "2026-07-29
12:34:56.789"));
+ }
+
+ private static FilterRows createTransform(Condition condition) {
+ FilterRowsMeta meta = new FilterRowsMeta();
+ meta.setCondition(condition);
+ TransformMeta transformMeta = new TransformMeta("Filter rows", meta);
+ PipelineMeta pipelineMeta = new PipelineMeta();
+ pipelineMeta.addTransform(transformMeta);
+ return new FilterRows(
+ transformMeta, meta, new FilterRowsData(), 0, pipelineMeta, new
LocalPipelineEngine());
+ }
+}
diff --git
a/ui/src/main/java/org/apache/hop/ui/core/dialog/EnterValueDialog.java
b/ui/src/main/java/org/apache/hop/ui/core/dialog/EnterValueDialog.java
index 04f1f1e2ba..3f099951ab 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/dialog/EnterValueDialog.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/dialog/EnterValueDialog.java
@@ -24,10 +24,12 @@ import org.apache.hop.core.row.ValueMetaAndData;
import org.apache.hop.core.row.value.ValueMetaFactory;
import org.apache.hop.core.row.value.ValueMetaString;
import org.apache.hop.core.util.Utils;
+import org.apache.hop.core.variables.IVariables;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.ui.core.PropsUi;
import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.gui.WindowProperty;
+import org.apache.hop.ui.core.widget.TextVar;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
@@ -39,6 +41,7 @@ import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
@@ -51,6 +54,7 @@ public class EnterValueDialog extends Dialog {
private CCombo wValueType;
private Text wInputString;
+ private TextVar wInputStringVar;
private CCombo wFormat;
@@ -69,12 +73,20 @@ public class EnterValueDialog extends Dialog {
private Object valueData;
private boolean modalDialog;
+ private final IVariables variables;
+ private String inputString;
public EnterValueDialog(Shell parent, int style, IValueMeta value, Object
data) {
+ this(parent, style, value, data, null);
+ }
+
+ public EnterValueDialog(
+ Shell parent, int style, IValueMeta value, Object data, IVariables
variables) {
super(parent, style);
this.props = PropsUi.getInstance();
this.valueMeta = value;
this.valueData = data;
+ this.variables = variables;
}
public ValueMetaAndData open() {
@@ -129,13 +141,21 @@ public class EnterValueDialog extends Dialog {
fdlInputString.right = new FormAttachment(middle, -margin);
fdlInputString.top = new FormAttachment(wValueType, margin);
wlInputString.setLayoutData(fdlInputString);
- wInputString = new Text(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
- PropsUi.setLook(wInputString);
+ Control inputControl;
+ if (variables == null) {
+ wInputString = new Text(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
+ PropsUi.setLook(wInputString);
+ inputControl = wInputString;
+ } else {
+ wInputStringVar = new TextVar(variables, shell, SWT.SINGLE | SWT.LEFT |
SWT.BORDER);
+ PropsUi.setLook(wInputStringVar);
+ inputControl = wInputStringVar;
+ }
FormData fdInputString = new FormData();
fdInputString.left = new FormAttachment(middle, 0);
fdInputString.top = new FormAttachment(wValueType, margin);
fdInputString.right = new FormAttachment(100, -margin);
- wInputString.setLayoutData(fdInputString);
+ inputControl.setLayoutData(fdInputString);
// Format mask
Label wlFormat = new Label(shell, SWT.RIGHT);
@@ -144,14 +164,14 @@ public class EnterValueDialog extends Dialog {
FormData fdlFormat = new FormData();
fdlFormat.left = new FormAttachment(0, 0);
fdlFormat.right = new FormAttachment(middle, -margin);
- fdlFormat.top = new FormAttachment(wInputString, margin);
+ fdlFormat.top = new FormAttachment(inputControl, margin);
wlFormat.setLayoutData(fdlFormat);
wFormat = new CCombo(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
PropsUi.setLook(wFormat);
FormData fdFormat = new FormData();
fdFormat.left = new FormAttachment(middle, 0);
fdFormat.right = new FormAttachment(100, -margin);
- fdFormat.top = new FormAttachment(wInputString, margin);
+ fdFormat.top = new FormAttachment(inputControl, margin);
wFormat.setLayoutData(fdFormat);
// Length line
@@ -207,7 +227,7 @@ public class EnterValueDialog extends Dialog {
// If the user changes data type or if we type a text, we set the default
mask for the type
// We also set the list of possible masks in the wFormat
//
- wInputString.addFocusListener(
+ addInputFocusListener(
new FocusListener() {
@Override
public void focusGained(FocusEvent focusEvent) {
@@ -245,12 +265,12 @@ public class EnterValueDialog extends Dialog {
int formatIndex = wFormat.getSelectionIndex();
String formatString = formatIndex >= 0 ? wFormat.getItem(formatIndex) : "";
int type = ValueMetaFactory.getIdForValueMeta(wValueType.getText());
- String string = wInputString.getText();
+ String string = getInputText();
// remove white spaces if not a string field
if ((type != IValueMeta.TYPE_STRING) && (string.startsWith(" ") ||
string.endsWith(" "))) {
string = Const.trim(string);
- wInputString.setText(string);
+ setInputText(string);
}
switch (type) {
case IValueMeta.TYPE_INTEGER:
@@ -307,11 +327,11 @@ public class EnterValueDialog extends Dialog {
if (valueData != null) {
String value = valueMeta.getString(valueData);
if (value != null) {
- wInputString.setText(value);
+ setInputText(value);
}
}
} catch (HopValueException e) {
- wInputString.setText(valueMeta.toString());
+ setInputText(valueMeta.toString());
}
setFormats();
@@ -334,8 +354,7 @@ public class EnterValueDialog extends Dialog {
setFormats();
- wInputString.setFocus();
- wInputString.selectAll();
+ focusInput();
}
private void cancel() {
@@ -347,7 +366,8 @@ public class EnterValueDialog extends Dialog {
private ValueMetaAndData getValue(String valuename) throws HopValueException
{
try {
int valtype = ValueMetaFactory.getIdForValueMeta(wValueType.getText());
- ValueMetaAndData val = new ValueMetaAndData(valuename,
wInputString.getText());
+ String valueText = variables == null ? getInputText() :
variables.resolve(getInputText());
+ ValueMetaAndData val = new ValueMetaAndData(valuename, valueText);
IValueMeta valueMeta =
ValueMetaFactory.cloneValueMeta(val.getValueMeta(), valtype);
Object valueData = val.getValueData();
@@ -373,6 +393,7 @@ public class EnterValueDialog extends Dialog {
private void ok() {
try {
+ inputString = getInputText();
valueMetaAndData = getValue(valueMeta.getName()); // Keep the same
name...
dispose();
} catch (HopValueException e) {
@@ -399,6 +420,40 @@ public class EnterValueDialog extends Dialog {
}
}
+ public String getInputString() {
+ return inputString;
+ }
+
+ private String getInputText() {
+ return wInputStringVar == null ? wInputString.getText() :
wInputStringVar.getText();
+ }
+
+ private void setInputText(String text) {
+ if (wInputStringVar == null) {
+ wInputString.setText(text);
+ } else {
+ wInputStringVar.setText(text);
+ }
+ }
+
+ private void addInputFocusListener(FocusListener listener) {
+ if (wInputStringVar == null) {
+ wInputString.addFocusListener(listener);
+ } else {
+ wInputStringVar.addFocusListener(listener);
+ }
+ }
+
+ private void focusInput() {
+ if (wInputStringVar == null) {
+ wInputString.setFocus();
+ wInputString.selectAll();
+ } else {
+ wInputStringVar.setFocus();
+ wInputStringVar.selectAll();
+ }
+ }
+
/**
* @return the modalDialog
*/
diff --git
a/ui/src/main/java/org/apache/hop/ui/core/widget/ConditionEditor.java
b/ui/src/main/java/org/apache/hop/ui/core/widget/ConditionEditor.java
index 9d2e558506..c5d67e3809 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/widget/ConditionEditor.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/widget/ConditionEditor.java
@@ -32,6 +32,7 @@ import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.row.ValueMetaAndData;
import org.apache.hop.core.row.value.ValueMetaFactory;
import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.variables.IVariables;
import org.apache.hop.core.xml.XmlHandler;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.ui.core.dialog.EnterSelectionDialog;
@@ -130,6 +131,7 @@ public class ConditionEditor extends Canvas implements
MouseMoveListener {
private ArrayList<Condition> parents;
private IRowMeta fields;
+ private IVariables variables;
private int maxFieldLength;
@@ -144,12 +146,18 @@ public class ConditionEditor extends Canvas implements
MouseMoveListener {
private Menu mPop;
public ConditionEditor(Composite composite, int arg1, Condition co, IRowMeta
inputFields) {
+ this(composite, arg1, co, inputFields, null);
+ }
+
+ public ConditionEditor(
+ Composite composite, int arg1, Condition co, IRowMeta inputFields,
IVariables variables) {
super(composite, arg1 | SWT.NO_BACKGROUND | SWT.V_SCROLL | SWT.H_SCROLL);
widget = this;
this.activeCondition = co;
this.fields = inputFields;
+ this.variables = variables;
imageAdd = GuiResource.getInstance().getImage("ui/images/add.svg");
@@ -352,7 +360,7 @@ public class ConditionEditor extends Canvas implements
MouseMoveListener {
}
EnterValueDialog evd =
new EnterValueDialog(
- shell, SWT.NONE, v.createValueMeta(),
v.createValueData());
+ shell, SWT.NONE, v.createValueMeta(),
v.createValueData(), variables);
evd.setModalDialog(
true); // To keep the condition editor from being
closed with a value dialog
// still
@@ -360,7 +368,11 @@ public class ConditionEditor extends Canvas implements
MouseMoveListener {
ValueMetaAndData newval = evd.open();
if (newval != null) {
activeCondition.setRightValueName(null);
- activeCondition.setRightValue(new
Condition.CValue(newval));
+ Condition.CValue newValue = new Condition.CValue(newval);
+ if (evd.getInputString() != null) {
+ newValue.setText(evd.getInputString());
+ }
+ activeCondition.setRightValue(newValue);
setModified();
}
widget.redraw();