[education/rkward/devel/workspace_output] rkward: More generic return value passing.

Thomas Friedrichsmeier null at kde.org
Sat Oct 24 21:46:54 BST 2020


Git commit 945b7e2e6d9f1e35d8af5ed43808c6e088b46240 by Thomas Friedrichsmeier.
Committed on 24/10/2020 at 20:13.
Pushed by tfry into branch 'devel/workspace_output'.

More generic return value passing.

M  +43   -43   rkward/misc/rkoutputdirectory.cpp
M  +13   -28   rkward/misc/rkoutputdirectory.h
M  +19   -1    rkward/rbackend/rcommand.h
M  +6    -5    rkward/rbackend/rkrbackend.cpp
M  +1    -1    rkward/rbackend/rkrbackend.h
M  +10   -0    rkward/rbackend/rkrbackendprotocol_shared.cpp
M  +2    -0    rkward/rbackend/rkrbackendprotocol_shared.h
M  +5    -10   rkward/rbackend/rkrinterface.cpp
M  +32   -1    rkward/rbackend/rkrsupport.cpp
M  +3    -1    rkward/rbackend/rkrsupport.h
M  +3    -6    rkward/rbackend/rpackages/rkward/R/internal.R
M  +2    -2    rkward/rbackend/rpackages/rkward/R/rk.output.R

https://invent.kde.org/education/rkward/commit/945b7e2e6d9f1e35d8af5ed43808c6e088b46240

diff --git a/rkward/misc/rkoutputdirectory.cpp b/rkward/misc/rkoutputdirectory.cpp
index 2bad21e2..7230bf60 100644
--- a/rkward/misc/rkoutputdirectory.cpp
+++ b/rkward/misc/rkoutputdirectory.cpp
@@ -115,9 +115,9 @@ RKOutputDirectory* RKOutputDirectory::getOutputBySaveUrl(const QString& _dest) {
 	return nullptr;
 }
 
