[education/rkward] rkward: Pass context information to rk.menu()-actions

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


Git commit 3e3745ea9aa6fb49db6e1e3097d96b085c44a3cc by Thomas Friedrichsmeier.
Committed on 06/09/2024 at 07:10.
Pushed by tfry into branch 'master'.

Pass context information to rk.menu()-actions

M  +2    -1    rkward/dataeditor/rkeditordataframe.cpp
M  +17   -3    rkward/misc/rkrapimenu.cpp
M  +13   -13   rkward/plugin/rkstandardcomponent.cpp
M  +20   -6    rkward/rbackend/rpackages/rkward/R/rk.menu.R
M  +0    -1    rkward/windows/rkcommandeditorwindow.cpp
M  +2    -2    rkward/windows/rkmdiwindow.h

https://invent.kde.org/education/rkward/-/commit/3e3745ea9aa6fb49db6e1e3097d96b085c44a3cc

diff --git a/rkward/dataeditor/rkeditordataframe.cpp b/rkward/dataeditor/rkeditordataframe.cpp
index d4b2ea741..ff4af5527 100644
--- a/rkward/dataeditor/rkeditordataframe.cpp
+++ b/rkward/dataeditor/rkeditordataframe.cpp
@@ -30,7 +30,8 @@ RKEditorDataFrame::RKEditorDataFrame (RContainerObject* object, QWidget *parent)
 	RK_ASSERT (!object->isPending ());
 	RKEditor::object = object;
 	RK_ASSERT (object->isDataFrame ());
-	setGlobalContextProperty ("current_object", object->getFullName());
+	setGlobalContextProperty("current_object", object->getFullName());
+	setGlobalContextProperty("current_dataframe", object->getFullName());
 
 	RKVarEditDataFrameModel* model = new RKVarEditDataFrameModel (object, this);
 	initTable (model, object);
diff --git a/rkward/misc/rkrapimenu.cpp b/rkward/misc/rkrapimenu.cpp
index 704ff6f80..72c2dea4a 100644
--- a/rkward/misc/rkrapimenu.cpp
+++ b/rkward/misc/rkrapimenu.cpp
@@ -17,6 +17,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "../rbackend/rkrinterface.h"
 #include "../core/robject.h"
+#include "../windows/rkworkplace.h"
+#include "../windows/rkmdiwindow.h"
 #include "../rkward.h"
 
 #include "../debug.h"
@@ -55,8 +57,6 @@ static QDomElement addChildElement(QDomNode &parent, const QString &tagname, con
 
 void RKRApiMenu::makeAction(QDomElement e, const QString &full_id, const QString &label, QStringList *actionlist) {
 	auto s = addChildElement(e, QStringLiteral("Action"), full_id);
-	auto t = addChildElement(s, QStringLiteral("Text"), QString());
-	t.appendChild(s.ownerDocument().createTextNode(label));
 
 	auto a = action(full_id);
 	if (!a) {
@@ -70,7 +70,21 @@ void RKRApiMenu::makeAction(QDomElement e, const QString &full_id, const QString
 				if (i) path += ',';
 				path += RObject::rQuote(segments[i]);
 			}
-			RInterface::issueCommand(new RCommand("rk.menu()$item(" + path + ")$call()", RCommand::App));
+
+			QStringList args;
+			RKMDIWindow *w = RKWorkplace::mainWorkplace()->activeWindow(RKMDIWindow::AnyWindowState);
+			if (w) {
+				const auto props = w->globalContextProperties();
+				for (const auto [key,value] : props.asKeyValueRange()) {
+					if (key == "current_object" || key == "current_dataframe") { // TODO: find cleaner solution than this special casing
+						args.append(key + '=' + value);
+					} else {
+						args.append(key + '=' + RObject::rQuote(value));
+					}
+				}
+			}
+
+			RInterface::issueCommand(new RCommand("rk.menu()$item(" + path + ")$call(" + args.join(',') + ')', RCommand::App));
 		});
 	}
 	a->setText(label);
