[education/rkward] rkward/windows: Implementing initial set of options for preview actions

Thomas Friedrichsmeier null at kde.org
Sun Aug 24 16:08:55 BST 2025


Git commit 8707b6e4e0deb13a226e74cf91fcf6401e24a953 by Thomas Friedrichsmeier.
Committed on 24/08/2025 at 15:06.
Pushed by tfry into branch 'master'.

Implementing initial set of options for preview actions

M  +82   -37   rkward/windows/rkcommandeditorwindow.cpp
M  +1    -1    rkward/windows/rkcommandeditorwindow.h

https://invent.kde.org/education/rkward/-/commit/8707b6e4e0deb13a226e74cf91fcf6401e24a953

diff --git a/rkward/windows/rkcommandeditorwindow.cpp b/rkward/windows/rkcommandeditorwindow.cpp
index a7078fd6f..7d3f45e41 100644
--- a/rkward/windows/rkcommandeditorwindow.cpp
+++ b/rkward/windows/rkcommandeditorwindow.cpp
@@ -373,23 +373,11 @@ void RKCommandEditorWindow::initializeActions(KActionCollection *ac) {
 	KActionMenu *actionmenu_preview = new KActionMenu(QIcon::fromTheme(QStringLiteral("view-preview")), i18n("Preview"), this);
 	actionmenu_preview->setPopupMode(QToolButton::InstantPopup);
 	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"));
-	initPreviewModes();
-	const auto preview_actions = preview_modes->actions();
-	for (auto *a : preview_actions) {
-		actionmenu_preview->addAction(a);
-	}
-	action_no_preview->setChecked(true);
-	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(QStringLiteral("input-keyboard")), i18nc("Checkable action: the preview gets updated while typing", "Update as you type"), ac);
 	action_preview_as_you_type->setToolTip(i18n("When this option is enabled, an update of the preview will be triggered every time you modify the script. When this option is disabled, the preview will be updated whenever you save the script, only."));
 	action_preview_as_you_type->setCheckable(true);
 	action_preview_as_you_type->setChecked(m_doc->url().isEmpty()); // By default, update as you type for unsaved "quick and dirty" scripts, preview on save for saved scripts
-	actionmenu_preview->addAction(action_preview_as_you_type);
+	initPreviewModes(actionmenu_preview);
 	ac->addAction(QStringLiteral("render_preview"), actionmenu_preview);
 
 	file_save_action = findAction(m_view, QStringLiteral("file_save"));
