[rkward/work/render_rmd] rkward: Split preview state management into a separate class, so code can be shared with script window previews.

Thomas Friedrichsmeier null at kde.org
Fri May 11 09:53:02 UTC 2018


Git commit 1d4a8cb881e949da5709058159db2e5fc48dd1cd by Thomas Friedrichsmeier.
Committed on 11/05/2018 at 09:52.
Pushed by tfry into branch 'work/render_rmd'.

Split preview state management into a separate class, so code can be shared with script window previews.

Fix a few small bugs wrt to preview data cleanup.

M  +89   -0    rkward/misc/rkxmlguipreviewarea.cpp
M  +35   -1    rkward/misc/rkxmlguipreviewarea.h
M  +24   -72   rkward/plugin/rkpreviewbox.cpp
M  +3    -7    rkward/plugin/rkpreviewbox.h
M  +1    -1    rkward/rbackend/rpackages/rkward/R/rk.plugin-functions.R

https://commits.kde.org/rkward/1d4a8cb881e949da5709058159db2e5fc48dd1cd

diff --git a/rkward/misc/rkxmlguipreviewarea.cpp b/rkward/misc/rkxmlguipreviewarea.cpp
index 1ab4abe6..e95d225a 100644
--- a/rkward/misc/rkxmlguipreviewarea.cpp
+++ b/rkward/misc/rkxmlguipreviewarea.cpp
@@ -30,6 +30,9 @@
 #include <KLocalizedString>
 
 #include "../windows/rkmdiwindow.h"
+#include "../rbackend/rcommand.h"
+#include "../rbackend/rkrinterface.h"
+#include "../rkglobals.h"
 #include "rkstandardicons.h"
 
 #include "../debug.h"
@@ -156,4 +159,90 @@ void RKXMLGUIPreviewArea::prepareMenu () {
 	}
 }
 
+
+#include "../windows/rkworkplace.h"
+
+RKPreviewManager::RKPreviewManager(QObject* parent) : QObject (parent) {
+	RK_TRACE (PLUGIN);
+
+	update_pending = NoUpdatePending;
+	updating = false;
+	id = QString ().sprintf ("%p", this).remove ('%');
+}
+
+RKPreviewManager::~RKPreviewManager () {
+	RK_TRACE (PLUGIN);
+}
+
+void RKPreviewManager::previewCommandDone (RCommand* command) {
+	RK_TRACE (PLUGIN);
+
+	updating = false;
+	if (update_pending == NoUpdatePossible) {
+		setNoPreviewAvailable ();
+	} else {
+		QString warnings = command->warnings () + command->error ();
+		if (!warnings.isEmpty ()) warnings = QString ("<b>%1</b>\n<pre>%2</pre>").arg (i18n ("Warnings or Errors:")).arg (warnings.toHtmlEscaped ());
+		setStatusMessage (warnings);
+	}
+}
+
+void RKPreviewManager::setCommand (RCommand* command) {
+	RK_TRACE (PLUGIN);
+
+	RK_ASSERT (!updating);
+	updating = true;
+	update_pending = NoUpdatePending;
+	connect (command->notifier(), &RCommandNotifier::commandFinished, this, &RKPreviewManager::previewCommandDone);
+	RKGlobals::rInterface ()->issueCommand (command);
+	setStatusMessage (shortStatusLabel ());
+}
+
+void RKPreviewManager::setUpdatePending () {
+	if (update_pending == UpdatePending) return;
+	RK_TRACE (PLUGIN);
+
+	update_pending = UpdatePending;
+	setStatusMessage (shortStatusLabel ());
+}
+
+void RKPreviewManager::setNoPreviewAvailable () {
+	if (update_pending == NoUpdatePossible) return;
+	RK_TRACE (PLUGIN);
+
+	update_pending = NoUpdatePossible;
+	setStatusMessage (shortStatusLabel ());
+}
+
+void RKPreviewManager::setPreviewDisabled () {
+	if (update_pending == PreviewDisabled) return;
+	RK_TRACE (PLUGIN);
+
+	update_pending = PreviewDisabled;
+	setStatusMessage (shortStatusLabel ());
+}
+
+void RKPreviewManager::setStatusMessage (const QString& message) {
+	RK_TRACE (PLUGIN);
+
+	RKMDIWindow *window = RKWorkplace::mainWorkplace ()->getNamedWindow (id);
+	if (window) window->setStatusMessage (message);
+
+	emit (statusChanged());
+}
+
+QString RKPreviewManager::shortStatusLabel() const {
+//	RK_TRACE (PLUGIN);
+
+	if (update_pending == NoUpdatePossible) {
+		return (i18n ("Preview not (yet) possible"));
+	} else if (update_pending == PreviewDisabled) {
+		return (i18n ("Preview disabled"));
+	} else if (updating || (update_pending == UpdatePending)) {
+		return (i18n ("Preview updating"));
+	} else {
+		return (i18n ("Preview up to date"));
+	}
+}
+
 #include "rkxmlguipreviewarea.moc"
