[education/rkward] rkward/windows: Convert preview modes to an abstracted list, for easier additions
Thomas Friedrichsmeier
null at kde.org
Mon Jun 24 22:13:02 BST 2024
Git commit e3d96896160ffba47afd7cfb02a50b4452f44ede by Thomas Friedrichsmeier.
Committed on 21/06/2024 at 16:21.
Pushed by tfry into branch 'master'.
Convert preview modes to an abstracted list, for easier additions
M +127 -98 rkward/windows/rkcommandeditorwindow.cpp
M +12 -8 rkward/windows/rkcommandeditorwindow.h
https://invent.kde.org/education/rkward/-/commit/e3d96896160ffba47afd7cfb02a50b4452f44ede
diff --git a/rkward/windows/rkcommandeditorwindow.cpp b/rkward/windows/rkcommandeditorwindow.cpp
index b035ac19a..1564d59ab 100644
--- a/rkward/windows/rkcommandeditorwindow.cpp
+++ b/rkward/windows/rkcommandeditorwindow.cpp
@@ -370,27 +370,25 @@ void RKCommandEditorWindow::initializeActions (KActionCollection* ac) {
action_setwd_to_script->setToolTip (action_setwd_to_script->statusTip ());
action_setwd_to_script->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionCDToScript));
- KActionMenu* actionmenu_preview = new KActionMenu (QIcon::fromTheme ("view-preview"), i18n ("Preview"), this);
+ KActionMenu* actionmenu_preview = new KActionMenu(QIcon::fromTheme("view-preview"), i18n("Preview"), this);
actionmenu_preview->setPopupMode(QToolButton::InstantPopup);
- preview_modes = new QActionGroup (this);
- actionmenu_preview->addAction (action_no_preview = new QAction (RKStandardIcons::getIcon (RKStandardIcons::ActionDelete), i18n ("No preview"), preview_modes));
- actionmenu_preview->addAction (new QAction (QIcon::fromTheme ("preview_math"), i18n ("R Markdown"), preview_modes));
- actionmenu_preview->addAction (new QAction (RKStandardIcons::getIcon (RKStandardIcons::WindowOutput), i18n ("RKWard Output"), preview_modes));
- actionmenu_preview->addAction (new QAction (RKStandardIcons::getIcon (RKStandardIcons::WindowX11), i18n ("Plot"), preview_modes));
- actionmenu_preview->addAction (new QAction (RKStandardIcons::getIcon (RKStandardIcons::WindowConsole), i18n ("R Console"), preview_modes));
- QList<QAction*> preview_actions = preview_modes->actions ();
- preview_actions[NoPreview]->setToolTip (i18n ("Disable preview"));
- preview_actions[RMarkdownPreview]->setToolTip (i18n ("Preview the script as rendered from RMarkdown format (appropriate for .Rmd files)."));
- preview_actions[ConsolePreview]->setToolTip (i18n ("Preview the script as if it was run in the interactive R Console"));
- preview_actions[GraphPreview]->setToolTip (i18n ("Preview any onscreen graphics produced by running this script. This preview will be empty, if there is no call to <i>plot()</i> or other graphics commands."));
- preview_actions[OutputWindow]->setToolTip (i18n ("Preview any output to the RKWard Output Window. This preview will be empty, if there is no call to <i>rk.print()</i> or other RKWard output commands."));
- for (int i = 0; i < preview_actions.size (); ++i) {
- preview_actions[i]->setCheckable (true);
- preview_actions[i]->setWhatsThis(preview_actions[i]->toolTip ());
+ preview_modes = new QActionGroup(this);
+ action_no_preview = preview_modes->addAction(RKStandardIcons::getIcon(RKStandardIcons::ActionDelete), i18n("No preview"));
+ action_no_preview->setToolTip(i18n("Disable preview"));
+ action_no_preview->setCheckable(true);
+ action_no_preview->setChecked(true);
+ actionmenu_preview->addAction(action_no_preview);
+ initPreviewModes();
+ for (int i = 0; i < preview_mode_list.size(); ++i) {
+ const PreviewMode &mode = preview_mode_list[i];
+ QAction *a = preview_modes->addAction(mode.icon, mode.actionlabel);
+ a->setWhatsThis(mode.tooltip);
+ a->setToolTip(mode.tooltip);
+ a->setCheckable(true);
+ actionmenu_preview->addAction(a);
}
- action_no_preview->setChecked (true);
- connect (preview, &RKXMLGUIPreviewArea::previewClosed, this, &RKCommandEditorWindow::discardPreview);
- connect (preview_modes, &QActionGroup::triggered, this, &RKCommandEditorWindow::changePreviewMode);
+ connect(preview, &RKXMLGUIPreviewArea::previewClosed, this, &RKCommandEditorWindow::discardPreview);
+ connect(preview_modes, &QActionGroup::triggered, this, &RKCommandEditorWindow::changePreviewMode);
actionmenu_preview->addSeparator ();
action_preview_as_you_type = new QAction (QIcon::fromTheme ("input-keyboard"), i18nc ("Checkable action: the preview gets updated while typing", "Update as you type"), ac);
@@ -406,6 +404,94 @@ void RKCommandEditorWindow::initializeActions (KActionCollection* ac) {
if (file_save_as_action) file_save_as_action->setText (i18n ("Save Script As..."));
}
+QString withCheckForPandoc(const QString &command, const QString &error_file) {
+ const QString header = RObject::rQuote(i18n("Pandoc is not installed"));
+ const QString message = RObject::rQuote(i18n("The software <tt>pandoc</tt>, required to rendering R markdown files, is not installed, or not in the system path of "
+ "the running R session. You will need to install pandoc from <a href=\"https://pandoc.org/\">https://pandoc.org/</a>.</br>"
+ "If it is installed, but cannot be found, try adding it to the system path of the running R session at "
+ "<a href=\"rkward://settings/rbackend\">Settings->Configure RKward->R-backend</a>."));
+ QString ret(
+ "if (!nzchar(Sys.which(\"pandoc\"))) {\n"
+ " output <- rk.set.output.html.file(%1, silent=TRUE)\n"
+ " rk.header(%3)\n"
+ " rk.print(%4)\n"
+ " rk.set.output.html.file(output, silent=TRUE)\n"
+ " rk.show.html(%1)\n"
+ "} else {\n"
+ "%2\n"
+ "}\n"
+ );
+ return ret.arg(error_file, command, header, message);
+}
+
+void RKCommandEditorWindow::initPreviewModes() {
+ RK_TRACE(COMMANDEDITOR);
+ preview_mode_list.append(PreviewMode{
+ QIcon::fromTheme("preview_math"), i18n("R Markdown"), i18n("Preview of rendered R Markdown"),
+ i18n("Preview the script as rendered from RMarkdown format (appropriate for .Rmd files)."),
+ QLatin1String(".Rmd"), QLatin1String(".html"),
+ [](const QString& infile, const QString& outfile, const QString& /*preview_id*/) {
+ auto command = QStringLiteral(
+ " require(rmarkdown)\n"
+ " rmarkdown::render (%1, output_format=\"html_document\", output_file=%2, quiet=TRUE)\n"
+ ).arg(RObject::rQuote(infile), RObject::rQuote(outfile));
+ return withCheckForPandoc(command, outfile + "._nopandoc_.html");
+ }
+ });
+ preview_mode_list.append(PreviewMode{
+ RKStandardIcons::getIcon(RKStandardIcons::WindowOutput), i18n("RKWard Output"), i18n("Preview of generated RKWard output"),
+ i18n("Preview any output to the RKWard Output Window. This preview will be empty, if there is no call to <i>rk.print()</i> or other RKWard output commands."),
+ QLatin1String(".R"), QLatin1String(".html"),
+ [](const QString& infile, const QString& outfile, const QString& /*preview_id*/) {
+ auto command = QLatin1String("output <- rk.set.output.html.file(%2, silent=TRUE)\n"
+ "try(rk.flush.output(ask=FALSE, style=\"preview\", silent=TRUE))\n"
+ "try(source(%1, local=TRUE))\n"
+ "rk.set.output.html.file(output, silent=TRUE)\n"
+ "rk.show.html(%2)\n");
+ return command.arg(RObject::rQuote(infile), RObject::rQuote(outfile));
+ }
+ });
+ preview_mode_list.append(PreviewMode{
+ RKStandardIcons::getIcon(RKStandardIcons::WindowConsole), i18n("R Console"), i18n("Preview of script running in interactive R Console"),
+ i18n("Preview the script as if it was run in the interactive R Console"),
+ QLatin1String(".R"), QLatin1String(".html"),
+ [](const QString& infile, const QString& outfile, const QString& /*preview_id*/) {
+ auto command = QLatin1String("output <- rk.set.output.html.file(%2, silent=TRUE)\n"
+ "on.exit(rk.set.output.html.file(output, silent=TRUE))\n"
+ "try(rk.flush.output(ask=FALSE, style=\"preview\", silent=TRUE))\n"
+ "exprs <- expression(NULL)\n"
+ "rk.capture.output(suppress.messages=TRUE)\n"
+ "try({\n"
+ " exprs <- parse (%1, keep.source=TRUE)\n"
+ "})\n"
+ ".rk.cat.output(rk.end.capture.output(TRUE))\n"
+ "for (i in seq_len(length(exprs))) {\n"
+ " rk.print.code(as.character(attr(exprs, \"srcref\")[[i]]))\n"
+ " rk.capture.output(suppress.messages=TRUE)\n"
+ " try({\n"
+ " withAutoprint(exprs[[i]], evaluated=TRUE, echo=FALSE)\n"
+ " })\n"
+ " .rk.cat.output(rk.end.capture.output(TRUE))\n"
+ "}\n"
+ "rk.set.output.html.file(output, silent=TRUE)\n"
+ "rk.show.html(%2)\n");
+ return command.arg(RObject::rQuote(infile), RObject::rQuote(outfile));
+ }
+ });
+ preview_mode_list.append(PreviewMode{
+ RKStandardIcons::getIcon(RKStandardIcons::WindowX11), i18n("Plot"), i18n("Preview of generated plot"),
+ i18n("Preview any onscreen graphics produced by running this script. This preview will be empty, if there is no call to <i>plot()</i> or other graphics commands."),
+ QLatin1String(".R"), QLatin1String(),
+ [](const QString& infile, const QString& /*outfile*/, const QString& preview_id) {
+ auto command = QLatin1String("olddev <- dev.cur()\n"
+ ".rk.startPreviewDevice(%2)\n"
+ "try(source(%1, local=TRUE, print.eval=TRUE))\n"
+ "if (olddev != 1) dev.set(olddev)\n");
+ return command.arg(RObject::rQuote(infile), RObject::rQuote(preview_id));
+ }
+ });
+}
+
void RKCommandEditorWindow::initBlocks () {
RK_TRACE (COMMANDEDITOR);
RK_ASSERT (block_records.isEmpty ());
@@ -823,17 +909,18 @@ void RKCommandEditorWindow::copyLinesToOutput () {
void RKCommandEditorWindow::doRenderPreview () {
RK_TRACE (COMMANDEDITOR);
- if (action_no_preview->isChecked ()) return;
- if (!preview_manager->needsCommand ()) return;
- int mode = preview_modes->actions ().indexOf (preview_modes->checkedAction ());
+ if (action_no_preview->isChecked()) return;
+ if (!preview_manager->needsCommand()) return;
+ int nmode = preview_modes->actions().indexOf(preview_modes->checkedAction());
+ const PreviewMode &mode = preview_mode_list.value(nmode-1);
if (!preview_dir) {
- preview_dir = new QTemporaryDir ();
+ preview_dir = new QTemporaryDir();
preview_input_file = nullptr;
}
if (preview_input_file) {
// When switching between .Rmd and .R previews, discard input file
- if ((mode == RMarkdownPreview) != (preview_input_file->fileName().endsWith (".Rmd"))) {
+ if (!preview_input_file->fileName().endsWith(mode.input_ext)) {
delete preview_input_file;
preview_input_file = nullptr;
} else {
@@ -841,94 +928,36 @@ void RKCommandEditorWindow::doRenderPreview () {
}
}
if (!preview_input_file) { // NOT an else!
- if (m_doc->url ().isEmpty () || !m_doc->url ().isLocalFile ()) {
- preview_input_file = new QFile (QDir (preview_dir->path()).absoluteFilePath (mode == RMarkdownPreview ? "script.Rmd" : "script.R"));
+ if (m_doc->url().isEmpty() || !m_doc->url().isLocalFile()) {
+ preview_input_file = new QFile(QDir(preview_dir->path()).absoluteFilePath("script" + mode.input_ext));
} else {
// If the file is already saved, save the preview input as a temp file in the same folder.
// esp. .Rmd files might try to include other files by relative path.
QString tempfiletemplate = m_doc->url().adjusted(QUrl::RemoveFilename).toLocalFile();
- tempfiletemplate.append(".tmp_XXXXXX.rkward_preview.R");
- if (mode == RMarkdownPreview) tempfiletemplate.append("md");
+ tempfiletemplate.append(".tmp_XXXXXX.rkward_preview" + mode.input_ext);
preview_input_file = new QTemporaryFile(tempfiletemplate);
}
}
- QString output_file = QDir (preview_dir->path()).absoluteFilePath ("output.html"); // NOTE: not needed by all types of preview
+ QString output_file = QDir(preview_dir->path()).absoluteFilePath("output" + mode.output_ext); // NOTE: not needed by all types of preview
- if (mode != GraphPreview && !preview->findChild<RKMDIWindow*>()) {
+ if (!preview->findChild<RKMDIWindow*>()) {
// (lazily) initialize the preview window with _something_, as an RKMDIWindow is needed to display messages (important, if there is an error during the first preview)
- RInterface::issueCommand (".rk.with.window.hints (rk.show.html(" + RObject::rQuote (output_file) + "), \"\", " + RObject::rQuote (preview_manager->previewId ()) + ", style=\"preview\")", RCommand::App | RCommand::Sync);
+ RInterface::issueCommand(".rk.with.window.hints (rk.show.html(\"_nothing_.html\"), \"\", " + RObject::rQuote(preview_manager->previewId()) + ", style=\"preview\")", RCommand::App | RCommand::Sync);
}
- RK_ASSERT (preview_input_file->open (QIODevice::WriteOnly));
- QTextStream out (preview_input_file);
- out.setEncoding (QStringConverter::Utf8); // make sure that all characters can be saved, without nagging the user
- out << m_doc->text ();
- preview_input_file->close ();
-
- QString command;
- if (mode == RMarkdownPreview) {
- preview->setLabel (i18n ("Preview of rendered R Markdown"));
-
- command = "if (!nzchar(Sys.which(\"pandoc\"))) {\n"
- " output <- rk.set.output.html.file(%2, silent=TRUE)\n"
- " rk.header (" + RObject::rQuote (i18n ("Pandoc is not installed")) + ")\n"
- " rk.print (" + RObject::rQuote (i18n ("The software <tt>pandoc</tt>, required to rendering R markdown files, is not installed, or not in the system path of "
- "the running R session. You will need to install pandoc from <a href=\"https://pandoc.org/\">https://pandoc.org/</a>.</br>"
- "If it is installed, but cannot be found, try adding it to the system path of the running R session at "
- "<a href=\"rkward://settings/rbackend\">Settings->Configure RKward->R-backend</a>.")) + ")\n"
- " rk.set.output.html.file(output, silent=TRUE)\n"
- "} else {\n"
- " require(rmarkdown)\n"
- " rmarkdown::render (%1, output_format=\"html_document\", output_file=%2, quiet=TRUE)\n"
- "}\n"
- "rk.show.html(%2)\n";
- command = command.arg (RObject::rQuote (preview_input_file->fileName ()), RObject::rQuote (output_file));
- } else if (mode == RKOutputPreview) {
- preview->setLabel (i18n ("Preview of generated RKWard output"));
- command = "output <- rk.set.output.html.file(%2, silent=TRUE)\n"
- "try(rk.flush.output(ask=FALSE, style=\"preview\", silent=TRUE))\n"
- "try(source(%1, local=TRUE))\n"
- "rk.set.output.html.file(output, silent=TRUE)\n"
- "rk.show.html(%2)\n";
- command = command.arg (RObject::rQuote (preview_input_file->fileName ()), RObject::rQuote (output_file));
- } else if (mode == GraphPreview) {
- preview->setLabel (i18n ("Preview of generated plot"));
- command = "olddev <- dev.cur()\n"
- ".rk.startPreviewDevice(%2)\n"
- "try(source(%1, local=TRUE, print.eval=TRUE))\n"
- "if (olddev != 1) dev.set(olddev)\n";
- command = command.arg (RObject::rQuote (preview_input_file->fileName ()), RObject::rQuote (preview_manager->previewId ()));
- } else if (mode == ConsolePreview) {
- preview->setLabel (i18n ("Preview of script running in interactive R Console"));
- command = "output <- rk.set.output.html.file(%2, silent=TRUE)\n"
- "on.exit(rk.set.output.html.file(output, silent=TRUE))\n"
- "try(rk.flush.output(ask=FALSE, style=\"preview\", silent=TRUE))\n"
- "exprs <- expression(NULL)\n"
- "rk.capture.output(suppress.messages=TRUE)\n"
- "try({\n"
- " exprs <- parse (%1, keep.source=TRUE)\n"
- "})\n"
- ".rk.cat.output(rk.end.capture.output(TRUE))\n"
- "for (i in seq_len(length(exprs))) {\n"
- " rk.print.code(as.character(attr(exprs, \"srcref\")[[i]]))\n"
- " rk.capture.output(suppress.messages=TRUE)\n"
- " try({\n"
- " withAutoprint(exprs[[i]], evaluated=TRUE, echo=FALSE)\n"
- " })\n"
- " .rk.cat.output(rk.end.capture.output(TRUE))\n"
- "}\n"
- "rk.set.output.html.file(output, silent=TRUE)\n"
- "rk.show.html(%2)\n";
- command = command.arg (RObject::rQuote (preview_input_file->fileName ()), RObject::rQuote (output_file));
- } else {
- RK_ASSERT (false);
- }
+ RK_ASSERT(preview_input_file->open(QIODevice::WriteOnly));
+ QTextStream out(preview_input_file);
+ out.setEncoding(QStringConverter::Utf8); // make sure that all characters can be saved, without nagging the user
+ out << m_doc->text();
+ preview_input_file->close();
- preview->show ();
+ QString command = mode.command(preview_input_file->fileName(), output_file, preview_manager->previewId());
+ preview->setLabel(mode.previewlabel);
+ preview->show();
- RCommand *rcommand = new RCommand (".rk.with.window.hints (local ({\n" + command + QStringLiteral ("}), \"\", ") + RObject::rQuote (preview_manager->previewId ()) + ", style=\"preview\")", RCommand::App);
- preview_manager->setCommand (rcommand);
+ RCommand *rcommand = new RCommand(".rk.with.window.hints(local({\n" + command + QStringLiteral("}), \"\", ") + RObject::rQuote(preview_manager->previewId()) + ", style=\"preview\")", RCommand::App);
+ preview_manager->setCommand(rcommand);
}
void RKCommandEditorWindow::runAll () {
diff --git a/rkward/windows/rkcommandeditorwindow.h b/rkward/windows/rkcommandeditorwindow.h
index 27af46d55..98047bfb9 100644
--- a/rkward/windows/rkcommandeditorwindow.h
+++ b/rkward/windows/rkcommandeditorwindow.h
@@ -190,14 +190,6 @@ private:
QAction* action_no_preview;
QAction* action_preview_as_you_type;
- enum PreviewMode {
- NoPreview,
- RMarkdownPreview,
- RKOutputPreview,
- GraphPreview,
- ConsolePreview,
- };
-
QAction* action_setwd_to_script;
QUrl previous_autosave_url;
@@ -209,6 +201,18 @@ private:
QString _id;
static QMap<QString, KTextEditor::Document*> unnamed_documents;
+ struct PreviewMode {
+ QIcon icon;
+ QString actionlabel;
+ QString previewlabel;
+ QString tooltip;
+ QString input_ext;
+ QString output_ext;
+ std::function<QString(const QString&, const QString&, const QString&)> command;
+ };
+ QList<PreviewMode> preview_mode_list;
+ void initPreviewModes();
+
RKXMLGUIPreviewArea *preview;
QTimer preview_timer;
RKPreviewManager *preview_manager;
More information about the rkward-tracker
mailing list