1 /***************************************************************************
2 rkrinterface.cpp - description
3 -------------------
4 begin : Fri Nov 1 2002
5 copyright : (C) 2002-2019 by Thomas Friedrichsmeier
6 email : thomas.friedrichsmeier@kdemail.net
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "rkrinterface.h"
19
20 #include "rcommandstack.h"
21 #include "rkrbackendprotocol_frontend.h"
22 #include "../rkward.h"
23 #include "../rkconsole.h"
24 #include "../settings/rksettingsmoduler.h"
25 #include "../settings/rksettingsmodulegeneral.h"
26 #include "../settings/rksettingsmoduleoutput.h"
27 #include "../settings/rksettingsmodulegraphics.h"
28 #include "../settings/rksettingsmoduleplugins.h"
29 #include "../core/robjectlist.h"
30 #include "../core/renvironmentobject.h"
31 #include "../core/rkmodificationtracker.h"
32 #include "../dialogs/rkloadlibsdialog.h"
33 #include "../dialogs/rkselectlistdialog.h"
34 #include "../dialogs/rkreadlinedialog.h"
35 #include "../dialogs/rkerrordialog.h"
36 #include "../agents/showedittextfileagent.h"
37 #include "../agents/rkeditobjectagent.h"
38 #include "../agents/rkprintagent.h"
39 #include "../agents/rkdebughandler.h"
40 #include "../windows/rcontrolwindow.h"
41 #include "../windows/rkworkplace.h"
42 #include "../windows/rkcommandlog.h"
43 #include "../windows/rkhtmlwindow.h"
44 #include "../plugin/rkcomponentmap.h"
45 #include "../misc/rkcommonfunctions.h"
46 #include "../misc/rkmessagecatalog.h"
47 #include "rksessionvars.h"
48
49 #include "../windows/rkwindowcatcher.h"
50
51 #include "../rkglobals.h"
52 #include "../version.h"
53 #include "../debug.h"
54
55 #include <kmessagebox.h>
56 #include <KLocalizedString>
57
58 #include <qdir.h>
59 #include <qvalidator.h>
60
61 #include <stdlib.h>
62 #include <QFileDialog>
63 #include <QApplication>
64 #include <QPushButton>
65
66 // flush new pieces of output after this period of time:
67 #define FLUSH_INTERVAL 100
68
69 #define GET_LIB_PATHS 1
70 #define GET_HELP_BASE 2
71 #define SET_RUNTIME_OPTS 3
72 #define STARTUP_PHASE2_COMPLETE 4
73 #define GET_R_VERSION 5
74 #define RSTARTUP_COMPLETE 6
75
76 // statics
77 double RInterface::na_real;
78 int RInterface::na_int;
79
RInterface()80 RInterface::RInterface () {
81 RK_TRACE (RBACKEND);
82
83 new RCommandStackModel (this);
84 RCommandStack::regular_stack = new RCommandStack ();
85 startup_phase2_error = false;
86 command_logfile_mode = NotRecordingCommands;
87 previously_idle = false;
88 locked = 0;
89 backend_dead = false;
90 flush_timer_id = 0;
91 dummy_command_on_stack = 0;
92
93 // create a fake init command
94 RCommand *fake = new RCommand (i18n ("R Startup"), RCommand::App | RCommand::Sync | RCommand::ObjectListUpdate, i18n ("R Startup"), this, STARTUP_PHASE2_COMPLETE);
95 issueCommand (fake);
96
97 new RKSessionVars (this);
98 new RKDebugHandler (this);
99 new RKRBackendProtocolFrontend (this);
100 RKRBackendProtocolFrontend::instance ()->setupBackend ();
101
102 /////// Further initialization commands, which do not necessarily have to run before everything else can be queued, here. ///////
103 // NOTE: will receive the list as a call plain generic request from the backend ("updateInstalledPackagesList")
104 issueCommand (".rk.get.installed.packages()", RCommand::App | RCommand::Sync);
105
106 issueCommand (new RCommand (QString (), RCommand::App | RCommand::Sync | RCommand::EmptyCommand, QString (), this, RSTARTUP_COMPLETE));
107 }
108
issueCommand(const QString & command,int type,const QString & rk_equiv,RCommandReceiver * receiver,int flags,RCommandChain * chain)109 void RInterface::issueCommand (const QString &command, int type, const QString &rk_equiv, RCommandReceiver *receiver, int flags, RCommandChain *chain) {
110 RK_TRACE (RBACKEND);
111 issueCommand (new RCommand (command, type, rk_equiv, receiver, flags), chain);
112 }
113
~RInterface()114 RInterface::~RInterface(){
115 RK_TRACE (RBACKEND);
116
117 RKWindowCatcher::discardInstance ();
118 }
119
backendIsIdle()120 bool RInterface::backendIsIdle () {
121 RK_TRACE (RBACKEND);
122
123 return (RCommandStack::regular_stack->isEmpty() && (!runningCommand()));
124 }
125
popPreviousCommand(int id)126 RCommand *RInterface::popPreviousCommand (int id) {
127 RK_TRACE (RBACKEND);
128
129 RK_ASSERT (!all_current_commands.isEmpty ());
130 for (int i = all_current_commands.size () - 1; i >= 0; --i) {
131 RCommand *ret = all_current_commands[i];
132 if (ret->id () == id) {
133 RCommandStack::pop (ret);
134 all_current_commands.removeAt (i);
135 return ret;
136 }
137 }
138 RK_ASSERT (false);
139 return 0;
140 }
141
openSubcommandChain(RCommand * parent_command)142 RCommandChain* RInterface::openSubcommandChain (RCommand* parent_command) {
143 RK_TRACE (RBACKEND);
144
145 current_commands_with_subcommands.append (parent_command);
146 return RCommandStack::startChain (parent_command);
147 }
148
closeSubcommandChain(RCommand * parent_command)149 void RInterface::closeSubcommandChain (RCommand* parent_command) {
150 RK_TRACE (RBACKEND);
151
152 if (current_commands_with_subcommands.contains (parent_command)) {
153 current_commands_with_subcommands.removeAll (parent_command);
154 doNextCommand (0);
155 }
156 if (parent_command && (parent_command == dummy_command_on_stack)) {
157 all_current_commands.removeAll (dummy_command_on_stack);
158 RCommandStack::pop (dummy_command_on_stack);
159 handleCommandOut (dummy_command_on_stack);
160 dummy_command_on_stack = 0;
161 }
162 }
163
tryNextCommand()164 void RInterface::tryNextCommand () {
165 RK_TRACE (RBACKEND);
166 RCommand *command = RCommandStack::currentCommand ();
167 if (command_requests.isEmpty ()) {
168 // if the backend is not requesting anything, only priority commands will be pushed
169 if (!command) return;
170 if (!(command->type () & RCommand::PriorityCommand)) return;
171 if (all_current_commands.contains (command)) return;
172 }
173
174 bool priority = command && (command->type () & RCommand::PriorityCommand);
175 bool on_top_level = all_current_commands.isEmpty ();
176 if (!(on_top_level && locked && !(priority))) { // do not respect locks for sub-commands
177 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
178 closeSubcommandChain (command);
179 return;
180 }
181
182 if (command) {
183 all_current_commands.append (command);
184
185 if (command->status & RCommand::Canceled) {
186 // avoid passing cancelled commands to R
187 command->status |= RCommand::Failed;
188
189 // notify ourselves...
190 RCommand* dummy = popPreviousCommand (command->id ());
191 RK_ASSERT (dummy == command);
192 handleCommandOut (command);
193 return;
194 }
195
196 if (previously_idle) RKWardMainWindow::getMain ()->setRStatus (RKWardMainWindow::Busy);
197 previously_idle = false;
198
199 doNextCommand (command);
200 return;
201 }
202 }
203
204 if (on_top_level) {
205 if (!previously_idle) RKWardMainWindow::getMain ()->setRStatus (RKWardMainWindow::Idle);
206 previously_idle = true;
207 }
208 }
209
handleCommandOut(RCommand * command)210 void RInterface::handleCommandOut (RCommand *command) {
211 RK_TRACE (RBACKEND);
212
213 RK_ASSERT (command);
214
215 #ifdef RKWARD_DEBUG
216 int dl = DL_WARNING; // failed application commands are an issue worth reporting, failed user commands are not
217 if (command->type () & RCommand::User) dl = DL_DEBUG;
218 if (command->failed ()) {
219 command->status |= RCommand::WasTried | RCommand::Failed;
220 if (command->status & RCommand::ErrorIncomplete) {
221 RK_DEBUG (RBACKEND, dl, "Command failed (incomplete)");
222 } else if (command->status & RCommand::ErrorSyntax) {
223 RK_DEBUG (RBACKEND, dl, "Command failed (syntax)");
224 } else if (command->status & RCommand::Canceled) {
225 RK_DEBUG (RBACKEND, dl, "Command failed (interrupted)");
226 } else {
227 RK_DEBUG (RBACKEND, dl, "Command failed (other)");
228 }
229 RK_DEBUG (RBACKEND, dl, "failed command was: '%s'", qPrintable (command->command ()));
230 RK_DEBUG (RBACKEND, dl, "- error message was: '%s'", qPrintable (command->error ()));
231 }
232 #endif
233
234 if (command->status & RCommand::Canceled) {
235 command->status |= RCommand::HasError;
236 ROutput *out = new ROutput;
237 out->type = ROutput::Error;
238 out->output = ("--- interrupted ---");
239 command->output_list.append (out);
240 command->newOutput (out);
241 }
242 command->finished ();
243 delete command;
244 }
245
doNextCommand(RCommand * command)246 void RInterface::doNextCommand (RCommand *command) {
247 RK_TRACE (RBACKEND);
248 RBackendRequest* command_request = currentCommandRequest ();
249 if (!command_request) {
250 if (!(command && (command->type () & RCommand::PriorityCommand))) return;
251 }
252 // importantly, this point is not reached for the fake startup command
253
254 if (RK_Debug::RK_Debug_CommandStep) {
255 QTime t;
256 t.start ();
257 while (t.elapsed () < RK_Debug::RK_Debug_CommandStep) {}
258 }
259
260 flushOutput (true);
261 RCommandProxy *proxy = 0;
262 if (command) {
263 RKWardMainWindow::getMain ()->setWorkspaceMightBeModified (true);
264 proxy = command->makeProxy ();
265
266 RK_DEBUG (RBACKEND, DL_DEBUG, "running command: %s", command->command ().toLatin1().data ());
267 command->status |= RCommand::Running;
268 RCommandStackModel::getModel ()->itemChange (command);
269
270 RKCommandLog::getLog ()->addInput (command);
271
272 if (command_logfile_mode != NotRecordingCommands) {
273 bool record = true;
274 if (command_logfile_mode != RecordingCommandsUnfiltered) {
275 if (command->type () & (RCommand::Silent | RCommand::Sync)) record = false;
276 }
277 if (record) {
278 command_logfile.write (command->command ().toUtf8 ());
279 command_logfile.write ("\n");
280 }
281 }
282 }
283
284 if (command && (command->type () & RCommand::PriorityCommand)) {
285 RKRBackendProtocolFrontend::sendPriorityCommand (proxy);
286 } else {
287 RK_ASSERT (command_request);
288 command_request->command = proxy;
289 RKRBackendProtocolFrontend::setRequestCompleted (command_request);
290 command_requests.pop_back ();
291 }
292 }
293
rCommandDone(RCommand * command)294 void RInterface::rCommandDone (RCommand *command) {
295 RK_TRACE (RBACKEND);
296
297 if (command->failed ()) {
298 startup_phase2_error = true;
299 return;
300 }
301
302 if (command->getFlags () == GET_LIB_PATHS) {
303 RK_ASSERT (command->getDataType () == RData::StringVector);
304 RKSettingsModuleRPackages::r_libs_user = command->stringVector ().value (0);
305 RKSettingsModuleRPackages::defaultliblocs += command->stringVector ().mid (1);
306
307 RCommandChain *chain = command->parent;
308 RK_ASSERT (chain);
309 RK_ASSERT (!chain->isClosed ());
310
311 // apply user configurable run time options
312 QStringList commands = RKSettingsModuleR::makeRRunTimeOptionCommands () + RKSettingsModuleRPackages::makeRRunTimeOptionCommands () + RKSettingsModuleOutput::makeRRunTimeOptionCommands () + RKSettingsModuleGraphics::makeRRunTimeOptionCommands ();
313 for (QStringList::const_iterator it = commands.begin (); it != commands.end (); ++it) {
314 issueCommand (*it, RCommand::App | RCommand::Sync, QString (), this, SET_RUNTIME_OPTS, chain);
315 }
316 // initialize output file
317 issueCommand ("rk.set.output.html.file (\"" + RKSettingsModuleGeneral::filesPath () + "/rk_out.html\")\n", RCommand::App | RCommand::Sync, QString (), this, SET_RUNTIME_OPTS, chain);
318 #ifdef Q_OS_MACOS
319 // On MacOS, the backend is started from inside R home to allow resolution of dynamic libs. Re-set to frontend wd, here.
320 issueCommand ("setwd (" + RKRSharedFunctionality::quote (QDir::currentPath ()) + ")\n", RCommand::App | RCommand::Sync, QString (), this, SET_RUNTIME_OPTS, chain);
321 #endif
322 closeChain (chain);
323 } else if (command->getFlags () == GET_R_VERSION) {
324 RK_ASSERT (command->getDataType () == RData::StringVector);
325 RK_ASSERT (command->getDataLength () == 1);
326 RKSessionVars::setRVersion (command->stringVector ().value (0));
327 } else if (command->getFlags () == GET_HELP_BASE) {
328 RK_ASSERT (command->getDataType () == RData::StringVector);
329 RK_ASSERT (command->getDataLength () == 1);
330 RKSettingsModuleR::help_base_url = command->stringVector ().value (0);
331 } else if (command->getFlags () == SET_RUNTIME_OPTS) {
332 // no special handling. In case of failures, staturt_fail was set to true, above.
333 } else if (command->getFlags () == STARTUP_PHASE2_COMPLETE) {
334 QString message = startup_errors;
335 if (startup_phase2_error) message.append (i18n ("<p>\t-An unspecified error occurred that is not yet handled by RKWard. Likely RKWard will not function properly. Please check your setup.</p>\n"));
336 if (!message.isEmpty ()) {
337 message.prepend (i18n ("<p>There was a problem starting the R backend. The following error(s) occurred:</p>\n"));
338
339 QString details = command->fullOutput().replace('<', "<").replace('\n', "<br>");
340 if (!details.isEmpty ()) {
341 // WORKAROUND for stupid KMessageBox behavior. (kdelibs 4.2.3)
342 // If length of details <= 512, it tries to show the details as a QLabel.
343 details = details.leftJustified (513);
344 }
345 KMessageBox::detailedError (0, message, details, i18n ("Error starting R"), KMessageBox::Notify | KMessageBox::AllowLink);
346 }
347
348 startup_errors.clear ();
349 } else if (command->getFlags () == RSTARTUP_COMPLETE) {
350 RKSettings::validateSettingsInteractive ();
351 }
352 }
353
handleRequest(RBackendRequest * request)354 void RInterface::handleRequest (RBackendRequest* request) {
355 RK_TRACE (RBACKEND);
356
357 if (request->type == RBackendRequest::OutputStartedNotification) {
358 RK_ASSERT (flush_timer_id == 0);
359 flush_timer_id = startTimer (FLUSH_INTERVAL); // calls flushOutput (false); see timerEvent ()
360 RKRBackendProtocolFrontend::setRequestCompleted (request);
361 return;
362 }
363
364 flushOutput (true);
365 if (request->type == RBackendRequest::CommandOut) {
366 RCommandProxy *cproxy = request->takeCommand ();
367 if (cproxy) {
368 RK_DEBUG (RBACKEND, DL_DEBUG, "Command out \"%s\", id %d", qPrintable (cproxy->command), cproxy->id);
369 } else {
370 RK_DEBUG (RBACKEND, DL_DEBUG, "Fake command out");
371 }
372 RCommand *command = 0;
373 // NOTE: the order of processing is: first try to submit the next command, then handle the old command.
374 // The reason for doing it this way, instead of the reverse, is that this allows the backend thread / process to continue working, concurrently
375 // NOTE: cproxy should only ever be 0 in the very first cycle
376 if (cproxy) command = popPreviousCommand (cproxy->id);
377 if (request->synchronous) command_requests.append (request);
378 tryNextCommand ();
379 if (cproxy) {
380 RK_ASSERT (command);
381 command->mergeAndDeleteProxy (cproxy);
382 handleCommandOut (command);
383 }
384 tryNextCommand ();
385 } else if (request->type == RBackendRequest::HistoricalSubstackRequest) {
386 RCommandProxy *cproxy = request->command;
387 RCommand *parent = 0;
388 for (int i = all_current_commands.size () - 1; i >= 0; --i) {
389 if (all_current_commands[i]->id () == cproxy->id) {
390 parent = all_current_commands[i];
391 break;
392 }
393 }
394 command_requests.append (request);
395 processHistoricalSubstackRequest (request->params["call"].toStringList (), parent);
396 } else if (request->type == RBackendRequest::PlainGenericRequest) {
397 request->params["return"] = QVariant (processPlainGenericRequest (request->params["call"].toStringList ()));
398 RKRBackendProtocolFrontend::setRequestCompleted (request);
399 } else if (request->type == RBackendRequest::Started) {
400 // The backend thread has finished basic initialization, but we still have more to do...
401 startup_errors = request->params["message"].toString ();
402
403 command_requests.append (request);
404 RCommandChain *chain = openSubcommandChain (runningCommand ());
405
406 issueCommand ("paste (R.version[c (\"major\", \"minor\")], collapse=\".\")\n", RCommand::GetStringVector | RCommand::App | RCommand::Sync, QString (), this, GET_R_VERSION, chain);
407 // find out about standard library locations
408 issueCommand ("c(path.expand(Sys.getenv(\"R_LIBS_USER\")), .libPaths())\n", RCommand::GetStringVector | RCommand::App | RCommand::Sync, QString (), this, GET_LIB_PATHS, chain);
409 // start help server / determined help base url
410 issueCommand (".rk.getHelpBaseUrl ()\n", RCommand::GetStringVector | RCommand::App | RCommand::Sync, QString (), this, GET_HELP_BASE, chain);
411
412 // NOTE: more initialization commands get run *after* we have determined the standard library locations (see rCommandDone())
413 } else {
414 processRBackendRequest (request);
415 }
416 }
417
timerEvent(QTimerEvent *)418 void RInterface::timerEvent (QTimerEvent *) {
419 // do not trace. called periodically
420 flushOutput (false);
421 }
422
flushOutput(bool forced)423 void RInterface::flushOutput (bool forced) {
424 // do not trace. called periodically
425 // RK_TRACE (RBACKEND);
426 ROutputList list = RKRBackendProtocolFrontend::instance ()->flushOutput (forced);
427
428 // this must come _after_ the output has been flushed.
429 if (forced || !list.isEmpty ()) {
430 if (flush_timer_id != 0) {
431 killTimer (flush_timer_id);
432 flush_timer_id = 0;
433 }
434 }
435
436 foreach (ROutput *output, list) {
437 if (all_current_commands.isEmpty ()) {
438 RK_DEBUG (RBACKEND, DL_WARNING, "output without receiver'%s'", qPrintable (output->output));
439 delete output;
440 continue; // to delete the other output pointers, too
441 } else {
442 RK_DEBUG (RBACKEND, DL_DEBUG, "output '%s'", qPrintable (output->output));
443 }
444
445 bool first = true;
446 foreach (RCommand* command, all_current_commands) {
447 ROutput *coutput = output;
448 if (!first) { // this output belongs to several commands at once. So we need to copy it.
449 coutput = new ROutput;
450 coutput->type = output->type;
451 coutput->output = output->output;
452 }
453 first = false;
454
455 if (coutput->type == ROutput::Output) {
456 command->status |= RCommand::HasOutput;
457 command->output_list.append (coutput);
458 } else if (coutput->type == ROutput::Warning) {
459 command->status |= RCommand::HasWarnings;
460 command->output_list.append (coutput);
461 } else if (coutput->type == ROutput::Error) {
462 command->status |= RCommand::HasError;
463 // An error output is typically just the copy of the previous output, so merge if possible
464 if (command->output_list.isEmpty ()) {
465 command->output_list.append (coutput);
466 }
467 if (command->output_list.last ()->output == coutput->output) {
468 command->output_list.last ()->type = ROutput::Error;
469 continue; // don't call command->newOutput(), again!
470 }
471 }
472 command->newOutput (coutput);
473 }
474 }
475 }
476
issueCommand(RCommand * command,RCommandChain * chain)477 void RInterface::issueCommand (RCommand *command, RCommandChain *chain) {
478 RK_TRACE (RBACKEND);
479
480 if (command->command ().isEmpty ()) command->_type |= RCommand::EmptyCommand;
481 if (RKCarbonCopySettings::shouldCarbonCopyCommand (command)) {
482 command->_type |= RCommand::CCCommand;
483 if (RKCarbonCopySettings::includeOutputInCarbonCopy ()) command->_type |= RCommand::CCOutput;
484 }
485 RCommandStack::issueCommand (command, chain);
486 tryNextCommand ();
487 }
488
startChain(RCommandChain * parent)489 RCommandChain *RInterface::startChain (RCommandChain *parent) {
490 RK_TRACE (RBACKEND);
491
492 return RCommandStack::startChain (parent);
493 };
494
closeChain(RCommandChain * chain)495 void RInterface::closeChain (RCommandChain *chain) {
496 RK_TRACE (RBACKEND);
497
498 RCommandStack::closeChain (chain);
499 tryNextCommand ();
500 };
501
cancelAll()502 void RInterface::cancelAll () {
503 RK_TRACE (RBACKEND);
504
505 QList<RCommand*> all_commands = RCommandStack::regular_stack->allCommands ();
506 foreach (RCommand* command, all_commands) cancelCommand (command);
507 }
508
softCancelCommand(RCommand * command)509 bool RInterface::softCancelCommand (RCommand* command) {
510 RK_TRACE (RBACKEND);
511
512 if (!(command->type () & RCommand::Running)) {
513 cancelCommand (command);
514 }
515 return command->status & RCommand::Canceled;
516 }
517
cancelCommand(RCommand * command)518 void RInterface::cancelCommand (RCommand *command) {
519 RK_TRACE (RBACKEND);
520
521 if (!(command->type () & RCommand::Sync)) {
522 command->status |= RCommand::Canceled;
523 if (command->type () & RCommand::Running) {
524 if ((RKDebugHandler::instance ()->state () == RKDebugHandler::InDebugPrompt) && (command == RKDebugHandler::instance ()->command ())) {
525 RKDebugHandler::instance ()->sendCancel ();
526 } else {
527 RKRBackendProtocolFrontend::instance ()->interruptCommand (command->id ());
528 }
529 }
530 RCommandStackModel::getModel ()->itemChange (command);
531 } else {
532 RK_ASSERT (false);
533 }
534 }
535
pauseProcessing(bool pause)536 void RInterface::pauseProcessing (bool pause) {
537 RK_TRACE (RBACKEND);
538
539 if (pause) locked |= User;
540 else locked -= locked & User;
541 }
542
processPlainGenericRequest(const QStringList & calllist)543 QStringList RInterface::processPlainGenericRequest (const QStringList &calllist) {
544 RK_TRACE (RBACKEND);
545
546 QString call = calllist.value (0);
547 if (call == "get.tempfile.name") {
548 RK_ASSERT (calllist.count () == 3);
549 return (QStringList (RKCommonFunctions::getUseableRKWardSavefileName (calllist.value (1), calllist.value (2))));
550 } else if (call == "set.output.file") {
551 RK_ASSERT (calllist.count () == 2);
552 RKOutputWindowManager::self ()->setCurrentOutputPath (calllist.value (1));
553 } else if (call == "wdChange") {
554 // in case of separate processes, apply new working directory in frontend, too.
555 QDir::setCurrent (calllist.value (1));
556 emit (backendWorkdirChanged());
557 } else if (call == "highlightRCode") {
558 return (QStringList (RKCommandHighlighter::commandToHTML (calllist.value (1))));
559 } else if (call == "quit") {
560 RKWardMainWindow::getMain ()->close ();
561 // if we're still alive, quitting was cancelled
562 return (QStringList ("FALSE"));
563 } else if (call == "preLocaleChange") {
564 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"));
565 if (res != KMessageBox::Continue) return (QStringList ("FALSE"));
566 } else if (call == "listPlugins") {
567 RK_ASSERT (calllist.count () == 1);
568 return RKComponentMap::getMap ()->listPlugins ();
569 } else if (call == "setPluginStatus") {
570 QStringList params = calllist.mid (1);
571 RK_ASSERT ((params.size () % 3) == 0);
572 const int rows = params.size () / 3;
573 QStringList ids = params.mid (0, rows);
574 QStringList contexts = params.mid (rows, rows);
575 QStringList visible = params.mid (rows*2, rows);
576 RKComponentMap::getMap ()->setPluginStatus (ids, contexts, visible);
577 } else if (call == "loadPluginMaps") {
578 bool force = (calllist.value (1) == "force");
579 bool reload = (calllist.value (2) == "reload");
580 RKSettingsModulePlugins::registerPluginMaps (calllist.mid (3), force, reload);
581 } else if (call == "updateInstalledPackagesList") {
582 RKSessionVars::instance ()->setInstalledPackages (calllist.mid (1));
583 } else if (call == "showHTML") {
584 RK_ASSERT (calllist.count () == 2);
585 RKWorkplace::mainWorkplace ()->openHelpWindow (QUrl::fromUserInput (calllist.value (1), QDir::currentPath (), QUrl::AssumeLocalFile));
586 } else if (call == "select.list") {
587 QString title = calllist.value (1);
588 bool multiple = (calllist.value (2) == "multi");
589 int num_preselects = calllist.value (3).toInt ();
590 QStringList preselects = calllist.mid (4, num_preselects);
591 QStringList choices = calllist.mid (4 + num_preselects);
592
593 QStringList results = RKSelectListDialog::doSelect (QApplication::activeWindow(), title, choices, preselects, multiple);
594 if (results.isEmpty ()) results.append (""); // R wants to have it that way
595 return (results);
596 } else if (call == "commandHistory") {
597 if (calllist.value (1) == "get") {
598 return (RKConsole::mainConsole ()->commandHistory ());
599 } else {
600 RKConsole::mainConsole ()->setCommandHistory (calllist.mid (2), calllist.value (1) == "append");
601 }
602 } else if (call == "getWorkspaceUrl") {
603 QUrl url = RKWorkplace::mainWorkplace ()->workspaceURL ();
604 if (!url.isEmpty ()) return (QStringList (url.url ()));
605 } else if (call == "workplace.layout") {
606 if (calllist.value (1) == "set") {
607 if (calllist.value (2) == "close") RKWorkplace::mainWorkplace ()->closeAll ();
608 QStringList list = calllist.mid (3);
609 RKWorkplace::mainWorkplace ()->restoreWorkplace (list);
610 } else {
611 RK_ASSERT (calllist.value (1) == "get");
612 return (RKWorkplace::mainWorkplace ()->makeWorkplaceDescription ());
613 }
614 } else if (call == "set.window.placement.hint") {
615 RKWorkplace::mainWorkplace ()->setWindowPlacementOverrides (calllist.value (1), calllist.value (2), calllist.value (3));
616 } else if (call == "getSessionInfo") {
617 // Non-translatable on purpose. This is meant for posting to the bug tracker, mostly.
618 QStringList lines ("-- Frontend --");
619 lines.append (RKSessionVars::frontendSessionInfo ());
620 lines.append (QString ());
621 lines.append ("-- Backend --");
622 lines.append ("Debug message file (this may contain relevant diagnostic output in case of trouble):");
623 lines.append (calllist.value (1));
624 lines.append (QString ());
625 lines.append ("R version (compile time): " + calllist.value (2));
626 return (lines);
627 } else if (call == "recordCommands") {
628 RK_ASSERT (calllist.count () == 3);
629 QString filename = calllist.value (1);
630 bool unfiltered = (calllist.value (2) == "include.all");
631
632 if (filename.isEmpty ()) {
633 command_logfile_mode = NotRecordingCommands;
634 command_logfile.close ();
635 } else {
636 if (command_logfile_mode != NotRecordingCommands) {
637 return (QStringList ("Attempt to start recording, while already recording commands. Ignoring.)"));
638 } else {
639 command_logfile.setFileName (filename);
640 bool ok = command_logfile.open (QIODevice::WriteOnly | QIODevice::Truncate);
641 if (ok) {
642 if (unfiltered) command_logfile_mode = RecordingCommandsUnfiltered;
643 else command_logfile_mode = RecordingCommands;
644 } else {
645 return (QStringList ("Could not open file for writing. Not recording commands"));
646 }
647 }
648 }
649 } else if (call == "printPreview") {
650 RKPrintAgent::printPostscript (calllist.value (1), true);
651 } else if (call == "endBrowserContext") {
652 RKDebugHandler::instance ()->endDebug ();
653 } else if (call == "switchLanguage") {
654 RKMessageCatalog::switchLanguage (calllist.value (1));
655 } else {
656 return (QStringList ("Error: unrecognized request '" + call + "'."));
657 }
658
659 // for those calls which were recognized, but do not return anything
660 return QStringList ();
661 }
662
processHistoricalSubstackRequest(const QStringList & calllist,RCommand * parent_command)663 void RInterface::processHistoricalSubstackRequest (const QStringList &calllist, RCommand *parent_command) {
664 RK_TRACE (RBACKEND);
665
666 RCommandChain *in_chain;
667 if (!parent_command) {
668 // This can happen for Tcl events. Create a dummy command on the stack to keep things looping.
669 parent_command = new RCommand (QString (), RCommand::App | RCommand::EmptyCommand | RCommand::Sync);
670 RCommandStack::issueCommand (parent_command, 0);
671 all_current_commands.append (parent_command);
672 dummy_command_on_stack = parent_command; // so we can get rid of it again, after it's sub-commands have finished
673 }
674 in_chain = openSubcommandChain (parent_command);
675 RK_DEBUG (RBACKEND, DL_DEBUG, "started sub-command chain (%p) for command %s", in_chain, qPrintable (parent_command->command ()));
676
677 QString call = calllist.value (0);
678 if (call == "sync") {
679 RK_ASSERT (calllist.count () >= 2);
680
681 for (int i = 1; i < calllist.count (); ++i) {
682 QString object_name = calllist[i];
683 RObject *obj = RObjectList::getObjectList ()->findObject (object_name);
684 if (obj) {
685 RK_DEBUG (RBACKEND, DL_DEBUG, "triggering update for symbol %s", object_name.toLatin1 ().data());
686 obj->markDataDirty ();
687 obj->updateFromR (in_chain);
688 } else {
689 RK_DEBUG (RBACKEND, DL_WARNING, "lookup failed for changed symbol %s", object_name.toLatin1 ().data());
690 }
691 }
692 } else if (call == "syncenvs") {
693 RK_DEBUG (RBACKEND, DL_DEBUG, "triggering update of object list");
694 int search_len = calllist.value (1).toInt ();
695 RObjectList::getObjectList ()->updateFromR (in_chain, calllist.mid (2, search_len), calllist.mid (2 + search_len));
696 } else if (call == "syncglobal") {
697 RK_DEBUG (RBACKEND, DL_DEBUG, "triggering update of globalenv");
698 RObjectList::getGlobalEnv ()->updateFromR (in_chain, calllist.mid (1));
699 #ifndef DISABLE_RKWINDOWCATCHER
700 // NOTE: WARNING: When converting these to PlainGenericRequests, the occasional "error, figure margins too large" starts coming up, again. Not sure, why.
701 } else if (call == "startOpenX11") {
702 RK_ASSERT (calllist.count () == 2);
703 RKWindowCatcher::instance ()->start (calllist.value (1).toInt ());
704 } else if (call == "endOpenX11") {
705 RK_ASSERT (calllist.count () == 2);
706 RKWindowCatcher::instance ()->stop (calllist.value (1).toInt ());
707 } else if (call == "updateDeviceHistory") {
708 if (calllist.count () >= 2) {
709 RKWindowCatcher::instance ()->updateHistory (calllist.mid (1));
710 }
711 } else if (call == "killDevice") {
712 RK_ASSERT (calllist.count () == 2);
713 RKWindowCatcher::instance ()->killDevice (calllist.value (1).toInt ());
714 #endif // DISABLE_RKWINDOWCATCHER
715 } else if (call == "edit") {
716 RK_ASSERT (calllist.count () >= 2);
717
718 QStringList object_list = calllist.mid (1);
719 new RKEditObjectAgent (object_list, in_chain);
720 } else if (call == "require") {
721 if (calllist.count () >= 2) {
722 QString lib_name = calllist[1];
723 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));
724 RKLoadLibsDialog::showInstallPackagesModal (0, in_chain, lib_name);
725 issueCommand (".rk.set.reply (\"\")", RCommand::App | RCommand::Sync, QString (), 0, 0, in_chain);
726 } else {
727 issueCommand (".rk.set.reply (\"Too few arguments in call to require.\")", RCommand::App | RCommand::Sync, QString (), 0, 0, in_chain);
728 }
729 } else if (call == "doPlugin") {
730 if (calllist.count () >= 3) {
731 QString message;
732 bool ok;
733 RKComponentMap::ComponentInvocationMode mode = RKComponentMap::ManualSubmit;
734 if (calllist[2] == "auto") mode = RKComponentMap::AutoSubmit;
735 else if (calllist[2] == "submit") mode = RKComponentMap::AutoSubmitOrFail;
736 ok = RKComponentMap::invokeComponent (calllist[1], calllist.mid (3), mode, &message, in_chain);
737
738 if (!message.isEmpty ()) {
739 QString type = "warning";
740 if (!ok) type = "error";
741 issueCommand (".rk.set.reply (list (type=\"" + type + "\", message=\"" + RKCommonFunctions::escape (message) + "\"))", RCommand::App | RCommand::Sync, QString (), 0, 0, in_chain);
742 }
743 } else {
744 RK_ASSERT (false);
745 }
746 } else {
747 issueCommand ("stop (\"Unrecognized call '" + call + "'. Ignoring\")", RCommand::App | RCommand::Sync, QString (), 0, 0, in_chain);
748 }
749
750 closeChain (in_chain);
751 }
752
addButtonToBox(QDialog * dialog,QDialogButtonBox * box,QDialogButtonBox::StandardButton which,const QString & text,const QString & def_text,bool is_default)753 int addButtonToBox (QDialog *dialog, QDialogButtonBox *box, QDialogButtonBox::StandardButton which, const QString &text, const QString &def_text, bool is_default) {
754 if (text.isEmpty ()) return 0;
755 QPushButton *button = box->addButton (which);
756 if (text != def_text) button->setText (text);
757 if (is_default) button->setDefault (true);
758 QObject::connect (button, &QPushButton::clicked, [dialog, which]() { dialog->done (which); });
759 return 1;
760 }
761
processRBackendRequest(RBackendRequest * request)762 void RInterface::processRBackendRequest (RBackendRequest *request) {
763 RK_TRACE (RBACKEND);
764
765 // first, copy out the type. Allows for easier typing below
766 RBackendRequest::RCallbackType type = request->type;
767
768 if (type == RBackendRequest::CommandLineIn) {
769 int id = request->params["commandid"].toInt ();
770 RCommand *command = all_current_commands.value (0, 0); // User command will always be the first.
771 if ((command == 0) || (command->id () != id)) {
772 RK_ASSERT (false);
773 } else {
774 command->commandLineIn ();
775 }
776 } else if (type == RBackendRequest::ShowMessage) {
777 QString caption = request->params["caption"].toString ();
778 QString message = request->params["message"].toString ();
779 QString button_yes = request->params["button_yes"].toString ();
780 QString button_no = request->params["button_no"].toString ();
781 QString button_cancel = request->params["button_cancel"].toString ();
782 QString def_button = request->params["default"].toString ();
783
784 // NOTE: In order to support non-modal (information) dialogs, we cannot use KMessageBox or QMessgaeBox, below.
785 QDialog* dialog = new QDialog ();
786 dialog->setResult (-1); // We use this to stand for cancelled
787 QDialogButtonBox *button_box = new QDialogButtonBox (dialog);
788 QPushButton *button;
789 int button_count = 0;
790 button_count += addButtonToBox (dialog, button_box, QDialogButtonBox::Yes, button_yes, "yes", def_button == button_yes);
791 button_count += addButtonToBox (dialog, button_box, QDialogButtonBox::No, button_no, "no", def_button == button_no);
792 button_count += addButtonToBox (dialog, button_box, QDialogButtonBox::Cancel, button_cancel, "cancel", def_button == button_cancel);
793 if (!button_count) { // cannot have no button defined at all
794 button_count += addButtonToBox (dialog, button_box, QDialogButtonBox::Ok, "ok", "ok", true);
795 }
796
797 bool synchronous = request->synchronous || (button_count > 1);
798 KMessageBox::createKMessageBox (dialog, button_box, button_count < 2 ? QMessageBox::Information : QMessageBox::Question, message, QStringList (), QString (), 0, KMessageBox::Notify | KMessageBox::NoExec);
799 dialog->setWindowTitle (caption);
800
801 if (!synchronous) {
802 dialog->setAttribute (Qt::WA_DeleteOnClose);
803 dialog->show();
804
805 RKRBackendProtocolFrontend::setRequestCompleted (request);
806 return;
807 } else {
808 int result = dialog->exec ();
809 QString result_string;
810 if (result == QDialogButtonBox::Yes || result == QDialogButtonBox::Ok) result_string = "yes";
811 else if (result == QDialogButtonBox::No) result_string = "no";
812 else result_string = "cancel";
813 request->params["result"] = result_string;
814 delete dialog;
815 }
816 } else if (type == RBackendRequest::ReadLine) {
817 QString result;
818
819 // yes, readline *can* be called outside of a current command (e.g. from tcl/tk)
820 bool dummy_command = false;
821 RCommand *command = runningCommand ();
822 if (!command) {
823 command = new RCommand ("", RCommand::EmptyCommand);
824 dummy_command = true;
825 }
826
827
828 bool ok = RKReadLineDialog::readLine (0, i18n ("R backend requests information"), request->params["prompt"].toString (), command, &result);
829 request->params["result"] = QVariant (result);
830
831 if (dummy_command) delete command;
832 if (!ok) request->params["cancelled"] = QVariant (true);
833 } else if (type == RBackendRequest::Debugger) {
834 RKDebugHandler::instance ()->debugCall (request, runningCommand ());
835 return; // request will be closed by the debug handler
836 } else if ((type == RBackendRequest::ShowFiles) || (type == RBackendRequest::EditFiles)) {
837 ShowEditTextFileAgent::showEditFiles (request);
838 return; // we are not done, yet!
839 } else if (type == RBackendRequest::ChooseFile) {
840 QString filename;
841 if (request->params["new"].toBool ()) {
842 filename = QFileDialog::getSaveFileName ();
843 } else {
844 filename = QFileDialog::getOpenFileName ();
845 }
846 request->params["result"] = QVariant (filename);
847 } else if (type == RBackendRequest::SetParamsFromBackend) {
848 na_real = request->params["na_real"].toDouble ();
849 na_int = request->params["na_int"].toInt ();
850 } else if (type == RBackendRequest::BackendExit) {
851 if (request->params.value ("regular", QVariant (false)).toBool ()) backend_dead = true; // regular exit via QuitCommand
852 if (!backend_dead) {
853 backend_dead = true;
854 QString message = request->params["message"].toString ();
855 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!");
856 RKErrorDialog::reportableErrorMessage (0, message, QString (), i18n ("R engine has died"), "r_engine_has_died");
857 }
858 } else {
859 RK_ASSERT (false);
860 }
861
862 RKRBackendProtocolFrontend::setRequestCompleted (request);
863 }
864
865