diff --git a/rkward/misc/rkxmlguipreviewarea.h b/rkward/misc/rkxmlguipreviewarea.h
index 77ba49f2..e46d2891 100644
--- a/rkward/misc/rkxmlguipreviewarea.h
+++ b/rkward/misc/rkxmlguipreviewarea.h
@@ -31,7 +31,7 @@ class RKXMLGUIPreviewArea : public KXmlGuiWindow {
 public:
 	RKXMLGUIPreviewArea (const QString &label, QWidget* parent);
 	~RKXMLGUIPreviewArea ();
-	/** (initializes, and) returns a wrapper widget that contains this widget along with a caption (see setLabel()), menu button, and close button. */
+	/** Returns a wrapper widget (created on first call of this function) that contains this widget along with a caption (see setLabel()), menu button, and close button. */
 	QWidget *wrapperWidget ();
 	QString label () const { return _label; };
 protected:
@@ -49,4 +49,38 @@ private:
 	QPointer<KParts::Part> current;
 };
 
+class RCommand;
+/** Simple manager (state machine) for previews. Keeps track of whether a preview is currently updating / up-to-date, and provides
+ *  status information to any preview window. */
+class RKPreviewManager : public QObject {
+	Q_OBJECT
+public:
+	explicit RKPreviewManager (QObject *parent);
+	~RKPreviewManager ();
+
+	void setUpdatePending ();
+	void setPreviewDisabled ();
+	void setNoPreviewAvailable ();
+	/** Start the next preview update, as given by command. You must call needsCommand() first, to check whether the next command is
+	 *  ready to go. */
+	void setCommand (RCommand *command);
+	bool needsCommand () const { return !updating && (update_pending == UpdatePending); };
+	QString previewId () const { return id; };
+	QString shortStatusLabel () const;
+signals:
+	void statusChanged ();
+private slots:
+	void previewCommandDone (RCommand *command);
+private:
+	void setStatusMessage (const QString &);
+	enum {
+		NoUpdatePending,
+		NoUpdatePossible,
+		PreviewDisabled,
+		UpdatePending
+	} update_pending;
+	bool updating;
+	QString id;
+};
+
 #endif
