[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