[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