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; }; };
