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);
auto page = new QWidget();
auto layout = new QGridLayout(page);
int row = -1;
layout->setColumnStretch(1, 2);
layout->setColumnStretch(2, 1);
+ bool reinstallation_required = false;
RKSetupWizardItem idir(i18n("Installation directory"));
if (RKCommonFunctions::getRKWardDataDir ().isEmpty ()) {
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."), []() {});
+ reinstallation_required = true;
} else {
idir.setStatus(RKSetupWizardItem::Good, i18n("Found."));
}
idir.createWidget(layout, ++row);
-/*
RKSetupWizardItem pluginmaps(i18n("RKWard plugins"));
- if (RKComponentMap::getMap()->isEmpty()) {
- pluginmaps.setStatus(RKSetupWizardItem::Error, i18n("None selected"));
+ if (RKSettingsModulePlugins::pluginMaps().isEmpty()) {
+ // TODO: Move to RKSettingsModulePlugins::validateSettingsInteractive
+ pluginmaps.setLongLabel(i18n("
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"), []() { RKSettingsModulePlugins::registerDefaultPluginMaps(RKSettingsModulePlugins::AddIfDefault); });
+ pluginmaps.addOption(i18n("No change"), i18n("Proceed without plugins"), []() {});
+
+ // TODO: Also offer help, if a suspicioulsy small share of plugins is active? RKSettingsModulePlugins::knownUsablePluginCount();
} else {
pluginmaps.setStatus(RKSetupWizardItem::Good, i18n("Found."));
}
- pluginmaps.createWidget(layout, ++row); */
+ pluginmaps.createWidget(layout, ++row);
+
+ RKSetupWizardItem kateplugins(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"), []() {}); // TODO
+ kateplugins.addOption(i18n("No change"), i18n("Proceed without plugins"), []() {});
+ } else {
+ kateplugins.setStatus(RKSetupWizardItem::Good, i18n("Found %1 plugins.", kateplugincount));
+ }
+ kateplugins.createWidget(layout, ++row);
layout->setRowStretch(++row, 1);
wizard->addPage(page, i18n("Basic installation"));
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);
wizard->exec();
+
+ // TODO: apply all
+ pluginmaps.apply();
+
delete wizard;
/* TODO
RKSettingsModulePlugins::knownUsablePluginCount();
RKWardMainWindow::getMain()->katePluginIntegration()->knownPluginCount(); */
/* TODO
* consolidate with RKSettings::validateSettingsInteractive */
}
diff --git a/rkward/rbackend/rkrinterface.cpp b/rkward/rbackend/rkrinterface.cpp
index b56cf154..46b4698a 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, 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);
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);
}
diff --git a/rkward/settings/rksettingsmoduleplugins.cpp b/rkward/settings/rksettingsmoduleplugins.cpp
index 5eb7c9c4..4ebf5091 100644
--- a/rkward/settings/rksettingsmoduleplugins.cpp
+++ b/rkward/settings/rksettingsmoduleplugins.cpp
@@ -1,575 +1,581 @@
/***************************************************************************
rksettingsmoduleplugins - description
-------------------
begin : Wed Jul 28 2004
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 "rksettingsmoduleplugins.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../rkward.h"
#include "../rkglobals.h"
#include "../misc/multistringselector.h"
#include "../misc/rkcommonfunctions.h"
#include "../misc/rkspinbox.h"
#include "../misc/xmlhelper.h"
#include "../plugin/rkcomponentmap.h"
#include "../dialogs/rkloadlibsdialog.h"
#include "rksettingsmodulegeneral.h"
#include "../debug.h"
// static members
QList RKSettingsModulePlugins::known_plugin_maps;
RKSettingsModulePlugins::PluginPrefs RKSettingsModulePlugins::interface_pref;
bool RKSettingsModulePlugins::show_code;
int RKSettingsModulePlugins::code_size;
int RKSettingsModulePlugins::side_preview_width;
RKSettingsModulePlugins::RKSettingsModulePlugins (RKSettings *gui, QWidget *parent) : RKSettingsModule (gui, parent) {
RK_TRACE (SETTINGS);
QVBoxLayout *main_vbox = new QVBoxLayout (this);
main_vbox->addSpacing (2*RKGlobals::spacingHint ());
main_vbox->addWidget (RKCommonFunctions::wordWrappedLabel (i18n ("Some plugins are available with both, a wizard-like interface and a traditional dialog interface. If both are available, which mode of presentation do you prefer?")));
QGroupBox* button_box = new QGroupBox (this);
QVBoxLayout* group_layout = new QVBoxLayout (button_box);
button_group = new QButtonGroup (button_box);
QAbstractButton* button;
button = new QRadioButton (i18n ("Always prefer dialogs"), button_box);
group_layout->addWidget (button);
button_group->addButton (button, PreferDialog);
button = new QRadioButton (i18n ("Prefer recommended interface"), button_box);
group_layout->addWidget (button);
button_group->addButton (button, PreferRecommended);
button = new QRadioButton (i18n ("Always prefer wizards"), button_box);
group_layout->addWidget (button);
button_group->addButton (button, PreferWizard);
if ((button = button_group->button (interface_pref))) button->setChecked (true);
connect (button_group, static_cast(&QButtonGroup::buttonClicked), this, &RKSettingsModulePlugins::settingChanged);
main_vbox->addWidget (button_box);
main_vbox->addSpacing (2*RKGlobals::spacingHint ());
QPushButton *pluginmap_config_button = new QPushButton (i18n ("Configure Active Plugins"), this);
connect (pluginmap_config_button, &QPushButton::clicked, this, &RKSettingsModulePlugins::configurePluginmaps);
main_vbox->addWidget (pluginmap_config_button);
main_vbox->addStretch ();
}
RKSettingsModulePlugins::~RKSettingsModulePlugins() {
RK_TRACE (SETTINGS);
}
void RKSettingsModulePlugins::settingChanged () {
RK_TRACE (SETTINGS);
change ();
}
QString RKSettingsModulePlugins::caption () {
RK_TRACE (SETTINGS);
return (i18n ("RKWard Plugins"));
}
void RKSettingsModulePlugins::applyChanges () {
RK_TRACE (SETTINGS);
interface_pref = static_cast (button_group->checkedId ());
}
RKSettingsModulePlugins::PluginMapList RKSettingsModulePlugins::setPluginMaps (const RKSettingsModulePlugins::PluginMapList new_list) {
RK_TRACE (SETTINGS);
known_plugin_maps = new_list;
fixPluginMapLists ();
RKWardMainWindow::getMain ()->initPlugins();
return known_plugin_maps;
}
void RKSettingsModulePlugins::configurePluginmaps () {
RK_TRACE (SETTINGS);
RKLoadLibsDialog::showPluginmapConfig (this, commandChain ());
}
void RKSettingsModulePlugins::save (KConfig *config) {
RK_TRACE (SETTINGS);
saveSettings (config);
}
void RKSettingsModulePlugins::saveSettings (KConfig *config) {
RK_TRACE (SETTINGS);
KConfigGroup cg = config->group ("Plugin Settings");
cg.deleteGroup ("Known Plugin maps"); // always start from scratch to remove cruft from pluginmaps
KConfigGroup pmg = cg.group ("Known Plugin maps");
QStringList all_known_maps;
for (int i = 0; i < known_plugin_maps.size (); ++i) {
const PluginMapStoredInfo &inf = known_plugin_maps[i];
KConfigGroup ppmg = pmg.group (inf.filename);
ppmg.writeEntry ("Active", inf.active);
ppmg.writeEntry ("Broken", inf.broken_in_this_version);
ppmg.writeEntry ("Quirky", inf.quirky_in_this_version);
ppmg.writeEntry ("timestamp", inf.last_modified);
ppmg.writeEntry ("id", inf.id);
ppmg.writeEntry ("priority", inf.priority);
all_known_maps.append (inf.filename);
}
// NOTE: The group list is always sorted alphabetically, which is why we need a separate list setting for saving info on order.
cg.writeEntry ("All known plugin maps", all_known_maps);
cg.writeEntry ("Interface Preferences", static_cast (interface_pref));
cg.writeEntry ("Code display default", show_code);
cg.writeEntry ("Code display size", code_size);
cg.writeEntry ("Other preview size", side_preview_width);
}
void RKSettingsModulePlugins::loadSettings (KConfig *config) {
RK_TRACE (SETTINGS);
KConfigGroup cg = config->group ("Plugin Settings");
if (RKSettingsModuleGeneral::storedConfigVersion () < RKSettingsModuleGeneral::RKWardConfig_0_6_1) {
QStringList plugin_maps = cg.readEntry ("Plugin Maps", QStringList ());
QStringList kplugin_maps = cg.readEntry ("All known plugin maps", QStringList ());
for (int i = 0; i < kplugin_maps.size (); ++i) {
PluginMapStoredInfo inf (RKSettingsModuleGeneral::checkAdjustLoadedPath (kplugin_maps[i]));
inf.active = plugin_maps.contains (kplugin_maps[i]); // comparing unadjusted path on purpose!
// state info will be properly initialized in fixPluginMapLists()
known_plugin_maps.append (inf);
}
} else {
KConfigGroup pmg = cg.group ("Known Plugin maps");
QStringList kplugin_maps = cg.readEntry ("All known plugin maps", QStringList ());
for (int i = 0; i < kplugin_maps.size (); ++i) {
KConfigGroup ppmg = pmg.group (kplugin_maps[i]); // unadjusted path on purpose!
PluginMapStoredInfo inf (RKSettingsModuleGeneral::checkAdjustLoadedPath (kplugin_maps[i]));
inf.active = ppmg.readEntry ("Active", false);
// Pluginmaps which are broken with one version of RKWard may be alright with other versions. So reset flags, if version has changed.
inf.broken_in_this_version = ppmg.readEntry ("Broken", false) && !RKSettingsModuleGeneral::rkwardVersionChanged ();
inf.quirky_in_this_version = ppmg.readEntry ("Quirky", false) && !RKSettingsModuleGeneral::rkwardVersionChanged ();
inf.last_modified = ppmg.readEntry ("timestamp", QDateTime ());
inf.id = ppmg.readEntry ("id");
inf.priority = ppmg.readEntry ("priority", (int) PriorityMedium);
known_plugin_maps.append (inf);
}
}
if (RKSettingsModuleGeneral::rkwardVersionChanged () || RKSettingsModuleGeneral::installationMoved ()) {
// if it is the first start this version or from a new path, scan the installation for new pluginmaps
// Note that in the case of installationMoved(), checkAdjustLoadedPath() has already kicked in, above, but rescanning is still useful
// e.g. if users have installed to a new location, because they had botched their previous installation
- QDir def_plugindir (RKCommonFunctions::getRKWardDataDir ());
- if (def_plugindir.dirName() == QStringLiteral ("rkwardinstall")) {
- // For running from build-dir: Work around bad design choice of installation layout
- def_plugindir.cd ("plugins");
- }
- QStringList def_pluginmaps = def_plugindir.entryList (QStringList ("*.pluginmap"));
- for (int i = 0; i < def_pluginmaps.size (); ++i) {
- def_pluginmaps[i] = def_plugindir.absoluteFilePath (def_pluginmaps[i]);
- }
- registerPluginMaps (def_pluginmaps, false, false, true);
+ registerDefaultPluginMaps(AddIfNewAndDefault);
}
fixPluginMapLists (); // removes any maps which don't exist any more
interface_pref = static_cast (cg.readEntry ("Interface Preferences", static_cast (PreferRecommended)));
show_code = cg.readEntry ("Code display default", false);
code_size = cg.readEntry ("Code display size", 250);
side_preview_width = cg.readEntry ("Other preview size", 250);
if (RKSettingsModuleGeneral::storedConfigVersion () <= RKSettingsModuleGeneral::RKWardConfig_Pre0_5_7) {
if (code_size == 40) code_size = 250; // previous default untouched.
}
}
+void RKSettingsModulePlugins::registerDefaultPluginMaps(AddMode add_mode) {
+ RK_TRACE (SETTINGS);
+
+ QDir def_plugindir (RKCommonFunctions::getRKWardDataDir ());
+ if (def_plugindir.dirName() == QStringLiteral ("rkwardinstall")) {
+ // For running from build-dir: Work around bad design choice of installation layout
+ def_plugindir.cd ("plugins");
+ }
+ QStringList def_pluginmaps = def_plugindir.entryList (QStringList ("*.pluginmap"));
+ for (int i = 0; i < def_pluginmaps.size (); ++i) {
+ def_pluginmaps[i] = def_plugindir.absoluteFilePath (def_pluginmaps[i]);
+ }
+ registerPluginMaps (def_pluginmaps, add_mode, false, add_mode == AddIfNewAndDefault);
+}
+
int findKnownPluginMap (const QString& filename, const RKSettingsModulePlugins::PluginMapList& haystack) {
RK_TRACE (SETTINGS);
int i;
for (i = haystack.size () - 1; i >= 0; --i) {
if (haystack[i].filename == filename) return i;
}
return i;
}
QString RKSettingsModulePlugins::findPluginMapById (const QString &id) {
RK_TRACE (SETTINGS);
for (int i = 0; i < known_plugin_maps.size (); ++i) {
if (known_plugin_maps[i].id == id) return known_plugin_maps[i].filename;
}
// for "rkward::" namespace, try a little harded:
if (id.startsWith (QLatin1String ("rkward::"))) {
QFileInfo info (RKCommonFunctions::getRKWardDataDir () + '/' + id.mid (8));
if (info.isReadable ()) return info.absoluteFilePath ();
}
return QString ();
}
bool RKSettingsModulePlugins::markPluginMapAsBroken (const QString& map) {
RK_TRACE (SETTINGS);
int index = findKnownPluginMap (map, known_plugin_maps);
if (index < 0) {
RK_ASSERT (index >= 0);
return false;
}
bool ret = !known_plugin_maps[index].broken_in_this_version;
known_plugin_maps[index].broken_in_this_version = true;
known_plugin_maps[index].active = false;
return ret;
}
bool RKSettingsModulePlugins::markPluginMapAsQuirky (const QString& map) {
RK_TRACE (SETTINGS);
int index = findKnownPluginMap (map, known_plugin_maps);
if (index < 0) {
RK_ASSERT (index >= 0);
return false;
}
bool ret = !known_plugin_maps[index].quirky_in_this_version;
known_plugin_maps[index].quirky_in_this_version = true;
return ret;
}
void RKSettingsModulePlugins::markPluginMapAsWorking (const QString& map) {
RK_TRACE (SETTINGS);
int index = findKnownPluginMap (map, known_plugin_maps);
if (index < 0) {
RK_ASSERT (index >= 0);
return;
}
known_plugin_maps[index].quirky_in_this_version = false;
known_plugin_maps[index].broken_in_this_version = false;
}
QStringList RKSettingsModulePlugins::pluginMaps () {
RK_TRACE (SETTINGS);
QStringList ret;
for (int i = 0; i < known_plugin_maps.size (); ++i) {
if (known_plugin_maps[i].active) ret.append (known_plugin_maps[i].filename);
}
return ret;
}
// static
int RKSettingsModulePlugins::uniqueUsablePluginMapCount() {
RK_TRACE (SETTINGS);
QSet ids;
for (int i = 0; i < known_plugin_maps.size (); ++i) {
if (known_plugin_maps[i].broken_in_this_version) return false;
ids.insert(known_plugin_maps[i].id);
}
return ids.size();
}
// static
-void RKSettingsModulePlugins::registerPluginMaps (const QStringList &maps, bool force_add, bool force_reload, bool suppress_reload) {
+void RKSettingsModulePlugins::registerPluginMaps (const QStringList &maps, AddMode add_mode, bool force_reload, bool suppress_reload) {
RK_TRACE (SETTINGS);
QStringList added;
foreach (const QString &map, maps) {
if (map.isEmpty ()) continue;
int index = findKnownPluginMap (map, known_plugin_maps);
if (index >= 0) {
if (known_plugin_maps[index].active) continue;
- if (!force_add) continue;
+ if (add_mode == AddIfNewAndDefault) continue;
} else { // not found
PluginMapStoredInfo inf (map);
known_plugin_maps.append (inf);
index = known_plugin_maps.size () - 1;
}
added.append (map);
}
fixPluginMapLists ();
// activate added (or forced) pluginmaps, *after* the list has been fixed (and info on priority has been read)
for (int i = 0; i < known_plugin_maps.size (); ++i) {
PluginMapStoredInfo &inf = known_plugin_maps[i];
int index = added.indexOf (inf.filename);
if (index >= 0) {
- if (force_add || (inf.priority >= PriorityMedium)) inf.active = true;
+ if ((add_mode == ManualAddition) || (inf.priority >= PriorityMedium)) inf.active = true;
else (added.removeAt (index));
}
}
if (suppress_reload) return;
if (force_reload || (!added.isEmpty ())) {
RKWardMainWindow::getMain ()->initPlugins (added);
}
}
void RKSettingsModulePlugins::fixPluginMapLists () {
RK_TRACE (SETTINGS);
// Users who installed versions before 0.6.3, manually, are likely to have all.pluginmap left over. Let's handle this, on the fly.
const QString obsolete_map = RKCommonFunctions::getRKWardDataDir () + "all.pluginmap";
for (int i = 0; i < known_plugin_maps.size (); ++i) {
PluginMapStoredInfo &inf = known_plugin_maps[i];
QFileInfo info (inf.filename);
if ((!info.isReadable ()) || (inf.filename == obsolete_map)) {
known_plugin_maps.removeAt (i);
--i;
continue;
}
if (info.lastModified () != inf.last_modified) {
inf.broken_in_this_version = false;
inf.quirky_in_this_version = false;
inf.last_modified = info.lastModified ();
inf.id.clear ();
}
if (inf.id.isEmpty ()) {
parsePluginMapBasics (inf.filename, &inf.id, &inf.priority);
}
}
}
void RKSettingsModulePlugins::parsePluginMapBasics (const QString &filename, QString *id, int *priority) {
RK_TRACE (SETTINGS);
RK_ASSERT (id);
RK_ASSERT (priority);
XMLHelper xml (filename);
QDomElement de = xml.openXMLFile (DL_WARNING);
*id = RKPluginMapFile::parseId (de, xml);
*priority = xml.getMultiChoiceAttribute (de, "priority", "hidden;low;medium;high", (int) PriorityMedium, DL_WARNING);
}
QStringList RKSettingsModulePlugins::findPluginMapsRecursive (const QString &basedir) {
RK_TRACE (SETTINGS);
QDir dir (basedir);
QStringList maps = dir.entryList (QDir::Files).filter (QRegExp (".*\\.pluginmap$"));
QStringList ret;
foreach (const QString &map, maps) ret.append (dir.absoluteFilePath (map));
QStringList subdirs = dir.entryList (QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
foreach (const QString& subdir, subdirs) {
ret.append (findPluginMapsRecursive (dir.absoluteFilePath (subdir)));
}
return ret;
}
RKSettingsModulePluginsModel::RKSettingsModulePluginsModel (QObject* parent) : QAbstractTableModel (parent) {
RK_TRACE (SETTINGS);
}
RKSettingsModulePluginsModel::~RKSettingsModulePluginsModel() {
RK_TRACE (SETTINGS);
foreach (const PluginMapMetaInfo &inf, plugin_map_dynamic_info) {
delete (inf.about);
}
}
void RKSettingsModulePluginsModel::init (const RKSettingsModulePlugins::PluginMapList& known_plugin_maps) {
RK_TRACE (SETTINGS);
beginResetModel ();
plugin_maps = known_plugin_maps;
endResetModel ();
}
int RKSettingsModulePluginsModel::rowCount (const QModelIndex& parent) const {
//RK_TRACE (SETTINGS);
if (parent.isValid ()) return 0;
return plugin_maps.size ();
}
#define COLUMN_CHECKED 0
#define COLUMN_TITLE 1
#define COLUMN_ID 2
#define COLUMN_STATUS 3
#define COLUMN_COUNT 4
int RKSettingsModulePluginsModel::columnCount (const QModelIndex& parent) const {
// RK_TRACE (SETTINGS);
if (parent.isValid ()) return 0;
return COLUMN_COUNT;
}
QVariant RKSettingsModulePluginsModel::data (const QModelIndex& index, int role) const {
// RK_TRACE (SETTINGS);
if (!index.isValid ()) return QVariant ();
int col = index.column ();
const RKSettingsModulePlugins::PluginMapStoredInfo &inf = plugin_maps[index.row ()];
if (role == Qt::BackgroundRole) {
if (inf.broken_in_this_version) return QColor (Qt::red);
if (inf.quirky_in_this_version) return QColor (Qt::yellow);
return (QVariant ());
} else if (role == Qt::ForegroundRole) {
if (inf.priority < RKSettingsModulePlugins::PriorityLow) return QColor (Qt::gray);
} else if (role == Qt::ToolTipRole) {
const PluginMapMetaInfo &meta = const_cast (this)->getPluginMapMetaInfo (inf.filename);
QString desc = meta.about->toHtml ();
if (!meta.dependencies.isEmpty ()) {
desc.append ("" + i18n ("Dependencies") + "");
desc.append (RKComponentDependency::depsToHtml (meta.dependencies));
}
desc.append ("
" + inf.filename + "
");
return desc;
}
if (col == COLUMN_CHECKED) {
if (role == Qt::CheckStateRole) {
if (inf.priority < RKSettingsModulePlugins::PriorityLow) return QVariant ();
return (inf.active ? Qt::Checked : Qt::Unchecked);
}
} else if (col == COLUMN_ID) {
if (role == Qt::DisplayRole) {
return inf.id;
}
} else if (col == COLUMN_TITLE) {
if (role == Qt::DisplayRole) {
const PluginMapMetaInfo &meta = const_cast (this)->getPluginMapMetaInfo (inf.filename);
return meta.about->name;
}
} else if (col == COLUMN_STATUS) {
if (role == Qt::DisplayRole) {
if (inf.broken_in_this_version) return i18n ("Broken");
QString status;
if (RKComponentMap::getMap ()->isPluginMapLoaded (inf.filename)) status = i18n ("Loaded");
if (inf.quirky_in_this_version) {
if (!status.isEmpty ()) status.append (", ");
status.append (i18n ("Quirky"));
}
return status;
}
}
return QVariant ();
}
Qt::ItemFlags RKSettingsModulePluginsModel::flags (const QModelIndex& index) const {
// RK_TRACE (SETTINGS);
Qt::ItemFlags flags = QAbstractItemModel::flags (index);
if (index.isValid () && (index.column () == COLUMN_CHECKED)) {
if (plugin_maps[index.row ()].priority > RKSettingsModulePlugins::PriorityHidden) flags |= Qt::ItemIsUserCheckable | Qt::ItemIsEditable;
}
return flags;
}
QVariant RKSettingsModulePluginsModel::headerData (int section, Qt::Orientation orientation, int role) const {
// RK_TRACE (SETTINGS);
if ((role == Qt::DisplayRole) && (orientation == Qt::Horizontal)) {
if (section == COLUMN_CHECKED) return i18n ("Active");
if (section == COLUMN_ID) return i18n ("ID");
if (section == COLUMN_TITLE) return i18n ("Title");
if (section == COLUMN_STATUS) return i18n ("Status");
RK_ASSERT (false);
}
return QVariant ();
}
bool RKSettingsModulePluginsModel::setData (const QModelIndex& index, const QVariant& value, int role) {
RK_TRACE (SETTINGS);
if (role == Qt::CheckStateRole) {
if (index.isValid () && (index.column () == COLUMN_CHECKED)) {
plugin_maps[index.row ()].active = value.toBool ();
emit (dataChanged (index, index));
return true;
}
}
return QAbstractItemModel::setData (index, value, role);
}
void RKSettingsModulePluginsModel::insertNewStrings (int above_row) {
RK_TRACE (SETTINGS);
QStringList files = QFileDialog::getOpenFileNames (static_cast (QObject::parent ()), i18n ("Select .pluginmap-file"), RKCommonFunctions::getRKWardDataDir (), "RKWard pluginmap files [*.pluginmap](*.pluginmap)");
// already known files are activated, but not added
for (int i = files.size () -1; i >= 0; --i) {
int pos = findKnownPluginMap (files[i], plugin_maps);
if (pos >= 0) {
if (!plugin_maps[pos].active) {
plugin_maps[pos].active = true;
emit (dataChanged (index (pos, 0), index (pos, COLUMN_COUNT - 1)));
}
files.removeAt (i);
}
}
beginInsertRows (QModelIndex (), above_row, files.size ());
for (int i = files.size () - 1; i >= 0; --i) {
RKSettingsModulePlugins::PluginMapStoredInfo inf (files[i]);
inf.active = true;
RKSettingsModulePlugins::parsePluginMapBasics (files[i], &(inf.id), &(inf.priority));
plugin_maps.insert (above_row, inf);
}
endInsertRows ();
}
void RKSettingsModulePluginsModel::swapRows (int rowa, int rowb) {
RK_TRACE (SETTINGS);
RK_ASSERT ((rowa >= 0) && (rowa < rowCount ()) && (rowb >= 0) && (rowb < rowCount ()));
RKSettingsModulePlugins::PluginMapStoredInfo inf = plugin_maps[rowa];
plugin_maps[rowa] = plugin_maps[rowb];
plugin_maps[rowb] = inf;
}
bool RKSettingsModulePluginsModel::removeRows (int row, int count, const QModelIndex& parent) {
RK_TRACE (SETTINGS);
RK_ASSERT (!parent.isValid ());
if ((row < 0) || (count < 1) || (row + count > rowCount ())) return false;
for (int i = row + count - 1; i >= row; --i) {
plugin_maps.removeAt (i);
}
return true;
}
const RKSettingsModulePluginsModel::PluginMapMetaInfo& RKSettingsModulePluginsModel::getPluginMapMetaInfo (const QString& pluginmapfile) {
RK_TRACE (SETTINGS);
if (!plugin_map_dynamic_info.contains (pluginmapfile)) {
// TODO
PluginMapMetaInfo inf;
XMLHelper xml (pluginmapfile);
QDomElement doc_elem = xml.openXMLFile (DL_WARNING);
inf.about = new RKComponentAboutData (xml.getChildElement (doc_elem, "about", DL_INFO), xml);
inf.dependencies = RKComponentDependency::parseDependencies (xml.getChildElement (doc_elem, "dependencies", DL_INFO), xml);
plugin_map_dynamic_info.insert (pluginmapfile, inf);
}
return (plugin_map_dynamic_info[pluginmapfile]);
}
diff --git a/rkward/settings/rksettingsmoduleplugins.h b/rkward/settings/rksettingsmoduleplugins.h
index 0b1125df..5bd9e4d6 100644
--- a/rkward/settings/rksettingsmoduleplugins.h
+++ b/rkward/settings/rksettingsmoduleplugins.h
@@ -1,146 +1,154 @@
/***************************************************************************
rksettingsmoduleplugins - description
-------------------
begin : Wed Jul 28 2004
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. *
* *
***************************************************************************/
#ifndef RKSETTINGSMODULEPLUGINS_H
#define RKSETTINGSMODULEPLUGINS_H
#include "rksettingsmodule.h"
#include "../plugin/rkcomponentmeta.h"
#include
#include
#include
class RKMultiStringSelectorV2;
class RKSettingsModulePluginsModel;
class QButtonGroup;
class QCheckBox;
class RKSpinBox;
/** The settings-module for plugin specific settings
@author Thomas Friedrichsmeier
*/
class RKSettingsModulePlugins : public RKSettingsModule {
Q_OBJECT
public:
RKSettingsModulePlugins (RKSettings *gui, QWidget *parent);
~RKSettingsModulePlugins ();
void applyChanges () override;
void save (KConfig *config) override;
enum PluginPrefs { PreferDialog=0, PreferRecommended=1, PreferWizard=2 };
static void saveSettings (KConfig *config);
static void loadSettings (KConfig *config);
static void validateSettingsInteractive (QList*) {};
QString caption () override;
/** @returns a list of active plugin maps */
static QStringList pluginMaps ();
static int uniqueUsablePluginMapCount ();
static PluginPrefs getInterfacePreference () { return interface_pref; };
static bool showCodeByDefault () { return show_code; };
static void setShowCodeByDefault (bool shown) { show_code = shown; };
static int defaultCodeHeight () { return code_size; };
static void setDefaultCodeHeight (int new_height) { code_size = new_height; }
static int defaultSidePreviewWidth () { return side_preview_width; };
static void setDefaultSidePreviewWidth (int new_width) { side_preview_width = new_width; }
+ enum AddMode {
+ ManualAddition,
+ AddIfDefault,
+ AddIfNewAndDefault
+ };
/** register a list of available plugin-maps (which may or may not already be known). New maps are activated, automatically.
* @param maps Plugin maps (filenames) to add
- * @param force_add If true, maps are added, even if they are not "new", and had previously been disabled by the user
+ * @param add_mode Controls whether to activate maps that have low priority, or had previously been disabled by the user
* @param force_reload If true, plugin maps are always reloaded, even if no maps were added
* @param suppress_reload If true (and force_reload is false), do not reload plugin maps, even if maps were added
*/
- static void registerPluginMaps (const QStringList &maps, bool force_add, bool force_reload, bool suppress_reload=false);
+ static void registerPluginMaps (const QStringList &maps, AddMode add_mode, bool force_reload, bool suppress_reload=false);
/** Looks for the given id among known plugin maps */
static QString findPluginMapById (const QString &id);
/** marks given map as broken (in this version), and deactivates it. @Returns false is the map was already known to be broken, true otherwise. */
static bool markPluginMapAsBroken (const QString &map);
/** marks given map as quirky (in this version). @Returns false is the map was already known to be quirky, true otherwise. */
static bool markPluginMapAsQuirky (const QString &map);
/** Clears the broken or quirky flags. E.g. after the map was loaded, successfully */
static void markPluginMapAsWorking (const QString &map);
enum PluginMapPriority { PriorityHidden = 0, PriorityLow, PriorityMedium, PriorityHigh };
/** Helper struct used by RKSettingsModulePlugins to keep track of plugin map files. */
struct PluginMapStoredInfo {
PluginMapStoredInfo (const QString &_filename) : filename (_filename), active (false), broken_in_this_version (false), quirky_in_this_version (false) {};
QString filename;
bool active;
bool broken_in_this_version;
bool quirky_in_this_version;
int priority;
QString id;
QDateTime last_modified;
};
typedef QList PluginMapList;
static PluginMapList knownPluginmaps () { return known_plugin_maps; };
static void parsePluginMapBasics (const QString &filename, QString *id, int *priority);
+ /** Registers the plugin maps that are shipped with RKWard.
+ * @param force_add All default maps are also activated, even if they were already known, and disabled by the user. */
+ static void registerDefaultPluginMaps (AddMode add_mode);
public slots:
void settingChanged ();
void configurePluginmaps ();
private:
QButtonGroup *button_group;
/** plugin maps which are not necessarily active, but have been encountered, before. @see plugin_maps */
static PluginMapList known_plugin_maps;
static PluginPrefs interface_pref;
static bool show_code;
static int code_size;
static int side_preview_width;
/* TODO: This one is currently unused (leftover of GHNS-based plugin installation), but might still be of interest */
static QStringList findPluginMapsRecursive (const QString &basedir);
static void fixPluginMapLists ();
friend class RKPluginMapSelectionWidget;
/** Sets the new list of plugins. Potentially removes unreadable ones, and returns the effective list. */
static PluginMapList setPluginMaps (const PluginMapList new_list);
};
class RKSettingsModulePluginsModel : public QAbstractTableModel {
Q_OBJECT
public:
explicit RKSettingsModulePluginsModel (QObject* parent);
virtual ~RKSettingsModulePluginsModel ();
/** (re-)initialize the model */
void init (const RKSettingsModulePlugins::PluginMapList &known_plugin_maps);
RKSettingsModulePlugins::PluginMapList pluginMaps () { return plugin_maps; };
public slots:
void swapRows (int rowa, int rowb);
void insertNewStrings (int above_row);
private:
RKSettingsModulePlugins::PluginMapList plugin_maps;
struct PluginMapMetaInfo {
RKComponentAboutData *about;
QList dependencies;
};
QHash plugin_map_dynamic_info;
const PluginMapMetaInfo &getPluginMapMetaInfo (const QString &pluginmapfile);
// reimplemented model functions
int rowCount (const QModelIndex &parent = QModelIndex()) const override;
int columnCount (const QModelIndex &parent = QModelIndex()) const override;
QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool setData (const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
bool removeRows (int row, int count, const QModelIndex& parent = QModelIndex ()) override;
Qt::ItemFlags flags (const QModelIndex &index) const override;
};
#endif