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

Reply via email to