[education/rkward] /: Allow the callback in addChangeCommand to be a callable value, directly.

Thomas Friedrichsmeier <[email protected]>
Newsgroups gmane.comp.kde.cvs,gmane.comp.kde.doc
Message-ID <[email protected]>
Git commit f70df378a674db997ffa2bfc1887ca67b3236197 by Thomas Friedrichsmeier.
Committed on 07/05/2025 at 13:21.
Pushed by tfry into branch 'master'.

Allow the callback in addChangeCommand to be a callable value, directly.

M  +2    -0    ChangeLog
M  +14   -14   doc/rkwardplugins/index.docbook
M  +10   -11   rkward/plugins/00saveload/save/save.xml
M  +5    -8    rkward/plugins/data/generate_random.xml
M  +2    -4    rkward/plugins/data/recode_categorical.xml
M  +3    -6    rkward/plugins/data/sort.xml
M  +3    -6    rkward/plugins/data/sort2.xml
M  +9    -10   rkward/plugins/testing/test1.xml
M  +16   -24   rkward/scriptbackends/rkcomponentscripting.cpp
M  +1    -6    rkward/scriptbackends/rkcomponentscripting.h

https://invent.kde.org/education/rkward/-/commit/f70df378a674db997ffa2bfc1887ca67b3236197

diff --git a/ChangeLog b/ChangeLog
index 3d25dc454..613c02f98 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,6 @@
 --- Version 0.8.2 - UNRELEASED
+- Added: Plugins: Enhance addChangeCommand() to accept callable objects, directly
+- Added: Plugins: Simplify running R commands inside GUI logic code
 - Fixed: Crash on script errors in plugins
 - Fixed: Printing of captured R messages/warnings in plugins was broken
 - Internal: Code cleanups
diff --git a/doc/rkwardplugins/index.docbook b/doc/rkwardplugins/index.docbook
index 885c8cac2..3c3d33da8 100644
--- a/doc/rkwardplugins/index.docbook
+++ b/doc/rkwardplugins/index.docbook
@@ -58,8 +58,8 @@ as Authors, publish date, the abstract, and Keywords -->
      and in the FDL itself on how to use it. -->
 <legalnotice>&FDLNotice;</legalnotice>
 
-<date>2024-09-07</date>
-<releaseinfo>0.8.1</releaseinfo>
+<date>2025-05-03</date>
+<releaseinfo>0.8.2</releaseinfo>
 
 <abstract>
 <para>
@@ -919,16 +919,13 @@ R code.
 '
 	&lt;logic&gt;
 		&lt;script&gt;&lt;![CDATA[
-			// ECMAScript code in this block
-			// the top-level statement is only called once
-			gui.addChangeCommand ("mode.string", "modeChanged ()");
-
-			// this function is called whenever the "mode" was changed
-			modeChanged = function () {
-				var varmode = (gui.getString ("mode.string") == "variable");
-				gui.setValue ("y.enabled", varmode);
-				gui.setValue ("constant.enabled", !varmode);
-			}
+			// [...] any code at the top level is called only once
+			gui.addChangeCommand("mode.string", function() {
+				// while this anonymous function will be called, whenever "mode.string" changes
+				var varmode = (gui.getString("mode.string") == "variable");
+				gui.setValue("y.enabled", varmode);
+				gui.setValue("constant.enabled", !varmode);
+			});
 		]]&gt;&lt;/script&gt;
 	&lt;/logic&gt;
 
@@ -936,8 +933,11 @@ R code.
 	[...]
 	</programlisting>
 	<para>
[suppressed due to size limit]
[suppressed due to size limit]
 	</para>
+	<note><para>
+		Alternatively, <command>gui.addChangeCommmand()</command> accepts a string to be evaluated on changes, e.g. <replaceable>"my_update_function()"</replaceable>. This can be useful, if you want to call the same function for changes in several different UI elements.
+	</para></note>
 	<para>
 		The scripted approach to &GUI; logic becomes particularly useful when you want to change the available option according to the type of object that the user has selected. See <link linkend="guilogic_functions">the reference</link> for available functions.
 	</para>
@@ -4477,7 +4477,7 @@ different types, using modifiers may lead to errors. For <replaceable>fixed_valu
 	<varlistentry><term>getList(id)</term><listitem><para>Returns the value of the given child property as an array of strings (if possible). Returns the value of this property, if ID is omitted.</para></listitem></varlistentry>
 	<varlistentry><term>setValue(id, value)</term><listitem><para>Set the value of the given child property to <emphasis>value</emphasis>.</para></listitem></varlistentry>
 	<varlistentry><term>getChild(id)</term><listitem><para>Return an instance of the child-property with the given <emphasis>id</emphasis>.</para></listitem></varlistentry>
