[education/rkward] /: Allow addChangeCommand to operate on arrays, and return command parameter, for convenience.

Thomas Friedrichsmeier null at kde.org
Thu May 8 20:17:20 BST 2025


Git commit d079f909c6a845e1fd8de5b9e01cade4ff20a2c0 by Thomas Friedrichsmeier.
Committed on 08/05/2025 at 18:31.
Pushed by tfry into branch 'master'.

Allow addChangeCommand to operate on arrays, and return command parameter, for convenience.

M  +1    -1    ChangeLog
M  +14   -6    doc/rkwardplugins/index.docbook
M  +2    -5    rkward/plugins/data/level_select.xml
M  +5    -6    rkward/plugins/data/limit_vector_length.xml
M  +4    -8    rkward/plugins/data/one_var_tabulation.xml
M  +2    -4    rkward/plugins/plots/barplot.xml
M  +2    -4    rkward/plugins/plots/dotchart.xml
M  +2    -4    rkward/plugins/plots/pareto.xml
M  +2    -4    rkward/plugins/plots/piechart.xml
M  +15   -12   rkward/scriptbackends/rkcomponentscripting.cpp
M  +1    -1    rkward/scriptbackends/rkcomponentscripting.h
M  +7    -2    rkward/scriptbackends/rkcomponentscripting.js

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

diff --git a/ChangeLog b/ChangeLog
index 613c02f98..b62a1734d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,5 @@
 --- Version 0.8.2 - UNRELEASED
-- Added: Plugins: Enhance addChangeCommand() to accept callable objects, directly
+- Added: Plugins: Enhance addChangeCommand() to allow writing more readable GUI logic code
 - 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
diff --git a/doc/rkwardplugins/index.docbook b/doc/rkwardplugins/index.docbook
index 7b1fa3041..240577508 100644
--- a/doc/rkwardplugins/index.docbook
+++ b/doc/rkwardplugins/index.docbook
@@ -942,8 +942,16 @@ R code.
[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>
+		If the same function should be invoked for changes in several elements, you can also pass an array of the respective <parameter>id=</parameter>s to <command>gui.addChangeCommmand()</command>. Further, for convenience, <command>gui.addChangeCommmand()</command> returns its second parameter, which
+		is useful, if you want to refer to this function elsewhere (e.g. to call it once during initialization). E.g.:
+	</para>
+	<programlisting>
+	let update = gui.addChangeCommand(["mode.string", "y.available"], function() {
+		// do something on each change of mode.string or y.available
+	});
+	update(); // do the same once during intialization
+	</programlisting>
+	</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>
@@ -1660,8 +1668,7 @@ This chapter contains information on some topics that are useful only to certain
 	<para>With this in mind, here is the general pattern. You will use this inside a <link linkend="logic_scripted">scripted UI logic</link> section:</para>
 	<programlisting>
 		<script><![CDATA[
-				gui.addChangeCommand ("variable", "update ()");
-				update = function () {
+				let update = gui.addChangeCommand ("variable", function () {
 					gui.setValue ("selector.enabled", 0);
 					variable = gui.getValue ("variable");
 					if (variable == "") return;
@@ -1675,7 +1682,7 @@ This chapter contains information on some topics that are useful only to certain
 						// if the command failed e.g.:
 						gui.setListValue ("selector.available", Array ("ERROR:", msg));
 					});
-				}
+				});
 		]]></script>
 	</programlisting>
 	<para>Here, <parameter>variable</parameter> is a property holding an object name (⪚ inside a <command><varslot></command>). Whenever that changes, you will want to update the display
@@ -4483,7 +4490,8 @@ 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. Command can either be a string to be evaluated, or callable value (usually a function).</para></listitem></varlistentry>
[suppressed due to size limit]
+	<para>The function returns the parameter <replaceable>command</replaceable>, for convenience (so you can e.g. assign it to a variable and/or call it during initialization).</para></listitem></varlistentry>
 	</variablelist></para></listitem>
 </varlistentry>
 <varlistentry><term>Class "RObject"</term>
