[education/rkward] rkward: Finish frontend implementaiton of rk.menu()

Thomas Friedrichsmeier null at kde.org
Sun Sep 8 20:42:17 BST 2024


Git commit f9a21d5c958204761a334f1570067760920c769e by Thomas Friedrichsmeier.
Committed on 08/08/2024 at 19:54.
Pushed by tfry into branch 'master'.

Finish frontend implementaiton of rk.menu()

M  +30   -0    rkward/autotests/core_test.cpp
M  +26   -1    rkward/misc/rkrapimenu.cpp
M  +5    -0    rkward/misc/rkrapimenu.h
M  +5    -0    rkward/rbackend/rkrinterface.cpp
M  +2    -1    rkward/rbackend/rpackages/rkward/R/rk.menu.R
M  +4    -2    rkward/rbackend/rpackages/rkward/man/rk.menu.Rd

https://invent.kde.org/education/rkward/-/commit/f9a21d5c958204761a334f1570067760920c769e

diff --git a/rkward/autotests/core_test.cpp b/rkward/autotests/core_test.cpp
index d836daade..645a40394 100644
--- a/rkward/autotests/core_test.cpp
+++ b/rkward/autotests/core_test.cpp
@@ -33,6 +33,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include "../misc/rkcommonfunctions.h"
 #include "../misc/rkcommandlineargs.h"
 #include "../misc/rkxmlguipreviewarea.h"
+#include "../misc/rkrapimenu.h"
 #include "../settings/rksettings.h"
 #include "../settings/rksettingsmodulekateplugins.h"
 #include "../windows/katepluginintegration.h"
@@ -530,6 +531,35 @@ private Q_SLOTS:
 		RKWardMainWindow::getMain()->slotCloseAllEditors();
 	}
 
+	void rkMenuTest() {
+		const QStringList actionpath {"analysis", "myaction"};
+		RInterface::issueCommand(new RCommand("a <- rk.menu()", RCommand::App));
+		for (int i = 0; i < actionpath.size(); ++i) {
+			RInterface::issueCommand(new RCommand("a <- a$item(" + RObject::rQuote(actionpath[i]) + ")", RCommand::App));
+		}
+		RInterface::issueCommand(new RCommand("a$define('My Label', function() assign('x', 'actionval', envir=globalenv()))", RCommand::User));
+		waitForAllFinished();
+		auto m = RKWardMainWindow::getMain()->rApiMenu();
+		auto a = m->actionByPath(actionpath);
+		QVERIFY(a);
+		QVERIFY(a && a->text()=="My Label");
+		QVERIFY(a && a->isEnabled());
+		if (a) a->trigger();
+		RInterface::issueCommand(new RCommand("a$enable(FALSE)", RCommand::User));
+		runCommandAsync(new RCommand("stopifnot(x == 'actionval')", RCommand::App), nullptr, [](RCommand *c) {
+			QVERIFY(c->succeeded());
+		});
+		waitForAllFinished();
+		auto b = m->actionByPath(actionpath);
+		QVERIFY(b);
+		QVERIFY(b == a); // existing action should have been reused
+		QVERIFY(b && (!b->isEnabled()));
+		RInterface::issueCommand(new RCommand("a$remove()", RCommand::App));
+		RInterface::issueCommand(new RCommand("rm(x)", RCommand::App));
+		waitForAllFinished();
+		QVERIFY(!m->actionByPath(actionpath));
+	}
+
 	void restartRBackend() {
 		RInterface::issueCommand(new RCommand("setwd(tempdir())", RCommand::User)); // retart used to fail, if in non-existant directory
 		RInterface::issueCommand(new RCommand("x <- 1", RCommand::User));
diff --git a/rkward/misc/rkrapimenu.cpp b/rkward/misc/rkrapimenu.cpp
index 8e8b9ee52..e49b2b3d8 100644
--- a/rkward/misc/rkrapimenu.cpp
+++ b/rkward/misc/rkrapimenu.cpp
@@ -10,6 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <QDomElement>
 #include <QDomDocument>
 #include <QAction>
+#include <QTimer>
 
 #include <KActionCollection>
 #include <KXMLGUIFactory>
@@ -68,7 +69,8 @@ void RKRApiMenu::makeXML(QDomDocument &doc, QDomElement e, const QVariantList &l
 	}
 }
 
