[kde-doc-english] [rkward] /: - Allow to open (any number of) R script files and rkward://-urls from the command line

Thomas Friedrichsmeier thomas.friedrichsmeier at ruhr-uni-bochum.de
Fri Nov 21 16:16:59 UTC 2014


Git commit f8b58c9eddff53ce5eaaa104685417c9aca24681 by Thomas Friedrichsmeier.
Committed on 21/11/2014 at 16:13.
Pushed by tfry into branch 'master'.

- Allow to open (any number of) R script files and rkward://-urls from the command line
- Add command line option --reuse for reusing an existing instance of RKWard
- This works by connecting to the running instance via dbus

M  +3    -0    ChangeLog
M  +10   -1    doc/rkward/man-rkward.1.docbook
M  +2    -2    rkward/CMakeLists.txt
M  +9    -4    rkward/agents/rkloadagent.cpp
M  +1    -0    rkward/agents/rkloadagent.h
M  +2    -1    rkward/agents/rksaveagent.cpp
M  +7    -2    rkward/main.cpp
M  +1    -0    rkward/misc/CMakeLists.txt
A  +64   -0    rkward/misc/rkdbusapi.cpp     [License: GPL (v2+)]
C  +14   -22   rkward/misc/rkdbusapi.h [from: rkward/agents/rkloadagent.h - 053% similarity]
M  +2    -1    rkward/rbackend/rinterface.cpp
M  +48   -55   rkward/rkward.cpp
M  +19   -13   rkward/rkward.h
M  +32   -0    rkward/rkward_startup_wrapper.cpp
M  +27   -19   rkward/windows/rkhtmlwindow.cpp
M  +6    -2    rkward/windows/rkhtmlwindow.h
M  +8    -4    rkward/windows/rkworkplace.cpp
M  +1    -1    rkward/windows/rkworkplace.h

http://commits.kde.org/rkward/f8b58c9eddff53ce5eaaa104685417c9aca24681

diff --git a/ChangeLog b/ChangeLog
index ac008aa..b039618 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,6 @@
+- Allow to open (any number of) R script files and rkward://-urls from the command line
+- Add command line option --reuse for reusing an existing instance of RKWard
+- Be slightly(!) smarter about when to ask for saving workspace on workspace load (e.g. not directly after workspace has been saved)
 - Change default in Workspace browser to showing only .GlobalEnv, initially
 - Support automatically generating a printable header parameter from most plugin elements
 - New Object based convience method for printing headers from plugins
diff --git a/doc/rkward/man-rkward.1.docbook b/doc/rkward/man-rkward.1.docbook
index 0576e5c..bff57c2 100644
--- a/doc/rkward/man-rkward.1.docbook
+++ b/doc/rkward/man-rkward.1.docbook
@@ -28,15 +28,16 @@
 <refsynopsisdiv>
 <cmdsynopsis>
 <command>rkward</command>
-<arg choice="opt"><replaceable>workspace_file</replaceable></arg>
 <group choice="opt"><option>--evaluate</option> <replaceable> Rcode</replaceable></group>
 <group choice="opt"><option>--debug-level</option> <replaceable> level</replaceable></group>
 <group choice="opt"><option>--debug-flags</option> <replaceable> flags</replaceable></group>
 <arg><option>--debugger</option> <replaceable>debugger_command</replaceable><arg choice="opt">debugger_args<arg choice="opt"><option>--</option></arg></arg></arg>
 <group choice="opt"><option>--backend-debugger</option> <replaceable> debugger_command</replaceable></group>
 <group choice="opt"><option>--r-executable</option> <replaceable> path_to_executable</replaceable></group>
+<group choice="opt"><option>--reuse</option></group>
 <arg choice="opt">KDE Generic Options</arg>
 <arg choice="opt">Qt Generic Options</arg>
+<arg choice="opt"><replaceable>files_to_open</replaceable></arg>
 </cmdsynopsis>
 </refsynopsisdiv>
 
@@ -76,6 +77,14 @@ Under Windows, the debugger command will <emphasis>not</emphasis> be connected t
 <term><option>--r-executable</option> <replaceable>command</replaceable></term>
 <listitem><para>In the case of several R installations, specify the installation to use, e.g. /usr/bin/R. Note that the rkward R library must have been installed to this installation of R, or startup will fail.</para></listitem>
 </varlistentry>
+<varlistentry>
+<term><option>--reuse</option></term>
+<listitem><para>If an instance of RKWard is already running, bring that to the front, and open <replaceable>files_to_open</replaceable>. Note that all other command line options will be ignored in case an instance is reused.</para></listitem>
+</varlistentry>
+<varlistentry>
+<term><replaceable>files_to_open</replaceable></term>
+<listitem><para>You can specify any number of file names or urls for RKWard to open. Usually this will be either workspace files, workplace files, R script files, or rkward://-urls (e.g. for starting with a plugin dialog). Specifying more than one workspace file will lead to the workspaces being merge together, and is not recommended.</para></listitem>
+</varlistentry>
 </variablelist>
 
 </refsect1>
diff --git a/rkward/CMakeLists.txt b/rkward/CMakeLists.txt
index d611186..b1a0212 100644
--- a/rkward/CMakeLists.txt
+++ b/rkward/CMakeLists.txt
@@ -63,7 +63,7 @@ IF(Q_WS_MAC)
 		@ONLY)
 ENDIF(Q_WS_MAC)
 
