[education/rkward] rkward: Start showing some progress information inline, to avoid flood of dialogs

Thomas Friedrichsmeier null at kde.org
Sun May 1 21:37:49 BST 2022


Git commit 648b705ce847870bc4f5913fc4f1d0ac7c4e9169 by Thomas Friedrichsmeier.
Committed on 29/04/2022 at 21:15.
Pushed by tfry into branch 'master'.

Start showing some progress information inline, to avoid flood of dialogs

M  +10   -9    rkward/dialogs/rkloadlibsdialog.cpp
M  +2    -1    rkward/dialogs/rkloadlibsdialog.h
M  +100  -0    rkward/misc/rkprogresscontrol.cpp
M  +26   -0    rkward/misc/rkprogresscontrol.h
M  +1    -0    rkward/rbackend/rcommand.cpp
M  +7    -4    rkward/rbackend/rcommand.h

https://invent.kde.org/education/rkward/commit/648b705ce847870bc4f5913fc4f1d0ac7c4e9169

diff --git a/rkward/dialogs/rkloadlibsdialog.cpp b/rkward/dialogs/rkloadlibsdialog.cpp
index c178fcdc..b862105f 100644
--- a/rkward/dialogs/rkloadlibsdialog.cpp
+++ b/rkward/dialogs/rkloadlibsdialog.cpp
@@ -25,6 +25,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <KLocalizedString>
 #include <kmessagebox.h>
 #include <kuser.h>
+#include <KMessageWidget>
 
 #include "../rbackend/rkrinterface.h"
 #include "../rbackend/rksessionvars.h"
@@ -655,8 +656,8 @@ InstallPackagesWidget::InstallPackagesWidget (RKLoadLibsDialog *dialog) : QWidge
 	hbox->addLayout (vbox);
 	hbox->setStretchFactor (vbox, 2);
 
-	packages_status = new RKRPackageInstallationStatus (this);
 	packages_view = new QTreeView (this);
+	packages_status = new RKRPackageInstallationStatus(this, packages_view);
 	packages_view->setSortingEnabled (true);
 	model = new RKRPackageInstallationStatusSortFilterModel (this);
 	model->setSourceModel (packages_status);
@@ -669,7 +670,6 @@ InstallPackagesWidget::InstallPackagesWidget (RKLoadLibsDialog *dialog) : QWidge
 	connect (packages_view, &QTreeView::clicked, this, &InstallPackagesWidget::rowClicked);
 	packages_view->setRootIsDecorated (false);
 	packages_view->setIndentation (0);
-	packages_view->setEnabled (false);
 	packages_view->setMinimumHeight (packages_view->sizeHintForRow (0) * 15);	// force a decent height
 	packages_view->setMinimumWidth (packages_view->fontMetrics ().width ("This is to force a sensible min width for the packages view (empty on construction)"));
 	vbox->addWidget (packages_view);
