diff --git a/rkward/dialogs/rkloadlibsdialog.cpp b/rkward/dialogs/rkloadlibsdialog.cpp
index 3c125a2f..f31e6398 100644
--- a/rkward/dialogs/rkloadlibsdialog.cpp
+++ b/rkward/dialogs/rkloadlibsdialog.cpp
@@ -1,1323 +1,1328 @@
/***************************************************************************
rkloadlibsdialog - description
-------------------
begin : Mon Sep 6 2004
- copyright : (C) 2004 - 2018 by Thomas Friedrichsmeier
+ copyright : (C) 2004 - 2020 by Thomas Friedrichsmeier
email : thomas.friedrichsmeier@kdemail.net
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "rkloadlibsdialog.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../rkglobals.h"
#include "../rbackend/rkrinterface.h"
#include "../settings/rksettingsmodulegeneral.h"
#include "../settings/rksettings.h"
#include "../core/robjectlist.h"
#include "../misc/rkprogresscontrol.h"
#include "../misc/rkstandardicons.h"
#include "../misc/rkcommonfunctions.h"
#include "../misc/rkdynamicsearchline.h"
#include "../debug.h"
#include
#define GET_CURRENT_LIBLOCS_COMMAND 1
RKLoadLibsDialog::RKLoadLibsDialog (QWidget *parent, RCommandChain *chain, bool modal) : KPageDialog (parent) {
RK_TRACE (DIALOGS);
RKLoadLibsDialog::chain = chain;
installation_process = 0;
setFaceType (KPageDialog::Tabbed);
setModal (modal);
setWindowTitle (i18n ("Configure Packages"));
setStandardButtons (QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel);
LoadUnloadWidget *luwidget = new LoadUnloadWidget (this);
addChild (luwidget, i18n ("Load / Unload R packages"));
connect (this, &RKLoadLibsDialog::installedPackagesChanged, luwidget, &LoadUnloadWidget::updateInstalledPackages);
install_packages_widget = new InstallPackagesWidget (this);
install_packages_pageitem = addChild (install_packages_widget, i18n ("Install / Update / Remove R packages"));
configure_pluginmaps_pageitem = addChild (new RKPluginMapSelectionWidget (this), i18n ("Manage RKWard Plugins"));
connect (this, &KPageDialog::currentPageChanged, this, &RKLoadLibsDialog::slotPageChanged);
QTimer::singleShot (0, this, SLOT (slotPageChanged()));
num_child_widgets = 4;
was_accepted = false;
RKGlobals::rInterface ()->issueCommand (".libPaths ()", RCommand::App | RCommand::GetStringVector, QString (), this, GET_CURRENT_LIBLOCS_COMMAND, chain);
}
RKLoadLibsDialog::~RKLoadLibsDialog () {
RK_TRACE (DIALOGS);
if (was_accepted) KPageDialog::accept ();
else KPageDialog::reject ();
}
KPageWidgetItem* RKLoadLibsDialog::addChild (QWidget *child_page, const QString &caption) {
RK_TRACE (DIALOGS);
// TODO: Can't convert these signal/slot connections to new syntax, without creating a common base class for the child pages
connect (this, SIGNAL (accepted()), child_page, SLOT (ok()));
connect (button (QDialogButtonBox::Apply), SIGNAL (clicked(bool)), child_page, SLOT (apply()));
connect (this, SIGNAL(rejected()), child_page, SLOT (cancel()));
connect (child_page, &QObject::destroyed, this, &RKLoadLibsDialog::childDeleted);
return addPage (child_page, caption);
}
void RKLoadLibsDialog::slotPageChanged () {
RK_TRACE (DIALOGS);
if (!currentPage ()) return;
QTimer::singleShot (0, currentPage ()->widget (), SLOT (activated()));
}
//static
-void RKLoadLibsDialog::showInstallPackagesModal (QWidget *parent, RCommandChain *chain, const QString &package_name) {
+void RKLoadLibsDialog::showInstallPackagesModal (QWidget *parent, RCommandChain *chain, const QStringList &package_names) {
RK_TRACE (DIALOGS);
RKLoadLibsDialog *dialog = new RKLoadLibsDialog (parent, chain, true);
- dialog->auto_install_package = package_name;
- QTimer::singleShot (0, dialog, SLOT (automatedInstall())); // to get past the dialog->exec, below
+ QTimer::singleShot (0, [dialog, package_names]() { dialog->automatedInstall(package_names); }); // to get past the dialog->exec, below
dialog->setCurrentPage (dialog->install_packages_pageitem);
dialog->exec ();
RK_TRACE (DIALOGS);
}
// static
void RKLoadLibsDialog::showPluginmapConfig (QWidget* parent, RCommandChain* chain) {
RK_TRACE (DIALOGS);
RKLoadLibsDialog *dialog = new RKLoadLibsDialog (parent, chain, false);
dialog->setCurrentPage (dialog->configure_pluginmaps_pageitem);
dialog->show ();
}
-void RKLoadLibsDialog::automatedInstall () {
+void RKLoadLibsDialog::automatedInstall(const QStringList &packages) {
RK_TRACE (DIALOGS);
- install_packages_widget->initialize ();
- install_packages_widget->trySelectPackage (auto_install_package);
+ install_packages_widget->initialize();
+ install_packages_widget->trySelectPackages(packages);
}
void RKLoadLibsDialog::tryDestruct () {
RK_TRACE (DIALOGS);
if (num_child_widgets <= 0) {
deleteLater ();
}
}
void RKLoadLibsDialog::childDeleted () {
RK_TRACE (DIALOGS);
--num_child_widgets;
tryDestruct ();
}
void RKLoadLibsDialog::closeEvent (QCloseEvent *e) {
RK_TRACE (DIALOGS);
e->accept ();
// do as if cancel button was clicked:
reject ();
}
void RKLoadLibsDialog::accept () {
was_accepted = true;
hide ();
// will self-destruct once all children are done
emit (accepted());
}
void RKLoadLibsDialog::reject () {
was_accepted = false;
hide ();
// will self-destruct once all children are done
emit (rejected());
}
void RKLoadLibsDialog::rCommandDone (RCommand *command) {
RK_TRACE (DIALOGS);
if (command->getFlags () == GET_CURRENT_LIBLOCS_COMMAND) {
RK_ASSERT (command->getDataType () == RData::StringVector);
RK_ASSERT (command->getDataLength () > 0);
// NOTE: The problem is that e.g. R_LIBS_USER is not in .libPaths() if it does not exist, yet. But it should be available as an option, of course
library_locations = command->stringVector ();
emit (libraryLocationsChanged (library_locations));
} else {
RK_ASSERT (false);
}
}
void RKLoadLibsDialog::addLibraryLocation (const QString& new_loc) {
RK_TRACE (DIALOGS);
if (library_locations.contains (new_loc)) return;
if (QDir ().mkpath (new_loc)) RKSettingsModuleRPackages::addLibraryLocation (new_loc, chain);
library_locations.prepend (new_loc);
emit (libraryLocationsChanged (library_locations));
}
bool RKLoadLibsDialog::removePackages (QStringList packages, QStringList from_liblocs) {
RK_TRACE (DIALOGS);
bool as_root = false;
QStringList not_removing;
QStringList not_writable;
QList not_writable_int;
for (int i = 0; i < packages.count (); ++i) {
if (RKSettingsModuleRPackages::essentialPackages ().contains (packages[i])) {
not_removing.append (i18n ("Package %1 at %2", packages[i], from_liblocs[i]));
packages.removeAt (i);
from_liblocs.removeAt (i);
--i;
} else {
QFileInfo fi (from_liblocs[i]);
if (!fi.isWritable ()) {
not_writable.append (i18n ("Package %1 at %2", packages[i], from_liblocs[i]));
not_writable_int.append (i);
}
}
}
if (!not_removing.isEmpty ()) {
KMessageBox::informationList (this, i18n ("The following packages, which you have selected for removal, are essential to the operation of RKWard, and will not be removed. If you are absolutely sure, that you want to remove these packages, please do so on the R command line."), not_removing, i18n ("Not removing certain packages"));
}
if (packages.isEmpty ()) return false;
if (!not_writable.isEmpty ()) {
#ifdef Q_OS_WIN
KMessageBox::informationList (this, i18n ("Your current user permissions do not allow removing the following packages. These will be skipped."), not_writable, i18n ("Insufficient user permissions"));
int res = KMessageBox::No;
#else
int res = KMessageBox::questionYesNoList (this, i18n ("Your current user permissions do not allow removing the following packages. Do you want to skip these packages, or do you want to proceed with administrator privileges (you will be prompted for the password)?"), not_writable, i18n ("Insufficient user permissions"), KGuiItem ("Become root"), KGuiItem ("Skip these packages"));
#endif
if (res == KMessageBox::Yes) as_root = true;
else {
for (int i = not_writable_int.count () - 1; i >= 0; --i) {
packages.removeAt (not_writable_int[i]);
from_liblocs.removeAt (not_writable_int[i]);
}
}
}
if (packages.isEmpty ()) return false;
RK_ASSERT (packages.count () == from_liblocs.count ());
QStringList descriptions;
QString command;
for (int i = 0; i < packages.count (); ++i) {
descriptions.append (i18n ("Package %1 at %2", packages[i], from_liblocs[i]));
// NOTE: the "lib"-parameter to remove.packages is NOT matched to the pkgs-parameter. Therefore, we simply concatenate a bunch of single removals.
command.append ("remove.packages (" + RObject::rQuote (packages[i]) + ", " + RObject::rQuote (from_liblocs[i]) + ")\n");
}
// last check. This may be an annoying third dialog, in the worst case, but at least it can be turned off.
int res = KMessageBox::warningContinueCancelList (this, i18n ("You are about to remove the following packages. Are you sure you want to proceed?"), descriptions, i18n ("About to remove packages"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "remove_packages_warning");
if (res != KMessageBox::Continue) return false;
runInstallationCommand (command, as_root, i18n ("Please stand by while removing selected packages"), i18n ("Removing packages"));
return true;
}
#ifdef Q_OS_WIN
extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
#endif
bool RKLoadLibsDialog::installPackages (const QStringList &packages, QString to_libloc, bool install_suggested_packages, const QStringList& repos) {
RK_TRACE (DIALOGS);
if (packages.isEmpty ()) return false;
bool as_root = false;
// It is ok, if the selected location does not yet exist. In order to know, whether we can write to it, we have to create it first.
QDir().mkpath (to_libloc);
QString altlibloc = RKSettingsModuleRPackages::addUserLibLocTo (library_locations).value (0);
#ifdef Q_OS_WIN
extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
qt_ntfs_permission_lookup++;
#endif
QFileInfo fi = QFileInfo (to_libloc);
bool writable = fi.isWritable ();
#ifdef Q_OS_WIN
qt_ntfs_permission_lookup--;
#endif
if (!writable) {
QString mcaption = i18n ("Selected library location not writable");
QString message = i18n ("
The directory you have selected for installation (%1) is not writable with your current user permissions.
"
"
Would you like to install to %2, instead (you can also press \"Cancel\" and use the \"Configure Repositories\"-button to set up a different directory)?
Alternatively, if you have access to an administrator account on this machine, you can use that to install the package(s), or "
"you could change the permissions of '%1'. Sorry, automatic switching to Administrator is not yet supported in RKWard on Windows.
", to_libloc));
int res = KMessageBox::warningContinueCancel (this, message, mcaption, KGuiItem (i18n ("Install to %1", altlibloc)));
if (res == KMessageBox::Continue) to_libloc = altlibloc;
#else
message.append (i18n ("
Alternatively, if you are the administrator of this machine, you can try to install the packages as root (you'll be prompted for the root password).
"));
int res = KMessageBox::warningYesNoCancel (this, message, mcaption, KGuiItem (i18n ("Install to %1", altlibloc)), KGuiItem (i18n ("Become root")));
if (res == KMessageBox::Yes) to_libloc = altlibloc;
if (res == KMessageBox::No) as_root = true;
#endif
if (res == KMessageBox::Cancel) return false;
}
addLibraryLocation (to_libloc);
QString command_string = "install.packages (c (\"" + packages.join ("\", \"") + "\")" + ", lib=" + RObject::rQuote (to_libloc);
QString downloaddir = QDir (RKSettingsModuleGeneral::filesPath ()).filePath ("package_archive");
if (RKSettingsModuleRPackages::archivePackages ()) {
QDir (RKSettingsModuleGeneral::filesPath ()).mkdir ("package_archive");
command_string += ", destdir=" + RObject::rQuote (downloaddir);
}
if (install_suggested_packages) command_string += ", dependencies=TRUE";
command_string += ")\n";
QString repos_string = "options (repos= c(";
for (int i = 0; i < repos.count (); ++i) {
if (i) repos_string.append (", ");
repos_string.append (RObject::rQuote (repos[i]));
}
repos_string.append ("))\n");
repos_string.append (RKSettingsModuleRPackages::pkgTypeOption ());
if (as_root) {
KUser user;
command_string.append ("system (\"chown " + user.loginName() + ' ' + downloaddir + "/*\")\n");
}
runInstallationCommand (repos_string + command_string, as_root, i18n ("Please stand by while installing selected packages"), i18n ("Installing packages"));
return true;
}
void RKLoadLibsDialog::runInstallationCommand (const QString& command, bool as_root, const QString& message, const QString& title) {
RK_TRACE (DIALOGS);
// TODO: won't work with some versions of GCC (which ones exactly)?
// QFile file (QDir (RKSettingsModuleGeneral::filesPath ()).filePath ("install_script.R"));
// WORKAROUND:
QDir dir = RKSettingsModuleGeneral::filesPath ();
QFile file (dir.filePath ("install_script.R"));
// WORKADOUND END
if (file.open (QIODevice::WriteOnly)) {
QTextStream stream (&file);
stream << command;
stream << "q ()\n";
file.close();
} else {
RK_ASSERT (false);
}
QString R_binary (getenv ("R_BINARY"));
QString call;
QStringList params;
#ifdef Q_OS_WIN
RK_ASSERT (!as_root);
call = R_binary;
#else
if (as_root) {
call = QStandardPaths::findExecutable ("kdesu");
if (call.isEmpty ()) call = QStandardPaths::findExecutable ("kdesudo");
params << "-t" << "--" << R_binary;
} else {
call = R_binary;
}
#endif
params << "--no-save" << "--no-restore" << "--file=" + file.fileName ();
installation_process = new QProcess ();
installation_process->setProcessChannelMode (QProcess::SeparateChannels);
connect (installation_process, static_cast(&QProcess::finished), this, &RKLoadLibsDialog::processExited);
connect (installation_process, &QProcess::readyReadStandardOutput, this, &RKLoadLibsDialog::installationProcessOutput);
connect (installation_process, &QProcess::readyReadStandardError, this, &RKLoadLibsDialog::installationProcessError);
RKProgressControl *installation_progress = new RKProgressControl (this, message, title, RKProgressControl::CancellableProgress);
connect (this, &RKLoadLibsDialog::installationComplete, installation_progress, &RKProgressControl::done);
connect (this, &RKLoadLibsDialog::installationOutput, installation_progress, static_cast(&RKProgressControl::newOutput));
connect (this, &RKLoadLibsDialog::installationError, installation_progress, &RKProgressControl::newError);
installation_process->start (call, params, QIODevice::ReadWrite | QIODevice::Unbuffered);
if (!installation_progress->doModal (true)) {
installation_process->kill ();
installation_process->waitForFinished (5000);
}
file.remove ();
delete installation_process;
installation_process = 0;
emit (installedPackagesChanged ());
}
void RKLoadLibsDialog::installationProcessOutput () {
RK_TRACE (DIALOGS);
RK_ASSERT (installation_process);
emit (installationOutput (QString::fromLocal8Bit (installation_process->readAllStandardOutput())));
}
void RKLoadLibsDialog::installationProcessError () {
RK_TRACE (DIALOGS);
RK_ASSERT (installation_process);
emit (installationError (QString::fromLocal8Bit (installation_process->readAllStandardError ())));
}
void RKLoadLibsDialog::processExited (int exitCode, QProcess::ExitStatus exitStatus) {
RK_TRACE (DIALOGS);
if (exitCode || (exitStatus != QProcess::NormalExit)) {
installationError ('\n' + i18n ("Installation process died with exit code %1", exitCode));
}
emit (installationComplete ());
}
////////////////////// LoadUnloadWidget ////////////////////////////
#define GET_INSTALLED_PACKAGES 1
#define GET_LOADED_PACKAGES 2
#define LOAD_PACKAGE_COMMAND 3
LoadUnloadWidget::LoadUnloadWidget (RKLoadLibsDialog *dialog) : QWidget (0) {
RK_TRACE (DIALOGS);
LoadUnloadWidget::parent = dialog;
QVBoxLayout *mvbox = new QVBoxLayout (this);
mvbox->setContentsMargins (0, 0, 0, 0);
QHBoxLayout *hbox = new QHBoxLayout ();
mvbox->addLayout (hbox);
hbox->setContentsMargins (0, 0, 0, 0);
QVBoxLayout *instvbox = new QVBoxLayout ();
hbox->addLayout (instvbox);
instvbox->setContentsMargins (0, 0, 0, 0);
QVBoxLayout *buttonvbox = new QVBoxLayout ();
hbox->addLayout (buttonvbox);
buttonvbox->setContentsMargins (0, 0, 0, 0);
QVBoxLayout *loadedvbox = new QVBoxLayout ();
hbox->addLayout (loadedvbox);
loadedvbox->setContentsMargins (0, 0, 0, 0);
QLabel *label = new QLabel (i18n ("Installed packages"), this);
installed_view = new QTreeWidget (this);
installed_view->setHeaderLabels (QStringList () << i18n ("Name") << i18n ("Title") << i18n ("Version") << i18n ("Location"));
installed_view->setSelectionMode (QAbstractItemView::ExtendedSelection);
instvbox->addWidget (label);
instvbox->addWidget (installed_view);
load_button = new QPushButton (RKStandardIcons::getIcon (RKStandardIcons::ActionAddRight), i18n ("Load"), this);
connect (load_button, &QPushButton::clicked, this, &LoadUnloadWidget::loadButtonClicked);
detach_button = new QPushButton (RKStandardIcons::getIcon (RKStandardIcons::ActionRemoveLeft), i18n ("Unload"), this);
connect (detach_button, &QPushButton::clicked, this, &LoadUnloadWidget::detachButtonClicked);
buttonvbox->addStretch (1);
buttonvbox->addWidget (load_button);
buttonvbox->addWidget (detach_button);
buttonvbox->addStretch (2);
label = new QLabel (i18n ("Loaded packages"), this);
loaded_view = new QTreeWidget (this);
loaded_view->setHeaderLabel (i18n ("Name"));
loaded_view->setSelectionMode (QAbstractItemView::ExtendedSelection);
loadedvbox->addWidget (label);
loadedvbox->addWidget (loaded_view);
connect (loaded_view, &QTreeWidget::itemSelectionChanged, this, &LoadUnloadWidget::updateButtons);
connect (installed_view, &QTreeWidget::itemSelectionChanged, this, &LoadUnloadWidget::updateButtons);
updateInstalledPackages ();
updateButtons ();
}
LoadUnloadWidget::~LoadUnloadWidget () {
RK_TRACE (DIALOGS);
}
void LoadUnloadWidget::activated () {
RK_TRACE (DIALOGS);
installed_view->setFocus ();
}
void LoadUnloadWidget::rCommandDone (RCommand *command) {
RK_TRACE (DIALOGS);
if (command->failed ()) return;
if (command->getFlags () == GET_INSTALLED_PACKAGES) {
RK_ASSERT (command->getDataLength () == 5);
RData::RDataStorage data = command->structureVector ();
QStringList package = data.at (0)->stringVector ();
QStringList title = data.at (1)->stringVector ();
QStringList version = data.at (2)->stringVector ();
QStringList libpath = data.at (3)->stringVector ();
int count = package.size ();
RK_ASSERT (count == title.size ());
RK_ASSERT (count == version.size ());
RK_ASSERT (count == libpath.size ());
for (int i=0; i < count; ++i) {
QTreeWidgetItem* item = new QTreeWidgetItem (installed_view);
item->setText (0, package.at (i));
item->setText (1, title.at (i));
item->setText (2, version.at (i));
item->setText (3, libpath.at (i));
}
installed_view->resizeColumnToContents (0);
installed_view->setSortingEnabled (true);
installed_view->sortItems (0, Qt::AscendingOrder);
} else if (command->getFlags () == GET_LOADED_PACKAGES) {
RK_ASSERT (command->getDataType () == RData::StringVector);
QStringList data = command->stringVector ();
for (int i=0; i < data.size (); ++i) {
QTreeWidgetItem* item = new QTreeWidgetItem (loaded_view);
item->setText (0, data.at (i));
if (RKSettingsModuleRPackages::essentialPackages ().contains (data.at (i))) {
item->setFlags (Qt::NoItemFlags);
}
}
loaded_view->resizeColumnToContents (0);
loaded_view->setSortingEnabled (true);
loaded_view->sortItems (0, Qt::AscendingOrder);
updateCurrentList ();
} else if (command->getFlags () == LOAD_PACKAGE_COMMAND) {
emit (loadUnloadDone ());
} else {
RK_ASSERT (false);
}
}
void LoadUnloadWidget::updateInstalledPackages () {
RK_TRACE (DIALOGS);
installed_view->clear ();
loaded_view->clear ();
RKGlobals::rInterface ()->issueCommand (".rk.get.installed.packages ()", RCommand::App | RCommand::Sync | RCommand::GetStructuredData, QString (), this, GET_INSTALLED_PACKAGES, parent->chain);
RKGlobals::rInterface ()->issueCommand (".packages ()", RCommand::App | RCommand::Sync | RCommand::GetStringVector, QString (), this, GET_LOADED_PACKAGES, parent->chain);
}
void LoadUnloadWidget::loadButtonClicked () {
RK_TRACE (DIALOGS);
loaded_view->clearSelection ();
QList sel = installed_view->selectedItems ();
for (int i = 0; i < sel.size (); ++i) {
QString package_name = sel[i]->text (0);
// is this package already loaded?
QList loaded = loaded_view->findItems (package_name, Qt::MatchExactly, 0);
if (loaded.isEmpty ()) {
QTreeWidgetItem* item = new QTreeWidgetItem (loaded_view);
item->setText (0, package_name);
item->setSelected (true);
}
}
}
void LoadUnloadWidget::detachButtonClicked () {
RK_TRACE (DIALOGS);
installed_view->clearSelection ();
QList sel = loaded_view->selectedItems ();
for (int i = 0; i < sel.size (); ++i) {
QString package_name = sel[i]->text (0);
delete (sel[i]); // remove from list of loaded packages
// select corresponding package in list of available packages
QList installed = installed_view->findItems (package_name, Qt::MatchExactly, 0);
if (!installed.isEmpty ()) {
//RK_ASSERT (installed.count () == 1); // In fact, several versions of one package can be installed in several library locations
installed[0]->setSelected (true);
}
}
}
void LoadUnloadWidget::updateButtons () {
RK_TRACE (DIALOGS);
detach_button->setEnabled (!loaded_view->selectedItems ().isEmpty ());
load_button->setEnabled (!installed_view->selectedItems ().isEmpty ());
}
void LoadUnloadWidget::ok () {
RK_TRACE (DIALOGS);
doLoadUnload ();
deleteLater ();
}
void LoadUnloadWidget::updateCurrentList () {
RK_TRACE (DIALOGS);
prev_packages.clear ();
for (int i = 0; i < loaded_view->topLevelItemCount (); ++i) {
prev_packages.append (loaded_view->topLevelItem (i)->text (0));
}
}
void LoadUnloadWidget::doLoadUnload () {
RK_TRACE (DIALOGS);
RKProgressControl *control = new RKProgressControl (this, i18n ("There has been an error while trying to load / unload packages. See transcript below for details"), i18n ("Error while handling packages"), RKProgressControl::DetailedError);
connect (this, &LoadUnloadWidget::loadUnloadDone, control, &RKProgressControl::done);
// load packages previously not loaded
for (int i = 0; i < loaded_view->topLevelItemCount (); ++i) {
QTreeWidgetItem* loaded = loaded_view->topLevelItem (i);
if (!prev_packages.contains (loaded->text (0))) {
RCommand *command = new RCommand ("library (\"" + loaded->text (0) + "\")", RCommand::App | RCommand::ObjectListUpdate);
control->addRCommand (command);
RKGlobals::rInterface ()->issueCommand (command, parent->chain);
}
}
// detach packages previously attached
QStringList packages_to_remove;
for (QStringList::Iterator it = prev_packages.begin (); it != prev_packages.end (); ++it) {
QList loaded = loaded_view->findItems ((*it), Qt::MatchExactly, 0);
if (loaded.isEmpty ()) { // no longer in the list
packages_to_remove.append ("package:" + *it);
}
}
if (!packages_to_remove.isEmpty ()) {
QStringList messages = RObjectList::getObjectList ()->detachPackages (packages_to_remove, parent->chain, control);
if (!messages.isEmpty ()) KMessageBox::sorry (this, messages.join ("\n"));
}
// find out, when we're done
RCommand *command = new RCommand (QString (), RCommand::EmptyCommand, QString (), this, LOAD_PACKAGE_COMMAND);
RKGlobals::rInterface ()->issueCommand (command, parent->chain);
control->doNonModal (true);
}
void LoadUnloadWidget::apply () {
RK_TRACE (DIALOGS);
doLoadUnload ();
updateCurrentList ();
}
void LoadUnloadWidget::cancel () {
RK_TRACE (DIALOGS);
deleteLater ();
}
/////////////////////// InstallPackagesWidget //////////////////////////
#include
#include
/** Responsible for drawing the "category" items */
class InstallPackagesDelegate : public QStyledItemDelegate {
public:
InstallPackagesDelegate (QTreeView* parent) : QStyledItemDelegate (parent) {
table = parent;
expanded = RKStandardIcons::getIcon (RKStandardIcons::ActionCollapseUp);
collapsed = RKStandardIcons::getIcon (RKStandardIcons::ActionExpandDown);
}
void initStyleOption (QStyleOptionViewItem* option, const QModelIndex& index) const override {
QStyledItemDelegate::initStyleOption (option, index);
if (!index.parent ().isValid ()) {
int ccount = index.model ()->rowCount (index);
option->text = option->text + " (" + QString::number (ccount) + ')';
if (ccount) {
option->icon = table->isExpanded (index) ? expanded : collapsed;
} else {
option->icon = QIcon (); // empty dummy icon to reserve space
}
option->features |= QStyleOptionViewItem::HasDecoration;
option->font.setBold (true);
option->backgroundBrush = table->palette ().mid ();
}
}
QTreeView* table;
QIcon expanded;
QIcon collapsed;
};
InstallPackagesWidget::InstallPackagesWidget (RKLoadLibsDialog *dialog) : QWidget (0) {
RK_TRACE (DIALOGS);
InstallPackagesWidget::parent = dialog;
QHBoxLayout *hbox = new QHBoxLayout (this);
hbox->setContentsMargins (0, 0, 0, 0);
QVBoxLayout *vbox = new QVBoxLayout ();
vbox->setContentsMargins (0, 0, 0, 0);
hbox->addLayout (vbox);
hbox->setStretchFactor (vbox, 2);
packages_status = new RKRPackageInstallationStatus (this);
packages_view = new QTreeView (this);
packages_view->setSortingEnabled (true);
model = new RKRPackageInstallationStatusSortFilterModel (this);
model->setSourceModel (packages_status);
model->setSortCaseSensitivity (Qt::CaseInsensitive);
packages_view->setModel (model);
packages_view->setItemDelegateForColumn (0, new InstallPackagesDelegate (packages_view));
for (int i = 0; i < model->rowCount (); ++i) { // the root level captions
packages_view->setFirstColumnSpanned (i, QModelIndex (), true);
}
connect (packages_view, &QTreeView::clicked, this, &InstallPackagesWidget::rowClicked);
packages_view->setRootIsDecorated (false);
packages_view->setIndentation (0);
packages_view->setEnabled (false);
packages_view->setMinimumHeight (packages_view->sizeHintForRow (0) * 15); // force a decent height
packages_view->setMinimumWidth (packages_view->fontMetrics ().width ("This is to force a sensible min width for the packages view (empty on construction)"));
vbox->addWidget (packages_view);
QPushButton *configure_repos_button = new QPushButton (i18n ("Configure Repositories"), this);
RKCommonFunctions::setTips (i18n ("Many packages are available on CRAN (Comprehensive R Archive Network), and other repositories. Click this to add more sources."), configure_repos_button);
connect (configure_repos_button, &QPushButton::clicked, this, &InstallPackagesWidget::configureRepositories);
vbox->addWidget (configure_repos_button);
QVBoxLayout *buttonvbox = new QVBoxLayout ();
hbox->addLayout (buttonvbox);
buttonvbox->setContentsMargins (0, 0, 0, 0);
QLabel *label = new QLabel (i18n ("Show only packages matching:"), this);
filter_edit = new RKDynamicSearchLine (this);
RKCommonFunctions::setTips (i18n ("
You can limit the packages displayed in the list to with names or titles matching a filter string.
") + filter_edit->regexpTip (), label, filter_edit);
filter_edit->setModelToFilter (model);
// NOTE: Although the search line sets the filter in the model, automatically, we connect it, here, in order to expand new and updateable sections, when the filter changes.
connect (filter_edit, &RKDynamicSearchLine::searchChanged, this, &InstallPackagesWidget::filterChanged);
rkward_packages_only = new QCheckBox (i18n ("Show only packages providing RKWard dialogs"), this);
RKCommonFunctions::setTips (i18n ("
Some but not all R packages come with plugins for RKWard. That means they provide a graphical user-interface in addition to R functions. Check this box to show only such packages.
"), rkward_packages_only);
connect (rkward_packages_only, &QCheckBox::stateChanged, this, &InstallPackagesWidget::filterChanged);
filterChanged ();
mark_all_updates_button = new QPushButton (i18n ("Select all updates"), this);
connect (mark_all_updates_button, &QPushButton::clicked, this, &InstallPackagesWidget::markAllUpdates);
install_params = new PackageInstallParamsWidget (this);
connect (parent, &RKLoadLibsDialog::libraryLocationsChanged, install_params, &PackageInstallParamsWidget::liblocsChanged);
buttonvbox->addWidget (label);
buttonvbox->addWidget (filter_edit);
buttonvbox->addWidget (rkward_packages_only);
buttonvbox->addStretch (1);
buttonvbox->addWidget (mark_all_updates_button);
buttonvbox->addStretch (1);
buttonvbox->addWidget (install_params);
buttonvbox->addStretch (1);
}
InstallPackagesWidget::~InstallPackagesWidget () {
RK_TRACE (DIALOGS);
}
void InstallPackagesWidget::activated () {
RK_TRACE (DIALOGS);
filter_edit->setFocus ();
if (!packages_status->initialized ()) {
initialize ();
packages_view->sortByColumn (RKRPackageInstallationStatus::PackageName, Qt::AscendingOrder);
}
}
void InstallPackagesWidget::initialize () {
RK_TRACE (DIALOGS);
packages_status->initialize (parent->chain);
packages_view->setEnabled (true);
// 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) {
packages_view->resizeColumnToContents (i);
}
// For whatever reason, we have to re-set these, here.
for (int i = 0; i < model->rowCount (); ++i) {
packages_view->setFirstColumnSpanned (i, QModelIndex (), true);
}
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
}
void InstallPackagesWidget::rowClicked (const QModelIndex& row) {
RK_TRACE (DIALOGS);
if (!row.parent ().isValid ()) {
QModelIndex fixed_row = model->index (row.row (), 0, row.parent ());
packages_view->setExpanded (fixed_row, !packages_view->isExpanded (fixed_row));
}
}
void InstallPackagesWidget::filterChanged () {
RK_TRACE (DIALOGS);
model->setRKWardOnly (rkward_packages_only->isChecked ());
packages_view->expand (model->mapFromSource (packages_status->index(RKRPackageInstallationStatus::UpdateablePackages, 0)));
packages_view->expand (model->mapFromSource (packages_status->index(RKRPackageInstallationStatus::NewPackages, 0)));
// NOTE: filter string already set by RKDynamicSearchLine
}
-void InstallPackagesWidget::trySelectPackage (const QString &package_name) {
+void InstallPackagesWidget::trySelectPackages (const QStringList &package_names) {
RK_TRACE (DIALOGS);
- QModelIndex index = packages_status->markPackageForInstallation (package_name);
- if (!index.isValid ()) {
- KMessageBox::sorry (0, i18n ("The package requested by the backend (\"%1\") was not found in the package repositories. Maybe the package name was mis-spelled. Or maybe you need to add additional repositories via the \"Configure Repositories\" button.", package_name), i18n ("Package not available"));
- } else {
- packages_view->scrollTo (model->mapFromSource (index));
+ QStringList failed_names;
+ for (int i = 0; i < package_names.size(); ++i) {
+ QModelIndex index = packages_status->markPackageForInstallation(package_names[i]);
+ if (!index.isValid()) {
+ failed_names.append(package_names[i]);
+ } else {
+ packages_view->scrollTo(model->mapFromSource(index));
+ }
+ }
+ 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"));
}
}
void InstallPackagesWidget::markAllUpdates () {
RK_TRACE (DIALOGS);
QModelIndex index = packages_status->markAllUpdatesForInstallation ();
packages_view->setExpanded (model->mapFromSource (index), true);
packages_view->scrollTo (model->mapFromSource (index));
}
void InstallPackagesWidget::doInstall (bool refresh) {
RK_TRACE (DIALOGS);
bool changed = false;
QStringList remove;
QStringList remove_locs;
packages_status->packagesToRemove (&remove, &remove_locs);
if (!remove.isEmpty ()) {
RK_ASSERT (remove.count () == remove_locs.count ());
changed |= parent->removePackages (remove, remove_locs);
}
QStringList install = packages_status->packagesToInstall ();
if (!install.isEmpty ()) {
QString dest = install_params->installLocation ();
if (!dest.isEmpty ()) {
changed |= parent->installPackages (install, dest, install_params->installSuggestedPackages (), packages_status->currentRepositories ());
}
}
if (changed && refresh) {
packages_status->clearStatus ();
initialize ();
}
}
void InstallPackagesWidget::apply () {
RK_TRACE (DIALOGS);
doInstall (true);
}
void InstallPackagesWidget::ok () {
RK_TRACE (DIALOGS);
doInstall (false);
deleteLater ();
}
void InstallPackagesWidget::cancel () {
RK_TRACE (DIALOGS);
deleteLater ();
}
void InstallPackagesWidget::configureRepositories () {
RK_TRACE (DIALOGS);
RKSettings::configureSettings (RKSettings::PageRPackages, this, parent->chain);
}
/////////////////////// PackageInstallParamsWidget //////////////////////////
#include
#include
PackageInstallParamsWidget::PackageInstallParamsWidget (QWidget *parent) : QWidget (parent) {
RK_TRACE (DIALOGS);
QVBoxLayout *vbox = new QVBoxLayout (this);
vbox->setContentsMargins (0, 0, 0, 0);
vbox->addWidget (new QLabel (i18n ("Install packages to:"), this));
libloc_selector = new QComboBox (this);
vbox->addWidget (libloc_selector);
suggested_packages = new QCheckBox (i18n ("Install suggested packages"), this);
suggested_packages->setChecked (false);
RKCommonFunctions::setTips (QString ("
%1
").arg (i18n ("Some packages \"suggest\" additional packages, which are not strictly necessary for using that package, but which may provide additional related functionality. Check this option to include such additional suggested packages.")), suggested_packages);
vbox->addStretch ();
vbox->addWidget (suggested_packages);
}
PackageInstallParamsWidget::~PackageInstallParamsWidget () {
RK_TRACE (DIALOGS);
}
bool PackageInstallParamsWidget::installSuggestedPackages () {
RK_TRACE (DIALOGS);
return suggested_packages->isChecked ();
}
QString PackageInstallParamsWidget::installLocation () {
RK_TRACE (DIALOGS);
return libloc_selector->currentText ();
}
void PackageInstallParamsWidget::liblocsChanged (const QStringList &newlist) {
RK_TRACE (DIALOGS);
libloc_selector->clear ();
libloc_selector->insertItems (0, RKSettingsModuleRPackages::addUserLibLocTo (newlist));
}
/////////// RKRPackageInstallationStatus /////////////////
RKRPackageInstallationStatus::RKRPackageInstallationStatus (QObject* parent) : QAbstractItemModel (parent) {
RK_TRACE (DIALOGS);
_initialized = false;
}
RKRPackageInstallationStatus::~RKRPackageInstallationStatus () {
RK_TRACE (DIALOGS);
}
QModelIndex RKRPackageInstallationStatus::markAllUpdatesForInstallation () {
RK_TRACE (DIALOGS);
// inefficient, but so what...
for (int i = updateable_packages_in_installed.count () - 1; i >= 0; --i) {
markPackageForInstallation (installed_packages[updateable_packages_in_installed[i]]);
}
return index (UpdateablePackages, 0, QModelIndex ());
}
QModelIndex RKRPackageInstallationStatus::markPackageForInstallation (const QString& package_name) {
RK_TRACE (DIALOGS);
// is the package available at all?
QModelIndex pindex;
int row = available_packages.indexOf (package_name);
if (row < 0) return pindex;
// find out, whether it is a new or and updateable package
QModelIndex parent;
int urow = updateable_packages_in_available.indexOf (row);
if (urow >= 0) {
parent = index (UpdateablePackages, 0);
row = urow;
} else {
row = new_packages_in_available.indexOf (row);
parent = index (NewPackages, 0);
}
if (row < 0) {
RK_ASSERT (false);
return pindex;
}
// mark for installation
pindex = index (row, InstallationStatus, parent);
setData (pindex, QVariant (Qt::Checked), Qt::CheckStateRole);
return pindex;
}
void RKRPackageInstallationStatus::initialize (RCommandChain *chain) {
RK_TRACE (DIALOGS);
_initialized = true; // will be re-set to false, should the command fail / be cancelled
RCommand *command = new RCommand (".rk.get.package.installation.state ()", RCommand::App | RCommand::GetStructuredData);
connect (command->notifier (), &RCommandNotifier::commandFinished, this, &RKRPackageInstallationStatus::statusCommandFinished);
RKProgressControl *control = new RKProgressControl (this, i18n ("
Please stand by while searching for installed and available packages.
Note: This requires a working internet connection, and may take some time, esp. if one or more repositories are temporarily unavailable.
The setup assistant has been invoked, automatically, because a problem has been detected in your setup.addWidget(RKCommonFunctions::wordWrappedLabel(intro));
auto waiting_to_start_label = RKCommonFunctions::wordWrappedLabel(i18n("Waiting for R backend...") + "
");
l->addWidget(waiting_to_start_label);
auto firstpageref = wizard->addPage (firstpage, i18n("RKWard Setup Assistant"));
wizard->setValid(firstpageref, false);
// Basic installation page
wizard->createStandardPage();
bool reinstallation_required = false;
- RKSetupWizardItem idir(i18n("Installation directory"));
+ auto idir = new RKSetupWizardItem(i18n("Installation directory"));
if (RKCommonFunctions::getRKWardDataDir ().isEmpty ()) {
- idir.setStatus(RKSetupWizardItem::Error, i18n("Not found."));
+ idir->setStatus(RKSetupWizardItem::Error, i18n("Not found."));
// TODO: test, whether links work, here
- idir.setLongLabel("
RKWard either could not find its resource files at all, or only an old version of those files. The most likely cause is that the last installation failed to place the files in the correct place. This can lead to all sorts of problems, from single missing features to complete failure to function.
");
- idir.addOption(i18n("Reinstallation required"), i18n("This problem cannot be corrected, automatically. You will have to reinstall RKWard."), [](RKSetupWizard*) {});
+ idir->setLongLabel("
RKWard either could not find its resource files at all, or only an old version of those files. The most likely cause is that the last installation failed to place the files in the correct place. This can lead to all sorts of problems, from single missing features to complete failure to function.
No plugins are enabled. This is probably not intended.
"));
+ pluginmaps->setStatus(RKSetupWizardItem::Warning, i18n("None selected"));
+ pluginmaps->addOption(i18n("Restore defaults"), i18n("Enable the default plugins"), [](RKSetupWizard*) { RKSettingsModulePlugins::registerDefaultPluginMaps(RKSettingsModulePlugins::AddIfDefault); });
+ pluginmaps->addOption(i18n("No change"), i18n("Proceed without plugins"), [](RKSetupWizard*) {});
// TODO: Also offer help, if a suspicioulsy small share of plugins is active? RKSettingsModulePlugins::knownUsablePluginCount();
} else {
- pluginmaps.setStatus(RKSetupWizardItem::Good, i18n("Found."));
+ pluginmaps->setStatus(RKSetupWizardItem::Good, i18n("Found."));
}
wizard->appendItem(pluginmaps);
- RKSetupWizardItem kateplugins(i18n("Kate plugins"));
+ auto kateplugins = new RKSetupWizardItem(i18n("Kate plugins"));
int kateplugincount = RKWardMainWindow::getMain()->katePluginIntegration()->knownPluginCount();
if (kateplugincount < 1) {
// TODO: Move to RKSettingsModuleKatePlugins::validateSettingsInteractive
- kateplugins.setLongLabel(i18n("
Important functionality in RKWard is provided by kate plugins. It looks like none are installed on this system. On Linux/BSD, this can usually be fixed by installing kate.
"));
- kateplugins.setStatus(RKSetupWizardItem::Error, i18n("None found"));
- kateplugins.addOption(i18n("(Attempt to) install kate"), i18n("Attempt to install kate using the muon package manager. If this is not available, please install kate, manuelly"), [](RKSetupWizard* wizard) { wizard->markExternalPackageForInstallation(QStringLiteral("kate"), true); });
- kateplugins.addOption(i18n("No change"), i18n("Proceed without plugins"), [](RKSetupWizard* wizard) { wizard->markExternalPackageForInstallation(QStringLiteral("kate"), false); });
+ kateplugins->setLongLabel(i18n("
Important functionality in RKWard is provided by kate plugins. It looks like none are installed on this system. On Linux/BSD, this can usually be fixed by installing kate.
"));
+ kateplugins->setStatus(RKSetupWizardItem::Error, i18n("None found"));
+ kateplugins->addOption(i18n("(Attempt to) install kate"), i18n("Attempt to install kate using the muon package manager. If this is not available, please install kate, manuelly"), [](RKSetupWizard* wizard) { wizard->markExternalPackageForInstallation(QStringLiteral("kate"), true); });
+ kateplugins->addOption(i18n("No change"), i18n("Proceed without plugins"), [](RKSetupWizard* wizard) { wizard->markExternalPackageForInstallation(QStringLiteral("kate"), false); });
} else {
- kateplugins.setStatus(RKSetupWizardItem::Good, i18n("Found %1 plugins.", kateplugincount));
+ kateplugins->setStatus(RKSetupWizardItem::Good, i18n("Found %1 plugins.", kateplugincount));
}
wizard->appendItem(kateplugins);
wizard->current_layout->setRowStretch(++wizard->current_row, 1);
wizard->addPage(wizard->current_page, i18n("Basic installation"));
// Wait for R Interface, then start dialog
wizard->show();
while (!RKGlobals::rInterface()->backendIsIdle()) {
if (RKGlobals::rInterface()->backendIsDead()) {
waiting_to_start_label->setText(i18n("R backend has crashed. Click \"Cancel\" to exit setup assitant."));
} else {
QApplication::processEvents(QEventLoop::AllEvents, 1000);
}
}
waiting_to_start_label->setText(i18n("R backend has started. Click \"Next\" to continue."));
wizard->setValid(firstpageref, true);
// R packages page
// This must be created _after_ the backend has started, for obvious reasons.
wizard->createStandardPage();
- RKSetupWizardItem r2htmlpackage = makeRPackageCheck("R2HTML", i18n("The R2HTML package is used by nearly all RKWard output functions, and thus required."), RKSetupWizardItem::Error, wizard);
+ auto r2htmlpackage = makeRPackageCheck("R2HTML", i18n("The R2HTML package is used by nearly all RKWard output functions, and thus required."), RKSetupWizardItem::Error, wizard);
wizard->appendItem(r2htmlpackage);
- RKSetupWizardItem rmarkdownpackage = makeRPackageCheck("rmarkdown", i18n("The rmarkdown package is required for rendering .Rmd files (including preview rendering), which is an optional but recommended feature."), RKSetupWizardItem::Warning, wizard);
+ auto rmarkdownpackage = makeRPackageCheck("rmarkdown", i18n("The rmarkdown package is required for rendering .Rmd files (including preview rendering), which is an optional but recommended feature."), RKSetupWizardItem::Warning, wizard);
wizard->appendItem(rmarkdownpackage);
wizard->current_layout->setRowStretch(++wizard->current_row, 1);
wizard->addPage(wizard->current_layout->parentWidget(), i18n("R Packages"));
wizard->exec();
- // TODO: apply all, not just this
- pluginmaps.apply(wizard);
+ for(int i = 0; i < wizard->items.size(); ++i) {
+ wizard->items[i]->apply(wizard);
+ }
+
+ if (!wizard->packages_to_install.isEmpty()) {
+ RKLoadLibsDialog::showInstallPackagesModal(wizard, 0, wizard->packages_to_install);
+ }
delete wizard;
/* TODO
RKSettingsModulePlugins::knownUsablePluginCount();
RKWardMainWindow::getMain()->katePluginIntegration()->knownPluginCount(); */
/* TODO
* consolidate with RKSettings::validateSettingsInteractive */
}
void RKSetupWizard::createStandardPage() {
RK_TRACE (DIALOGS);
current_page = new QWidget();
current_layout = new QGridLayout(current_page);
current_layout->setColumnStretch(1, 2);
current_layout->setColumnStretch(2, 1);
current_row = -1;
}
-void RKSetupWizard::appendItem(RKSetupWizardItem& item) {
+void RKSetupWizard::appendItem(RKSetupWizardItem* item) {
RK_TRACE (DIALOGS);
- item.createWidget(current_layout, ++current_row);
+ item->createWidget(current_layout, ++current_row);
+ items.append(item);
}
void RKSetupWizard::markExternalPackageForInstallation(const QString& name, bool install) {
RK_TRACE (DIALOGS);
bool present = software_to_install.contains(name);
if (install && !present) software_to_install.append(name);
if (present && !install) software_to_install.removeAll(name);
}
void RKSetupWizard::markRPackageForInstallation(const QString& name, bool install) {
RK_TRACE (DIALOGS);
bool present = packages_to_install.contains(name);
if (install && !present) packages_to_install.append(name);
if (present && !install) packages_to_install.removeAll(name);
}
diff --git a/rkward/dialogs/rksetupwizard.h b/rkward/dialogs/rksetupwizard.h
index f96646e9..ce22466e 100644
--- a/rkward/dialogs/rksetupwizard.h
+++ b/rkward/dialogs/rksetupwizard.h
@@ -1,53 +1,54 @@
/***************************************************************************
rksetupwizard - description
-------------------
begin : Fri May 25 20200
copyright : (C) 2020 by Thomas Friedrichsmeier
email : thomas.friedrichsmeier@kdemail.net
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef RKSETUPWIZARD_H
#define RKSETUPWIZARD_H
#include
class QGridLayout;
class RKSetupWizardItem;
class RKSetupWizard : public KAssistantDialog {
protected:
RKSetupWizard(QWidget* parent);
~RKSetupWizard();
enum InvokationReason {
ProblemsDetected,
NewVersionRKWard,
NewVersionR,
ManualCheck
};
static bool has_been_run;
public:
static void doAutoCheck();
static void fullInteractiveCheck(InvokationReason reason);
void markExternalPackageForInstallation(const QString &name, bool install);
void markRPackageForInstallation(const QString &name, bool install);
private:
QStringList software_to_install;
QStringList packages_to_install;
void createStandardPage();
QWidget *current_page;
int current_row;
QGridLayout* current_layout;
- void appendItem(RKSetupWizardItem& item);
+ void appendItem(RKSetupWizardItem* item);
+ QList items;
};
#endif
diff --git a/rkward/rbackend/rkrinterface.cpp b/rkward/rbackend/rkrinterface.cpp
index 46b4698a..4e65ab9d 100644
--- a/rkward/rbackend/rkrinterface.cpp
+++ b/rkward/rbackend/rkrinterface.cpp
@@ -1,864 +1,864 @@
/***************************************************************************
rkrinterface.cpp - description
-------------------
begin : Fri Nov 1 2002
copyright : (C) 2002-2020 by Thomas Friedrichsmeier
email : thomas.friedrichsmeier@kdemail.net
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "rkrinterface.h"
#include "rcommandstack.h"
#include "rkrbackendprotocol_frontend.h"
#include "../rkward.h"
#include "../rkconsole.h"
#include "../settings/rksettingsmoduler.h"
#include "../settings/rksettingsmodulegeneral.h"
#include "../settings/rksettingsmoduleoutput.h"
#include "../settings/rksettingsmodulegraphics.h"
#include "../settings/rksettingsmoduleplugins.h"
#include "../core/robjectlist.h"
#include "../core/renvironmentobject.h"
#include "../core/rkmodificationtracker.h"
#include "../dialogs/rkloadlibsdialog.h"
#include "../dialogs/rkselectlistdialog.h"
#include "../dialogs/rkreadlinedialog.h"
#include "../dialogs/rkerrordialog.h"
#include "../agents/showedittextfileagent.h"
#include "../agents/rkeditobjectagent.h"
#include "../agents/rkprintagent.h"
#include "../agents/rkdebughandler.h"
#include "../windows/rcontrolwindow.h"
#include "../windows/rkworkplace.h"
#include "../windows/rkcommandlog.h"
#include "../windows/rkhtmlwindow.h"
#include "../plugin/rkcomponentmap.h"
#include "../misc/rkcommonfunctions.h"
#include "../misc/rkmessagecatalog.h"
#include "rksessionvars.h"
#include "../windows/rkwindowcatcher.h"
#include "../rkglobals.h"
#include "../version.h"
#include "../debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
// flush new pieces of output after this period of time:
#define FLUSH_INTERVAL 100
#define GET_LIB_PATHS 1
#define GET_HELP_BASE 2
#define SET_RUNTIME_OPTS 3
#define STARTUP_PHASE2_COMPLETE 4
#define GET_R_VERSION 5
#define RSTARTUP_COMPLETE 6
// statics
double RInterface::na_real;
int RInterface::na_int;
RInterface::RInterface () {
RK_TRACE (RBACKEND);
new RCommandStackModel (this);
RCommandStack::regular_stack = new RCommandStack ();
startup_phase2_error = false;
command_logfile_mode = NotRecordingCommands;
previously_idle = false;
locked = 0;
backend_dead = false;
flush_timer_id = 0;
dummy_command_on_stack = 0;
// create a fake init command
RCommand *fake = new RCommand (i18n ("R Startup"), RCommand::App | RCommand::Sync | RCommand::ObjectListUpdate, i18n ("R Startup"), this, STARTUP_PHASE2_COMPLETE);
issueCommand (fake);
new RKSessionVars (this);
new RKDebugHandler (this);
new RKRBackendProtocolFrontend (this);
RKRBackendProtocolFrontend::instance ()->setupBackend ();
/////// Further initialization commands, which do not necessarily have to run before everything else can be queued, here. ///////
// NOTE: will receive the list as a call plain generic request from the backend ("updateInstalledPackagesList")
issueCommand (".rk.get.installed.packages()", RCommand::App | RCommand::Sync);
issueCommand (new RCommand (QString (), RCommand::App | RCommand::Sync | RCommand::EmptyCommand, QString (), this, RSTARTUP_COMPLETE));
}
void RInterface::issueCommand (const QString &command, int type, const QString &rk_equiv, RCommandReceiver *receiver, int flags, RCommandChain *chain) {
RK_TRACE (RBACKEND);
issueCommand (new RCommand (command, type, rk_equiv, receiver, flags), chain);
}
RInterface::~RInterface(){
RK_TRACE (RBACKEND);
RKWindowCatcher::discardInstance ();
}
bool RInterface::backendIsIdle () {
RK_TRACE (RBACKEND);
return (RCommandStack::regular_stack->isEmpty() && (!runningCommand()));
}
RCommand *RInterface::popPreviousCommand (int id) {
RK_TRACE (RBACKEND);
RK_ASSERT (!all_current_commands.isEmpty ());
for (int i = all_current_commands.size () - 1; i >= 0; --i) {
RCommand *ret = all_current_commands[i];
if (ret->id () == id) {
RCommandStack::pop (ret);
all_current_commands.removeAt (i);
return ret;
}
}
RK_ASSERT (false);
return 0;
}
RCommandChain* RInterface::openSubcommandChain (RCommand* parent_command) {
RK_TRACE (RBACKEND);
current_commands_with_subcommands.append (parent_command);
return RCommandStack::startChain (parent_command);
}
void RInterface::closeSubcommandChain (RCommand* parent_command) {
RK_TRACE (RBACKEND);
if (current_commands_with_subcommands.contains (parent_command)) {
current_commands_with_subcommands.removeAll (parent_command);
doNextCommand (0);
}
if (parent_command && (parent_command == dummy_command_on_stack)) {
all_current_commands.removeAll (dummy_command_on_stack);
RCommandStack::pop (dummy_command_on_stack);
handleCommandOut (dummy_command_on_stack);
dummy_command_on_stack = 0;
}
}
void RInterface::tryNextCommand () {
RK_TRACE (RBACKEND);
RCommand *command = RCommandStack::currentCommand ();
if (command_requests.isEmpty ()) {
// if the backend is not requesting anything, only priority commands will be pushed
if (!command) return;
if (!(command->type () & RCommand::PriorityCommand)) return;
if (all_current_commands.contains (command)) return;
}
bool priority = command && (command->type () & RCommand::PriorityCommand);
bool on_top_level = all_current_commands.isEmpty ();
if (!(on_top_level && locked && !(priority))) { // do not respect locks for sub-commands
if ((!on_top_level) && all_current_commands.contains (command)) { // all sub-commands of the current command have finished, it became the top-most item of the RCommandStack, again
closeSubcommandChain (command);
return;
}
if (command) {
all_current_commands.append (command);
if (command->status & RCommand::Canceled) {
// avoid passing cancelled commands to R
command->status |= RCommand::Failed;
// notify ourselves...
RCommand* dummy = popPreviousCommand (command->id ());
RK_ASSERT (dummy == command);
handleCommandOut (command);
return;
}
if (previously_idle) emit backendStatusChanged(Busy);
previously_idle = false;
doNextCommand (command);
return;
}
}
if (on_top_level) {
if (!previously_idle) emit backendStatusChanged(Idle);
previously_idle = true;
}
}
void RInterface::handleCommandOut (RCommand *command) {
RK_TRACE (RBACKEND);
RK_ASSERT (command);
#ifdef RKWARD_DEBUG
int dl = DL_WARNING; // failed application commands are an issue worth reporting, failed user commands are not
if (command->type () & RCommand::User) dl = DL_DEBUG;
if (command->failed ()) {
command->status |= RCommand::WasTried | RCommand::Failed;
if (command->status & RCommand::ErrorIncomplete) {
RK_DEBUG (RBACKEND, dl, "Command failed (incomplete)");
} else if (command->status & RCommand::ErrorSyntax) {
RK_DEBUG (RBACKEND, dl, "Command failed (syntax)");
} else if (command->status & RCommand::Canceled) {
RK_DEBUG (RBACKEND, dl, "Command failed (interrupted)");
} else {
RK_DEBUG (RBACKEND, dl, "Command failed (other)");
}
RK_DEBUG (RBACKEND, dl, "failed command was: '%s'", qPrintable (command->command ()));
RK_DEBUG (RBACKEND, dl, "- error message was: '%s'", qPrintable (command->error ()));
}
#endif
if (command->status & RCommand::Canceled) {
command->status |= RCommand::HasError;
ROutput *out = new ROutput;
out->type = ROutput::Error;
out->output = ("--- interrupted ---");
command->output_list.append (out);
command->newOutput (out);
}
command->finished ();
delete command;
}
void RInterface::doNextCommand (RCommand *command) {
RK_TRACE (RBACKEND);
RBackendRequest* command_request = currentCommandRequest ();
if (!command_request) {
if (!(command && (command->type () & RCommand::PriorityCommand))) return;
}
// importantly, this point is not reached for the fake startup command
if (RK_Debug::RK_Debug_CommandStep) {
QTime t;
t.start ();
while (t.elapsed () < RK_Debug::RK_Debug_CommandStep) {}
}
flushOutput (true);
RCommandProxy *proxy = 0;
if (command) {
RKWardMainWindow::getMain ()->setWorkspaceMightBeModified (true);
proxy = command->makeProxy ();
RK_DEBUG (RBACKEND, DL_DEBUG, "running command: %s", command->command ().toLatin1().data ());
command->status |= RCommand::Running;
RCommandStackModel::getModel ()->itemChange (command);
RKCommandLog::getLog ()->addInput (command);
if (command_logfile_mode != NotRecordingCommands) {
bool record = true;
if (command_logfile_mode != RecordingCommandsUnfiltered) {
if (command->type () & (RCommand::Silent | RCommand::Sync)) record = false;
}
if (record) {
command_logfile.write (command->command ().toUtf8 ());
command_logfile.write ("\n");
}
}
}
if (command && (command->type () & RCommand::PriorityCommand)) {
RKRBackendProtocolFrontend::sendPriorityCommand (proxy);
} else {
RK_ASSERT (command_request);
command_request->command = proxy;
RKRBackendProtocolFrontend::setRequestCompleted (command_request);
command_requests.pop_back ();
}
}
void RInterface::rCommandDone (RCommand *command) {
RK_TRACE (RBACKEND);
if (command->failed ()) {
startup_phase2_error = true;
return;
}
if (command->getFlags () == GET_LIB_PATHS) {
RK_ASSERT (command->getDataType () == RData::StringVector);
RKSettingsModuleRPackages::r_libs_user = command->stringVector ().value (0);
RKSettingsModuleRPackages::defaultliblocs += command->stringVector ().mid (1);
RCommandChain *chain = command->parent;
RK_ASSERT (chain);
RK_ASSERT (!chain->isClosed ());
// apply user configurable run time options
QStringList commands = RKSettingsModuleR::makeRRunTimeOptionCommands () + RKSettingsModuleRPackages::makeRRunTimeOptionCommands () + RKSettingsModuleOutput::makeRRunTimeOptionCommands () + RKSettingsModuleGraphics::makeRRunTimeOptionCommands ();
for (QStringList::const_iterator it = commands.begin (); it != commands.end (); ++it) {
issueCommand (*it, RCommand::App | RCommand::Sync, QString (), this, SET_RUNTIME_OPTS, chain);
}
// initialize output file
issueCommand ("rk.set.output.html.file (\"" + RKSettingsModuleGeneral::filesPath () + "/rk_out.html\")\n", RCommand::App | RCommand::Sync, QString (), this, SET_RUNTIME_OPTS, chain);
#ifdef Q_OS_MACOS
// On MacOS, the backend is started from inside R home to allow resolution of dynamic libs. Re-set to frontend wd, here.
issueCommand ("setwd (" + RKRSharedFunctionality::quote (QDir::currentPath ()) + ")\n", RCommand::App | RCommand::Sync, QString (), this, SET_RUNTIME_OPTS, chain);
#endif
closeChain (chain);
} else if (command->getFlags () == GET_R_VERSION) {
RK_ASSERT (command->getDataType () == RData::StringVector);
RK_ASSERT (command->getDataLength () == 1);
RKSessionVars::setRVersion (command->stringVector ().value (0));
} else if (command->getFlags () == GET_HELP_BASE) {
RK_ASSERT (command->getDataType () == RData::StringVector);
RK_ASSERT (command->getDataLength () == 1);
RKSettingsModuleR::help_base_url = command->stringVector ().value (0);
} else if (command->getFlags () == SET_RUNTIME_OPTS) {
// no special handling. In case of failures, staturt_fail was set to true, above.
} else if (command->getFlags () == STARTUP_PHASE2_COMPLETE) {
QString message = startup_errors;
if (startup_phase2_error) message.append (i18n ("
\t-An unspecified error occurred that is not yet handled by RKWard. Likely RKWard will not function properly. Please check your setup.
\n"));
if (!message.isEmpty ()) {
message.prepend (i18n ("
There was a problem starting the R backend. The following error(s) occurred:
\n"));
QString details = command->fullOutput().replace('<', "<").replace('\n', " ");
if (!details.isEmpty ()) {
// WORKAROUND for stupid KMessageBox behavior. (kdelibs 4.2.3)
// If length of details <= 512, it tries to show the details as a QLabel.
details = details.leftJustified (513);
}
KMessageBox::detailedError (0, message, details, i18n ("Error starting R"), KMessageBox::Notify | KMessageBox::AllowLink);
}
startup_errors.clear ();
} else if (command->getFlags () == RSTARTUP_COMPLETE) {
RKSettings::validateSettingsInteractive ();
}
}
void RInterface::handleRequest (RBackendRequest* request) {
RK_TRACE (RBACKEND);
if (request->type == RBackendRequest::OutputStartedNotification) {
RK_ASSERT (flush_timer_id == 0);
flush_timer_id = startTimer (FLUSH_INTERVAL); // calls flushOutput (false); see timerEvent ()
RKRBackendProtocolFrontend::setRequestCompleted (request);
return;
}
flushOutput (true);
if (request->type == RBackendRequest::CommandOut) {
RCommandProxy *cproxy = request->takeCommand ();
if (cproxy) {
RK_DEBUG (RBACKEND, DL_DEBUG, "Command out \"%s\", id %d", qPrintable (cproxy->command), cproxy->id);
} else {
RK_DEBUG (RBACKEND, DL_DEBUG, "Fake command out");
}
RCommand *command = 0;
// NOTE: the order of processing is: first try to submit the next command, then handle the old command.
// The reason for doing it this way, instead of the reverse, is that this allows the backend thread / process to continue working, concurrently
// NOTE: cproxy should only ever be 0 in the very first cycle
if (cproxy) command = popPreviousCommand (cproxy->id);
if (request->synchronous) command_requests.append (request);
tryNextCommand ();
if (cproxy) {
RK_ASSERT (command);
command->mergeAndDeleteProxy (cproxy);
handleCommandOut (command);
}
tryNextCommand ();
} else if (request->type == RBackendRequest::HistoricalSubstackRequest) {
RCommandProxy *cproxy = request->command;
RCommand *parent = 0;
for (int i = all_current_commands.size () - 1; i >= 0; --i) {
if (all_current_commands[i]->id () == cproxy->id) {
parent = all_current_commands[i];
break;
}
}
command_requests.append (request);
processHistoricalSubstackRequest (request->params["call"].toStringList (), parent);
} else if (request->type == RBackendRequest::PlainGenericRequest) {
request->params["return"] = 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...
startup_errors = request->params["message"].toString ();
command_requests.append (request);
RCommandChain *chain = openSubcommandChain (runningCommand ());
issueCommand ("paste (R.version[c (\"major\", \"minor\")], collapse=\".\")\n", RCommand::GetStringVector | RCommand::App | RCommand::Sync, QString (), this, GET_R_VERSION, chain);
// find out about standard library locations
issueCommand ("c(path.expand(Sys.getenv(\"R_LIBS_USER\")), .libPaths())\n", RCommand::GetStringVector | RCommand::App | RCommand::Sync, QString (), this, GET_LIB_PATHS, chain);
// start help server / determined help base url
issueCommand (".rk.getHelpBaseUrl ()\n", RCommand::GetStringVector | RCommand::App | RCommand::Sync, QString (), this, GET_HELP_BASE, chain);
// NOTE: more initialization commands get run *after* we have determined the standard library locations (see rCommandDone())
} else {
processRBackendRequest (request);
}
}
void RInterface::timerEvent (QTimerEvent *) {
// do not trace. called periodically
flushOutput (false);
}
void RInterface::flushOutput (bool forced) {
// do not trace. called periodically
// RK_TRACE (RBACKEND);
ROutputList list = RKRBackendProtocolFrontend::instance ()->flushOutput (forced);
// this must come _after_ the output has been flushed.
if (forced || !list.isEmpty ()) {
if (flush_timer_id != 0) {
killTimer (flush_timer_id);
flush_timer_id = 0;
}
}
foreach (ROutput *output, list) {
if (all_current_commands.isEmpty ()) {
RK_DEBUG (RBACKEND, DL_WARNING, "output without receiver'%s'", qPrintable (output->output));
delete output;
continue; // to delete the other output pointers, too
} else {
RK_DEBUG (RBACKEND, DL_DEBUG, "output '%s'", qPrintable (output->output));
}
bool first = true;
foreach (RCommand* command, all_current_commands) {
ROutput *coutput = output;
if (!first) { // this output belongs to several commands at once. So we need to copy it.
coutput = new ROutput;
coutput->type = output->type;
coutput->output = output->output;
}
first = false;
if (coutput->type == ROutput::Output) {
command->status |= RCommand::HasOutput;
command->output_list.append (coutput);
} else if (coutput->type == ROutput::Warning) {
command->status |= RCommand::HasWarnings;
command->output_list.append (coutput);
} else if (coutput->type == ROutput::Error) {
command->status |= RCommand::HasError;
// An error output is typically just the copy of the previous output, so merge if possible
if (command->output_list.isEmpty ()) {
command->output_list.append (coutput);
}
if (command->output_list.last ()->output == coutput->output) {
command->output_list.last ()->type = ROutput::Error;
continue; // don't call command->newOutput(), again!
}
}
command->newOutput (coutput);
}
}
}
void RInterface::issueCommand (RCommand *command, RCommandChain *chain) {
RK_TRACE (RBACKEND);
if (command->command ().isEmpty ()) command->_type |= RCommand::EmptyCommand;
if (RKCarbonCopySettings::shouldCarbonCopyCommand (command)) {
command->_type |= RCommand::CCCommand;
if (RKCarbonCopySettings::includeOutputInCarbonCopy ()) command->_type |= RCommand::CCOutput;
}
RCommandStack::issueCommand (command, chain);
tryNextCommand ();
}
RCommandChain *RInterface::startChain (RCommandChain *parent) {
RK_TRACE (RBACKEND);
return RCommandStack::startChain (parent);
};
void RInterface::closeChain (RCommandChain *chain) {
RK_TRACE (RBACKEND);
RCommandStack::closeChain (chain);
tryNextCommand ();
};
void RInterface::cancelAll () {
RK_TRACE (RBACKEND);
QList all_commands = RCommandStack::regular_stack->allCommands ();
foreach (RCommand* command, all_commands) cancelCommand (command);
}
bool RInterface::softCancelCommand (RCommand* command) {
RK_TRACE (RBACKEND);
if (!(command->type () & RCommand::Running)) {
cancelCommand (command);
}
return command->status & RCommand::Canceled;
}
void RInterface::cancelCommand (RCommand *command) {
RK_TRACE (RBACKEND);
if (!(command->type () & RCommand::Sync)) {
command->status |= RCommand::Canceled;
if (command->type () & RCommand::Running) {
if ((RKDebugHandler::instance ()->state () == RKDebugHandler::InDebugPrompt) && (command == RKDebugHandler::instance ()->command ())) {
RKDebugHandler::instance ()->sendCancel ();
} else {
RKRBackendProtocolFrontend::instance ()->interruptCommand (command->id ());
}
}
RCommandStackModel::getModel ()->itemChange (command);
} else {
RK_ASSERT (false);
}
}
void RInterface::pauseProcessing (bool pause) {
RK_TRACE (RBACKEND);
if (pause) locked |= User;
else locked -= locked & User;
}
QStringList RInterface::processPlainGenericRequest (const QStringList &calllist) {
RK_TRACE (RBACKEND);
QString call = calllist.value (0);
if (call == "get.tempfile.name") {
RK_ASSERT (calllist.count () == 3);
return (QStringList (RKCommonFunctions::getUseableRKWardSavefileName (calllist.value (1), calllist.value (2))));
} else if (call == "set.output.file") {
RK_ASSERT (calllist.count () == 2);
RKOutputWindowManager::self ()->setCurrentOutputPath (calllist.value (1));
} else if (call == "wdChange") {
// in case of separate processes, apply new working directory in frontend, too.
QDir::setCurrent (calllist.value (1));
emit (backendWorkdirChanged());
} else if (call == "highlightRCode") {
return (QStringList (RKCommandHighlighter::commandToHTML (calllist.value (1))));
} else if (call == "quit") {
RKWardMainWindow::getMain ()->close ();
// if we're still alive, quitting was cancelled
return (QStringList ("FALSE"));
} else if (call == "preLocaleChange") {
int res = KMessageBox::warningContinueCancel (0, i18n ("A command in the R backend is trying to change the character encoding. While RKWard offers support for this, and will try to adjust to the new locale, this operation may cause subtle bugs, if data windows are currently open. Also the feature is not well tested, yet, and it may be advisable to save your workspace before proceeding.\nIf you have any data editor opened, or in any doubt, it is recommended to close those first (this will probably be auto-detected in later versions of RKWard). In this case, please choose 'Cancel' now, then close the data windows, save, and retry."), i18n ("Locale change"));
if (res != KMessageBox::Continue) return (QStringList ("FALSE"));
} else if (call == "listPlugins") {
RK_ASSERT (calllist.count () == 1);
return RKComponentMap::getMap ()->listPlugins ();
} else if (call == "setPluginStatus") {
QStringList params = calllist.mid (1);
RK_ASSERT ((params.size () % 3) == 0);
const int rows = params.size () / 3;
QStringList ids = params.mid (0, rows);
QStringList contexts = params.mid (rows, rows);
QStringList visible = params.mid (rows*2, rows);
RKComponentMap::getMap ()->setPluginStatus (ids, contexts, visible);
} else if (call == "loadPluginMaps") {
bool force = (calllist.value (1) == "force");
bool reload = (calllist.value (2) == "reload");
RKSettingsModulePlugins::registerPluginMaps (calllist.mid (3), force ? RKSettingsModulePlugins::ManualAddition : RKSettingsModulePlugins::AddIfNewAndDefault, reload);
} else if (call == "updateInstalledPackagesList") {
RKSessionVars::instance ()->setInstalledPackages (calllist.mid (1));
} else if (call == "showHTML") {
RK_ASSERT (calllist.count () == 2);
RKWorkplace::mainWorkplace ()->openHelpWindow (QUrl::fromUserInput (calllist.value (1), QDir::currentPath (), QUrl::AssumeLocalFile));
} else if (call == "select.list") {
QString title = calllist.value (1);
bool multiple = (calllist.value (2) == "multi");
int num_preselects = calllist.value (3).toInt ();
QStringList preselects = calllist.mid (4, num_preselects);
QStringList choices = calllist.mid (4 + num_preselects);
QStringList results = RKSelectListDialog::doSelect (QApplication::activeWindow(), title, choices, preselects, multiple);
if (results.isEmpty ()) results.append (""); // R wants to have it that way
return (results);
} else if (call == "commandHistory") {
if (calllist.value (1) == "get") {
return (RKConsole::mainConsole ()->commandHistory ());
} else {
RKConsole::mainConsole ()->setCommandHistory (calllist.mid (2), calllist.value (1) == "append");
}
} else if (call == "getWorkspaceUrl") {
QUrl url = RKWorkplace::mainWorkplace ()->workspaceURL ();
if (!url.isEmpty ()) return (QStringList (url.url ()));
} else if (call == "workplace.layout") {
if (calllist.value (1) == "set") {
if (calllist.value (2) == "close") RKWorkplace::mainWorkplace ()->closeAll ();
QStringList list = calllist.mid (3);
RKWorkplace::mainWorkplace ()->restoreWorkplace (list);
} else {
RK_ASSERT (calllist.value (1) == "get");
return (RKWorkplace::mainWorkplace ()->makeWorkplaceDescription ());
}
} else if (call == "set.window.placement.hint") {
RKWorkplace::mainWorkplace ()->setWindowPlacementOverrides (calllist.value (1), calllist.value (2), calllist.value (3));
} else if (call == "getSessionInfo") {
// Non-translatable on purpose. This is meant for posting to the bug tracker, mostly.
QStringList lines ("-- Frontend --");
lines.append (RKSessionVars::frontendSessionInfo ());
lines.append (QString ());
lines.append ("-- Backend --");
lines.append ("Debug message file (this may contain relevant diagnostic output in case of trouble):");
lines.append (calllist.value (1));
lines.append (QString ());
lines.append ("R version (compile time): " + calllist.value (2));
return (lines);
} else if (call == "recordCommands") {
RK_ASSERT (calllist.count () == 3);
QString filename = calllist.value (1);
bool unfiltered = (calllist.value (2) == "include.all");
if (filename.isEmpty ()) {
command_logfile_mode = NotRecordingCommands;
command_logfile.close ();
} else {
if (command_logfile_mode != NotRecordingCommands) {
return (QStringList ("Attempt to start recording, while already recording commands. Ignoring.)"));
} else {
command_logfile.setFileName (filename);
bool ok = command_logfile.open (QIODevice::WriteOnly | QIODevice::Truncate);
if (ok) {
if (unfiltered) command_logfile_mode = RecordingCommandsUnfiltered;
else command_logfile_mode = RecordingCommands;
} else {
return (QStringList ("Could not open file for writing. Not recording commands"));
}
}
}
} else if (call == "printPreview") {
RKPrintAgent::printPostscript (calllist.value (1), true);
} else if (call == "endBrowserContext") {
RKDebugHandler::instance ()->endDebug ();
} else if (call == "switchLanguage") {
RKMessageCatalog::switchLanguage (calllist.value (1));
} else {
return (QStringList ("Error: unrecognized request '" + call + "'."));
}
// for those calls which were recognized, but do not return anything
return QStringList ();
}
void RInterface::processHistoricalSubstackRequest (const QStringList &calllist, RCommand *parent_command) {
RK_TRACE (RBACKEND);
RCommandChain *in_chain;
if (!parent_command) {
// This can happen for Tcl events. Create a dummy command on the stack to keep things looping.
parent_command = new RCommand (QString (), RCommand::App | RCommand::EmptyCommand | RCommand::Sync);
RCommandStack::issueCommand (parent_command, 0);
all_current_commands.append (parent_command);
dummy_command_on_stack = parent_command; // so we can get rid of it again, after it's sub-commands have finished
}
in_chain = openSubcommandChain (parent_command);
RK_DEBUG (RBACKEND, DL_DEBUG, "started sub-command chain (%p) for command %s", in_chain, qPrintable (parent_command->command ()));
QString call = calllist.value (0);
if (call == "sync") {
RK_ASSERT (calllist.count () >= 2);
for (int i = 1; i < calllist.count (); ++i) {
QString object_name = calllist[i];
RObject *obj = RObjectList::getObjectList ()->findObject (object_name);
if (obj) {
RK_DEBUG (RBACKEND, DL_DEBUG, "triggering update for symbol %s", object_name.toLatin1 ().data());
obj->markDataDirty ();
obj->updateFromR (in_chain);
} else {
RK_DEBUG (RBACKEND, DL_WARNING, "lookup failed for changed symbol %s", object_name.toLatin1 ().data());
}
}
} else if (call == "syncenvs") {
RK_DEBUG (RBACKEND, DL_DEBUG, "triggering update of object list");
int search_len = calllist.value (1).toInt ();
RObjectList::getObjectList ()->updateFromR (in_chain, calllist.mid (2, search_len), calllist.mid (2 + search_len));
} else if (call == "syncglobal") {
RK_DEBUG (RBACKEND, DL_DEBUG, "triggering update of globalenv");
RObjectList::getGlobalEnv ()->updateFromR (in_chain, calllist.mid (1));
#ifndef DISABLE_RKWINDOWCATCHER
// NOTE: WARNING: When converting these to PlainGenericRequests, the occasional "error, figure margins too large" starts coming up, again. Not sure, why.
} else if (call == "startOpenX11") {
RK_ASSERT (calllist.count () == 2);
RKWindowCatcher::instance ()->start (calllist.value (1).toInt ());
} else if (call == "endOpenX11") {
RK_ASSERT (calllist.count () == 2);
RKWindowCatcher::instance ()->stop (calllist.value (1).toInt ());
} else if (call == "updateDeviceHistory") {
if (calllist.count () >= 2) {
RKWindowCatcher::instance ()->updateHistory (calllist.mid (1));
}
} else if (call == "killDevice") {
RK_ASSERT (calllist.count () == 2);
RKWindowCatcher::instance ()->killDevice (calllist.value (1).toInt ());
#endif // DISABLE_RKWINDOWCATCHER
} else if (call == "edit") {
RK_ASSERT (calllist.count () >= 2);
QStringList object_list = calllist.mid (1);
new RKEditObjectAgent (object_list, in_chain);
} else if (call == "require") {
if (calllist.count () >= 2) {
QString lib_name = calllist[1];
KMessageBox::information (0, i18n ("The R-backend has indicated that in order to carry out the current task it needs the package '%1', which is not currently installed. We will open the package-management tool, and there you can try to locate and install the needed package.", lib_name), i18n ("Require package '%1'", lib_name));
- RKLoadLibsDialog::showInstallPackagesModal (0, in_chain, lib_name);
+ RKLoadLibsDialog::showInstallPackagesModal (0, in_chain, QStringList(lib_name));
issueCommand (".rk.set.reply (\"\")", RCommand::App | RCommand::Sync, QString (), 0, 0, in_chain);
} else {
issueCommand (".rk.set.reply (\"Too few arguments in call to require.\")", RCommand::App | RCommand::Sync, QString (), 0, 0, in_chain);
}
} else if (call == "doPlugin") {
if (calllist.count () >= 3) {
QString message;
bool ok;
RKComponentMap::ComponentInvocationMode mode = RKComponentMap::ManualSubmit;
if (calllist[2] == "auto") mode = RKComponentMap::AutoSubmit;
else if (calllist[2] == "submit") mode = RKComponentMap::AutoSubmitOrFail;
ok = RKComponentMap::invokeComponent (calllist[1], calllist.mid (3), mode, &message, in_chain);
if (!message.isEmpty ()) {
QString type = "warning";
if (!ok) type = "error";
issueCommand (".rk.set.reply (list (type=\"" + type + "\", message=\"" + RKCommonFunctions::escape (message) + "\"))", RCommand::App | RCommand::Sync, QString (), 0, 0, in_chain);
}
} else {
RK_ASSERT (false);
}
} else {
issueCommand ("stop (\"Unrecognized call '" + call + "'. Ignoring\")", RCommand::App | RCommand::Sync, QString (), 0, 0, in_chain);
}
closeChain (in_chain);
}
int addButtonToBox (QDialog *dialog, QDialogButtonBox *box, QDialogButtonBox::StandardButton which, const QString &text, const QString &def_text, bool is_default) {
if (text.isEmpty ()) return 0;
QPushButton *button = box->addButton (which);
if (text != def_text) button->setText (text);
if (is_default) button->setDefault (true);
QObject::connect (button, &QPushButton::clicked, [dialog, which]() { dialog->done (which); });
return 1;
}
void RInterface::processRBackendRequest (RBackendRequest *request) {
RK_TRACE (RBACKEND);
// first, copy out the type. Allows for easier typing below
RBackendRequest::RCallbackType type = request->type;
if (type == RBackendRequest::CommandLineIn) {
int id = request->params["commandid"].toInt ();
RCommand *command = all_current_commands.value (0, 0); // User command will always be the first.
if ((command == 0) || (command->id () != id)) {
RK_ASSERT (false);
} else {
command->commandLineIn ();
}
} else if (type == RBackendRequest::ShowMessage) {
QString caption = request->params["caption"].toString ();
QString message = request->params["message"].toString ();
QString button_yes = request->params["button_yes"].toString ();
QString button_no = request->params["button_no"].toString ();
QString button_cancel = request->params["button_cancel"].toString ();
QString def_button = request->params["default"].toString ();
// NOTE: In order to support non-modal (information) dialogs, we cannot use KMessageBox or QMessgaeBox, below.
QDialog* dialog = new QDialog ();
dialog->setResult (-1); // We use this to stand for cancelled
QDialogButtonBox *button_box = new QDialogButtonBox (dialog);
int button_count = 0;
button_count += addButtonToBox (dialog, button_box, QDialogButtonBox::Yes, button_yes, "yes", def_button == button_yes);
button_count += addButtonToBox (dialog, button_box, QDialogButtonBox::No, button_no, "no", def_button == button_no);
button_count += addButtonToBox (dialog, button_box, QDialogButtonBox::Cancel, button_cancel, "cancel", def_button == button_cancel);
if (!button_count) { // cannot have no button defined at all
button_count += addButtonToBox (dialog, button_box, QDialogButtonBox::Ok, "ok", "ok", true);
}
bool synchronous = request->synchronous || (button_count > 1);
KMessageBox::createKMessageBox (dialog, button_box, button_count < 2 ? QMessageBox::Information : QMessageBox::Question, message, QStringList (), QString (), 0, KMessageBox::Notify | KMessageBox::NoExec);
dialog->setWindowTitle (caption);
if (!synchronous) {
dialog->setAttribute (Qt::WA_DeleteOnClose);
dialog->show();
RKRBackendProtocolFrontend::setRequestCompleted (request);
return;
} else {
int result = dialog->exec ();
QString result_string;
if (result == QDialogButtonBox::Yes || result == QDialogButtonBox::Ok) result_string = "yes";
else if (result == QDialogButtonBox::No) result_string = "no";
else result_string = "cancel";
request->params["result"] = result_string;
delete dialog;
}
} else if (type == RBackendRequest::ReadLine) {
QString result;
// yes, readline *can* be called outside of a current command (e.g. from tcl/tk)
bool dummy_command = false;
RCommand *command = runningCommand ();
if (!command) {
command = new RCommand ("", RCommand::EmptyCommand);
dummy_command = true;
}
bool ok = RKReadLineDialog::readLine (0, i18n ("R backend requests information"), request->params["prompt"].toString (), command, &result);
request->params["result"] = QVariant (result);
if (dummy_command) delete command;
if (!ok) request->params["cancelled"] = QVariant (true);
} else if (type == RBackendRequest::Debugger) {
RKDebugHandler::instance ()->debugCall (request, runningCommand ());
return; // request will be closed by the debug handler
} else if ((type == RBackendRequest::ShowFiles) || (type == RBackendRequest::EditFiles)) {
ShowEditTextFileAgent::showEditFiles (request);
return; // we are not done, yet!
} else if (type == RBackendRequest::ChooseFile) {
QString filename;
if (request->params["new"].toBool ()) {
filename = QFileDialog::getSaveFileName ();
} else {
filename = QFileDialog::getOpenFileName ();
}
request->params["result"] = QVariant (filename);
} else if (type == RBackendRequest::SetParamsFromBackend) {
na_real = request->params["na_real"].toDouble ();
na_int = request->params["na_int"].toInt ();
} else if (type == RBackendRequest::BackendExit) {
if (request->params.value ("regular", QVariant (false)).toBool ()) backend_dead = true; // regular exit via QuitCommand
if (!backend_dead) {
backend_dead = true;
QString message = request->params["message"].toString ();
message += i18n ("\nThe R backend will be shut down immediately. This means, you can not use any more functions that rely on it. I.e. you can do hardly anything at all, not even save the workspace (but if you're lucky, R already did that). What you can do, however, is save any open command-files, the output, or copy data out of open data editors. Quit RKWard after that. Sorry!");
RKErrorDialog::reportableErrorMessage (0, message, QString (), i18n ("R engine has died"), "r_engine_has_died");
emit backendStatusChanged(Dead);
}
} else {
RK_ASSERT (false);
}
RKRBackendProtocolFrontend::setRequestCompleted (request);
}