[education/rkward] /: Allow the callback in addChangeCommand to be a callable value, directly.
Thomas Friedrichsmeier
null at kde.org
Wed May 7 14:21:37 BST 2025
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.
'
<logic>
<script><![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);
+ });
]]></script>
</logic>
@@ -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 ¤t_file = QString());
};
More information about the kde-doc-english
mailing list