-	<varlistentry><term>addChangeCommand(id, command)</term><listitem><para>Execute <emphasis>command</emphasis> whenever the child property given by <emphasis>id</emphasis> changes.</para></listitem></varlistentry>
+	<varlistentry><term>addChangeCommand(id, command)</term><listitem><para>Execute <emphasis>command</emphasis> whenever the child property given by <emphasis>id</emphasis> changes. Command can either be a string to be evaluated, or callable value (usually a function).</para></listitem></varlistentry>
 	</variablelist></para></listitem>
 </varlistentry>
 <varlistentry><term>Class "RObject"</term>
diff --git a/rkward/plugins/00saveload/save/save.xml b/rkward/plugins/00saveload/save/save.xml
index 7110933f0..a2be7e2b7 100644
--- a/rkward/plugins/00saveload/save/save.xml
+++ b/rkward/plugins/00saveload/save/save.xml
@@ -17,17 +17,16 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<connect governor="lgc_compress" client="complevel.enabled" />
 		<connect governor="lgc_cmprssxz" client="xzextreme.enabled" />
 		<script>
-			<![CDATA[ gui.addChangeCommand("compress.string", "compressionChanged()");
-					// try to set compression level dynamically
-					// run each time the compression method is changed
-					compressionChanged = function(){
-							var thisObject = gui.getValue("compress.string");
-							 if(thisObject == "bzip2" | thisObject == "xz"){
-								gui.setValue("complevel.int", 9);
-							} else {
-								gui.setValue("complevel.int", 6);
-							}
-						} ]]>
+			<![CDATA[
+				gui.addChangeCommand("compress.string", function() {
+					var thisObject = gui.getValue("compress.string");
+					if(thisObject == "bzip2" | thisObject == "xz"){
+						gui.setValue("complevel.int", 9);
+					} else {
+						gui.setValue("complevel.int", 6);
+					}
+				})
+			]]>
 		</script>
 	</logic>
 	<dialog label="Save R objects">
diff --git a/rkward/plugins/data/generate_random.xml b/rkward/plugins/data/generate_random.xml
index 4eb25adbb..f90d8bf84 100644
--- a/rkward/plugins/data/generate_random.xml
+++ b/rkward/plugins/data/generate_random.xml
@@ -9,14 +9,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
 	<logic>
 		<connect governor="current_object" client="saveto.parent"/>
 		<script><![CDATA[
-			// the top-level block is called only once
-			gui.addChangeCommand ("saveto.parent", "parentChanged ()");
-
-			// this function is called on every change of the saveto's parent
-			parentChanged = function () {
-				parent_object = makeRObject (gui.getValue ("saveto.parent"));
-				gui.setValue ("length.enabled", !parent_object.isDataFrame ());
-			}
+			gui.addChangeCommand("saveto.parent", function() {
+				// this function is called on every change of the saveto's parent
+				parent_object = makeRObject(gui.getValue("saveto.parent"));
+				gui.setValue("length.enabled", !parent_object.isDataFrame());
+			});
 			]]></script>
 	</logic>
 	<dialog label="Generate random data (normal distribution)">
diff --git a/rkward/plugins/data/recode_categorical.xml b/rkward/plugins/data/recode_categorical.xml
index 2ce05e0db..0c46db6d3 100644
--- a/rkward/plugins/data/recode_categorical.xml
+++ b/rkward/plugins/data/recode_categorical.xml
@@ -18,12 +18,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<connect governor="other_values_custom" client="other_custom.enabled"/>
 
 		<script><![CDATA[
-			gui.addChangeCommand ("datamode", "update ()");
-
-			update = function () {
+			gui.addChangeCommand("datamode", function() {
 				var mode = gui.getValue ("datamode.string");
 				gui.setValue ("set.contents.quotation_note.visible", mode == "factor" || mode == "character");
-			}
+			});
 		]]></script>
 	</logic>
 	<dialog label="Recode categorical data"><tabbook>