diff --git a/rkward/plugins/data/level_select.xml b/rkward/plugins/data/level_select.xml
index 6741cb4d2..d95e994c2 100644
--- a/rkward/plugins/data/level_select.xml
+++ b/rkward/plugins/data/level_select.xml
@@ -21,10 +21,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<script><![CDATA[
 				gui.setValue ("limitnote.visible", false);
 
-				gui.addChangeCommand ("variable", "update ()");
-				gui.addChangeCommand ("limit", "update ()");
-				gui.addChangeCommand ("custom_expression", "update ()");
-				update = function () {
+				gui.addChangeCommand(["variable", "limit", "custom_expression"], function() {
 					gui.setValue ("selector.available", "");
 					gui.setValue ("selector.enabled", 0);
 
@@ -56,7 +53,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 						.catch(err => {
 							gui.setListValue("selector.available", Array("ERROR:", err));
 						});
-				}
+				});
 		]]></script>
 	</logic>
 	<dialog label="Level selector">
diff --git a/rkward/plugins/data/limit_vector_length.xml b/rkward/plugins/data/limit_vector_length.xml
index cff8fac8c..a582fd5fd 100644
--- a/rkward/plugins/data/limit_vector_length.xml
+++ b/rkward/plugins/data/limit_vector_length.xml
@@ -16,12 +16,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<connect client="custom_stat.enabled" governor="is_custom_stat"/>
 
 		<script><![CDATA[
-				gui.addChangeCommand ("sorting.string", "updateDescription ()");
-				gui.addChangeCommand ("cutoff.int", "updateDescription ()");
-				updateDescription = function () {
-					gui.setValue ("parameters", '"Limit"="' + gui.getValue ("cutoff.int") + ' ' + gui.getValue ("sorting.string") + ' values"');
-				}
-				updateDescription ();
+				updateDescription = function() {
+					gui.setValue("parameters", `"Limit"="${gui.getValue("cutoff.int")} ${gui.getValue("sorting.string")} values"`);
+				};
+				gui.addChangeCommand(["sorting.string", "cutoff.int"], updateDescription);
+				updateDescription();
 		]]></script>
 	</logic>
 	<dialog label="Limit Vector Length">
diff --git a/rkward/plugins/data/one_var_tabulation.xml b/rkward/plugins/data/one_var_tabulation.xml
index d78320a30..98ca983a1 100644
--- a/rkward/plugins/data/one_var_tabulation.xml
+++ b/rkward/plugins/data/one_var_tabulation.xml
@@ -21,10 +21,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<connect client="custom_stat.enabled" governor="is_custom"/>
 
 		<script><![CDATA[
-			gui.addChangeCommand ("stat.string", "updateFunLabel ()");
-			gui.addChangeCommand ("custom_stat.text", "updateFunLabel ()");
-			gui.addChangeCommand ("outcome.available", "updateFunLabel ()");
-			updateFunLabel = function () {
+			updateFunLabel = gui.addChangeCommand(["stat.string", "custom_stat.text", "outcome.available"], function() {
 				var stat = gui.getValue ("stat.string");
 				var label;
 				if (stat == "freq") {
@@ -39,12 +36,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
 				}
 				gui.setValue ("fun_label", label);
 				updateDescription ();
-			}
+			});
 
-			gui.addChangeCommand ("groups.available", "updateDescription ()");
-			updateDescription = function () {
+			updateDescription = gui.addChangeCommand("groups.available", function() {
 				gui.setValue ("parameters", i18n ("Tabulation groups") + '=paste (names (groups), collapse=' + i18nc ("Tabulate X by Y [by Z [...]]", " by ") + '), ' + i18n ("Tabulation statistic") + '=' + gui.getValue ("fun_label"));
-			}
+			});
 			updateFunLabel ();
 		]]></script>
 	</logic>
