[education/rkward/devel/workspace_output] rkward: More work in progress on output directories

Thomas Friedrichsmeier null at kde.org
Thu Oct 22 20:13:27 BST 2020


Git commit c3bb4ab8dc8499c31bf46c43d80d6267b23eccd4 by Thomas Friedrichsmeier.
Committed on 22/10/2020 at 19:13.
Pushed by tfry into branch 'devel/workspace_output'.

More work in progress on output directories

M  +6    -3    rkward/dialogs/rksavemodifieddialog.cpp
M  +69   -80   rkward/misc/rkoutputdirectory.cpp
M  +13   -6    rkward/misc/rkoutputdirectory.h
M  +1    -2    rkward/windows/rkhtmlwindow.h

https://invent.kde.org/education/rkward/commit/c3bb4ab8dc8499c31bf46c43d80d6267b23eccd4

diff --git a/rkward/dialogs/rksavemodifieddialog.cpp b/rkward/dialogs/rksavemodifieddialog.cpp
index 3ea71ea1..fa990668 100644
--- a/rkward/dialogs/rksavemodifieddialog.cpp
+++ b/rkward/dialogs/rksavemodifieddialog.cpp
@@ -29,6 +29,7 @@
 
 #include "../windows/rkworkplace.h"
 #include "../windows/rkhtmlwindow.h"
+#include "../misc/rkoutputdirectory.h"
 
 #include "../debug.h"
 
@@ -92,7 +93,7 @@ project = true;
 		header->addChild (save_project_check);
 		save_project_check->setCheckState (0, Qt::Checked);
 