-void RKRApiMenu::updateFromR(const QVariantList &rep) {
+void RKRApiMenu::commit() {
+	if (rep.isEmpty()) return;
 	RK_TRACE(MISC);
 
 	auto f = RKWardMainWindow::getMain()->factory();
@@ -95,4 +97,27 @@ void RKRApiMenu::updateFromR(const QVariantList &rep) {
 	}
 
 	f->addClient(this);
+	rep = QVariantList();
+}
+
+void RKRApiMenu::updateFromR(const QVariantList &_rep) {
+	RK_TRACE(MISC);
+
+	// Actual update is done debounced
+	if (rep.isEmpty()) QTimer::singleShot(100, factory(), [this](){ commit(); });
+	rep = _rep;
+}
+
+QAction *RKRApiMenu::actionByPath(const QStringList &path) {
+	commit(); // force commit before lookup
+	return action(path.join(','));
+}
+
+void RKRApiMenu::enableAction(const QStringList &path, bool enable, bool show) {
+	RK_TRACE(MISC);
+	auto a = actionByPath(path);
+	if (a) {
+		a->setEnabled(enable);
+		a->setVisible(show);
+	}
 }
diff --git a/rkward/misc/rkrapimenu.h b/rkward/misc/rkrapimenu.h
index 35bed97ab..862e74749 100644
--- a/rkward/misc/rkrapimenu.h
+++ b/rkward/misc/rkrapimenu.h
@@ -16,8 +16,13 @@ public:
 	RKRApiMenu();
 	~RKRApiMenu() override;
 	void updateFromR(const QVariantList &rep);
+	void enableAction(const QStringList &path, bool enable, bool show);
+	/** mostly for testing: Lookup action by path */
+	QAction* actionByPath(const QStringList &path);
 private:
 	void makeXML(QDomDocument &doc, QDomElement e, const QVariantList &l, const QString &path, QStringList *actionlist);
+	QVariantList rep;
+	void commit();
 };
 
 #endif
diff --git a/rkward/rbackend/rkrinterface.cpp b/rkward/rbackend/rkrinterface.cpp
index df445914c..5838e473f 100644
--- a/rkward/rbackend/rkrinterface.cpp
+++ b/rkward/rbackend/rkrinterface.cpp
@@ -801,6 +801,11 @@ GenericRRequestResult RInterface::processRCallRequest (const QString &call, cons
 		RKMessageCatalog::switchLanguage(arglist.value(0));
 	} else if (call == "menuupdate") {
 		RKWardMainWindow::getMain()->rApiMenu()->updateFromR(args.toList());
+	} else if (call == "menuenable") {
+		auto path = args.toList().value(0).toStringList();
+		auto enable = args.toList().value(1).toList().value(0).toBool(); // NOTE: bool value is a vector of ints, in R, therefore the toList(), first
+		auto show = args.toList().value(2).toList().value(0).toBool();
+		RKWardMainWindow::getMain()->rApiMenu()->enableAction(path, enable, show);
 	} else {
 		return GenericRRequestResult::makeError(i18n("Error: unrecognized request '%1'", call));
 	}
diff --git a/rkward/rbackend/rpackages/rkward/R/rk.menu.R b/rkward/rbackend/rpackages/rkward/R/rk.menu.R
index 118e3590b..53e2b7552 100644
--- a/rkward/rbackend/rpackages/rkward/R/rk.menu.R
+++ b/rkward/rbackend/rpackages/rkward/R/rk.menu.R
@@ -18,7 +18,8 @@
 #'              associated R function.
 #'
 #'              Adding/removing menu items is a fairly computation heavy exercise, internally, and is handled asynchronously, in the frontend. Should you need
-#'              to remove and re-add certainly elements, frequently, hiding them will be more efficient (see \code{$enable()}).
+#'              to remove and re-add certainly elements, frequently, hiding them will be more efficient (see \code{$enable()}). Note: A disabled menu item
+#'              can still be called programmatically, using \code{$call()}
 #'
 #'              This interface is still somewhat experimental, and currently kept to a minimal set of functions, deliberately. Please don't hesistate to give
 #'              us feedback on what you would like to see added. Only items defined using this mechanism can be manipulated / removed.
diff --git a/rkward/rbackend/rpackages/rkward/man/rk.menu.Rd b/rkward/rbackend/rpackages/rkward/man/rk.menu.Rd
index 74d2f56c5..986c177a1 100644
--- a/rkward/rbackend/rpackages/rkward/man/rk.menu.Rd
+++ b/rkward/rbackend/rpackages/rkward/man/rk.menu.Rd
@@ -12,7 +12,8 @@
 \item{func}{Function to call for leaf item}
 }
 \value{
-\code{rk.menu()} and \code{$item()} return a handle. \code{call()} passes on the return value of the associated function. The other methods return \code{NULL}
+\code{rk.menu()} and \code{$item()} return a handle. \code{$define() returns the handle it was given (to allow command chaining)}.
+         \code{call()} passes on the return value of the associated function. The other methods return \code{NULL}
 }
 \description{
 Allows to add (and subsequently remove) menu items associated to specific plain R functions in the RKWard main window.
@@ -28,7 +29,8 @@ Allows to add (and subsequently remove) menu items associated to specific plain
              associated R function.
 
              Adding/removing menu items is a fairly computation heavy exercise, internally, and is handled asynchronously, in the frontend. Should you need
-             to remove and re-add certainly elements, frequently, hiding them will be more efficient (see \code{$enable()}).
+             to remove and re-add certainly elements, frequently, hiding them will be more efficient (see \code{$enable()}). Note: A disabled menu item
+             can still be called programmatically, using \code{$call()}
 
              This interface is still somewhat experimental, and currently kept to a minimal set of functions, deliberately. Please don't hesistate to give
              us feedback on what you would like to see added. Only items defined using this mechanism can be manipulated / removed.



More information about the rkward-tracker mailing list