[education/rkward] /: Add ability to display PDF windows inline

Thomas Friedrichsmeier null at kde.org
Mon Jun 24 22:13:02 BST 2024


Git commit 8583aeda4a02bb92a94bfb9c575ebb51d99abc50 by Thomas Friedrichsmeier.
Committed on 19/06/2024 at 15:06.
Pushed by tfry into branch 'master'.

Add ability to display PDF windows inline

M  +1    -0    ChangeLog
M  +3    -8    rkward/agents/rkprintagent.cpp
M  +2    -0    rkward/misc/rkstandardicons.cpp
M  +1    -0    rkward/misc/rkstandardicons.h
M  +3    -0    rkward/rbackend/rkrinterface.cpp
M  +1    -0    rkward/rbackend/rpackages/rkward/NAMESPACE
M  +10   -1    rkward/rbackend/rpackages/rkward/R/rk.edit-functions.R
M  +1    -0    rkward/windows/CMakeLists.txt
M  +2    -1    rkward/windows/rkmdiwindow.h
A  +60   -0    rkward/windows/rkpdfwindow.cpp     [License: GPL(v2.0+)]
A  +27   -0    rkward/windows/rkpdfwindow.h     [License: GPL(v2.0+)]
M  +32   -15   rkward/windows/rkworkplace.cpp
M  +4    -1    rkward/windows/rkworkplace.h

https://invent.kde.org/education/rkward/-/commit/8583aeda4a02bb92a94bfb9c575ebb51d99abc50

diff --git a/ChangeLog b/ChangeLog
index 57c8ccd2f..f185abb9c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,4 @@
+- Added: Ability to open PDF documents inside the RKWard window, and associated R function rk.show.pdf()
 - Fixed: Add breeze icons as a fallback on systems that use a different icon theme (fixes missing icons)
 - Added: Setup dialog includes setting to select a different R installation
 - Added: Command line option "--setup" to force (re-)initialization as if a new version of RKWard had been installed
diff --git a/rkward/agents/rkprintagent.cpp b/rkward/agents/rkprintagent.cpp
index 81dec957c..683060ec4 100644
--- a/rkward/agents/rkprintagent.cpp
+++ b/rkward/agents/rkprintagent.cpp
@@ -20,12 +20,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <KPluginFactory>
 #include <kcoreaddons_version.h>
 
+#include "../windows/rkpdfwindow.h"
 #include "../rkward.h"
 
 #include "../debug.h"
 
