diff --git a/rkward/main.cpp b/rkward/main.cpp
index 90403bb7..d23e1d8c 100644
--- a/rkward/main.cpp
+++ b/rkward/main.cpp
@@ -1,343 +1,397 @@
/***************************************************************************
main.cpp - description
-------------------
begin : Tue Oct 29 20:06:08 CET 2002
- copyright : (C) 2002-2018 by Thomas Friedrichsmeier
+ copyright : (C) 2002-2019 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. *
* *
***************************************************************************/
/*!
** \mainpage RKWard
** \author Thomas Friedrichsmeier and the RKWard Team
**
** \section website Website
**
** RKWard's project page
**
** \section description Description
**
** RKWard is meant to become an easy to use, transparent frontend to the R-language, a very powerful, yet hard-to-get-into
** scripting-language with a strong focus on statistic functions. It will not only provide a convenient user-interface, however, but also
** take care of seamless integration with an office-suite. Practical statistics is not just about calculating, after all, but also about
** documenting and ultimately publishing the results.
**
** RKWard then is (will be) something like a free replacement for commercial statistical packages.
**
** \section docOverview Getting started with the documentation
**
** The following sections of the API-documentation provide useful entry-points:
**
** - \ref UsingTheInterfaceToR
** - \ref RKComponentProperties
**
** \section copyright Copyright
**
** 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
#include
#include
#include
#ifdef WITH_KCRASH
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
+#include
+#include
#ifdef Q_OS_MACOS
// Needed to allow execution of launchctl
# include
#endif
#include
#include
#include "rkward.h"
#include "rkglobals.h"
#include "settings/rksettingsmoduledebug.h"
#include "windows/rkdebugmessagewindow.h"
#include "misc/rkdbusapi.h"
#include "misc/rkcommonfunctions.h"
#ifdef Q_OS_WIN
// these are needed for the exit hack.
# include
#endif
#include "debug.h"
#include "version.h"
#ifndef R_EXECUTABLE
# define R_EXECUTABLE ""
#endif
#ifdef Q_OS_WIN
# define PATH_VAR_SEP ';'
#else
# define PATH_VAR_SEP ':'
#endif
QString findExeAtPath (const QString appname, const QString &path) {
QDir dir (path);
dir.makeAbsolute ();
if (QFileInfo (dir.filePath (appname)).isExecutable ()) return dir.filePath (appname);
#ifdef Q_OS_WIN
if (QFileInfo (dir.filePath (appname + ".exe")).isExecutable ()) return dir.filePath (appname + ".exe");
if (QFileInfo (dir.filePath (appname + ".com")).isExecutable ()) return dir.filePath (appname + ".com");
if (QFileInfo (dir.filePath (appname + ".bat")).isExecutable ()) return dir.filePath (appname + ".bat");
#endif
return QString ();
}
bool RK_Debug_Terminal = true;
QMutex RK_Debug_Mutex;
void RKDebugMessageOutput (QtMsgType type, const QMessageLogContext &ctx, const QString &msg) {
RK_Debug_Mutex.lock ();
if (type == QtFatalMsg) {
fprintf (stderr, "%s\n", qPrintable (msg));
}
if (RK_Debug_Terminal) {
#ifdef QT_MESSAGELOGCONTEXT
fprintf (stderr, "%s, %s: %s", ctx.file, ctx.function, qPrintable (msg));
#else
Q_UNUSED (ctx);
fprintf (stderr, "%s", qPrintable (msg));
#endif
fprintf (stderr, "\n");
} else {
#ifdef QT_MESSAGELOGCONTEXT
RK_Debug::debug_file->write (ctx.file);
RK_Debug::debug_file->write (ctx.function);
#endif
RK_Debug::debug_file->write (qPrintable (msg));
RK_Debug::debug_file->write ("\n");
RK_Debug::debug_file->flush ();
}
RK_Debug_Mutex.unlock ();
}
/** The point of this redirect (to be called via the RK_DEBUG() macro) is to separate RKWard specific debug messages from
* any other noise, coming from Qt / kdelibs. Also it allows us to retain info on flags and level, and to show messages
* in a tool window, esp. for debugging plugins. */
void RKDebug (int flags, int level, const char *fmt, ...) {
const int bufsize = 1024*8;
char buffer[bufsize];
va_list ap;
va_start (ap, fmt);
vsnprintf (buffer, bufsize-1, fmt, ap);
va_end (ap);
RKDebugMessageOutput (QtDebugMsg, QMessageLogContext (), buffer);
if (QApplication::instance ()->thread () == QThread::currentThread ()) {
// not safe to call from any other than the GUI thread
RKDebugMessageWindow::newMessage (flags, level, QString (buffer));
}
}
+/** Check if the given path to R (or "auto") is executable, and fail with an appropriate message, otherwise. If "auto" is given as input, try to auto-locate an R installation at the standard
+installation path(s) for this platform. */
+QString resolveRSpecOrFail (QString input, QString message) {
+ if (input == QLatin1String ("auto")) {
+ QString ret;
+#ifdef Q_OS_MACOS
+ QString instroot ("/Library/Frameworks/R.framework/Versions");
+ if (QFileInfo (instroot).isReadable ()) {
+ QDir dir (instroot);
+ QStringList candidates = dir.entryList ();
+ QVersionNumber highest (0, 0, 0);
+ for (int i = candidates.count (); i >= 0; --i) {
+ QString found = findExeAtPath ("Resoures/bin/R", dir.absoluteFilePath (candidates[i]));
+ if (!found.isNull()) {
+ QVersionNumber version = QVersionNumber::fromString (candidates[i]);
+ if (version > highest) {
+ ret = found;
+ }
+ }
+ }
+ }
+#endif
+#ifdef Q_OS_WIN
+ QString instroot = QString (getenv ("PROGRAMFILES")) + "/R";
+ if (!QFileInfo (instroot).isReadable ()) instroot = QString (getenv ("PROGRAMFILES(x86)")) + "/R";
+ if (QFileInfo (instroot).isReadable ()) {
+ QDir dir (instroot);
+ QStringList candidates = dir.entryList (QStringList ("R-*"), QDir::Dirs);
+ QVersionNumber highest (0, 0, 0);
+ for (int i = candidates.count (); i >= 0; --i) {
+ QString found = findExeAtPath ("bin/R", dir.absoluteFilePath (candidates[i]));
+ if (!found.isNull()) {
+ QVersionNumber version = QVersionNumber::fromString (candidates[i].mid (2));
+ if (version > highest) {
+ ret = found;
+ }
+ }
+ }
+ }
+#endif
+ // On Unix, but also, if R was not found in the default locations on Windows or Mac,
+ // try to find R in the system path.
+ if (ret.isNull ()) ret = QStandardPaths::findExecutable ("R");
+
+ if (ret.isNull() || !QFileInfo (ret).isExecutable()) {
+ QMessageBox::critical (0, i18n ("Unable to detect R installation"), i18n ("RKWard failed to detect an R installation on this system. Either R is not installed, or not at one of the standard installation locations. You can use the command line parameter '--r-executable auto / PATH_TO_R', or supply an rkward.ini file to specify a non-standard location."));
+ exit (1);
+ }
+
+ RK_DEBUG (APP, DL_DEBUG, "Using auto-detected R at %s", qPrintable (ret));
+ return ret;
+ } else {
+ if (QFileInfo (input).isExecutable ()) {
+ return input;
+ }
+
+ // TODO, while fixing krazy2 warnings: KMessageBox layout for static messages is quirky in that it has squeezed caption, and does not allow resize -> Submit a patch.
+ //KMessageBox::error (0, QString ("The R executable specified on the command line (%1) does not exist or is not executable.").arg (r_exe), "Specified R executable does not exist");
+ QMessageBox::critical (0, i18n ("Specified R executable does not exist"), message);
+ exit(1);
+ }
+ return QString(); // not reached
+}
+
int main (int argc, char *argv[]) {
RK_Debug::RK_Debug_Level = DL_WARNING;
QApplication app (argc, argv);
#ifdef WITH_KCRASH
KCrash::setDrKonqiEnabled (true);
#endif
// Don't complain when linking rkward://-pages from Rd pages
KUrlAuthorized::allowUrlAction ("redirect", QUrl("http://"), QUrl ("rkward://"));
// Don't complain when trying to open help pages
KUrlAuthorized::allowUrlAction ("redirect", QUrl("rkward://"), QUrl ("help:"));
KLocalizedString::setApplicationDomain ("rkward");
- KAboutData aboutData ("rkward", i18n ("RKWard"), RKWARD_VERSION, i18n ("Frontend to the R statistics language"), KAboutLicense::GPL, i18n ("(c) 2002, 2004 - 2018"), QString (), "http://rkward.kde.org");
+ KAboutData aboutData ("rkward", i18n ("RKWard"), RKWARD_VERSION, i18n ("Frontend to the R statistics language"), KAboutLicense::GPL, i18n ("(c) 2002, 2004 - 2019"), QString (), "http://rkward.kde.org");
aboutData.addAuthor (i18n ("Thomas Friedrichsmeier"), i18n ("Project leader / main developer"));
aboutData.addAuthor (i18n ("Pierre Ecochard"), i18n ("C++ developer between 2004 and 2007"));
aboutData.addAuthor (i18n ("Prasenjit Kapat"), i18n ("Many plugins, suggestions, plot history feature"));
aboutData.addAuthor (i18n ("Meik Michalke"), i18n ("Many plugins, suggestions, rkwarddev package"));
aboutData.addAuthor (i18n ("Stefan Roediger"), i18n ("Many plugins, suggestions, marketing, translations"));
aboutData.addCredit (i18n ("Contributors in alphabetical order"));
aboutData.addCredit (i18n ("Björn Balazs"), i18n ("Extensive usability feedback"));
aboutData.addCredit (i18n ("Aaron Batty"), i18n ("Whealth of feedback, hardware donations"));
aboutData.addCredit (i18n ("Jan Dittrich"), i18n ("Extensive usability feedback"));
aboutData.addCredit (i18n ("Philippe Grosjean"), i18n ("Several helpful comments and discussions"));
aboutData.addCredit (i18n ("Adrien d'Hardemare"), i18n ("Plugins and patches"));
aboutData.addCredit (i18n ("Yves Jacolin"), i18n ("New website"));
aboutData.addCredit (i18n ("Germán Márquez Mejía"), i18n ("HP filter plugin, spanish translation"), 0);
aboutData.addCredit (i18n ("Marco Martin"), i18n ("A cool icon"));
aboutData.addCredit (i18n ("Daniele Medri"), i18n ("RKWard logo, many suggestions, help on wording"));
aboutData.addCredit (i18n ("David Sibai"), i18n ("Several valuable comments, hints and patches"));
aboutData.addCredit (i18n ("Ilias Soumpasis"), i18n ("Translation, Suggestions, plugins"));
aboutData.addCredit (i18n ("Ralf Tautenhahn"), i18n ("Many comments, useful suggestions, and bug reports"));
aboutData.addCredit (i18n ("Jannis Vajen"), i18n ("German Translation, bug reports"));
aboutData.addCredit (i18n ("Roland Vollgraf"), i18n ("Some patches"));
aboutData.addCredit (i18n ("Roy Qu"), i18n ("patches and helpful comments"));
aboutData.addCredit (i18n ("Many more people on rkward-devel@kde.org"), i18n ("Sorry, if we forgot to list you. Please contact us to get added"));
KAboutData::setApplicationData (aboutData);
QCommandLineParser parser;
parser.addVersionOption ();
parser.addHelpOption ();
parser.addOption (QCommandLineOption ("evaluate", i18n ("After starting (and after loading the specified workspace, if applicable), evaluate the given R code."), "Rcode", QString ()));
parser.addOption (QCommandLineOption ("debug-level", i18n ("Verbosity of debug messages (0-5)"), "level", "2"));
parser.addOption (QCommandLineOption ("debug-flags", i18n ("Mask for components to debug (see debug.h)"), "flags", QString::number (DEBUG_ALL)));
parser.addOption (QCommandLineOption ("debug-output", i18n ("Where to send debug message (file|terminal)"), "where", "file"));
parser.addOption (QCommandLineOption ("backend-debugger", i18n ("Debugger for the backend. (Enclose any debugger arguments in single quotes ('') together with the command. Make sure to re-direct stdout!)"), "command", QString ()));
parser.addOption (QCommandLineOption ("r-executable", i18n ("Use specified R installation, instead of the one configured at compile time (note: rkward R library must be installed to that installation of R)"), "command", QString ()));
parser.addOption (QCommandLineOption ("reuse", i18n ("Reuse a running RKWard instance (if available). If a running instance is reused, only the file arguments will be interpreted, all other options will be ignored.")));
parser.addOption (QCommandLineOption ("autoreuse", i18n ("Behaves like --reuse, if any file arguments are also given, starts a new instance, otherwise. Intended for use in the .desktop file.")));
parser.addOption (QCommandLineOption ("nowarn-external", i18n ("When used in conjunction with rkward://runplugin/-URLs specified on the command line, suppresses the warning about application-external (untrusted) links.")));
parser.addPositionalArgument ("files", i18n ("File or files to open, typically a workspace, or an R script file. When loading several things, you should specify the workspace, first."), "[Files...]");
aboutData.setupCommandLine (&parser);
parser.process (app);
aboutData.processCommandLine (&parser);
// Set up debugging
RK_Debug::RK_Debug_Level = DL_FATAL - QString (parser.value ("debug-level")).toInt ();
RK_Debug::RK_Debug_Flags = QString (parser.value ("debug-flags")).toInt ();
RK_Debug_Terminal = QString (parser.value ("debug-output")) == "terminal";
if (RK_Debug::setupLogFile (QDir::tempPath () + "/rkward.frontend")) {
RK_DEBUG (APP, DL_INFO, "Full debug output is at %s", qPrintable (RK_Debug::debug_file->fileName ()));
} else {
RK_Debug_Terminal = true;
RK_DEBUG (APP, DL_INFO, "Failed to open debug file %s", qPrintable (RK_Debug::debug_file->fileName ()));
}
qInstallMessageHandler (RKDebugMessageOutput);
// handle positional (file) arguments, first
QStringList url_args = parser.positionalArguments ();
if (!url_args.isEmpty ()) {
for (int i = 0; i < url_args.size (); ++i) {
url_args[i] = QUrl::fromUserInput (url_args[i], QDir::currentPath (), QUrl::AssumeLocalFile).toString ();
}
RKGlobals::startup_options["initial_urls"] = url_args;
RKGlobals::startup_options["warn_external"] = !parser.isSet ("nowarn-external");
}
RKGlobals::startup_options["evaluate"] = parser.value ("evaluate");
RKGlobals::startup_options["backend-debugger"] = parser.value ("backend-debugger");
// MacOS may need some path adjustments, first
#ifdef Q_OS_MACOS
QString oldpath = qgetenv ("PATH");
if (!oldpath.contains (INSTALL_PATH)) {
//ensure that PATH is set to include what we deliver with the bundle
qputenv ("PATH", QString ("%1/bin:%1/sbin:%2").arg (INSTALL_PATH).arg (oldpath).toLocal8Bit ());
if (RK_Debug::RK_Debug_Level > 3) qDebug ("Adjusting system path to %s", qPrintable (qgetenv ("PATH")));
}
// ensure that RKWard finds its own packages
qputenv ("R_LIBS", R_LIBS);
if (!qEnvironmentVariableIsSet ("DBUS_LAUNCHD_SESSION_BUS_SOCKET")) {
// try to ensure that DBus is running before trying to connect
QProcess::execute ("launchctl", QStringList () << "load" << "/Library/LaunchAgents/org.freedesktop.dbus-session.plist");
}
#endif
// This is _not_ the same path adjustment as above: Make sure to add the current dir to the path, before launching R and backend.
QStringList syspaths = QString (qgetenv ("PATH")).split (PATH_VAR_SEP);
if (!syspaths.contains (app.applicationDirPath ())) {
syspaths.prepend (RKCommonFunctions::windowsShellScriptSafeCommand (app.applicationDirPath ()));
qputenv ("PATH", syspaths.join (PATH_VAR_SEP).toLocal8Bit ());
}
// Handle --reuse option, by placing a dbus-call to existing RKWard process (if any) and exiting
if (parser.isSet ("reuse") || (parser.isSet ("autoreuse") && !url_args.isEmpty ())) {
if (!QDBusConnection::sessionBus ().isConnected ()) {
RK_DEBUG (DEBUG_ALL, DL_WARNING, "Could not connect to session dbus");
} else {
QDBusInterface iface (RKDBUS_SERVICENAME, "/", "", QDBusConnection::sessionBus ());
if (iface.isValid ()) {
QDBusReply reply = iface.call ("openAnyUrl", url_args, !parser.isSet ("nowarn-external"));
if (!reply.isValid ()) {
RK_DEBUG (DEBUG_ALL, DL_ERROR, "Error while placing dbus call: %s", qPrintable (reply.error ().message ()));
return 1;
}
return 0;
}
}
}
// Look for R:
//- command line parameter
//- Specified in cfg file next to rkward executable
//- compile-time default
QString r_exe = parser.value ("r-executable");
if (!r_exe.isNull ()) {
- if (!QFileInfo (r_exe).isExecutable ()) {
- // TODO, while fixing krazy2 warnings: KMessageBox layout for static messages is quirky in that it has squeezed caption, and does not allow resize -> Submit a patch.
- //KMessageBox::error (0, QString ("The R executable specified on the command line (%1) does not exist or is not executable.").arg (r_exe), "Specified R executable does not exist");
- QMessageBox::critical (0, "Specified R executable does not exist", QString ("The R executable specified on the command line (%1) does not exist or is not executable.").arg (r_exe));
- exit (1);
- }
+ r_exe = resolveRSpecOrFail (r_exe, i18n ("The R executable specified on the command line (%1) does not exist or is not executable.", r_exe));
RK_DEBUG (APP, DL_DEBUG, "Using R specified on command line");
} else {
QDir frontend_path = app.applicationDirPath ();
QFileInfo rkward_ini_file (frontend_path.absoluteFilePath ("rkward.ini"));
if (rkward_ini_file.isReadable ()) {
QSettings rkward_ini (rkward_ini_file.absoluteFilePath (), QSettings::IniFormat);
r_exe = rkward_ini.value ("R executable").toString ();
if (!r_exe.isNull ()) {
- if (QDir::isRelativePath (r_exe)) {
+ if (QDir::isRelativePath (r_exe) && r_exe != QStringLiteral ("auto")) {
r_exe = frontend_path.absoluteFilePath (r_exe);
}
- if (!QFileInfo (r_exe).isExecutable ()) {
- QMessageBox::critical (0, "Specified R executable does not exist", QString ("The R executable specified in the rkward.ini file (%1) does not exist or is not executable.").arg (rkward_ini_file.absoluteFilePath ()));
- exit (1);
- }
+ r_exe = resolveRSpecOrFail (r_exe, i18n ("The R executable (%1) specified in the rkward.ini file (%2) does not exist or is not executable.", r_exe, rkward_ini_file.absoluteFilePath ()));
}
RK_DEBUG (APP, DL_DEBUG, "Using R as configured in config file %s", qPrintable (rkward_ini_file.absoluteFilePath ()));
}
if (r_exe.isNull ()) {
- r_exe = R_EXECUTABLE;
- if (!QFileInfo (r_exe).isExecutable ()) {
- QMessageBox::critical (0, "Specified R executable does not exist", QString ("The R executable specified at compile time (%1) does not exist or is not executable. Probably the installation of R has moved. You can use the command line parameter '--r-executable PATH_TO_R', or supply an rkward.ini file to specify the new location.").arg (r_exe));
- exit (1);
- }
+ r_exe = resolveRSpecOrFail (R_EXECUTABLE, i18n ("The R executable specified at compile time (%1) does not exist or is not executable. Probably the installation of R has moved. You can use the command line parameter '--r-executable auto / PATH_TO_R', or supply an rkward.ini file to specify the new location.", QString (R_EXECUTABLE)));
RK_DEBUG (APP, DL_DEBUG, "Using R as configured at compile time");
}
}
// TODO: Store somewhere else
qputenv ("R_BINARY", r_exe.toLocal8Bit ());
qsrand (QTime::currentTime ().msec ()); // Workaround for some versions of kcoreaddons (5.21.0 through at least 5.34.0). See https://phabricator.kde.org/D5966
if (app.isSessionRestored ()) {
RESTORE(RKWardMainWindow); // well, whatever this is supposed to do -> TODO
} else {
new RKWardMainWindow ();
}
// do it!
int status = app.exec ();
qInstallMessageHandler (0);
RK_Debug::debug_file->close ();
return status;
}