-RKOutputDirectory::GenericRCallResult RKOutputDirectory::save(const QString& dest, RKOutputDirectory::OverwriteBehavior overwrite) {
+GenericRRequestResult RKOutputDirectory::save(const QString& dest, RKOutputDirectory::OverwriteBehavior overwrite) {
 	RK_TRACE (APP);
-	GenericRCallResult res = exportAs(dest, overwrite);
+	GenericRRequestResult res = exportAs(dest, overwrite);
 	if (!res.failed()) {
 		updateSavedHash();
 		save_dir = res.ret.toString(); // might by different from dest, notably, if dest was empty
@@ -125,7 +125,7 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::save(const QString& des
 	return res;
 }
 
-RKOutputDirectory::GenericRCallResult RKOutputDirectory::exportAs (const QString& _dest, RKOutputDirectory::OverwriteBehavior overwrite) {
+GenericRRequestResult RKOutputDirectory::exportAs (const QString& _dest, RKOutputDirectory::OverwriteBehavior overwrite) {
 	RK_TRACE (APP);
 
 	QString dest = _dest;
@@ -137,7 +137,7 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::exportAs (const QString
 		dialog.setOption(QFileDialog::DontConfirmOverwrite, true);  // custom handling below
 
 		if (dialog.exec () != QDialog::Accepted) {
-			return GenericRCallResult::makeError(i18n("File selection cancelled"));
+			return GenericRRequestResult::makeError(i18n("File selection cancelled"));
 		}
 
 		dest = QDir::cleanPath(dialog.selectedFiles().value(0));
@@ -151,18 +151,18 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::exportAs (const QString
 	if (exists) {
 #warning TODO Check terminology ("output directory") once finalized
 		if (!isRKWwardOutputDirectory(dest)) {
-			return GenericRCallResult::makeError(i18n("The directory %1 exists, but does not appear to be an RKWard output directory. Refusing to overwrite it.", dest));
+			return GenericRRequestResult::makeError(i18n("The directory %1 exists, but does not appear to be an RKWard output directory. Refusing to overwrite it.", dest));
 		}
 /*		if (isOutputDirectoryModified(dest)) {
-			return GenericRCallResult::makeError(i18n("The output directory %1 has been modified by an external process. Refusing to overwrite it.", dest));
+			return GenericRRequestResult::makeError(i18n("The output directory %1 has been modified by an external process. Refusing to overwrite it.", dest));
 		} */
 		if (overwrite == Ask) {
 			const QString warning = i18n("Are you sure you want to overwrite the existing directory '%1'? All current contents, <b>including subdirectories</b> will be lost.", dest);
 			KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel(RKWardMainWindow::getMain(), warning, i18n("Overwrite Directory?"), KStandardGuiItem::overwrite(),
 												KStandardGuiItem::cancel(), QString(), KMessageBox::Options(KMessageBox::Notify | KMessageBox::Dangerous));
-			if (KMessageBox::Continue != res) return GenericRCallResult::makeError(i18n("User cancelled"));
+			if (KMessageBox::Continue != res) return GenericRRequestResult::makeError(i18n("User cancelled"));
 		} else if (overwrite != Force) {
-			return GenericRCallResult::makeError(i18n("Not overwriting existing output"));
+			return GenericRRequestResult::makeError(i18n("Not overwriting existing output"));
 		}
 
 		tempname = dest + '~';
@@ -170,7 +170,7 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::exportAs (const QString
 			tempname.append('~');
 		}
 		if (!QDir().rename(dest, tempname)) {
-			return GenericRCallResult::makeError(i18n("Failed to create temporary backup file %1.", tempname));
+			return GenericRRequestResult::makeError(i18n("Failed to create temporary backup file %1.", tempname));
 		}
 	}
 
@@ -179,60 +179,60 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::exportAs (const QString
 		if (!tempname.isEmpty()) {
 			QDir().rename(tempname, dest);
 		}
-		return GenericRCallResult::makeError(i18n("Error while copying %1 to %2", work_dir, dest));
+		return GenericRRequestResult::makeError(i18n("Error while copying %1 to %2", work_dir, dest));
 	}
 	if (!tempname.isEmpty()) {
 		QDir(tempname).removeRecursively();
 	}
 
-	return GenericRCallResult(QVariant(dest));  // return effective destination path. Needed by save()
+	return GenericRRequestResult(QVariant(dest));  // return effective destination path. Needed by save()
 }
 
-RKOutputDirectory::GenericRCallResult RKOutputDirectory::importInternal(const QString &_dir) {
+GenericRRequestResult RKOutputDirectory::importInternal(const QString &_dir) {
 	RK_TRACE (APP);
 
 	QFileInfo fi(_dir);
 	if (!fi.isDir()) {
-		return GenericRCallResult::makeError(i18n("The path %1 does not exist or is not a directory.", _dir));
+		return GenericRRequestResult::makeError(i18n("The path %1 does not exist or is not a directory.", _dir));
 	}
 	QString dir = fi.canonicalFilePath ();
 
 	if (!copyDirRecursively(dir, work_dir)) {
 		QDir(work_dir).removeRecursively();
-		return GenericRCallResult::makeError(i18n("The path %1 could not be imported (copy failure).", _dir));
+		return GenericRRequestResult::makeError(i18n("The path %1 could not be imported (copy failure).", _dir));
 	}
 
 	save_dir = dir;
 	updateSavedHash();
 	initialized = true;
 
-	return GenericRCallResult(QVariant(id));
+	return GenericRRequestResult(QVariant(id));
 }
 
-RKOutputDirectory::GenericRCallResult RKOutputDirectory::import(const QString& _dir) {
+GenericRRequestResult RKOutputDirectory::import(const QString& _dir) {
 	RK_TRACE (APP);
 
 	if (initialized) {
-		return GenericRCallResult::makeError(i18n("Output directory %1 is already in use.", id));
+		return GenericRRequestResult::makeError(i18n("Output directory %1 is already in use.", id));
 	}
 
 	return importInternal(_dir);
 }
 
-RKOutputDirectory::GenericRCallResult RKOutputDirectory::revert(OverwriteBehavior discard) {
+GenericRRequestResult RKOutputDirectory::revert(OverwriteBehavior discard) {
 	RK_TRACE (APP);
 
 	if (save_dir.isEmpty()) {
-		return GenericRCallResult::makeError(i18n("Output directory %1 has not previously been saved. Cannot revert.", id));
+		return GenericRRequestResult::makeError(i18n("Output directory %1 has not previously been saved. Cannot revert.", id));
 	}
-	if (!isModified()) return GenericRCallResult(id, i18n("Output directory %1 had no modifications. Nothing reverted.", id));
+	if (!isModified()) return GenericRRequestResult(id, i18n("Output directory %1 had no modifications. Nothing reverted.", id));
 	if (discard == Ask) {
 		if (KMessageBox::warningContinueCancel(RKWardMainWindow::getMain(), i18n("Reverting will destroy any changes, since the last time you saved (%1). Are you sure you want to proceed?"), save_timestamp.toString()) == KMessageBox::Continue) {
 			discard = Force;
 		}
 	}
 	if (discard != Force) {
-		return GenericRCallResult::makeError(i18n("User cancelled."));
+		return GenericRRequestResult::makeError(i18n("User cancelled."));
 	}
 
 	return importInternal(save_dir);
@@ -263,7 +263,7 @@ RKOutputDirectory* RKOutputDirectory::createOutputDirectoryInternal() {
 	return d;
 }
 
-RKOutputDirectory::GenericRCallResult RKOutputDirectory::activate(RCommandChain* chain) {
+GenericRRequestResult RKOutputDirectory::activate(RCommandChain* chain) {
 	RK_TRACE (APP);
 
 	QString index_file = work_dir + "/index.html";
@@ -276,10 +276,10 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::activate(RCommandChain*
 		initialized = true;
 	}
 
-	return GenericRCallResult(QVariant(index_file));
+	return GenericRRequestResult(QVariant(index_file));
 }
 
-RKOutputDirectory::GenericRCallResult RKOutputDirectory::clear(OverwriteBehavior discard) {
+GenericRRequestResult RKOutputDirectory::clear(OverwriteBehavior discard) {
 	RK_TRACE(APP);
 
 	if (!isEmpty()) {
@@ -288,7 +288,7 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::clear(OverwriteBehavior
 				discard = Force;
 			}
 			if (discard != Force) {
-				return GenericRCallResult::makeError(i18n("Output is not empty. Not clearing it."));
+				return GenericRRequestResult::makeError(i18n("Output is not empty. Not clearing it."));
 			}
 		}
 	}
@@ -299,7 +299,7 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::clear(OverwriteBehavior
 	initialized = false;
 	if (isActive()) activate();
 
-	return GenericRCallResult();
+	return GenericRRequestResult();
 }
 
 bool RKOutputDirectory::isEmpty() const {
@@ -324,17 +324,17 @@ QString RKOutputDirectory::caption() const {
 	return i18n("Unsaved output");
 }
 
-RKOutputDirectory::GenericRCallResult RKOutputDirectory::purge(RKOutputDirectory::OverwriteBehavior discard, RCommandChain* chain) {
+GenericRRequestResult RKOutputDirectory::purge(RKOutputDirectory::OverwriteBehavior discard, RCommandChain* chain) {
 	RK_TRACE(APP);
 
 	if (isModified()) {
 		if (discard == Fail) {
-			return GenericRCallResult::makeError(i18n("Output has been modified. Not closing it."));
+			return GenericRRequestResult::makeError(i18n("Output has been modified. Not closing it."));
 		}
 		if (discard == Ask) {
 			auto res = KMessageBox::questionYesNoCancel(RKWardMainWindow::getMain(), i18n("The output has been modified, and closing it will discard all changes. What do you want to do?"), i18n("Discard unsaved changes?"), KStandardGuiItem::discard(), KStandardGuiItem::save(), KStandardGuiItem::cancel());
 			if (res == KMessageBox::Cancel) {
-				return GenericRCallResult::makeError(i18n("User cancelled"));
+				return GenericRRequestResult::makeError(i18n("User cancelled"));
 			}
 			if (res == KMessageBox::No) {
 				auto ret = save();
@@ -355,7 +355,7 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::purge(RKOutputDirectory
 			getCurrentOutput(chain)->activate(chain);
 		}
 	}
-	return GenericRCallResult();
+	return GenericRRequestResult();
 }
 
 QString RKOutputDirectory::workPath() const {
@@ -430,7 +430,7 @@ RKOutputDirectory::OverwriteBehavior parseOverwrite(const QString &param) {
 	return RKOutputDirectory::Fail;
 }
 
-RKOutputDirectory::GenericRCallResult RKOutputDirectory::view(bool raise) {
+GenericRRequestResult RKOutputDirectory::view(bool raise) {
 	RK_TRACE(APP);
 
 	auto list = RKOutputWindowManager::self()->existingOutputWindows(workPath());
@@ -442,10 +442,10 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::view(bool raise) {
 	} else {
 		RKWorkplace::mainWorkplace()->openOutputWindow(QUrl::fromLocalFile(workPath()));
 	}
-	return GenericRCallResult(id);
+	return GenericRRequestResult(id);
 }
 
-RKOutputDirectory::GenericRCallResult RKOutputDirectory::handleRCall(const QStringList& params, RCommandChain *chain) {
+GenericRRequestResult RKOutputDirectory::handleRCall(const QStringList& params, RCommandChain *chain) {
 	RK_TRACE(APP);
 
 	QString command = params.value(0);
@@ -456,7 +456,7 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::handleRCall(const QStri
 			for (int i=0; i < all.size(); ++i) {
 				ret.append(all[i]->getId());
 			}
-			return GenericRCallResult(ret);
+			return GenericRRequestResult(ret);
 		}
 
 		QString filename = params.value(3);
@@ -472,29 +472,29 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::handleRCall(const QStri
 			filename = QFileInfo(filename).canonicalFilePath();
 			out = getOutputBySaveUrl(filename);
 			if (create) {
-				if (out) return GenericRCallResult::makeError(i18n("Output '1%' is already loaded in this session. Cannot create it.", filename));
-				if (QFileInfo(filename).exists()) return GenericRCallResult::makeError(i18n("As file named '1%' already exists. Cannot create it.", filename));
+				if (out) return GenericRRequestResult::makeError(i18n("Output '1%' is already loaded in this session. Cannot create it.", filename));
+				if (QFileInfo(filename).exists()) return GenericRRequestResult::makeError(i18n("As file named '1%' already exists. Cannot create it.", filename));
 				out = createOutputDirectoryInternal();
 				return (out->import(filename));
 			} else {
 				if (!out) return (out->import(filename));
 			}
 		}
-		return GenericRCallResult(out->getId());
+		return GenericRRequestResult(out->getId());
 	} else {
 		// all other commands pass the output id as second parameter. Look that up, first
 		QString id = params.value(1);
 		auto out = getOutputById(id);
 		if (!out) {
-			return GenericRCallResult::makeError(i18n("The output identified by '%1' is not loaded in this session.", id));
+			return GenericRRequestResult::makeError(i18n("The output identified by '%1' is not loaded in this session.", id));
 		}
 
 		if (command == QStringLiteral("activate")) {
 			return out->activate(chain);
 		} else if (command == QStringLiteral("isEmpty")) {
-			return GenericRCallResult(out->isEmpty());
+			return GenericRRequestResult(out->isEmpty());
 		} else if (command == QStringLiteral("isModified")) {
-			return GenericRCallResult(out->isModified());
+			return GenericRRequestResult(out->isModified());
 		} else if (command == QStringLiteral("revert")) {
 			return out->revert(parseOverwrite(params.value(2)));
 		} else if (command == QStringLiteral("save")) {
@@ -508,12 +508,12 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::handleRCall(const QStri
 		} else if (command == QStringLiteral("view")) {
 			return out->view(params.value(2) == QStringLiteral("raise"));
 		} else if (command == QStringLiteral("workingDir")) {
-			return GenericRCallResult(out->workDir());
+			return GenericRRequestResult(out->workDir());
 		} else if (command == QStringLiteral("filename")) {
-			return GenericRCallResult(out->filename());
+			return GenericRRequestResult(out->filename());
 		}
 	}
-	return GenericRCallResult::makeError(i18n("Unhandled output command '%1'", command));
+	return GenericRRequestResult::makeError(i18n("Unhandled output command '%1'", command));
 }
 
 
diff --git a/rkward/misc/rkoutputdirectory.h b/rkward/misc/rkoutputdirectory.h
index 2bd3b263..41a18d33 100644
--- a/rkward/misc/rkoutputdirectory.h
+++ b/rkward/misc/rkoutputdirectory.h
@@ -25,56 +25,41 @@
 #include <QPointer>
 #include <QObject>
 
+#include "../rbackend/rcommand.h"
+
 class RKMDIWindow;
 class RCommandChain;
 
 class RKOutputDirectory : public QObject {
 	Q_OBJECT
 public:
-#warning TODO: Move me to backend
-	/** Standard wrapper for the result of a "generic" API call, such that we need less special-casing in the backend. The conventions are simple:
-	 *  - If @param error is non-null, stop() will be called in the backend, with the given message.
-	 *  - If @param warning is non-null, the message will be shown (as a warning), but no error will be raised.
-	 *  - Unless an error was thrown, @param ret will be returned as a basic data type (possibly NULL). */
-	struct GenericRCallResult {
-		GenericRCallResult(const QVariant& ret=QVariant(), const QString& warning=QString(), const QString& error=QString()) :
-			error(error), warning(warning), ret(ret) {};
-		static GenericRCallResult makeError(const QString& error) {
-			return GenericRCallResult(QVariant(), QString(), error);
-		}
-		QString error;
-		QString warning;
-		QVariant ret;
-		bool failed() { return !error.isEmpty(); }
-		bool toBool() { return ret.toBool(); }
-	};
 	enum OverwriteBehavior {
 		Force,
 		Ask,
 		Fail
 	};
-	GenericRCallResult activate(RCommandChain* chain=0);
-	GenericRCallResult revert(OverwriteBehavior discard=Ask);
-	GenericRCallResult save(const QString& dest=QString(), OverwriteBehavior overwrite=Ask);
-	GenericRCallResult exportAs(const QString& dest=QString(), OverwriteBehavior overwrite=Ask);
-	GenericRCallResult clear(OverwriteBehavior discard=Ask);
-	GenericRCallResult purge(OverwriteBehavior discard=Ask, RCommandChain* chain=0);
+	GenericRRequestResult activate(RCommandChain* chain=0);
+	GenericRRequestResult revert(OverwriteBehavior discard=Ask);
+	GenericRRequestResult save(const QString& dest=QString(), OverwriteBehavior overwrite=Ask);
+	GenericRRequestResult exportAs(const QString& dest=QString(), OverwriteBehavior overwrite=Ask);
+	GenericRRequestResult clear(OverwriteBehavior discard=Ask);
+	GenericRRequestResult purge(OverwriteBehavior discard=Ask, RCommandChain* chain=0);
 	QString getId() const { return id; };
 	bool isEmpty() const;
 	bool isActive() const;
 	bool isModified() const;
-	GenericRCallResult view(bool raise);
+	GenericRRequestResult view(bool raise);
 	QString filename() const { return save_dir; };
 	QString workDir() const { return work_dir; }
 	QString workPath() const;
 	QString caption() const;
-	static GenericRCallResult handleRCall(const QStringList& params, RCommandChain *chain);
+	static GenericRRequestResult handleRCall(const QStringList& params, RCommandChain *chain);
 	static RKOutputDirectory* getOutputById(const QString& id);
 	static RKOutputDirectory* getOutputBySaveUrl(const QString& dest);
 /** Return a list of all current output directories that have been modified. Used for asking for save during shutdown. */
 	static QList<RKOutputDirectory*> modifiedOutputDirectories();
 
-	static RKOutputDirectory::GenericRCallResult R_rk_Output(const QString& filename=QString(), bool create=false, bool all=false);
+	static GenericRRequestResult R_rk_Output(const QString& filename=QString(), bool create=false, bool all=false);
 /** Returns the active output (in case there is one).
  *  If no output is active, find an activate the next output without a save url.
  *  If that does not exist, activate and return the next existing output.
@@ -97,10 +82,10 @@ private:
 	/** map of outputs. */
 	static QMap<QString, RKOutputDirectory*> outputs;
 
-	GenericRCallResult import(const QString& from);
+	GenericRRequestResult import(const QString& from);
 	static RKOutputDirectory* createOutputDirectoryInternal();
 	static bool isRKWwardOutputDirectory (const QString &dir);
-	GenericRCallResult importInternal(const QString &dir);
+	GenericRRequestResult importInternal(const QString &dir);
 };
 
 #endif
diff --git a/rkward/rbackend/rcommand.h b/rkward/rbackend/rcommand.h
index 6f0a7b43..8e63c8a6 100644
--- a/rkward/rbackend/rcommand.h
+++ b/rkward/rbackend/rcommand.h
@@ -2,7 +2,7 @@
                           rcommand.h  -  description
                              -------------------
     begin                : Mon Nov 11 2002
-    copyright            : (C) 2002, 2006, 2007, 2009, 2010, 2013 by Thomas Friedrichsmeier
+    copyright            : (C) 2002-2020 by Thomas Friedrichsmeier
     email                : thomas.friedrichsmeier at kdemail.net
  ***************************************************************************/
 
@@ -233,4 +233,22 @@ friend class RCommandStackModel;
 	RCommandNotifier *_notifier;
 };
 
+#include <QVariant>
+/** Standard wrapper for the result of a "generic" API call, such that we need less special-casing in the backend. The conventions are simple:
+*  - If @param error is non-null, stop() will be called in the backend, with the given message.
+*  - If @param warning is non-null, the message will be shown (as a warning), but no error will be raised.
+*  - Unless an error was thrown, @param ret will be returned as a basic data type (possibly NULL). */
+struct GenericRRequestResult {
+	GenericRRequestResult(const QVariant& ret=QVariant(), const QString& warning=QString(), const QString& error=QString()) :
+		error(error), warning(warning), ret(ret) {};
+	static GenericRRequestResult makeError(const QString& error) {
+		return GenericRRequestResult(QVariant(), QString(), error);
+	}
+	QString error;
+	QString warning;
+	QVariant ret;
+	bool failed() { return !error.isEmpty(); }
+	bool toBool() { return ret.toBool(); }
+};
+
 #endif
diff --git a/rkward/rbackend/rkrbackend.cpp b/rkward/rbackend/rkrbackend.cpp
index f1949c9e..ea106df6 100644
--- a/rkward/rbackend/rkrbackend.cpp
+++ b/rkward/rbackend/rkrbackend.cpp
@@ -946,10 +946,11 @@ SEXP doSubstackCall (SEXP call) {
 	} */
 
 
-	QStringList ret = RKRBackend::this_pointer->handleHistoricalSubstackRequest (list).toStringList ();
+	auto ret = RKRBackend::this_pointer->handleHistoricalSubstackRequest(list);
+	if (!ret.warning.isEmpty()) Rf_warning(RKRBackend::fromUtf8(ret.warning));  // print warnings, first, as errors are - an error
+	if (!ret.error.isEmpty()) Rf_error(RKRBackend::fromUtf8(ret.error));
 
-	if (ret.isEmpty ()) return R_NilValue;
-	return RKRSupport::StringListToSEXP (ret);
+	return RKRSupport::QVariantToSEXP(ret.ret);
 }
 
 SEXP doPlainGenericRequest (SEXP call, SEXP synchronous) {
@@ -1624,14 +1625,14 @@ RCommandProxy* RKRBackend::fetchNextCommand () {
 	return (handleRequest (&req, false));
 }
 
-QVariant RKRBackend::handleHistoricalSubstackRequest (const QStringList &list) {
+GenericRRequestResult RKRBackend::handleHistoricalSubstackRequest (const QStringList &list) {
 	RK_TRACE (RBACKEND);
 
 	RBackendRequest request (true, RBackendRequest::HistoricalSubstackRequest);
 	request.params["call"] = list;
 	request.command = current_command;
 	handleRequest (&request);
-	return request.params.value ("return");
+	return request.getResult();
 }
 
 QString getLibLoc() {
diff --git a/rkward/rbackend/rkrbackend.h b/rkward/rbackend/rkrbackend.h
index 4c920d0d..f6c879c4 100644
--- a/rkward/rbackend/rkrbackend.h
+++ b/rkward/rbackend/rkrbackend.h
@@ -107,7 +107,7 @@ public:
 	void handleRequest (RBackendRequest *request) { handleRequest (request, true); };
 /** A relic of history. In contrast to handlePlainGenericRequest(), these requests support running sub-commands. However, the remaining requests which are currently handled this way
 should probably be converted to dedicated RKRBackendRequest's in the future. See also handlePlainGenericRequest(). */
-	QVariant handleHistoricalSubstackRequest (const QStringList &list);
+	GenericRRequestResult handleHistoricalSubstackRequest (const QStringList &list);
 /** Sends a request to the frontend and returns the result (an empty QStringList in case of asynchronous requests). Note that this function has considerable overlap with
 handleHistoricalSubstackRequest(). Exactly which requests get handled by which function is somewhat arbitrary, ATM. However, request that do not need sub-commands to be run, should generally be converted to use handlePlainGenericRequest(). (And probably all historicalSubstackRequests should be replaced!) */
 	QStringList handlePlainGenericRequest (const QStringList &parameters, bool synchronous);
diff --git a/rkward/rbackend/rkrbackendprotocol_shared.cpp b/rkward/rbackend/rkrbackendprotocol_shared.cpp
index 0503dfe6..e0ea6dd6 100644
--- a/rkward/rbackend/rkrbackendprotocol_shared.cpp
+++ b/rkward/rbackend/rkrbackendprotocol_shared.cpp
@@ -81,6 +81,16 @@ RBackendRequest* RBackendRequest::duplicate () {
 	return ret;
 }
 
+void RBackendRequest::setResult(const GenericRRequestResult& res) {
+	if (!res.warning.isNull()) params[".w"] = res.warning;
+	if (!res.error.isNull()) params[".e"] = res.error;
+	else params[".r"] = res.ret;
+}
+
+GenericRRequestResult RBackendRequest::getResult() const {
+	return GenericRRequestResult(params.value(".r"), params.value(".w").toString(), params.value(".e").toString());
+}
+
 
 
 #define MAX_BUF_LENGTH 16000
diff --git a/rkward/rbackend/rkrbackendprotocol_shared.h b/rkward/rbackend/rkrbackendprotocol_shared.h
index af9dc38a..ca159975 100644
--- a/rkward/rbackend/rkrbackendprotocol_shared.h
+++ b/rkward/rbackend/rkrbackendprotocol_shared.h
@@ -75,6 +75,8 @@ public:
 	RCommandProxy *command;
 /** Any other parameters, esp. for RCallbackType::OtherRequest. Can be used in both directions. */
 	QVariantMap params;
+	void setResult(const GenericRRequestResult &res);
+	GenericRRequestResult getResult() const;
 /** NOTE: only used for separate process backend. See RCallbackType::Output */
 	ROutputList *output;
 /** NOTE: this does @em not copy merge the "done" flag. Do that manually, @em after merging (and don't touch the request from the transmitter thread, after that). */
diff --git a/rkward/rbackend/rkrinterface.cpp b/rkward/rbackend/rkrinterface.cpp
index deb808ac..de5c3aaa 100644
--- a/rkward/rbackend/rkrinterface.cpp
+++ b/rkward/rbackend/rkrinterface.cpp
@@ -405,7 +405,7 @@ void RInterface::handleRequest (RBackendRequest* request) {
 		command_requests.append (request);
 		processHistoricalSubstackRequest (request->params["call"].toStringList (), parent, request);
 	} else if (request->type == RBackendRequest::PlainGenericRequest) {
-		request->params["return"] = QVariant (processPlainGenericRequest (request->params["call"].toStringList ()));
+		request->setResult(QVariant(processPlainGenericRequest(request->params["call"].toStringList())));
 		RKRBackendProtocolFrontend::setRequestCompleted (request);
 	} else if (request->type == RBackendRequest::Started) {
 		// The backend thread has finished basic initialization, but we still have more to do...
@@ -551,6 +551,7 @@ void RInterface::pauseProcessing (bool pause) {
 	else locked -= locked & User;
 }
 
+#warning Convert this to GenericRRequestResult
 QStringList RInterface::processPlainGenericRequest (const QStringList &calllist) {
 	RK_TRACE (RBACKEND);
 
@@ -740,21 +741,15 @@ void RInterface::processHistoricalSubstackRequest (const QStringList &calllist,
 			ok = RKComponentMap::invokeComponent (calllist[1], calllist.mid (3), mode, &message, in_chain);
 
 			if (!message.isEmpty ()) {
-				request->params["return"] = QVariant (QStringList (ok ? QStringLiteral ("warning") : QStringLiteral ("error")) << message);
+				request->setResult(GenericRRequestResult(QVariant(), ok ? message : QString(), !ok ? message : QString()));
 			}
 		} else {
 			RK_ASSERT (false);
 		}
 	} else if (call == QStringLiteral ("output")) {
-		RKOutputDirectory::GenericRCallResult res = RKOutputDirectory::handleRCall(calllist.mid(1), in_chain);
-#warning TODO
-		if (res.failed()) {
-			request->params["return"] = QVariant(QStringList(QStringLiteral("error")) << res.error);
-		} else {
-			request->params["return"] = QVariant(res.ret);
-		}
+		request->setResult(RKOutputDirectory::handleRCall(calllist.mid(1), in_chain));
 	} else {
-		request->params["return"] = QVariant (QStringList (QStringLiteral ("error")) << i18n ("Unrecognized call '%1'", call));
+		request->setResult(GenericRRequestResult::makeError(i18n("Unrecognized call '%1'", call)));
 	}
 	
 	closeChain (in_chain);
diff --git a/rkward/rbackend/rkrsupport.cpp b/rkward/rbackend/rkrsupport.cpp
index ee039454..3aa02368 100644
--- a/rkward/rbackend/rkrsupport.cpp
+++ b/rkward/rbackend/rkrsupport.cpp
@@ -2,7 +2,7 @@
                           rkrsupport  -  description
                              -------------------
     begin                : Mon Oct 25 2010
-    copyright            : (C) 2010-2018 by Thomas Friedrichsmeier
+    copyright            : (C) 2010-2020 by Thomas Friedrichsmeier
     email                : thomas.friedrichsmeier at kdemail.net
  ***************************************************************************/
 
@@ -142,6 +142,37 @@ SEXP RKRSupport::StringListToSEXP (const QStringList& list) {
 	return ret;
 }
 
+SEXP RKRSupport::QVariantToSEXP(const QVariant& var) {
+	RK_TRACE (RBACKEND);
+
+	if (var.isNull()) return R_NilValue;
+
+	QMetaType::Type t = (QMetaType::Type) var.type();
+	if (t == QMetaType::Bool) {
+		SEXP ret = Rf_allocVector(LGLSXP, 1);
+		LOGICAL(ret)[0] = var.toBool();
+		return ret;
+	} else if (t == QMetaType::Int) {
+		SEXP ret = Rf_allocVector(INTSXP, 1);
+		INTEGER(ret)[0] = var.toInt();
+		return ret;
+	} else if (t != QMetaType::QString && t != QMetaType::QStringList) {
+		Rf_warning("unsupported QVariant type in QVariantToSEXP");
+	}
+	QStringList list = var.toStringList();
+
+	SEXP ret = Rf_allocVector (STRSXP, list.size ());
+	for (int i = 0; i < list.size (); ++i) {
+#if R_VERSION >= R_Version(2,13,0)
+		SET_STRING_ELT (ret, i, Rf_mkCharCE (list[i].toUtf8 (), CE_UTF8));
+#else
+		// TODO Rf_mkCharCE _might_ have been introduced earlier. Check if still an ongoing concern.
+		SET_STRING_ELT (ret, i, Rf_mkChar (RKRBackend::fromUtf8 (list[i]).data ()));
+#endif
+	}
+	return ret;
+}
+
 RData::IntStorage RKRSupport::SEXPToIntArray (SEXP from_exp) {
 	RK_TRACE (RBACKEND);
 
diff --git a/rkward/rbackend/rkrsupport.h b/rkward/rbackend/rkrsupport.h
index d5eb3ac2..5dd1cf51 100644
--- a/rkward/rbackend/rkrsupport.h
+++ b/rkward/rbackend/rkrsupport.h
@@ -2,7 +2,7 @@
                           rkrsupport  -  description
                              -------------------
     begin                : Mon Oct 25 2010
-    copyright            : (C) 2010 by Thomas Friedrichsmeier
+    copyright            : (C) 2010-2020 by Thomas Friedrichsmeier
     email                : thomas.friedrichsmeier at kdemail.net
  ***************************************************************************/
 
@@ -20,6 +20,7 @@
 
 #include <limits.h>
 
+#include <QVariant>
 #include <QStringList>
 
 #include "rdata.h"
@@ -36,6 +37,7 @@ namespace RKRSupport {
 
 	QStringList SEXPToStringList (SEXP from_exp);
 	SEXP StringListToSEXP (const QStringList &list);
+	SEXP QVariantToSEXP(const QVariant &val);
 	QString SEXPToString (SEXP from_exp);
 	RData::IntStorage SEXPToIntArray (SEXP from_exp);
 	int SEXPToInt (SEXP from_exp, int def_value = INT_MIN);
diff --git a/rkward/rbackend/rpackages/rkward/R/internal.R b/rkward/rbackend/rpackages/rkward/R/internal.R
index 1dc0955b..b2385524 100755
--- a/rkward/rbackend/rpackages/rkward/R/internal.R
+++ b/rkward/rbackend/rpackages/rkward/R/internal.R
@@ -122,12 +122,9 @@
 }
 
 ".rk.do.call" <- function (x, args=NULL) {
-	ret <- .Call ("rk.do.command", c (x, args), PACKAGE="(embedding)");
-	if (!is.null (ret)) {
-		if (ret[1] == "warning") warning (ret[2])
-		else if (ret[1] == "error") stop (ret[2])
-		else ret[1:length(ret)]
-	}
+	x <- .Call ("rk.do.command", c (x, args), PACKAGE="(embedding)");
+	if (is.null(x)) invisible(NULL)
+	else x
 }
 
 ".rk.do.plain.call" <- function (x, args=NULL, synchronous=TRUE) {
diff --git a/rkward/rbackend/rpackages/rkward/R/rk.output.R b/rkward/rbackend/rpackages/rkward/R/rk.output.R
index 28d96378..e3d72da5 100644
--- a/rkward/rbackend/rpackages/rkward/R/rk.output.R
+++ b/rkward/rbackend/rpackages/rkward/R/rk.output.R
@@ -47,11 +47,11 @@ RK.Output <- setRefClass(Class="RK.Output", fields=list(id="character"),
 		},
 		isEmpty=function() {
 "Returns TRUE, if the output is currently empty."
-			isTRUE(.rk.do.call("output", c ("isEmpty", .checkId())))
+			.rk.do.call("output", c ("isEmpty", .checkId()))
 		},
 		isModified=function() {
 "Returns TRUE, if this output has any changes that may need saving."
-			isTRUE(.rk.do.call("output", c ("isModified", .checkId())))
+			.rk.do.call("output", c ("isModified", .checkId()))
 		},
 		revert=function(discard=NULL) {
 "Revert this output to the last saved state. If no previous state is available (never saved, before), clears the output."




More information about the rkward-tracker mailing list