diff --git a/rkward/plugin/rkpreviewbox.cpp b/rkward/plugin/rkpreviewbox.cpp
index bf9428f0..34499201 100644
--- a/rkward/plugin/rkpreviewbox.cpp
+++ b/rkward/plugin/rkpreviewbox.cpp
@@ -38,8 +38,8 @@
 RKPreviewBox::RKPreviewBox (const QDomElement &element, RKComponent *parent_component, QWidget *parent_widget) : RKComponent (parent_component, parent_widget) {
 	RK_TRACE (PLUGIN);
 
-	prior_preview_done = true;
-	new_preview_pending = false;
+	manager = new RKPreviewManager (this);
+	connect (manager, &RKPreviewManager::statusChanged, this, &RKPreviewBox::tryPreview);
 
 	// get xml-helper
 	XMLHelper *xml = parent_component->xmlHelper ();
@@ -48,7 +48,7 @@ RKPreviewBox::RKPreviewBox (const QDomElement &element, RKComponent *parent_comp
 	placement = (PreviewPlacement) xml->getMultiChoiceAttribute (element, "placement", "default;attached;detached;docked", 0, DL_INFO);
 	if (placement == DefaultPreview) placement = DockedPreview;
 	preview_active = xml->getBoolAttribute (element, "active", false, DL_INFO);
-	idprop = RObject::rQuote (QString ().sprintf ("%p", this));
+	idprop = RObject::rQuote (manager->previewId ());
 
 	// create and add property
 	addChild ("state", state = new RKComponentPropertyBool (this, true, preview_active, "active", "inactive"));
@@ -74,11 +74,11 @@ RKPreviewBox::RKPreviewBox (const QDomElement &element, RKComponent *parent_comp
 	if (placement == AttachedPreview) placement_end.append ("\"attached\"");
 	else if (placement == DetachedPreview) placement_end.append ("\"detached\"");
 	else placement_end.append ("\"\"");
-	placement_end.append (", " + RObject::rQuote (idprop) + ", style=\"preview\")");
+	placement_end.append (", " + idprop + ", style=\"preview\")");
 	if (placement == DockedPreview) {
 		RKStandardComponent *uicomp = topmostStandardComponent ();
 		if (uicomp) {
-			uicomp->addDockedPreview (state, toggle_preview_box->text (), idprop);
+			uicomp->addDockedPreview (state, toggle_preview_box->text (), manager->previewId ());
 
 			if (preview_mode == OutputPreview) {
 				RKGlobals::rInterface ()->issueCommand ("local ({\n"
@@ -144,6 +144,9 @@ void RKPreviewBox::changedState (RKComponentPropertyBase *) {
 	toggle_preview_box->setChecked (state->boolValue ());
 	updating = false;
 
+	if (toggle_preview_box->isChecked ()) manager->setUpdatePending ();
+	else manager->setPreviewDisabled ();
+
 	tryPreview ();
 	changed ();
 }
@@ -151,6 +154,7 @@ void RKPreviewBox::changedState (RKComponentPropertyBase *) {
 void RKPreviewBox::changedCode (RKComponentPropertyBase *) {
 	RK_TRACE (PLUGIN);
 
+	if (toggle_preview_box->isChecked ()) manager->setUpdatePending ();
 	tryPreview ();
 }
 
@@ -166,111 +170,59 @@ void RKPreviewBox::tryPreview () {
 	if (isEnabled () && toggle_preview_box->isChecked ()) update_timer->start (10);
 	else killPreview ();
 
-	updateStatusLabel ();
+	status_label->setText (manager->shortStatusLabel ());
 }
 
 void RKPreviewBox::tryPreviewNow () {
 	RK_TRACE (PLUGIN);
 
 	if (!code_property) return;
+
 	ComponentStatus s = parentComponent ()->recursiveStatus ();
 	if (s != Satisfied) {
+		manager->setNoPreviewAvailable ();
 		if (s == Processing) tryPreview ();
-		else {
-			setStatusMessage (i18n ("Preview not (currently) possible"));
-		}
 		return;
 	}
 
-	if (!prior_preview_done) {		// if the last plot is not done, yet, wait before starting the next.
-		new_preview_pending = true;
-		updateStatusLabel ();
-		return;
-	}
+	if (!manager->needsCommand ()) return; // if the last plot is not done, yet, wait before starting the next.
 
 	preview_active = true;
 
-	setStatusMessage (i18n ("Preview updating"));
+	RCommand *command;
 	if (preview_mode == PlotPreview) {
 		RKGlobals::rInterface ()->issueCommand (placement_command + ".rk.startPreviewDevice (" + idprop + ')' + placement_end, RCommand::Plugin | RCommand::Sync, QString ());
 		// creating window generates warnings, sometimes. Don't make those part of the warnings shown for the preview -> separate command for the actual plot.
-		RKGlobals::rInterface ()->issueCommand ("local({\n" + code_property->preview () + "})\n", RCommand::Plugin | RCommand::Sync, QString (), this, DO_PREVIEW);
+		command = new RCommand ("local({\n" + code_property->preview () + "})\n", RCommand::Plugin | RCommand::Sync);
 	} else if (preview_mode == DataPreview) {
-		RKGlobals::rInterface ()->issueCommand ("local({try({\n" + code_property->preview () + "\n})\nif(!exists(\"preview_data\",inherits=FALSE)) preview_data <- data.frame ('ERROR')\nrk.assign.preview.data(" + idprop + ", preview_data)\n})\n" + placement_command + "rk.edit(rkward::.rk.variables$.rk.preview.data[[" + idprop + "]])" + placement_end, RCommand::Plugin | RCommand::Sync, QString (), this, DO_PREVIEW);
+		command = new RCommand ("local({try({\n" + code_property->preview () + "\n})\nif(!exists(\"preview_data\",inherits=FALSE)) preview_data <- data.frame ('ERROR')\nrk.assign.preview.data(" + idprop + ", preview_data)\n})\n" + placement_command + "rk.edit(rkward::.rk.variables$.rk.preview.data[[" + idprop + "]])" + placement_end, RCommand::Plugin | RCommand::Sync);
 	} else if (preview_mode == OutputPreview) {
-		RKGlobals::rInterface ()->issueCommand (placement_command + "local({\n"
+		command = new RCommand (placement_command + "local({\n"
 		    "	oldfile <- rk.set.output.html.file(rk.get.preview.data (" + idprop + ")$filename, style='preview')\n"
 		    "	rk.flush.output(ask=FALSE, style='preview')\n"
 		    "	local({try({\n" + code_property->preview () + "\n})})\n"  // nested local to make sure "oldfile" is not overwritten.
 		    "	rk.set.output.html.file(oldfile)\n})\n"
-		    "rk.show.html(rk.get.preview.data (" + idprop + ")$filename)" + placement_end, RCommand::Plugin | RCommand::Sync, QString (), this, DO_PREVIEW);
+		    "rk.show.html(rk.get.preview.data (" + idprop + ")$filename)" + placement_end, RCommand::Plugin | RCommand::Sync);
 	} else {
-		RKGlobals::rInterface ()->issueCommand ("local({\n" + placement_command + code_property->preview () + placement_end + "})\n", RCommand::Plugin | RCommand::Sync, QString (), this, DO_PREVIEW);
+		command = new RCommand ("local({\n" + placement_command + code_property->preview () + placement_end + "})\n", RCommand::Plugin | RCommand::Sync);
 	}
-
-	prior_preview_done = false;
-	new_preview_pending = false;
-
-	updateStatusLabel ();
-}
-
-void RKPreviewBox::setStatusMessage(const QString& status) {
-	RK_TRACE (PLUGIN);
-
-	RKMDIWindow *window = RKWorkplace::mainWorkplace ()->getNamedWindow (idprop);
-	if (!window) return;
-	window->setStatusMessage (status);
+	manager->setCommand (command);
 }
 
 void RKPreviewBox::killPreview (bool cleanup) {
 	RK_TRACE (PLUGIN);
 
-	if (!preview_active) return;
+	if (!(preview_active || cleanup)) return;
 	preview_active = false;
 
 	if (cleanup) {
 		QString command;
-		if (preview_mode == PlotPreview) command = ".rk.killPreviewDevice (" + idprop + ')';
-		else command = "rk.discard.preview.data (" + idprop + ')';
+		if (preview_mode == PlotPreview) command = ".rk.killPreviewDevice (" + idprop + ")\n";
+		command += "rk.discard.preview.data (" + idprop + ')';
 		RKGlobals::rInterface ()->issueCommand (command, RCommand::Plugin | RCommand::Sync);
 	}
 	if (placement != DockedPreview) {
-		RKMDIWindow *window =  RKWorkplace::mainWorkplace ()->getNamedWindow (idprop);
+		RKMDIWindow *window =  RKWorkplace::mainWorkplace ()->getNamedWindow (manager->previewId ());
 		if (window) window->deleteLater ();
 	}
-
-	prior_preview_done = true;
-	new_preview_pending = false;
-}
-
-void RKPreviewBox::rCommandDone (RCommand *command) {
-	RK_TRACE (PLUGIN);
-
-	prior_preview_done = true;
-	if (new_preview_pending) tryPreview ();
-
-	QString warnings = command->warnings () + command->error ();
-	if (!warnings.isEmpty ()) warnings = QString ("<b>%1</b>\n<pre>%2</pre>").arg (i18n ("Warnings or Errors:")).arg (warnings.toHtmlEscaped ());
-	setStatusMessage (warnings);
-
-	updateStatusLabel ();
-}
-
-void RKPreviewBox::updateStatusLabel () {
-	RK_TRACE (PLUGIN);
-
-	if (!toggle_preview_box->isChecked ()) {
-		status_label->setText (i18n ("Preview disabled"));
-	} else {
-		if (parentComponent ()->isSatisfied ()) {
-			if (prior_preview_done && (!new_preview_pending)) {
-				status_label->setText (i18n ("Preview up to date"));
-			} else {
-				status_label->setText (i18n ("Preview updating"));
-			}
-		} else {
-			status_label->setText (i18n ("Preview not (yet) possible"));
-		}
-	}
 }
-
diff --git a/rkward/plugin/rkpreviewbox.h b/rkward/plugin/rkpreviewbox.h
index 75af29e4..f887ce50 100644
--- a/rkward/plugin/rkpreviewbox.h
+++ b/rkward/plugin/rkpreviewbox.h
@@ -28,13 +28,14 @@ class QCheckBox;
 class QDomElement;
 class QLabel;
 class QTimer;
+class RKPreviewManager;
 
 /**
 This RKComponent provides a (togglable) automatic graphical preview. WARNING: This component violates some standards of "good component behavior", esp. by assuming several things about the nature of the parent component. So please do not take this as an example for basing other components on.
 
 @author Thomas Friedrichsmeier
 */
-class RKPreviewBox : public RKComponent, public RCommandReceiver {
+class RKPreviewBox : public RKComponent {
 	Q_OBJECT
 public: 
 	RKPreviewBox (const QDomElement &element, RKComponent *parent_component, QWidget *parent_widget);
@@ -47,17 +48,12 @@ public slots:
 	void changedState (RKComponentPropertyBase *);
 	void changedCode (RKComponentPropertyBase *);
 	void tryPreviewNow ();
-protected:
-	void rCommandDone (RCommand *) override;
 private:
 	bool updating;		// prevent recursion
 	bool preview_active;
-	bool prior_preview_done;
-	bool new_preview_pending;
 	void tryPreview ();
 	void killPreview (bool cleanup = false);
-	void updateStatusLabel ();
-	void setStatusMessage (const QString& status);
+	RKPreviewManager *manager;
 	enum PreviewMode {
 		PlotPreview,
 		DataPreview,
diff --git a/rkward/rbackend/rpackages/rkward/R/rk.plugin-functions.R b/rkward/rbackend/rpackages/rkward/R/rk.plugin-functions.R
index f55ce0c3..eecd9653 100644
--- a/rkward/rbackend/rpackages/rkward/R/rk.plugin-functions.R
+++ b/rkward/rbackend/rpackages/rkward/R/rk.plugin-functions.R
@@ -247,7 +247,7 @@ assign(".rk.preview.data", list (), envir=.rk.variables)
 #' @rdname rk.assign.preview.data
 "rk.discard.preview.data" <- function (id) {
 	pdata <- .rk.variables$.rk.preview.data
-	if (!is.null (pdata[[id]]) && !is.null (pdata[[id]]$on.exit)) pdata[[id]]$on.delete (id)
+	if (!is.null (pdata[[id]]) && !is.null (pdata[[id]]$on.delete)) pdata[[id]]$on.delete (id)
 	pdata[[id]] <- NULL
 	assign (".rk.preview.data", pdata, envir=.rk.variables)
 	rk.sync (.rk.variables$.rk.preview.data)



More information about the rkward-tracker mailing list