Commit 1d4a8cb8 authored by Thomas Friedrichsmeier's avatar Thomas Friedrichsmeier
Browse files

Split preview state management into a separate class, so code can be shared...

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.
parent 442e9a86
......@@ -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"
......@@ -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
......@@ -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"));
}
}
}
......@@ -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,
......
......@@ -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)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment