[education/rkward] rkward: Further mockup of R backend selection

Thomas Friedrichsmeier null at kde.org
Sat Jun 8 23:21:01 BST 2024


Git commit fb8a18ccfc60cc3dd892b761f87311aae5d3ee77 by Thomas Friedrichsmeier.
Committed on 08/06/2024 at 22:10.
Pushed by tfry into branch 'master'.

Further mockup of R backend selection

M  +1    -1    rkward/dialogs/CMakeLists.txt
M  +41   -8    rkward/dialogs/rksetupwizard.cpp
M  +4    -2    rkward/dialogs/rksetupwizard.h
M  +1    -58   rkward/main.cpp
M  +52   -1    rkward/rbackend/rksessionvars.cpp
M  +1    -0    rkward/rbackend/rksessionvars.h

https://invent.kde.org/education/rkward/-/commit/fb8a18ccfc60cc3dd892b761f87311aae5d3ee77

diff --git a/rkward/dialogs/CMakeLists.txt b/rkward/dialogs/CMakeLists.txt
index ebd185e76..b7399a38a 100644
--- a/rkward/dialogs/CMakeLists.txt
+++ b/rkward/dialogs/CMakeLists.txt
@@ -17,4 +17,4 @@ SET(dialogs_STAT_SRCS
 
 ADD_DEFINITIONS(-DLIBEXECDIR="${KDE_INSTALL_LIBEXECDIR}")
 ADD_LIBRARY(dialogs STATIC ${dialogs_STAT_SRCS})
-TARGET_LINK_LIBRARIES(dialogs Qt6::Widgets KF6::Parts KF6::ConfigWidgets KF6::TextEditor KF6::I18n)
+TARGET_LINK_LIBRARIES(dialogs Qt6::Widgets KF6::Parts KF6::ConfigWidgets KF6::TextEditor KF6::I18n KF6::KIOWidgets)
diff --git a/rkward/dialogs/rksetupwizard.cpp b/rkward/dialogs/rksetupwizard.cpp
index 37af06084..45e30000a 100644
--- a/rkward/dialogs/rksetupwizard.cpp
+++ b/rkward/dialogs/rksetupwizard.cpp
@@ -20,8 +20,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <QStandardPaths>
 #include <QRadioButton>
 #include <QGroupBox>
+#include <QButtonGroup>
 
 #include <KLocalizedString>
+#include <KUrlRequester>
 #include <KMessageBox>
 
 #include "../settings/rksettingsmoduleplugins.h"
@@ -263,9 +265,11 @@ RKSetupWizard::RKSetupWizard(QWidget* parent, InvokationReason reason, const QLi
 			if (!(RInterface::instance()->backendIsDead() || RInterface::instance()->backendIsIdle())) return;
 
 			anim->stop();
+			bool current_valid = true;
 			if (RInterface::instance()->backendIsDead()) {
 				rstatus_icon->setPixmap(iconForStatus(RKSetupWizardItem::Error));
 				rstatus_label->setText(i18n("<b>R backend has crashed.</b>"));
+				current_valid = false;
 			} else {
 				RK_ASSERT(RInterface::instance()->backendIsIdle());
 				QString statustext = i18n("<b>R version %1 started, successfully.</b>", RKSessionVars::RVersion(false));
@@ -283,11 +287,34 @@ RKSetupWizard::RKSetupWizard(QWidget* parent, InvokationReason reason, const QLi
 				setValid(pageref, true);
 			}
 
+			// Selector widget for R installation. Need to delete/recreate this on each page enter
+			delete (property("sel").value<QWidget*>());
 			auto box = new QGroupBox(i18n("R version to use"));
+			setProperty("sel", QVariant::fromValue(box));
+
+			auto group = new QButtonGroup(box);
 			l->addWidget(box);
 			auto bl = new QVBoxLayout(box);
-			auto button = new QRadioButton(i18n("Keep current version"), box); bl->addWidget(button);
-			button = new QRadioButton(i18n("Some dummy option, TODO"), box); bl->addWidget(button);
+			QStringList r_installations = RKSessionVars::findRInstallations();
+			r_installations.removeAll(RKSessionVars::RBinary());
+			auto button = new QRadioButton(i18n("Keep current version"), box); bl->addWidget(button); group->addButton(button, 0);
+			if (!current_valid) button->setText(i18n("Attempt to restart R at %1", RKSessionVars::RBinary()));
+			button->setChecked(true);
+
+			for(const QString &inst : std::as_const(r_installations)) {
+				button = new QRadioButton(i18n("Use R at %1", inst), box); bl->addWidget(button); group->addButton(button);
+			}
+			button = new QRadioButton(i18n("Use R at:"), box); bl->addWidget(button); group->addButton(button);
+			auto req = new KUrlRequester(); bl->addWidget(req);
+			req->setPlaceholderText(i18n("Select another R executable"));
+			req->setEnabled(false);
+			req->setWindowTitle(i18n("Select R executable"));
+			connect(button, &QAbstractButton::toggled, req, &QWidget::setEnabled);
+
+			next_callbacks.insert(pageref, [group]() -> bool {
+				bool proceed = (group->checkedId() == 0) && RInterface::instance()->backendIsIdle();
+				return proceed;
+			});
 		};
 		connect(RInterface::instance(), &RInterface::backendStatusChanged, this, statuslambda);
 		statuslambda();
@@ -315,7 +342,7 @@ RKSetupWizard::RKSetupWizard(QWidget* parent, InvokationReason reason, const QLi
 	auto last_page_label = RKCommonFunctions::linkedWrappedLabel("");
 	l->addWidget(last_page_label);
 	l->addStretch();
-	page->lazyInitRepeated([this, last_page_label](RKSetupWizardPage *) {
+	page->lazyInitRepeated([this, last_page_label](RKSetupWizardPage *page) {
 		software_to_install.clear();
 		packages_to_install.clear();
 		r_commands_to_run.clear();;
@@ -350,12 +377,9 @@ RKSetupWizard::RKSetupWizard(QWidget* parent, InvokationReason reason, const QLi
 		} else {
 			label_text.append(i18n("No R packages to install"));
 		}
-
-		// TODO: This height calculation is not quite correct, somehow, but good enough for now.
-		int spare_height = height() - last_page_label->parentWidget()->sizeHint().height();
 		last_page_label->setText(label_text);
-		int new_height = qMax(height(), spare_height+last_page_label->minimumSizeHint().height());
-		resize(width(), new_height);
+		// Somehow we need to help the geometry along, or height will not take line-wraps into account
+		last_page_label->setMinimumHeight(last_page_label->heightForWidth(page->width()));
 	});
 }
 
@@ -366,6 +390,15 @@ RKSetupWizard::~RKSetupWizard() {
 	}
 }
 
+void RKSetupWizard::next() {
+	RK_TRACE (DIALOGS);
+
+	if (next_callbacks.contains(currentPage())) {
+		if (!next_callbacks[currentPage()]()) return;
+	}
+	KAssistantDialog::next();
+}
+
 void RKSetupWizard::doAutoCheck() {
 	RK_TRACE (DIALOGS);
 
diff --git a/rkward/dialogs/rksetupwizard.h b/rkward/dialogs/rksetupwizard.h
index aa0494a01..1ad537134 100644
--- a/rkward/dialogs/rksetupwizard.h
+++ b/rkward/dialogs/rksetupwizard.h
@@ -1,6 +1,6 @@
 /*
-rksetupwizard - This file is part of the RKWard project. Created: Fri May 25 20200
-SPDX-FileCopyrightText: 2020 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+rksetupwizard - This file is part of the RKWard project. Created: Fri May 25 2020
+SPDX-FileCopyrightText: 2020-2024 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
 */
@@ -36,12 +36,14 @@ public:
 
 	void markSoftwareForInstallation(const QString &name, const QString &download_url, bool install);
 	void markRPackageForInstallation(const QString &name, bool install);
+	void next() override;
 private:
 	QStringList software_to_install;
 	QStringList software_to_install_urls;
 	QStringList packages_to_install;
 	QStringList r_commands_to_run;
 friend class RKSetupWizardPage;
+	QMap<KPageWidgetItem*, std::function<bool()>> next_callbacks;
 	QList<RKSetupWizardItem*> items;
 	bool reinstallation_required;
 };
diff --git a/rkward/main.cpp b/rkward/main.cpp
index 5f1ecb07d..8071a01b4 100644
--- a/rkward/main.cpp
+++ b/rkward/main.cpp
@@ -63,14 +63,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <QTime>
 #include <QSettings>
 #include <QStandardPaths>
-#if defined(Q_OS_MACOS) || defined(Q_OS_WINDOWS)
-#	include <QVersionNumber>
-#endif
-
-#ifdef Q_OS_MACOS
-	// Needed to allow execution of launchctl
-#	include <QProcess>
-#endif
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -103,17 +95,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #	define PATH_VAR_SEP ':'
 #endif
 
-QString findExeAtPath (const QString &appname, const QString &path) {
-	QDir dir (path);
-	dir.makeAbsolute ();
-	if (QFileInfo (dir.filePath (appname)).isExecutable ()) return dir.filePath (appname);
-#ifdef Q_OS_WIN
-	if (QFileInfo (dir.filePath (appname + ".exe")).isExecutable ()) return dir.filePath (appname + ".exe");
-	if (QFileInfo (dir.filePath (appname + ".com")).isExecutable ()) return dir.filePath (appname + ".com");
-#endif
-	return QString ();
-}
-
 bool RK_Debug_Terminal = true;
 QMutex RK_Debug_Mutex;
 
@@ -165,45 +146,7 @@ void RKDebug (int flags, int level, const char *fmt, ...) {
 installation path(s) for this platform. */
 QString resolveRSpecOrFail (QString input, const QString &message) {
 	if (input == QLatin1String ("auto")) {
-		QString ret;
-#ifdef Q_OS_MACOS
-		QString instroot ("/Library/Frameworks/R.framework/Versions");
-		if (QFileInfo (instroot).isReadable ()) {
-			QDir dir (instroot);
-			QStringList candidates = dir.entryList ();
-			QVersionNumber highest (0, 0, 0);
-			for (int i = candidates.count () - 1; i >= 0; --i) {
-				QString found = findExeAtPath ("Resources/bin/R", dir.absoluteFilePath (candidates[i]));
-				if (!found.isNull()) {
-					QVersionNumber version = QVersionNumber::fromString (candidates[i]);
-					if (version > highest) {
-						ret = found;
-					}
-				}
-			}
-		}
-#endif
-#ifdef Q_OS_WIN
-		QString instroot = QString (getenv ("PROGRAMFILES")) + "/R";
-		if (!QFileInfo (instroot).isReadable ()) instroot = QString (getenv ("PROGRAMFILES(x86)")) + "/R";
-		if (QFileInfo (instroot).isReadable ()) {
-			QDir dir (instroot);
-			QStringList candidates = dir.entryList (QStringList ("R-*"), QDir::Dirs);
-			QVersionNumber highest (0, 0, 0);
-			for (int i = candidates.count () - 1; i >= 0; --i) {
-				QString found = findExeAtPath ("bin/R", dir.absoluteFilePath (candidates[i]));
-				if (!found.isNull()) {
-					QVersionNumber version = QVersionNumber::fromString (candidates[i].mid (2));
-					if (version > highest) {
-						ret = found;
-					}
-				}
-			}
-		}
-#endif
-		// On Unix, but also, if R was not found in the default locations on Windows or Mac,
-		// try to find R in the system path.
-		if (ret.isNull ()) ret = QStandardPaths::findExecutable ("R");
+		QString ret = RKSessionVars::findRInstallations().value(0);
 
 		if (ret.isNull() || !QFileInfo (ret).isExecutable()) {
 			QMessageBox::critical(nullptr, i18n("Unable to detect R installation"), i18n("RKWard failed to detect an R installation on this system. Either R is not installed, or not at one of the standard installation locations. You can use the command line parameter '--r-executable <i>auto / PATH_TO_R</i>', or supply an rkward.ini file to specify a non-standard location."));
diff --git a/rkward/rbackend/rksessionvars.cpp b/rkward/rbackend/rksessionvars.cpp
index b89c683ba..1b0f9b14f 100644
--- a/rkward/rbackend/rksessionvars.cpp
+++ b/rkward/rbackend/rksessionvars.cpp
@@ -1,6 +1,6 @@
 /*
 rksessionvars - This file is part of RKWard (https://rkward.kde.org). Created: Thu Sep 08 2011
-SPDX-FileCopyrightText: 2011-2020 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileCopyrightText: 2011-2024 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
 */
@@ -19,6 +19,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <QStandardPaths>
 #include <QSysInfo>
 #include <QFileInfo>
+#include <QVersionNumber>
+#include <QDir>
 
 #include "../debug.h"
 
@@ -105,3 +107,52 @@ QStringList RKSessionVars::frontendSessionInfo () {
 	return lines;
 }
 
+#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
+static QString findExeAtPath (const QString &appname, const QString &path) {
+	QDir dir (path);
+	dir.makeAbsolute ();
+	if (QFileInfo (dir.filePath (appname)).isExecutable ()) return dir.filePath (appname);
+#ifdef Q_OS_WIN
+	if (QFileInfo (dir.filePath (appname + ".exe")).isExecutable ()) return dir.filePath (appname + ".exe");
+	if (QFileInfo (dir.filePath (appname + ".com")).isExecutable ()) return dir.filePath (appname + ".com");
+#endif
+	return QString ();
+}
+
+/** glob dirs instroot/prefix-* /rpath, sorted by version number represented in "*" */
+static QStringList globVersionedDirs(const QString &instroot, const QString &prefix, const QString &rpath) {
+	QStringList ret;
+	if (QFileInfo(instroot).isReadable()) {
+		QDir dir(instroot);
+		QStringList candidates = dir.entryList(QStringList(prefix + "*"), QDir::Dirs);
+		std::sort(candidates.begin(), candidates.end(), [prefix](const QString&a, const QString& b) -> bool {
+			return QVersionNumber::fromString(a.mid(prefix.length())) > QVersionNumber::fromString(b.mid(prefix.length()));
+		});
+		for (int i = 0; i < candidates.count(); ++i) {
+			QString found = findExeAtPath(rpath, dir.absoluteFilePath(candidates[i]));
+			if (!found.isNull()) ret.append(found);
+		}
+	}
+	return ret;
+}
+#endif
+
+QStringList RKSessionVars::findRInstallations() {
+	QStringList ret;
+#if defined(Q_OS_MACOS)
+	ret = globVersionedDirs("/Library/Frameworks/R.framework/Versions", QString(), "Resources/bin/R");
+#elif defined(Q_OS_WIN)
+	QString instroot = QString(getenv("PROGRAMFILES")) + "/R";
+	if (!QFileInfo(instroot).isReadable()) instroot = QString(getenv("PROGRAMFILES(x86)")) + "/R";
+	ret = globVersionedDirs(instroot, "R-", "bin/R");
+#else
+	const QStringList candidates{"/usr/bin/R", "/usr/local/bin/R"};
+	for(const QString &p : candidates) {
+		if (!ret.contains(p) && QFileInfo(p).isExecutable()) ret.append(p);
+	}
+#endif
+	// On Unix, but also, if R was not found in the default locations try to find R in the system path.
+	QString r = QStandardPaths::findExecutable("R");
+	if (!(r.isNull() || ret.contains(r))) ret.append(r);
+	return ret;
+}
diff --git a/rkward/rbackend/rksessionvars.h b/rkward/rbackend/rksessionvars.h
index e88832d85..79a2d64f6 100644
--- a/rkward/rbackend/rksessionvars.h
+++ b/rkward/rbackend/rksessionvars.h
@@ -37,6 +37,7 @@ is returned as suffix (via the suffix pointer; if that is 0, an error is reporte
 	static QString RBinary() { return r_binary; }
 	static bool runningInAppImage() { return !appimagedir.isNull(); }
 	static bool isPathInAppImage(const QString &path);
+	static QStringList findRInstallations();
 Q_SIGNALS:
 	void installedPackagesChanged ();
 protected:



More information about the rkward-tracker mailing list