[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