@@ -571,6 +559,11 @@ class RKPreviewMode : public QAction {
 		setCheckable(true);
 		connect(doc, &KTextEditor::Document::highlightingModeChanged, this, [this, doc] { checkApplicable(doc); });
 		checkApplicable(doc);
+		connect(this, &QAction::toggled, this, [this]() {
+			for (const auto a : std::as_const(options)) {
+				a->setVisible(isChecked());
+			}
+		});
 	};
 
 	void checkApplicable(KTextEditor::Document *doc) {
@@ -578,12 +571,12 @@ class RKPreviewMode : public QAction {
 	};
 
 	QString preview_label;
-	std::function<QString(const QString &, const QString &, const QString &)> command;
-	QList<QAction*> options();
+	std::function<QString(const QString &, const QString &, const QString &, RKPreviewMode*)> command;
+	QAction* getOption(int id) const { return options.value(id); };
+	QAction* addOption(int id, QAction *action) { options.insert(id, action); return action; };
 	QString input_ext;
 	RKCommandHighlighter::HighlightingMode valid_mode;
-  private:
-	QList<QAction*> _options;
+	QMap<int, QAction*> options;
 };
 
 class RKScriptPreviewIO {
@@ -652,7 +645,7 @@ void RKCommandEditorWindow::changePreviewMode(QAction *mode) {
 
 	if (mode != action_no_preview) {
 		if (!(preview_io || RKWardMainWindow::suppressModalDialogsForTesting())) { // triggered on change from no preview to some preview, but not between previews
[suppressed due to size limit]
[suppressed due to size limit]
 				discardPreview();
 			}
 		}
@@ -723,29 +716,37 @@ QString RmarkDownRender(const QString &infile, const QString &outdir, const QStr
 	        mode_arg);
 }
 
-void RKCommandEditorWindow::initPreviewModes() {
+void RKCommandEditorWindow::initPreviewModes(KActionMenu *menu) {
 	RK_TRACE(COMMANDEDITOR);
+	QAction *a;
 
-	auto m = new RKPreviewMode(m_doc, i18n("R Markdown (HTML)"), QIcon::fromTheme(u"preview_math"_s), u".Rmd"_s, RKCommandHighlighter::RMarkdown);
-	m->preview_label = i18n("Preview of rendered R Markdown");
-	m->setToolTip(i18n("Preview the script as rendered from RMarkdown format (.Rmd) to HTML."));
-	m->command = [](const QString &infile, const QString &outdir, const QString & /*preview_id*/) {
-		return RmarkDownRender(infile, outdir, u"output_format=\"html_document\", "_s);
-	};
-	preview_modes->addAction(m);
+	// Must define this one first, as doRenderPreview() may trigger during setup of the further actions!
+	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);
 
-	m = new RKPreviewMode(m_doc, i18n("R Markdown (auto)"), QIcon::fromTheme(u"preview_math"_s), u".Rmd"_s, RKCommandHighlighter::RMarkdown);
+	auto m = new RKPreviewMode(m_doc, i18n("R Markdown"), QIcon::fromTheme(u"preview_math"_s), u".Rmd"_s, RKCommandHighlighter::RMarkdown);
 	m->preview_label = i18n("Preview of rendered R Markdown");
-	m->setToolTip(i18n("Preview the script as rendered from RMarkdown format (.Rmd) to the format specified in the document header."));
-	m->command = [](const QString &infile, const QString &outdir, const QString & /*preview_id*/) {
-		return RmarkDownRender(infile, outdir, QString());
+	m->setToolTip(i18n("Preview the script as rendered from RMarkdown format (.Rmd)"));
+	enum _RenderMode { HTML, PDF, Auto };
+	auto group = new QActionGroup(m);
+	m->addOption(HTML, new QAction(i18n("Render as HTML"), group))->setCheckable(true);
+	m->addOption(PDF, new QAction(i18n("Render as PDF"), group))->setCheckable(true);
+	m->addOption(Auto, new QAction(i18n("Auto Format"), group))->setCheckable(true);
+	m->getOption(Auto)->setChecked(true);
+	m->command = [](const QString &infile, const QString &outdir, const QString & /*preview_id*/, RKPreviewMode *m) {
+		QString arg;
+		if (m->getOption(HTML)->isChecked()) arg = u"output_format=\"html_document\", "_s;
+		else if (m->getOption(PDF)->isChecked()) arg = u"output_format=\"pdf_document\", "_s;
+		return RmarkDownRender(infile, outdir, arg);
 	};
 	preview_modes->addAction(m);
 
 	m = new RKPreviewMode(m_doc, i18n("RKWard Output"), RKStandardIcons::getIcon(RKStandardIcons::WindowOutput), u".R"_s, RKCommandHighlighter::RScript);
 	m->preview_label = i18n("Preview of generated RKWard output");
 	m->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."));
-	m->command = [](const QString &infile, const QString &outdir, const QString & /*preview_id*/) {
+	m->command = [](const QString &infile, const QString &outdir, const QString & /*preview_id*/, RKPreviewMode *) {
 		auto command = QStringLiteral("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"
@@ -758,17 +759,41 @@ void RKCommandEditorWindow::initPreviewModes() {
 	m = new RKPreviewMode(m_doc, i18n("R Console"), RKStandardIcons::getIcon(RKStandardIcons::WindowConsole), u".R"_s, RKCommandHighlighter::RScript);
 	m->preview_label = i18n("Preview of script running in interactive R Console");
 	m->setToolTip(i18n("Preview the script as if it was run in the interactive R Console"));
-	m->command = [](const QString &infile, const QString &outdir, const QString & /*preview_id*/) {
-		auto command = QStringLiteral("rk.eval.as.preview(%1, %2)\n"
+	enum _ConsoleOpts { Echo, Continue, None, Global, Scoped, Local };
+	a = m->addOption(Echo, new QAction(i18n("Echo statements")));
+	a->setCheckable(true);
+	a->setChecked(true);
+	m->addOption(Continue, new QAction(i18n("Continue on error")))->setCheckable(true);
+	m->addOption(None, new QAction(i18n("Evaluation environment")))->setSeparator(true);
+	a = m->addOption(Global, new QAction(i18n(".GlobalEnv"), group));
+	a->setCheckable(true);
+	a->setIcon(QIcon::fromTheme(u"emblem-warning"_s));
+	a->setToolTip(i18n("Evaluate code directly in .GlobalEnv. Assignments can access <b>and modify</b> objects in .GlobalEnv."));
+	a = m->addOption(Scoped, new QAction(i18n("Scoped"), group));
+	a->setCheckable(true);
+	a->setToolTip(i18n("Evaluate code in a child scope of .GlobalEnv. Objects in .GlobalEnv can be accessed, but regular assignments using the <tt><-</tt>-operator will not modify them. (Other assignment operations could still modify them!)"));
+	a->setChecked(true);
+	a = m->addOption(Local, new QAction(i18n("Local"), group));
+	a->setCheckable(true);
+	a->setToolTip(i18n("Evaluate code in true local environment. Objects in .GlobalEnv are not on the search path, and will not be modified by regular assignments using the <tt><-</tt>-operator. (Other assignment operations could still modify them!)"));
+	m->command = [](const QString &infile, const QString &outdir, const QString & /*preview_id*/, RKPreviewMode *m) {
+		auto command = QStringLiteral("try(rk.eval.as.preview(%1, %2%3), silent=TRUE)\n" // silent, because error will be printed!
 		                              "rk.show.html(%2)\n");
-		return command.arg(RObject::rQuote(infile), RObject::rQuote(outdir + u"/output.html"_s));
+		QString opt;
+		if (m->getOption(Echo)->isChecked()) opt.append(u", echo=TRUE"_s);
+		else opt.append(u", echo=FALSE"_s);
+		if (m->getOption(Global)->isChecked()) opt.append(u", env=globalenv()"_s);
+		else if (m->getOption(Local)->isChecked()) opt.append(u", env=local()"_s);
+		if (m->getOption(Continue)->isChecked()) opt.append(u", stop.on.error=FALSE"_s);
+		else opt.append(u", stop.on.error=TRUE"_s);
+		return command.arg(RObject::rQuote(infile), RObject::rQuote(outdir + u"/output.html"_s), opt);
 	};
 	preview_modes->addAction(m);
 
 	m = new RKPreviewMode(m_doc, i18n("Plot"), RKStandardIcons::getIcon(RKStandardIcons::WindowX11), u".R"_s, RKCommandHighlighter::RScript);
 	m->preview_label = i18n("Preview of generated plot");
 	m->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."));
-	m->command = [](const QString &infile, const QString & /*outdir*/, const QString &preview_id) {
+	m->command = [](const QString &infile, const QString & /*outdir*/, const QString &preview_id, RKPreviewMode *) {
 		auto command = QStringLiteral("olddev <- dev.cur()\n"
 		                              ".rk.startPreviewDevice(%2)\n"
 		                              "try(source(%1, local=TRUE, print.eval=TRUE))\n"
@@ -776,6 +801,26 @@ void RKCommandEditorWindow::initPreviewModes() {
 		return command.arg(RObject::rQuote(infile), RObject::rQuote(preview_id));
 	};
 	preview_modes->addAction(m);
+
+	auto optmenu = new QMenu(i18n("Options"));
+	const auto preview_actions = preview_modes->actions();
+	for (auto *a : preview_actions) {
+		menu->addAction(a);
+		if (a == action_no_preview) continue;
+		for (auto oa : static_cast<RKPreviewMode*>(a)->options) {
+			optmenu->addAction(oa);
+			connect(oa, &QAction::toggled, this, [this]() { preview_manager->setUpdatePending(); doRenderPreview(); });
+			oa->setVisible(false);
+		}
+	}
+	connect(preview, &RKXMLGUIPreviewArea::previewClosed, this, &RKCommandEditorWindow::discardPreview);
+	connect(preview_modes, &QActionGroup::triggered, this, &RKCommandEditorWindow::changePreviewMode);
+
+	menu->addSeparator();
+	menu->addAction(optmenu->menuAction());
+
+	optmenu->addSeparator();
+	optmenu->addAction(action_preview_as_you_type);
 }
 
 void RKCommandEditorWindow::doRenderPreview() {
@@ -783,15 +828,15 @@ void RKCommandEditorWindow::doRenderPreview() {
 
 	if (action_no_preview->isChecked()) return;
 	if (!preview_manager->needsCommand()) return;
-	const auto mode = static_cast<RKPreviewMode *>(preview_modes->checkedAction());
 
 	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(u".rk.with.window.hints(rk.show.html(content=\"\"), \"\", "_s + RObject::rQuote(preview_manager->previewId()) + u", style=\"preview\")"_s, RCommand::App | RCommand::Sync);
 	}
 
+	const auto mode = static_cast<RKPreviewMode *>(preview_modes->checkedAction());
 	preview_io = RKScriptPreviewIO::init(preview_io, m_doc, mode);
-	QString command = mode->command(preview_io->inpath(), preview_io->outpath(QLatin1String("")), preview_manager->previewId());
+	QString command = mode->command(preview_io->inpath(), preview_io->outpath(QLatin1String("")), preview_manager->previewId(), mode);
 	preview->setLabel(mode->preview_label.isEmpty() ? mode->text() : mode->preview_label);
 	preview->show();
 
diff --git a/rkward/windows/rkcommandeditorwindow.h b/rkward/windows/rkcommandeditorwindow.h
index 46ab563ba..4e94c7b2a 100644
--- a/rkward/windows/rkcommandeditorwindow.h
+++ b/rkward/windows/rkcommandeditorwindow.h
@@ -194,7 +194,7 @@ class RKCommandEditorWindow : public RKMDIWindow, public RKScriptContextProvider
 	QString _id;
 	static QMap<QString, KTextEditor::Document *> unnamed_documents;
 
-	void initPreviewModes();
+	void initPreviewModes(KActionMenu *menu);
 
 	RKXMLGUIPreviewArea *preview;
 	QTimer preview_timer;



More information about the rkward-tracker mailing list