[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