diff --git a/rkward/plugins/plots/barplot.xml b/rkward/plugins/plots/barplot.xml
index 8218c4dba..9f8075779 100644
--- a/rkward/plugins/plots/barplot.xml
+++ b/rkward/plugins/plots/barplot.xml
@@ -19,9 +19,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 			<connect client="x.enabled" governor="tabulate.checked.not"/>
 			<connect client="tabulate_options.varsource.selected" governor="vars.selected" />
 			<script><![CDATA[
-				gui.addChangeCommand ("tabulate.checked", "updateFunLabel ()");
-				gui.addChangeCommand ("tabulate_options.fun_label", "updateFunLabel ()");
-				updateFunLabel = function () {
+				updateFunLabel = gui.addChangeCommand(["tabulate.checked", "tabulate_options.fun_label"], function() {
 					if (gui.getValue ("tabulate.checked")) {
 						gui.setValue ("barplot_embed.plotoptions.default_ylab", gui.getValue ("tabulate_options.fun_label"));
 						gui.setValue ("barplot_embed.plotoptions.default_xlab", "title");
@@ -29,7 +27,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 						gui.setValue ("barplot_embed.plotoptions.default_ylab", "");
 						gui.setValue ("barplot_embed.plotoptions.default_xlab", "");
 					}
-				}
+				});
 				updateFunLabel ();
 			]]></script>
 		</logic>
diff --git a/rkward/plugins/plots/dotchart.xml b/rkward/plugins/plots/dotchart.xml
index 4e4f05479..b648eb9c7 100644
--- a/rkward/plugins/plots/dotchart.xml
+++ b/rkward/plugins/plots/dotchart.xml
@@ -22,9 +22,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<connect client="tabulate_options.varsource.selected" governor="vars.selected" />
 
 		<script><![CDATA[
-			gui.addChangeCommand ("tabulate.checked", "updateFunLabel ()");
-			gui.addChangeCommand ("tabulate_options.fun_label", "updateFunLabel ()");
-			updateFunLabel = function () {
+			updateFunLabel = gui.addChangeCommand(["tabulate.checked", "tabulate_options.fun_label"], function() {
 				if (gui.getValue ("tabulate.checked")) {
 					gui.setValue ("plotoptions.default_xlab", gui.getValue ("tabulate_options.fun_label"));
 					gui.setValue ("plotoptions.default_ylab", "title");
@@ -32,7 +30,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 					gui.setValue ("plotoptions.default_xlab", "");
 					gui.setValue ("plotoptions.default_ylab", "");
 				}
-			}
+			});
 			updateFunLabel ();
 		]]></script>
 	</logic>
diff --git a/rkward/plugins/plots/pareto.xml b/rkward/plugins/plots/pareto.xml
index dc3271e59..b6b7118b5 100644
--- a/rkward/plugins/plots/pareto.xml
+++ b/rkward/plugins/plots/pareto.xml
@@ -16,15 +16,13 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<connect client="x.enabled" governor="tabulate.checked.not"/>
 		<connect client="tabulate_options.varsource.selected" governor="vars.selected" />
 		<script><![CDATA[
-			gui.addChangeCommand ("tabulate.checked", "updateFunLabel ()");
-			gui.addChangeCommand ("tabulate_options.fun_label", "updateFunLabel ()");
-			updateFunLabel = function () {
+			updateFunLabel = gui.addChangeCommand(["tabulate.checked", "tabulate_options.fun_label"], function() {
 				if (gui.getValue ("tabulate.checked")) {
 					gui.setValue ("plotoptions.default_ylab", gui.getValue ("tabulate_options.fun_label"));
 				} else {
 					gui.setValue ("plotoptions.default_ylab", quote ("Frequency"));
 				}
-			}
+			});
 			updateFunLabel ();
 		]]></script>
 	</logic>