-		QStringList modified_outputs = RKOutputWindowManager::self()->modifiedOutputDirectories();
+		QStringList modified_outputs = RKOutputDirectory::modifiedOutputDirectories();
 		if (!modified_outputs.isEmpty ()) {
 			QTreeWidgetItem *header = makeHeaderItem (i18n ("Output files"), tree);
 			for (int i = 0; i < modified_outputs.size (); ++i) {
@@ -155,9 +156,11 @@ void RKSaveModifiedDialog::saveSelected () {
 #warning TODO
 	}
 
-	for (QMap<QTreeWidgetItem *, QString>::const_iterator 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;
-		RKOutputWindowManager::self()->saveOutputDirectory(it.value());
+		RKOutputDirectory *dir = RKOutputDirectory::getOutputById(it.value(), nullptr);
+		if (dir) dir->save();
+		else RK_ASSERT(dir);
 	}
 
 	if (all_ok) accept ();
diff --git a/rkward/misc/rkoutputdirectory.cpp b/rkward/misc/rkoutputdirectory.cpp
index 8c020e50..7b3e04e2 100644
--- a/rkward/misc/rkoutputdirectory.cpp
+++ b/rkward/misc/rkoutputdirectory.cpp
@@ -20,13 +20,17 @@
 #include <QDir>
 #include <QFileInfo>
 #include <QFileDialog>
-#include <KLocalizedString>
 
+#include <KLocalizedString>
 #include <KMessageBox>
 
 #include "../settings/rksettingsmodulegeneral.h"
 #include "../rbackend/rcommand.h"
+#include "../rbackend/rkrinterface.h"
+#include "../windows/rkmdiwindow.h"
+#include "../windows/rkhtmlwindow.h"
 #include "../misc/rkcommonfunctions.h"
+#include "../agents/rkquitagent.h"
 #include "../rkglobals.h"
 #include "../rkward.h"
 
@@ -85,16 +89,15 @@ RKOutputDirectory::RKOutputDirectory() : initialized(false), window(nullptr) {
 	RK_TRACE(APP);
 }
 
-~RKOutputDirectory::RKOutputDirectory() {
+RKOutputDirectory::~RKOutputDirectory() {
 	RK_TRACE(APP);
 }
 
 RKOutputDirectory* RKOutputDirectory::getOutputById(const QString& id, GenericRCallResult* result) {
 	RK_TRACE (APP);
-	RK_ASSERT (result);
 
 	if (!outputs.contains(id)) {
-		result->error = i18n("The output identified by '%1' is not loaded in this session.", id);
+		if (result) result->error = i18n("The output identified by '%1' is not loaded in this session.", id);
 		return 0;
 	}
 	return outputs[id];
@@ -104,7 +107,7 @@ RKOutputDirectory* RKOutputDirectory::getOutputBySaveUrl(const QString& _dest, b
 	RK_TRACE (APP);
 
 	if (_dest.isEmpty()) {
-		if (!create) return getActiveOutput();
+		if (!create) return RKOutputWindowManager::self()->getCurrentOutput();
 		return createOutputDirectoryInternal();
 	}
 
@@ -162,13 +165,13 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::exportAs (const QString
 		if (!isRKWwardOutputDirectory(dest)) {
 			return GenericRCallResult::makeError(i18n("The directory %1 exists, but does not appear to be an RKWard output directory. Refusing to overwrite it.", dest));
 		}
-		if (isOutputDirectoryModified(dest)) {
+/*		if (isOutputDirectoryModified(dest)) {
 			return GenericRCallResult::makeError(i18n("The output directory %1 has been modified by an external process. Refusing to overwrite it.", dest));
-		}
+		} */
 		if (overwrite == Ask) {
 			const QString warning = i18n("Are you sure you want to overwrite the existing directory '%1'? All current contents, <b>including subdirectories</b> will be lost.", dest);
 			KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel(RKWardMainWindow::getMain(), warning, i18n("Overwrite Directory?"), KStandardGuiItem::overwrite(),
-												KStandardGuiItem::cance (), QString(), KMessageBox::Options(KMessageBox::Notify | KMessageBox::Dangerous));
+												KStandardGuiItem::cancel(), QString(), KMessageBox::Options(KMessageBox::Notify | KMessageBox::Dangerous));
 			if (KMessageBox::Continue != res) return GenericRCallResult::makeError(i18n("User cancelled"));
 		} else if (overwrite != Force) {
 			return GenericRCallResult::makeError(i18n("Not overwriting existing output"));
@@ -239,7 +242,7 @@ RKOutputDirectory* RKOutputDirectory::createOutputDirectoryInternal() {
 	marker.close();
 
 	auto d = new RKOutputDirectory();
-	d->work_dir = QFileInfo(ddir.absoluteFilePAth(destname)).canonicalFilePath();
+	d->work_dir = QFileInfo(ddir.absoluteFilePath(destname)).canonicalFilePath();
 	d->id = d->work_dir;
 	d->initialized = false;
 	outputs.insert(d->id, d);
@@ -254,7 +257,7 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::activate(RCommandChain*
 	if(!initialized) {
 		// when an output directory is first initialized, we don't want that to count as a "modification". Therefore, update the "saved hash" _after_ initialization
 		RCommand *command = new RCommand(QString(), RCommand::App | RCommand::Sync | RCommand::EmptyCommand);
-		connect (command->notifier(), &RCommandNotifier::commandFinished, this, &RKOutputDirectory::updateSavedHash);
+		connect(command->notifier(), &RCommandNotifier::commandFinished, this, &RKOutputDirectory::updateSavedHash);
 		RKGlobals::rInterface()->issueCommand(command, chain);
 		initialized = true;
 	}
@@ -262,107 +265,93 @@ RKOutputDirectory::GenericRCallResult RKOutputDirectory::activate(RCommandChain*
 	return GenericRCallResult(QVariant(index_file));
 }
 
+RKOutputDirectory::GenericRCallResult RKOutputDirectory::clear(OverwriteBehavior discard) {
+	RK_TRACE(APP);
 
-
-
-
-
-
-
-
-
-
-
-
-QString RKOutputDirectory::dropOutputDirectory (const QString& dir, bool ask, RCommandChain* chain) {
-	RK_TRACE (APP);
-
-	OutputDirectory od = outputs[work_dir];
-	if (ask) {
-		if (od.saved_hash != hashDirectoryState (work_dir)) {
-			if (KMessageBox::warningContinueCancel (RKWardMainWindow::getMain (), i18n ("The output directory %1 has unsaved changes, which will be lost by dropping it. Are you sure you want to proceed?", work_dir)) != KMessageBox::Continue) return i18n ("Cancelled");
+	if (!isEmpty()) {
+		if (discard == Ask) {
+			if (KMessageBox::warningContinueCancel(RKWardMainWindow::getMain(), i18n("Clearing will destroy any unsaved changes, and - upon saving - also saved changes. Are you sure you want to proceed?")) == KMessageBox::Continue) {
+				discard = Force;
+			}
+			if (discard != Force) {
+				return GenericRCallResult::makeError(i18n("Output is not empty. Not clearing it."));
+			}
 		}
 	}
 
-	QString error = dropOutputDirectoryInternal (work_dir);
-	if (!error.isEmpty ()) return error;
+	QDir dir(work_dir);
+	dir.removeRecursively();
+	dir.mkpath(".");
+	initialized = false;
+	if (isActive()) activate();
+}
 
-	if (current_default_path.startsWith (work_dir)) {
-		if (!outputs.isEmpty ()) {
-			backendActivateOutputDirectory (outputs.constBegin ().key (), chain);
-		} else {
-			createOutputDirectory (chain);
-		}
-	}
+bool RKOutputDirectory::isEmpty() const {
+	RK_TRACE(APP);
+
+	if (!save_dir.isEmpty()) return false;  // we _could_ have saved an empty output, of course, but no worries about corner cases. In any doubt we return false.
 
-	return QString ();
+	if (!initialized) return true;
+	if (!isModified()) return true;   // because we have not saved/loaded this file, before, see above
+	return false;
 }
 
-QString RKOutputDirectory::dropOutputDirectoryInternal (const QString& dir) {
-	RK_TRACE (APP);
+RKOutputDirectory::GenericRCallResult RKOutputDirectory::purge(RKOutputDirectory::OverwriteBehavior discard) {
+	RK_TRACE(APP);
 
-	if (!isRKWwardOutputDirectory (dir)) {
-		RK_ASSERT (false); // this should not happen unless user messes with the temporary file, but we better play it safe.
-		return (i18n ("The directory %1 does not appear to be an RKWard output directory. Refusing to remove it.", dir));
-	}
-	outputs.remove (dir);
-	QDir (dir).removeRecursively ();
-
-	QStringList paths = windows.keys ();
-	for (int i = 0; i < paths.size (); ++i) {
-		if (paths[i].startsWith (dir)) {
-//			RKWorkplace::closeWindows (windows.values (paths[i]));  // NOTE: Won't work with current compilers
-			QList<RKHTMLWindow*> wins = windows.values (paths[i]);
-			for (int j = 0; j < wins.size (); ++j) {
-				RKWorkplace::mainWorkplace ()->closeWindow (wins[j]);
+	if (isModified()) {
+		if (discard == Fail) {
+			return GenericRCallResult::nakeError(i18n("Output has been modified. Not closing it."));
+		}
+		if (discard == Ask) {
+			auto res = KMessageBox::questionYesNoCancel(RKWardMainWindow::getMain(), i18n("The output has been modified, and closing it will discard all changes. What do you want to do?"), i18n("Discard unsaved changes?"), KStandardGuiItem::discard(), KStandardGuiItem::save(), KStandardGuiItem::cancel());
+			if (res == KMessageBox::Cancel) {
+				return GenericRCallResult::nakeError(i18n("User cancelled"));
+			}
+			if (res == KMessageBox::No) {
+				auto ret = save();
+				if (ret.failed()) return ret;
 			}
 		}
 	}
 
-	return QString ();
+	QDir dir(work_dir);
+	dir.removeRecursively();
+	if (isActive()) {
+		if (RKQuitAgent::quittingInProgress()) {
+			RK_DEBUG(APP, DEBUG, "Skipping activation of new output file: quitting in progress");
+		} else {
+			activateDefault();
+		}
+	}
+	outputs.remove(id);
+	deleteLater();
+	return GenericRCallResult();
 }
 
-void RKOutputDirectory::purgeAllOututputDirectories () {
-	RK_TRACE (APP);
+bool RKOutputDirectory::isActive() const {
+	RK_TRACE(APP);
 
-	QStringList output_dirs = outputs.keys ();
-	for (int i = output_dirs.size () - 1; i >= 0; --i) {
-		if (i > 0) dropOutputDirectoryInternal (output_dirs[i]);
-		else dropOutputDirectory (output_dirs[i], false);
-	}
+	return RKOutputWindowManager::self()->currentDefaultPath().startsWith(work_dir);
 }
 
 QStringList RKOutputDirectory::modifiedOutputDirectories() const {
 	RK_TRACE (APP);
 
 	QStringList ret;
-	for (QMap<QString, OutputDirectory>::const_iterator it = outputs.constBegin (); it != outputs.constEnd (); ++it) {
-		if (it.value ().saved_hash != hashDirectoryState (it.key ())) ret.append (it.key ());
+	for (auto it = outputs.constBegin (); it != outputs.constEnd (); ++it) {
+		if (it.value()->isModified()) ret.append(it.key());
 	}
 	return ret;
 }
 
-QString RKOutputDirectory::outputCaption (const QString& dir) const {
-	RK_TRACE (APP);
-
-	// TODO: real implementation!
-	return (dir);
-}
-
-bool RKOutputDirectory::isRKWwardOutputDirectory (const QString& dir) {
+bool RKOutputDirectory::isRKWwardOutputDirectory(const QString& dir) {
 	RK_TRACE (APP);
 
 	return (QDir(dir).exists(QStringLiteral("rkward_output_marker.txt")));
 }
 
-bool RKOutputDirectory::isOutputDirectoryModified(const QString dir) {
-	RK_TRACE (APP);
-
-#warning TODO
-	return true;
-}
-
-void RKOutputDirectory::updateSavedHash () {
+void RKOutputDirectory::updateSavedHash() {
 	RK_TRACE (APP);
 
 	saved_hash = hashDirectoryState(work_dir);
diff --git a/rkward/misc/rkoutputdirectory.h b/rkward/misc/rkoutputdirectory.h
index 4bd784de..e56b42ba 100644
--- a/rkward/misc/rkoutputdirectory.h
+++ b/rkward/misc/rkoutputdirectory.h
@@ -25,6 +25,9 @@
 #include <QPointer>
 #include <QObject>
 
+class RKMDIWindow;
+class RCommandChain;
+
 class RKOutputDirectory : public QObject {
 	Q_OBJECT
 public:
@@ -42,7 +45,7 @@ public:
 		QString error;
 		QString warning;
 		QVariant ret;
-		bool failed() { !error.isEmpty(); }
+		bool failed() { return !error.isEmpty(); }
 		bool toBool() { return ret.toBool(); }
 	};
 	enum OverwriteBehavior {
@@ -57,12 +60,17 @@ public:
 	GenericRCallResult clear(OverwriteBehavior discard=Ask);
 	GenericRCallResult purge(OverwriteBehavior discard=Ask);
 	QString getId() const { return id; };
+	bool isEmpty() const;
+	bool isActive() const;
+	bool isModified() const;
 	void view(bool raise);
-	QString filename();
-	QString workDir();
+	QString filename() const;
+	QString workDir() const;
 	static GenericRCallResult handleRCall(const QStringList& params);
-	static RKOutputDirectory* getOutputById(const QString& id, GenericCallResult* result);
+	static RKOutputDirectory* getOutputById(const QString& id, GenericRCallResult* result);
 	static RKOutputDirectory* getOutputBySaveUrl(const QString& dest, bool create=false);
+/** Return a list of all current output directories that have been modified. Used for asking for save during shutdown. */
+	static QStringList modifiedOutputDirectories();
 private:
 	RKOutputDirectory();
 	~RKOutputDirectory();
@@ -74,6 +82,7 @@ private:
 	QString save_dir;
 	QString id;
 	bool initialized;
+
 	QPointer<RKMDIWindow> window;
 	/** map of outputs. */
 	static QMap<QString, RKOutputDirectory*> outputs;
@@ -81,8 +90,6 @@ private:
 	GenericRCallResult import(const QString& from);
 	static RKOutputDirectory* createOutputDirectoryInternal();
 	static bool isRKWwardOutputDirectory (const QString &dir);
-	static bool isOutputDirectoryModified (const QString &dir);
-	QString dropOutputDirectoryInternal (const QString& dir);
 };
 
 #endif
diff --git a/rkward/windows/rkhtmlwindow.h b/rkward/windows/rkhtmlwindow.h
index 74ce74bb..9959c20f 100644
--- a/rkward/windows/rkhtmlwindow.h
+++ b/rkward/windows/rkhtmlwindow.h
@@ -207,6 +207,7 @@ public:
 	void registerWindow (RKHTMLWindow *window);
 /** R may produce output while no output window is active. This allows to set the file that should be monitored for such changes (called from within rk.set.html.output.file()). */
 	void setCurrentOutputPath (const QString &path);
+	QString currentOutputPath() const;
 /** returns a list (possibly empty) of pointers to existing output windows for the given path (for the current output path, if no path given). */
 	QList<RKHTMLWindow*> existingOutputWindows (const QString &path = QString ()) const;
 /** Create (and show) a new output window (for the current output path, unless path is specified), and @return the pointer */
@@ -216,8 +217,6 @@ public:
 	RKOutputDirectory* getCurrentOutput(const QString& filename=QString(), bool create=false);
 	QList<RKOutputDirectory*> allOutputs();
 
-/** Return a list of all current output directories that have been modified. Used for asking for save during shutdown. */
-	QStringList modifiedOutputDirectories () const;
 /** Return the name / caption of the given output directory */
 	QString outputCaption (const QString &dir) const;
 /** Use with care! Purges all current output directories, saved or not. You should query modifiedOutputDirectories (), and make sure to prompt for saving, before calling this. For use during shutdown. */




More information about the rkward-tracker mailing list