[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