[education/rkward/devel/workspace_output] rkward: Implement all UI entry point for opening output files.

Thomas Friedrichsmeier null at kde.org
Thu Mar 3 15:44:07 GMT 2022


Git commit 7ecf7bfe7d3ec792ca3848daa6dabc3c3e1d3ccd by Thomas Friedrichsmeier.
Committed on 03/03/2022 at 15:43.
Pushed by tfry into branch 'devel/workspace_output'.

Implement all UI entry point for opening output files.

M  +15   -7    rkward/misc/rkoutputdirectory.cpp
M  +1    -0    rkward/misc/rkoutputdirectory.h
M  +11   -0    rkward/rbackend/rpackages/rkward/R/rk.output.R
A  +-    --    rkward/rbackend/rpackages/rkward_0.7.3.tar.gz
M  +23   -5    rkward/rkward.cpp
M  +9    -2    rkward/rkward.h
M  +6    -2    rkward/rkwardui.rc
M  +11   -2    rkward/windows/rkhtmlwindow.cpp
M  +23   -19   rkward/windows/rkworkplace.cpp
M  +9    -2    rkward/windows/rkworkplace.h

https://invent.kde.org/education/rkward/commit/7ecf7bfe7d3ec792ca3848daa6dabc3c3e1d3ccd

