[education/rkward] /: API-mockup for rk.menu()

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


Git commit 5e337eafe79ac3217c585510616ba9b6215419f0 by Thomas Friedrichsmeier.
Committed on 04/08/2024 at 20:30.
Pushed by tfry into branch 'master'.

API-mockup for rk.menu()

M  +1    -1    VERSION.cmake
M  +29   -3    rkward/rbackend/rkrinterface.cpp
M  +3    -2    rkward/rbackend/rpackages/rkward/DESCRIPTION
M  +1    -0    rkward/rbackend/rpackages/rkward/NAMESPACE
A  +112  -0    rkward/rbackend/rpackages/rkward/R/rk.menu.R
M  +1    -1    rkward/rbackend/rpackages/rkward/man/DeviceOverrides.Rd
A  +60   -0    rkward/rbackend/rpackages/rkward/man/rk.menu.Rd
M  +0    -7    rkward/rbackend/rpackages/rkwardtests/man/rkwardtests-package.Rd

https://invent.kde.org/education/rkward/-/commit/5e337eafe79ac3217c585510616ba9b6215419f0

diff --git a/VERSION.cmake b/VERSION.cmake
index 64f36e67c..eee06b758 100644
--- a/VERSION.cmake
+++ b/VERSION.cmake
@@ -1,3 +1,3 @@
 # DO NOT CHANGE THIS FILE MANUALLY!
 # It will be overwritten by scripts/set_dist_version.sh
-SET(RKVERSION_NUMBER 0.8.0z+0.8.1+devel1)
+SET(RKVERSION_NUMBER 0.8.0z+0.8.1+devel3)
diff --git a/rkward/rbackend/rkrinterface.cpp b/rkward/rbackend/rkrinterface.cpp
index dde6812fc..6ee206cb6 100644
--- a/rkward/rbackend/rkrinterface.cpp
+++ b/rkward/rbackend/rkrinterface.cpp
@@ -46,9 +46,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <kmessagebox.h>
 #include <KLocalizedString>
 
-#include <qdir.h>
-#include <qvalidator.h>
-
+#include <QDir>
+#include <QDomElement>
+#include <QDomDocument>
 #include <stdlib.h>
 #include <QFileDialog>
 #include <QApplication>
@@ -800,6 +800,32 @@ GenericRRequestResult RInterface::processRCallRequest (const QString &call, cons
 		RKDebugHandler::instance ()->endDebug();
 	} else if (call == "switchLanguage") {
 		RKMessageCatalog::switchLanguage(arglist.value(0));
+	} else if (call == "menuupdate") {
+		QDomDocument doc;
+		std::function<void(QDomDocument &, QDomElement, const QVariantList &)> makeXml;
+		makeXml = [&makeXml](QDomDocument &doc, QDomElement e, const QVariantList &l) {
+			const auto id = l.value(0).toString();
+			const auto children = l.value(1).toList();
+			const auto label = l.value(2).toString();
+			const auto callable = l.value(3).toList().value(0).toBool();
+			auto s = doc.createElement(callable ? "Action" : "Menu");
+			s.setAttribute("name", id);
+			auto t = doc.createElement("Text");
+			t.appendChild(doc.createTextNode(label));
+			s.appendChild(t);
+			for (auto it = children.constBegin(); it != children.constEnd(); ++it) {
+				RK_ASSERT(!callable);
+				makeXml(doc, s, (*it).toList());
+			}
+			e.appendChild(s);
+		};
+		auto r = doc.createElement("kpartgui");
+		r.setAttribute("name", "rapi_menu");  // TODO: version attribute
+		auto mb = doc.createElement("MenuBar");
+		makeXml(doc, mb, args.toList());
+		r.appendChild(mb);
+		doc.appendChild(r);
+		qDebug("%s", qPrintable(doc.toString()));
 	} else {
 		return GenericRRequestResult::makeError(i18n("Error: unrecognized request '%1'", call));
 	}
diff --git a/rkward/rbackend/rpackages/rkward/DESCRIPTION b/rkward/rbackend/rpackages/rkward/DESCRIPTION
index adba1ce85..37452c80c 100755
--- a/rkward/rbackend/rpackages/rkward/DESCRIPTION
+++ b/rkward/rbackend/rpackages/rkward/DESCRIPTION
@@ -14,8 +14,8 @@ Encoding: UTF-8
 LazyLoad: yes
 Authors at R: c(person(given="Thomas", family="Friedrichsmeier", email="thomas.friedrichsmeier at kdemail.net", role=c("aut")), person(given="the RKWard", family="team",
                     email="rkward-devel at kde.org", role=c("cre","aut")))
-Version: 0.8.0
-Date: 2024-07-25
+Version: 0.8.1
+Date: 2024-08-04
 RoxygenNote: 7.3.2
 Collate: 
     'base_overrides.R'
@@ -32,6 +32,7 @@ Collate:
     'rk.edit-functions.R'
     'rk.filename-functions.R'
     'rk.label-functions.R'
+    'rk.menu.R'
     'rk.output.R'
     'rk.plugin-functions.R'
     'rk.print-functions.R'