-TARGET_LINK_LIBRARIES(rkward.frontend ${KDE4_KDECORE_LIBS} windows ${RKWARD_ADDLIBS} agents dialogs plugin settings dataeditor core scriptbackends rbackend misc ${KDE4_KTEXTEDITOR_LIBS} ${KDE4_KHTML_LIBS} ${KDE4_KFILE_LIBS} ${KDE4_KDEUI_LIBS} ${QT_QTSCRIPT_LIBRARY} ${QT_QTNETWORK_LIBRARY})
+TARGET_LINK_LIBRARIES(rkward.frontend ${KDE4_KDECORE_LIBS} windows ${RKWARD_ADDLIBS} agents dialogs plugin settings dataeditor core scriptbackends rbackend misc ${KDE4_KTEXTEDITOR_LIBS} ${KDE4_KHTML_LIBS} ${KDE4_KFILE_LIBS} ${KDE4_KDEUI_LIBS} ${QT_QTDBUS_LIBRARY} ${QT_QTSCRIPT_LIBRARY} ${QT_QTNETWORK_LIBRARY})
 
 # wrapper executable
 GET_DIRECTORY_PROPERTY(R_EXECUTABLE DIRECTORY rbackend DEFINITION R_EXECUTABLE)
@@ -80,7 +80,7 @@ add_definitions ("-DR_EXECUTABLE=\\\"${R_EXECUTABLE}\\\"")
 add_definitions ("-DINSTALL_PATH=\\\"${CMAKE_INSTALL_PREFIX}\\\"")
 add_definitions ("-DR_LIBS=\\\"${R_LIBDIR}\\\"")
 add_definitions ("-DRKWARD_FRONTEND_LOCATION=\\\"${RKWARD_FRONTEND_LOCATION}\\\"")
-TARGET_LINK_LIBRARIES(rkward ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY})
+TARGET_LINK_LIBRARIES(rkward ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY})
 
 ADD_DEPENDENCIES(rkward.frontend rkward)
 
diff --git a/rkward/agents/rkloadagent.cpp b/rkward/agents/rkloadagent.cpp
index 66f377a..e0eb5c0 100644
--- a/rkward/agents/rkloadagent.cpp
+++ b/rkward/agents/rkloadagent.cpp
@@ -2,7 +2,7 @@
                           rkloadagent  -  description
                              -------------------
     begin                : Sun Sep 5 2004
-    copyright            : (C) 2004, 2007, 2009, 2011, 2012 by Thomas Friedrichsmeier
+    copyright            : (C) 2004, 2007, 2009, 2011, 2012, 2014 by Thomas Friedrichsmeier
     email                : tfry at users.sourceforge.net
  ***************************************************************************/
 
@@ -33,11 +33,13 @@
 #include "../debug.h"
 
 #define WORKSPACE_LOAD_COMMAND 1