diff --git a/rkward/misc/rkoutputdirectory.cpp b/rkward/misc/rkoutputdirectory.cpp
index c0b959e7..815c4743 100644
--- a/rkward/misc/rkoutputdirectory.cpp
+++ b/rkward/misc/rkoutputdirectory.cpp
@@ -241,8 +241,8 @@ GenericRRequestResult RKOutputDirectory::revert(OverwriteBehavior discard) {
 	if (!isModifiedAccurate()) return GenericRRequestResult(id, i18n("Output had no modifications. Nothing reverted."));
 	if (discard == Ask) {
 		if (save_filename.isEmpty()) {
-			if (KMessageBox::warningContinueCancel(RKWardMainWindow::getMain(), i18n("This output has not been saved before. Reverting will clear it, entirely. Are you sure you want to proceed?")) == KMessageBox::Continue) {
-				discard = Force;
+			if (KMessageBox::warningContinueCancel(RKWardMainWindow::getMain(), i18n("This output has not been saved before. Reverting will clear it, entirely. Are you sure you want to proceed?"), QString(), KStandardGuiItem::clear()) == KMessageBox::Continue) {
+				return clear(Force);
 			}
 		} else if (KMessageBox::warningContinueCancel(RKWardMainWindow::getMain(), i18n("Reverting will destroy any changes, since the last time you saved (%1). Are you sure you want to proceed?", save_timestamp.toString())) == KMessageBox::Continue) {
 			discard = Force;
@@ -461,7 +461,7 @@ RKOutputDirectory* RKOutputDirectory::activeOutput() {
 	return nullptr;
 }
 
-GenericRRequestResult RKOutputDirectory::view(bool raise, RCommandChain* chain) {
+RKMDIWindow * RKOutputDirectory::getOrCreateView(bool raise, RCommandChain* chain) {
 	RK_TRACE(APP);
 
 	if (!initialized) {
@@ -476,11 +476,18 @@ GenericRRequestResult RKOutputDirectory::view(bool raise, RCommandChain* chain)
 		if (!w->isActiveWindow() || raise) {
 			list[0]->activate();
 		}
-	} else {
-		if (!known_modified) known_modified = isModifiedAccurate();
-		RKWorkplace::mainWorkplace()->openOutputWindow(QUrl::fromLocalFile(workPath()));
+
+		return w;
 	}
 
+	if (!known_modified) known_modified = isModifiedAccurate();
+	return RKWorkplace::mainWorkplace()->openNewOutputWindow(this);
+}
+
+GenericRRequestResult RKOutputDirectory::view(bool raise, RCommandChain* chain) {
+	RK_TRACE(APP);
+
+	getOrCreateView(raise, chain);
 	return GenericRRequestResult(id);
 }
 
@@ -502,8 +509,9 @@ RKOutputDirectoryCallResult RKOutputDirectory::get(const QString &_filename, boo
 		// NOTE: annoyingly QFileInfo::canonicalFilePath() returns an empty string, if the file does not exist
 		if (create) {
 			if (dir) return GenericRRequestResult::makeError(i18n("Output '1%' is already loaded in this session. Cannot create it.", filename));
-			if (file_exists) return GenericRRequestResult::makeError(i18n("A file named '1%' already exists. Cannot create it.", filename));
+			if (file_exists) return GenericRRequestResult::makeError(i18n("A file named '%1' already exists. Cannot create it.", filename));
 
+			// NOTE: This is a bit of an unusual case: Creating a fresh output with save file name already specified. To avoid issues (esp. around canonicalFilePath()), we make sure to actually save the new output, right away (much like "touch"), instead of only setting the save file name for later usage
 			ret.setDir(createOutputDirectoryInternal());
 			ret.addMessages(dir->save(filename));  // NOTE: save() takes care of normalizing
 		} else {
diff --git a/rkward/misc/rkoutputdirectory.h b/rkward/misc/rkoutputdirectory.h
index f4b6aea2..96981661 100644
--- a/rkward/misc/rkoutputdirectory.h
+++ b/rkward/misc/rkoutputdirectory.h
@@ -64,6 +64,7 @@ public:
 	/** This function may not always be accurate, but is fast. It is fairly reliable as long as there is an active view, but should not be used when there is not.  */
 	bool isModifiedFast() const;
 	GenericRRequestResult view(bool raise, RCommandChain* chain=0);
+	RKMDIWindow* getOrCreateView(bool raise, RCommandChain* chain=0);
 	QString filename() const { return save_filename; };
 	QString workDir() const { return work_dir; }
 	QString workPath() const;
diff --git a/rkward/rbackend/rpackages/rkward/R/rk.output.R b/rkward/rbackend/rpackages/rkward/R/rk.output.R
index c603b9cf..445d5d6c 100644
--- a/rkward/rbackend/rpackages/rkward/R/rk.output.R
+++ b/rkward/rbackend/rpackages/rkward/R/rk.output.R
@@ -40,6 +40,17 @@
 #'
 #' @returns NULL
 #' @field id An internal identifier. NULL for a closed output. This should be treated as read-only, but you can use this to test whether two output handles are the same.
+#'
+#' @examples
+#'
+#' ## Not run
+#' x <- rk.output(create=TRUE)
+#' x$activate()
+#' rk.print("Hello World!")
+#' x$view()
+#' x$save() # Will prompt for filename
+#' x$close()
+#'
 #' @import methods
 #' @exportClass RK.Output
 RK.Output <- setRefClass(Class="RK.Output", fields=list(id="character"),
diff --git a/rkward/rbackend/rpackages/rkward_0.7.3.tar.gz b/rkward/rbackend/rpackages/rkward_0.7.3.tar.gz
new file mode 100644
index 00000000..0cff173c
Binary files /dev/null and b/rkward/rbackend/rpackages/rkward_0.7.3.tar.gz differ
diff --git a/rkward/rkward.cpp b/rkward/rkward.cpp
index 748bfdde..43d9f3f5 100644
--- a/rkward/rkward.cpp
+++ b/rkward/rkward.cpp
@@ -495,14 +495,17 @@ void RKWardMainWindow::initActions() {
 	new_command_editor->setText (i18n ("Script File"));
 	new_command_editor->setIcon (RKStandardIcons::getIcon (RKStandardIcons::WindowCommandEditor));
 
-	fileOpen = actionCollection ()->addAction (KStandardAction::Open, "file_openy", this, SLOT(slotOpenCommandEditor()));
-	fileOpen->setText (i18n ("Open R Script File..."));
+	fileOpenScript = actionCollection()->addAction(KStandardAction::Open, "file_open_script", this, SLOT(slotOpenCommandEditor()));
+	actionCollection()->setDefaultShortcut(fileOpenScript, Qt::ControlModifier + Qt::AltModifier + Qt::Key_O);
+	fileOpenScript->setText(i18n("Open R Script File..."));
+
+	fileOpenOutput = actionCollection()->addAction(KStandardAction::Open, "file_open_output", this, SLOT(slotOpenOutput()));
+	actionCollection()->setDefaultShortcut(fileOpenOutput, QKeySequence());
+	fileOpenOutput->setText(i18n("Open RKWard Output File..."));
 
 	QAction *file_open_any = actionCollection ()->addAction (KStandardAction::Open, "file_open_any");
 	connect (file_open_any, &QAction::triggered, this, &RKWardMainWindow::openAnyFile);
-
 	file_open_any->setText (i18n ("Open any File..."));
-	actionCollection ()->setDefaultShortcut (file_open_any, Qt::ControlModifier + Qt::AltModifier + Qt::Key_O);
 
 	fileOpenRecent = static_cast<KRecentFilesAction*> (actionCollection ()->addAction (KStandardAction::OpenRecent, "file_open_recenty", this, SLOT(slotOpenCommandEditor(QUrl))));
 	fileOpenRecent->setText (i18n ("Open Recent R Script File"));
@@ -597,7 +600,9 @@ void RKWardMainWindow::initActions() {
 	open_any_action->addAction (fileOpenWorkspace);
 	open_any_action->addAction (fileOpenRecentWorkspace);
 	open_any_action->addSeparator ();
-	open_any_action->addAction (fileOpen);
+	open_any_action->addAction (fileOpenScript);
+	open_any_action->addAction (fileOpenOutput);
+	open_any_action->addSeparator ();
 	open_any_action->addAction (fileOpenRecent);
 	open_any_action->addAction (file_open_any);
 	open_any_action->addSeparator ();
@@ -981,6 +986,19 @@ void RKWardMainWindow::openAnyFile () {
 	}
 }
 
+void RKWardMainWindow::slotNewOutput() {
+	RK_TRACE (APP);
+
+	RKWorkplace::mainWorkplace()->openOutputWindow(QUrl(), true);
+}
+
+void RKWardMainWindow::slotOpenOutput() {
+	RK_TRACE (APP);
+
+	QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Select RKWard Output file to open..."), RKSettingsModuleGeneral::lastUsedUrlFor("output"), i18n("RKWard Output Files [*.rko](*.rko);;All files [*](*)"));
+	RKWorkplace::mainWorkplace()->openOutputWindow(url);
+}
+
 void RKWardMainWindow::slotOpenCommandEditor (const QUrl &url, const QString &encoding) {
 	RK_TRACE (APP);
 
diff --git a/rkward/rkward.h b/rkward/rkward.h
index dd478e6c..9a437a6a 100644
--- a/rkward/rkward.h
+++ b/rkward/rkward.h
@@ -124,6 +124,11 @@ public slots:
 /** open a new command editor (load given url) */
 	void slotOpenCommandEditor (const QUrl &url, const QString& encoding = QString ());
 
+/** create and show a new output window */
+	void slotNewOutput();
+/** load an output Window (ask for file to open) */
+	void slotOpenOutput();
+
 /** close current window (Windows->Close). */
 	void slotCloseWindow ();
 /** close all windows (Windows->Close All) */
@@ -148,9 +153,10 @@ private:
 	KParts::PartManager *part_manager;
 
 	// QAction pointers to enable/disable actions
-	QAction* fileOpen;
+	QAction* fileOpenScript;
+	QAction* fileOpenOutput;
 	KRecentFilesAction* fileOpenRecent;
-	
+
 	QAction* fileOpenWorkspace;
 	KRecentFilesAction* fileOpenRecentWorkspace;
 	QAction* fileSaveWorkspace;
@@ -160,6 +166,7 @@ private:
 	QAction* close_all_editors;
 	QAction* new_data_frame;
 	QAction* new_command_editor;
+	QAction* new_output;
 
 	QAction* window_close_all;
 	QAction* window_detach;
diff --git a/rkward/rkwardui.rc b/rkward/rkwardui.rc
index 41eec43d..039899a3 100644
--- a/rkward/rkwardui.rc
+++ b/rkward/rkwardui.rc
@@ -1,5 +1,5 @@
 <!DOCTYPE kpartgui>
-<kpartgui name="rkward_main" version="721">
+<kpartgui name="rkward_main" version="722">
 <MenuBar>
 	<!-- The Main Window ui.rc is the only one, where merging happens, reliably. That is, why we need to define
 	     a lot of merge points, here, which can then be used be mdi windows and their children.
@@ -8,9 +8,13 @@
 		<Menu name="new_data"><text>&New</text>
 			<Action name="new_data_frame"/>
 			<Action name="new_command_editor"/>
+			<Action name="new_output"/>
 		</Menu>
-		<Action name="file_openy"/>
 		<Action name="file_open_any"/>
+		<Menu name="open_specific"><text>&Open by Type</text>
+			<Action name="file_open_script"/>
+			<Action name="file_open_output"/>
+		</Menu>
 		<Action name="file_open_recenty"/>
 		<Separator/>
 		<Menu name="import"><text>&Import</text>
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index b9a53229..01a4286a 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -709,12 +709,14 @@ void RKHTMLWindow::updateCaption (const QUrl &url) {
 			QString mods;
 			if (dir->isActive()) mods.append(i18n("[Active]"));
 			// TODO: use icon(s), instead
-			if (dir->isModifiedFast()) mods.append("(*)");
+			bool mod = dir->isModifiedFast();
+			if (mod) mods.append("(*)");
 			if (!mods.isEmpty()) {
 				name.append(' ');
 				name.append(mods);
 			}
 			setCaption(name);
+			part->revert->setEnabled(mod);
 		} else {
 			setCaption (i18n ("Output %1", url.fileName ()));
 		}
@@ -1384,7 +1386,14 @@ void RKOutputWindowManager::fileChanged (const QString &path) {
 		if (w->outputDirectory()) w->outputDirectory()->setKnownModified(true);
 	} else {
 		RK_ASSERT (path == current_default_path);
-		if (RKSettingsModuleOutput::autoShow ()) RKWorkplace::mainWorkplace ()->openOutputWindow (QUrl::fromUserInput (path, QString (), QUrl::AssumeLocalFile));
+		if (RKSettingsModuleOutput::autoShow ()) {
+			RKOutputDirectory* dir = RKOutputDirectory::findOutputByWorkPath(path);
+			if (dir) {
+				dir->view(true);
+			} else {
+				RKWorkplace::mainWorkplace()->openHTMLWindow(QUrl::fromUserInput(path, QString(), QUrl::AssumeLocalFile));
+			}
+		}
 	}
 }
 
diff --git a/rkward/windows/rkworkplace.cpp b/rkward/windows/rkworkplace.cpp
index 54ae864c..24e1ca21 100644
--- a/rkward/windows/rkworkplace.cpp
+++ b/rkward/windows/rkworkplace.cpp
@@ -430,14 +430,8 @@ bool RKWorkplace::openAnyUrl (const QUrl &url, const QString &known_mimetype, bo
 			return true;	// TODO
 		}
 		if (lname.endsWith(".rko")) {
-			auto ret = RKOutputDirectory::get(url.toLocalFile(), false);
-			if (!ret.failed()) {
-				ret.dir()->view(true);
-				return true;
-			} else {
-				KMessageBox::sorry(this, i18n("Failed to open output file. Error message was '%1'", ret.error));
-				return false;
-			}
+			auto win = openOutputWindow(url, false);
+			return win != nullptr;
 		}
 		if (mimetype.inherits ("text/plain")) {
 			return (openScriptEditor (url, QString ()));
@@ -518,23 +512,33 @@ RKMDIWindow* RKWorkplace::openHelpWindow (const QUrl &url, bool only_once) {
 	return (hw);
 }
 
-RKMDIWindow* RKWorkplace::openOutputWindow(const QUrl &url) {
+RKMDIWindow* RKWorkplace::openHTMLWindow(const QUrl &url) {
 	RK_TRACE (APP);
 
-	QString path = url.toLocalFile();
-	if (path.isNull()) path = RKOutputWindowManager::self()->currentOutputPath();
+	// special treatment, for now
+	return openHelpWindow(url, true);
+}
 
-	QList<RKHTMLWindow*> owins = RKOutputWindowManager::self()->existingOutputWindows(url.toLocalFile());
-	for (int i = 0; i < owins.size (); ++i) {
-		if (view()->windowInActivePane(owins[i])) {
-			owins[i]->activate();
-			return owins[i];
-		}
+RKMDIWindow* RKWorkplace::openOutputWindow(const QUrl &url, bool create) {
+	RK_TRACE (APP);
+
+	if (create) RK_ASSERT(url.isEmpty());
+
+	RKOutputDirectoryCallResult res = RKOutputDirectory::get(url.toLocalFile(), create);
+	if (res.failed()) {
+		KMessageBox::sorry(this, i18n("Failed to open output file. Error message was '%1'", res.error));
+		return nullptr;
 	}
+	RK_ASSERT(res.dir());
+	return res.dir()->getOrCreateView(true); // Will call openNewOutputWindow(), unless a view alredy exists
+}
+
+RKMDIWindow* RKWorkplace::openNewOutputWindow(RKOutputDirectory* dir) {
+	RK_TRACE (APP);
 
 	RKHTMLWindow* win = new RKHTMLWindow(view(), RKHTMLWindow::HTMLOutputWindow);
-	win->openURL(QUrl::fromLocalFile(path));
-	RK_ASSERT(win->url().toLocalFile() == path);
+	win->openURL(QUrl::fromLocalFile(dir->workPath()));
+	RK_ASSERT(win->url().toLocalFile() == dir->workPath());
 	addWindow(win);
 	return win;
 }
diff --git a/rkward/windows/rkworkplace.h b/rkward/windows/rkworkplace.h
index 40d616c6..4d65e8ea 100644
--- a/rkward/windows/rkworkplace.h
+++ b/rkward/windows/rkworkplace.h
@@ -41,6 +41,7 @@ class RKMDIWindowHistoryWidget;
 class RKGraphicsDevice;
 class KMessageWidget;
 class QWindow;
+class RKOutputDirectory;
 
 #define TOOL_WINDOW_BAR_COUNT 4
 
@@ -131,8 +132,14 @@ public:
 @param only_once if true, checks whether any help window already shows this URL. If so, raise it, but do not open a new window. Else show the new window */
 	RKMDIWindow* openHelpWindow (const QUrl &url=QUrl (), bool only_once=false);
 /** Opens a new output window, or raise / refresh the current output window.
- at param url currently ignored! */
-	RKMDIWindow* openOutputWindow (const QUrl &url=QUrl ());
+ at param url Ouput file to load. If empty, the active output is opened/raised.
+ at param create Create a new output, instead of showing an existing one. */
+	RKMDIWindow* openOutputWindow(const QUrl &url, bool create=false);
+/** Opens a new output window, for the given existing output directory. Generally, you should call RKOutputDirectory::view(), instead. */
+	RKMDIWindow* openNewOutputWindow(RKOutputDirectory* dir);
+/** Opens an HTML window / legacy output file
+ at param url Ouput file to load. */
+	RKMDIWindow* openHTMLWindow(const QUrl &url);
 
 	void newX11Window (QWindow* window_to_embed, int device_number);
 	void newRKWardGraphisWindow (RKGraphicsDevice *dev, int device_number);


More information about the rkward-tracker mailing list