@@ -727,7 +727,6 @@ void InstallPackagesWidget::initialize () {
 	RK_TRACE (DIALOGS);
 
 	packages_status->initialize (parent->chain);
-	packages_view->setEnabled (true);
 	// Force a good width for the icon column, particularly for MacOS X.
 	packages_view->header ()->resizeSection (0, packages_view->sizeHintForIndex (model->index (0, 0, model->index (RKRPackageInstallationStatus::NewPackages, 0, QModelIndex ()))).width () + packages_view->indentation ());
 	for (int i = 1; i <= RKRPackageInstallationStatus::PackageName; ++i) {
@@ -878,7 +877,7 @@ void PackageInstallParamsWidget::liblocsChanged (const QStringList &newlist) {
 
 /////////// RKRPackageInstallationStatus /////////////////
 
-RKRPackageInstallationStatus::RKRPackageInstallationStatus (QObject* parent) : QAbstractItemModel (parent) {
+RKRPackageInstallationStatus::RKRPackageInstallationStatus (QObject* parent, QWidget* display_area) : QAbstractItemModel (parent), display_area(display_area) {
 	RK_TRACE (DIALOGS);
 	_initialized = false;
 }
@@ -932,11 +931,13 @@ void RKRPackageInstallationStatus::initialize (RCommandChain *chain) {
 	_initialized = true;	// will be re-set to false, should the command fail / be cancelled
 
 	RCommand *command = new RCommand (".rk.get.package.installation.state ()", RCommand::App | RCommand::GetStructuredData);
-	connect (command->notifier (), &RCommandNotifier::commandFinished, this, &RKRPackageInstallationStatus::statusCommandFinished);
-	RKProgressControl *control = new RKProgressControl (this, i18n ("<p>Please stand by while searching for installed and available packages.</p><p><strong>Note:</strong> This requires a working internet connection, and may take some time, esp. if one or more repositories are temporarily unavailable.</p>"), i18n ("Searching for packages"), RKProgressControl::CancellableProgress | RKProgressControl::AutoCancelCommands);
-	control->addRCommand (command, true);
-	RInterface::issueCommand (command, chain);
-	control->doModal (true);
+	connect(command->notifier(), &RCommandNotifier::commandFinished, this, &RKRPackageInstallationStatus::statusCommandFinished);
+	RKInlineProgressControl *control = new RKInlineProgressControl(display_area, true);
+	control->messageWidget()->setText(i18n("<p>Please stand by while searching for installed and available packages.</p><p><strong>Note:</strong> This requires a working internet connection, and may take some time, esp. if one or more repositories are temporarily unavailable.</p>"));
+	control->addRCommand(command);
+	//control->setAutoCloseWhenCommandsDone(true);
+	RInterface::issueCommand(command, chain);
+	control->show(100);
 }
 
 void RKRPackageInstallationStatus::statusCommandFinished (RCommand *command) {
diff --git a/rkward/dialogs/rkloadlibsdialog.h b/rkward/dialogs/rkloadlibsdialog.h
index ee60ec3c..cdf29c5f 100644
--- a/rkward/dialogs/rkloadlibsdialog.h
+++ b/rkward/dialogs/rkloadlibsdialog.h
@@ -137,7 +137,7 @@ private:
 class RKRPackageInstallationStatus : public QAbstractItemModel {
 	Q_OBJECT
 public:
-	explicit RKRPackageInstallationStatus (QObject* parent);
+	explicit RKRPackageInstallationStatus (QObject* parent, QWidget* diplay_area);
 	~RKRPackageInstallationStatus ();
 
 	void initialize (RCommandChain *chain);
@@ -204,6 +204,7 @@ private:
 	bool _initialized;
 
 	QStringList current_repos;
+	QWidget *display_area;
 };
 
 class RKRPackageInstallationStatusSortFilterModel : public QSortFilterProxyModel {
diff --git a/rkward/misc/rkprogresscontrol.cpp b/rkward/misc/rkprogresscontrol.cpp
index f3eba6cd..839ab119 100644
--- a/rkward/misc/rkprogresscontrol.cpp
+++ b/rkward/misc/rkprogresscontrol.cpp
@@ -381,3 +381,103 @@ void RKProgressControlDialog::closeEvent (QCloseEvent *e) {
 	}
 }
 
+#include <KMessageWidget>
+#include <KStandardAction>
+
+RKInlineProgressControl::RKInlineProgressControl(QWidget *display_area, bool allow_cancel) : QObject(display_area),
+						autoclose(false),
+						allow_cancel(allow_cancel),
+						display_area(display_area),
+						prevent_close_message(nullptr),
+						close_action(nullptr) {
+	RK_TRACE(MISC);
+
+	display_area->window()->installEventFilter(this);
+
+	wrapper = new QWidget(display_area);
+	auto layout = new QVBoxLayout(wrapper);
+	message_widget = new KMessageWidget();
+	message_widget->setWordWrap(true);
+	message_widget->setCloseButtonVisible(false);  // we want a button, instead, for consistency with cancel
+	if (allow_cancel) {
+		setCloseAction(i18n("Cancel"));
+	}
+	output_display = new QTextEdit();
+	layout->addWidget(message_widget);
+	layout->addWidget(output_display);
+	wrapper->resize(display_area->size());
+}
+
+RKInlineProgressControl::~RKInlineProgressControl() {
+	RK_TRACE(MISC);
+
+	display_area->window()->removeEventFilter(this);
+	delete wrapper;
+	for (int i = 0; i < unfinished_commands.size(); ++i) {
+		RInterface::instance()->cancelCommand(unfinished_commands[i]);
+	}
+}
+
+void RKInlineProgressControl::setCloseAction(const QString &label) {
+	RK_TRACE(MISC);
+
+	if (!close_action) {
+		close_action = KStandardAction::create(KStandardAction::Close, this, &QObject::deleteLater, this);
+		message_widget->addAction(close_action);
+	}
+	close_action->setText(label);
+}
+
+void RKInlineProgressControl::addRCommand(RCommand *command) {
+	RK_TRACE(MISC);
+	unfinished_commands.append(command);
+	connect(command->notifier(), &RCommandNotifier::commandFinished, this, [this](RCommand *c) {
+		unfinished_commands.removeAll(c);
+		if (c->failed()) {
+			autoclose = false;
+			message_widget->setMessageType(KMessageWidget::Error);
+		}
+		if (unfinished_commands.isEmpty()) {
+			if (autoclose) {
+				deleteLater();
+			} else {
+				setCloseAction(i18n("Close"));
+			}
+		}
+	});
+	connect(command->notifier(), &RCommandNotifier:: commandOutput, this, [this](RCommand *, const ROutput *o) {
+		if (o->type == ROutput::Output) {
+			output_display->setTextColor(RKStyle::viewScheme()->foreground(KColorScheme::NormalText).color());
+		} else {
+			output_display->setTextColor(RKStyle::viewScheme()->foreground(KColorScheme::NegativeText).color());
+		}
+		output_display->insertPlainText(o->output);
+	});
+}
+
+void RKInlineProgressControl::show(int delay_ms) {
+	RK_TRACE(MISC);
+	if (delay_ms > 0) {
+		QTimer::singleShot(delay_ms, wrapper, &QWidget::show);
+	} else {
+		wrapper->show();
+	}
+}
+
+bool RKInlineProgressControl::eventFilter(QObject *, QEvent *e) {
+	RK_TRACE(MISC);
+	if (e->type() == QEvent::Resize) {
+		QTimer::singleShot(0, this, [this]() {
+			wrapper->resize(display_area->size());
+		});
+		return false;
+	}
+	if (!allow_cancel && e->type() == QEvent::Close) {
+		// TODO
+		prevent_close_message = new KMessageWidget(i18n("An operation is still running, please wait."));
+		return true;
+	}
+	return false;
+}
+
+
diff --git a/rkward/misc/rkprogresscontrol.h b/rkward/misc/rkprogresscontrol.h
index 82f6a5a2..9ee1d7d1 100644
--- a/rkward/misc/rkprogresscontrol.h
+++ b/rkward/misc/rkprogresscontrol.h
@@ -99,4 +99,30 @@ protected:
 	void rCommandDone (RCommand *command) override;
 };
 
+class KMessageWidget;
+class QAction;
+
+class RKInlineProgressControl : public QObject {
+public:
+/** @param allow_cancel Whether the operation may be cancelled. If true, a cancel button will be added. If false, will also prevent the parent dialog from being closed. Default: true */
+	RKInlineProgressControl(QWidget *display_area, bool allow_cancel);
+	~RKInlineProgressControl();
+	void addRCommand(RCommand *command);
+	void setAutoCloseWhenCommandsDone(bool _autoclose) { autoclose = _autoclose; };
+	void show(int delay_ms=0);
+	KMessageWidget *messageWidget() { return message_widget; };
+private:
+	void setCloseAction(const QString &label);
+	bool eventFilter(QObject *, QEvent *e) override;
+	bool autoclose;
+	bool allow_cancel;
+	QWidget* wrapper;
+	QWidget* display_area;
+	KMessageWidget *message_widget;
+	KMessageWidget *prevent_close_message;
+	QTextEdit *output_display;
+	QList<RCommand*> unfinished_commands;
+	QAction* close_action;
+};
+
 #endif
diff --git a/rkward/rbackend/rcommand.cpp b/rkward/rbackend/rcommand.cpp
index 2c205958..36961fa8 100644
--- a/rkward/rbackend/rcommand.cpp
+++ b/rkward/rbackend/rcommand.cpp
@@ -122,6 +122,7 @@ void RCommand::newOutput (ROutput *output) {
 		if (receivers[i] == 0) continue;
 		receivers[i]->newOutput (this, output);
 	}
+	if (_notifier) _notifier->emitOutput(this, output);
 }
 
 void RCommand::commandLineIn () {
diff --git a/rkward/rbackend/rcommand.h b/rkward/rbackend/rcommand.h
index bbb56f01..fa6d29e9 100644
--- a/rkward/rbackend/rcommand.h
+++ b/rkward/rbackend/rcommand.h
@@ -67,12 +67,15 @@ typedef QList<ROutput*> ROutputList;
 class RCommandNotifier : public QObject {
 	Q_OBJECT
 signals:
-	void commandFinished (RCommand *command);
+/** given command has finished (not necessarily successfully) */
+	void commandFinished(RCommand *command);
+	void commandOutput(RCommand *command, const ROutput* output);
 private:
 friend class RCommand;
-	RCommandNotifier ();
-	~RCommandNotifier ();
-	void emitFinished (RCommand *command) { emit commandFinished (command); };
+	RCommandNotifier();
+	~RCommandNotifier();
+	void emitFinished(RCommand *command) { emit commandFinished(command); };
+	void emitOutput(RCommand *command, const ROutput* output) { emit commandOutput(command, output); };
 };
 
 /** For introductory information on using RCommand, see \ref UsingTheInterfaceToR 



More information about the rkward-tracker mailing list