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