diff --git a/rkward/plugins/plots/piechart.xml b/rkward/plugins/plots/piechart.xml
index 4fae6856c..17a21f19f 100644
--- a/rkward/plugins/plots/piechart.xml
+++ b/rkward/plugins/plots/piechart.xml
@@ -27,9 +27,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<connect client="angle_inc.enabled" governor="density_0.not"/>
 
 		<script><![CDATA[
-			gui.addChangeCommand ("tabulate.checked", "updateFunLabel ()");
-			gui.addChangeCommand ("tabulate_options.fun_label", "updateFunLabel ()");
-			updateFunLabel = function () {
+			updateFunLabel = gui.addChangeCommand(["tabulate.checked", "tabulate_options.fun_label"], function() {
 				if (gui.getValue ("tabulate.checked")) {
 					gui.setValue ("plotoptions.default_main", "title");
 					gui.setValue ("plotoptions.default_sub", gui.getValue ("tabulate_options.fun_label"));
@@ -37,7 +35,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 					gui.setValue ("plotoptions.default_main", "");
 					gui.setValue ("plotoptions.default_sub", "");
 				}
-			}
+			});
 			updateFunLabel ();
 		]]></script>
 	</logic>
diff --git a/rkward/scriptbackends/rkcomponentscripting.cpp b/rkward/scriptbackends/rkcomponentscripting.cpp
index a65642d4a..57af6dd3b 100644
--- a/rkward/scriptbackends/rkcomponentscripting.cpp
+++ b/rkward/scriptbackends/rkcomponentscripting.cpp
@@ -92,16 +92,9 @@ void RKComponentScriptingProxy::evaluate(const QString &code, const QString &fil
 	handleScriptError(result, filename);
 }
 
-void RKComponentScriptingProxy::addChangeCommand(const QString &changed_id, const QJSValue &command) {
+void RKComponentScriptingProxy::addChangeCommand(const QStringList &changed_ids, 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;
-	}
-
 	auto callback = [this, command](RKComponentBase *) {
 		if (command.isCallable()) {
 			auto res = command.call();
@@ -111,10 +104,20 @@ void RKComponentScriptingProxy::addChangeCommand(const QString &changed_id, cons
 		}
 	};
 
-	if (base->isComponent()) {
-		connect(static_cast<RKComponent *>(base), &RKComponent::componentChanged, this, callback);
-	} else {
-		connect(static_cast<RKComponentPropertyBase *>(base), &RKComponentPropertyBase::valueChanged, this, callback);
+	for (const QString &changed_id : changed_ids) {
+		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 (base->isComponent()) {
+			connect(static_cast<RKComponent *>(base), &RKComponent::componentChanged, this, callback);
+		} else {
+			connect(static_cast<RKComponentPropertyBase *>(base), &RKComponentPropertyBase::valueChanged, this, callback);
+		}
 	}
 }
 
diff --git a/rkward/scriptbackends/rkcomponentscripting.h b/rkward/scriptbackends/rkcomponentscripting.h
index ba38739db..10b8ac5fd 100644
--- a/rkward/scriptbackends/rkcomponentscripting.h
+++ b/rkward/scriptbackends/rkcomponentscripting.h
@@ -36,7 +36,7 @@ class RKComponentScriptingProxy : public QObject {
   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 QJSValue &command);
+	Q_INVOKABLE void addChangeCommand(const QStringList &changed_ids, 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);
diff --git a/rkward/scriptbackends/rkcomponentscripting.js b/rkward/scriptbackends/rkcomponentscripting.js
index 720c63f83..92e42f069 100644
--- a/rkward/scriptbackends/rkcomponentscripting.js
+++ b/rkward/scriptbackends/rkcomponentscripting.js
@@ -60,8 +60,13 @@ function Component(id) {
 		return (new Component(this.absoluteId(id)));
 	};
 
-	this.addChangeCommand = function(id, command) {
-		_rkward.addChangeCommand(this.absoluteId(id), command);
+	this.addChangeCommand = function(ids, command) {
+		if (Array.isArray(ids)) {
+			_rkward.addChangeCommand(ids.map((id) => this.absoluteId(id)), command);
+		} else {
+			_rkward.addChangeCommand(Array(this.absoluteId(ids)), command);
+		}
+		return command;
 	};
 };
 



More information about the kde-doc-english mailing list