[education/rkward] rkward/dialogs: Rework setup wizards internals, to allow addition of backend selection page

Thomas Friedrichsmeier null at kde.org
Sat Jun 8 23:21:01 BST 2024


Git commit 9a675d83640409ab5dd23d89feda1dacd9aa6a5b by Thomas Friedrichsmeier.
Committed on 08/06/2024 at 22:10.
Pushed by tfry into branch 'master'.

Rework setup wizards internals, to allow addition of backend selection page

M  +112  -82   rkward/dialogs/rksetupwizard.cpp
M  +4    -9    rkward/dialogs/rksetupwizard.h

https://invent.kde.org/education/rkward/-/commit/9a675d83640409ab5dd23d89feda1dacd9aa6a5b

diff --git a/rkward/dialogs/rksetupwizard.cpp b/rkward/dialogs/rksetupwizard.cpp
index b220a97ff..15724be59 100644
--- a/rkward/dialogs/rksetupwizard.cpp
+++ b/rkward/dialogs/rksetupwizard.cpp
@@ -36,6 +36,53 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "../debug.h"
 
+class RKSetupWizardPage : public QWidget {
+public:
+	RKSetupWizardPage(RKSetupWizard* wizard, const QString& caption) : QWidget(), wizard(wizard), current_row(-1), glayout(nullptr) {
+		RK_TRACE (DIALOGS);
+		ref = wizard->addPage(this, caption);
+	};
+
+	void lazyInitOnce(std::function<void(RKSetupWizardPage*)> initfun) {
+		auto refcopy = ref;
+		QObject::connect(wizard, &KPageDialog::currentPageChanged, this, [this, refcopy, initfun](KPageWidgetItem* current, KPageWidgetItem*) mutable {
+			if (current && current == refcopy) {
+				initfun(this);
+				refcopy = nullptr;
+			}
+		});
+	}
+	void lazyInitRepeated(std::function<void(RKSetupWizardPage*)> initfun) {
+		QObject::connect(wizard, &KPageDialog::currentPageChanged, this, [this, initfun](KPageWidgetItem* current, KPageWidgetItem*) {
+			if (current && current == ref) {
+				initfun(this);
+			}
+		});
+	}
+
+	RKSetupWizard *wizard;
+	int current_row;
+	QGridLayout *glayout;
+	KPageWidgetItem *ref;
+	void appendItem(RKSetupWizardItem* item) {
+		RK_TRACE (DIALOGS);
+
+		if (!glayout) {
+			RK_ASSERT(!layout());  // must now mix with different type of layout
+			glayout = new QGridLayout(this);
+			glayout->setColumnStretch(1, 2);
+			glayout->setColumnStretch(2, 1);
+		}
+
+		item->createWidget(glayout, ++current_row);
+		wizard->items.append(item);
+	};
+	void addStretch() {
+		RK_ASSERT(glayout); // Offered for grid-layout pages, only, for now
+		glayout->setRowStretch(++current_row, 1);
+	}
+};
+
 bool RKSetupWizard::has_been_run = false;
 
 void RKSetupWizardItem::createWidget(QGridLayout *layout, int row) {
@@ -118,7 +165,7 @@ RKSetupWizard::RKSetupWizard(QWidget* parent, InvokationReason reason, const QLi
 	RK_TRACE (DIALOGS);
 
 	// Cover page
-	auto firstpage = new QWidget();
+	auto firstpage = new RKSetupWizardPage(this, i18n("RKWard Setup Assistant"));
 	auto l = new QVBoxLayout(firstpage);
 	QString intro = i18n("<p>This dialog will guide you through a quick check of the basic setup of the required (or recommended) components.</p>");
 	if (reason == NewVersionRKWard) {
@@ -132,11 +179,11 @@ RKSetupWizard::RKSetupWizard(QWidget* parent, InvokationReason reason, const QLi
 	l->addWidget(RKCommonFunctions::wordWrappedLabel(intro));
 	waiting_to_start_label = RKCommonFunctions::wordWrappedLabel(i18n("<b>Waiting for R backend...</b>") + "<p> </p><p> </p>");
 	l->addWidget(waiting_to_start_label);
-	firstpageref = addPage (firstpage, i18n("RKWard Setup Assistant"));
+	firstpageref = firstpage->ref;;
 	setValid(firstpageref, false);
 
 	// Basic installation page
-	createStandardPage();
+	auto page = new RKSetupWizardPage(this, i18n("Basic installation"));
 	reinstallation_required = false;
 	auto idir = new RKSetupWizardItem(i18n("Installation directory"));
 	if (RKCommonFunctions::getRKWardDataDir ().isEmpty ()) {
@@ -147,7 +194,7 @@ RKSetupWizard::RKSetupWizard(QWidget* parent, InvokationReason reason, const QLi
 	} else {
 		idir->setStatus(RKSetupWizardItem::Good, i18n("Found."));
 	}
-	appendItem(idir);
+	page->appendItem(idir);
 
 	auto pluginmaps = new RKSetupWizardItem(i18n("RKWard plugins"));
 	if (RKSettingsModulePlugins::pluginMaps().isEmpty()) {
@@ -160,7 +207,7 @@ RKSetupWizard::RKSetupWizard(QWidget* parent, InvokationReason reason, const QLi
 	} else {
 		pluginmaps->setStatus(RKSetupWizardItem::Good, i18n("Found."));
 	}
-	appendItem(pluginmaps);
+	page->appendItem(pluginmaps);
 
 	auto kateplugins = new RKSetupWizardItem(i18n("Kate plugins"));
 	int kateplugincount = RKWardMainWindow::getMain()->katePluginIntegration()->knownPluginCount();
@@ -171,7 +218,7 @@ RKSetupWizard::RKSetupWizard(QWidget* parent, InvokationReason reason, const QLi
 	} else {
 		kateplugins->setStatus(RKSetupWizardItem::Good, i18np("Found %1 plugin.", "Found %1 plugins.", kateplugincount));
 	}
-	appendItem(kateplugins);
+	page->appendItem(kateplugins);
 
 	// TODO: Remove, eventually
 	auto legacy_output = new RKSetupWizardItem(i18n("Pre 0.7.3 output file"));
@@ -184,79 +231,50 @@ RKSetupWizard::RKSetupWizard(QWidget* parent, InvokationReason reason, const QLi
 	} else {
 		legacy_output->setStatus(RKSetupWizardItem::Good, i18n("Found."));
 	}
-	appendItem(legacy_output);
+	page->appendItem(legacy_output);
 
 	for (int i = 0; i < settings_items.size(); ++i) {
-		appendItem(settings_items[i]);
+		page->appendItem(settings_items[i]);
 	}
+	page->addStretch();
 
-	current_layout->setRowStretch(++current_row, 1);
-	addPage(current_page, i18n("Basic installation"));
-
-	// Next we'll want to wait for the R backend to start up. Previous solution was to set Qt::ApplicationModal, and wait, calling processEvents().
-	// This does not seem to work well on mac, however, so instead we return, here, so exec will be called from outside, then fire a timer to finish constuction.
-	QTimer::singleShot(10, this, &RKSetupWizard::setupWizardPhase2);
-}
-
-void RKSetupWizard::setupWizardPhase2() {
-	// Wait for R Interface, then enable dialog
-	if (!RInterface::instance()->backendIsIdle()) {
-		if (RInterface::instance()->backendIsDead()) {
-			// TODO
-			waiting_to_start_label->setText(i18n("<b>R backend has crashed. Click \"Cancel\" to exit setup assistant.</b>"));
-		} else {
-			QTimer::singleShot(100, this, &RKSetupWizard::setupWizardPhase2);
-		}
-		return;
-	}
-	RK_TRACE(APP);
-
-	waiting_to_start_label->setText(i18n("<b>R backend has started. Click \"Next\" to continue.</b>"));
-	setValid(firstpageref, true);
+	// The following pages need the R backend, and are thus initialized lazily
 
 	// R backend page
-	// This must be created _after_ the backend has started, for obvious reasons.
-	createStandardPage();
-	current_layout->addWidget(new QLabel(i18n("R backend info, here")));
-	addPage(current_page, i18n("R Backend"));
+	page = new RKSetupWizardPage(this, i18n("R Backend"));
+	page->lazyInitOnce([](RKSetupWizardPage *p) {
+		auto l = new QVBoxLayout(p);
+		l->addWidget(new QLabel(i18n("R backend info, here")));
+		l->addStretch();
+	});
 
 	// R packages page
-	createStandardPage();
-
-	appendItem(makeRPackageCheck("R2HTML", i18n("The R2HTML package is used by nearly all RKWard output functions, and thus required."), RKSetupWizardItem::Error));
-	appendItem(makeRPackageCheck("rmarkdown", i18n("The rmarkdown package is required for rendering .Rmd files (including preview rendering), which is an optional but recommended feature."), RKSetupWizardItem::Warning));
-
-	current_layout->setRowStretch(++current_row, 1);
-	addPage(current_page, i18n("R Packages"));
+	page = new RKSetupWizardPage(this, i18n("R Packages"));
+	page->lazyInitOnce([](RKSetupWizardPage *p) {
+		p->appendItem(makeRPackageCheck("R2HTML", i18n("The R2HTML package is used by nearly all RKWard output functions, and thus required."), RKSetupWizardItem::Error));
+		p->appendItem(makeRPackageCheck("rmarkdown", i18n("The rmarkdown package is required for rendering .Rmd files (including preview rendering), which is an optional but recommended feature."), RKSetupWizardItem::Warning));
+		p->addStretch();
+	});
 
 	// external software page
-	createStandardPage();
-
-	appendItem(makeSoftwareCheck("pandoc", i18n("The pandoc software is needed for rendering (or previewing) R markdown (.Rmd) files. This is optional but recommended."), "https://pandoc.org/installing.html", RKSetupWizardItem::Warning));
-	appendItem(makeSoftwareCheck("kbibtex", i18n("The kbibtex software is useful for managing citations while writing articles. It integrates into RKWard via the Document Preview kate plugin."), "https://userbase.kde.org/KBibTeX", RKSetupWizardItem::Warning));
-
-	current_layout->setRowStretch(++current_row, 1);
-	second_to_last_page_ref = addPage(current_page, i18n("External software"));
+	page = new RKSetupWizardPage(this, i18n("External software"));
+	page->lazyInitOnce([](RKSetupWizardPage *p) {
+		p->appendItem(makeSoftwareCheck("pandoc", i18n("The pandoc software is needed for rendering (or previewing) R markdown (.Rmd) files. This is optional but recommended."), "https://pandoc.org/installing.html", RKSetupWizardItem::Warning));
+		p->appendItem(makeSoftwareCheck("kbibtex", i18n("The kbibtex software is useful for managing citations while writing articles. It integrates into RKWard via the Document Preview kate plugin."), "https://userbase.kde.org/KBibTeX", RKSetupWizardItem::Warning));
+		p->addStretch();
+	});
 
 	// summary page
-	createStandardPage();
-	last_page_label = RKCommonFunctions::linkedWrappedLabel("");
-	current_layout->addWidget(last_page_label, 0, 0, 0, 3);
-	current_layout->setRowStretch(1, 1);
-	addPage(current_page, i18n("Summary of the next steps"));
-}
-
-RKSetupWizard::~RKSetupWizard() {
-	RK_TRACE (DIALOGS);
-	for(int i = 0; i < items.size(); ++i) {
-		delete items[i];
-	}
-}
-
-void RKSetupWizard::next() {
-	RK_TRACE (DIALOGS);
+	page = new RKSetupWizardPage(this, i18n("Summary of the next steps"));
+	l = new QVBoxLayout(page);
+	auto last_page_label = RKCommonFunctions::linkedWrappedLabel("");
+	l->addWidget(last_page_label);
+	l->addStretch();
+	page->lazyInitRepeated([this, last_page_label](RKSetupWizardPage *) {
+		software_to_install.clear();
+		packages_to_install.clear();
+		r_commands_to_run.clear();;
 
-	if (currentPage() == second_to_last_page_ref) {
 		// NOTE: This is not quite clean: Some settings get applied before clicking finish, this way.
 		//       However, I don't really want to pop up a separate dialog for a summary page, either.
 		for(int i = 0; i < items.size(); ++i) {
@@ -293,8 +311,35 @@ void RKSetupWizard::next() {
 		last_page_label->setText(label_text);
 		int new_height = qMax(height(), spare_height+last_page_label->minimumSizeHint().height());
 		resize(width(), new_height);
+	});
+
+	// Next we'll want to wait for the R backend to start up. Previous solution was to set Qt::ApplicationModal, and wait, calling processEvents().
+	// This does not seem to work well on mac, however, so instead we return, here, so exec will be called from outside, then fire a timer to finish constuction.
+	QTimer::singleShot(10, this, &RKSetupWizard::setupWizardPhase2);
+}
+
+void RKSetupWizard::setupWizardPhase2() {
+	// Wait for R Interface, then enable dialog
+	if (!RInterface::instance()->backendIsIdle()) {
+		if (RInterface::instance()->backendIsDead()) {
+			// TODO
+			waiting_to_start_label->setText(i18n("<b>R backend has crashed. Click \"Cancel\" to exit setup assistant.</b>"));
+		} else {
+			QTimer::singleShot(100, this, &RKSetupWizard::setupWizardPhase2);
+		}
+		return;
+	}
+	RK_TRACE(APP);
+
+	waiting_to_start_label->setText(i18n("<b>R backend has started. Click \"Next\" to continue.</b>"));
+	setValid(firstpageref, true);
+}
+
+RKSetupWizard::~RKSetupWizard() {
+	RK_TRACE (DIALOGS);
+	for(int i = 0; i < items.size(); ++i) {
+		delete items[i];
 	}
-	KAssistantDialog::next();
 }
 
 void RKSetupWizard::doAutoCheck() {
@@ -345,21 +390,6 @@ void RKSetupWizard::fullInteractiveCheck(InvokationReason reason, const QList<RK
 	delete wizard;
 }
 
-void RKSetupWizard::createStandardPage() {
-	RK_TRACE (DIALOGS);
-	current_page = new QWidget();
-	current_layout = new QGridLayout(current_page);
-	current_layout->setColumnStretch(1, 2);
-	current_layout->setColumnStretch(2, 1);
-	current_row = -1;
-}
-
-void RKSetupWizard::appendItem(RKSetupWizardItem* item) {
-	RK_TRACE (DIALOGS);
-	item->createWidget(current_layout, ++current_row);
-	items.append(item);
-}
-
 void RKSetupWizard::markSoftwareForInstallation(const QString& name, const QString& downloadurl, bool install) {
 	RK_TRACE (DIALOGS);
 	bool present = software_to_install.contains(name);
diff --git a/rkward/dialogs/rksetupwizard.h b/rkward/dialogs/rksetupwizard.h
index 929a950d8..fad6831c3 100644
--- a/rkward/dialogs/rksetupwizard.h
+++ b/rkward/dialogs/rksetupwizard.h
@@ -14,6 +14,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
 class QGridLayout;
 class QLabel;
 class RKSetupWizardItem;
+class RKSetupWizardPage;
+
 class RKSetupWizard : public KAssistantDialog {
 protected:
 	enum InvokationReason {
@@ -43,17 +45,9 @@ private:
 	QStringList software_to_install_urls;
 	QStringList packages_to_install;
 	QStringList r_commands_to_run;
-	void createStandardPage();
-	QWidget *current_page;
-	int current_row;
-	QGridLayout* current_layout;
-	void appendItem(RKSetupWizardItem* item);
+friend class RKSetupWizardPage;
 	QList<RKSetupWizardItem*> items;
 	bool reinstallation_required;
-
-	void next() override;
-	KPageWidgetItem *second_to_last_page_ref;
-	QLabel* last_page_label;
 };
 
 class QComboBox;
@@ -73,6 +67,7 @@ public:
 	void setShortLabel(const QString &label) { shortlabel = label; };
 	void setLongLabel(const QString &label) { longlabel = label; };
 private:
+friend class RKSetupWizardPage;
 friend class RKSetupWizard;
 	void createWidget(QGridLayout *layout, int row);
 	void apply(RKSetupWizard *wizard);



More information about the rkward-tracker mailing list