diff --git a/rkward/plugins/data/sort.xml b/rkward/plugins/data/sort.xml
index 07d3c1138..14975c89a 100644
--- a/rkward/plugins/data/sort.xml
+++ b/rkward/plugins/data/sort.xml
@@ -22,11 +22,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<convert id="custom_conversion" mode="equals" sources="conversion.string" standard="custom"/>
 		<connect governor="custom_conversion" client="conversion_custom.enabled"/>
 		<script><![CDATA[
-			// the top-level block is called only once
-			gui.addChangeCommand ("object.available", "objectChanged ()");
-
-			// this function is called on every change of the saveto's parent
-			objectChanged = function () {
+			gui.addChangeCommand ("object.available", function () {
+				// this function is called on every change of the saveto's parent
 				object = makeRObject (gui.getValue ("object.available"));
 				gui.setValue ("sortby.enabled", object.isDataFrame ());
 				if (object.isDataFrame ()) gui.setValue ("selector.root", object.getName ());
@@ -38,7 +35,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 					// Not very elegant, but does the trick
 					gui.setValue ("notice.text", i18n ("Sorting this type of object is not supported in this plugin"));
 				}
-			}
+			});
 			]]></script>
 	</logic>
 
diff --git a/rkward/plugins/data/sort2.xml b/rkward/plugins/data/sort2.xml
index c0cd9b4c6..65faca967 100644
--- a/rkward/plugins/data/sort2.xml
+++ b/rkward/plugins/data/sort2.xml
@@ -17,11 +17,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<connect governor="saveto_other_object" client="saveto.enabled"/>
 		<connect governor="saveto_other_object" client="saveto.required"/>
 		<script><![CDATA[
-			// the top-level block is called only once
-			gui.addChangeCommand ("object.available", "objectChanged ()");
-
-			// this function is called on every change of the saveto's parent
-			objectChanged = function () {
+			gui.addChangeCommand ("object.available", function () {
+				// this function is called on every change of the saveto's parent
 				object = makeRObject (gui.getValue ("object.available"));
 				gui.setValue ("sortby_frame.enabled", object.isDataFrame ());
 				gui.setValue ("sortby.required", object.isDataFrame ());
@@ -32,7 +29,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 					// Not very elegant, but does the trick
 					gui.setValue ("notice.text", i18n ("Sorting this type of object is not supported in this plugin"));
 				}
-			}
+			});
 			]]></script>
 	</logic>
 	<dialog label="Sort data (Variant 2)">
diff --git a/rkward/plugins/testing/test1.xml b/rkward/plugins/testing/test1.xml
index 7ee93a068..dbcd047b5 100644
--- a/rkward/plugins/testing/test1.xml
+++ b/rkward/plugins/testing/test1.xml
@@ -11,15 +11,13 @@ SPDX-License-Identifier: GPL-2.0-or-later
 	<logic>
 		<script><![CDATA[
 			call_num = 0;
- 			last_command_id = -1;
 			gui.setValue ("text.text", i18n (noquote ("Select a dependent variable!")));
 
 			/*f = Kross.module('forms');
 			label = f.createWidget(scripty, 'QLabel', 'Label', {});
 			label.setText ('<b>This label was created by the script. Be sure to read the label above, too.</b>');*/
 
-			gui.addChangeCommand ("x.available", "updateText ()");
-			updateText = function () {
+			gui.addChangeCommand ("x.available", function() {
 				call_num += 1;
 				obj = makeRObject (gui.getValue ("x.available"));
 				text = "So, you think it's '" + obj.objectname + "'?\n";
@@ -27,12 +25,13 @@ SPDX-License-Identifier: GPL-2.0-or-later
 				text += "Updates of this text so far: " + call_num;
 				gui.setValue ("text.text", text);
 
-				if (obj.objectname != "") last_command_id = doRCommand ("levels (" + obj.objectname + ")", "levelsCommandFinished");
-			}
-			levelsCommandFinished = function (value, id) {
-				otext = gui.getValue ("text.text");
-				gui.setValue ("text.text", otext + "\n" + last_command_id + "-" + id + "\nadas" + value.join ("-"));
-			}
+				if (obj.objectname != "") last_command_id = new RCommand("levels (" + obj.objectname + ")", "levelscom").then(result => {
+					otext = gui.getValue ("text.text");
+					gui.setValue ("text.text", otext + "\n" + last_command_id + "-" + id + "\nadas" + value.join ("-"));
+				}).catch(err => {
+					// no error handling
+				});
+			});
 			]]></script>
 	</logic>
 
@@ -59,4 +58,4 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		</tabbook>
 	</dialog>
 
-</document>
\ No newline at end of file
+</document>
diff --git a/rkward/scriptbackends/rkcomponentscripting.cpp b/rkward/scriptbackends/rkcomponentscripting.cpp
index 7d71e65e6..e3b8a9fd2 100644
--- a/rkward/scriptbackends/rkcomponentscripting.cpp
+++ b/rkward/scriptbackends/rkcomponentscripting.cpp
@@ -92,24 +92,33 @@ void RKComponentScriptingProxy::evaluate(const QString &code, const QString &fil
 	handleScriptError(result, filename);
 }
 
