[education/rkward] rkward: Simplify adding a callback once a sequence of commands has finished.

Thomas Friedrichsmeier null at kde.org
Sat May 21 21:14:53 BST 2022


Git commit 6e2ca617136ce22ee2ab507036be4917fd49854d by Thomas Friedrichsmeier.
Committed on 21/05/2022 at 20:13.
Pushed by tfry into branch 'master'.

Simplify adding a callback once a sequence of commands has finished.

M  +3    -4    rkward/agents/rkloadagent.cpp
M  +3    -11   rkward/dataeditor/rkeditordataframe.cpp
M  +2    -5    rkward/dataeditor/rkeditordataframe.h
M  +4    -8    rkward/dialogs/rkloadlibsdialog.cpp
M  +1    -3    rkward/misc/rkoutputdirectory.cpp
M  +1    -3    rkward/misc/rkxmlguipreviewarea.cpp
M  +26   -19   rkward/rbackend/rkrinterface.h

https://invent.kde.org/education/rkward/commit/6e2ca617136ce22ee2ab507036be4917fd49854d

diff --git a/rkward/agents/rkloadagent.cpp b/rkward/agents/rkloadagent.cpp
index 62b56b68..494e6d0e 100644
--- a/rkward/agents/rkloadagent.cpp
+++ b/rkward/agents/rkloadagent.cpp
@@ -57,7 +57,7 @@ RKLoadAgent::RKLoadAgent (const QUrl &url, bool merge) {
 	}
 
 	command = new RCommand ("load (\"" + filename + "\")", RCommand::App | RCommand::ObjectListUpdate);