+#define WORKSPACE_LOAD_COMPLETE_COMMAND 2
 
 RKLoadAgent::RKLoadAgent (const KUrl &url, bool merge) {
 	RK_TRACE (APP);
 	RKWardMainWindow::getMain ()->slotSetStatusBarText (i18n ("Loading Workspace..."));
 
+	_merge = merge;
 	QString filename;
 	if (!url.isLocalFile ()) {
 		KIO::NetAccess::download (url, tmpfile, RKWardMainWindow::getMain ());
@@ -55,7 +57,7 @@ RKLoadAgent::RKLoadAgent (const KUrl &url, bool merge) {
 		RKGlobals::rInterface ()->issueCommand (command);
 	}
 
-	command = new RCommand ("load (\"" + filename + "\")", RCommand::App | RCommand::ObjectListUpdate, QString::null, this, WORKSPACE_LOAD_COMMAND);
+	command = new RCommand ("load (\"" + filename + "\")", RCommand::App | RCommand::ObjectListUpdate, QString (), this, WORKSPACE_LOAD_COMMAND);
 	RKGlobals::rInterface ()->issueCommand (command);
 
 	RKWorkplace::mainWorkplace ()->setWorkspaceURL (url);
@@ -74,15 +76,18 @@ void RKLoadAgent::rCommandDone (RCommand *command) {
 			KMessageBox::error (0, i18n ("There has been an error opening file '%1':\n%2", RKWorkplace::mainWorkplace ()->workspaceURL ().path (), command->error ()), i18n ("Error loading workspace"));
 			RKWorkplace::mainWorkplace ()->setWorkspaceURL (KUrl());
 		} else {
-			RKWorkplace::mainWorkplace ()->restoreWorkplace ();
+			RKWorkplace::mainWorkplace ()->restoreWorkplace (0, _merge);
 			if (RKSettingsModuleGeneral::cdToWorkspaceOnLoad ()) {
 				if (RKWorkplace::mainWorkplace ()->workspaceURL ().isLocalFile ()) {
 					RKGlobals::rInterface ()->issueCommand ("setwd (" + RObject::rQuote (RKWorkplace::mainWorkplace ()->workspaceURL ().directory ()) + ")", RCommand::App);
 				}
 			}
+			RKGlobals::rInterface ()->issueCommand (QString (), RCommand::EmptyCommand | RCommand::App, QString (), this, WORKSPACE_LOAD_COMPLETE_COMMAND);
 		}
+		RKWardMainWindow::getMain ()->setCaption (QString ());	// trigger update of caption
+	} else if (command->getFlags () == WORKSPACE_LOAD_COMPLETE_COMMAND) {
 		RKWardMainWindow::getMain ()->slotSetStatusReady ();
-		RKWardMainWindow::getMain ()->setCaption (QString::null);	// trigger update of caption
+		RKWardMainWindow::getMain ()->setWorkspaceMightBeModified (false);
 
 		delete this;
 		return;
diff --git a/rkward/agents/rkloadagent.h b/rkward/agents/rkloadagent.h
index a13f7e1..cb7c236 100644
--- a/rkward/agents/rkloadagent.h
+++ b/rkward/agents/rkloadagent.h
@@ -38,6 +38,7 @@ protected:
 private:
 /// needed if file to be loaded is remote
 	QString tmpfile;
+	bool _merge;
 };
 
 #endif
diff --git a/rkward/agents/rksaveagent.cpp b/rkward/agents/rksaveagent.cpp
index 6a40894..7d5b402 100644
--- a/rkward/agents/rksaveagent.cpp
+++ b/rkward/agents/rksaveagent.cpp
@@ -99,11 +99,12 @@ void RKSaveAgent::rCommandDone (RCommand *command) {
 
 void RKSaveAgent::done () {
 	RK_TRACE (APP);
+	RKWardMainWindow::getMain ()->setWorkspaceMightBeModified (false);
 	if (save_chain) {
 		RKGlobals::rInterface ()->closeChain (save_chain);
 	}
 	if (when_done == Load) {
-		RKWardMainWindow::getMain ()->fileOpenNoSave (load_url);
+		RKWardMainWindow::getMain ()->askOpenWorkspace (load_url);
 	}
 	deleteLater ();
 }
diff --git a/rkward/main.cpp b/rkward/main.cpp
index 6d462e4..d02fa70 100644
--- a/rkward/main.cpp
+++ b/rkward/main.cpp
@@ -126,7 +126,8 @@ int main(int argc, char *argv[]) {
 	options.add ("debugger <command and arguments>", ki18n ("Debugger for the frontend. Specify last, or add '--' after all debugger arguments"), "");
 	options.add ("backend-debugger <command>", ki18n ("Debugger for the backend. (Enclose any debugger arguments in single quotes ('') together with the command. Make sure to re-direct stdout!)"), "");
 	options.add ("r-executable <command>", ki18n ("Use specified R installation, instead of the one configured at compile time (note: rkward R library must be installed to that installation of R)"), "");
-	options.add ("+[File]", ki18n ("R workspace file to open"), 0);
+	options.add ("reuse", ki18n ("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."), 0);
+	options.add ("+[Files]", ki18n ("File or files to open, typically a workspace, or an R script file. When loading several things, you should specify the workspace, first."), 0);
 
 	KAboutData aboutData("rkward", QByteArray (), ki18n ("RKWard"), RKWARD_VERSION, ki18n ("Frontend to the R statistics language"), KAboutData::License_GPL, ki18n ("(c) 2002, 2004 - 2014"), KLocalizedString (), "http://rkward.sf.net", "rkward-devel at lists.sourceforge.net");
 	aboutData.addAuthor (ki18n ("Thomas Friedrichsmeier"), ki18n ("Project leader / main developer"));
@@ -168,7 +169,11 @@ int main(int argc, char *argv[]) {
 	}
 
 	if (args->count ()) {
-		RKGlobals::startup_options["initial_url"] = QUrl (KCmdLineArgs::makeURL (decodeArgument (args->arg (0)).toUtf8 ()));
+		QVariantList urls_to_open;
+		for (int i = 0; i < args->count (); ++i) {
+			urls_to_open.append (QUrl (KCmdLineArgs::makeURL (decodeArgument (args->arg (i)).toUtf8 ())));
+		}
+		RKGlobals::startup_options["initial_urls"] = urls_to_open;
 	}
 	RKGlobals::startup_options["evaluate"] = decodeArgument (args->getOption ("evaluate"));
 	RKGlobals::startup_options["backend-debugger"] = decodeArgument (args->getOption ("backend-debugger"));
diff --git a/rkward/misc/CMakeLists.txt b/rkward/misc/CMakeLists.txt
index 934f4cb..1ae4afc 100644
--- a/rkward/misc/CMakeLists.txt
+++ b/rkward/misc/CMakeLists.txt
@@ -24,6 +24,7 @@ SET(misc_STAT_SRCS
    editlabelsdialog.cpp
    editformatdialog.cpp
    rkmessagecatalog.cpp
+   rkdbusapi.cpp
    )
 
 QT4_AUTOMOC(${misc_STAT_SRCS})
diff --git a/rkward/misc/rkdbusapi.cpp b/rkward/misc/rkdbusapi.cpp
new file mode 100644
index 0000000..3ab397e
--- /dev/null
+++ b/rkward/misc/rkdbusapi.cpp
@@ -0,0 +1,64 @@
+/***************************************************************************
+                          rkdbusapi  -  description
+                             -------------------
+    begin                : Thu Nov 20 2014
+    copyright            : (C) 2014 by Thomas Friedrichsmeier
+    email                : tfry at users.sourceforge.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 "rkdbusapi.h"
+
+#include <QtDBus>
+#include <kwindowsystem.h>
+#include "../windows/rkworkplace.h"
+#include "../rkward.h"
+
+#include "../debug.h"
+
+RKDBusAPI::RKDBusAPI (QObject* parent): QObject (parent) {
+	RK_TRACE (APP);
+
+	if (!QDBusConnection::sessionBus ().isConnected ()) {
+		RK_DEBUG (DL_ERROR, APP, "D-Bus session bus in not connected");
+		return;
+	}
+
+	if (!QDBusConnection::sessionBus ().registerService (RKDBUS_SERVICENAME)) {
+		RK_DEBUG (DL_ERROR, APP, "Could not register org.kde.rkward on session bus");
+		return;
+	}
+
+	QDBusConnection::sessionBus ().registerObject ("/", this, QDBusConnection::ExportScriptableSlots);
+}
+
+void RKDBusAPI::openAnyUrl(const QStringList& urls) {
+	RK_TRACE (APP);
+
+	RKWardMainWindow::getMain ()->setMergeLoads (true);
+	for (int i = 0; i < urls.size (); ++i) {
+		RKWorkplace::mainWorkplace ()->openAnyUrl (urls[i]);
+	}
+	RKWardMainWindow::getMain ()->setMergeLoads (false);
+
+	// ok, raising the app window is totally hard to do, reliably. This solution copied from kate.
+	QWidget *main = RKWardMainWindow::getMain ();
+	main->show();
+	main->activateWindow();
+	main->raise();
+#ifdef Q_WS_X11
+	KWindowSystem::forceActiveWindow (main->winId ());
+	KWindowSystem::raiseWindow (main->winId ());
+	KWindowSystem::demandAttention (main->winId ());
+#endif
+}
+
+#include "rkdbusapi.moc"
diff --git a/rkward/agents/rkloadagent.h b/rkward/misc/rkdbusapi.h
similarity index 53%
copy from rkward/agents/rkloadagent.h
copy to rkward/misc/rkdbusapi.h
index a13f7e1..1fef208 100644
--- a/rkward/agents/rkloadagent.h
+++ b/rkward/misc/rkdbusapi.h
@@ -1,8 +1,8 @@
 /***************************************************************************
-                          rkloadagent  -  description
+                          rkdbusapi  -  description
                              -------------------
-    begin                : Sun Sep 5 2004
-    copyright            : (C) 2004 by Thomas Friedrichsmeier
+    begin                : Thu Nov 20 2014
+    copyright            : (C) 2014 by Thomas Friedrichsmeier
     email                : tfry at users.sourceforge.net
  ***************************************************************************/
 
@@ -14,30 +14,22 @@
  *   (at your option) any later version.                                   *
  *                                                                         *
  ***************************************************************************/
-#ifndef RKLOADAGENT_H
-#define RKLOADAGENT_H
 
-#include <qobject.h>
-#include "../rbackend/rcommandreceiver.h"
+#ifndef RKDBUSAPI_H
+#define RKDBUSAPI_H
 
-#include <qstring.h>
-#include <kurl.h>
+#define RKDBUS_SERVICENAME "org.kde.rkward.api"
 
-/** The RKLoadAgent is really a rather simple agent. All it needs to do is display an error message, if loading fails. No further action is required. Like all
-agents, the RKLoadAgent self-destructs when done.
- at author Thomas Friedrichsmeier
-*/
-class RKLoadAgent : public QObject, public RCommandReceiver {
+#include <QObject>
+
+class RKDBusAPI : public QObject {
 	Q_OBJECT
 public:
-	explicit RKLoadAgent (const KUrl &url, bool merge=false);
-
-	~RKLoadAgent ();
-protected:
-	void rCommandDone (RCommand *command);
-private:
-/// needed if file to be loaded is remote
-	QString tmpfile;
+/** Creates an object (should be a singleton) to relay incoming DBus calls, and registers it on the session bus. */
+	RKDBusAPI (QObject *parent);
+	~RKDBusAPI () {};
+public slots:
+	Q_SCRIPTABLE void openAnyUrl (const QStringList &urls);
 };
 
 #endif
diff --git a/rkward/rbackend/rinterface.cpp b/rkward/rbackend/rinterface.cpp
index 40678e2..77cb6bf 100644
--- a/rkward/rbackend/rinterface.cpp
+++ b/rkward/rbackend/rinterface.cpp
@@ -2,7 +2,7 @@
                           rinterface.cpp  -  description
                              -------------------
     begin                : Fri Nov 1 2002
-    copyright            : (C) 2002-2013 by Thomas Friedrichsmeier
+    copyright            : (C) 2002-2014 by Thomas Friedrichsmeier
     email                : tfry at users.sourceforge.net
  ***************************************************************************/
 
@@ -265,6 +265,7 @@ void RInterface::doNextCommand (RCommand *command) {
 	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 ());
diff --git a/rkward/rkward.cpp b/rkward/rkward.cpp
index 0056f28..d8e2b39 100644
--- a/rkward/rkward.cpp
+++ b/rkward/rkward.cpp
@@ -16,19 +16,11 @@
  ***************************************************************************/
 
 // include files for QT
-#include <qprinter.h>
-#include <qpainter.h>
-#include <qcheckbox.h>
-#include <qpushbutton.h>
-#include <qlineedit.h>
 #include <qtimer.h>
-#include <QDBusConnection>
 #include <QDesktopWidget>
-#include <QLabel>
 #include <QCloseEvent>
 
 // include files for KDE
-#include <kiconloader.h>
 #include <kmessagebox.h>
 #include <kencodingfiledialog.h>
 #include <kmenubar.h>
@@ -64,6 +56,7 @@
 #include "misc/rkstandardicons.h"
 #include "misc/rkcommonfunctions.h"
 #include "misc/rkxmlguisyncer.h"
+#include "misc/rkdbusapi.h"
 #include "rkglobals.h"
 #include "dialogs/startupdialog.h"
 #include "dialogs/rkloadlibsdialog.h"
@@ -120,6 +113,9 @@ RKWardMainWindow::RKWardMainWindow () : KParts::MainWindow ((QWidget *)0, (Qt::W
 	RK_ASSERT (rkward_mainwin == 0);
 
 	gui_rebuild_locked = true;
+	no_ask_save = true;
+	workspace_modified = false;
+	merge_loads = false;
 	rkward_mainwin = this;
 	RKGlobals::rinter = 0;
 	RKSettings::settings_tracker = new RKSettingsTracker (this);
@@ -201,7 +197,7 @@ void RKWardMainWindow::doPostInit () {
 		KMessageBox::error (this, i18n ("<p>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.</p><p><b>You should quit RKWard, now, and fix your installation</b>. For help with that, see <a href=\"http://p.sf.net/rkward/compiling\">http://p.sf.net/rkward/compiling</a>.</p>"), i18n ("Broken installation"), KMessageBox::Notify | KMessageBox::AllowLink);
 	}
 
-	KUrl open_url = RKGlobals::startup_options.take ("initial_url").toUrl ();
+	QVariantList open_urls = RKGlobals::startup_options.take ("initial_urls").toList ();
 	QString evaluate_code = RKGlobals::startup_options.take ("evaluate").toString ();
 
 	initPlugins ();
@@ -222,41 +218,57 @@ void RKWardMainWindow::doPostInit () {
 #endif
 
 	KUrl recover_url = RKRecoverDialog::checkRecoverCrashedWorkspace ();
-	if (!recover_url.isEmpty ()) open_url = recover_url;
-	if (!open_url.isEmpty () && (open_url.isRelative ())) {
+	if (!recover_url.isEmpty ()) {
+		open_urls.clear ();
+		open_urls.append (recover_url);		// Well, not a perfect solution. But we certainly don't want to overwrite the just recovered workspace.
+	}
+
+	setMergeLoads (true);
+	for (int i = 0; i < open_urls.size (); ++i) {
 		// make sure local urls are absolute, as we may be changing wd before loading
-		open_url = KUrl::fromLocalFile (QDir::current ().absoluteFilePath (open_url.toLocalFile ()));
+		KUrl url = open_urls[i].toUrl ();
+		if (url.isRelative ()) {
+			open_urls[i] = KUrl::fromLocalFile (QDir::current ().absoluteFilePath (url.toLocalFile ()));
+		}
 	}
+	setMergeLoads (false);
 
 	QString cd_to = RKSettingsModuleGeneral::initialWorkingDirectory ();
 	if (!cd_to.isEmpty ()) {
 		RKGlobals::rInterface ()->issueCommand ("setwd (" + RObject::rQuote (cd_to) + ")\n", RCommand::App);
 	}
 
-	if (!open_url.isEmpty()) {
-		openWorkspace (open_url);
+	if (!open_urls.isEmpty()) {
+		// this is also done when there are no urls specified on the command line. But in that case _after_ loading any workspace, so
+		// the help window will be on top
+		if (RKSettingsModuleGeneral::showHelpOnStartup ()) toplevel_actions->showRKWardHelp ();
+
+		for (int i = 0; i < open_urls.size (); ++i) {
+			RKWorkplace::mainWorkplace ()->openAnyUrl (open_urls[i].toUrl ());
+		}
 	} else {
 		StartupDialog::StartupDialogResult result = StartupDialog::getStartupAction (this, fileOpenRecentWorkspace);
 		if (!result.open_url.isEmpty ()) {
 			openWorkspace (result.open_url);
 		} else {
 			if (result.result == StartupDialog::ChoseFile) {
-				fileOpenNoSave (KUrl());
+				askOpenWorkspace (KUrl());
 			} else if (result.result == StartupDialog::EmptyTable) {
 				RKWorkplace::mainWorkplace ()->editNewDataFrame (i18n ("my.data"));
 			}
 		}
-	}
-
-	if (RKSettingsModuleGeneral::workplaceSaveMode () == RKSettingsModuleGeneral::SaveWorkplaceWithSession) {
-		RKWorkplace::mainWorkplace ()->restoreWorkplace (RKSettingsModuleGeneral::getSavedWorkplace (KGlobal::config ().data ()).split ('\n'));
-	}
 
-	if (RKSettingsModuleGeneral::showHelpOnStartup ()) {
-		toplevel_actions->showRKWardHelp ();
+		if (RKSettingsModuleGeneral::workplaceSaveMode () == RKSettingsModuleGeneral::SaveWorkplaceWithSession) {
+			RKWorkplace::mainWorkplace ()->restoreWorkplace (RKSettingsModuleGeneral::getSavedWorkplace (KGlobal::config ().data ()).split ('\n'));
+		}
+		if (RKSettingsModuleGeneral::showHelpOnStartup ()) toplevel_actions->showRKWardHelp ();
 	}
+	setNoAskSave (false);
 
 	if (!evaluate_code.isEmpty ()) RKConsole::pipeUserCommand (evaluate_code);
+	RKDBusAPI *dbus = new RKDBusAPI (this);
+	connect (this, SIGNAL(aboutToQuitRKWard()), dbus, SLOT(deleteLater()));	// RKWard sometimes needs to wait for R to quit. We don't want it sticking
+	// around on the bus in this case.
 
 	setCaption (QString ());	// our version of setCaption takes care of creating a correct caption, so we do not need to provide it here
 }
@@ -431,7 +443,7 @@ void RKWardMainWindow::initActions() {
 	fileOpenWorkspace->setShortcut (Qt::ControlModifier + Qt::ShiftModifier + Qt::Key_O);
 	fileOpenWorkspace->setStatusTip (i18n ("Opens an existing document"));
 
-	fileOpenRecentWorkspace = static_cast<KRecentFilesAction*> (actionCollection ()->addAction (KStandardAction::OpenRecent, "file_open_recentx", this, SLOT(slotFileOpenRecentWorkspace(KUrl))));
+	fileOpenRecentWorkspace = static_cast<KRecentFilesAction*> (actionCollection ()->addAction (KStandardAction::OpenRecent, "file_open_recentx", this, SLOT(askOpenWorkspace(KUrl))));
 	fileOpenRecentWorkspace->setText (i18n ("Open Recent Workspace"));
 	fileOpenRecentWorkspace->setStatusTip (i18n ("Opens a recently used file"));
 
@@ -634,7 +646,7 @@ void RKWardMainWindow::openWorkspace (const KUrl &url) {
 	RK_TRACE (APP);
 	if (url.isEmpty ()) return;
 
-	new RKLoadAgent (url, false);
+	new RKLoadAgent (url, merge_loads);
 }
 
 void RKWardMainWindow::saveOptions () {
@@ -733,9 +745,19 @@ void RKWardMainWindow::slotNewDataFrame () {
 	if (ok) RKWorkplace::mainWorkplace ()->editNewDataFrame (name);
 }
 
-void RKWardMainWindow::fileOpenNoSave (const KUrl &url) {
+void RKWardMainWindow::askOpenWorkspace (const KUrl &url) {
 	RK_TRACE (APP);
 
+	if (!no_ask_save && !RObjectList::getGlobalEnv ()->isEmpty () && workspace_modified) {
+		int res;
+		res = KMessageBox::questionYesNoCancel (this, i18n ("Do you want to save the current workspace?"), i18n ("Save Workspace?"));
+		if (res == KMessageBox::Yes) {
+			new RKSaveAgent (RKWorkplace::mainWorkplace ()->workspaceURL (), false, RKSaveAgent::Load, url);
+		} else if (res != KMessageBox::No) { // Cancel
+			return;
+		}
+	}
+
 	slotCloseAllEditors ();
 
 	slotSetStatusBarText(i18n("Opening workspace..."));
@@ -755,32 +777,9 @@ void RKWardMainWindow::fileOpenNoSave (const KUrl &url) {
 	slotSetStatusReady ();
 }
 
-void RKWardMainWindow::fileOpenAskSave (const KUrl &url) {
-	RK_TRACE (APP);
-	if (RObjectList::getObjectList ()->isEmpty ()) {
-		fileOpenNoSave (url);
-		return;
-	}
-	
-	int res;
-	res = KMessageBox::questionYesNoCancel (this, i18n ("Do you want to save the current workspace?"), i18n ("Save Workspace?"));
-	if (res == KMessageBox::No) {
-		fileOpenNoSave (url);
-	} else if (res == KMessageBox::Yes) {
-		new RKSaveAgent (RKWorkplace::mainWorkplace ()->workspaceURL (), false, RKSaveAgent::Load, url);
-	}
-	// else: cancel. Don't do anything
-}
-
 void RKWardMainWindow::slotFileOpenWorkspace () {
 	RK_TRACE (APP);
-	fileOpenAskSave (KUrl ());
-}
-
-void RKWardMainWindow::slotFileOpenRecentWorkspace(const KUrl& url)
-{
-	RK_TRACE (APP);
-	fileOpenAskSave (url);
+	askOpenWorkspace (KUrl ());
 }
 
 void RKWardMainWindow::slotFileLoadLibs () {
@@ -912,12 +911,6 @@ void RKWardMainWindow::slotOpenCommandEditor () {
 	}
 };
 
-void RKWardMainWindow::openHTML (const KUrl &url) {
-	RK_TRACE (APP);
-
-	RKWorkplace::mainWorkplace ()->openHelpWindow (url);
-}
-
 void RKWardMainWindow::setCaption (const QString &) {
 	RK_TRACE (APP);
 
diff --git a/rkward/rkward.h b/rkward/rkward.h
index 3986633..dbcb69a 100644
--- a/rkward/rkward.h
+++ b/rkward/rkward.h
@@ -2,7 +2,7 @@
                           rkward.h  -  description
                              -------------------
     begin                : Tue Oct 29 20:06:08 CET 2002
-    copyright            : (C) 2002-2013 by Thomas Friedrichsmeier 
+    copyright            : (C) 2002-2014 by Thomas Friedrichsmeier 
     email                : tfry at users.sourceforge.net
  ***************************************************************************/
 
@@ -37,8 +37,7 @@ The main class of rkward. This is where all strings are tied togther, controlls
 class RKWardMainWindow : public KParts::MainWindow {
 	Q_OBJECT
 public:
-/** construtor
- at param options Options from command line. RKWardMainWindow will take ownership of this pointer, and delete it, once not longer needed. */
+/** construtor */
 	RKWardMainWindow ();
 /** destructor */
 	~RKWardMainWindow ();
@@ -46,13 +45,6 @@ public:
 /** initialize the backend */
 	void startR ();
 
-/** open a workspace. Do not ask whether to save the old one. The old workspace is deleted! */
-	void fileOpenNoSave (const KUrl &url);
-/** open a workspace. If the current workspace is not empty, ask wether to save first. */
-	void fileOpenAskSave (const KUrl &url);
-/** opens the given url, assuming it is an HTML-help page. */
-	void openHTML (const KUrl &url);
-
 	KParts::PartManager *partManager () { return part_manager; };
 
 	static RKWardMainWindow *getMain () { return rkward_mainwin; };
@@ -60,8 +52,14 @@ public:
 /** (try to) close all windows, and ask whether it is ok to quit */
 	bool doQueryQuit ();
 	void lockGUIRebuild (bool lock);
+/** Set whether not to ask for saving, although the workspace @em might be modified */
+	void setNoAskSave (bool no_ask) { no_ask_save = no_ask; };
+/** Set whether workspace is known to be unmodified, or could be modified.
+    TODO: Some less guessing would be nice... */
+	void setWorkspaceMightBeModified (bool modified) { workspace_modified = modified; };
+/** Merge files to be loaded, instead of closing windows / clearing workspace */
+	void setMergeLoads (bool merge) { merge_loads = merge; };
 protected:
-	void openWorkspace (const KUrl &url);
 	/** save Options/Settings. Includes general Options like all bar positions and status as well as the geometry and the recent file list */
 	void saveOptions();
 /** read general Options again and initialize all variables like the recent file list */
@@ -78,12 +76,14 @@ protected:
 signals:
 	void aboutToQuitRKWard ();
 public slots:
+	/** open a workspace. If the current workspace is not empty, ask wether to save first.
+    @see setNoAskSave ()
+    @see setWorkspaceMightBeModified () */
+	void askOpenWorkspace (const KUrl &url);
 	/** creates a new (empty) data.frame */
 	void slotNewDataFrame ();
 	/** open a file and load it into the document*/
 	void slotFileOpenWorkspace();
-	/** opens a file from the recent files menu */
-	void slotFileOpenRecentWorkspace(const KUrl& url);
 	/** save a document */
 	void slotFileSaveWorkspace();
 	/** save a document by a new filename*/
@@ -135,6 +135,9 @@ public slots:
 private slots:
 	void partChanged (KParts::Part *new_part);
 private:
+/** Opens a new workspace, without asking or closing anything. */
+	void openWorkspace (const KUrl &url);
+
 	QLabel* statusbar_r_status;
 	KSqueezedTextLabel* statusbar_cwd;
 	QLabel* statusbar_ready;
@@ -191,6 +194,9 @@ private:
 
 	RKTopLevelWindowGUI *toplevel_actions;
 	bool gui_rebuild_locked;
+	bool no_ask_save;
+	bool workspace_modified;
+	bool merge_loads;
 };
 
 #endif // RKWARD_H
diff --git a/rkward/rkward_startup_wrapper.cpp b/rkward/rkward_startup_wrapper.cpp
index c83b083..d667bb8 100644
--- a/rkward/rkward_startup_wrapper.cpp
+++ b/rkward/rkward_startup_wrapper.cpp
@@ -28,6 +28,8 @@
 #include <QSettings>
 #include <QUrl>
 #include <QFile>
+#include <QtDBus>
+#include "misc/rkdbusapi.h"
 
 #ifndef RKWARD_FRONTEND_LOCATION
 #	define RKWARD_FRONTEND_LOCATION ""
@@ -101,6 +103,8 @@ int main (int argc, char *argv[]) {
 	// Parse arguments that need handling in the wrapper
 	bool usage = false;
 	QStringList debugger_args;
+	QStringList file_args;
+	bool reuse = false;
 	QString r_exe_arg;
 	int debug_level = 2;
 
@@ -123,6 +127,34 @@ int main (int argc, char *argv[]) {
 			if ((i+1) < args.size ()) {
 				debug_level = args[i+1].toInt ();
 			}
+		} else if (args[i] == "--reuse") {
+			reuse = true;
+		} else if (args[i].startsWith ("--")) {
+			// all RKWard and KDE options (other than --reuse) are of the for --option <value>. So skip over the <value>
+			i++;
+		} else {
+			QUrl url (args[i]);
+			if (url.isRelative ()) {
+				file_args.append (QDir::current ().absoluteFilePath (url.toLocalFile ()));
+			} else {
+				file_args.append (args[i]);
+			}
+		}
+	}
+
+	if (reuse) {
+		if (!QDBusConnection::sessionBus ().isConnected ()) {
+			if (debug_level > 2) qDebug ("Could not connect to session dbus");
+		} else {
+			QDBusInterface iface (RKDBUS_SERVICENAME, "/", "", QDBusConnection::sessionBus ());
+			if (iface.isValid ()) {
+				QDBusReply<void> reply = iface.call ("openAnyUrl", file_args);
+				if (!reply.isValid ()) {
+					if (debug_level > 2) qDebug ("Error while placing dbus call: %s", qPrintable (reply.error ().message ()));
+					return 1;
+				}
+				return 0;
+			}
 		}
 	}
 
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index 6b24348..3e80d4a 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -243,7 +243,30 @@ void RKHTMLWindow::slotBack () {
 	openLocationFromHistory (url_history[current_history_position]);
 }
 
-bool RKHTMLWindow::handleRKWardURL (const KUrl &url) {
+void RKHTMLWindow::openRKHPage (const KUrl& url) {
+	RK_TRACE (APP);
+
+	RK_ASSERT (url.protocol () == "rkward");
+	changeURL (url);
+	bool ok = false;
+	if (url.host () == "component") {
+		ok = renderRKHelp (url);
+	} else if (url.host () == "page") {
+		ok = renderRKHelp (url);
+	} else if (url.host ().toUpper () == "RHELPBASE") {	// NOTE: QUrl () may lowercase the host part, internally
+		KUrl fixed_url = KUrl (RKSettingsModuleR::helpBaseUrl ());
+		fixed_url.setPath (url.path ());
+		if (url.hasQuery ()) fixed_url.setQuery (url.query ());
+		if (url.hasFragment ()) fixed_url.setFragment (url.fragment ());
+		ok = openURL (fixed_url);
+	}
+	if (!ok) {
+		fileDoesNotExistMessage ();
+	}
+}
+
+// static
+bool RKHTMLWindow::handleRKWardURL (const KUrl &url, RKHTMLWindow *window) {
 	RK_TRACE (APP);
 
 	if (url.protocol () == "rkward") {
@@ -260,23 +283,8 @@ bool RKHTMLWindow::handleRKWardURL (const KUrl &url) {
 				return true;
 			}
 
-			changeURL (url);
-			bool ok = false;
-			if (url.host () == "component") {
-				ok = renderRKHelp (url);
-			} else if (url.host () == "page") {
-				ok = renderRKHelp (url);
-			} else if (url.host ().toUpper () == "RHELPBASE") {	// NOTE: QUrl () may lowercase the host part, internally
-				KUrl fixed_url = KUrl (RKSettingsModuleR::helpBaseUrl ());
-				fixed_url.setPath (url.path ());
-				if (url.hasQuery ()) fixed_url.setQuery (url.query ());
-				if (url.hasFragment ()) fixed_url.setFragment (url.fragment ());
-				ok = openURL (fixed_url);
-			}
-
-			if (!ok) {
-				fileDoesNotExistMessage ();
-			}
+			if (window) window->openRKHPage (url);
+			else RKWorkplace::mainWorkplace ()->openHelpWindow (url);	// will recurse with window set, via openURL()
 			return true;
 		}
 	}
@@ -286,7 +294,7 @@ bool RKHTMLWindow::handleRKWardURL (const KUrl &url) {
 bool RKHTMLWindow::openURL (const KUrl &url) {
 	RK_TRACE (APP);
 
-	if (handleRKWardURL (url)) return true;
+	if (handleRKWardURL (url, this)) return true;
 
 	if (window_mode == HTMLOutputWindow) {
 		if (url != current_url) {
diff --git a/rkward/windows/rkhtmlwindow.h b/rkward/windows/rkhtmlwindow.h
index 04fe626..62ed8a7 100644
--- a/rkward/windows/rkhtmlwindow.h
+++ b/rkward/windows/rkhtmlwindow.h
@@ -59,8 +59,12 @@ public:
 	~RKHTMLWindow ();
 /** open given URL. Returns false, if the URL is not an existing local file. Loading a non-local URL may succeed, even if this returns false! */
 	bool openURL (const KUrl &url);
-/** takes care of special handling, if the url is an rkward://-url. Does nothing and returns false, otherwise. */
-	bool handleRKWardURL (const KUrl &url);
+/** takes care of special handling, if the url is an rkward://-url. Does nothing and returns false, otherwise.
+ *  If window is not 0, and the url is a help window, open it, there (otherwise in a new window).
+ *  TODO: move to RKWorkplace? As this can really open a bunch of different things, although generally _from_ an html window.
+ */
+	static bool handleRKWardURL (const KUrl &url, RKHTMLWindow *window=0);
+	void openRKHPage (const KUrl &url);
 /** initialize all actions */
 	void initActions ();
 
diff --git a/rkward/windows/rkworkplace.cpp b/rkward/windows/rkworkplace.cpp
index e1c9e27..b9244f1 100644
--- a/rkward/windows/rkworkplace.cpp
+++ b/rkward/windows/rkworkplace.cpp
@@ -249,7 +249,9 @@ void RKWorkplace::placeInToolWindowBar (RKMDIWindow *window, int position) {
 bool RKWorkplace::openAnyUrl (const KUrl &url, const QString &known_mimetype, bool force_external) {
 	RK_TRACE (APP);
 
-#warning TODO support rkward:\/\/-protocol, here, too
+	if (url.protocol () == "rkward") {
+		if (RKHTMLWindow::handleRKWardURL (url)) return true;
+	}
 	KMimeType::Ptr mimetype;
 	if (!known_mimetype.isEmpty ()) mimetype = KMimeType::mimeType (known_mimetype);
 	else mimetype = KMimeType::findByUrl (url);
@@ -262,7 +264,7 @@ bool RKWorkplace::openAnyUrl (const KUrl &url, const QString &known_mimetype, bo
 			return true;	// TODO
 		}
 		if (url.fileName ().toLower ().endsWith (".rdata")) {
-			RKWardMainWindow::getMain ()->fileOpenAskSave (url);
+			RKWardMainWindow::getMain ()->askOpenWorkspace (url);
 			return true;	// TODO
 		}
 		if (mimetype->name ().startsWith ("text")) {
@@ -618,11 +620,13 @@ void RKWorkplace::saveWorkplace (RCommandChain *chain) {
 	RKGlobals::rInterface ()->issueCommand ("rk.save.workplace(description=" + RObject::rQuote (makeWorkplaceDescription().join ("\n")) + ")", RCommand::App, i18n ("Save Workplace layout"), 0, 0, chain);
 }
 
-void RKWorkplace::restoreWorkplace (RCommandChain *chain) {
+void RKWorkplace::restoreWorkplace (RCommandChain *chain, bool merge) {
 	RK_TRACE (APP);
 	if (RKSettingsModuleGeneral::workplaceSaveMode () != RKSettingsModuleGeneral::SaveWorkplaceWithWorkspace) return;
 
-	RKGlobals::rInterface ()->issueCommand ("rk.restore.workplace()", RCommand::App, i18n ("Restore Workplace layout"), 0, 0, chain);
+	QString no_close_windows;
+	if (merge) no_close_windows = "close.windows = FALSE";
+	RKGlobals::rInterface ()->issueCommand ("rk.restore.workplace(" + no_close_windows + ')', RCommand::App, i18n ("Restore Workplace layout"), 0, 0, chain);
 }
 
 KUrl checkAdjustRestoredUrl (const QString &_url, const QString old_base) {
diff --git a/rkward/windows/rkworkplace.h b/rkward/windows/rkworkplace.h
index 881dfad..0a52522 100644
--- a/rkward/windows/rkworkplace.h
+++ b/rkward/windows/rkworkplace.h
@@ -145,7 +145,7 @@ public:
 /** Load a description of windows from the R backend (created by saveWorkplace ()), and (try to) restore all windows accordingly
 Has no effect, if RKSettingsModuleGeneral::workplaceSaveMode () != RKSettingsModuleGeneral::SaveWorkplaceWithWorkspace
 @param chain command chain to place the command in */
-	void restoreWorkplace (RCommandChain *chain=0);
+	void restoreWorkplace (RCommandChain *chain=0, bool merge=false);
 /** Like the other restoreWorkplace (), but takes the description as a parameter rather than reading from the R workspace. To be used, when RKSettingsModuleGeneral::workplaceSaveMode () == RKSettingsModuleGeneral::SaveWorkplaceWithSeesion
 @param description workplace description */
 	void restoreWorkplace (const QStringList &description);


More information about the kde-doc-english mailing list