-void RKComponentScriptingProxy::addChangeCommand(const QString &changed_id, const QString &command) {
+void RKComponentScriptingProxy::addChangeCommand(const QString &changed_id, const QJSValue &command) {
 	RK_TRACE(PHP);
 
 	QString remainder;
 	RKComponentBase *base = component->lookupComponent(changed_id, &remainder);
+	if (!remainder.isEmpty()) {
+		evaluate(QStringLiteral("error ('No such property %1 (failed portion was %2)');\n").arg(changed_id, remainder));
+		return;
+	}
 
-	if (remainder.isEmpty()) {
-		component_commands.insert(base, command);
-		if (base->isComponent()) {
-			connect(static_cast<RKComponent *>(base), &RKComponent::componentChanged, this, &RKComponentScriptingProxy::componentChanged);
+	auto callback = [this, command](RKComponentBase *) {
+		if (command.isCallable()) {
+			auto res = command.call();
+			handleScriptError(res);
 		} else {
-			connect(static_cast<RKComponentPropertyBase *>(base), &RKComponentPropertyBase::valueChanged, this, &RKComponentScriptingProxy::propertyChanged);
+			evaluate(command.toString());
 		}
+	};
+
+	if (base->isComponent()) {
+		connect(static_cast<RKComponent *>(base), &RKComponent::componentChanged, this, callback);
 	} else {
-		evaluate(QStringLiteral("error ('No such property %1 (failed portion was %2)');\n").arg(changed_id, remainder));
+		connect(static_cast<RKComponentPropertyBase *>(base), &RKComponentPropertyBase::valueChanged, this, callback);
 	}
 }
 
+// TODO: retire this function (how do we depracate js-level calls?
 QVariant RKComponentScriptingProxy::doRCommand(const QString &command, const QString &callback) {
 	RK_TRACE(PHP);
 
@@ -206,23 +215,6 @@ void RKComponentScriptingProxy::scriptRCommandFinished(RCommand *command) {
 	handleScriptError(res);
 }
 
-void RKComponentScriptingProxy::componentChanged(RKComponent *changed) {
-	RK_TRACE(PHP);
-	handleChange(changed);
-}
-
-void RKComponentScriptingProxy::propertyChanged(RKComponentPropertyBase *changed) {
-	RK_TRACE(PHP);
-	handleChange(changed);
-}
-
-void RKComponentScriptingProxy::handleChange(RKComponentBase *changed) {
-	RK_TRACE(PHP);
-
-	QString command = component_commands.value(changed);
-	evaluate(command);
-}
-
 QVariant RKComponentScriptingProxy::getValue(const QString &id) const {
 	RK_TRACE(PHP);
 	return (component->fetchValue(id, RKComponent::TraditionalValue));
diff --git a/rkward/scriptbackends/rkcomponentscripting.h b/rkward/scriptbackends/rkcomponentscripting.h
index 015bd90b3..e741befa5 100644
--- a/rkward/scriptbackends/rkcomponentscripting.h
+++ b/rkward/scriptbackends/rkcomponentscripting.h
@@ -32,14 +32,10 @@ class RKComponentScriptingProxy : public QObject {
 	~RKComponentScriptingProxy();
 
 	void initialize(const QString &file, const QString &command);
-  public Q_SLOTS:
-	void componentChanged(RKComponent *changed);
-	void propertyChanged(RKComponentPropertyBase *changed);
-
   public:
 	// these are meant to be called from the script
 	Q_INVOKABLE void include(const QString &filename);
-	Q_INVOKABLE void addChangeCommand(const QString &changed_id, const QString &command);
+	Q_INVOKABLE void addChangeCommand(const QString &changed_id, const QJSValue &command);
 	/** @returns id of the command issued. */
 	Q_INVOKABLE QVariant doRCommand(const QString &command, const QString &callback);
 	Q_INVOKABLE void doRCommand2(const QString &command, const QString &id, const QJSValue resolve, const QJSValue reject);
@@ -72,7 +68,6 @@ class RKComponentScriptingProxy : public QObject {
 	void evaluate(const QString &code, const QString &filename = QString());
 
 	void handleChange(RKComponentBase *changed);
-	QHash<RKComponentBase *, QString> component_commands;
 
 	void handleScriptError(const QJSValue &val, const QString &current_file = QString());
 };
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.