-	command->whenFinished(this, [this](RCommand* command){
+	command->whenFinished(this, [this](RCommand* command) {
 		if (command->failed()) {
 			KMessageBox::error(0, i18n("There has been an error opening file '%1':\n%2", RKWorkplace::mainWorkplace()->workspaceURL().path(), command->warnings() + command->error()), i18n("Error loading workspace"));
 			RKWorkplace::mainWorkplace()->setWorkspaceURL(QUrl());
@@ -69,15 +69,14 @@ RKLoadAgent::RKLoadAgent (const QUrl &url, bool merge) {
 				}
 			}
 		}
-		RCommand *c = new RCommand(QString(), RCommand::EmptyCommand | RCommand::App);
-		c->whenFinished(this, [this]() {
+
+		RInterface::whenAllFinished(this, [this]() {
 			RKWardMainWindow::getMain()->slotSetStatusReady();
 			RKWardMainWindow::getMain()->setWorkspaceMightBeModified(false);
 			RKOutputDirectory::getCurrentOutput();  // make sure some output file exists
 
 			deleteLater();
 		});
-		RInterface::issueCommand(c);
 		RKWardMainWindow::getMain ()->setCaption (QString ());	// trigger update of caption
 	});
 	RInterface::issueCommand (command);
diff --git a/rkward/dataeditor/rkeditordataframe.cpp b/rkward/dataeditor/rkeditordataframe.cpp
index e8069be5..ff03e059 100644
--- a/rkward/dataeditor/rkeditordataframe.cpp
+++ b/rkward/dataeditor/rkeditordataframe.cpp
@@ -1,6 +1,6 @@
 /*
 rkeditordataframe - This file is part of RKWard (https://rkward.kde.org). Created: Fri Aug 20 2004
-SPDX-FileCopyrightText: 2004-2010 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileCopyrightText: 2004-2022 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
 */
@@ -22,7 +22,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "../debug.h"
 
-#define LOAD_COMPLETE_COMMAND 1
 // warning! numbers above GET_DATA_OFFSET are used to determine, which row, the data should go to!
 #define GET_DATA_OFFSET 10
 
@@ -98,18 +97,11 @@ void RKEditorDataFrame::waitForLoad () {
 
 	enableEditing (false);
 
-	RCommand *command = new RCommand (QString (), RCommand::EmptyCommand | RCommand::Sync | RCommand::GetStringVector, QString (), this, LOAD_COMPLETE_COMMAND);
-	RInterface::issueCommand (command, open_chain);
-}
-
-void RKEditorDataFrame::rCommandDone (RCommand *command) {
-	RK_TRACE (EDITOR);
-
-	if (command->getFlags () == LOAD_COMPLETE_COMMAND) {
+	RInterface::whenAllFinished(this, [this]() {
 		RInterface::closeChain (open_chain);
 		open_chain = nullptr;
 		enableEditing (true);
-	}
+	}, open_chain);
 }
 
 void RKEditorDataFrame::restoreObject (RObject *object) {
diff --git a/rkward/dataeditor/rkeditordataframe.h b/rkward/dataeditor/rkeditordataframe.h
index 0bab6c22..963f4670 100644
--- a/rkward/dataeditor/rkeditordataframe.h
+++ b/rkward/dataeditor/rkeditordataframe.h
@@ -1,6 +1,6 @@
 /*
 rkeditordataframe - This file is part of the RKWard project. Created: Fri Aug 20 2004
-SPDX-FileCopyrightText: 2004-2006 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileCopyrightText: 2004-2022 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
 */
@@ -9,7 +9,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "rkeditor.h"
 #include "twintable.h"
-#include "../rbackend/rcommandreceiver.h"
 
 class TwinTable;
 class RCommand;
@@ -20,7 +19,7 @@ An RKEditor for data.frames.
 
 @author Thomas Friedrichsmeier
 */
-class RKEditorDataFrame : public TwinTable, public RCommandReceiver {
+class RKEditorDataFrame : public TwinTable {
 	Q_OBJECT
 public:
 /** constructor.
@@ -46,8 +45,6 @@ private:
 	void commonInit ();
 	RCommandChain *open_chain;
 	void waitForLoad ();
-protected:
-	void rCommandDone (RCommand *command) override;
 };
 
 #endif
diff --git a/rkward/dialogs/rkloadlibsdialog.cpp b/rkward/dialogs/rkloadlibsdialog.cpp
index 01cf987e..25ea9ca2 100644
--- a/rkward/dialogs/rkloadlibsdialog.cpp
+++ b/rkward/dialogs/rkloadlibsdialog.cpp
@@ -715,8 +715,7 @@ void InstallPackagesWidget::initialize () {
 	packages_status->clearStatus();
 	packages_status->initialize (parent->chain);
 
-	RCommand *dummy = new RCommand(QString(), RCommand::EmptyCommand); // dummy command will finish, after initialization is complete
-	connect(dummy->notifier(), &RCommandNotifier::commandFinished, this, [this]() {
+	RInterface::whenAllFinished(this, [this]() {
 		// Force a good width for the icon column, particularly for MacOS X.
 		packages_view->header ()->resizeSection (0, packages_view->sizeHintForIndex (model->index (0, 0, model->index (RKRPackageInstallationStatus::NewPackages, 0, QModelIndex ()))).width () + packages_view->indentation ());
 		for (int i = 1; i <= RKRPackageInstallationStatus::PackageName; ++i) {
@@ -729,8 +728,7 @@ void InstallPackagesWidget::initialize () {
 		window()->raise(); // needed on Mac, otherwise the dialog may go hiding behind the main app window, after the progress control window closes, for some reason
 		clearChanged();
 		updateStatus();
-	});
-	RInterface::issueCommand(dummy, parent->chain);
+	}, parent->chain);
 }
 
 void InstallPackagesWidget::rowClicked (const QModelIndex& row) {
@@ -754,8 +752,7 @@ void InstallPackagesWidget::filterChanged () {
 void InstallPackagesWidget::trySelectPackages (const QStringList &package_names) {
 	RK_TRACE (DIALOGS);
 
-	RCommand *dummy = new RCommand(QString(), RCommand::EmptyCommand); // dummy command will finish, after initialization is complete
-	connect(dummy->notifier(), &RCommandNotifier::commandFinished, this, [this, package_names]() {
+	RInterface::whenAllFinished(this, [this, package_names]() {
 		QStringList failed_names;
 		for (int i = 0; i < package_names.size(); ++i) {
 			QModelIndex index = packages_status->markPackageForInstallation(package_names[i]);
@@ -768,8 +765,7 @@ void InstallPackagesWidget::trySelectPackages (const QStringList &package_names)
 		if (!failed_names.isEmpty()) {
 			KMessageBox::sorry (0, i18n ("The following package(s) requested by the backend have not been found in the package repositories: \"%1\". Maybe the package name was mis-spelled. Or maybe you need to add additional repositories via the \"Configure Repositories\" button.", failed_names.join("\", \"")), i18n ("Package not available"));
 		}
-	});
-	RInterface::issueCommand(dummy, parent->chain);
+	}, parent->chain);
 }
 
 void InstallPackagesWidget::markAllUpdates () {
diff --git a/rkward/misc/rkoutputdirectory.cpp b/rkward/misc/rkoutputdirectory.cpp
index efc3ccc2..8de7d777 100644
--- a/rkward/misc/rkoutputdirectory.cpp
+++ b/rkward/misc/rkoutputdirectory.cpp
@@ -274,9 +274,7 @@ GenericRRequestResult RKOutputDirectory::activate(RCommandChain* chain) {
 	RInterface::issueCommand(QStringLiteral("rk.set.output.html.file(\"") + RKCommonFunctions::escape(index_file) + QStringLiteral("\")\n"), RCommand::App, QString(), 0, 0, chain);
 	if (!initialized) {
 		// when an output directory is first initialized, we don't want that to count as a "modification". Therefore, update the "saved hash" _after_ initialization
-		RCommand *command = new RCommand(QString(), RCommand::App | RCommand::Sync | RCommand::EmptyCommand);
-		connect(command->notifier(), &RCommandNotifier::commandFinished, this, &RKOutputDirectory::updateSavedHash);
-		RInterface::issueCommand(command, chain);
+		RInterface::whenAllFinished(this, [this]() { updateSavedHash(); }, chain);
 		initialized = true;
 	}
 
diff --git a/rkward/misc/rkxmlguipreviewarea.cpp b/rkward/misc/rkxmlguipreviewarea.cpp
index bab6e844..ed3b1ecc 100644
--- a/rkward/misc/rkxmlguipreviewarea.cpp
+++ b/rkward/misc/rkxmlguipreviewarea.cpp
@@ -193,9 +193,7 @@ void RKPreviewManager::setCommand (RCommand* command) {
 	connect (command->notifier(), &RCommandNotifier::commandFinished, this, &RKPreviewManager::previewCommandDone);
 
 	// Send an empty dummy command first. This is to sync up with any commands that should have been run _before_ the preview (e.g. to set up the preview area, so that status labels can be shown)
-	RCommand *dummy = new RCommand (QString (), RCommand::App | RCommand::Sync | RCommand::EmptyCommand);
-	connect(dummy->notifier(), &RCommandNotifier::commandFinished, this, [this]() { setStatusMessage(shortStatusLabel()); });
-	RInterface::issueCommand (dummy);
+	RInterface::whenAllFinished(this, [this]() { setStatusMessage(shortStatusLabel()); });
 
 	RInterface::issueCommand (command);
 	setStatusMessage (shortStatusLabel ());
diff --git a/rkward/rbackend/rkrinterface.h b/rkward/rbackend/rkrinterface.h
index 2f413cdd..6af464c9 100644
--- a/rkward/rbackend/rkrinterface.h
+++ b/rkward/rbackend/rkrinterface.h
@@ -45,6 +45,13 @@ public:
 /** convenience function to create a new command and issue it. See documentation on RCommand::RCommand() and RInterface::issueCommand() */
 	static void issueCommand(const QString &command, int type = 0, const QString &rk_equiv = QString(), RCommandReceiver *receiver=0, int flags=0, RCommandChain *chain=0);
 
+/** convenience function to call a function / lambda, once all commands issued in the given chain have finished (successfully or not). Internally, this works by queing an empty command. */
+	template<typename T> static void whenAllFinished(QObject *receiver, T func, RCommandChain *chain=nullptr) {
+		RCommand *c = new RCommand(QString(), RCommand::EmptyCommand | RCommand::Sync);
+		c->whenFinished(receiver, func);
+		issueCommand(c, chain);
+	};
+
 /** opens a new command chain. Returns a pointer to the new chain. If you specify a parent, the new chain will be a sub-chain of that chain. */
 	static RCommandChain *startChain(RCommandChain *parent=0);
 /** closes the command chain. The chain (and even its parent, if it is already closed) may be deleted right afterwards! */
@@ -180,21 +187,20 @@ the R-command log (currently class RKwatch), whether the command can be cancelle
 
 Most of the time you don't just want to run a command, but you also want to know the result. Now, this is a tad bit more difficult than one might expect at first glance. The reason for this is that the R backend runs in a separate thread. Hence, whenever you submit a command, it generally does not get executed right away - or at least you just don't know, when exactly it gets executed, and when the result is available. This is necessary, so (expensive) commands running in the backend do not block operations in the GUI/frontend.
 
-Ok, so how do you get informed, when your command was completed? Using RCommandReceiver. What you will want to do is inherit the class you
-want to handle the results of RCommands from RCommandReceiver. When finished, the RCommand will be submitted to the (pure virtual) RCommandReceiver::rCommandDone function, which of course you'll have to implement in a meaningful way in your derived class.
+Ok, so how do you get informed, when your command was completed? Using RCommand::notifier(), or in most cases, the convience function RCommand::whenFinished(). Here, you can simply specify a function or
+lambda expression that will be called. This function or expression @em may take an RCommand* as parameter. In some cases, all you want to know is when all commands that have been already queued up to now
+have been run. In this case, you can use RInterface::whenAllFinished().
+
+ at Note that all this was much more complicated in the old days, and you will still find a lot of places, where classes are derived from RCommandReceiver, and the command is handled in an overridden
+rCommandDone() function.
 
-The corresponding code would look something like this:
+Example code:
 
 \code
 
 #include "rbackend/rinterface.h"
-#include "rbackend/rcommandreceiver.h"
 
-class MyReceiver : public RCommandReceiver {
-//...
-protected:
-/// receives finished RCommands and processes them
-	void rCommandDone (RCommand *command);
+class MyReceiver : public QObject {
 //...
 private:
 /// does something by submitting an RCommand
@@ -204,27 +210,28 @@ private:
 
 
 void MyReceiver::someFunction () {
-	RInterface::issueCommand ("print (1+1)", RCommand::App, QString (), this);
+	RCommand *c = new RCommand("print (1+1)", RCommand::App);
+	c->whenFinished(this, [this](RCommand *command) {
+		if (command->successful ()) {
+			qDebug ("Result was %s", command->output ()->utf8 ());
+		}
+	});
+	RInterface::issueCommand(c);
 }
 
-void MyReceiver::rCommandDone (RCommand *command) {
-	if (command->successful ()) {
-		qDebug ("Result was %s", command->output ()->utf8 ());
-	}
-}
 \endcode
 
-First thing to note is, that this time we're passing two additional parameters to RInterface::issueCommand. The first (or rather third) is really not used as of the time of this writing. What it's meant to become is a short descriptive string attached to the RCommand, that allows the user to make some sense of what this command is all about. The other (fourth) is a pointer to an RCommandReceiver that should be informed of the result. In this case, we'll handle the result in the same object that we issued the command from, so we use "this".
-
-So next the RCommand created with RInterface::issueCommand goes on its way to/through the backend (we'll discuss what happens there further down below). Then later, when it has completed its journey, rCommandDone will be called with a pointer to the command as parameter. Now we can use this pointer to retrieve the information we need to know. RCommand::successful and some further simple functions give information about whether the command had any errors and what kind of errors (see RCommand for details). RCommand::output contains the output of the command as a QString (provided the command was successful and had any output). In this case that should be "[1] 2".
+So what happens is that the RCommand created with RInterface::issueCommand goes on its way to/through the backend (we'll discuss what happens there further down below). Then later, when it has completed its journey, the lambda expression will be called with a pointer to the command as parameter. Now we can use this pointer to retrieve the information we need to know. RCommand::successful and some further simple functions give information about whether the command had any errors and what kind of errors (see RCommand for details). RCommand::output contains the output of the command as a QString (provided the command was successful and had any output). In this case that should be "[1] 2".
 
 \section UsingTheInterfaceToRMultipleCommands Dealing with several RCommands in the same object
 
+// TODO: Adjust this section to RCommandNotifier / RCommand::whenFinished().
+
 In many cases you don't just want to deal with a single RCommand in an RCommandReceiver, but rather you might submit a bunch of different commands (for instance to find out about several different properties of an object in R-space), and then use some special handling for each of those commands. So the problem is, how to find out, which of your commands you're currently dealing with in rCommandDone.
 
 There are several ways to deal with this:
 
-	- storing the RCommand::id () (each command is automatically assigned a unique id, TODO: do we need this functionality? Maybe remove it for redundancy)
+	- storing the RCommand::id () (each command is automatically assigned a unique id)
 	- passing appropriate flags to know how to handle the command
 	- keeping the pointer (CAUTION: don't use that pointer except to compare it with the pointer of an incoming command. Commands get deleted when they are finished, and maybe (in the future) if they become obsolete etc. Hence the pointers you keep may be invalid!)
 


More information about the rkward-tracker mailing list