diff --git a/rkward/plugin/rkstandardcomponent.cpp b/rkward/plugin/rkstandardcomponent.cpp
index 35ce7c697..d6a0b3062 100644
--- a/rkward/plugin/rkstandardcomponent.cpp
+++ b/rkward/plugin/rkstandardcomponent.cpp
@@ -64,21 +64,21 @@ RKStandardComponent::RKStandardComponent (RKComponent *parent_component, QWidget
 	addChild ("code", code = new RKComponentPropertyCode (this, true));		// do not change this name!
 	code->setInternal (true);
 
-	RKComponentPropertyRObjects *current_object_property = new RKComponentPropertyRObjects (this, false);
-	RKComponentPropertyRObjects *current_dataframe_property = new RKComponentPropertyRObjects (this, false);
-	RKComponentPropertyBase *current_filename_property = new RKComponentPropertyBase (this, false);
-	current_object_property->setInternal(true);
-	current_dataframe_property->setInternal(true);
-	current_filename_property->setInternal(true);
-	RKMDIWindow *w = RKWorkplace::mainWorkplace ()->activeWindow (RKMDIWindow::AnyWindowState);
+	RKMDIWindow *w = RKWorkplace::mainWorkplace()->activeWindow(RKMDIWindow::AnyWindowState);
 	if (w) {
-		current_object_property->setValue (w->globalContextProperty ("current_object"));
-		current_filename_property->setValue (w->globalContextProperty ("current_filename"));
+		const auto props = w->globalContextProperties();
+		for (const auto [key, value] : props.asKeyValueRange()) {
+			RKComponentPropertyBase *prop;
+			if (key == "current_object" || key == "current_dataframe") { // TODO: find cleaner solution than this special casing
+				prop = new RKComponentPropertyRObjects(this, false);
+			} else {
+				prop = new RKComponentPropertyBase(this, false);
+			}
+			prop->setInternal(true);
+			prop->setValue(value);
+			addChild(key, prop);
+		}
 	}
-	if (current_object_property->objectValue () && current_object_property->objectValue ()->isDataFrame ()) current_dataframe_property->setObjectValue (current_object_property->objectValue ());
-	addChild ("current_object", current_object_property);
-	addChild ("current_dataframe", current_dataframe_property);
-	addChild ("current_filename", current_filename_property);
 
 	// open the main description file for parsing
 	XMLHelper* xml = getXmlHelper ();
diff --git a/rkward/rbackend/rpackages/rkward/R/rk.menu.R b/rkward/rbackend/rpackages/rkward/R/rk.menu.R
index d2067953c..094693726 100644
--- a/rkward/rbackend/rpackages/rkward/R/rk.menu.R
+++ b/rkward/rbackend/rpackages/rkward/R/rk.menu.R
@@ -16,8 +16,7 @@
 #'              items in the toolbar (nesting into submenus inside a toolbar is not currently supported).
 #'
 #'              To actually create a menu entry for a handle, the method \code{$define} needs to be called, specifying a label, and - for leaf items - and
-#'              associated R function. It is recommended that specified functions have an ignored \code{...} parameter. This will be unused, currently, but
-#'              might be useful for compatibility with future expansions.
+#'              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 certain elements, frequently, hiding them will be more efficient (see \code{$enable()}). Note: A disabled menu item
@@ -30,11 +29,26 @@
 #'
 #' @param label Label of the menu entry (character)
 #'
-#' @param func Function to call for leaf item
+#' @param func Function to call for leaf item. Depending on the context, this function will be called with certain named arguments, which you may or may not
+#'             want to handle (see section Details). It is strongly recommended for this R function to include an \code{...} parameter (generally unused),
+#'             for compatibility with future expansions. 
 #'
 #' @returns \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}
 #'
+#' @details The R function associated with a menu item will be called with the following (named, not positional) parameters:
+#'          \itemize{
+#'              \item \code{current_filename}: Specified if, and only if a script editor window is currently active: The filename of that script.
+#'              \item \code{current_object}: Specified if, and only if a data editor/viewer window is currently active: The R object being edited/views.
+#'                    To refer to the "name" of this object, use \code{deparse(substitute(current_object))} (see also \code{\link{rk.get.description}).
+#'                    Similarly, it is possible to assign to the given object using \code{eval(substitute(current_object <- value), envir=globalenv())}.
+#'                    It is strongly recommended, however, not to modify data, without asking for explicit confirmation by the user, first (see
+#'                    \code{\link{rk.ask.question}}).
+#'              \item \code{current_dataframe}: Identical to \code{current_object}, but provided only, if the current object is a \code{data.frame}.
+#'              \item \code{...}: To swallow unused argumens, and for compatibility with future extensions, you function should always accept a
+#'                    \code{...}-parameter.
+#'          }
+#'
 #' @import methods
 #' @export rk.menu
 #'
@@ -61,7 +75,7 @@ rk.menu <- setRefClass("rk.menu",
 		rk.menu(path=c(path, ...))
 	},
 	define=function(label, func) {
-		"(Re-)define the menu item at this path. If call is specified, this become a leaf item, associated with the given function, otherwise, a submenu is created."
+		"(Re-)define the menu item at this path. If call is specified, this becomes a leaf item, associated with the given function, otherwise, a submenu is created."
 		x <- .retrieve(TRUE)
 		x$label <- label
 		if (!missing(func)) {
@@ -71,10 +85,10 @@ rk.menu <- setRefClass("rk.menu",
 		.rk.call.async("menuupdate", rk.menu()$.list())
 		invisible(rk.menu(path=path))
 	},
-	call=function() {
+	call=function(...) {
 		"Call the function associated with this menu item"
 		x <- .retrieve(FALSE)
-		x$fun()
+		x$fun(...)
 	},
 	remove=function() {
 		"Remove any registered menu entry at this path from the menu"
diff --git a/rkward/windows/rkcommandeditorwindow.cpp b/rkward/windows/rkcommandeditorwindow.cpp
index e451eec18..b9fb22006 100644
--- a/rkward/windows/rkcommandeditorwindow.cpp
+++ b/rkward/windows/rkcommandeditorwindow.cpp
@@ -245,7 +245,6 @@ RKCommandEditorWindow::RKCommandEditorWindow (QWidget *parent, const QUrl &_url,
 	connect (m_doc, &KTextEditor::Document::documentSavedOrUploaded, this, &RKCommandEditorWindow::documentSaved);
 	layout->addWidget(preview_splitter);
 
-	setGlobalContextProperty("current_filename", m_doc->url().url());
 	connect (m_doc, &KTextEditor::Document::documentUrlChanged, this, &RKCommandEditorWindow::urlChanged);
 	connect (m_doc, &KTextEditor::Document::modifiedChanged, this, &RKCommandEditorWindow::updateCaption);                // of course most of the time this causes a redundant call to updateCaption. Not if a modification is undone, however.
 #ifdef __GNUC__
diff --git a/rkward/windows/rkmdiwindow.h b/rkward/windows/rkmdiwindow.h
index eb7ab9b13..d76ec4a96 100644
--- a/rkward/windows/rkmdiwindow.h
+++ b/rkward/windows/rkmdiwindow.h
@@ -120,8 +120,8 @@ is simply busy (e.g. when saving the current plot to history). */
 	bool isActiveInsideToplevelWindow ();
 /** Returns a pointer to an action collection suitable to place RKStandardAction in. This collection (and the corresponding KXMLGUIClient) is created on the fly. */
 	KActionCollection *standardActionCollection ();
-/** plugin-accessible properties of this object in the global context. Currently used only by RKEditorDataFrame to give information on the currently active data.frame. NOTE: ATM, you cannot set arbitrary properties. Only those supported in RKStandardComponent will have an effect. */
-	QString globalContextProperty (const QString& property) { return global_context_properties.value (property); };
+/** plugin-accessible properties of this object in the global context, if any (currently: current_filename, current_dataframe, current_object. */
+	QMap<QString, QString> globalContextProperties() { return global_context_properties; };
 /** @returns the save action applicable for this window (if any). Will be plugged into the save dropdown */
 	QAction* fileSaveAction () { return file_save_action; };
 /** @returns the save as action applicable for this window (if any). Will be plugged into the save dropdown */



More information about the rkward-tracker mailing list