[education/rkward/devel/workspace_output] rkward: Implement workspace-save-on-exit with the new save dialog.
Thomas Friedrichsmeier
null at kde.org
Wed Oct 28 17:57:13 GMT 2020
Git commit 5d237f4b65a38ff66c7021f50a94d4501103eeec by Thomas Friedrichsmeier.
Committed on 28/10/2020 at 17:55.
Pushed by tfry into branch 'devel/workspace_output'.
Implement workspace-save-on-exit with the new save dialog.
This commit also does away with most of the complexity in the previously over-engineered RKSaveAgent.
M +32 -75 rkward/agents/rksaveagent.cpp
M +8 -20 rkward/agents/rksaveagent.h
M +13 -10 rkward/dialogs/rksavemodifieddialog.cpp
M +1 -1 rkward/misc/rkoutputdirectory.cpp
M +13 -31 rkward/rkward.cpp
M +15 -14 rkward/windows/rkworkplace.cpp
M +4 -3 rkward/windows/rkworkplace.h
https://invent.kde.org/education/rkward/commit/5d237f4b65a38ff66c7021f50a94d4501103eeec
diff --git a/rkward/agents/rksaveagent.cpp b/rkward/agents/rksaveagent.cpp
index 526e19ee..b9b8e0e3 100644
--- a/rkward/agents/rksaveagent.cpp
+++ b/rkward/agents/rksaveagent.cpp
@@ -2,7 +2,7 @@
rksaveagent - description
-------------------
begin : Sun Aug 29 2004
- copyright : (C) 2004, 2009, 2010, 2011, 2012 by Thomas Friedrichsmeier
+ copyright : (C) 2004-2020 by Thomas Friedrichsmeier
email : thomas.friedrichsmeier at kdemail.net
***************************************************************************/
@@ -23,6 +23,7 @@
#include <QFileDialog>
#include "../rbackend/rkrinterface.h"
+#include "../misc/rkprogresscontrol.h"
#include "../core/robjectlist.h"
#include "../rkglobals.h"
#include "../rkward.h"
@@ -31,32 +32,6 @@
#include "../debug.h"
-RKSaveAgent::RKSaveAgent (QUrl url, bool save_file_as, DoneAction when_done, QUrl load_url) : QObject () {
- RK_TRACE (APP);
- save_url = url;
- RKSaveAgent::when_done = when_done;
- RKSaveAgent::load_url = load_url;
- previous_url = RKWorkplace::mainWorkplace ()->workspaceURL ();
- save_chain = 0;
- if (save_url.isEmpty () || save_file_as) {
- if (!askURL ()) {
- deleteLater();
- return;
- }
- }
-
- RKWorkplace::mainWorkplace ()->flushAllData ();
- save_chain = RKGlobals::rInterface ()->startChain (0);
-
- RKWorkplace::mainWorkplace ()->setWorkspaceURL (save_url, true);
- RKWorkplace::mainWorkplace ()->saveWorkplace (save_chain);
- RKGlobals::rInterface ()->issueCommand (new RCommand ("save.image (" + RObject::rQuote (save_url.toLocalFile ()) + ')', RCommand::App, QString (), this), save_chain);
-}
-
-RKSaveAgent::~RKSaveAgent () {
- RK_TRACE (APP);
-}
-
// We save to several files at once, meaning the standard overwrite check is not quite good enough for us.
// More importantly, it is entirely broken in KF5 < 5.22.0 (https://bugs.kde.org/show_bug.cgi?id=360666)
// So check for overwriting ourselves.
@@ -93,56 +68,38 @@ bool checkOverwriteWorkspace (QUrl url, QWidget *parent) {
KStandardGuiItem::cancel (), QString (), KMessageBox::Options (KMessageBox::Notify | KMessageBox::Dangerous));
}
-bool RKSaveAgent::askURL () {
- RK_TRACE (APP);
- save_url = QUrl::fromLocalFile (QFileDialog::getSaveFileName (RKWardMainWindow::getMain (), QString (), save_url.toLocalFile (), i18n ("R Workspace Files [%1](%1);;All files [*](*)", RKSettingsModuleGeneral::workspaceFilenameFilter ()), 0, QFileDialog::DontConfirmOverwrite));
- if (!checkOverwriteWorkspace (save_url, RKWardMainWindow::getMain ())) save_url.clear ();
+bool RKSaveAgent::saveWorkspaceAs(const QUrl& previous_url) {
+ RK_TRACE(APP);
- if (save_url.isEmpty ()) {
- if (when_done != DoNothing) {
- if (KMessageBox::warningYesNo (0, i18n ("No filename given. Your data was NOT saved. Do you still want to proceed?")) != KMessageBox::Yes) when_done = DoNothing;
- }
- return false;
- }
- return true;
-}
+ QUrl save_url = QUrl::fromLocalFile(QFileDialog::getSaveFileName(RKWardMainWindow::getMain(), QString(), previous_url.toLocalFile(), i18n("R Workspace Files [%1](%1);;All files [*](*)", RKSettingsModuleGeneral::workspaceFilenameFilter()), 0, QFileDialog::DontConfirmOverwrite));
+ if (save_url.isEmpty()) return false;
+ if (!checkOverwriteWorkspace(save_url, RKWardMainWindow::getMain())) return false;
-void RKSaveAgent::rCommandDone (RCommand *command) {
- RK_TRACE (APP);
- if (command->hasError ()) {
- RKWorkplace::mainWorkplace ()->setWorkspaceURL (previous_url);
-
- int res;
- if (when_done != DoNothing) {
- res = KMessageBox::warningYesNoCancel (0, i18n ("Saving to file '%1' failed. What do you want to do?", save_url.path ()), i18n ("Save failed"), KGuiItem (i18n ("Try saving with a different filename")), KGuiItem (i18n ("Saving failed")));
- } else {
- res = KMessageBox::warningYesNo (0, i18n ("Saving to file '%1' failed. Do you want to try saving to a different filename?", save_url.path ()));
- }
-
- if (res == KMessageBox::Yes) {
- if (askURL ()) {
- RKGlobals::rInterface ()->issueCommand (new RCommand ("save.image (\"" + save_url.toLocalFile () + "\")", RCommand::App, QString (), this), save_chain);
- return;
- }
- } else if (res == KMessageBox::No) {
- done ();
- return;
- }
-
- // else
- when_done = DoNothing;
- }
- done ();
+ return saveWorkspace(save_url);
}
-void RKSaveAgent::done () {
- RK_TRACE (APP);
- RKWardMainWindow::getMain ()->setWorkspaceMightBeModified (false);
- if (save_chain) {
- RKGlobals::rInterface ()->closeChain (save_chain);
- }
- if (when_done == Load) {
- RKWardMainWindow::getMain ()->askOpenWorkspace (load_url);
- }
- deleteLater ();
+bool RKSaveAgent::saveWorkspace(const QUrl& _url) {
+ RK_TRACE(APP);
+
+ QUrl url = _url;
+ if (url.isEmpty()) url = RKWorkplace::mainWorkplace()->workspaceURL();
+ if (url.isEmpty()) return saveWorkspaceAs();
+
+ RKWorkplace::mainWorkplace()->flushAllData();
+ auto save_chain = RKGlobals::rInterface()->startChain(0);
+
+ RKWorkplace::mainWorkplace()->saveWorkplace(url, save_chain);
+ auto command = new RCommand("save.image(" + RObject::rQuote(url.toLocalFile()) + ')', RCommand::App);
+ RKProgressControl control(RKWardMainWindow::getMain(), i18n("Workspace is being saved. <b>Hint:</b>Should this take an unusually long time, on a regular sized workspace, this may be due to other commands still pending completion in the R backend."), i18n("Saving workspace"), RKProgressControl::CancellableNoProgress);
+ control.addRCommand(command, true);
+ bool success = false;
+ QObject::connect(command->notifier(), &RCommandNotifier::commandFinished, [&success, command]() { success = command->succeeded(); });
+ RKGlobals::rInterface()->issueCommand(command, save_chain);
+ control.doModal(false);
+ RKGlobals::rInterface()->closeChain(save_chain);
+
+ if (!success) KMessageBox::error(RKWardMainWindow::getMain(), i18n("Save failed"), i18n("An error occured while trying to save the workspace. You data was <b>not</b> saved."));
+ RKWorkplace::mainWorkplace()->setWorkspaceURL(url, true);
+
+ return success;
}
diff --git a/rkward/agents/rksaveagent.h b/rkward/agents/rksaveagent.h
index 78533360..4b083a47 100644
--- a/rkward/agents/rksaveagent.h
+++ b/rkward/agents/rksaveagent.h
@@ -2,7 +2,7 @@
rksaveagent - description
-------------------
begin : Sun Aug 29 2004
- copyright : (C) 2004, 2009, 2011 by Thomas Friedrichsmeier
+ copyright : (C) 2004-2020 by Thomas Friedrichsmeier
email : thomas.friedrichsmeier at kdemail.net
***************************************************************************/
@@ -20,33 +20,21 @@
#include "../rbackend/rcommandreceiver.h"
#include <QUrl>
-#include <QObject>
class RCommandChain;
/**
-This class basically provides a mechanism to let the user save a workspace, find out whether saving was successful and - if it was not - to ask for a new filename or the like.
+This class used to have much more (over-)complexity. It could probably be merged into RKWorkplace, today. Used to control workspace saving.
@author Thomas Friedrichsmeier
*/
-class RKSaveAgent : public RCommandReceiver, public QObject {
+class RKSaveAgent {
public:
- enum DoneAction { DoNothing=0, Load=1 };
-
-/** creates a new RKSaveAgent. If when_done == Quit, the RKSaveAgent will quit the application as soon as saving was successful (or it asked to by the user). Similarly, if when_done==Load, it will load a new workspace after saving (specify the url in load_url). If url is given (not empty), and not save_file_as, the agent will try to save to the given url, else it will ask the user to specify a url. RKSaveAgent will self destruct when done. */
- explicit RKSaveAgent (QUrl url, bool save_file_as=false, DoneAction when_done=DoNothing, QUrl load_url=QUrl());
-
- ~RKSaveAgent ();
-protected:
- void rCommandDone (RCommand *command) override;
-private:
- bool askURL ();
- void done ();
- RCommandChain *save_chain;
- QUrl save_url;
- QUrl load_url;
- QUrl previous_url;
- DoneAction when_done;
+/** Save the workspace. If no URL is given use the last known save url. If the workspace has not been saved, previously, ask for url to save to. */
+ static bool saveWorkspace(const QUrl &url=QUrl());
+/** Save the workspace, asking for a (new) file name.
+ @param previous_url If given, specified the default directory and file name. */
+ static bool saveWorkspaceAs(const QUrl &previous_url=QUrl());
};
#endif
diff --git a/rkward/dialogs/rksavemodifieddialog.cpp b/rkward/dialogs/rksavemodifieddialog.cpp
index 4023400f..50331462 100644
--- a/rkward/dialogs/rksavemodifieddialog.cpp
+++ b/rkward/dialogs/rksavemodifieddialog.cpp
@@ -30,6 +30,7 @@
#include "../windows/rkworkplace.h"
#include "../windows/rkhtmlwindow.h"
#include "../misc/rkoutputdirectory.h"
+#include "../agents/rksaveagent.h"
#include "../debug.h"
@@ -81,15 +82,14 @@ RKSaveModifiedDialog::RKSaveModifiedDialog (QWidget* parent, QList<RKMDIWindow*>
save_project_check = 0;
tree->header ()->hide ();
-#warning TODO: remove me
-project = true;
+
if (project) {
QTreeWidgetItem *header = makeHeaderItem (i18n ("R Workspace (Data and Functions)"), tree);
QString url = RKWorkplace::mainWorkplace ()->workspaceURL ().toDisplayString ();
if (url.isEmpty ()) {
url = i18n ("Not previously saved");
}
- QTreeWidgetItem *save_project_check = new QTreeWidgetItem (QStringList (url));
+ save_project_check = new QTreeWidgetItem (QStringList (url));
header->addChild (save_project_check);
save_project_check->setCheckState (0, Qt::Checked);
@@ -142,7 +142,8 @@ void RKSaveModifiedDialog::saveWorkplaceChanged () {
// TODO: enable / disable "save with workplace" option for Output windows
}
-void RKSaveModifiedDialog::saveSelected () {
+#include <KMessageBox>
+void RKSaveModifiedDialog::saveSelected() {
RK_TRACE (APP);
bool all_ok = true;
@@ -152,17 +153,19 @@ void RKSaveModifiedDialog::saveSelected () {
if (!it.value ()->save ()) all_ok = false; // but we proceed with the others
}
- if (save_project_check && save_project_check->checkState (0) == Qt::Checked) {
-#warning TODO
+ if (save_project_check && save_project_check->checkState(0) == Qt::Checked) {
+ if (!RKSaveAgent::saveWorkspace()) all_ok = false;
}
- for (auto it = outputdir_checklist.constBegin (); it != outputdir_checklist.constEnd (); ++it) {
+ for (auto it = outputdir_checklist.constBegin(); it != outputdir_checklist.constEnd(); ++it) {
if (it.key ()->checkState (0) != Qt::Checked) continue;
RKOutputDirectory *dir = RKOutputDirectory::getOutputById(it.value());
- if (dir) dir->save();
+ if (dir) {
+ if (dir->save().failed()) all_ok = false;
+ }
else RK_ASSERT(dir);
}
- if (all_ok) accept ();
- else reject ();
+ if (all_ok) accept();
+ else reject();
}
diff --git a/rkward/misc/rkoutputdirectory.cpp b/rkward/misc/rkoutputdirectory.cpp
index d357d0ba..b2b1b1a4 100644
--- a/rkward/misc/rkoutputdirectory.cpp
+++ b/rkward/misc/rkoutputdirectory.cpp
@@ -115,7 +115,7 @@ GenericRRequestResult RKOutputDirectory::exportAs (const QString& _dest, RKOutpu
if (dest.isEmpty()) {
QFileDialog dialog(RKWardMainWindow::getMain(), i18n("Select destination file name"), QFileInfo(save_filename).absolutePath());
dialog.setFileMode(QFileDialog::AnyFile);
- dialog.setNameFilters(QStringList() << i18n("RKWard Output Files (*.rko)") << i18n("All Files (*)"));
+ dialog.setNameFilters(QStringList() << i18n("RKWard Output Files [*.rko](*.rko)") << i18n("All Files [*](*)"));
dialog.setAcceptMode(QFileDialog::AcceptSave);
dialog.setOption(QFileDialog::DontConfirmOverwrite, true); // custom handling below
diff --git a/rkward/rkward.cpp b/rkward/rkward.cpp
index cf1e655c..7218c398 100644
--- a/rkward/rkward.cpp
+++ b/rkward/rkward.cpp
@@ -71,6 +71,7 @@
#include "dialogs/rkimportdialog.h"
#include "dialogs/rkrecoverdialog.h"
#include "dialogs/rksetupwizard.h"
+#include "dialogs/rksavemodifieddialog.h"
#include "agents/rksaveagent.h"
#include "agents/rkloadagent.h"
#include "agents/rkquitagent.h"
@@ -791,26 +792,10 @@ bool RKWardMainWindow::doQueryQuit () {
if (RKSettingsModuleGeneral::workplaceSaveMode () == RKSettingsModuleGeneral::SaveWorkplaceWithSession) {
RKSettingsModuleGeneral::setSavedWorkplace (RKWorkplace::mainWorkplace ()->makeWorkplaceDescription ().join ("\n"), KSharedConfig::openConfig ().data ());
}
-
-// if (!RObjectList::getGlobalEnv ()->isEmpty ()) {
- int res;
- res = KMessageBox::questionYesNoCancel (this, i18n ("Quitting RKWard: Do you want to save the workspace?"), i18n ("Save Workspace?"), KStandardGuiItem::save (), KStandardGuiItem::discard (), KGuiItem (i18n ("Do Not Quit")));
- if (res == KMessageBox::Yes) {
- new RKSaveAgent (RKWorkplace::mainWorkplace ()->workspaceURL (), false, RKSaveAgent::DoNothing);
- } else if (res == KMessageBox::Cancel) {
- slotSetStatusReady ();
- return false;
- }
-// }
-
- lockGUIRebuild (true);
- if (!RKWorkplace::mainWorkplace ()->closeAll ()) {
- // User cancelled closing, when asked to save changes
+ if (!RKWorkplace::mainWorkplace()->closeAll(RKMDIWindow::AnyType, RKMDIWindow::AnyWindowState, true)) {
slotSetStatusReady ();
- lockGUIRebuild (false);
return false;
}
-// lockGUIRebuild (false); // No need to update GUI anymore (and doing so is potentially asking for trouble, anyway)
return true;
}
@@ -827,28 +812,25 @@ void RKWardMainWindow::slotNewDataFrame () {
void RKWardMainWindow::askOpenWorkspace (const QUrl &url) {
RK_TRACE (APP);
- if (!no_ask_save && ((!RObjectList::getGlobalEnv ()->isEmpty () && workspace_modified) || !RKGlobals::rInterface ()->backendIsIdle ())) {
+ if (!no_ask_save && ((!RObjectList::getGlobalEnv()->isEmpty() && workspace_modified) || !RKGlobals::rInterface()->backendIsIdle())) {
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;
- }
+ res = KMessageBox::questionYesNoCancel (this, i18n("Do you want to save the current workspace?"), i18n("Save Workspace?"));
+ if (res != KMessageBox::No) return; // Cancel
+ if (!RKSaveAgent::saveWorkspace()) return; // Save failed
}
- slotCloseAllEditors ();
+ slotCloseAllEditors();
slotSetStatusBarText(i18n("Opening workspace..."));
QUrl lurl = url;
if (lurl.isEmpty ()) {
- lurl = QFileDialog::getOpenFileUrl (this, i18n("Select workspace to open..."), RKSettingsModuleGeneral::lastUsedUrlFor ("workspaces"), i18n ("R Workspace Files [%1](%1);;All files [*](*)", RKSettingsModuleGeneral::workspaceFilenameFilter ()));
+ lurl = QFileDialog::getOpenFileUrl(this, i18n("Select workspace to open..."), RKSettingsModuleGeneral::lastUsedUrlFor("workspaces"), i18n("R Workspace Files [%1](%1);;All files [*](*)", RKSettingsModuleGeneral::workspaceFilenameFilter()));
}
if (!lurl.isEmpty ()) {
- RKSettingsModuleGeneral::updateLastUsedUrl ("workspaces", lurl.adjusted (QUrl::RemoveFilename));
- openWorkspace (lurl);
+ RKSettingsModuleGeneral::updateLastUsedUrl("workspaces", lurl.adjusted(QUrl::RemoveFilename));
+ openWorkspace(lurl);
}
- slotSetStatusReady ();
+ slotSetStatusReady();
}
void RKWardMainWindow::slotFileOpenWorkspace () {
@@ -864,12 +846,12 @@ void RKWardMainWindow::slotFileLoadLibs () {
void RKWardMainWindow::slotFileSaveWorkspace () {
RK_TRACE (APP);
- new RKSaveAgent (RKWorkplace::mainWorkplace ()->workspaceURL ());
+ RKSaveAgent::saveWorkspace();
}
void RKWardMainWindow::slotFileSaveWorkspaceAs () {
RK_TRACE (APP);
- new RKSaveAgent (RKWorkplace::mainWorkplace ()->workspaceURL (), true);
+ RKSaveAgent::saveWorkspaceAs();
}
void RKWardMainWindow::addWorkspaceUrl (const QUrl &url) {
diff --git a/rkward/windows/rkworkplace.cpp b/rkward/windows/rkworkplace.cpp
index c2d804ed..e0289666 100644
--- a/rkward/windows/rkworkplace.cpp
+++ b/rkward/windows/rkworkplace.cpp
@@ -663,26 +663,25 @@ RKWorkplace::RKWorkplaceObjectList RKWorkplace::getObjectList (int type, int sta
return ret;
}
-bool RKWorkplace::closeAll (int type, int state) {
- RK_TRACE (APP);
+bool RKWorkplace::closeAll(int type, int state, bool ask_close_project) {
+ RK_TRACE(APP);
- return closeWindows (windows);
+ return closeWindows(getObjectList(type, state), ask_close_project);
}
-bool RKWorkplace::closeWindows (QList<RKMDIWindow*> windows) {
- RK_TRACE (APP);
+bool RKWorkplace::closeWindows(QList<RKMDIWindow*> windows, bool ask_close_project) {
+ RK_TRACE(APP);
bool allclosed = true;
- RKWardMainWindow::getMain ()->lockGUIRebuild (true);
+ RKWardMainWindow::getMain()->lockGUIRebuild(true);
- bool ok = RKSaveModifiedDialog::askSaveModified (this, windows, false);
+ bool ok = RKSaveModifiedDialog::askSaveModified(this, windows, ask_close_project);
if (ok) {
- for (int i = windows.size () - 1; i >= 0; --i) {
- RK_ASSERT(closeWindow (windows[i], RKMDIWindow::NoAskSaveModified));
- // TODO: Do not ask for saving _again_
+ for (int i = windows.size() - 1; i >= 0; --i) {
+ RK_ASSERT(closeWindow(windows[i], RKMDIWindow::NoAskSaveModified));
}
}
- RKWardMainWindow::getMain ()->lockGUIRebuild (false);
+ RKWardMainWindow::getMain()->lockGUIRebuild(false);
return ok;
}
@@ -909,13 +908,15 @@ QStringList RKWorkplace::makeWorkplaceDescription () {
return workplace_description;
}
-void RKWorkplace::saveWorkplace (RCommandChain *chain) {
+void RKWorkplace::saveWorkplace(const QUrl& for_url, RCommandChain *chain) {
RK_TRACE (APP);
// TODO: This is still a mess. All workplace-related settings, including the workspaceConfig(), should be saved to a single place, and in
// standard KConfig format.
- if (RKSettingsModuleGeneral::workplaceSaveMode () != RKSettingsModuleGeneral::SaveWorkplaceWithWorkspace) return;
+ if (RKSettingsModuleGeneral::workplaceSaveMode() != RKSettingsModuleGeneral::SaveWorkplaceWithWorkspace) return;
- RKGlobals::rInterface ()->issueCommand ("rk.save.workplace(description=" + RObject::rQuote (makeWorkplaceDescription().join ("\n")) + ')', RCommand::App, i18n ("Save Workplace layout"), 0, 0, chain);
+ QString file_param;
+ if (!for_url.isEmpty()) file_param = QString("file=") + RObject::rQuote(for_url.toLocalFile()) + QStringLiteral(", ");
+ RKGlobals::rInterface()->issueCommand("rk.save.workplace(" + file_param + "description=" + RObject::rQuote (makeWorkplaceDescription().join ("\n")) + ')', RCommand::App, i18n ("Save Workplace layout"), 0, 0, chain);
}
void RKWorkplace::restoreWorkplace (RCommandChain *chain, bool merge) {
diff --git a/rkward/windows/rkworkplace.h b/rkward/windows/rkworkplace.h
index 0eb26afe..9d76cf1d 100644
--- a/rkward/windows/rkworkplace.h
+++ b/rkward/windows/rkworkplace.h
@@ -161,16 +161,17 @@ public:
/** Close the given windows, whether they are attached or detached.
@param windows list windows to close
@returns true, if _all_ windows were actually closed. */
- bool closeWindows (QList<RKMDIWindow*> windows);
+ bool closeWindows (QList<RKMDIWindow*> windows, bool ask_close_project=false);
/** Closes all windows of the given type(s). Default call (no arguments) closes all windows
@param type: A bitwise OR of RKWorkplaceObjectType
@param state: A bitwise OR of RKWorkplaceObjectState
@returns false if cancelled by user (user was prompted for saving, and chose cancel) */
- bool closeAll (int type=RKMDIWindow::AnyType, int state=RKMDIWindow::AnyWindowState);
+ bool closeAll(int type=RKMDIWindow::AnyType, int state=RKMDIWindow::AnyWindowState, bool ask_close_project=false);
/** Write a description of all current windows to the R backend. This can later be read by restoreWorkplace (). Has no effect, if RKSettingsModuleGeneral::workplaceSaveMode () != RKSettingsModuleGeneral::SaveWorkplaceWithWorkspace
+ at param url the url to use. Can be left null, in which case the current workspace url will be used.
@param chain command chain to place the command in */
- void saveWorkplace (RCommandChain *chain=0);
+ void saveWorkplace (const QUrl &for_url=QUrl(), RCommandChain *chain=0);
/** 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 */
More information about the rkward-tracker
mailing list