diff --git a/rkward/rbackend/rpackages/rkward/NAMESPACE b/rkward/rbackend/rpackages/rkward/NAMESPACE
index de67aaafc..d1c5d71e8 100644
--- a/rkward/rbackend/rpackages/rkward/NAMESPACE
+++ b/rkward/rbackend/rpackages/rkward/NAMESPACE
@@ -84,6 +84,7 @@ export(rk.list.names)
 export(rk.list.plugins)
 export(rk.load.pluginmaps)
 export(rk.make.repos.string)
+export(rk.menu)
 export(rk.next.plot)
 export(rk.old.packages)
 export(rk.output)
diff --git a/rkward/rbackend/rpackages/rkward/R/rk.menu.R b/rkward/rbackend/rpackages/rkward/R/rk.menu.R
new file mode 100644
index 000000000..aa5490728
--- /dev/null
+++ b/rkward/rbackend/rpackages/rkward/R/rk.menu.R
@@ -0,0 +1,112 @@
+# - This file is part of the RKWard project (https://rkward.kde.org).
+# SPDX-FileCopyrightText: by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+# SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
+# SPDX-License-Identifier: GPL-2.0-or-later
+#' Class for adding menu items from R.
+#' @name rk.menu
+#'
+#' @description Allows to add (and subsequently remove) menu items associated to specific plain R functions in the RKWard main window.
+#'              Note that this mechanism is primarily targetted as users looking for an easy way to add their own customizations, or for bridiging to
+#'              third party UI packages. For anything more complex, and/or specifically targetted at RKWard, it is seriously recommended to create regular
+#'              RKWard plugins, for best UI consistency.
+#'
+#'              The reference class \code{rk.menu()} creates a handle for a menu item (which may either represent a submenu, or an action/leaf item). Handles
+#'              are identified by their "path", with is a character vector, with each element identifying a level of the menu-hierarchy. (Sub-)handles can be
+#'              created using the \code{$item()} method.
+#'
+#'              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.
+#'
+#'              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()}).
+#'
+#'              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.
+#'
+#' @param ... Path elements (character) given either as separate arguments, or as a multi-element character vector.
+#'
+#' @param label Label of the menu entry (character)
+#'
+#' @param func Function to call for leaf item
+#'
+#' @returns \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}
+#'
+#' @import methods
+#' @export rk.menu
+#'
+#' @examples
+#' \dontrun{
+#' x <- rk.menu()$item("analysis")                          # handle to the predefined analysis menu
+#' sub <- x$item("my_functions")$define("My Functions")     # create submenu
+#' a <- rk.menu()$item("analysis", "my_functions", "yeah")  # handle to an item in that submenu
+#' a <- sub$item("yeah")                                    # alternative variant for retrieving the above handle
+#' a$define("Print Yeah", function() { rk.print("Yeah!") }) # define leaf item
+#' a$call()                                                 # invoke, programmatically
+#' sub$remove()                                             # remove submenu, including the "yeah" action
+#' }
+rk.menu <- setRefClass("rk.menu",
+	fields=list(path="character"),  # for future expansion: context="character" for x11 / script / data functions
+	methods=list(item=function(...) {
+		"Return a child item of this item, given a relative path"
+		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."
+		x <- .retrieve(TRUE)
+		x$label <- label
+		if (!missing(func)) {
+			if (exists("children", envir=x)) stop(paste("Submenu", paste(path, collapse="/"), "cannot be redefined as a leaf item."))
+			x$fun <- func
+		}
+		.rk.call.async("menuupdate", rk.menu()$.list())
+		invisible(NULL)
+	},
+	call=function() {
+		"Call the function associated with this menu item"
+		x <- .retrieve(FALSE)
+		x$fun()
+	},
+	remove=function() {
+		"Remove any registered menu entry at this path from the menu"
+		parent <- rk.menu(path=path[1:length(path)-1])$.retrieve(FALSE)
+		rm(list=path[length(path)], envir=parent$children)
+		invisible(NULL)
+	},
+	enable=function(enable=TRUE, show=TRUE) {
+		"Disable and/or hide this menu item"
+		.retrieve(FALSE) # Check for existence
+		.rk.call.async("menuenable", list(path, isTRUE(enable), isTRUE(show)))
+		invisible(NULL)
+	},
+	.retrieve=function(create=FALSE) {
+		"Internal, do not use: Walk the tree of registered menu entries. If create is set to TRUE, missing paths are created, otherwise, missing paths are an error."
+		if (!exists("menuroot", envir=.rk.variables)) .rk.variables$menuroot <- new.env()
+		parent <- .rk.variables$menuroot
+		walkedpath <- NULL
+		for(p in path) {
+			walkedpath <- c(walkedpath, p)
+			if (!exists("children", envir=parent)) {
+				if (!create) stop(paste("Menu path", paste(walkedpath, collapse="/"), "is not defined"))
+				if (exists("fun", envir=parent)) stop(paste("Leaf menu item", paste(walkedpath, collapse="/"), "cannot be redefined as a submenu."))
+				parent$children <- list()
+			}
+			if (is.null(parent$children[[p]])) {
+				if (!create) stop(paste("Menu path", paste(walkedpath, collapse="/"), "is not defined"))
+				parent$children[[p]] <- new.env()
+			}
+			parent = parent$children[[p]]
+		}
+		parent
+	},
+	.list=function(x, id="") {
+		"Internal, do not use: Create nested list representation of the menu structure."
+		if (missing(x)) x <- .retrieve(FALSE)
+		ret <- list()
+		ret[[".ID"]] <- id
+		nms <- names(x$children)
+		ret[[".CHILDREN"]] <- lapply(nms, function(X) .list(x$children[[X]], X))
+		ret[[".LABEL"]] <- ifelse(is.null(x$label), "", x$label)
+		ret[[".FUN"]] <- ifelse(is.null(x$fun), "0", "1")
+		ret
+	})
+)
diff --git a/rkward/rbackend/rpackages/rkward/man/DeviceOverrides.Rd b/rkward/rbackend/rpackages/rkward/man/DeviceOverrides.Rd
index ee47975ab..261550396 100644
--- a/rkward/rbackend/rpackages/rkward/man/DeviceOverrides.Rd
+++ b/rkward/rbackend/rpackages/rkward/man/DeviceOverrides.Rd
@@ -131,7 +131,7 @@ quartz(
     \code{"nbcairo"} or \code{"dbcairo"}.  Only the first will be
     available if the system was compiled without support for
     cairographics.  The default is \code{"cairo"} where \R was built
-    using \code{pangocairo} (often not the case on macOS), otherwise
+    using \code{pangocairo} (so not usually on macOS), otherwise
     \code{"Xlib"}.}
 
 \item{antialias}{for cairo types, the type of anti-aliasing (if any)
diff --git a/rkward/rbackend/rpackages/rkward/man/rk.menu.Rd b/rkward/rbackend/rpackages/rkward/man/rk.menu.Rd
new file mode 100644
index 000000000..74d2f56c5
--- /dev/null
+++ b/rkward/rbackend/rpackages/rkward/man/rk.menu.Rd
@@ -0,0 +1,60 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/rk.menu.R
+\docType{class}
+\name{rk.menu}
+\alias{rk.menu}
+\title{Class for adding menu items from R.}
+\arguments{
+\item{...}{Path elements (character) given either as separate arguments, or as a multi-element character vector.}
+
+\item{label}{Label of the menu entry (character)}
+
+\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}
+}
+\description{
+Allows to add (and subsequently remove) menu items associated to specific plain R functions in the RKWard main window.
+             Note that this mechanism is primarily targetted as users looking for an easy way to add their own customizations, or for bridiging to
+             third party UI packages. For anything more complex, and/or specifically targetted at RKWard, it is seriously recommended to create regular
+             RKWard plugins, for best UI consistency.
+
+             The reference class \code{rk.menu()} creates a handle for a menu item (which may either represent a submenu, or an action/leaf item). Handles
+             are identified by their "path", with is a character vector, with each element identifying a level of the menu-hierarchy. (Sub-)handles can be
+             created using the \code{$item()} method.
+
+             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.
+
+             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()}).
+
+             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.
+}
+\section{Methods}{
+
+\describe{
+\item{\code{call()}}{Call the function associated with this menu item}
+
+\item{\code{define(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.}
+
+\item{\code{enable(enable = TRUE, show = TRUE)}}{Disable and/or hide this menu item}
+
+\item{\code{item(...)}}{Return a child item of this item, given a relative path}
+
+\item{\code{remove()}}{Remove any registered menu entry at this path from the menu}
+}}
+
+\examples{
+\dontrun{
+x <- rk.menu()$item("analysis")                          # handle to the predefined analysis menu
+sub <- x$item("my_functions")$define("My Functions")     # create submenu
+a <- rk.menu()$item("analysis", "my_functions", "yeah")  # handle to an item in that submenu
+a <- sub$item("yeah")                                    # alternative variant for retrieving the above handle
+a$define("Print Yeah", function() { rk.print("Yeah!") }) # define leaf item
+a$call()                                                 # invoke, programmatically
+sub$remove()                                             # remove submenu, including the "yeah" action
+}
+}
diff --git a/rkward/rbackend/rpackages/rkwardtests/man/rkwardtests-package.Rd b/rkward/rbackend/rpackages/rkwardtests/man/rkwardtests-package.Rd
index f158731dd..28531fd7d 100644
--- a/rkward/rbackend/rpackages/rkwardtests/man/rkwardtests-package.Rd
+++ b/rkward/rbackend/rpackages/rkwardtests/man/rkwardtests-package.Rd
@@ -22,13 +22,6 @@ License: \tab GPL (>= 2)\cr
 LazyLoad: \tab yes\cr
 URL: \tab https://rkward.kde.org\cr
 }
-}
-\seealso{
-Useful links:
-\itemize{
-  \item \url{https://rkward.kde.org}
-}
-
 }
 \author{
 Thomas Friedrichsmeier, Meik Michalke



More information about the rkward-tracker mailing list