[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