[education/rkward] rkward: Allow to create toolbar buttons. Some restructuring
Thomas Friedrichsmeier
null at kde.org
Sun Sep 8 20:42:17 BST 2024
Git commit 01075ca53c8d7706307c5e7f1ba411fdb244aa83 by Thomas Friedrichsmeier.
Committed on 10/08/2024 at 09:03.
Pushed by tfry into branch 'master'.
Allow to create toolbar buttons. Some restructuring
M +80 -43 rkward/misc/rkrapimenu.cpp
M +2 -1 rkward/misc/rkrapimenu.h
M +18 -10 rkward/rbackend/rpackages/rkward/R/rk.menu.R
M +18 -10 rkward/rbackend/rpackages/rkward/man/rk.menu.Rd
https://invent.kde.org/education/rkward/-/commit/01075ca53c8d7706307c5e7f1ba411fdb244aa83
diff --git a/rkward/misc/rkrapimenu.cpp b/rkward/misc/rkrapimenu.cpp
index e49b2b3d8..169100a71 100644
--- a/rkward/misc/rkrapimenu.cpp
+++ b/rkward/misc/rkrapimenu.cpp
@@ -29,43 +29,72 @@ RKRApiMenu::~RKRApiMenu() {
RK_TRACE(MISC);
}
-void RKRApiMenu::makeXML(QDomDocument &doc, QDomElement e, const QVariantList &l, const QString &path, QStringList *actionlist) {
- const auto id = l.value(0).toString();
- const QString full_id = path.isEmpty() ? id : path + ',' + id;
- 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", callable ? full_id : id);
- if (!label.isEmpty()) {
- 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(), full_id, actionlist);
+static QString getId(const QVariantList &l) {
+ return l.value(0).toString();
+}
+
+static QVariantList getChildlist(const QVariantList &l) {
+ return l.value(1).toList();
+}
+
+static QString getLabel(const QVariantList &l) {
+ return l.value(2).toString();
+}
+
+static bool getCallable(const QVariantList &l) {
+ return l.value(3).toList().value(0).toBool();
+}
+
+static QDomElement addChildElement(QDomNode &parent, const QString &tagname, const QString &nameattribute) {
+ QDomDocument doc = parent.isDocument() ? parent.toDocument() : parent.ownerDocument();
+ auto ret = doc.createElement(tagname);
+ if (!nameattribute.isNull()) ret.setAttribute(QStringLiteral("name"), nameattribute);
+ parent.appendChild(ret);
+ return ret;
+}
+
+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) {
+ a = new QAction();
+ a->setObjectName(full_id);
+ actionCollection()->addAction(full_id, a);
+ QObject::connect(a, &QAction::triggered, a, [full_id]() {
+ QString path;
+ auto segments = full_id.split(',');
+ for (int i = 0; i < segments.size(); ++i) {
+ if (i) path += ',';
+ path += RObject::rQuote(segments[i]);
+ }
+ RInterface::issueCommand(new RCommand("rk.menu()$item(" + path + ")$call()", RCommand::App));
+ });
}
- e.appendChild(s);
+ a->setText(label);
+ actionlist->append(full_id);
+}
+
+void RKRApiMenu::makeXML(QDomElement e, const QVariantList &l, const QString &path, QStringList *actionlist) {
+ const auto id = getId(l);
+ const QString full_id = path.isEmpty() ? id : path + ',' + id;
+ const auto label = getLabel(l);
+ const auto callable = getCallable(l);
if (callable) {
- auto a = action(full_id);
- if (!a) {
- a = new QAction();
- a->setObjectName(full_id);
- actionCollection()->addAction(full_id, a);
- QObject::connect(a, &QAction::triggered, a, [full_id]() {
- QString path;
- auto segments = full_id.split(',');
- for (int i = 0; i < segments.size(); ++i) {
- if (i) path += ',';
- path += RObject::rQuote(segments[i]);
- }
- RInterface::issueCommand(new RCommand("rk.menu()$item(" + path + ")$call()", RCommand::App));
- });
+ makeAction(e, full_id, label, actionlist);
+ } else {
+ auto s = addChildElement(e, QStringLiteral("Menu"), id);
+ if (!label.isEmpty()) {
+ auto t = addChildElement(s, QStringLiteral("Text"), QString());
+ t.appendChild(s.ownerDocument().createTextNode(label));
+ }
+ const auto children = getChildlist(l);
+ for (auto it = children.constBegin(); it != children.constEnd(); ++it) {
+ makeXML(s, (*it).toList(), full_id, actionlist);
}
- a->setText(label);
- actionlist->append(full_id);
}
}
@@ -73,19 +102,27 @@ void RKRApiMenu::commit() {
if (rep.isEmpty()) return;
RK_TRACE(MISC);
- auto f = RKWardMainWindow::getMain()->factory();
- f->removeClient(this);
QStringList actionlist;
- QDomDocument doc;
- auto menus = rep.value(1).toList();
- auto r = doc.createElement("kpartgui");
- r.setAttribute("name", "rapi_menu"); // TODO: version attribute
- auto mb = doc.createElement("MenuBar");
+ QDomDocument doc("kpartgui");
+ auto r = addChildElement(doc, QStringLiteral("kpartgui"), QStringLiteral("rapi_menu"));
+ auto mb = addChildElement(r, QStringLiteral("MenuBar"), QString());
+ const auto menus = getChildlist(rep);
for (auto it = menus.constBegin(); it != menus.constEnd(); ++it) {
- makeXML(doc, mb, (*it).toList(), QString(), &actionlist);
+ auto menu = (*it).toList();
+ if (getId((*it).toList()) == "toolbar") {
+ auto tb = addChildElement(r, QStringLiteral("ToolBar"), QStringLiteral("mainToolBar"));
+ const auto tb_children = getChildlist(menu);
+ for (auto tbit : tb_children) {
+ makeXML(tb, tbit.toList(), QStringLiteral("toolbar"), &actionlist);
+ }
+ } else {
+ makeXML(mb, menu, QString(), &actionlist);
+ }
}
- r.appendChild(mb);
- doc.appendChild(r);
+
+ auto f = RKWardMainWindow::getMain()->factory();
+ f->removeClient(this);
+ qDebug("%s", qPrintable(doc.toString()));
setXMLGUIBuildDocument(doc);
// delete any actions that are no longer around
diff --git a/rkward/misc/rkrapimenu.h b/rkward/misc/rkrapimenu.h
index 862e74749..ea3f916ab 100644
--- a/rkward/misc/rkrapimenu.h
+++ b/rkward/misc/rkrapimenu.h
@@ -20,7 +20,8 @@ public:
/** 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);
+ void makeXML(QDomElement e, const QVariantList &l, const QString &path, QStringList *actionlist);
+ void makeAction(QDomElement e, const QString &id, const QString &label, QStringList *actionlist);
QVariantList rep;
void commit();
};
diff --git a/rkward/rbackend/rpackages/rkward/R/rk.menu.R b/rkward/rbackend/rpackages/rkward/R/rk.menu.R
index 53e2b7552..d2067953c 100644
--- a/rkward/rbackend/rpackages/rkward/R/rk.menu.R
+++ b/rkward/rbackend/rpackages/rkward/R/rk.menu.R
@@ -12,13 +12,15 @@
#'
#' 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.
+#' created using the \code{$item()} method. The special string "toolbar", when used as the first item in the path places child
+#' 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.
+#' 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.
#'
#' 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()}). Note: A disabled menu item
+#' to remove and re-add certain 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
@@ -38,13 +40,19 @@
#'
#' @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
+#' 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
+#'
+#' # Create toolbar button. We use chaining, here to write everything in one statement
+#' btn <- rk.menu()$item("toolbar", "my_button")$define("Show", function(...) { rk.show.message("Clicked") })
+#'
+#' # clean up
+#' sub$remove() # remove submenu, including the "yeah" action
+#' btn$remove() # remove the toolbar button
#' }
rk.menu <- setRefClass("rk.menu",
fields=list(path="character"), # for future expansion: context="character" for x11 / script / data functions
diff --git a/rkward/rbackend/rpackages/rkward/man/rk.menu.Rd b/rkward/rbackend/rpackages/rkward/man/rk.menu.Rd
index 986c177a1..90546de6d 100644
--- a/rkward/rbackend/rpackages/rkward/man/rk.menu.Rd
+++ b/rkward/rbackend/rpackages/rkward/man/rk.menu.Rd
@@ -23,13 +23,15 @@ Allows to add (and subsequently remove) menu items associated to specific plain
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.
+ created using the \code{$item()} method. The special string "toolbar", when used as the first item in the path places child
+ 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.
+ 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.
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()}). Note: A disabled menu item
+ to remove and re-add certain 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
@@ -51,12 +53,18 @@ Allows to add (and subsequently remove) menu items associated to specific plain
\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
+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
+
+# Create toolbar button. We use chaining, here to write everything in one statement
+btn <- rk.menu()$item("toolbar", "my_button")$define("Show", function(...) { rk.show.message("Clicked") })
+
+# clean up
+sub$remove() # remove submenu, including the "yeah" action
+btn$remove() # remove the toolbar button
}
}
More information about the rkward-tracker
mailing list