[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