-#define OKULAR_LIBRARY_NAME "kf6/parts/okularpart"
-
 RKPrintAgent::RKPrintAgent(const QString &file, KParts::ReadOnlyPart *provider, bool delete_file) : QObject(), file(file), provider(provider), delete_file(delete_file) {
 	RK_TRACE (APP);
 	//provider->widget()->show(); // not very helpful as a preview, unfortunately
@@ -51,17 +50,13 @@ void fallbackToGeneric(const QString &file, bool delete_file) {
 void RKPrintAgent::printPostscript (const QString &file, bool delete_file) {
 	RK_TRACE (APP)
 
-	const KPluginMetaData okularPart(QStringLiteral(OKULAR_LIBRARY_NAME));
-	const QVariantList args {"ViewerWidget"};
-	auto result = KPluginFactory::instantiatePlugin<KParts::ReadOnlyPart>(okularPart, nullptr, args);
-
-	if(!result) {
+	auto provider = RKPDFWindow::getOkularPart({"ViewerWidget"});
+	if(!provider) {
 		RK_DEBUG(APP, DL_WARNING, "No valid postscript provider was found");
 		fallbackToGeneric(file, delete_file);
 		return;
 	}
 
-	auto provider = result.plugin;
 	QAction *printaction = provider->action("print");
 	if (!printaction) printaction = provider->action("file_print");
 	if (!printaction) {
diff --git a/rkward/misc/rkstandardicons.cpp b/rkward/misc/rkstandardicons.cpp
index a63fd3d23..e1fa82a92 100644
--- a/rkward/misc/rkstandardicons.cpp
+++ b/rkward/misc/rkstandardicons.cpp
@@ -116,6 +116,7 @@ void RKStandardIcons::doInitIcons () {
 	icons[WindowFileBrowser] = QIcon::fromTheme("folder");
 	icons[WindowDebugConsole] = QIcon::fromTheme("view-process-system");
 	icons[WindowCallstackViewer] = QIcon::fromTheme("view-sort-ascending");
+	icons[WindowPDF] = QIcon::fromTheme("application-pdf");
 
 	// TODO: We really want an hourglass symbol, or similar, here.
 	icons[StatusWaitingUpdating] = QIcon::fromTheme ("system-search");
@@ -181,6 +182,7 @@ QIcon RKStandardIcons::iconForWindow (const RKMDIWindow* window) {
 	if (window->isType (RKMDIWindow::FileBrowserWindow)) return getIcon (WindowFileBrowser);
 	if (window->isType (RKMDIWindow::DebugConsoleWindow)) return getIcon (WindowDebugConsole);
 	if (window->isType (RKMDIWindow::CallstackViewerWindow)) return getIcon (WindowCallstackViewer);
+	if (window->isType(RKMDIWindow::PDFWindow)) return getIcon(WindowPDF);
 	if (window->isType (RKMDIWindow::DebugMessageWindow)) return QIcon();
 
 	RK_ASSERT (false);
diff --git a/rkward/misc/rkstandardicons.h b/rkward/misc/rkstandardicons.h
index 0c7078018..a685b832c 100644
--- a/rkward/misc/rkstandardicons.h
+++ b/rkward/misc/rkstandardicons.h
@@ -102,6 +102,7 @@ public:
 		WindowFileBrowser,
 		WindowDebugConsole,
 		WindowCallstackViewer,
+		WindowPDF,
 
 		StatusWaitingUpdating,
 
diff --git a/rkward/rbackend/rkrinterface.cpp b/rkward/rbackend/rkrinterface.cpp
index 3127d973d..12e6e042b 100644
--- a/rkward/rbackend/rkrinterface.cpp
+++ b/rkward/rbackend/rkrinterface.cpp
@@ -698,6 +698,9 @@ GenericRRequestResult RInterface::processRCallRequest (const QString &call, cons
 	} else if (call == "showHTML") {
 		RK_ASSERT(arglist.size() == 1);
 		RKWorkplace::mainWorkplace()->openHelpWindow(QUrl::fromUserInput(arglist.value(0), QDir::currentPath (), QUrl::AssumeLocalFile));
+	} else if (call == "showPDF") {
+		RK_ASSERT(arglist.size() == 1);
+		RKWorkplace::mainWorkplace()->openPDFWindow(QUrl::fromUserInput(arglist.value(0), QDir::currentPath (), QUrl::AssumeLocalFile));
 	} else if (call == "select.list") {
 		QString title = arglist.value(0);
 		bool multiple = (arglist.value(1) == "multi");
diff --git a/rkward/rbackend/rpackages/rkward/NAMESPACE b/rkward/rbackend/rpackages/rkward/NAMESPACE
index f8ff57bbc..96e3063a7 100644
--- a/rkward/rbackend/rpackages/rkward/NAMESPACE
+++ b/rkward/rbackend/rpackages/rkward/NAMESPACE
@@ -112,6 +112,7 @@ export(rk.set.plugin.status)
 export(rk.show.files)
 export(rk.show.html)
 export(rk.show.message)
+export(rk.show.pdf)
 export(rk.show.plot.info)
 export(rk.show.question)
 export(rk.switch.frontend.language)
diff --git a/rkward/rbackend/rpackages/rkward/R/rk.edit-functions.R b/rkward/rbackend/rpackages/rkward/R/rk.edit-functions.R
index a77023bd3..3e382385a 100644
--- a/rkward/rbackend/rpackages/rkward/R/rk.edit-functions.R
+++ b/rkward/rbackend/rpackages/rkward/R/rk.edit-functions.R
@@ -16,7 +16,9 @@
 #' and \link{browseURL}, instead. These will call the respective RKWard functions
 #' by default, when run inside an RKWard session. (via \code{getOption("editor")},
 #' and \code{getOption("browser")}.
-#' 
+#'
+#' \code{rk.show.pdf} opens a PDF (or postscript) document in a viewer window inside RKWard.
+#'
 #' @param x an object to edit.
 #' @aliases rk.edit rk.edit.files rk.show.files rk.show.html
 #' @return All functions described on this page return \code{NULL},
@@ -96,3 +98,10 @@
 "rk.show.html" <- function (url) {
 	.rk.call.async("showHTML", as.character(url));
 }
+
+#' @param url a URL to show.
+#' @export
+#' @rdname rk.edit
+"rk.show.pdf" <- function(url) {
+	.rk.call.async("showPDF", as.character(url));
+}
diff --git a/rkward/windows/CMakeLists.txt b/rkward/windows/CMakeLists.txt
index be6928e1f..88195c0ea 100644
--- a/rkward/windows/CMakeLists.txt
+++ b/rkward/windows/CMakeLists.txt
@@ -11,6 +11,7 @@ SET(windows_STAT_SRCS
 	rkdebugconsole.cpp
 	rkcallstackviewer.cpp
 	rkhtmlwindow.cpp
+	rkpdfwindow.cpp
 	rcontrolwindow.cpp
 	detachedwindowcontainer.cpp
 	rkmdiwindow.cpp
diff --git a/rkward/windows/rkmdiwindow.h b/rkward/windows/rkmdiwindow.h
index 79ac4f56b..9c730232f 100644
--- a/rkward/windows/rkmdiwindow.h
+++ b/rkward/windows/rkmdiwindow.h
@@ -41,6 +41,7 @@ public:
 		HelpWindow=1 << 3,
 		X11Window=1 << 4,
 		ObjectWindow=1 << 5,
+		PDFWindow=1 << 6,
 		ConsoleWindow=1 << 10,
 		CommandLogWindow=1 << 11,
 		WorkspaceBrowserWindow=1 << 12,
@@ -82,7 +83,7 @@ public:
 /** @returns A short caption (e.g. only the filename without the path). Default implementation simply calls QWidget::caption () */
 	virtual QString shortCaption ();
 /** @returns The corresponding KPart for this window */
-	KParts::Part *getPart () { return part; };
+	KParts::Part *getPart() const { return part; };
 /** Is this window attached (or detached)?
 @returns true if attached, false if detached */
 	bool isAttached () const { return (state == Attached); };
diff --git a/rkward/windows/rkpdfwindow.cpp b/rkward/windows/rkpdfwindow.cpp
new file mode 100644
index 000000000..9244c7700
--- /dev/null
+++ b/rkward/windows/rkpdfwindow.cpp
@@ -0,0 +1,60 @@
+/*
+rkpdfwindow - This file is part of the RKWard project. Created: Tue Jun 18 2024
+SPDX-FileCopyrightText: 2024 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
+SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "rkpdfwindow.h"
+
+#include <QVBoxLayout>
+#include <QLabel>
+
+#include <KLocalizedString>
+#include <KParts/PartLoader>
+
+#include "../misc/rkdummypart.h"
+
+#include "../debug.h"
+
+#define OKULAR_LIBRARY_NAME "kf6/parts/okularpart"
+
+RKPDFWindow::RKPDFWindow(QWidget *parent) : RKMDIWindow(parent, RKMDIWindow::PDFWindow), valid(false) {
+	RK_TRACE(APP);
+
+	auto l = new QVBoxLayout(this);
+	// Try loading okular part first, then fall back to any other pdf part
+	auto pdfpart = getOkularPart();
+	if (!pdfpart) {
+		pdfpart = KParts::PartLoader::instantiatePartForMimeType<KParts::ReadOnlyPart>("application/pdf").plugin;
+	}
+	if (pdfpart) {
+		setPart(pdfpart);
+		valid = true;
+	} else {
+		setPart(new RKDummyPart(nullptr, new QLabel(i18n("No PDF viewer component found"))));
+	}
+	l->addWidget(getPart()->widget());
+}
+
+RKPDFWindow::~RKPDFWindow() {
+	RK_TRACE(APP);
+}
+
+void RKPDFWindow::openURL(const QUrl &url) {
+	RK_TRACE(APP);
+	if (valid) static_cast<KParts::ReadOnlyPart*>(getPart())->openUrl(url);
+	setWindowTitle(url.fileName());
+}
+
+QUrl RKPDFWindow::url() const {
+	if (valid) return static_cast<KParts::ReadOnlyPart*>(getPart())->url();
+	return QUrl();
+}
+
+KParts::ReadOnlyPart* RKPDFWindow::getOkularPart(const QVariantList &args) {
+	RK_TRACE(APP);
+	const KPluginMetaData okularPart(QStringLiteral(OKULAR_LIBRARY_NAME));
+	auto result = KPluginFactory::instantiatePlugin<KParts::ReadOnlyPart>(okularPart, nullptr, args);
+	return result.plugin; // may be nullptr
+}
diff --git a/rkward/windows/rkpdfwindow.h b/rkward/windows/rkpdfwindow.h
new file mode 100644
index 000000000..2d50930d2
--- /dev/null
+++ b/rkward/windows/rkpdfwindow.h
@@ -0,0 +1,27 @@
+/*
+rkpdfwindow - This file is part of the RKWard project. Created: Tue Jun 18 2024
+SPDX-FileCopyrightText: 2024 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
+SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef RKPDFWINDOW_H
+#define RKPDFWINDOW_H
+
+#include "rkmdiwindow.h"
+
+#include <KParts/ReadOnlyPart>
+
+class RKPDFWindow : public RKMDIWindow {
+	Q_OBJECT
+public:
+	RKPDFWindow(QWidget *parent);
+	~RKPDFWindow();
+	void openURL(const QUrl &url);
+	QUrl url() const;
+	static KParts::ReadOnlyPart* getOkularPart(const QVariantList &args=QVariantList());
+private:
+	bool valid;
+};
+
+#endif
diff --git a/rkward/windows/rkworkplace.cpp b/rkward/windows/rkworkplace.cpp
index b3716c6c0..d93ece5be 100644
--- a/rkward/windows/rkworkplace.cpp
+++ b/rkward/windows/rkworkplace.cpp
@@ -29,6 +29,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include "detachedwindowcontainer.h"
 #include "rkcommandeditorwindow.h"
 #include "rkhtmlwindow.h"
+#include "rkpdfwindow.h"
 #include "rkworkplaceview.h"
 #include "rktoolwindowbar.h"
 #include "rktoolwindowlist.h"
@@ -369,12 +370,14 @@ void RKWorkplace::registerNamedWindow (const QString& id, QObject* owner, QWidge
 	if (window) connect (window, &QObject::destroyed, this, &RKWorkplace::namedWindowOwnerDestroyed);
 }
 
-RKMDIWindow* RKWorkplace::getNamedWindow (const QString& id) {
+template<typename T> T* RKWorkplace::getNamedWindow(const QString& id) {
 	RK_TRACE (APP);
 
+	if (id.isEmpty()) return nullptr;
+
 	for (int i = 0; i < named_windows.size (); ++i) {
 		if (named_windows[i].id == id) {
-			return named_windows[i].window;
+			return dynamic_cast<T*>(named_windows[i].window);
 		}
 	}
 
@@ -433,6 +436,9 @@ bool RKWorkplace::openAnyUrl (const QUrl &url, const QString &known_mimetype, bo
 		if (mimetype.inherits ("text/html")) {
 			openHelpWindow (url, true);
 			return true;	// TODO
+		} else if (mimetype.inherits("application/pdf")) {
+			openPDFWindow(url);
+			return true;
 		}
 		QString lname = url.fileName().toLower();
 		if (lname.endsWith(QLatin1String(".rdata")) || lname.endsWith(QLatin1String(".rda"))) {
@@ -481,6 +487,20 @@ RKMDIWindow* RKWorkplace::openScriptEditor (const QUrl &url, const QString& enco
 	return (editor);
 }
 
+RKMDIWindow* RKWorkplace::openPDFWindow(const QUrl &url) {
+	RK_TRACE(APP);
+	auto pw = getNamedWindow<RKPDFWindow>(window_name_override);
+	// TODO: also match by url?
+	if (pw) {
+		pw->openURL(url);
+	} else {
+		pw = new RKPDFWindow(view());
+		pw->openURL(url);  // needs to be before addwindow, or title won't show correctly
+		addWindow(pw);
+	}
+	return pw;
+}
+
 RKMDIWindow* RKWorkplace::openHelpWindow (const QUrl &url, bool only_once) {
 	RK_TRACE (APP);
 
@@ -501,21 +521,13 @@ RKMDIWindow* RKWorkplace::openHelpWindow (const QUrl &url, bool only_once) {
 		}
 	}
 	// if we're working with a window hint, try to _reuse_ the existing window, even if it did not get found, above
-	if (!window_name_override.isEmpty ()) {
-		for (int i = 0; i < named_windows.size (); ++i) {
-			if (named_windows[i].id == window_name_override) {
-				RKHTMLWindow *w = dynamic_cast<RKHTMLWindow*> (named_windows[i].window);
-				if (w) {
-					w->openURL (url);
-//					w->activate ();   // HACK: Keep preview windows from stealing focus
-					return w;
-				}
-				break;
-			}
-		}
+	auto w = getNamedWindow<RKHTMLWindow>(window_name_override);
+	if (w) {
+		w->openURL (url);
+//		w->activate ();   // HACK: Keep preview windows from stealing focus
+		return w;
 	}
 
-
 	RKHTMLWindow *hw = new RKHTMLWindow (view (), RKHTMLWindow::HTMLHelpWindow);
 	hw->openURL (url);
 	addWindow (hw);
@@ -853,6 +865,9 @@ QString RKWorkplace::makeItemDescription (RKMDIWindow *win) const {
 	} else if (win->isType (RKMDIWindow::HelpWindow)) {
 		type = "help";
 		specification = static_cast<RKHTMLWindow*> (win)->restorableUrl ().url ();
+	} else if (win->isType(RKMDIWindow::PDFWindow)) {
+		type = "pdf";
+		specification = static_cast<RKPDFWindow*>(win)->url().url();
 	} else if (win->isToolWindow ()) {
 		type = RKToolWindowList::idOfWindow (win);
 	} else if (win->isType (RKMDIWindow::ObjectWindow)) {
@@ -930,6 +945,8 @@ RKMDIWindow* restoreDocumentWindowInternal (RKWorkplace* wp, const ItemSpecifica
 		win = RKWorkplace::mainWorkplace()->openOutputWindow(QUrl::fromLocalFile(dir->workPath()));
 	} else if (spec.type == "help") {
 		win = wp->openHelpWindow (checkAdjustRestoredUrl (spec.specification, base), true);
+	} else if (spec.type == "pdf") {
+		win = wp->openPDFWindow(checkAdjustRestoredUrl(spec.specification, base));
 	} else if (spec.type == "object") {
 		RObject *object = RObjectList::getObjectList ()->findObject (spec.specification);
 		if (object) win = wp->newObjectViewer (object);
diff --git a/rkward/windows/rkworkplace.h b/rkward/windows/rkworkplace.h
index 0ef04f76a..77e9c019b 100644
--- a/rkward/windows/rkworkplace.h
+++ b/rkward/windows/rkworkplace.h
@@ -133,6 +133,9 @@ public:
 /** Opens an HTML window / legacy output file
 @param url Ouput file to load. */
 	RKMDIWindow* openHTMLWindow(const QUrl &url);
+/** Opens a PDF viewer window
+ at param url Ouput file to load. */
+	RKMDIWindow* openPDFWindow(const QUrl &url);
 
 	void newX11Window (QWindow* window_to_embed, int device_number);
 	void newRKWardGraphisWindow (RKGraphicsDevice *dev, int device_number);
@@ -201,7 +204,7 @@ Has no effect, if RKSettingsModuleGeneral::workplaceSaveMode () != RKSettingsMod
 /** Register a named area where to place MDI windows. For directing preview windows to a specific location. */
 	void registerNamedWindow (const QString& id, QObject *owner, QWidget* parent, RKMDIWindow *window=nullptr);
 /** Return the window in the specified named area (can be 0). */
-	RKMDIWindow *getNamedWindow (const QString& id);
+	template<typename T=RKMDIWindow> T* getNamedWindow (const QString& id);
 /** Make the next window to be created appear in a specific location (can be a named window). 
  *  @note It is the caller's responsibility to clear the override (by calling setWindowPlacementOverride ()) after the window in question has been created. */
 	void setWindowPlacementOverrides (const QString& placement=QString (), const QString& name=QString (), const QString& style=QString ());



More information about the rkward-tracker mailing list