[education/rkward] rkward: Disentangle the 1-1 relationship between settings modules and settings dialog pages

Thomas Friedrichsmeier null at kde.org
Mon Jul 22 22:34:28 BST 2024


Git commit ca229309a5455f45c27cb07e7bac6844714f5a3b by Thomas Friedrichsmeier.
Committed on 22/07/2024 at 21:34.
Pushed by tfry into branch 'master'.

Disentangle the 1-1 relationship between settings modules and settings dialog pages

M  +16   -1    rkward/autotests/core_test.cpp
M  +1    -1    rkward/dialogs/rkloadlibsdialog.cpp
M  +1    -1    rkward/rkconsole.cpp
M  +4    -5    rkward/rkward.cpp
M  +100  -187  rkward/settings/rksettings.cpp
M  +10   -49   rkward/settings/rksettings.h
M  +12   -8    rkward/settings/rksettingsmodule.cpp
M  +41   -28   rkward/settings/rksettingsmodule.h
M  +82   -84   rkward/settings/rksettingsmodulecommandeditor.cpp
M  +7    -29   rkward/settings/rksettingsmodulecommandeditor.h
M  +49   -45   rkward/settings/rksettingsmoduleconsole.cpp
M  +6    -13   rkward/settings/rksettingsmoduleconsole.h
M  +84   -78   rkward/settings/rksettingsmoduledebug.cpp
M  +5    -14   rkward/settings/rksettingsmoduledebug.h
M  +107  -93   rkward/settings/rksettingsmodulegeneral.cpp
M  +5    -12   rkward/settings/rksettingsmodulegeneral.h
M  +134  -121  rkward/settings/rksettingsmodulegraphics.cpp
M  +6    -21   rkward/settings/rksettingsmodulegraphics.h
M  +60   -53   rkward/settings/rksettingsmodulekateplugins.cpp
M  +5    -10   rkward/settings/rksettingsmodulekateplugins.h
M  +38   -45   rkward/settings/rksettingsmoduleobjectbrowser.cpp
M  +6    -20   rkward/settings/rksettingsmoduleobjectbrowser.h
M  +93   -88   rkward/settings/rksettingsmoduleoutput.cpp
M  +6    -17   rkward/settings/rksettingsmoduleoutput.h
M  +59   -50   rkward/settings/rksettingsmoduleplugins.cpp
M  +9    -17   rkward/settings/rksettingsmoduleplugins.h
M  +281  -289  rkward/settings/rksettingsmoduler.cpp
M  +13   -37   rkward/settings/rksettingsmoduler.h
M  +113  -107  rkward/settings/rksettingsmodulewatch.cpp
M  +8    -25   rkward/settings/rksettingsmodulewatch.h
M  +1    -1    rkward/windows/katepluginintegration.h
M  +2    -1    rkward/windows/rcontrolwindow.cpp
M  +1    -1    rkward/windows/rkcommandeditorwindow.cpp
M  +5    -7    rkward/windows/rkcommandlog.cpp
M  +1    -1    rkward/windows/rkcommandlog.h
M  +2    -2    rkward/windows/rkhtmlwindow.cpp
M  +4    -4    rkward/windows/rkmdiwindow.cpp
M  +2    -2    rkward/windows/rkmdiwindow.h
M  +1    -1    rkward/windows/rkwindowcatcher.cpp
M  +2    -1    rkward/windows/robjectbrowser.cpp

https://invent.kde.org/education/rkward/-/commit/ca229309a5455f45c27cb07e7bac6844714f5a3b

diff --git a/rkward/autotests/core_test.cpp b/rkward/autotests/core_test.cpp
index 225868c97..8c75d22bc 100644
--- a/rkward/autotests/core_test.cpp
+++ b/rkward/autotests/core_test.cpp
@@ -422,7 +422,22 @@ private Q_SLOTS:
 			QCOMPARE(RKWorkplace::mainWorkplace()->getObjectList(RKMDIWindow::HelpWindow).size(), 0);
 		});
 		RInterface::issueCommand(new RCommand("dev.off()", RCommand::User));
-		waitForAllFinished(5000);  // priority_command_done must remain in scope until done
+		waitForAllFinished(5000);
+	}
+
+	void SettingsTest() {
+		// Another very basic test. Essentially we check, whether the settings dialog can be created.
+		QVERIFY(!RKSettings::settings_dialog);
+		RKWorkplace::mainWorkplace()->openAnyUrl(QUrl("rkward://settings/plugins"));
+		auto dialog = RKSettings::settings_dialog;
+		QVERIFY(dialog);
+
+		RKWorkplace::mainWorkplace()->openAnyUrl(QUrl("rkward://settings/graphics"));
+		QVERIFY(dialog == RKSettings::settings_dialog);  // shall be reused
+
+		dialog->close();
+		waitForAllFinished();
+		QVERIFY(!RKSettings::settings_dialog);
 	}
 
 	void ScriptWindowTest() {
diff --git a/rkward/dialogs/rkloadlibsdialog.cpp b/rkward/dialogs/rkloadlibsdialog.cpp
index 0c4efd18d..de877f026 100644
--- a/rkward/dialogs/rkloadlibsdialog.cpp
+++ b/rkward/dialogs/rkloadlibsdialog.cpp
@@ -827,7 +827,7 @@ void InstallPackagesWidget::apply () {
 
 void InstallPackagesWidget::configureRepositories () {
 	RK_TRACE (DIALOGS);
-	RKSettings::configureSettings (RKSettings::PageRPackages, this, parent->chain);
+	RKSettings::configureSettings(RKSettingsModuleRPackages::page_id, this, parent->chain);
 }
 
 /////////// RKRPackageInstallationStatus /////////////////
diff --git a/rkward/rkconsole.cpp b/rkward/rkconsole.cpp
index 0440aa700..46b2a9ffc 100644
--- a/rkward/rkconsole.cpp
+++ b/rkward/rkconsole.cpp
@@ -114,7 +114,7 @@ RKConsole::RKConsole (QWidget *parent, bool tool_window, const char *name) : RKM
 	setCaption (i18n ("R Console"));
 	console_part = new RKConsolePart (this);
 	setPart (console_part);
-	setMetaInfo (shortCaption (), QUrl ("rkward://page/rkward_console"), RKSettings::PageConsole);
+	setMetaInfo(shortCaption(), QUrl("rkward://page/rkward_console"), RKSettingsModuleConsole::page_id);
 	initializeActivationSignals ();
 	initializeActions (getPart ()->actionCollection ());
 	QAction* action = RKSettingsModuleConsole::showMinimap()->makeAction(this, i18n("Scrollbar minimap"), [this](bool val) { view->setConfigValue("scrollbar-minimap", val); });
diff --git a/rkward/rkward.cpp b/rkward/rkward.cpp
index 77efc710a..c15fcd8a4 100644
--- a/rkward/rkward.cpp
+++ b/rkward/rkward.cpp
@@ -126,7 +126,6 @@ RKWardMainWindow::RKWardMainWindow() : KParts::MainWindow() {
 	katepluginintegration = nullptr;
 	active_ui_buddy = nullptr;
 	RKCommonFunctions::getRKWardDataDir(); // call this before any forking, in order to avoid potential race conditions during initialization of data dir
-	RKSettings::settings_tracker = new RKSettingsTracker (this);
 
 	///////////////////////////////////////////////////////////////////
 	// call inits to invoke all other construction parts
@@ -399,9 +398,9 @@ void RKWardMainWindow::startR () {
 	RObjectBrowser::mainBrowser ()->unlock ();
 }
 
-void RKWardMainWindow::slotConfigure () {
-	RK_TRACE (APP);
-	RKSettings::configureSettings (RKSettings::NoPage, this);
+void RKWardMainWindow::slotConfigure() {
+	RK_TRACE(APP);
+	RKSettings::configureSettings(RKSettingsModule::no_page_id, this);
 }
 
 void RKWardMainWindow::slotCancelAllCommands () {
@@ -798,7 +797,7 @@ void RKWardMainWindow::initStatusBar () {
 	dummy->menu()->addAction(restart_r);
 	dummy->menu()->addSeparator();
 	QAction *a = new QAction(i18n("Configure R backend"), this);
-	connect(a, &QAction::triggered, this, []() { RKSettings::configureSettings(RKSettings::PageR); });
+	connect(a, &QAction::triggered, this, []() { RKSettings::configureSettings(RKSettingsModuleR::page_id); });
 	dummy->menu()->addAction(a);
 	dummy->setFixedHeight(statusbar_r_status->height());
 	boxl->addWidget(dummy);
diff --git a/rkward/settings/rksettings.cpp b/rkward/settings/rksettings.cpp
index 3c106b933..0740fafb8 100644
--- a/rkward/settings/rksettings.cpp
+++ b/rkward/settings/rksettings.cpp
@@ -1,18 +1,18 @@
 /*
 rksettings - This file is part of RKWard (https://rkward.kde.org). Created: Wed Jul 28 2004
-SPDX-FileCopyrightText: 2004-2022 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileCopyrightText: 2004-2024 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
 SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
 SPDX-License-Identifier: GPL-2.0-or-later
 */
 #include "rksettings.h"
 
 #include <QPushButton>
-#include <QVBoxLayout>
 
 #include <KLocalizedString>
 #include <KSharedConfig>
 
 #include "../windows/rkworkplace.h"
+#include "../rkward.h"
 
 // modules
 #include "rksettingsmoduleplugins.h"
@@ -31,172 +31,91 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 //static
 RKSettings *RKSettings::settings_dialog = nullptr;
-RKSettingsTracker *RKSettings::settings_tracker = nullptr;
+QList<RKSettingsModule *> RKSettings::modules;
 
 //static 
-void RKSettings::configureSettings (SettingsPage page, QWidget *parent, RCommandChain *chain) {
+void RKSettings::configureSettings(const RKSettingsModule::PageId page, QWidget *parent, RCommandChain *chain) {
 	RK_TRACE (SETTINGS);
 
 	RKSettingsModule::chain = chain;
 
 	if (!settings_dialog) {
-		settings_dialog = new RKSettings (parent);
+		settings_dialog = new RKSettings(parent);
 	}
 
-	if (page != NoPage) settings_dialog->raisePage (page);
-	settings_dialog->show ();
-	settings_dialog->raise ();
-}
-
-//static
-void RKSettings::configureSettings (const QString& page, QWidget *parent, RCommandChain *chain) {
-	RK_TRACE (SETTINGS);
-
-	if (page == QStringLiteral("rbackend")) {
-		RKSettings::configureSettings(RKSettings::PageR, parent, chain);
-	} else if (page == QStringLiteral("console")) {
-		RKSettings::configureSettings(RKSettings::PageConsole, parent, chain);
-	} else if (page == QStringLiteral("editor")) {
-		RKSettings::configureSettings(RKSettings::PageCommandEditor, parent, chain);
-	} else if (page == QStringLiteral("graphics")) {
-		RKSettings::configureSettings(RKSettings::PageX11, parent, chain);
-	} else if (page == QStringLiteral("browser")) {
-		RKSettings::configureSettings(RKSettings::PageObjectBrowser, parent, chain);
-	} else if (page == QStringLiteral("rpackages")) {
-		RKSettings::configureSettings(RKSettings::PageRPackages, parent, chain);
-	} else if (page == QStringLiteral("output")) {
-		RKSettings::configureSettings(RKSettings::PageOutput, parent, chain);
-	} else if (page == QStringLiteral("general")) {
-		RKSettings::configureSettings(RKSettings::PageGeneral, parent, chain);
-	} else if (page == QStringLiteral("addons")) {
-		RKSettings::configureSettings(RKSettings::SuperPageAddons, parent, chain);
-	} else if (page == QStringLiteral("plugins")) {
-		RKSettings::configureSettings(RKSettings::PagePlugins, parent, chain);
-	} else if (page == QStringLiteral("kateplugins")) {
-		RKSettings::configureSettings(RKSettings::PageKatePlugins, parent, chain);
-	} else {
-		RK_ASSERT(page.isEmpty());
-		RKSettings::configureSettings(RKSettings::NoPage, parent, chain);
+	if (page != "") {
+		settings_dialog->setCurrentPage(settings_dialog->findPage(page));
 	}
+	settings_dialog->show();
+	settings_dialog->raise();
 }
 
-//static
-void RKSettings::dialogClosed () {
-	RK_TRACE (SETTINGS);
-	settings_dialog = nullptr;
-}
-
-RKSettings::RKSettings (QWidget *parent) : KPageDialog (parent) {
-	RK_TRACE (SETTINGS);
+RKSettings::RKSettings(QWidget *parent) : KPageDialog (parent) {
+	RK_TRACE(SETTINGS);
+	RK_ASSERT(!settings_dialog);
 
-	setFaceType (KPageDialog::Tree);
-	setWindowTitle (i18n ("Settings"));
-	buttonBox ()->setStandardButtons (QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::Help);
-	button (QDialogButtonBox::Apply)->setEnabled (false);
-	connect (button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &RKSettings::applyAll);
-	connect (button(QDialogButtonBox::Help), &QPushButton::clicked, this, &RKSettings::helpClicked);
+	setFaceType(KPageDialog::Tree);
+	setWindowTitle(i18n("Settings"));
+	buttonBox()->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::Help);
+	button(QDialogButtonBox::Apply)->setEnabled(false);
+	connect(button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &RKSettings::applyAll);
+	connect(button(QDialogButtonBox::Help), &QPushButton::clicked, this, &RKSettings::helpClicked);
 
-	setAttribute (Qt::WA_DeleteOnClose, true);
+	setAttribute(Qt::WA_DeleteOnClose, true);
 
-	initModules ();
+	initDialogPages();
 
-	connect (this, &KPageDialog::currentPageChanged, this, &RKSettings::pageChange);
+	connect(this, &KPageDialog::currentPageChanged, this, &RKSettings::pageChange);
 	setCurrentPage(pages[0]); // init -> see pageChange
 }
 
 RKSettings::~RKSettings() {
-	RK_TRACE (SETTINGS);
-
-	ModuleMap::const_iterator it;
-	for (it = modules.constBegin (); it != modules.constEnd (); ++it) {
-		delete *it;
-	}
-	modules.clear ();
-
-	dialogClosed ();
-}
-
-void RKSettings::registerPageModule(RKSettings::SettingsPage super, int child) {
-	RK_TRACE (SETTINGS);
-
-	RKSettingsModule *childm = modules[child];
-	if (super == NoPage) {
-		pages[child] = addPage(childm, childm->caption());
-	} else {
-		pages[child] = addSubPage(pages[super], childm, childm->caption());
-	}
-	pages[child]->setHeader(childm->longCaption());
-	pages[child]->setIcon(childm->icon());
+	RK_TRACE(SETTINGS);
+	settings_dialog = nullptr;
 }
 
-#include <QLabel>
-
-void RKSettings::initModules () {
-	RK_TRACE (SETTINGS);
-
-	auto ktexteditorpages = RKSettingsModuleCommandEditor::kateConfigPages(this, nullptr);
-	pages.resize(NumPages + ktexteditorpages.size());
-	modules.insert (PagePlugins, new RKSettingsModulePlugins(this, nullptr));
-	modules.insert (PageKatePlugins, new RKSettingsModuleKatePlugins(this, nullptr));
-	modules.insert (PageR, new RKSettingsModuleR(this, nullptr));
-	modules.insert (PageRPackages, new RKSettingsModuleRPackages(this, nullptr));
-	modules.insert (PageGeneral, new RKSettingsModuleGeneral(this, nullptr));
-	modules.insert (PageOutput, new RKSettingsModuleOutput(this, nullptr));
-	modules.insert (PageX11, new RKSettingsModuleGraphics(this, nullptr));
-	modules.insert (PageWatch, new RKSettingsModuleWatch(this, nullptr));
-	modules.insert (PageConsole, new RKSettingsModuleConsole(this, nullptr));
-	modules.insert (PageCommandEditor, new RKSettingsModuleCommandEditor(this, nullptr));
-	modules.insert (PageObjectBrowser, new RKSettingsModuleObjectBrowser(this, nullptr));
-	modules.insert (PageDebug, new RKSettingsModuleDebug(this, nullptr));
-	for (int i = 0; i < ktexteditorpages.size(); ++i) {
-		modules.insert(NumPages+i, ktexteditorpages[i]);
-	}
+void RKSettings::initDialogPages() {
+	RK_TRACE(SETTINGS);
+	RK_ASSERT(pages.isEmpty());
 
-	QWidget *page = new QWidget();
-	auto layout = new QVBoxLayout(page);
-	QLabel *l = new QLabel(i18n("<h1>Add-ons</h1><p>RKWard add-ons come in a variety of forms, each with their own configuration options:</p><h2>R packages</h2><p><a href=\"rkward://settings/rpackages\">Add-ons to the R language itself</a>. These are usually downloaded from \"CRAN\". Some of these add-on packages may additionally contain RKWard plugins.</p><h2>RKWard plugins</h2><p><a href=\"rkward://settings/plugins\">Graphical dialogs to R functionality</a>. These plugins are usually pre-installed with RKWard, or with an R package. However, they can be activated/deactivated to help keep the menus manageable. Note that it is relatively easy to <a href=\"https://api.kde.org/doc/rkwardplugins/\">create your own custom dialogs as plugins</a>!</p><h2>Kate plugins</h2><p><a href=\"rkward://settings/kateplugins\">Plugins developed for Kate / KTextEditor</a>. These provide shared functionality that is useful in the context of text editing and IDE applications. These plugins are usually found pre-installed on your system. You can configure to load the plugins that are useful to your own workflow.</p>"));
-	l->setWordWrap(true);
-	connect(l, &QLabel::linkActivated, [=](const QString &url) { RKWorkplace::mainWorkplace()->openAnyUrl(QUrl(url)); });
-	layout->addWidget(l);
-	layout->addStretch();
-	pages[SuperPageAddons] = addPage(page, i18n("Add-ons"));
-	registerPageModule(SuperPageAddons, PageRPackages);
-	registerPageModule(SuperPageAddons, PagePlugins);
-	registerPageModule(SuperPageAddons, PageKatePlugins);
-	registerPageModule(NoPage, PageR);
-	registerPageModule(NoPage, PageGeneral);
-	registerPageModule(NoPage, PageOutput);
-	registerPageModule(NoPage, PageX11);
-	registerPageModule(NoPage, PageWatch);
-	registerPageModule(NoPage, PageConsole);
-	registerPageModule(NoPage, PageCommandEditor);
-	registerPageModule(NoPage, PageObjectBrowser);
-	registerPageModule(NoPage, PageDebug);
-	for (int i = 0; i < ktexteditorpages.size(); ++i) {
-		registerPageModule(PageCommandEditor, NumPages+i);
+	for (auto it = modules.constBegin(); it != modules.constEnd(); ++it) {
+		auto pagewidgets = (*it)->createPages(this);
+		for (auto pit = pagewidgets.constBegin(); pit != pagewidgets.constEnd(); ++pit) {
+			auto widget = *pit;
+			RK_ASSERT(widget->pageid != RKSettingsModule::no_page_id); // all toplevel pages shall have an id
+			auto superp = widget->superpageid;
+			KPageWidgetItem *page;
+			if (superp.isEmpty()) {
+				page = addPage(widget, widget->windowTitle());
+			} else {
+				auto superpage = findPage(superp);
+				RK_ASSERT(superpage);
+				page = addSubPage(superpage, widget, widget->windowTitle());
+			}
+			page->setHeader(widget->longCaption());
+			page->setIcon(widget->windowIcon());
+			connect(widget, &RKSettingsModuleWidget::settingsChanged, this, [this]() { button(QDialogButtonBox::Apply)->setEnabled(true); });
+			pages.append(page);
+		}
 	}
 }
 
-void RKSettings::raisePage (SettingsPage page) {
+KPageWidgetItem* RKSettings::findPage(const RKSettingsModule::PageId id) const {
 	RK_TRACE (SETTINGS);
-
-	if (page != NoPage) {
-		setCurrentPage (pages[(int) page]);
+	for (auto it = pages.constBegin(); it != pages.constEnd(); ++it) {
+		if (static_cast<RKSettingsModuleWidget*>((*it)->widget())->pageid == id) return *it;
 	}
+	RK_ASSERT(false);
+	return nullptr;
 }
 
 void RKSettings::pageChange (KPageWidgetItem *current, KPageWidgetItem *) {
 	RK_TRACE (SETTINGS);
-	RKSettingsModule *new_module = dynamic_cast<RKSettingsModule*> (current->widget ());
-
-	bool has_help;
-	if (!new_module) {
-		RK_ASSERT (false);
-		has_help = false;
-	} else {
-		has_help = !(new_module->helpURL ().isEmpty ());
-	}
-	button (QDialogButtonBox::Help)->setEnabled (has_help);
+	RKSettingsModuleWidget *current_page = dynamic_cast<RKSettingsModuleWidget*>(current->widget());
+	RK_ASSERT(current_page);
+
+	bool has_help = current_page && !current_page->helpURL().isEmpty();
+	button(QDialogButtonBox::Help)->setEnabled(has_help);
 }
 
 void RKSettings::done (int result) {
@@ -209,79 +128,73 @@ void RKSettings::done (int result) {
 void RKSettings::helpClicked () {
 	RK_TRACE (SETTINGS);
 
-	RKSettingsModule *current_module = dynamic_cast<RKSettingsModule*> (currentPage ()->widget ());
-	if (!current_module) {
-		RK_ASSERT (false);
+	RKSettingsModuleWidget *current_page = dynamic_cast<RKSettingsModuleWidget*>(currentPage()->widget());
+	if (!current_page) {
+		RK_ASSERT(false);
 		return;
 	}
-
-	RKWorkplace::mainWorkplace ()->openHelpWindow (current_module->helpURL ());
+	RKWorkplace::mainWorkplace()->openHelpWindow(current_page->helpURL());
 }
 
 void RKSettings::applyAll() {
 	RK_TRACE (SETTINGS);
 
-	for (auto it = modules.constBegin(); it != modules.constEnd(); ++it) {
-		if (it.value()->hasChanges()) {
-			it.value()->doApply();
-			it.value()->save(KSharedConfig::openConfig().data());
-			tracker()->signalSettingsChange(RKSettings::SettingsPage(it.key()));
+	QSet<RKSettingsModule*> changed_modules;
+	for (auto it = pages.constBegin(); it != pages.constEnd(); ++it) {
+		auto w = static_cast<RKSettingsModuleWidget*>((*it)->widget());
+		if (w->hasChanges()) {
+			w->doApply();
+			changed_modules.insert(w->parentModule());
 		}
 	}
+	for (auto it = changed_modules.constBegin(); it != changed_modules.constEnd(); ++it) {
+		(*it)->syncConfig(KSharedConfig::openConfig().data(), RKConfigBase::SaveConfig);
+		Q_EMIT (*it)->settingsChanged();
+	}
 	button(QDialogButtonBox::Apply)->setEnabled(false);
 }
 
-void RKSettings::enableApply () {
-	RK_TRACE (SETTINGS);
-	button (QDialogButtonBox::Apply)->setEnabled (true);
+void RKSettings::loadSettings(KConfig *config) {
+	RK_TRACE(SETTINGS);
+
+	if (modules.isEmpty()) {
+		QObject *p = RKWardMainWindow::getMain();
+		// NOTE: handle ModuleGeneral, first, as it contains paths used by other modules
+		modules.append(new RKSettingsModuleGeneral(p));
+		modules.append(new RKSettingsModulePlugins(p));
+		modules.append(new RKSettingsModuleKatePlugins(p));
+		modules.append(new RKSettingsModuleR(p));
+		modules.append(new RKSettingsModuleRPackages(p));
+		modules.append(new RKSettingsModuleOutput(p));
+		modules.append(new RKSettingsModuleGraphics(p));
+		modules.append(new RKSettingsModuleWatch(p));
+		modules.append(new RKSettingsModuleConsole(p));
+		modules.append(new RKSettingsModuleCommandEditor(p));
+		modules.append(new RKSettingsModuleObjectBrowser(p));
+		modules.append(new RKSettingsModuleDebug(p));
+	}
+	for(auto it = modules.constBegin(); it != modules.constEnd(); ++it) {
+		(*it)->syncConfig(config, RKConfigBase::LoadConfig);
+	}
 }
 
-#define FOREACH_SETTINGS_MODULE(X)           \
-	RKSettingsModuleGeneral::X;  /* always handle this first (esp., when loading settings), as it contains the base path for rkward files */ \
-	RKSettingsModuleKatePlugins::X;          \
-	RKSettingsModulePlugins::X;          \
-	RKSettingsModuleR::X;                \
-	RKSettingsModuleRPackages::X;        \
-	RKSettingsModuleOutput::X;           \
-	RKSettingsModuleGraphics::X;         \
-	RKSettingsModuleWatch::X;            \
-	RKSettingsModuleConsole::X;          \
-	RKSettingsModuleCommandEditor::X;    \
-	RKSettingsModuleObjectBrowser::X;
-
-void RKSettings::loadSettings (KConfig *config) {
-	RK_TRACE (SETTINGS);
+void RKSettings::saveSettings(KConfig *config) {
+	RK_TRACE(SETTINGS);
+	RK_ASSERT(!modules.isEmpty());
 
-	FOREACH_SETTINGS_MODULE(syncConfig(config, RKConfigBase::LoadConfig));
-}
-
-void RKSettings::saveSettings (KConfig *config) {
-	RK_TRACE (SETTINGS);
-
-	FOREACH_SETTINGS_MODULE(syncConfig(config, RKConfigBase::SaveConfig));
+	for(auto it = modules.constBegin(); it != modules.constEnd(); ++it) {
+		(*it)->syncConfig(config, RKConfigBase::SaveConfig);
+	}
 }
 
 QList<RKSetupWizardItem*> RKSettings::validateSettingsInteractive () {
 	RK_TRACE (SETTINGS);
 
 	QList<RKSetupWizardItem*> interaction_items;
-	FOREACH_SETTINGS_MODULE(validateSettingsInteractive(&interaction_items));
+	for(auto it = modules.constBegin(); it != modules.constEnd(); ++it) {
+		(*it)->validateSettingsInteractive(&interaction_items);
+	}
 	return interaction_items;
 }
 
 //############ END RKSettings ##################
-//############ BEGIN RKSettingsTracker ############
-
-RKSettingsTracker::RKSettingsTracker (QObject *parent) : QObject (parent) {
-	RK_TRACE (SETTINGS);
-}
-
-RKSettingsTracker::~RKSettingsTracker () {
-	RK_TRACE (SETTINGS);
-}
-
-void RKSettingsTracker::signalSettingsChange (RKSettings::SettingsPage page) {
-	RK_TRACE (SETTINGS);
-	Q_EMIT settingsChanged(page);
-}
-
diff --git a/rkward/settings/rksettings.h b/rkward/settings/rksettings.h
index 4f2d19b0d..39d609485 100644
--- a/rkward/settings/rksettings.h
+++ b/rkward/settings/rksettings.h
@@ -1,15 +1,16 @@
 /*
 rksettings - This file is part of the RKWard project. Created: Wed Jul 28 2004
-SPDX-FileCopyrightText: 2004-2020 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileCopyrightText: 2004-2024 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
 SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
 SPDX-License-Identifier: GPL-2.0-or-later
 */
 #ifndef RKSETTINGS_H
 #define RKSETTINGS_H
 
-#include <kpagedialog.h>
+#include <KPageDialog>
+#include <QMap>
 
-#include <qmap.h>
+#include "rksettingsmodule.h"
 
 class RKSettingsModule;
 class KConfig;
@@ -26,26 +27,7 @@ The main settings-dialog. Contains subsections (tabs) for different modules. Use
 class RKSettings : public KPageDialog {
 	Q_OBJECT
 public:
-	enum SettingsPage {
-		NoPage=0,
-		SuperPageAddons,
-		PagePlugins,
-		PageKatePlugins,
-		PageR,
-		PageRPackages,
-		PageGeneral,
-		PageOutput,
-		PageX11,
-		PageWatch,
-		PageConsole,
-		PageCommandEditor,
-		PageObjectBrowser,
-		PageDebug,
-		NumPages = PageDebug + 1
-	};
-
-	static void configureSettings (SettingsPage page=NoPage, QWidget *parent=nullptr, RCommandChain *chain=nullptr);
-	static void configureSettings (const QString& page, QWidget *parent=nullptr, RCommandChain *chain=nullptr);
+	static void configureSettings(const RKSettingsModule::PageId page, QWidget *parent=nullptr, RCommandChain *chain=nullptr);
 
 	static void loadSettings (KConfig *config);
 	static void saveSettings (KConfig *config);
@@ -53,8 +35,6 @@ public:
 	static QList<RKSetupWizardItem*> validateSettingsInteractive ();
 
 	void enableApply ();
-	
-	static RKSettingsTracker* tracker () { return settings_tracker; };
 public Q_SLOTS:
 	void pageChange (KPageWidgetItem *current, KPageWidgetItem *before);
 protected:
@@ -66,32 +46,13 @@ private Q_SLOTS:
 	void applyAll ();
 	void helpClicked ();
 private:
-	void initModules ();
-	void raisePage (SettingsPage page);
-	static void dialogClosed ();
-
-	typedef QMap<int, RKSettingsModule *> ModuleMap;
-	ModuleMap modules;
-	std::vector<KPageWidgetItem*> pages;
+friend class RKWardCoreTest;
+	static QList<RKSettingsModule *> modules;
+	void initDialogPages();
+	QList<KPageWidgetItem*> pages;
+	KPageWidgetItem *findPage(const RKSettingsModule::PageId id) const;
 
 	static RKSettings *settings_dialog;
-friend class RKWardMainWindow;
-	static RKSettingsTracker *settings_tracker;
-
-	void registerPageModule(SettingsPage super, int child);
-};
-
-/** This class represents a very simple QObject. It's only purpose is to emit signals when certain settings have changed. Classes that need to
-update themselves on certain changed settings should connect to those signals. */
-class RKSettingsTracker : public QObject {
-	Q_OBJECT
-public:
-	explicit RKSettingsTracker (QObject *parent);
-	~RKSettingsTracker ();
-
-	void signalSettingsChange (RKSettings::SettingsPage page);
-Q_SIGNALS:
-	void settingsChanged (RKSettings::SettingsPage);
 };
 
 #endif
diff --git a/rkward/settings/rksettingsmodule.cpp b/rkward/settings/rksettingsmodule.cpp
index 936175e3d..79b7d0aad 100644
--- a/rkward/settings/rksettingsmodule.cpp
+++ b/rkward/settings/rksettingsmodule.cpp
@@ -1,6 +1,6 @@
 /*
 rksettingsmodule - This file is part of RKWard (https://rkward.kde.org). Created: Wed Jul 28 2004
-SPDX-FileCopyrightText: 2004-2022 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileCopyrightText: 2004-2024 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
 SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
 SPDX-License-Identifier: GPL-2.0-or-later
 */
@@ -19,18 +19,22 @@ SPDX-License-Identifier: GPL-2.0-or-later
 //static
 RCommandChain* RKSettingsModule::chain = nullptr;
 
-RKSettingsModule::RKSettingsModule(RKSettings *gui, QWidget *parent) : RKSettingsModuleWidget (parent, nullptr) {
-	connect(this, &RKSettingsModule::settingsChanged, gui, &RKSettings::enableApply);
+RKSettingsModule::RKSettingsModule(QObject *parent) : QObject(parent) {
+	RK_TRACE(SETTINGS);
 }
 
 RKSettingsModule::~RKSettingsModule() {
+	RK_TRACE(SETTINGS);
 }
 
-RKSettingsModuleWidget::RKSettingsModuleWidget(QWidget *parent, RKSettingsModule *parent_module) : QWidget(parent), changed(false) {
-	if (parent_module) {
-		connect(this, &RKSettingsModuleWidget::settingsChanged, parent_module, &RKSettingsModuleWidget::change);
-		connect(parent_module, &RKSettingsModule::apply, this, &RKSettingsModuleWidget::doApply);
-	}
+RKSettingsModuleWidget::RKSettingsModuleWidget(QWidget *parent, RKSettingsModule *parent_module, const RKSettingsModule::PageId pageid, const RKSettingsModule::PageId superpageid) :
+	QWidget(parent),
+	changed(false),
+	pageid(pageid),
+	superpageid(superpageid),
+	parent_module(parent_module)
+{
+	RK_TRACE(SETTINGS);
 }
 
 void RKSettingsModuleWidget::change() {
diff --git a/rkward/settings/rksettingsmodule.h b/rkward/settings/rksettingsmodule.h
index 14864c65e..e2fec3f93 100644
--- a/rkward/settings/rksettingsmodule.h
+++ b/rkward/settings/rksettingsmodule.h
@@ -1,6 +1,6 @@
 /*
 rksettingsmodule - This file is part of the RKWard project. Created: Wed Jul 28 2004
-SPDX-FileCopyrightText: 2004-2022 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileCopyrightText: 2004-2024 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
 SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
 SPDX-License-Identifier: GPL-2.0-or-later
 */
@@ -124,20 +124,53 @@ private:
 	std::vector<RKConfigBase*> values;
 };
 
+class RKSettingsModuleWidget;
+
+/**
+Base class for settings modules. This provides a wrapper around the stored settings.
+
+UI pages (for the settings dialog) are created via pages().
+
+ at author Thomas Friedrichsmeier
+*/
+class RKSettingsModule : public QObject {
+	Q_OBJECT
+public:
+	RKSettingsModule(QObject *parent);
+	virtual ~RKSettingsModule();
+/** Some settings modules execute R commands on "apply". If an RCommandChain is specified for the RKSettings-dialog, those commands should
+be inserted into this chain. It's safe to use this unconditionally, as if there is no chain, this will return 0, which corresponds to using the top-level chain */
+	RCommandChain *commandChain () { return chain; };
+	typedef QLatin1StringView PageId;
+	static constexpr PageId no_page_id = QLatin1String("");
+Q_SIGNALS:
+	void settingsChanged();
+protected:
+	virtual QList<RKSettingsModuleWidget*> createPages(QWidget *parent) = 0;
+private:
+friend class RKSettings;
+	static RCommandChain *chain;
+	virtual void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) = 0;
+	virtual void validateSettingsInteractive (QList<RKSetupWizardItem*>*) {};
+};
+
 /** Base class for UI widgets operating on an RKSettingsModule. For now this is used, only where similar settings are shared across modules (e.g. code completion). Eventually, this could be used to disentangle RKSettingsModule from QWidget. */
 class RKSettingsModuleWidget : public QWidget {
 	Q_OBJECT
 public:
-	RKSettingsModuleWidget(QWidget *parent, RKSettingsModule *parent_module);
+	RKSettingsModuleWidget(QWidget *parent, RKSettingsModule *parent_module, const RKSettingsModule::PageId pageid, const RKSettingsModule::PageId superpageid = RKSettingsModule::no_page_id);
 	~RKSettingsModuleWidget() {};
 	virtual void applyChanges() = 0;
 /** Mark this module as "changed" (propagates to parent module) */
-	void change ();
-	bool hasChanges () { return changed; };
+	void change();
+	bool hasChanges() const { return changed; };
+	virtual QString longCaption() const { return windowTitle(); };
+	RKSettingsModule* parentModule() const { return parent_module; };
 Q_SIGNALS:
 	void settingsChanged();
 	void apply();
 protected:
+	QUrl helpURL() { return help_url; };
 	bool changed;
 /** temporary indirection until applyChanges() has been obsolete, everywhere */
 	void doApply() {
@@ -145,31 +178,11 @@ protected:
 		applyChanges();
 		changed = false;
 	}
-};
-
-/**
-Base class for settings modules. Provides some pure virtual calls.
-
- at author Thomas Friedrichsmeier
-*/
-class RKSettingsModule : public RKSettingsModuleWidget {
-public:
-	RKSettingsModule (RKSettings *gui, QWidget *parent);
-	virtual ~RKSettingsModule ();
-
-	virtual void save (KConfig *config) = 0;
-	
-	virtual QString caption() const = 0;
-	virtual QString longCaption() const { return caption(); };
-	virtual QIcon icon() const { return QIcon(); };
-/** Some settings modules execute R commands on "apply". If an RCommandChain is specified for the RKSettings-dialog, those commands should
-be inserted into this chain. It's safe to use this unconditionally, as if there is no chain, this will return 0, which corresponds to using the top-level chain */
-	RCommandChain *commandChain () { return chain; };
-
-	virtual QUrl helpURL () { return QUrl (); };
-private:
 friend class RKSettings;
-	static RCommandChain *chain;
+	const RKSettingsModule::PageId pageid;
+	const RKSettingsModule::PageId superpageid;
+	RKSettingsModule *parent_module;
+	QUrl help_url;
 };
 
 #endif
diff --git a/rkward/settings/rksettingsmodulecommandeditor.cpp b/rkward/settings/rksettingsmodulecommandeditor.cpp
index adc90727d..c987cf336 100644
--- a/rkward/settings/rksettingsmodulecommandeditor.cpp
+++ b/rkward/settings/rksettingsmodulecommandeditor.cpp
@@ -1,6 +1,6 @@
 /*
 rksettingsmodulecommandeditor - This file is part of RKWard (https://rkward.kde.org). Created: Tue Oct 23 2007
-SPDX-FileCopyrightText: 2007-2022 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileCopyrightText: 2007-2024 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
 SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
 SPDX-License-Identifier: GPL-2.0-or-later
 */
@@ -36,7 +36,7 @@ RKConfigValue<bool> RKSettingsModuleCommandEditor::autosave_keep { "Autosave kee
 RKConfigValue<int> RKSettingsModuleCommandEditor::autosave_interval {"Autosave interval", 5 };
 RKConfigValue<QString> RKSettingsModuleCommandEditor::script_file_filter { "Script file filter", "*.R *.S *.q *.Rhistory" };
 
-RKCodeCompletionSettingsWidget::RKCodeCompletionSettingsWidget(QWidget *parent, RKSettingsModule *module, RKCodeCompletionSettings *settings, bool show_common) : RKSettingsModuleWidget(parent, module), settings(settings) {
+RKCodeCompletionSettingsWidget::RKCodeCompletionSettingsWidget(QWidget *parent, RKSettingsModule *module, RKCodeCompletionSettings *settings, bool show_common) : RKSettingsModuleWidget(parent, module, RKSettingsModule::no_page_id), settings(settings) {
 	RK_TRACE (SETTINGS);
 	QVBoxLayout* main_vbox = new QVBoxLayout (this);
 	main_vbox->setContentsMargins(0,0,0,0);
@@ -113,61 +113,101 @@ void RKCodeCompletionSettingsWidget::makeCompletionTypeBoxes(const QStringList&
 	}
 }
 
-RKSettingsModuleCommandEditor::RKSettingsModuleCommandEditor (RKSettings *gui, QWidget *parent) : RKSettingsModule (gui, parent) {
+RKSettingsModuleCommandEditor::RKSettingsModuleCommandEditor(QObject *parent) : RKSettingsModule(parent) {
 	RK_TRACE (SETTINGS);
+}
 
-	QVBoxLayout* main_vbox = new QVBoxLayout (this);
-	main_vbox->addWidget (RKCommonFunctions::wordWrappedLabel (i18n ("Settings marked with (*) do not take effect until you restart RKWard")));
-	main_vbox->addSpacing (2 * RKStyle::spacingHint ());
+RKSettingsModuleCommandEditor::~RKSettingsModuleCommandEditor() {
+	RK_TRACE (SETTINGS);
+}
 
-	main_vbox->addWidget (completion_settings_widget = new RKCodeCompletionSettingsWidget (this, this, &completion_settings, true));
+class RKSettingsPageCommandEditor : public RKSettingsModuleWidget {
+public:
+	RKSettingsPageCommandEditor(QWidget* parent, RKSettingsModuleCommandEditor *parent_module) : 
+		RKSettingsModuleWidget(parent, parent_module, RKSettingsModuleCommandEditor::page_id)
+	{
+		setWindowTitle(i18n("Script editor"));
+		setWindowIcon(RKStandardIcons::getIcon(RKStandardIcons::WindowCommandEditor));
 
-	main_vbox->addSpacing (2 * RKStyle::spacingHint ());
+		QVBoxLayout* main_vbox = new QVBoxLayout(this);
+		main_vbox->addWidget(RKCommonFunctions::wordWrappedLabel(i18n("Settings marked with (*) do not take effect until you restart RKWard")));
+		main_vbox->addSpacing(2 * RKStyle::spacingHint());
 
-	QGroupBox *group = autosave_enabled_box = new QGroupBox (i18n ("Autosaves"), this);
-	autosave_enabled_box->setCheckable (true);
-	autosave_enabled_box->setChecked (autosave_enabled);
-	connect (autosave_enabled_box, &QGroupBox::toggled, this, &RKSettingsModule::change);
-	QFormLayout *form_layout = new QFormLayout (group);
+		main_vbox->addWidget(completion_settings_widget = new RKCodeCompletionSettingsWidget(this, parent_module, &parent_module->completion_settings, true));
 
-	form_layout->addRow(i18n("Autosave interval (minutes)"), autosave_interval.makeSpinBox(1, INT_MAX, this));
+		main_vbox->addSpacing(2 * RKStyle::spacingHint());
 
-	form_layout->addRow(autosave_keep.makeCheckbox(i18n("Keep autosave file after manual save"), this));
+		QGroupBox *group = autosave_enabled_box = new QGroupBox(i18n("Autosaves"), this);
+		autosave_enabled_box->setCheckable(true);
+		autosave_enabled_box->setChecked(RKSettingsModuleCommandEditor::autosave_enabled);
+		connect(autosave_enabled_box, &QGroupBox::toggled, this, &RKSettingsPageCommandEditor::change);
+		QFormLayout *form_layout = new QFormLayout(group);
 
-	main_vbox->addWidget (group);
+		form_layout->addRow(i18n("Autosave interval (minutes)"), RKSettingsModuleCommandEditor::autosave_interval.makeSpinBox(1, INT_MAX, this));
 
-	main_vbox->addSpacing (2 * RKStyle::spacingHint ());
+		form_layout->addRow(RKSettingsModuleCommandEditor::autosave_keep.makeCheckbox(i18n("Keep autosave file after manual save"), this));
 
-	script_file_filter_box = new QLineEdit();
-	script_file_filter_box->setText(script_file_filter);
-	RKCommonFunctions::setTips(i18n("<p>A list of filters (file name extensions) that should be treated as R script files. Most importantly, files matching one of these filters will always be opened with R syntax highlighting.</p><p>Filters are case insensitive.</p>"), script_file_filter_box);
-	connect(script_file_filter_box, &QLineEdit::textChanged, this, &RKSettingsModule::change);
-	main_vbox->addWidget(new QLabel(i18n("R script file filters (separated by spaces)")));
-	main_vbox->addWidget(script_file_filter_box);
+		main_vbox->addWidget(group);
 
-	main_vbox->addStretch ();
-}
+		main_vbox->addSpacing(2 * RKStyle::spacingHint ());
 
-RKSettingsModuleCommandEditor::~RKSettingsModuleCommandEditor () {
-	RK_TRACE (SETTINGS);
-}
+		script_file_filter_box = new QLineEdit();
+		script_file_filter_box->setText(RKSettingsModuleCommandEditor::script_file_filter);
+		RKCommonFunctions::setTips(i18n("<p>A list of filters (file name extensions) that should be treated as R script files. Most importantly, files matching one of these filters will always be opened with R syntax highlighting.</p><p>Filters are case insensitive.</p>"), script_file_filter_box);
+		connect(script_file_filter_box, &QLineEdit::textChanged, this, &RKSettingsPageCommandEditor::change);
+		main_vbox->addWidget(new QLabel(i18n("R script file filters (separated by spaces)")));
+		main_vbox->addWidget(script_file_filter_box);
 
-QString RKSettingsModuleCommandEditor::caption() const {
-	RK_TRACE(SETTINGS);
-	return(i18n("Script editor"));
-}
-
-QIcon RKSettingsModuleCommandEditor::icon() const {
-	RK_TRACE(SETTINGS);
-	return RKStandardIcons::getIcon(RKStandardIcons::WindowCommandEditor);
-}
+		main_vbox->addStretch();
+	}
+	void applyChanges() {
+		RK_TRACE(SETTINGS);
 
-void RKSettingsModuleCommandEditor::applyChanges () {
-	RK_TRACE (SETTINGS);
+		completion_settings_widget->applyChanges();
+		RKSettingsModuleCommandEditor::autosave_enabled = autosave_enabled_box->isChecked();
+		RKSettingsModuleCommandEditor::script_file_filter = script_file_filter_box->text();
+	}
+private:
+	RKCodeCompletionSettingsWidget *completion_settings_widget;
+	QGroupBox* autosave_enabled_box;
+	QLineEdit* script_file_filter_box;
+};
+
+class RKTextEditorConfigPageWrapper : public RKSettingsModuleWidget {
+public:
+	RKTextEditorConfigPageWrapper(QWidget* parent, RKSettingsModule *parent_module, KTextEditor::ConfigPage* wrapped) :
+		RKSettingsModuleWidget(parent, parent_module, QLatin1String(("kate_" + wrapped->name()).toLatin1()), RKSettingsModuleCommandEditor::page_id),
+		page(wrapped)
+	{
+		RK_TRACE(SETTINGS);
+		setWindowTitle(page->name());
+		setWindowIcon(page->icon());
+		
+		auto vbox = new QVBoxLayout(this);
+		vbox->setContentsMargins(0,0,0,0);
+		vbox->addWidget(wrapped);
+		connect(wrapped, &KTextEditor::ConfigPage::changed, this, &RKTextEditorConfigPageWrapper::change);
+	}
+	void applyChanges() override {
+		page->apply();
+	}
+	QString longCaption() const override {
+		return page->fullName();
+	}
+private:
+	KTextEditor::ConfigPage* page;
+};
 
-	completion_settings_widget->applyChanges ();
-	autosave_enabled = autosave_enabled_box->isChecked ();
-	script_file_filter = script_file_filter_box->text ();
+QList<RKSettingsModuleWidget*> RKSettingsModuleCommandEditor::createPages(QWidget *parent) {
+	RK_TRACE(SETTINGS);
+	QList<RKSettingsModuleWidget*> ret;
+	ret.append(new RKSettingsPageCommandEditor(parent, this));
+	auto ed = KTextEditor::Editor::instance();
+	int n = ed->configPages();
+	for (int i = 0; i < n; ++i) {
+		ret.append(new RKTextEditorConfigPageWrapper(parent, this, ed->configPage(i, parent)));
+	}
+	return ret;
 }
 
 QString completionTypeToConfigKey (int cat) {
@@ -204,45 +244,3 @@ bool RKSettingsModuleCommandEditor::matchesScriptFileFilter (const QString &file
 	}
 	return false;
 }
-
-
-QList<RKSettingsModuleKTextEditorConfigWrapper *> RKSettingsModuleCommandEditor::kateConfigPages(RKSettings* gui, QWidget* parent) {
-	RK_TRACE(SETTINGS);
-
-	QList<RKSettingsModuleKTextEditorConfigWrapper *> ret;
-	auto ed = KTextEditor::Editor::instance();
-	int n = ed->configPages();
-	for (int i = 0; i < n; ++i) {
-		ret.append(new RKSettingsModuleKTextEditorConfigWrapper(gui, parent, ed->configPage(i, parent)));
-	}
-	return ret;
-}
-
-
-RKSettingsModuleKTextEditorConfigWrapper::RKSettingsModuleKTextEditorConfigWrapper(RKSettings* gui, QWidget* parent, KTextEditor::ConfigPage* wrapped) : RKSettingsModule(gui, parent), page(wrapped) {
-	RK_TRACE(SETTINGS);
-	auto vbox = new QVBoxLayout(this);
-	vbox->setContentsMargins(0,0,0,0);
-	vbox->addWidget(wrapped);
-	connect(wrapped, &KTextEditor::ConfigPage::changed, this, &RKSettingsModuleKTextEditorConfigWrapper::change);
-}
-
-RKSettingsModuleKTextEditorConfigWrapper::~RKSettingsModuleKTextEditorConfigWrapper() {
-	RK_TRACE(SETTINGS);
-}
-
-void RKSettingsModuleKTextEditorConfigWrapper::applyChanges() {
-	page->apply();
-}
-
-QString RKSettingsModuleKTextEditorConfigWrapper::caption() const {
-	return page->name();
-}
-
-QString RKSettingsModuleKTextEditorConfigWrapper::longCaption() const {
-	return page->fullName();
-}
-
-QIcon RKSettingsModuleKTextEditorConfigWrapper::icon() const {
-	return page->icon();
-}
diff --git a/rkward/settings/rksettingsmodulecommandeditor.h b/rkward/settings/rksettingsmodulecommandeditor.h
index 1bfe1bd7d..1916b7aa0 100644
--- a/rkward/settings/rksettingsmodulecommandeditor.h
+++ b/rkward/settings/rksettingsmodulecommandeditor.h
@@ -1,6 +1,6 @@
 /*
 rksettingsmodulecommandeditor - This file is part of the RKWard project. Created: Tue Oct 23 2007
-SPDX-FileCopyrightText: 2007-2022 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileCopyrightText: 2007-2024 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
 SPDX-FileContributor: The RKWard Team <rkward-devel at kde.org>
 SPDX-License-Identifier: GPL-2.0-or-later
 */
@@ -76,20 +76,6 @@ private:
 	RKCodeCompletionSettings *settings;
 };
 
-class RKSettingsModuleKTextEditorConfigWrapper : public RKSettingsModule {
-public:
-	RKSettingsModuleKTextEditorConfigWrapper(RKSettings* gui, QWidget* parent, KTextEditor::ConfigPage* wrapped);
-	~RKSettingsModuleKTextEditorConfigWrapper();
-	void applyChanges() override;
-	void save(KConfig *) override { };
-	static void validateSettingsInteractive(QList<RKSetupWizardItem*>*) {};
-	QString caption() const override;
-	QString longCaption() const override;
-	QIcon icon() const override;
-private:
-	KTextEditor::ConfigPage* page;
-};
-
 /**
 configuration for the Command Editor windows
 
@@ -98,16 +84,11 @@ configuration for the Command Editor windows
 class RKSettingsModuleCommandEditor : public RKSettingsModule {
 	Q_OBJECT
 public:
-	RKSettingsModuleCommandEditor (RKSettings *gui, QWidget *parent);
-	~RKSettingsModuleCommandEditor ();
+	RKSettingsModuleCommandEditor(QObject *parent);
+	~RKSettingsModuleCommandEditor();
 	
-	void applyChanges () override;
-	void save (KConfig *config) override { syncConfig(config, RKConfigBase::SaveConfig); };
-	static void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction);
-	static void validateSettingsInteractive (QList<RKSetupWizardItem*>*) {};
-
-	QString caption() const override;
-	QIcon icon() const override;
+	void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) override;
+	QList<RKSettingsModuleWidget*> createPages(QWidget *parent) override;
 
 	static const RKCodeCompletionSettings* completionSettings() { return &completion_settings; };
 
@@ -119,17 +100,14 @@ public:
 	static QString scriptFileFilter () { return script_file_filter; };
 	static bool matchesScriptFileFilter (const QString &filename);
 
-	static QList<RKSettingsModuleKTextEditorConfigWrapper*> kateConfigPages(RKSettings* gui, QWidget* parent);
+	static constexpr PageId page_id = QLatin1String("editor");
 private:
+friend class RKSettingsPageCommandEditor;
 	static RKCodeCompletionSettings completion_settings;
 	static RKConfigValue<bool> autosave_enabled;
 	static RKConfigValue<bool> autosave_keep;
 	static RKConfigValue<int> autosave_interval;
 
-	RKCodeCompletionSettingsWidget *completion_settings_widget;
-	QGroupBox* autosave_enabled_box;
-	QLineEdit* script_file_filter_box;
-
 	static RKConfigValue<QString> script_file_filter;
 };
 
diff --git a/rkward/settings/rksettingsmoduleconsole.cpp b/rkward/settings/rksettingsmoduleconsole.cpp
index dc630b3b6..0db57e470 100644
--- a/rkward/settings/rksettingsmoduleconsole.cpp
+++ b/rkward/settings/rksettingsmoduleconsole.cpp
@@ -35,43 +35,66 @@ RKConfigValue<bool> RKSettingsModuleConsole::context_sensitive_history_by_defaul
 RKConfigValue<bool> RKSettingsModuleConsole::show_minimap {"show minimap", true};
 RKConfigValue<bool> RKSettingsModuleConsole::word_wrap {"dynamic word wrap", false};
 
-RKSettingsModuleConsole::RKSettingsModuleConsole (RKSettings *gui, QWidget *parent) : RKSettingsModule (gui, parent) {
-	RK_TRACE (SETTINGS);
+class RKSettingsPageConsole : public RKSettingsModuleWidget {
+public:
+	RKSettingsPageConsole(QWidget* parent, RKSettingsModule* parent_module) : RKSettingsModuleWidget(parent, parent_module, RKSettingsModuleConsole::page_id) {
+		RK_TRACE(SETTINGS);
+
+		setWindowTitle(i18n("Console"));
+		setWindowIcon(RKStandardIcons::getIcon(RKStandardIcons::WindowConsole));
+		help_url = QUrl("rkward://page/rkward_console#settings");
+
+		QVBoxLayout *vbox = new QVBoxLayout(this);
 
-	QVBoxLayout *vbox = new QVBoxLayout (this);
+		vbox->addWidget(completion_settings_widget = new RKCodeCompletionSettingsWidget(this, parentModule(), &RKSettingsModuleConsole::completion_settings, false));
 
-	vbox->addWidget (completion_settings_widget = new RKCodeCompletionSettingsWidget (this, this, &completion_settings, false));
+		vbox->addWidget(RKSettingsModuleConsole::save_history.makeCheckbox(i18n("Load/Save command history"), this));
 
-	vbox->addWidget (save_history.makeCheckbox(i18n("Load/Save command history"), this));
+		vbox->addWidget(new QLabel(i18n("Maximum length of command history (0 for no limit)")));
+		auto max_history_length_spinner = RKSettingsModuleConsole::max_history_length.makeSpinBox(0, 10000, this);
+		vbox->addWidget(max_history_length_spinner);
 
-	vbox->addWidget (new QLabel (i18n ("Maximum length of command history (0 for no limit)"), this));
-	auto max_history_length_spinner = max_history_length.makeSpinBox(0, 10000, this);
-	vbox->addWidget (max_history_length_spinner);
+		vbox->addWidget(new QLabel(i18n("Maximum number of paragraphs/lines to display in the console (0 for not limit)")));
+		auto max_console_lines_spinner = RKSettingsModuleConsole::max_console_lines.makeSpinBox(0, 10000, this);
+		vbox->addWidget(max_console_lines_spinner);
 
-	vbox->addWidget (new QLabel (i18n ("Maximum number of paragraphs/lines to display in the console (0 for not limit)"), this));
-	auto max_console_lines_spinner = max_console_lines.makeSpinBox(0, 10000, this);
-	vbox->addWidget (max_console_lines_spinner);
+		vbox->addSpacing(2*RKStyle::spacingHint());
 
-	vbox->addSpacing (2*RKStyle::spacingHint ());
+		auto pipe_user_commands_through_console_box = RKSettingsModuleConsole::pipe_user_commands_through_console.makeCheckbox(i18n("Run commands from script editor through console"), this);
+		vbox->addWidget(pipe_user_commands_through_console_box);
 
-	auto pipe_user_commands_through_console_box = pipe_user_commands_through_console.makeCheckbox(i18n("Run commands from script editor through console"), this);
-	vbox->addWidget(pipe_user_commands_through_console_box);
+		vbox->addWidget(new QLabel(i18n("Also add those commands to console history")));
+		auto add_piped_commands_to_history_box = RKSettingsModuleConsole::add_piped_commands_to_history.makeDropDown(RKConfigBase::LabelList({
+			{(int) RKSettingsModuleConsole::DontAdd, i18n("Do not add")},
+			{(int) RKSettingsModuleConsole::AddSingleLine, i18n("Add only if single line")},
+			{(int) RKSettingsModuleConsole::AlwaysAdd, i18n("Add all commands")}
+		}), this);
+		add_piped_commands_to_history_box->setEnabled(pipe_user_commands_through_console_box->isChecked());
+		connect(pipe_user_commands_through_console_box, &QCheckBox::stateChanged, add_piped_commands_to_history_box, [add_piped_commands_to_history_box](int state) {
+			add_piped_commands_to_history_box->setEnabled(state);
+		});
+		vbox->addWidget(add_piped_commands_to_history_box);
 
-	vbox->addWidget (new QLabel (i18n ("Also add those commands to console history"), this));
-	auto add_piped_commands_to_history_box = add_piped_commands_to_history.makeDropDown(RKConfigBase::LabelList(
-		{{(int) DontAdd, i18n("Do not add")}, {(int) AddSingleLine, i18n("Add only if single line")}, {(int) AlwaysAdd, i18n("Add all commands")}}
-	), this);
-	add_piped_commands_to_history_box->setEnabled(pipe_user_commands_through_console_box->isChecked());
-	connect(pipe_user_commands_through_console_box, &QCheckBox::stateChanged, add_piped_commands_to_history_box, [add_piped_commands_to_history_box](int state) {
-		add_piped_commands_to_history_box->setEnabled(state);
-	});
-	vbox->addWidget (add_piped_commands_to_history_box);
+		vbox->addSpacing(2*RKStyle::spacingHint());
 
-	vbox->addSpacing (2*RKStyle::spacingHint ());
+		vbox->addWidget(RKSettingsModuleConsole::context_sensitive_history_by_default.makeCheckbox(i18n("Command history is context sensitive by default"), this));
+
+		vbox->addStretch();
+	}
+	void applyChanges() override {
+		RK_TRACE(SETTINGS);
+		completion_settings_widget->applyChanges();
+	}
+private:
+	RKCodeCompletionSettingsWidget *completion_settings_widget;
+};
 
-	vbox->addWidget (context_sensitive_history_by_default.makeCheckbox(i18n("Command history is context sensitive by default"), this));
+RKSettingsModuleConsole::RKSettingsModuleConsole(QObject *parent) : RKSettingsModule(parent) {
+	RK_TRACE(SETTINGS);
+}
 
-	vbox->addStretch ();
+QList<RKSettingsModuleWidget*> RKSettingsModuleConsole::createPages(QWidget *parent) {
+	return QList<RKSettingsModuleWidget*>{ new RKSettingsPageConsole(parent, this) };
 }
 
 RKSettingsModuleConsole::~RKSettingsModuleConsole () {
@@ -123,22 +146,3 @@ void RKSettingsModuleConsole::saveCommandHistory (const QStringList &list) {
 	}
 	cg.sync ();
 }
-
-void RKSettingsModuleConsole::applyChanges () {
-	RK_TRACE (SETTINGS);
-
-	completion_settings_widget->applyChanges();
-}
-	
-QString RKSettingsModuleConsole::caption() const {
-	RK_TRACE(SETTINGS);
-
-	return(i18n("Console"));
-}
-
-QIcon RKSettingsModuleConsole::icon() const {
-	RK_TRACE(SETTINGS);
-
-	return RKStandardIcons::getIcon(RKStandardIcons::WindowConsole);
-}
-
diff --git a/rkward/settings/rksettingsmoduleconsole.h b/rkward/settings/rksettingsmoduleconsole.h
index 78e4a5fdd..10049bc64 100644
--- a/rkward/settings/rksettingsmoduleconsole.h
+++ b/rkward/settings/rksettingsmoduleconsole.h
@@ -23,13 +23,12 @@ Settings module for the console. Allows you to configure whether to store comman
 class RKSettingsModuleConsole : public RKSettingsModule {
 Q_OBJECT
 public:
-	RKSettingsModuleConsole (RKSettings *gui, QWidget *parent);
-	~RKSettingsModuleConsole ();
+	RKSettingsModuleConsole(QObject *parent);
+	~RKSettingsModuleConsole();
 
-	void save(KConfig *config) override { syncConfig(config, RKConfigBase::SaveConfig); };
-	static void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction);
-	static void validateSettingsInteractive (QList<RKSetupWizardItem*>*) {};
-	void applyChanges () override;
+	void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) override;
+	QList<RKSettingsModuleWidget*> createPages(QWidget *parent) override;
+	static constexpr PageId page_id = QLatin1String("console");
 
 	static bool saveHistory () { return save_history; };
 	static uint maxHistoryLength () { return max_history_length; };
@@ -51,12 +50,8 @@ public:
 
 	static QStringList loadCommandHistory ();
 	static void saveCommandHistory (const QStringList &list);
-
-	QString caption() const override;
-	QIcon icon() const override;
-
-	QUrl helpURL () override { return QUrl ("rkward://page/rkward_console#settings"); };
 private:
+friend class RKSettingsPageConsole;
 	static RKCodeCompletionSettings completion_settings;
 	static RKConfigValue<bool> save_history;
 	static RKConfigValue<uint> max_history_length;
@@ -66,8 +61,6 @@ private:
 	static RKConfigValue<bool> context_sensitive_history_by_default;
 	static RKConfigValue<bool> show_minimap;
 	static RKConfigValue<bool> word_wrap;
-
-	RKCodeCompletionSettingsWidget *completion_settings_widget;
 };
 
 #endif
diff --git a/rkward/settings/rksettingsmoduledebug.cpp b/rkward/settings/rksettingsmoduledebug.cpp
index 0bf7a5eef..86e0f57a2 100644
--- a/rkward/settings/rksettingsmoduledebug.cpp
+++ b/rkward/settings/rksettingsmoduledebug.cpp
@@ -24,92 +24,98 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "../debug.h"
 
-RKSettingsModuleDebug::RKSettingsModuleDebug (RKSettings *gui, QWidget *parent) : RKSettingsModule (gui, parent) {
-	RK_TRACE (SETTINGS);
-
-	QVBoxLayout* main_vbox = new QVBoxLayout (this);
-
-	main_vbox->addWidget (RKCommonFunctions::wordWrappedLabel (i18n ("<b>These settings are for debugging purposes, only.</b> It is safe to leave them untouched. Also, these settings will only apply to the current session, and will not be saved.")));
-
-	main_vbox->addSpacing (2 * RKStyle::spacingHint ());
-
-	QLabel* label = new QLabel (i18n ("Debug level"), this);
-	debug_level_box = new RKSpinBox (this);
-	debug_level_box->setIntMode (DL_TRACE, DL_FATAL, DL_FATAL - RK_Debug::RK_Debug_Level);
-	connect (debug_level_box, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &RKSettingsModuleDebug::settingChanged);
-	main_vbox->addWidget (label);
-	main_vbox->addWidget (debug_level_box);
-
-
-	debug_flags_group = new QButtonGroup (this);
-	debug_flags_group->setExclusive (false);
-	QGroupBox* group = new QGroupBox (i18n ("Debug flags"), this);
-	QVBoxLayout* box_layout = new QVBoxLayout (group);
-
-	debug_flags_group->addButton (new QCheckBox ("APP", group), APP);
-	debug_flags_group->addButton (new QCheckBox ("PLUGIN", group), PLUGIN);
-	debug_flags_group->addButton (new QCheckBox ("OBJECTS", group), OBJECTS);
-	debug_flags_group->addButton (new QCheckBox ("EDITOR", group), EDITOR);
-	debug_flags_group->addButton (new QCheckBox ("SETTINGS", group), SETTINGS);
-	debug_flags_group->addButton (new QCheckBox ("PHP", group), PHP);
-	debug_flags_group->addButton (new QCheckBox ("RBACKEND", group), RBACKEND);
-	debug_flags_group->addButton (new QCheckBox ("COMMANDEDITOR", group), COMMANDEDITOR);
-	debug_flags_group->addButton (new QCheckBox ("MISC", group), MISC);
-	debug_flags_group->addButton (new QCheckBox ("DIALOGS", group), DIALOGS);
-	debug_flags_group->addButton (new QCheckBox ("OUTPUT", group), OUTPUT);
-	debug_flags_group->addButton (new QCheckBox ("XML", group), XML);
-	debug_flags_group->addButton (new QCheckBox ("GRAPHICS_DEVICE", group), GRAPHICS_DEVICE);
-
-	QList<QAbstractButton*> buttons = debug_flags_group->buttons ();
-	for (QList<QAbstractButton*>::const_iterator it = buttons.constBegin (); it != buttons.constEnd (); ++it) {
-		box_layout->addWidget (*it);
-		(*it)->setChecked (RK_Debug::RK_Debug_Flags & debug_flags_group->id (*it));
+class RKSettingsPageDebug : public RKSettingsModuleWidget {
+public:
+	RKSettingsPageDebug(QWidget* parent, RKSettingsModule* parent_module) : RKSettingsModuleWidget(parent, parent_module, RKSettingsModuleDebug::page_id) {
+		RK_TRACE(SETTINGS);
+
+		setWindowTitle(i18n("Debug"));
+
+		QVBoxLayout* main_vbox = new QVBoxLayout(this);
+
+		main_vbox->addWidget(RKCommonFunctions::wordWrappedLabel(i18n("<b>These settings are for debugging purposes, only.</b> It is safe to leave them untouched. Also, these settings will only apply to the current session, and will not be saved.")));
+
+		main_vbox->addSpacing(2 * RKStyle::spacingHint());
+
+		QLabel* label = new QLabel(i18n("Debug level"), this);
+		debug_level_box = new RKSpinBox(this);
+		debug_level_box->setIntMode(DL_TRACE, DL_FATAL, DL_FATAL - RK_Debug::RK_Debug_Level);
+		connect(debug_level_box, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &RKSettingsPageDebug::change);
+		main_vbox->addWidget(label);
+		main_vbox->addWidget(debug_level_box);
+
+
+		debug_flags_group = new QButtonGroup(this);
+		debug_flags_group->setExclusive(false);
+		QGroupBox* group = new QGroupBox(i18n("Debug flags"), this);
+		QVBoxLayout* box_layout = new QVBoxLayout(group);
+
+		debug_flags_group->addButton(new QCheckBox("APP", group), APP);
+		debug_flags_group->addButton(new QCheckBox("PLUGIN", group), PLUGIN);
+		debug_flags_group->addButton(new QCheckBox("OBJECTS", group), OBJECTS);
+		debug_flags_group->addButton(new QCheckBox("EDITOR", group), EDITOR);
+		debug_flags_group->addButton(new QCheckBox("SETTINGS", group), SETTINGS);
+		debug_flags_group->addButton(new QCheckBox("PHP", group), PHP);
+		debug_flags_group->addButton(new QCheckBox("RBACKEND", group), RBACKEND);
+		debug_flags_group->addButton(new QCheckBox("COMMANDEDITOR", group), COMMANDEDITOR);
+		debug_flags_group->addButton(new QCheckBox("MISC", group), MISC);
+		debug_flags_group->addButton(new QCheckBox("DIALOGS", group), DIALOGS);
+		debug_flags_group->addButton(new QCheckBox("OUTPUT", group), OUTPUT);
+		debug_flags_group->addButton(new QCheckBox("XML", group), XML);
+		debug_flags_group->addButton(new QCheckBox("GRAPHICS_DEVICE", group), GRAPHICS_DEVICE);
+
+		QList<QAbstractButton*> buttons = debug_flags_group->buttons();
+		for (QList<QAbstractButton*>::const_iterator it = buttons.constBegin(); it != buttons.constEnd(); ++it) {
+			box_layout->addWidget(*it);
+			(*it)->setChecked(RK_Debug::RK_Debug_Flags & debug_flags_group->id(*it));
+		}
+		connect(debug_flags_group, &QButtonGroup::idClicked, this, &RKSettingsPageDebug::change);
+		main_vbox->addWidget(group);
+
+
+		label = new QLabel(i18n("Command timeout"), this);
+		command_timeout_box = new RKSpinBox(this);
+		command_timeout_box->setIntMode(0, 10000, RK_Debug::RK_Debug_CommandStep);
+		connect(command_timeout_box, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &RKSettingsPageDebug::change);
+		main_vbox->addWidget(label);
+		main_vbox->addWidget(command_timeout_box);
+
+		main_vbox->addStretch();
+
+		if (RK_Debug::debug_file) {
+			label = new QLabel(i18n("<i>Note:</i> Debug output is written to %1", RK_Debug::debug_file->fileName()));
+			main_vbox->addWidget(label);
+			main_vbox->addStretch();
+		}
 	}
-	connect (debug_flags_group, &QButtonGroup::idClicked, this, &RKSettingsModuleDebug::settingChanged);
-	main_vbox->addWidget (group);
-
-
-	label = new QLabel (i18n ("Command timeout"), this);
-	command_timeout_box = new RKSpinBox (this);
-	command_timeout_box->setIntMode (0, 10000, RK_Debug::RK_Debug_CommandStep);
-	connect (command_timeout_box, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &RKSettingsModuleDebug::settingChanged);
-	main_vbox->addWidget (label);
-	main_vbox->addWidget (command_timeout_box);
-
-	main_vbox->addStretch ();
-
-	if (RK_Debug::debug_file) {
-		label = new QLabel (i18n ("<i>Note:</i> Debug output is written to %1", RK_Debug::debug_file->fileName ()));
-		main_vbox->addWidget (label);
-		main_vbox->addStretch ();
+	void applyChanges() {
+		RK_TRACE(SETTINGS);
+
+		RK_Debug::RK_Debug_Level = DL_FATAL - debug_level_box->intValue();
+		RK_Debug::RK_Debug_CommandStep = command_timeout_box->intValue();
+		int flags = 0;
+		QList<QAbstractButton*> buttons = debug_flags_group->buttons();
+		for (QList<QAbstractButton*>::const_iterator it = buttons.constBegin(); it != buttons.constEnd(); ++it) {
+			if ((*it)->isChecked()) flags |= debug_flags_group->id(*it);
+		}
+		RK_Debug::RK_Debug_Flags = flags;
 	}
-}
+private:
+	RKSpinBox* command_timeout_box;
+	RKSpinBox* debug_level_box;
+	QButtonGroup* debug_flags_group;
+};
 
-RKSettingsModuleDebug::~RKSettingsModuleDebug () {
-	RK_TRACE (SETTINGS);
-}
-
-void RKSettingsModuleDebug::settingChanged (int) {
-	RK_TRACE (SETTINGS);
-	change ();
-}
-
-QString RKSettingsModuleDebug::caption() const {
+RKSettingsModuleDebug::RKSettingsModuleDebug(QObject *parent) : RKSettingsModule(parent) {
 	RK_TRACE(SETTINGS);
-	return(i18n("Debug"));
 }
 
-void RKSettingsModuleDebug::applyChanges () {
+RKSettingsModuleDebug::~RKSettingsModuleDebug () {
 	RK_TRACE (SETTINGS);
+}
 
-	RK_Debug::RK_Debug_Level = DL_FATAL - debug_level_box->intValue ();
-	RK_Debug::RK_Debug_CommandStep = command_timeout_box->intValue ();
-	int flags = 0;
-	QList<QAbstractButton*> buttons = debug_flags_group->buttons ();
-	for (QList<QAbstractButton*>::const_iterator it = buttons.constBegin (); it != buttons.constEnd (); ++it) {
-		if ((*it)->isChecked ()) flags |= debug_flags_group->id (*it);
-	}
-	RK_Debug::RK_Debug_Flags = flags;
+QList<RKSettingsModuleWidget*> RKSettingsModuleDebug::createPages(QWidget *parent) {
+	return QList<RKSettingsModuleWidget*>{ new RKSettingsPageDebug(parent, this) };
 }
 
 void RKSettingsModuleDebug::syncConfig(KConfig*, RKConfigBase::ConfigSyncAction) {
diff --git a/rkward/settings/rksettingsmoduledebug.h b/rkward/settings/rksettingsmoduledebug.h
index 0c02fc88e..f47601abc 100644
--- a/rkward/settings/rksettingsmoduledebug.h
+++ b/rkward/settings/rksettingsmoduledebug.h
@@ -21,23 +21,14 @@ configuration for the Command Editor windows
 class RKSettingsModuleDebug : public RKSettingsModule {
 	Q_OBJECT
 public:
-	RKSettingsModuleDebug (RKSettings *gui, QWidget *parent);
-	~RKSettingsModuleDebug ();
+	RKSettingsModuleDebug(QObject *parent);
+	~RKSettingsModuleDebug();
 
-	void applyChanges () override;
-	void save(KConfig *config) override { syncConfig(config, RKConfigBase::SaveConfig); };
-	static void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a);
-	static void validateSettingsInteractive (QList<RKSetupWizardItem*>*) {};
-
-	QString caption() const override;
+	void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) override;
+	QList<RKSettingsModuleWidget*> createPages(QWidget *parent) override;
+	static constexpr PageId page_id = QLatin1String("debug");
 
 	// static members are declared in debug.h and defined in main.cpp
-public Q_SLOTS:
-	void settingChanged (int);
-private:
-	RKSpinBox* command_timeout_box;
-	RKSpinBox* debug_level_box;
-	QButtonGroup* debug_flags_group;
 };
 
 #endif
diff --git a/rkward/settings/rksettingsmodulegeneral.cpp b/rkward/settings/rksettingsmodulegeneral.cpp
index e7ed808bc..6b0bea6b3 100644
--- a/rkward/settings/rksettingsmodulegeneral.cpp
+++ b/rkward/settings/rksettingsmodulegeneral.cpp
@@ -52,89 +52,120 @@ bool RKSettingsModuleGeneral::installation_moved = false;
 QString RKSettingsModuleGeneral::previous_rkward_data_dir;
 RKConfigValue<int> RKSettingsModuleGeneral::num_recent_files { "Max number of recent files", 8 };
 
-RKSettingsModuleGeneral::RKSettingsModuleGeneral (RKSettings *gui, QWidget *parent) : RKSettingsModule (gui, parent) {
-	RK_TRACE (SETTINGS);
 
-	QVBoxLayout *main_vbox = new QVBoxLayout (this);
-	files_choser = new GetFileNameWidget (this, GetFileNameWidget::ExistingDirectory, true, i18n ("Directory where rkward may store files (setting takes effect after restarting RKWard)"), QString (), new_files_path);
-	connect (files_choser, &GetFileNameWidget::locationChanged, this, &RKSettingsModuleGeneral::change);
-	main_vbox->addWidget (files_choser);
-
-	main_vbox->addSpacing (2*RKStyle::spacingHint ());
-
-	auto group = new QGroupBox(i18n("Startup behavior"));
-	auto vbox = new QVBoxLayout(group);
-	vbox->addWidget(autorestore_from_wd.makeCheckbox(i18n("Load .RData-file from startup directory, if available (R option '--restore')"), this));
-	vbox->addWidget(show_help_on_startup.makeCheckbox(i18n("Show RKWard Help on Startup"), this));
-
-	QGroupBox* group_box = new QGroupBox (i18n ("Initial working directory"), this);
-	QHBoxLayout *hlayout = new QHBoxLayout (group_box);
-	auto initial_dir_chooser = initial_dir.makeDropDown(RKConfigBase::LabelList(
-		{{CurrentDirectory, i18n("Do not change current directory on startup")}, {RKWardDirectory, i18n("RKWard files directory (as specified, above)")}, {UserHomeDirectory, i18n("User home directory")}, {LastUsedDirectory, i18n("Last used directory")}, {CustomDirectory, i18n("The following directory (please specify):")}}
-	), this);
-	hlayout->addWidget (initial_dir_chooser);
-	initial_dir_custom_chooser = new GetFileNameWidget (group_box, GetFileNameWidget::ExistingDirectory, true, QString(), i18n ("Initial working directory"), initial_dir_specification);
-	initial_dir_custom_chooser->setEnabled (initial_dir == CustomDirectory);
-	connect (initial_dir_custom_chooser, &GetFileNameWidget::locationChanged, this, &RKSettingsModuleGeneral::change);
-	connect(initial_dir_chooser, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [initial_dir_chooser, this]() { this->initial_dir_custom_chooser->setEnabled(initial_dir_chooser->currentData()==CustomDirectory); });
-	hlayout->addWidget (initial_dir_custom_chooser);
-	RKCommonFunctions::setTips (i18n ("<p>The initial working directory to use. Note that if you are loading a workspace on startup, and you have configured RKWard to change to the directory of loaded workspaces, that directory will take precedence.</p>"), group_box, initial_dir_chooser, initial_dir_custom_chooser);
-	vbox->addWidget (group_box);
-	main_vbox->addWidget(group);
-
-	main_vbox->addSpacing (2*RKStyle::spacingHint ());
-
-	auto num_recent_files_box = num_recent_files.makeSpinBox(1, INT_MAX, this);
-	RKCommonFunctions::setTips (i18n ("<p>The number of recent files to remember (in the Open Recent R Script File menu).</p>") + RKCommonFunctions::noteSettingsTakesEffectAfterRestart (), num_recent_files_box, num_recent_files_box);
-	vbox->addWidget(new QLabel(i18n("Maximum number of recently used files to remember per category")));
-	vbox->addWidget(num_recent_files_box);
-
-	main_vbox->addSpacing (2*RKStyle::spacingHint ());
-
-	main_vbox->addWidget (RKCommonFunctions::wordWrappedLabel (i18n ("The workplace layout (i.e. which script-, data-, help-windows are open) may be saved (and loaded) per R workspace, or independent of the R workspace. Which do you prefer?")));
-
-	workplace_save_chooser = new QButtonGroup (this);
-	group_box = new QGroupBox (this);
-	QVBoxLayout *group_layout = new QVBoxLayout(group_box);
-
-	QAbstractButton* button;
-	button = new QRadioButton (i18n ("Save/restore with R workspace, when saving/loading R workspace"), group_box);
-	group_layout->addWidget (button);
-	workplace_save_chooser->addButton (button, SaveWorkplaceWithWorkspace);
-	button = new QRadioButton (i18n ("Save/restore independent of R workspace (save at end of RKWard session, restore at next start)"), group_box);
-	group_layout->addWidget (button);
-	workplace_save_chooser->addButton (button, SaveWorkplaceWithSession);
-	button = new QRadioButton (i18n ("Do not save/restore workplace layout"), group_box);
-	group_layout->addWidget (button);
-	workplace_save_chooser->addButton (button, DontSaveWorkplace);	
-	if ((button = workplace_save_chooser->button (workplace_save_mode))) button->setChecked (true);
-	connect (workplace_save_chooser, &QButtonGroup::idClicked, this, &RKSettingsModuleGeneral::change);
-	main_vbox->addWidget (group_box);
-
-	main_vbox->addSpacing (2*RKStyle::spacingHint ());
-
-	main_vbox->addWidget(cd_to_workspace_dir_on_load.makeCheckbox(i18n("When loading a workspace, change to the corresponding directory."), this));
-
-	main_vbox->addSpacing (2*RKStyle::spacingHint ());
-
-	main_vbox->addWidget (new QLabel(i18n("Warn when editing objects with more than this number of fields (0 for no limit):")));
-	main_vbox->addWidget (warn_size_object_edit.makeSpinBox(0, INT_MAX, this));
-
-	main_vbox->addSpacing (2*RKStyle::spacingHint ());
-
-	main_vbox->addWidget(new QLabel(i18n("MDI window focus behavior"), this));
-	auto mdi_focus_policy_chooser = mdi_focus_policy.makeDropDown(RKConfigBase::LabelList(
-		{{RKMDIClickFocus, i18n("Click to focus")}, {RKMDIFocusFollowsMouse, i18n("Focus follows mouse")}}
-	), this);
-	main_vbox->addWidget(mdi_focus_policy_chooser);
-
-	main_vbox->addStretch ();
+class RKSettingsPageGeneral : public RKSettingsModuleWidget {
+public:
+	RKSettingsPageGeneral(QWidget *parent, RKSettingsModule *parent_module) : RKSettingsModuleWidget(parent, parent_module, RKSettingsModuleGeneral::page_id) {
+		RK_TRACE(SETTINGS);
+
+		setWindowTitle(i18n("General"));
+		setWindowIcon(RKStandardIcons::getIcon(RKStandardIcons::RKWardIcon));
+
+		QVBoxLayout *main_vbox = new QVBoxLayout(this);
+		files_choser = new GetFileNameWidget(this, GetFileNameWidget::ExistingDirectory, true, i18n("Directory where rkward may store files (setting takes effect after restarting RKWard)"), QString(), RKSettingsModuleGeneral::new_files_path);
+		connect(files_choser, &GetFileNameWidget::locationChanged, this, &RKSettingsPageGeneral::change);
+		main_vbox->addWidget(files_choser);
+
+		main_vbox->addSpacing(2*RKStyle::spacingHint());
+
+		auto group = new QGroupBox(i18n("Startup behavior"));
+		auto vbox = new QVBoxLayout(group);
+		vbox->addWidget(RKSettingsModuleGeneral::autorestore_from_wd.makeCheckbox(i18n("Load .RData-file from startup directory, if available (R option '--restore')"), this));
+		vbox->addWidget(RKSettingsModuleGeneral::show_help_on_startup.makeCheckbox(i18n("Show RKWard Help on Startup"), this));
+
+		QGroupBox* group_box = new QGroupBox(i18n("Initial working directory"), this);
+		QHBoxLayout *hlayout = new QHBoxLayout(group_box);
+		auto initial_dir_chooser = RKSettingsModuleGeneral::initial_dir.makeDropDown(RKConfigBase::LabelList({
+			{RKSettingsModuleGeneral::CurrentDirectory, i18n("Do not change current directory on startup")},
+			{RKSettingsModuleGeneral::RKWardDirectory, i18n("RKWard files directory (as specified, above)")},
+			{RKSettingsModuleGeneral::UserHomeDirectory, i18n("User home directory")},
+			{RKSettingsModuleGeneral::LastUsedDirectory, i18n("Last used directory")},
+			{RKSettingsModuleGeneral::CustomDirectory, i18n("The following directory (please specify):")}
+		}), this);
+		hlayout->addWidget(initial_dir_chooser);
+		initial_dir_custom_chooser = new GetFileNameWidget(group_box, GetFileNameWidget::ExistingDirectory, true, QString(), i18n ("Initial working directory"), RKSettingsModuleGeneral::initial_dir_specification);
+		initial_dir_custom_chooser->setEnabled(RKSettingsModuleGeneral::initial_dir == RKSettingsModuleGeneral::CustomDirectory);
+		connect(initial_dir_custom_chooser, &GetFileNameWidget::locationChanged, this, &RKSettingsPageGeneral::change);
+		connect(initial_dir_chooser, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [initial_dir_chooser, this]() { this->initial_dir_custom_chooser->setEnabled(initial_dir_chooser->currentData()==RKSettingsModuleGeneral::CustomDirectory); });
+		hlayout->addWidget(initial_dir_custom_chooser);
+		RKCommonFunctions::setTips(i18n("<p>The initial working directory to use. Note that if you are loading a workspace on startup, and you have configured RKWard to change to the directory of loaded workspaces, that directory will take precedence.</p>"), group_box, initial_dir_chooser, initial_dir_custom_chooser);
+		vbox->addWidget(group_box);
+		main_vbox->addWidget(group);
+
+		main_vbox->addSpacing(2*RKStyle::spacingHint());
+
+		auto num_recent_files_box = RKSettingsModuleGeneral::num_recent_files.makeSpinBox(1, INT_MAX, this);
+		RKCommonFunctions::setTips(i18n("<p>The number of recent files to remember (in the Open Recent R Script File menu).</p>") + RKCommonFunctions::noteSettingsTakesEffectAfterRestart(), num_recent_files_box, num_recent_files_box);
+		vbox->addWidget(new QLabel(i18n("Maximum number of recently used files to remember per category")));
+		vbox->addWidget(num_recent_files_box);
+
+		main_vbox->addSpacing(2*RKStyle::spacingHint ());
+
+		main_vbox->addWidget(RKCommonFunctions::wordWrappedLabel(i18n("The workplace layout (i.e. which script-, data-, help-windows are open) may be saved (and loaded) per R workspace, or independent of the R workspace. Which do you prefer?")));
+
+		workplace_save_chooser = new QButtonGroup(this);
+		group_box = new QGroupBox(this);
+		QVBoxLayout *group_layout = new QVBoxLayout(group_box);
+
+		QAbstractButton* button;
+		button = new QRadioButton(i18n("Save/restore with R workspace, when saving/loading R workspace"), group_box);
+		group_layout->addWidget(button);
+		workplace_save_chooser->addButton(button, RKSettingsModuleGeneral::SaveWorkplaceWithWorkspace);
+		button = new QRadioButton(i18n("Save/restore independent of R workspace (save at end of RKWard session, restore at next start)"), group_box);
+		group_layout->addWidget(button);
+		workplace_save_chooser->addButton(button, RKSettingsModuleGeneral::SaveWorkplaceWithSession);
+		button = new QRadioButton(i18n("Do not save/restore workplace layout"), group_box);
+		group_layout->addWidget(button);
+		workplace_save_chooser->addButton(button, RKSettingsModuleGeneral::DontSaveWorkplace);	
+		if ((button = workplace_save_chooser->button(RKSettingsModuleGeneral::workplace_save_mode))) button->setChecked(true);
+		connect(workplace_save_chooser, &QButtonGroup::idClicked, this, &RKSettingsPageGeneral::change);
+		main_vbox->addWidget(group_box);
+
+		main_vbox->addSpacing(2*RKStyle::spacingHint());
+
+		main_vbox->addWidget(RKSettingsModuleGeneral::cd_to_workspace_dir_on_load.makeCheckbox(i18n("When loading a workspace, change to the corresponding directory."), this));
+
+		main_vbox->addSpacing(2*RKStyle::spacingHint());
+
+		main_vbox->addWidget(new QLabel(i18n("Warn when editing objects with more than this number of fields (0 for no limit):")));
+		main_vbox->addWidget(RKSettingsModuleGeneral::warn_size_object_edit.makeSpinBox(0, INT_MAX, this));
+
+		main_vbox->addSpacing(2*RKStyle::spacingHint());
+
+		main_vbox->addWidget(new QLabel(i18n("MDI window focus behavior"), this));
+		auto mdi_focus_policy_chooser = RKSettingsModuleGeneral::mdi_focus_policy.makeDropDown(RKConfigBase::LabelList({
+			{RKSettingsModuleGeneral::RKMDIClickFocus, i18n("Click to focus")},
+			{RKSettingsModuleGeneral::RKMDIFocusFollowsMouse, i18n("Focus follows mouse")}
+	}	), this);
+		main_vbox->addWidget(mdi_focus_policy_chooser);
+
+		main_vbox->addStretch();
+	}
+	~RKSettingsPageGeneral() {};
+	void applyChanges() override {
+		RK_TRACE (SETTINGS);
+		RKSettingsModuleGeneral::new_files_path = files_choser->getLocation();
+		RKSettingsModuleGeneral::workplace_save_mode = static_cast<RKSettingsModuleGeneral::WorkplaceSaveMode>(workplace_save_chooser->checkedId());
+		RKSettingsModuleGeneral::initial_dir_specification = initial_dir_custom_chooser->getLocation();
+	}
+private:
+	GetFileNameWidget *files_choser;
+	QButtonGroup *workplace_save_chooser;
+	GetFileNameWidget *initial_dir_custom_chooser;
+};
+
+RKSettingsModuleGeneral::RKSettingsModuleGeneral(QObject *parent) : RKSettingsModule(parent) {
+	RK_TRACE (SETTINGS);
 }
 
 RKSettingsModuleGeneral::~RKSettingsModuleGeneral() {
 	RK_TRACE (SETTINGS);
 }
 
+QList<RKSettingsModuleWidget*> RKSettingsModuleGeneral::createPages(QWidget *parent) {
+	return QList<RKSettingsModuleWidget*>{ new RKSettingsPageGeneral(parent, this) };
+}
+
 QString RKSettingsModuleGeneral::initialWorkingDirectory () {
 	if (initial_dir == CurrentDirectory) return QString ();
 	if (initial_dir == RKWardDirectory) return filesPath ();
@@ -142,23 +173,6 @@ QString RKSettingsModuleGeneral::initialWorkingDirectory () {
 	return initial_dir_specification;
 }
 
-QString RKSettingsModuleGeneral::caption() const {
-	RK_TRACE(SETTINGS);
-	return(i18n("General"));
-}
-
-QIcon RKSettingsModuleGeneral::icon() const {
-	RK_TRACE(SETTINGS);
-	return RKStandardIcons::getIcon(RKStandardIcons::RKWardIcon);
-}
-
-void RKSettingsModuleGeneral::applyChanges () {
-	RK_TRACE (SETTINGS);
-	new_files_path = files_choser->getLocation ();
-	workplace_save_mode = static_cast<WorkplaceSaveMode> (workplace_save_chooser->checkedId ());
-	initial_dir_specification = initial_dir_custom_chooser->getLocation ();
-}
-
 void RKSettingsModuleGeneral::syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a) {
 	RK_TRACE (SETTINGS);
 
diff --git a/rkward/settings/rksettingsmodulegeneral.h b/rkward/settings/rksettingsmodulegeneral.h
index 073937d07..45896998d 100644
--- a/rkward/settings/rksettingsmodulegeneral.h
+++ b/rkward/settings/rksettingsmodulegeneral.h
@@ -20,7 +20,7 @@ class RKSpinBox;
 class RKSettingsModuleGeneral : public RKSettingsModule {
 	Q_OBJECT
 public:
-	RKSettingsModuleGeneral (RKSettings *gui, QWidget *parent);
+	RKSettingsModuleGeneral(QObject *parent);
 	~RKSettingsModuleGeneral ();
 
 	enum WorkplaceSaveMode {	// don't change the int values of this enum, or you'll ruin users saved settings. Append new values at the end
@@ -42,13 +42,9 @@ public:
 		RKMDIFocusFollowsMouse=1
 	};
 
-	void applyChanges () override;
-	void save(KConfig *config) override { syncConfig(config, RKConfigBase::SaveConfig); };
-	static void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a);
-	static void validateSettingsInteractive (QList<RKSetupWizardItem*>*) {};
-
-	QString caption() const override;
-	QIcon icon() const override;
+	void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) override;
+	QList<RKSettingsModuleWidget*> createPages(QWidget *parent) override;
+	static constexpr PageId page_id = QLatin1String("general");
 
 /// returns the directory-name where the logfiles should reside
 	static QString &filesPath () { return files_path; };
@@ -93,10 +89,7 @@ public:
 	/** Returns true, if rkward seems to have started from a different path than on the previous run. */
 	static bool installationMoved () { return installation_moved; };
 private:
-	GetFileNameWidget *files_choser;
-	QButtonGroup *workplace_save_chooser;
-	GetFileNameWidget *initial_dir_custom_chooser;
-
+friend class RKSettingsPageGeneral;
 	static RKConfigValue<bool> autorestore_from_wd;
 	static QString files_path;
 /** since changing the files_path can not easily be done while in an active session, the setting should only take effect on the next start. This string stores a changed setting, while keeping the old one intact as long as RKWard is running */
diff --git a/rkward/settings/rksettingsmodulegraphics.cpp b/rkward/settings/rksettingsmodulegraphics.cpp
index 4280254b5..25ae934c2 100644
--- a/rkward/settings/rksettingsmodulegraphics.cpp
+++ b/rkward/settings/rksettingsmodulegraphics.cpp
@@ -39,139 +39,152 @@ RKConfigValue<RKSettingsModuleGraphics::DefaultDevice, int> RKSettingsModuleGrap
 RKConfigValue<QString> RKSettingsModuleGraphics::default_device_other {"default_device_custom", QString("Cairo")};
 RKConfigValue<RKSettingsModuleGraphics::StandardDevicesMode, int> RKSettingsModuleGraphics::replace_standard_devices {"replace_device", ReplaceDevice};
 
-RKSettingsModuleGraphics::RKSettingsModuleGraphics (RKSettings *gui, QWidget *parent) : RKSettingsModule(gui, parent) {
-	RK_TRACE (SETTINGS);
-
-	QVBoxLayout *main_vbox = new QVBoxLayout (this);
-
-	QHBoxLayout *h_layout1 = new QHBoxLayout();
-	main_vbox->addLayout (h_layout1);
-	QGroupBox *group = new QGroupBox (i18n ("Default graphics device"), this);
-	default_device_group = new QButtonGroup (this);
-	QVBoxLayout* group_layout = new QVBoxLayout (group);
-	QRadioButton *button = new QRadioButton (i18n ("RKWard native device"), group);
-	default_device_group->addButton (button, (int) RKDevice);
-	group_layout->addWidget (button);
-	button = new QRadioButton (i18n ("Platform default device"), group);
-	default_device_group->addButton (button, (int) PlatformDevice);
-	group_layout->addWidget (button);
-	button = new QRadioButton (i18n ("Other device:"), group);
-	default_device_group->addButton (button, (int) OtherDevice);
-	QHBoxLayout *h_layout = new QHBoxLayout ();
-	group_layout->addLayout (h_layout);
-	h_layout->addWidget (button);
-	default_device_other_edit = new QLineEdit (default_device_other, group);
-	h_layout->addWidget (default_device_other_edit);
-	button = static_cast<QRadioButton*> (default_device_group->button ((int) default_device));
-	if (button) button->setChecked (true);
-	RKCommonFunctions::setTips (i18n ("<p>The default device to be used for plotting, i.e. when new plot is created, while no graphics device is active (see <i>options(\"device\")</i>).</p>"
-	                                  "<p>The RKWard native device is the recommended choice for most users. This corresponds to the R command <i>RK()</i>.</p>"
-	                                  "<p>The 'Platform default device' corresponds to one of <i>X11()</i>, <i>windows()</i>, or <i>quartz()</i>, depending on the platform.</p>"
-	                                  "<p>You can also specify the name of a function such as <i>Cairo</i>.</p>"), group);
-	connect (default_device_group, &QButtonGroup::idClicked, this, &RKSettingsModuleGraphics::boxChanged);
-	connect (default_device_other_edit, &QLineEdit::textChanged, this, &RKSettingsModuleGraphics::boxChanged);
-	h_layout1->addWidget (group);
-
-	group = new QGroupBox (i18n ("Integration of R standard devices"), this);
-	replace_standard_devices_group = new QButtonGroup (this);
-	group_layout = new QVBoxLayout (group);
-	button = new QRadioButton (i18n ("Replace with RKWard device"), group);
-	replace_standard_devices_group->addButton (button, (int) ReplaceDevice);
-	group_layout->addWidget (button);
-	button = new QRadioButton (i18n ("Embed original device"), group);
-	replace_standard_devices_group->addButton (button, (int) EmbedDevice);
-	group_layout->addWidget (button);
+class RKSettingsPageGraphics : public RKSettingsModuleWidget {
+public:
+	RKSettingsPageGraphics(QWidget *parent, RKSettingsModule* parent_module) : RKSettingsModuleWidget(parent, parent_module, RKSettingsModuleGraphics::page_id) {
+		RK_TRACE(SETTINGS);
+
+		setWindowTitle(i18n("Onscreen Graphics"));
+		setWindowIcon(RKStandardIcons::getIcon(RKStandardIcons::WindowX11));
+		help_url = QUrl("rkward://page/rkward_plot_history#scd_settings");
+
+		QVBoxLayout *main_vbox = new QVBoxLayout(this);
+
+		QHBoxLayout *h_layout1 = new QHBoxLayout();
+		main_vbox->addLayout(h_layout1);
+		QGroupBox *group = new QGroupBox(i18n("Default graphics device"), this);
+		default_device_group = new QButtonGroup(this);
+		QVBoxLayout* group_layout = new QVBoxLayout(group);
+		QRadioButton *button = new QRadioButton(i18n("RKWard native device"), group);
+		default_device_group->addButton(button, (int) RKSettingsModuleGraphics::RKDevice);
+		group_layout->addWidget(button);
+		button = new QRadioButton(i18n("Platform default device"), group);
+		default_device_group->addButton(button, (int) RKSettingsModuleGraphics::PlatformDevice);
+		group_layout->addWidget(button);
+		button = new QRadioButton(i18n("Other device:"), group);
+		default_device_group->addButton(button, (int) RKSettingsModuleGraphics::OtherDevice);
+		QHBoxLayout *h_layout = new QHBoxLayout();
+		group_layout->addLayout(h_layout);
+		h_layout->addWidget(button);
+		default_device_other_edit = new QLineEdit(RKSettingsModuleGraphics::default_device_other, group);
+		h_layout->addWidget(default_device_other_edit);
+		button = static_cast<QRadioButton*>(default_device_group->button((int) RKSettingsModuleGraphics::default_device));
+		if (button) button->setChecked(true);
+		RKCommonFunctions::setTips(i18n("<p>The default device to be used for plotting, i.e. when new plot is created, while no graphics device is active (see <i>options(\"device\")</i>).</p>"
+		                                "<p>The RKWard native device is the recommended choice for most users. This corresponds to the R command <i>RK()</i>.</p>"
+		                                "<p>The 'Platform default device' corresponds to one of <i>X11()</i>, <i>windows()</i>, or <i>quartz()</i>, depending on the platform.</p>"
+		                                "<p>You can also specify the name of a function such as <i>Cairo</i>.</p>"), group);
+		connect(default_device_group, &QButtonGroup::idClicked, this, &RKSettingsPageGraphics::boxChanged);
+		connect(default_device_other_edit, &QLineEdit::textChanged, this, &RKSettingsPageGraphics::boxChanged);
+		h_layout1->addWidget(group);
+
+		group = new QGroupBox(i18n("Integration of R standard devices"), this);
+		replace_standard_devices_group = new QButtonGroup(this);
+		group_layout = new QVBoxLayout(group);
+		button = new QRadioButton(i18n("Replace with RKWard device"), group);
+		replace_standard_devices_group->addButton(button, (int) RKSettingsModuleGraphics::ReplaceDevice);
+		group_layout->addWidget(button);
+		button = new QRadioButton(i18n("Embed original device"), group);
+		replace_standard_devices_group->addButton(button, (int) RKSettingsModuleGraphics::EmbedDevice);
+		group_layout->addWidget(button);
 #ifdef Q_OS_MACOS
-	button->setEnabled (false);
+		button->setEnabled(false);
 #endif
-	button = new QRadioButton (i18n ("No device integration"), group);
-	replace_standard_devices_group->addButton (button, (int) LeaveDevice);
-	group_layout->addWidget (button);
-	button = static_cast<QRadioButton*> (replace_standard_devices_group->button ((int) replace_standard_devices));
-	if (button) button->setChecked (true);
-	RKCommonFunctions::setTips (i18n ("<p>Many scripts use calls to platform specific standard devices (<i>X11()</i>, <i>windows()</i>, <i>quartz()</i>), although any on-screen device "
-	                                  "could be used at these places. RKWard provides overloads for these standard device functions, which can change their behavior when used in "
-	                                  "user code:</p>"
-	                                  "<ul><li>The calls can be re-directed to the RKWard native device (<i>RK()</i>). Some, but not all function arguments will be matched, others will "
-	                                  "be ignored.</li>"
-	                                  "<li>The original platform specific devices can be used, but embedded into RKWard windows. This option is not available on MacOS X.</li>"
-	                                  "<li>The original platform specific devices can be used unchanged, without the addition of RKWard specific features.</li></ul>"
-	                                  "<p>Regardless of this setting, the original devices are always accessible as <i>grDevices::X11()</i>, etc.</p>"
-	                                  "<p>Using a device on a platform where it is not defined (e.g. <i>Windows()</i> on Mac OS X) will always fall back to the <i>RK()</i> device.</p>"), group);
-	connect (replace_standard_devices_group, &QButtonGroup::idClicked, this, &RKSettingsModuleGraphics::boxChanged);
-	h_layout1->addWidget (group);
-
-	group = new QGroupBox(i18n("Default window size (for RK(), or embedded device windows)"));
-	group_layout = new QVBoxLayout(group);
-	group_layout->addWidget(new QLabel(i18n("Default width (inches):")));
-	group_layout->addWidget(graphics_width.makeSpinBox(1, 100.0, this));
-	group_layout->addSpacing(2*RKStyle::spacingHint());
-	group_layout->addWidget(new QLabel(i18n("Default height (inches)")));
-	group_layout->addWidget(graphics_height.makeSpinBox(1, 100.0, this));
-	main_vbox->addWidget (group);
-
-	main_vbox->addWidget(options_kde_printing.makeCheckbox(i18n("Use KDE printer dialog for printing devices (if available)"), this));
-
-	graphics_hist_box = new QGroupBox (i18n ("Screen device history"), this);
-	graphics_hist_box->setCheckable (true);
-	graphics_hist_box->setChecked (graphics_hist_enable);
-	group_layout = new QVBoxLayout (graphics_hist_box);
-	connect (graphics_hist_box, &QGroupBox::toggled, this, &RKSettingsModuleGraphics::boxChanged);
-	h_layout = new QHBoxLayout ();
-	group_layout->addLayout (h_layout);
-	h_layout->addWidget (new QLabel (i18n ("Maximum number of recorded plots:"), graphics_hist_box));
-	h_layout->addWidget (graphics_hist_max_length.makeSpinBox(1, 200, this));
-	h_layout = new QHBoxLayout ();
-	group_layout->addLayout (h_layout);
-	h_layout->addWidget (new QLabel (i18n ("Maximum size of a single recorded plot (in KB):"), graphics_hist_box));
-	h_layout->addWidget (graphics_hist_max_plotsize.makeSpinBox(4, 50000, this));
-	main_vbox->addWidget (graphics_hist_box);
-
-	main_vbox->addStretch ();
-	updateControls ();
-}
+		button = new QRadioButton(i18n("No device integration"), group);
+		replace_standard_devices_group->addButton(button, (int) RKSettingsModuleGraphics::LeaveDevice);
+		group_layout->addWidget(button);
+		button = static_cast<QRadioButton*>(replace_standard_devices_group->button((int) RKSettingsModuleGraphics::replace_standard_devices));
+		if (button) button->setChecked(true);
+		RKCommonFunctions::setTips(i18n("<p>Many scripts use calls to platform specific standard devices (<i>X11()</i>, <i>windows()</i>, <i>quartz()</i>), although any on-screen device "
+		                                "could be used at these places. RKWard provides overloads for these standard device functions, which can change their behavior when used in "
+		                                "user code:</p>"
+		                                "<ul><li>The calls can be re-directed to the RKWard native device (<i>RK()</i>). Some, but not all function arguments will be matched, others will "
+		                                "be ignored.</li>"
+		                                "<li>The original platform specific devices can be used, but embedded into RKWard windows. This option is not available on MacOS X.</li>"
+		                                "<li>The original platform specific devices can be used unchanged, without the addition of RKWard specific features.</li></ul>"
+		                                "<p>Regardless of this setting, the original devices are always accessible as <i>grDevices::X11()</i>, etc.</p>"
+		                                "<p>Using a device on a platform where it is not defined (e.g. <i>Windows()</i> on Mac OS X) will always fall back to the <i>RK()</i> device.</p>"), group);
+		connect(replace_standard_devices_group, &QButtonGroup::idClicked, this, &RKSettingsPageGraphics::boxChanged);
+		h_layout1->addWidget(group);
+
+		group = new QGroupBox(i18n("Default window size (for RK(), or embedded device windows)"));
+		group_layout = new QVBoxLayout(group);
+		group_layout->addWidget(new QLabel(i18n("Default width (inches):")));
+		group_layout->addWidget(RKSettingsModuleGraphics::graphics_width.makeSpinBox(1, 100.0, this));
+		group_layout->addSpacing(2*RKStyle::spacingHint());
+		group_layout->addWidget(new QLabel(i18n("Default height (inches)")));
+		group_layout->addWidget(RKSettingsModuleGraphics::graphics_height.makeSpinBox(1, 100.0, this));
+		main_vbox->addWidget(group);
+
+		main_vbox->addWidget(RKSettingsModuleGraphics::options_kde_printing.makeCheckbox(i18n("Use KDE printer dialog for printing devices (if available)"), this));
+
+		graphics_hist_box = new QGroupBox(i18n("Screen device history"), this);
+		graphics_hist_box->setCheckable(true);
+		graphics_hist_box->setChecked(RKSettingsModuleGraphics::graphics_hist_enable);
+		group_layout = new QVBoxLayout(graphics_hist_box);
+		connect(graphics_hist_box, &QGroupBox::toggled, this, &RKSettingsPageGraphics::boxChanged);
+		h_layout = new QHBoxLayout();
+		group_layout->addLayout(h_layout);
+		h_layout->addWidget(new QLabel(i18n("Maximum number of recorded plots:"), graphics_hist_box));
+		h_layout->addWidget(RKSettingsModuleGraphics::graphics_hist_max_length.makeSpinBox(1, 200, this));
+		h_layout = new QHBoxLayout();
+		group_layout->addLayout(h_layout);
+		h_layout->addWidget(new QLabel(i18n("Maximum size of a single recorded plot (in KB):"), graphics_hist_box));
+		h_layout->addWidget(RKSettingsModuleGraphics::graphics_hist_max_plotsize.makeSpinBox(4, 50000, this));
+		main_vbox->addWidget(graphics_hist_box);
+
+		main_vbox->addStretch();
+		updateControls();
+	}
+	~RKSettingsPageGraphics() {
+		RK_TRACE(SETTINGS);
+	}
+	void updateControls() {
+		RK_TRACE(SETTINGS);
+		default_device_other_edit->setEnabled(default_device_group->checkedId () == (int) RKSettingsModuleGraphics::OtherDevice);
+		QRadioButton *button = static_cast<QRadioButton*>(replace_standard_devices_group->button((int) RKSettingsModuleGraphics::ReplaceDevice));
+		if (button) button->setEnabled(default_device_group->checkedId() != RKSettingsModuleGraphics::PlatformDevice);
+	}
 
-RKSettingsModuleGraphics::~RKSettingsModuleGraphics() {
-	RK_TRACE (SETTINGS);
-}
+	void boxChanged() {
+		RK_TRACE(SETTINGS);
 
-void RKSettingsModuleGraphics::updateControls () {
-	RK_TRACE (SETTINGS);
-	default_device_other_edit->setEnabled (default_device_group->checkedId () == (int) OtherDevice);
-	QRadioButton *button = static_cast<QRadioButton*> (replace_standard_devices_group->button ((int) ReplaceDevice));
-	if (button) button->setEnabled (default_device_group->checkedId () != PlatformDevice);
-}
+		updateControls();
+		change();
+	}
 
-void RKSettingsModuleGraphics::boxChanged () {
-	RK_TRACE (SETTINGS);
+	void applyChanges() {
+		RK_TRACE(SETTINGS);
 
-	updateControls ();
-	change ();
-}
+		RKSettingsModuleGraphics::default_device = (RKSettingsModuleGraphics::DefaultDevice) default_device_group->checkedId();
+		RKSettingsModuleGraphics::default_device_other = default_device_other_edit->text();
+		RKSettingsModuleGraphics::replace_standard_devices = (RKSettingsModuleGraphics::StandardDevicesMode) replace_standard_devices_group->checkedId();
 
-QString RKSettingsModuleGraphics::caption() const {
-	RK_TRACE(SETTINGS);
-	return(i18n("Onscreen Graphics"));
-}
+		RKSettingsModuleGraphics::graphics_hist_enable = graphics_hist_box->isChecked();
 
-QIcon RKSettingsModuleGraphics::icon() const {
-	RK_TRACE(SETTINGS);
-	return RKStandardIcons::getIcon(RKStandardIcons::WindowX11);
-}
+		QStringList commands = RKSettingsModuleGraphics::makeRRunTimeOptionCommands();
+		for (QStringList::const_iterator it = commands.cbegin(); it != commands.cend(); ++it) {
+			RInterface::issueCommand(new RCommand(*it, RCommand::App), parent_module->commandChain());
+		}
+	}
+private:
+	QButtonGroup *default_device_group;
+	QLineEdit *default_device_other_edit;
+	QButtonGroup *replace_standard_devices_group;
 
-void RKSettingsModuleGraphics::applyChanges () {
-	RK_TRACE (SETTINGS);
+	QGroupBox *graphics_hist_box;
+};
 
-	default_device = (DefaultDevice) default_device_group->checkedId ();
-	default_device_other = default_device_other_edit->text ();
-	replace_standard_devices = (StandardDevicesMode) replace_standard_devices_group->checkedId ();
+RKSettingsModuleGraphics::RKSettingsModuleGraphics(QObject *parent) : RKSettingsModule(parent) {
+	RK_TRACE (SETTINGS);
+}
 
-	graphics_hist_enable = graphics_hist_box->isChecked ();
+RKSettingsModuleGraphics::~RKSettingsModuleGraphics() {
+	RK_TRACE (SETTINGS);
+}
 
-	QStringList commands = makeRRunTimeOptionCommands ();
-	for (QStringList::const_iterator it = commands.cbegin (); it != commands.cend (); ++it) {
-		RInterface::issueCommand(new RCommand(*it, RCommand::App), commandChain ());
-	}
+QList<RKSettingsModuleWidget*> RKSettingsModuleGraphics::createPages(QWidget *parent) {
+	return QList<RKSettingsModuleWidget*>{ new RKSettingsPageGraphics(parent, this) };
 }
 
 void RKSettingsModuleGraphics::syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a) {
diff --git a/rkward/settings/rksettingsmodulegraphics.h b/rkward/settings/rksettingsmodulegraphics.h
index 992f796d7..61dbc381e 100644
--- a/rkward/settings/rksettingsmodulegraphics.h
+++ b/rkward/settings/rksettingsmodulegraphics.h
@@ -24,24 +24,18 @@ class QRadioButton;
 class RKSettingsModuleGraphics : public RKSettingsModule {
 	Q_OBJECT
 public:
-	RKSettingsModuleGraphics (RKSettings *gui, QWidget *parent);
-	~RKSettingsModuleGraphics ();
+	RKSettingsModuleGraphics(QObject *parent);
+	~RKSettingsModuleGraphics();
 	
-	void applyChanges () override;
-
 /** generate the commands needed to set the R run time options */
 	static QStringList makeRRunTimeOptionCommands ();
 
 /** Configured to (attempt to) use KDE printing dialog? */
 	static bool kdePrintingEnabled () { return options_kde_printing; };
 
-	void save(KConfig *config) override { syncConfig(config, RKConfigBase::SaveConfig); };
-	static void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a);
-	static void validateSettingsInteractive (QList<RKSetupWizardItem*>*) {};
-
-	QString caption() const override;
-	QIcon icon() const override;
-	QUrl helpURL () override { return QUrl ("rkward://page/rkward_plot_history#scd_settings"); };
+	void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) override;
+	QList<RKSettingsModuleWidget*> createPages(QWidget *parent) override;
+	static constexpr PageId page_id = QLatin1String("graphics");
 
 	enum DefaultDevice {
 		RKDevice,
@@ -55,17 +49,8 @@ public:
 	};
 	
 	static bool plotHistoryEnabled () { return graphics_hist_enable; };
-public Q_SLOTS:
-	void boxChanged ();
 private:
-	void updateControls ();
-
-	QButtonGroup *default_device_group;
-	QLineEdit *default_device_other_edit;
-	QButtonGroup *replace_standard_devices_group;
-
-	QGroupBox *graphics_hist_box;
-
+friend class RKSettingsPageGraphics;
 	static RKConfigValue<DefaultDevice, int> default_device;
 	static RKConfigValue<QString> default_device_other;
 	static RKConfigValue<StandardDevicesMode, int> replace_standard_devices;
diff --git a/rkward/settings/rksettingsmodulekateplugins.cpp b/rkward/settings/rksettingsmodulekateplugins.cpp
index de6d76517..12449f05b 100644
--- a/rkward/settings/rksettingsmodulekateplugins.cpp
+++ b/rkward/settings/rksettingsmodulekateplugins.cpp
@@ -17,6 +17,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <KConfig>
 #include <QIcon>
 
+#include "rksettingsmoduleplugins.h"
 #include "../windows/katepluginintegration.h"
 #include "../misc/rkcommonfunctions.h"
 #include "../rkward.h"
@@ -25,58 +26,74 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 RKConfigValue<QStringList> RKSettingsModuleKatePlugins::plugins_to_load {"Plugins to load", QStringList() << "katesearchplugin" << "kateprojectplugin" << "katesnippetsplugin"};
 
-RKSettingsModuleKatePlugins::RKSettingsModuleKatePlugins(RKSettings *gui, QWidget *parent) : RKSettingsModule(gui, parent) {
-	RK_TRACE(SETTINGS);
+class RKSettingsPageKatePlugins : public RKSettingsModuleWidget {
+public:
+	RKSettingsPageKatePlugins(QWidget *parent, RKSettingsModule *parent_module) : RKSettingsModuleWidget(parent, parent_module, RKSettingsModuleKatePlugins::page_id, RKSettingsModulePlugins::addons_superpage_id) {
+		RK_TRACE(SETTINGS);
+
+		setWindowTitle(i18n("Kate Plugins"));
+		setWindowIcon(QIcon::fromTheme("kate"));
+
+		/* Known kate plugins at the time of this writing (March 2020): katesearchplugin katexmltoolsplugin katexmlcheckplugin katectagsplugin katefiletreeplugin 	katecloseexceptplugin katebacktracebrowserplugin tabswitcherplugin kterustcompletionplugin katekonsoleplugin katesnippetsplugin katefilebrowserplugin katereplicodeplugin ktexteditor_lumen kateprojectplugin kateopenheaderplugin katesymbolviewerplugin ktexteditorpreviewplugin katesqlplugin kategdbplugin katebuildplugin textfilterplugin */
+		QStringList recommended_plugins = QStringList({"katesearchplugin", "katecloseexceptplugin", "katekonsoleplugin", "katesnippetsplugin", "katefiletreeplugin", "kateprojectplugin", "ktexteditorpreviewplugin", "textfilterplugin"});
+
+		QVBoxLayout *vbox = new QVBoxLayout(this);
+		vbox->setContentsMargins(0, 0, 0, 0);
+		vbox->addWidget(RKCommonFunctions::wordWrappedLabel(i18n("<p>Kate plugins to load in RKWard. Note that some loaded plugins will not become visible until certain conditions are met, e.g. you are loading a version controlled file for the <i>Project</i> plugin.</p><p>The plugins listed here have not been developed specifically for RKWard, and several do not make a whole lot of sense in the context of RKWard. Plugins shown in <b>bold</b> have been reported as \"useful\" by RKWard users.</p>")));
+
+		plugin_table = new QTreeWidget();
+		QFont boldfont = plugin_table->font();
+		boldfont.setBold(true);
+		plugin_table->setHeaderLabels(QStringList() << QString() << i18n("Name") << i18n("Description"));
+		KatePluginIntegrationApp *pluginapp = RKWardMainWindow::getMain()->katePluginIntegration();
+		const auto keys = pluginapp->known_plugins.keys();
+		for (const QString &key : keys) {
+			QTreeWidgetItem *item = new QTreeWidgetItem();
+			KPluginMetaData plugindata = pluginapp->known_plugins.value(key).data;
+			item->setData(1, Qt::DisplayRole, plugindata.name());
+			if (recommended_plugins.contains(key)) item->setData(1, Qt::FontRole, boldfont); 
+			item->setData(2, Qt::DisplayRole, plugindata.description());
+			item->setData(1, Qt::DecorationRole, QIcon::fromTheme(plugindata.iconName()));
+			item->setData(1, Qt::UserRole, key);
+			item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable);
+			item->setCheckState(0, RKSettingsModuleKatePlugins::plugins_to_load.get().contains(key) ? Qt::Checked : Qt::Unchecked);
+			plugin_table->addTopLevelItem(item);
+		}
+		plugin_table->resizeColumnToContents(0);
+		plugin_table->resizeColumnToContents(1);
+		plugin_table->sortItems(1, Qt::AscendingOrder);
+		vbox->addWidget(plugin_table);
 
-/* Known kate plugins at the time of this writing (March 2020): katesearchplugin katexmltoolsplugin katexmlcheckplugin katectagsplugin katefiletreeplugin katecloseexceptplugin katebacktracebrowserplugin tabswitcherplugin kterustcompletionplugin katekonsoleplugin katesnippetsplugin katefilebrowserplugin katereplicodeplugin ktexteditor_lumen kateprojectplugin kateopenheaderplugin katesymbolviewerplugin ktexteditorpreviewplugin katesqlplugin kategdbplugin katebuildplugin textfilterplugin */
-	QStringList recommended_plugins = QStringList () << "katesearchplugin" << "katecloseexceptplugin" << "katekonsoleplugin" << "katesnippetsplugin" << "katefiletreeplugin" << "kateprojectplugin" << "ktexteditorpreviewplugin" << "textfilterplugin";
-
-	QVBoxLayout *vbox = new QVBoxLayout(this);
-	vbox->setContentsMargins(0, 0, 0, 0);
-	vbox->addWidget(RKCommonFunctions::wordWrappedLabel(i18n("<p>Kate plugins to load in RKWard. Note that some loaded plugins will not become visible until certain conditions are met, e.g. you are loading a version controlled file for the <i>Project</i> plugin.</p><p>The plugins listed here have not been developed specifically for RKWard, and several do not make a whole lot of sense in the context of RKWard. Plugins shown in <b>bold</b> have been reported as \"useful\" by RKWard users.</p>")));
-
-	plugin_table = new QTreeWidget();
-	QFont boldfont = plugin_table->font();
-	boldfont.setBold(true);
-	plugin_table->setHeaderLabels(QStringList() << QString() << i18n("Name") << i18n("Description"));
-	KatePluginIntegrationApp *pluginapp = RKWardMainWindow::getMain()->katePluginIntegration();
-	const auto keys = pluginapp->known_plugins.keys();
-	for (const QString &key : keys) {
-		QTreeWidgetItem *item = new QTreeWidgetItem();
-		KPluginMetaData plugindata = pluginapp->known_plugins.value(key).data;
-		item->setData(1, Qt::DisplayRole, plugindata.name());
-		if (recommended_plugins.contains(key)) item->setData(1, Qt::FontRole, boldfont); 
-		item->setData(2, Qt::DisplayRole, plugindata.description());
-		item->setData(1, Qt::DecorationRole, QIcon::fromTheme(plugindata.iconName()));
-		item->setData(1, Qt::UserRole, key);
-		item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable);
-		item->setCheckState(0, plugins_to_load.get().contains(key) ? Qt::Checked : Qt::Unchecked);
-		plugin_table->addTopLevelItem(item);
+		connect(plugin_table, &QTreeWidget::itemChanged, this, &RKSettingsPageKatePlugins::change);
+	}
+	void applyChanges() {
+		RK_TRACE(SETTINGS);
+
+		QStringList p;
+		for (int i = plugin_table->topLevelItemCount() - 1; i >= 0; --i) {
+			QTreeWidgetItem *item = plugin_table->topLevelItem(i);
+			if (item->checkState(0) == Qt::Checked) {
+				p.append (item->data(1, Qt::UserRole).toString());
+			}
+		}
+		RKSettingsModuleKatePlugins::plugins_to_load = p;
+		RKWardMainWindow::getMain()->katePluginIntegration()->loadPlugins(p);
 	}
-	plugin_table->resizeColumnToContents(0);
-	plugin_table->resizeColumnToContents(1);
-	plugin_table->sortItems(1, Qt::AscendingOrder);
-	vbox->addWidget(plugin_table);
+private:
+	QTreeWidget *plugin_table;
+};
 
-	connect(plugin_table, &QTreeWidget::itemChanged, this, &RKSettingsModuleKatePlugins::change);
+RKSettingsModuleKatePlugins::RKSettingsModuleKatePlugins(QObject *parent) : RKSettingsModule(parent) {
+	RK_TRACE(SETTINGS);
 }
 
 RKSettingsModuleKatePlugins::~RKSettingsModuleKatePlugins() {
 	RK_TRACE(SETTINGS);
 }
 
-void RKSettingsModuleKatePlugins::applyChanges() {
-	RK_TRACE(SETTINGS);
-
-	QStringList p;
-	for (int i = plugin_table->topLevelItemCount() - 1; i >= 0; --i) {
-		QTreeWidgetItem *item = plugin_table->topLevelItem(i);
-		if (item->checkState(0) == Qt::Checked) {
-			p.append (item->data(1, Qt::UserRole).toString());
-		}
-	}
-	plugins_to_load = p;
-	RKWardMainWindow::getMain()->katePluginIntegration()->loadPlugins(plugins_to_load);
+QList<RKSettingsModuleWidget*> RKSettingsModuleKatePlugins::createPages(QWidget *parent) {
+// TODO: add the plugin config pages
+	return QList<RKSettingsModuleWidget*>{ new RKSettingsPageKatePlugins(parent, this) };
 }
 
 void RKSettingsModuleKatePlugins::syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a) {
@@ -90,13 +107,3 @@ void RKSettingsModuleKatePlugins::syncConfig(KConfig *config, RKConfigBase::Conf
 	KConfigGroup cg = config->group("Kate Plugins");
 	plugins_to_load.syncConfig(cg, a);
 }
-
-QString RKSettingsModuleKatePlugins::caption() const {
-	RK_TRACE(SETTINGS);
-	return i18n("Kate Plugins");
-}
-
-QIcon RKSettingsModuleKatePlugins::icon() const {
-	RK_TRACE(SETTINGS);
-	return QIcon::fromTheme("kate");
-}
diff --git a/rkward/settings/rksettingsmodulekateplugins.h b/rkward/settings/rksettingsmodulekateplugins.h
index 83e641c16..837f4063b 100644
--- a/rkward/settings/rksettingsmodulekateplugins.h
+++ b/rkward/settings/rksettingsmodulekateplugins.h
@@ -18,21 +18,16 @@ class QTreeWidget;
 */
 class RKSettingsModuleKatePlugins : public RKSettingsModule {
 public:
-	RKSettingsModuleKatePlugins(RKSettings *gui, QWidget *parent);
+	RKSettingsModuleKatePlugins(QObject *parent);
 	~RKSettingsModuleKatePlugins();
 
-	void applyChanges() override;
-	void save(KConfig *config) override { syncConfig(config, RKConfigBase::SaveConfig); };
-	static void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a);
-	static void validateSettingsInteractive(QList<RKSetupWizardItem*>*) {};
-
-	QString caption() const override;
-	QIcon icon() const override;
+	void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) override;
+	QList<RKSettingsModuleWidget*> createPages(QWidget *parent) override;
+	static constexpr PageId page_id = QLatin1String("kateplugins");
 
 	static QStringList pluginsToLoad() { return plugins_to_load; };
 private:
-	QTreeWidget *plugin_table;
-
+friend class RKSettingsPageKatePlugins;
 	static RKConfigValue<QStringList> plugins_to_load;
 };
 
diff --git a/rkward/settings/rksettingsmoduleobjectbrowser.cpp b/rkward/settings/rksettingsmoduleobjectbrowser.cpp
index 80e09d7d4..ffd94054a 100644
--- a/rkward/settings/rksettingsmoduleobjectbrowser.cpp
+++ b/rkward/settings/rksettingsmoduleobjectbrowser.cpp
@@ -28,26 +28,52 @@ RKConfigValue<bool> RKSettingsModuleObjectBrowser::workspace_settings[RKObjectLi
 RKConfigValue<bool> RKSettingsModuleObjectBrowser::varselector_settings[RKObjectListViewSettings::SettingsCount] { RKSettingsModuleObjectBrowser::workspace_settings[0],RKSettingsModuleObjectBrowser::workspace_settings[1],RKSettingsModuleObjectBrowser::workspace_settings[2],RKSettingsModuleObjectBrowser::workspace_settings[3]};
 RKConfigValue<QStringList> RKSettingsModuleObjectBrowser::getstructure_blacklist {"package blacklist", QStringList("GO")};
 
-RKSettingsModuleObjectBrowser::RKSettingsModuleObjectBrowser (RKSettings *gui, QWidget *parent) : RKSettingsModule (gui, parent) {
-	RK_TRACE (SETTINGS);
-
-	QVBoxLayout *layout = new QVBoxLayout (this);
+class RKSettingsPageObjectBrowser : public RKSettingsModuleWidget {
+public:
+	RKSettingsPageObjectBrowser(QWidget* parent, RKSettingsModule* parent_module) : RKSettingsModuleWidget(parent, parent_module, RKSettingsModuleObjectBrowser::page_id) {
+		RK_TRACE(SETTINGS);
+
+		setWindowTitle(i18n("Workspace"));
+		setWindowIcon(RKStandardIcons::getIcon(RKStandardIcons::WindowWorkspaceBrowser));
+		help_url = QUrl("rkward://page/rkward_workspace_browser#settings");
+
+		QVBoxLayout *layout = new QVBoxLayout(this);
+
+		// Note: Up to RKWard 0.6.3, this settings module had a lot of additional checkboxes. Since 0.6.4, most settings are stored, implicitly,
+		//       i.e. the Workspace Browser tool window "remembers" its latest settings (and so does the Varselector, separately). This modules
+		//       is still responsible to storing / loading settings.
+
+		blacklist_choser = new MultiStringSelector(i18n("Never fetch the structure of these packages:"), this);
+		blacklist_choser->setValues(RKSettingsModuleObjectBrowser::getstructure_blacklist);
+		connect(blacklist_choser, &MultiStringSelector::listChanged, this, &RKSettingsPageObjectBrowser::change);
+		connect(blacklist_choser, &MultiStringSelector::getNewStrings, this, [this](QStringList *string_list) {
+			bool ok;
+			QString new_string = QInputDialog::getText(this, i18n("Add exclusion"), i18n("Add the name of the package that no structure should be fetched for"), QLineEdit::Normal, QString (), &ok);
+			if (ok) (*string_list).append(new_string);
+		});
+		layout->addWidget(blacklist_choser);
+	}
+	void applyChanges() {
+		RK_TRACE(SETTINGS);
 
-	// Note: Up to RKWard 0.6.3, this settings module had a lot of additional checkboxes. Since 0.6.4, most settings are stored, implicitly,
-	//       i.e. the Workspace Browser tool window "remembers" its latest settings (and so does the Varselector, separately). This modules
-	//       is still responsible to storing / loading settings.
+		RKSettingsModuleObjectBrowser::getstructure_blacklist = blacklist_choser->getValues();
+	}
+private:
+	MultiStringSelector *blacklist_choser;
+};
 
-	blacklist_choser = new MultiStringSelector (i18n ("Never fetch the structure of these packages:"), this);
-	blacklist_choser->setValues (getstructure_blacklist);
-	connect (blacklist_choser, &MultiStringSelector::listChanged, this, &RKSettingsModuleObjectBrowser::listChanged);
-	connect (blacklist_choser, &MultiStringSelector::getNewStrings, this, &RKSettingsModuleObjectBrowser::addBlackList);
-	layout->addWidget (blacklist_choser);
+RKSettingsModuleObjectBrowser::RKSettingsModuleObjectBrowser(QObject *parent) : RKSettingsModule(parent) {
+	RK_TRACE(SETTINGS);
 }
 
 RKSettingsModuleObjectBrowser::~RKSettingsModuleObjectBrowser () {
 	RK_TRACE (SETTINGS);
 }
 
+QList<RKSettingsModuleWidget*> RKSettingsModuleObjectBrowser::createPages(QWidget *parent) {
+	return QList<RKSettingsModuleWidget*>{ new RKSettingsPageObjectBrowser(parent, this) };
+}
+
 //static
 void RKSettingsModuleObjectBrowser::setDefaultForWorkspace (RKObjectListViewSettings::PersistentSettings setting, bool state) {
 	RK_TRACE (SETTINGS);
@@ -66,29 +92,6 @@ bool RKSettingsModuleObjectBrowser::isPackageBlacklisted (const QString &package
 	return getstructure_blacklist.get().contains (package_name);
 }
 
-void RKSettingsModuleObjectBrowser::addBlackList (QStringList *string_list) {
-	RK_TRACE (SETTINGS);
-	bool ok;
-	QString new_string = QInputDialog::getText (this, i18n ("Add exclusion"), i18n ("Add the name of the package that no structure should be fetched for"), QLineEdit::Normal, QString (), &ok);
-	if (ok) (*string_list).append (new_string);
-}
-
-void RKSettingsModuleObjectBrowser::applyChanges () {
-	RK_TRACE (SETTINGS);
-
-	getstructure_blacklist = blacklist_choser->getValues();
-}
-
-QString RKSettingsModuleObjectBrowser::caption() const {
-	RK_TRACE(SETTINGS);
-	return(i18n("Workspace"));
-}
-
-QIcon RKSettingsModuleObjectBrowser::icon() const {
-	RK_TRACE(SETTINGS);
-	return RKStandardIcons::getIcon(RKStandardIcons::WindowWorkspaceBrowser);
-}
-
 void writeSettings (KConfigGroup &cg, bool *settings) {
 	cg.writeEntry ("show hidden vars", settings[RKObjectListViewSettings::ShowObjectsHidden]);
 	cg.writeEntry ("show label field", settings[RKObjectListViewSettings::ShowFieldsLabel]);
@@ -113,13 +116,3 @@ void RKSettingsModuleObjectBrowser::syncConfig(KConfig *config, RKConfigBase::Co
 	syncSettings (cg.group("Varselector"), a, varselector_settings);
 }
 
-void RKSettingsModuleObjectBrowser::boxChanged (int) {
-	RK_TRACE (SETTINGS);
-	change ();
-}
-
-void RKSettingsModuleObjectBrowser::listChanged () {
-	RK_TRACE (SETTINGS);
-	change ();
-}
-
diff --git a/rkward/settings/rksettingsmoduleobjectbrowser.h b/rkward/settings/rksettingsmoduleobjectbrowser.h
index 5ab45d739..8e6a0115b 100644
--- a/rkward/settings/rksettingsmoduleobjectbrowser.h
+++ b/rkward/settings/rksettingsmoduleobjectbrowser.h
@@ -22,19 +22,12 @@ class MultiStringSelector;
 class RKSettingsModuleObjectBrowser : public RKSettingsModule {
 	Q_OBJECT
 public:
-	RKSettingsModuleObjectBrowser (RKSettings *gui, QWidget *parent);
-	~RKSettingsModuleObjectBrowser ();
+	RKSettingsModuleObjectBrowser(QObject *parent);
+	~RKSettingsModuleObjectBrowser();
 
-/** applies current settings in this RKSettingsModule. This will only be called, if hasChanges () is true */
-	void applyChanges () override;
-
-/** @returns the caption ("Workspace Browser") */
-	QString caption() const override;
-	QIcon icon() const override;
-
-	void save(KConfig *config) override { syncConfig(config, RKConfigBase::SaveConfig); };
-	static void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a);
-	static void validateSettingsInteractive (QList<RKSetupWizardItem*>*) {};
+	void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) override;
+	QList<RKSettingsModuleWidget*> createPages(QWidget *parent) override;
+	static constexpr PageId page_id = QLatin1String("browser");
 
 	static bool isDefaultForWorkspace (RKObjectListViewSettings::PersistentSettings setting) { return workspace_settings[setting]; };
 	static bool isDefaultForVarselector (RKObjectListViewSettings::PersistentSettings setting) { return varselector_settings[setting]; };
@@ -42,15 +35,8 @@ public:
 	static void setDefaultForVarselector (RKObjectListViewSettings::PersistentSettings setting, bool state);
 
 	static bool isPackageBlacklisted (const QString &package_name);
-
-	QUrl helpURL () override { return QUrl ("rkward://page/rkward_workspace_browser#settings"); };
-public Q_SLOTS:
-/** called when a checkbox has been changed. Signals change to RKSettings dialog to enable apply button */
-	void boxChanged (int);
-	void listChanged ();
-	void addBlackList (QStringList *string_list);
 private:
-	MultiStringSelector *blacklist_choser;
+friend class RKSettingsPageObjectBrowser;
 	static RKConfigValue<QStringList> getstructure_blacklist;
 
 	static RKConfigValue<bool> workspace_settings[RKObjectListViewSettings::SettingsCount];
diff --git a/rkward/settings/rksettingsmoduleoutput.cpp b/rkward/settings/rksettingsmoduleoutput.cpp
index 0d2e49c28..db754d5c1 100644
--- a/rkward/settings/rksettingsmoduleoutput.cpp
+++ b/rkward/settings/rksettingsmoduleoutput.cpp
@@ -33,7 +33,7 @@ RKConfigValue<bool> RKCarbonCopySettings::cc_app_plugin_commands {"CC app/plugin
 RKConfigValue<bool> RKCarbonCopySettings::cc_command_output {"CC command output", true};
 QList<RKCarbonCopySettings*> RKCarbonCopySettings::instances;
 
-RKCarbonCopySettings::RKCarbonCopySettings(QWidget* parent, RKSettingsModule* module) : RKSettingsModuleWidget(parent, module) {
+RKCarbonCopySettings::RKCarbonCopySettings(QWidget* parent, RKSettingsModule* module) : RKSettingsModuleWidget(parent, module, RKSettingsModule::no_page_id) {
 	RK_TRACE (SETTINGS);
 
 	QVBoxLayout *main_vbox = new QVBoxLayout (this);
@@ -111,100 +111,105 @@ RKConfigValue<int> RKSettingsModuleOutput::graphics_jpg_quality {"graphics_jpg_q
 RKConfigValue<QString> RKSettingsModuleOutput::custom_css_file {"custom css file", QString()};
 RKConfigValue<bool> RKSettingsModuleOutput::shared_default_output {"Shared default output", false};
 
-RKSettingsModuleOutput::RKSettingsModuleOutput (RKSettings *gui, QWidget *parent) : RKSettingsModule(gui, parent) {
-	RK_TRACE (SETTINGS);
-
-	QVBoxLayout *main_vbox = new QVBoxLayout (this);
-	
-	QGroupBox *group = new QGroupBox (i18n ("Output Window options"), this);
-	QVBoxLayout* group_layout = new QVBoxLayout (group);
-	auto auto_show_box = auto_show.makeCheckbox(i18n("show window on new output"), this);
-	group_layout->addWidget (auto_show_box);
-	auto auto_raise_box = auto_raise.makeCheckbox(i18n("raise window on new output"), this);
-	group_layout->addWidget (auto_raise_box);
-	auto_raise_box->setEnabled (auto_show);
-	connect(auto_show_box, &QCheckBox::stateChanged, auto_raise_box, [auto_raise_box](int state) {
-		auto_raise_box->setEnabled(state);
-	});
-
-	main_vbox->addWidget (group);
-
-	main_vbox->addWidget(shared_default_output.makeCheckbox(i18n("Default output (used, while no other output has been set, explicitly) is shared across workspaces(*)."), this));
-
-	custom_css_file_box = new GetFileNameWidget (this, GetFileNameWidget::ExistingFile, true, i18n ("CSS file to use for output (leave empty for default)"), i18n ("Select CSS file"), custom_css_file);
-	connect (custom_css_file_box, &GetFileNameWidget::locationChanged, this, &RKSettingsModuleOutput::boxChanged);
-	RKCommonFunctions::setTips (i18n ("Select a CSS file for custom formatting of the output window. Leave empty to use the default CSS file shipped with RKWard. Note that this setting takes effect, when initializing an output file (e.g. after flushing the output), only."), custom_css_file_box);
-	main_vbox->addWidget (custom_css_file_box);
-
-	group = new QGroupBox (i18n ("Graphics"), this);
-	group_layout = new QVBoxLayout (group);
-	QHBoxLayout *h_layout = new QHBoxLayout ();
-	group_layout->addLayout (h_layout);
-	h_layout->addWidget (new QLabel (i18n ("File format"), group));
-	h_layout->addWidget (graphics_type_box = new QComboBox (group));
-	graphics_type_box->addItem (i18n ("<Default>"), QString ("NULL"));
-	graphics_type_box->addItem (i18n ("PNG"), QString ("\"PNG\""));
-	graphics_type_box->addItem (i18n ("SVG"), QString ("\"SVG\""));
-	graphics_type_box->addItem (i18n ("JPG"), QString ("\"JPG\""));
-	graphics_type_box->setCurrentIndex (graphics_type_box->findData (graphics_type.get()));
-	graphics_type_box->setEditable (false);
-	connect (graphics_type_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &RKSettingsModuleOutput::boxChanged);
-	h_layout->addSpacing (2*RKStyle::spacingHint ());
-	h_layout->addWidget (new QLabel (i18n ("JPG quality"), group));
-	h_layout->addWidget (graphics_jpg_quality_box = graphics_jpg_quality.makeSpinBox(1, 100, this));
-	graphics_jpg_quality_box->setEnabled (graphics_type == "\"JPG\"");
-	h_layout->addStretch ();
-
-	h_layout = new QHBoxLayout ();
-	group_layout->addLayout (h_layout);
-	h_layout->addWidget (new QLabel (i18n ("Width:"), group));
-	h_layout->addWidget (graphics_width.makeSpinBox(1, INT_MAX, this));
-	h_layout->addSpacing (2*RKStyle::spacingHint ());
-	h_layout->addWidget (new QLabel (i18n ("Height:"), group));
-	h_layout->addWidget (graphics_height.makeSpinBox(1, INT_MAX, this));
-	h_layout->addStretch ();
-
-	main_vbox->addWidget (group);
-
-	cc_settings = new RKCarbonCopySettings(this, this);
-	main_vbox->addWidget (cc_settings);
-
-	main_vbox->addStretch ();
-}
-
-RKSettingsModuleOutput::~RKSettingsModuleOutput() {
-	RK_TRACE (SETTINGS);
-}
-
-void RKSettingsModuleOutput::boxChanged () {
-	RK_TRACE (SETTINGS);
-	change ();
-	graphics_jpg_quality_box->setEnabled (graphics_type_box->itemData (graphics_type_box->currentIndex ()).toString () == "\"JPG\"");
-}
+class RKSettingsPageOutput : public RKSettingsModuleWidget {
+public:
+	RKSettingsPageOutput(QWidget *parent, RKSettingsModule* parent_module) : RKSettingsModuleWidget(parent, parent_module, RKSettingsModuleOutput::page_id) {
+		RK_TRACE(SETTINGS);
+
+		setWindowTitle(i18n("Output"));
+		setWindowIcon(RKStandardIcons::getIcon(RKStandardIcons::WindowOutput));
+
+		QVBoxLayout *main_vbox = new QVBoxLayout(this);
+		
+		QGroupBox *group = new QGroupBox(i18n("Output Window options"), this);
+		QVBoxLayout* group_layout = new QVBoxLayout(group);
+		auto auto_show_box = RKSettingsModuleOutput::auto_show.makeCheckbox(i18n("show window on new output"), this);
+		group_layout->addWidget(auto_show_box);
+		auto auto_raise_box = RKSettingsModuleOutput::auto_raise.makeCheckbox(i18n("raise window on new output"), this);
+		group_layout->addWidget(auto_raise_box);
+		auto_raise_box->setEnabled(RKSettingsModuleOutput::auto_show);
+		connect(auto_show_box, &QCheckBox::stateChanged, auto_raise_box, [auto_raise_box](int state) {
+			auto_raise_box->setEnabled(state);
+		});
+
+		main_vbox->addWidget(group);
+
+		main_vbox->addWidget(RKSettingsModuleOutput::shared_default_output.makeCheckbox(i18n("Default output (used, while no other output has been set, explicitly) is shared across workspaces(*)."), this));
+
+		custom_css_file_box = new GetFileNameWidget(this, GetFileNameWidget::ExistingFile, true, i18n ("CSS file to use for output (leave empty for default)"), i18n ("Select CSS file"), RKSettingsModuleOutput::custom_css_file);
+		connect (custom_css_file_box, &GetFileNameWidget::locationChanged, this, &RKSettingsPageOutput::boxChanged);
+		RKCommonFunctions::setTips (i18n ("Select a CSS file for custom formatting of the output window. Leave empty to use the default CSS file shipped with RKWard. Note that this setting takes effect, when initializing an output file (e.g. after flushing the output), only."), custom_css_file_box);
+		main_vbox->addWidget (custom_css_file_box);
+
+		group = new QGroupBox(i18n("Graphics"), this);
+		group_layout = new QVBoxLayout(group);
+		QHBoxLayout *h_layout = new QHBoxLayout();
+		group_layout->addLayout(h_layout);
+		h_layout->addWidget(new QLabel(i18n("File format"), group));
+		h_layout->addWidget(graphics_type_box = new QComboBox(group));
+		graphics_type_box->addItem(i18n("<Default>"), QString("NULL"));
+		graphics_type_box->addItem(i18n("PNG"), QString("\"PNG\""));
+		graphics_type_box->addItem(i18n("SVG"), QString("\"SVG\""));
+		graphics_type_box->addItem(i18n("JPG"), QString("\"JPG\""));
+		graphics_type_box->setCurrentIndex(graphics_type_box->findData (RKSettingsModuleOutput::graphics_type.get()));
+		graphics_type_box->setEditable(false);
+		connect(graphics_type_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &RKSettingsPageOutput::boxChanged);
+		h_layout->addSpacing(2*RKStyle::spacingHint());
+		h_layout->addWidget(new QLabel(i18n("JPG quality"), group));
+		h_layout->addWidget(graphics_jpg_quality_box = RKSettingsModuleOutput::graphics_jpg_quality.makeSpinBox(1, 100, this));
+		graphics_jpg_quality_box->setEnabled(RKSettingsModuleOutput::graphics_type == "\"JPG\"");
+		h_layout->addStretch();
+
+		h_layout = new QHBoxLayout();
+		group_layout->addLayout(h_layout);
+		h_layout->addWidget(new QLabel(i18n("Width:"), group));
+		h_layout->addWidget(RKSettingsModuleOutput::graphics_width.makeSpinBox(1, INT_MAX, this));
+		h_layout->addSpacing(2*RKStyle::spacingHint());
+		h_layout->addWidget(new QLabel(i18n("Height:"), group));
+		h_layout->addWidget(RKSettingsModuleOutput::graphics_height.makeSpinBox(1, INT_MAX, this));
+		h_layout->addStretch();
+
+		main_vbox->addWidget(group);
+
+		cc_settings = new RKCarbonCopySettings(this, parent_module);
+		main_vbox->addWidget(cc_settings);
+
+		main_vbox->addStretch();
+	}
+	void boxChanged() {
+		RK_TRACE(SETTINGS);
+		change();
+		graphics_jpg_quality_box->setEnabled(graphics_type_box->itemData(graphics_type_box->currentIndex()).toString() == "\"JPG\"");
+	}
+	void applyChanges() {
+		RK_TRACE(SETTINGS);
+		RKSettingsModuleOutput::custom_css_file = custom_css_file_box->getLocation();
+		RKSettingsModuleOutput::graphics_type = graphics_type_box->itemData(graphics_type_box->currentIndex()).toString();
 
-QString RKSettingsModuleOutput::caption() const {
-	RK_TRACE(SETTINGS);
-	return(i18n("Output"));
-}
+		QStringList commands = RKSettingsModuleOutput::makeRRunTimeOptionCommands();
+		for (QStringList::const_iterator it = commands.cbegin(); it != commands.cend(); ++it) {
+			RInterface::issueCommand(new RCommand(*it, RCommand::App), parent_module->commandChain());
+		}
 
-QIcon RKSettingsModuleOutput::icon() const{
+		cc_settings->applyChanges();
+	}
+private:
+	QComboBox *graphics_type_box;
+	RKSpinBox *graphics_jpg_quality_box;
+	RKCarbonCopySettings *cc_settings;
+	GetFileNameWidget *custom_css_file_box;
+};
+
+RKSettingsModuleOutput::RKSettingsModuleOutput(QObject *parent) : RKSettingsModule(parent) {
 	RK_TRACE(SETTINGS);
-	return RKStandardIcons::getIcon(RKStandardIcons::WindowOutput);
 }
 
-void RKSettingsModuleOutput::applyChanges () {
+RKSettingsModuleOutput::~RKSettingsModuleOutput() {
 	RK_TRACE (SETTINGS);
+}
 
-	custom_css_file = custom_css_file_box->getLocation ();
-
-	graphics_type = graphics_type_box->itemData (graphics_type_box->currentIndex ()).toString ();
-
-	QStringList commands = makeRRunTimeOptionCommands ();
-	for (QStringList::const_iterator it = commands.cbegin (); it != commands.cend (); ++it) {
-		RInterface::issueCommand(new RCommand(*it, RCommand::App), commandChain());
-	}
-
-	cc_settings->applyChanges ();
+QList<RKSettingsModuleWidget*> RKSettingsModuleOutput::createPages(QWidget *parent) {
+	return QList<RKSettingsModuleWidget*>{ new RKSettingsPageOutput(parent, this) };
 }
 
 void RKSettingsModuleOutput::syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a) {
diff --git a/rkward/settings/rksettingsmoduleoutput.h b/rkward/settings/rksettingsmoduleoutput.h
index 48425ba3b..eaa6e5daf 100644
--- a/rkward/settings/rksettingsmoduleoutput.h
+++ b/rkward/settings/rksettingsmoduleoutput.h
@@ -59,32 +59,21 @@ private:
 class RKSettingsModuleOutput : public RKSettingsModule {
 	Q_OBJECT
 public:
-	RKSettingsModuleOutput (RKSettings *gui, QWidget *parent);
-	~RKSettingsModuleOutput ();
+	RKSettingsModuleOutput(QObject *parent);
+	~RKSettingsModuleOutput();
 
-	void applyChanges () override;
-	void save(KConfig *config) override { syncConfig(config, RKConfigBase::SaveConfig); };
-	static void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a);
+	void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) override;
+	QList<RKSettingsModuleWidget*> createPages(QWidget *parent) override;
+	static constexpr PageId page_id = QLatin1String("output");
 
 /** generate the commands needed to set the R run time options */
 	static QStringList makeRRunTimeOptionCommands ();
 
-	static void validateSettingsInteractive (QList<RKSetupWizardItem*>*) {};
-
-	QString caption() const override;
-	QIcon icon() const override;
-
 	static bool autoShow () { return auto_show; };
 	static bool autoRaise () { return auto_raise; };
 	static bool sharedDefaultOutput() { return shared_default_output; };
-public Q_SLOTS:
-	void boxChanged ();
 private:
-	QComboBox *graphics_type_box;
-	RKSpinBox *graphics_jpg_quality_box;
-	RKCarbonCopySettings *cc_settings;
-	GetFileNameWidget *custom_css_file_box;
-
+friend class RKSettingsPageOutput;
 	static RKConfigValue<bool> auto_show;
 	static RKConfigValue<bool> auto_raise;
 	static RKConfigValue<QString> graphics_type;
diff --git a/rkward/settings/rksettingsmoduleplugins.cpp b/rkward/settings/rksettingsmoduleplugins.cpp
index ae3f44d1e..7dce1fb18 100644
--- a/rkward/settings/rksettingsmoduleplugins.cpp
+++ b/rkward/settings/rksettingsmoduleplugins.cpp
@@ -28,6 +28,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include "../misc/rkcommonfunctions.h"
 #include "../misc/rkspinbox.h"
 #include "../misc/xmlhelper.h"
+#include "../windows/rkworkplace.h"
 #include "../plugin/rkcomponentmap.h"
 #include "../dialogs/rkloadlibsdialog.h"
 #include "rksettingsmodulegeneral.h"
@@ -41,67 +42,81 @@ RKConfigValue<bool> RKSettingsModulePlugins::show_code {"Code display default",
 RKConfigValue<int> RKSettingsModulePlugins::code_size {"Code display size", 250};
 RKConfigValue<int> RKSettingsModulePlugins::side_preview_width {"Other preview size", 250};
 
-RKSettingsModulePlugins::RKSettingsModulePlugins (RKSettings *gui, QWidget *parent) : RKSettingsModule (gui, parent) {
-	RK_TRACE (SETTINGS);
 
-	QVBoxLayout *main_vbox = new QVBoxLayout (this);
-	
-	main_vbox->addSpacing (2*RKStyle::spacingHint ());
-	
-	main_vbox->addWidget (RKCommonFunctions::wordWrappedLabel (i18n ("Some plugins are available with both, a wizard-like interface and a traditional dialog interface. If both are available, which mode of presentation do you prefer?")));
+#include <QLabel>
 
+class RKSettingsPagePlugins : public RKSettingsModuleWidget {
+public:
+	RKSettingsPagePlugins(QWidget *parent, RKSettingsModule *parent_module) : RKSettingsModuleWidget(parent, parent_module, RKSettingsModulePlugins::page_id, RKSettingsModulePlugins::addons_superpage_id) {
+		RK_TRACE(SETTINGS);
 
-	QGroupBox* button_box = new QGroupBox (this);
-	QVBoxLayout* group_layout = new QVBoxLayout (button_box);
-	button_group = new QButtonGroup (button_box);
+		setWindowTitle(i18n("RKWard Plugins"));
+		setWindowIcon(QIcon::fromTheme("plugins"));
 
-	QAbstractButton* button;
-	button = new QRadioButton (i18n ("Always prefer dialogs"), button_box);
-	group_layout->addWidget (button);
-	button_group->addButton (button, PreferDialog);
-	button = new QRadioButton (i18n ("Prefer recommended interface"), button_box);
-	group_layout->addWidget (button);
-	button_group->addButton (button, PreferRecommended);
-	button = new QRadioButton (i18n ("Always prefer wizards"), button_box);
-	group_layout->addWidget (button);
-	button_group->addButton (button, PreferWizard);
-	if ((button = button_group->button (interface_pref))) button->setChecked (true);
+		QVBoxLayout *main_vbox = new QVBoxLayout(this);
+		main_vbox->addSpacing(2*RKStyle::spacingHint());
+		main_vbox->addWidget(RKCommonFunctions::wordWrappedLabel(i18n("Some plugins are available with both, a wizard-like interface and a traditional dialog interface. If both are available, which mode of presentation do you prefer?")));
 
-	connect (button_group, &QButtonGroup::idClicked, this, &RKSettingsModulePlugins::settingChanged);
-	main_vbox->addWidget (button_box);
+		QGroupBox* button_box = new QGroupBox(this);
+		QVBoxLayout* group_layout = new QVBoxLayout(button_box);
+		button_group = new QButtonGroup(button_box);
 
-	main_vbox->addSpacing (2*RKStyle::spacingHint ());
+		QAbstractButton* button;
+		button = new QRadioButton(i18n("Always prefer dialogs"), button_box);
+		group_layout->addWidget(button);
+		button_group->addButton(button, RKSettingsModulePlugins::PreferDialog);
+		button = new QRadioButton(i18n("Prefer recommended interface"), button_box);
+		group_layout->addWidget(button);
+		button_group->addButton(button, RKSettingsModulePlugins::PreferRecommended);
+		button = new QRadioButton(i18n("Always prefer wizards"), button_box);
+		group_layout->addWidget(button);
+		button_group->addButton(button, RKSettingsModulePlugins::PreferWizard);
+		if ((button = button_group->button(RKSettingsModulePlugins::interface_pref))) button->setChecked(true);
 
-	QPushButton *pluginmap_config_button = new QPushButton (i18n ("Configure Active Plugins"), this);
-	connect (pluginmap_config_button, &QPushButton::clicked, this, &RKSettingsModulePlugins::configurePluginmaps);
-	main_vbox->addWidget (pluginmap_config_button);
+		connect(button_group, &QButtonGroup::idClicked, this, &RKSettingsPagePlugins::change);
+		main_vbox->addWidget(button_box);
 
-	main_vbox->addStretch ();
-}
+		main_vbox->addSpacing(2*RKStyle::spacingHint());
 
-RKSettingsModulePlugins::~RKSettingsModulePlugins() {
-	RK_TRACE (SETTINGS);
-}
+		QPushButton *pluginmap_config_button = new QPushButton(i18n("Configure Active Plugins"), this);
+		connect(pluginmap_config_button, &QPushButton::clicked, this, [this]() { RKLoadLibsDialog::showPluginmapConfig(this, parentModule()->commandChain()); });
+		main_vbox->addWidget(pluginmap_config_button);
 
-void RKSettingsModulePlugins::settingChanged () {
-	RK_TRACE (SETTINGS);
-	change ();
-}
+		main_vbox->addStretch();
+	}
+	void applyChanges() {
+		RK_TRACE(SETTINGS);
 
-QString RKSettingsModulePlugins::caption() const {
-	RK_TRACE(SETTINGS);
-	return(i18n("RKWard Plugins"));
-}
+		RKSettingsModulePlugins::interface_pref = static_cast<RKSettingsModulePlugins::PluginPrefs>(button_group->checkedId());
+	}
+private:
+	QButtonGroup *button_group;
+};
+
+class RKSettingsHeaderPage : public RKSettingsModuleWidget {
+public:
+	RKSettingsHeaderPage(QWidget *parent, RKSettingsModule *parent_module) : RKSettingsModuleWidget(parent, parent_module, RKSettingsModulePlugins::addons_superpage_id) {
+		setWindowTitle(i18n("Add-ons"));
+		auto layout = new QVBoxLayout(this);
+		QLabel *l = new QLabel(i18n("<h1>Add-ons</h1><p>RKWard add-ons come in a variety of forms, each with their own configuration options:</p><h2>R packages</h2><p><a href=\"rkward://settings/rpackages\">Add-ons to the R language itself</a>. These are usually downloaded from \"CRAN\". Some of these add-on packages may additionally contain RKWard plugins.</p><h2>RKWard plugins</h2><p><a href=\"rkward://settings/plugins\">Graphical dialogs to R functionality</a>. These plugins are usually pre-installed with RKWard, or with an R package. However, they can be activated/deactivated to help keep the menus manageable. Note that it is relatively easy to <a href=\"https://api.kde.org/doc/rkwardplugins/\">create your own custom dialogs as plugins</a>!</p><h2>Kate plugins</h2><p><a href=\"rkward://settings/kateplugins\">Plugins developed for Kate / KTextEditor</a>. These provide shared functionality that is useful in the context of text editing and IDE applications. These plugins are usually found pre-installed on your system. You can configure to load the plugins that are useful to your own workflow.</p>"));
+		l->setWordWrap(true);
+		connect(l, &QLabel::linkActivated, [=](const QString &url) { RKWorkplace::mainWorkplace()->openAnyUrl(QUrl(url)); });
+		layout->addWidget(l);
+		layout->addStretch();
+	}
+	void applyChanges() {};
+};
 
-QIcon RKSettingsModulePlugins::icon() const {
+RKSettingsModulePlugins::RKSettingsModulePlugins(QObject *parent) : RKSettingsModule(parent) {
 	RK_TRACE(SETTINGS);
-	return QIcon::fromTheme("plugins");
 }
 
-void RKSettingsModulePlugins::applyChanges () {
+RKSettingsModulePlugins::~RKSettingsModulePlugins() {
 	RK_TRACE (SETTINGS);
+}
 
-	interface_pref = static_cast<PluginPrefs> (button_group->checkedId ());
+QList<RKSettingsModuleWidget*> RKSettingsModulePlugins::createPages(QWidget *parent) {
+	return QList<RKSettingsModuleWidget*>{ new RKSettingsHeaderPage(parent, this), new RKSettingsPagePlugins(parent, this) };
 }
 
 RKSettingsModulePlugins::RKPluginMapList RKSettingsModulePlugins::setPluginMaps(const RKPluginMapList &new_list) {
@@ -113,12 +128,6 @@ RKSettingsModulePlugins::RKPluginMapList RKSettingsModulePlugins::setPluginMaps(
 	return known_plugin_maps;
 }
 
-void RKSettingsModulePlugins::configurePluginmaps () {
-	RK_TRACE (SETTINGS);
-
-	RKLoadLibsDialog::showPluginmapConfig (this, commandChain ());
-}
-
 void RKSettingsModulePlugins::RKPluginMapList::saveToConfig(KConfigGroup& cg) {
 	RK_TRACE(SETTINGS);
 
diff --git a/rkward/settings/rksettingsmoduleplugins.h b/rkward/settings/rksettingsmoduleplugins.h
index e6ce6f458..6b9d4fef5 100644
--- a/rkward/settings/rksettingsmoduleplugins.h
+++ b/rkward/settings/rksettingsmoduleplugins.h
@@ -25,19 +25,15 @@ class RKSpinBox;
 class RKSettingsModulePlugins : public RKSettingsModule {
 	Q_OBJECT
 public:
-	RKSettingsModulePlugins (RKSettings *gui, QWidget *parent);
-	~RKSettingsModulePlugins ();
-
-	void applyChanges () override;
+	RKSettingsModulePlugins(QObject *parent);
+	~RKSettingsModulePlugins();
 
 	enum PluginPrefs { PreferDialog=0, PreferRecommended=1, PreferWizard=2 };
 
-	void save(KConfig *config) override { syncConfig(config, RKConfigBase::SaveConfig); };
-	static void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a);
-	static void validateSettingsInteractive (QList<RKSetupWizardItem*>*) {};
-
-	QString caption() const override;
-	QIcon icon() const override;
+	void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) override;
+	QList<RKSettingsModuleWidget*> createPages(QWidget *parent) override;
+	static constexpr PageId page_id = QLatin1String("plugins");
+	static constexpr PageId addons_superpage_id = QLatin1String("addons");
 
 	/** @returns a list of active plugin maps */
 	static QStringList pluginMaps ();
@@ -95,7 +91,7 @@ public:
 		void saveToConfig(KConfigGroup &cg);
 		void readFromConfig(KConfigGroup &cg);
 	private:
-friend class RKSettingsModulePluginsModel;
+	friend class RKSettingsModulePluginsModel;
 		struct DummyListStruct {
 			bool active;
 			PluginMapInfoList list;
@@ -106,15 +102,11 @@ friend class RKSettingsModulePluginsModel;
 	/** Registers the plugin maps that are shipped with RKWard.
 	 * @param force_add All default maps are also activated, even if they were already known, and disabled by the user. */
 	static void registerDefaultPluginMaps (AddMode add_mode);
-public Q_SLOTS:
-	void settingChanged ();
-	void configurePluginmaps ();
 private:
-	QButtonGroup *button_group;
-
+friend class RKSettingsPagePlugins;
+friend class RKSettingsModulePluginsModel;
 	/** plugin maps which are not necessarily active, but have been encountered, before. @see plugin_maps */
 	static RKPluginMapList known_plugin_maps;
-friend class RKSettingsModulePluginsModel;
 	static RKPluginMapList knownPluginmaps() { return known_plugin_maps; };
 	static PluginMapStoredInfo parsePluginMapBasics(const QString &filename);
 
diff --git a/rkward/settings/rksettingsmoduler.cpp b/rkward/settings/rksettingsmoduler.cpp
index 971a2abc4..7d0ecdea1 100755
--- a/rkward/settings/rksettingsmoduler.cpp
+++ b/rkward/settings/rksettingsmoduler.cpp
@@ -24,6 +24,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <QInputDialog>
 
 #include "rksettingsmodulegeneral.h"
+#include "rksettingsmoduleplugins.h"
 #include "../core/robject.h"
 #include "../dialogs/rksetupwizard.h"
 #include "../misc/multistringselector.h"
@@ -59,170 +60,170 @@ RKConfigValue<QString> RKSettingsModuleR::options_further {"further init command
 RKConfigValue<QStringList> RKSettingsModuleR::options_addpaths {"addsyspaths", QStringList()};
 RKConfigValue<QString> RKSettingsModuleR::options_r_binary {"user configured R binary", QString()};
 
-RKSettingsModuleR::RKSettingsModuleR (RKSettings *gui, QWidget *parent) : RKSettingsModule(gui, parent) {
-	RK_TRACE (SETTINGS);
-
-	QVBoxLayout *main_vbox = new QVBoxLayout (this);
-
-	main_vbox->addSpacing (2*RKStyle::spacingHint ());
-
-	main_vbox->addWidget (RKCommonFunctions::wordWrappedLabel (i18n ("The following settings mostly affect R behavior in the console. It is generally safe to keep these unchanged.")));
-
-	QGridLayout *grid = new QGridLayout ();
-	main_vbox->addLayout (grid);
-	int row = -1;
-
-	// options (warn)
-	grid->addWidget(new QLabel(i18n("Display warnings")), ++row, 0);
-	auto warn_input = options_warn.makeDropDown(RKConfigBase::LabelList(
-		{{-1, i18n("Suppress warnings")}, {0, i18n("Print warnings later (default)")}, {1, i18n("Print warnings immediately")}, {2, i18n ("Convert warnings to errors")}}
-	), this);
-	grid->addWidget(warn_input, row, 1);
-
-	// options (OutDec)
-	grid->addWidget (new QLabel (i18n ("Decimal character (only for printing)"), this), ++row, 0);
-	outdec_input = new QLineEdit (options_outdec, this);
-	outdec_input->setMaxLength (1);
-	connect (outdec_input, &QLineEdit::textChanged, this, &RKSettingsModuleR::settingChanged);
-	grid->addWidget (outdec_input, row, 1);
-
-	// options (width)
-	grid->addWidget(new QLabel(i18n("Output width (characters)")), ++row, 0);
-	grid->addWidget(options_width.makeSpinBox(10, 10000, this), row, 1);
-
-	// options (max.print)
-	grid->addWidget(new QLabel(i18n("Maximum number of elements shown in print")), ++row, 0);
-	grid->addWidget(options_maxprint.makeSpinBox(100, INT_MAX, this), row, 1);
-
-	// options (warnings.length)
-	grid->addWidget(new QLabel(i18n("Maximum length of warnings/errors to print")), ++row, 0);
-	grid->addWidget(options_warningslength.makeSpinBox(100, 8192, this), row, 1);
-
-	// options (keep.source)
-	grid->addWidget(new QLabel(i18n("Keep comments in functions")), ++row, 0);
-	auto keepsource_input = options_keepsource.makeDropDown(RKConfigBase::LabelList({{1, i18n("TRUE (default)")}, {0, i18n("FALSE")}}), this);
-	grid->addWidget(keepsource_input, row, 1);
-
-	// options (keep.source.pkgs)
-	grid->addWidget(new QLabel(i18n("Keep comments in packages")), ++row, 0);
-	auto keepsourcepkgs_input = options_keepsourcepkgs.makeDropDown(RKConfigBase::LabelList({{1, i18n("TRUE")}, {0, i18n("FALSE (default)")}}), this);
-	grid->addWidget(keepsourcepkgs_input, row, 1);
-
-	// options (expressions)
-	grid->addWidget(new QLabel(i18n("Maximum level of nested expressions")), ++row, 0);
-	grid->addWidget(options_expressions.makeSpinBox(25, 500000, this), row, 1);
-
-	// options (digits)
-	grid->addWidget(new QLabel(i18n("Default decimal precision in print ()")), ++row, 0);
-	grid->addWidget(options_digits.makeSpinBox(1, 22, this), row, 1);
-
-	// options (check.bounds)
-	grid->addWidget(new QLabel(i18n("Check vector bounds (warn)")), ++row, 0);
-	auto checkbounds_input = options_checkbounds.makeDropDown(RKConfigBase::LabelList({{1, i18n("TRUE")}, {0, i18n("FALSE (default)")}}), this);
-	grid->addWidget(checkbounds_input, row, 1);
-
-	grid->addWidget (new QLabel (i18n ("Editor command"), this), ++row, 0);
-	editor_input = new QComboBox (this);
-	editor_input->setEditable (true);
-	editor_input->addItem (builtin_editor);
-	if (options_editor != builtin_editor) {
-		editor_input->addItem (options_editor);
-		editor_input->setCurrentIndex (1);
-	}
-	connect (editor_input, &QComboBox::editTextChanged, this, &RKSettingsModuleR::settingChanged);
-	grid->addWidget (editor_input, row, 1);
-
-	grid->addWidget (new QLabel (i18n ("Pager command"), this), ++row, 0);
-	pager_input = new QComboBox (this);
-	pager_input->setEditable (true);
-	pager_input->addItem (builtin_editor);
-	if (options_pager != builtin_editor) {
-		pager_input->addItem (options_pager);
-		pager_input->setCurrentIndex (1);
+class RKSettingsPageR : public RKSettingsModuleWidget {
+public:
+	RKSettingsPageR(QWidget* parent, RKSettingsModule *parent_module) : RKSettingsModuleWidget(parent, parent_module, RKSettingsModuleR::page_id) {
+		RK_TRACE(SETTINGS);
+
+		setWindowTitle(i18n("R-Backend"));
+		setWindowIcon(QIcon::fromTheme("emblem-system-symbolic"));
+
+		QVBoxLayout *main_vbox = new QVBoxLayout(this);
+
+		main_vbox->addSpacing(2*RKStyle::spacingHint());
+
+		main_vbox->addWidget(RKCommonFunctions::wordWrappedLabel(i18n("The following settings mostly affect R behavior in the console. It is generally safe to keep these unchanged.")));
+
+		QGridLayout *grid = new QGridLayout();
+		main_vbox->addLayout(grid);
+		int row = -1;
+
+		// options (warn)
+		grid->addWidget(new QLabel(i18n("Display warnings")), ++row, 0);
+		auto warn_input = RKSettingsModuleR::options_warn.makeDropDown(RKConfigBase::LabelList(
+			{{-1, i18n("Suppress warnings")}, {0, i18n("Print warnings later (default)")}, {1, i18n("Print warnings immediately")}, {2, i18n ("Convert warnings to errors")}}
+		), this);
+		grid->addWidget(warn_input, row, 1);
+
+		// options (OutDec)
+		grid->addWidget(new QLabel(i18n("Decimal character (only for printing)"), this), ++row, 0);
+		outdec_input = new QLineEdit(RKSettingsModuleR::options_outdec, this);
+		outdec_input->setMaxLength(1);
+		connect(outdec_input, &QLineEdit::textChanged, this, &RKSettingsPageR::change);
+		grid->addWidget(outdec_input, row, 1);
+
+		// options (width)
+		grid->addWidget(new QLabel(i18n("Output width (characters)")), ++row, 0);
+		grid->addWidget(RKSettingsModuleR::options_width.makeSpinBox(10, 10000, this), row, 1);
+
+		// options (max.print)
+		grid->addWidget(new QLabel(i18n("Maximum number of elements shown in print")), ++row, 0);
+		grid->addWidget(RKSettingsModuleR::options_maxprint.makeSpinBox(100, INT_MAX, this), row, 1);
+
+		// options (warnings.length)
+		grid->addWidget(new QLabel(i18n("Maximum length of warnings/errors to print")), ++row, 0);
+		grid->addWidget(RKSettingsModuleR::options_warningslength.makeSpinBox(100, 8192, this), row, 1);
+
+		// options (keep.source)
+		grid->addWidget(new QLabel(i18n("Keep comments in functions")), ++row, 0);
+		auto keepsource_input = RKSettingsModuleR::options_keepsource.makeDropDown(RKConfigBase::LabelList({{1, i18n("TRUE (default)")}, {0, i18n("FALSE")}}), this);
+		grid->addWidget(keepsource_input, row, 1);
+
+		// options (keep.source.pkgs)
+		grid->addWidget(new QLabel(i18n("Keep comments in packages")), ++row, 0);
+		auto keepsourcepkgs_input = RKSettingsModuleR::options_keepsourcepkgs.makeDropDown(RKConfigBase::LabelList({{1, i18n("TRUE")}, {0, i18n("FALSE (default)")}}), this);
+		grid->addWidget(keepsourcepkgs_input, row, 1);
+
+		// options (expressions)
+		grid->addWidget(new QLabel(i18n("Maximum level of nested expressions")), ++row, 0);
+		grid->addWidget(RKSettingsModuleR::options_expressions.makeSpinBox(25, 500000, this), row, 1);
+
+		// options (digits)
+		grid->addWidget(new QLabel(i18n("Default decimal precision in print ()")), ++row, 0);
+		grid->addWidget(RKSettingsModuleR::options_digits.makeSpinBox(1, 22, this), row, 1);
+
+		// options (check.bounds)
+		grid->addWidget(new QLabel(i18n("Check vector bounds (warn)")), ++row, 0);
+		auto checkbounds_input = RKSettingsModuleR::options_checkbounds.makeDropDown(RKConfigBase::LabelList({{1, i18n("TRUE")}, {0, i18n("FALSE (default)")}}), this);
+		grid->addWidget(checkbounds_input, row, 1);
+
+		grid->addWidget (new QLabel(i18n("Editor command"), this), ++row, 0);
+		editor_input = new QComboBox(this);
+		editor_input->setEditable(true);
+		editor_input->addItem(RKSettingsModuleR::builtin_editor);
+		if (RKSettingsModuleR::options_editor != RKSettingsModuleR::builtin_editor) {
+			editor_input->addItem(RKSettingsModuleR::options_editor);
+			editor_input->setCurrentIndex(1);
+		}
+		connect(editor_input, &QComboBox::editTextChanged, this, &RKSettingsPageR::change);
+		grid->addWidget(editor_input, row, 1);
+
+		grid->addWidget(new QLabel(i18n("Pager command"), this), ++row, 0);
+		pager_input = new QComboBox(this);
+		pager_input->setEditable(true);
+		pager_input->addItem(RKSettingsModuleR::builtin_editor);
+		if (RKSettingsModuleR::options_pager != RKSettingsModuleR::builtin_editor) {
+			pager_input->addItem(RKSettingsModuleR::options_pager);
+			pager_input->setCurrentIndex(1);
+		}
+		connect(pager_input, &QComboBox::editTextChanged, this, &RKSettingsPageR::change);
+		grid->addWidget(pager_input, row, 1);
+
+		grid->addWidget(new QLabel(i18n ("Further (option) commands to run in each session"), this), ++row, 0, 1, 2);
+		further_input = new QTextEdit(this);
+		further_input->setWordWrapMode(QTextOption::NoWrap);
+		further_input->setAcceptRichText(false);
+		further_input->setPlainText(RKSettingsModuleR::options_further);
+		connect(further_input, &QTextEdit::textChanged, this, &RKSettingsPageR::change);
+		grid->addWidget(further_input, ++row, 0, 1, 2);
+
+		main_vbox->addStretch();
+
+		addpaths_selector = new MultiStringSelector(i18n("Addition search paths for utilities used by R"), this);
+		addpaths_selector->setValues(RKSettingsModuleR::options_addpaths);
+		connect(addpaths_selector, &MultiStringSelector::listChanged, this, &RKSettingsPageR::change);
+		connect(addpaths_selector, &MultiStringSelector::getNewStrings, this, [this](QStringList* string_list) {
+			QDialog dialog(this);
+			dialog.setWindowTitle(i18n("Add System Path Directory"));
+			QVBoxLayout *layout = new QVBoxLayout(&dialog);
+			layout->addWidget (RKCommonFunctions::wordWrappedLabel(i18n("Specify or select directory to add to the system file path of the running R session")));
+
+			KUrlRequester *req = new KUrlRequester();
+			req->setMode(KFile::Directory);
+			layout->addWidget(req);
+
+			QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+			buttons->button(QDialogButtonBox::Ok)->setText(i18nc("Add directory to list", "Add"));
+			connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
+			connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
+			layout->addWidget(buttons);
+
+			if (dialog.exec() == QDialog::Accepted) {
+				if (!req->text().isEmpty()) (*string_list).append(req->text());
+			}
+		});
+		main_vbox->addWidget(addpaths_selector);
 	}
-	connect (pager_input, &QComboBox::editTextChanged, this, &RKSettingsModuleR::settingChanged);
-	grid->addWidget (pager_input, row, 1);
-
-	grid->addWidget (new QLabel (i18n ("Further (option) commands to run in each session"), this), ++row, 0, 1, 2);
-	further_input = new QTextEdit (this);
-	further_input->setWordWrapMode (QTextOption::NoWrap);
-	further_input->setAcceptRichText (false);
-	further_input->setPlainText (options_further);
-	connect (further_input, &QTextEdit::textChanged, this, &RKSettingsModuleR::settingChanged);
-	grid->addWidget (further_input, ++row, 0, 1, 2);
-
-	main_vbox->addStretch ();
-
-	addpaths_selector = new MultiStringSelector (i18n ("Addition search paths for utilities used by R"), this);
-	addpaths_selector->setValues (options_addpaths);
-	connect (addpaths_selector, &MultiStringSelector::listChanged, this, &RKSettingsModuleR::settingChanged);
-	connect (addpaths_selector, &MultiStringSelector::getNewStrings, this, &RKSettingsModuleR::addPaths);
-	main_vbox->addWidget (addpaths_selector);
-}
-
-RKSettingsModuleR::~RKSettingsModuleR() {
-	RK_TRACE (SETTINGS);
-}
-
-void RKSettingsModuleR::settingChanged () {
-	RK_TRACE (SETTINGS);
-	change ();
-}
-
-QString RKSettingsModuleR::caption() const {
-	RK_TRACE(SETTINGS);
-	return(i18n("R-Backend"));
-}
+	void applyChanges() {
+		RK_TRACE(SETTINGS);
+
+		RKSettingsModuleR::options_outdec = outdec_input->text();
+		RKSettingsModuleR::options_editor = editor_input->currentText();
+		RKSettingsModuleR::options_pager = pager_input->currentText();
+		RKSettingsModuleR::options_further = further_input->toPlainText();
+		// normalize system paths before adding
+		QStringList paths = addpaths_selector->getValues();
+		QStringList cleanpaths;
+		for (int i = 0; i < paths.count(); ++i) {
+			QString path = QDir::cleanPath(paths[i]);
+			if (!cleanpaths.contains(path)) cleanpaths.append(path);
+		}
+		RKSettingsModuleR::options_addpaths = cleanpaths;
 
-QIcon RKSettingsModuleR::icon() const {
+		// apply run time options in R
+		QStringList commands = RKSettingsModuleR::makeRRunTimeOptionCommands();
+		for (QStringList::const_iterator it = commands.cbegin(); it != commands.cend(); ++it) {
+			RInterface::issueCommand(new RCommand(*it, RCommand::App), parentModule()->commandChain());
+		}
+	}
+private:
+	QLineEdit *outdec_input;
+	QComboBox *editor_input;
+	QComboBox *pager_input;
+	QTextEdit *further_input;
+	MultiStringSelector *addpaths_selector;
+};
+
+RKSettingsModuleR::RKSettingsModuleR(QObject *parent) : RKSettingsModule(parent) {
 	RK_TRACE(SETTINGS);
-	return QIcon::fromTheme("emblem-system-symbolic");
 }
 
-void RKSettingsModuleR::applyChanges () {
+RKSettingsModuleR::~RKSettingsModuleR() {
 	RK_TRACE (SETTINGS);
-
-	options_outdec = outdec_input->text ();
-	options_editor = editor_input->currentText ();
-	options_pager = pager_input->currentText ();
-	options_further = further_input->toPlainText ();
-	// normalize system paths before adding
-	QStringList paths = addpaths_selector->getValues ();
-	QStringList cleanpaths;
-	for (int i = 0; i < paths.count(); ++i) {
-		QString path = QDir::cleanPath(paths[i]);
-		if (!cleanpaths.contains(path)) cleanpaths.append(path);
-	}
-	options_addpaths = cleanpaths;
-
-// apply run time options in R
-	QStringList commands = makeRRunTimeOptionCommands ();
-	for (QStringList::const_iterator it = commands.cbegin (); it != commands.cend (); ++it) {
-		RInterface::issueCommand(new RCommand(*it, RCommand::App), commandChain ());
-	}
 }
 
-void RKSettingsModuleR::addPaths(QStringList* string_list) {
-	RK_TRACE (SETTINGS);
-
-	QDialog dialog (this);
-	dialog.setWindowTitle (i18n ("Add System Path Directory"));
-	QVBoxLayout *layout = new QVBoxLayout (&dialog);
-	layout->addWidget (RKCommonFunctions::wordWrappedLabel (i18n ("Specify or select directory to add to the system file path of the running R session")));
-
-	KUrlRequester *req = new KUrlRequester ();
-	req->setMode (KFile::Directory);
-	layout->addWidget (req);
-
-	QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
-	buttons->button(QDialogButtonBox::Ok)->setText (i18nc ("Add directory to list", "Add"));
-	connect (buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
-	connect (buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
-	layout->addWidget (buttons);
-
-	if (dialog.exec () == QDialog::Accepted) {
-		if (!req->text ().isEmpty ()) (*string_list).append (req->text ());
-	}
+QList<RKSettingsModuleWidget*> RKSettingsModuleR::createPages(QWidget *parent) {
+	return QList<RKSettingsModuleWidget*>{ new RKSettingsPageR(parent, this) };
 }
 
 static QLatin1String RTrueFalse(bool val) {
@@ -312,67 +313,140 @@ RKConfigValue<QString> RKSettingsModuleRPackages::cran_mirror_url {"CRAN mirror
 QStringList RKSettingsModuleRPackages::defaultliblocs;
 QString RKSettingsModuleRPackages::r_libs_user;
 
-RKSettingsModuleRPackages::RKSettingsModuleRPackages (RKSettings *gui, QWidget *parent) : RKSettingsModule(gui, parent) {
-	RK_TRACE (SETTINGS);
-
-	QVBoxLayout *main_vbox = new QVBoxLayout (this);
-
-	main_vbox->addSpacing (2*RKStyle::spacingHint ());
+class RKSettingsPageRPackages : public RKSettingsModuleWidget {
+public:
+	RKSettingsPageRPackages(QWidget *parent, RKSettingsModule *parent_module) : RKSettingsModuleWidget(parent, parent_module, RKSettingsModuleRPackages::page_id, RKSettingsModulePlugins::addons_superpage_id) {
+		RK_TRACE(SETTINGS);
+
+		setWindowTitle(i18n("R-Packages"));
+		setWindowIcon(RKStandardIcons::getIcon(RKStandardIcons::ObjectPackageEnvironment));
+
+		QVBoxLayout *main_vbox = new QVBoxLayout(this);
+
+		main_vbox->addSpacing(2*RKStyle::spacingHint());
+
+		main_vbox->addWidget(new QLabel(i18n("CRAN download mirror (leave empty to be prompted once each session):"), this));
+		QHBoxLayout* hbox = new QHBoxLayout();
+		main_vbox->addLayout(hbox);
+		cran_mirror_input = new QLineEdit(RKSettingsModuleRPackages::cran_mirror_url, this);
+		if (RKSettingsModuleRPackages::cran_mirror_url == "@CRAN@") cran_mirror_input->clear();
+		connect(cran_mirror_input, &QLineEdit::textChanged, this, &RKSettingsPageRPackages::change);
+		hbox->addWidget(cran_mirror_input);
+		QPushButton* cran_mirror_button = new QPushButton(i18n("Select mirror"), this);
+		connect(cran_mirror_button, &QPushButton::clicked, this, [this]() {
+			QString title = i18n("Select CRAN mirror");
+			RCommand* command = new RCommand("rk.select.CRAN.mirror()\n", RCommand::App | RCommand::GetStringVector, title);
+			connect(command->notifier(), &RCommandNotifier::commandFinished, this, [this](RCommand *command) {
+				if (command->succeeded()) {
+					RK_ASSERT(command->getDataLength() >= 1);
+					cran_mirror_input->setText(command->stringVector().value(0));
+				}
+			});
+
+			RKProgressControl* control = new RKProgressControl(this, title, title, RKProgressControl::CancellableProgress);
+			control->addRCommand(command, true);
+			RInterface::issueCommand(command, parentModule()->commandChain());
+			control->doModal(true);
+		});
+		hbox->addWidget(cran_mirror_button);
+
+		repository_selector = new MultiStringSelector(i18n("Additional package repositories (where libraries are downloaded from)"), this);
+		repository_selector->setValues(RKSettingsModuleRPackages::package_repositories);
+		connect(repository_selector, &MultiStringSelector::listChanged, this, &RKSettingsPageRPackages::change);
+		connect(repository_selector, &MultiStringSelector::getNewStrings, this, [this](QStringList *string_list) {
+			bool ok;
+			QString new_string = QInputDialog::getText(this, i18n("Add repository"), i18n("Add URL of new repository"), QLineEdit::Normal, QString(), &ok);
+			if (ok) (*string_list).append(new_string);
+		});
+		main_vbox->addWidget(repository_selector);
+
+		main_vbox->addWidget(RKSettingsModuleRPackages::archive_packages.makeCheckbox(i18n("Archive downloaded packages"), this));
+
+		auto source_packages_box = RKSettingsModuleRPackages::source_packages.makeCheckbox(i18n("Build packages from source"), this);
+#if !(defined Q_OS_WIN || defined Q_OS_MACOS)
+		source_packages_box->setText(i18n("Build packages from source (not configurable on this platform)"));
+		source_packages_box->setChecked(true);
+		source_packages_box->setEnabled(false);
+#endif
+		RKCommonFunctions::setTips(QString("<p>%1</p>").arg(i18n("Installing packages from pre-compiled binaries (if available) is generally faster, and does not require an installation of development tools and libraries. On the other hand, building packages from source provides best compatibility.")), source_packages_box);
+		main_vbox->addWidget(source_packages_box);
+
+		hbox = new QHBoxLayout();
+		main_vbox->addLayout(hbox);
+		auto button = new QPushButton(i18n("Install from git"));
+		auto label = RKCommonFunctions::wordWrappedLabel(i18n("Some add-on packages are not available in the CRAN repository, but can be installed from development repositories. Use the button \"%1\", to install such packages, comfortably.", button->text()));
+		hbox->addWidget(label);
+		hbox->setStretchFactor(label, 2);
+		connect(button, &QPushButton::clicked, this, []() { RKComponentMap::getMap()->invokeComponent("rkward::install_from_git", QStringList()); });
+		hbox->addWidget(button);
+
+		main_vbox->addStretch();
+
+		libloc_selector = new MultiStringSelector(i18n("R Library locations (where libraries get installed to, locally)"), this);
+		libloc_selector->setValues(RKSettingsModuleRPackages::liblocs);
+		connect(libloc_selector, &MultiStringSelector::listChanged, this, &RKSettingsPageRPackages::change);
+		connect(libloc_selector, &MultiStringSelector::getNewStrings, this, [this](QStringList *string_list) {
+			QDialog dialog(this);
+			dialog.setWindowTitle(i18n("Add R Library Directory"));
+			QVBoxLayout *layout = new QVBoxLayout(&dialog);
+			layout->addWidget(RKCommonFunctions::wordWrappedLabel(
+				i18n("Specify or select library location to add.\nNote that locations may contain a '%v', which will expand to the first "
+				     "two components of the R version number (e.g. to 3.5), automatically. Including this is recommended, because R packages "
+				     "compiled for one version of R will often fail to work correctly in a different version of R.")));
+			KUrlRequester *req = new KUrlRequester();
+			req->setText(QDir(RKSettingsModuleGeneral::filesPath()).absoluteFilePath("library/%v"));
+			req->setMode(KFile::Directory);
+			layout->addWidget(req);
+
+			QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+			buttons->button(QDialogButtonBox::Ok)->setText(i18nc("Add file to list", "Add"));
+			connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
+			connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
+			layout->addWidget(buttons);
+
+			if (dialog.exec() == QDialog::Accepted) {
+				if (!req->text().isEmpty()) (*string_list).append(req->text());
+			}
+		});
+		main_vbox->addWidget(libloc_selector);
+		label = new QLabel(i18n("Note: The startup defaults will always be used in addition to the locations you specify in this list"), this);
+		main_vbox->addWidget(label);
+
+		main_vbox->addStretch();
+	}
+	void applyChanges() {
+		RK_TRACE(SETTINGS);
 
-	main_vbox->addWidget (new QLabel (i18n ("CRAN download mirror (leave empty to be prompted once each session):"), this));
-	QHBoxLayout* hbox = new QHBoxLayout ();
-	main_vbox->addLayout (hbox);
-	cran_mirror_input = new QLineEdit (cran_mirror_url, this);
-	if (cran_mirror_url == "@CRAN@") cran_mirror_input->clear ();
-	connect (cran_mirror_input, &QLineEdit::textChanged, this, &RKSettingsModuleRPackages::settingChanged);
-	hbox->addWidget (cran_mirror_input);
-	QPushButton* cran_mirror_button = new QPushButton (i18n ("Select mirror"), this);
-	connect (cran_mirror_button, &QPushButton::clicked, this, &RKSettingsModuleRPackages::selectCRANMirror);
-	hbox->addWidget (cran_mirror_button);
+		RKSettingsModuleRPackages::cran_mirror_url = cran_mirror_input->text();
+		if (RKSettingsModuleRPackages::cran_mirror_url.get().isEmpty ()) RKSettingsModuleRPackages::cran_mirror_url = "@CRAN@";
 
-	repository_selector = new MultiStringSelector (i18n ("Additional package repositories (where libraries are downloaded from)"), this);
-	repository_selector->setValues (package_repositories);
-	connect (repository_selector, &MultiStringSelector::listChanged, this, &RKSettingsModuleRPackages::settingChanged);
-	connect (repository_selector, &MultiStringSelector::getNewStrings, this, &RKSettingsModuleRPackages::addRepository);
-	main_vbox->addWidget (repository_selector);
+		RKSettingsModuleRPackages::package_repositories = repository_selector->getValues();
+		RKSettingsModuleRPackages::liblocs = libloc_selector->getValues ();
 
-	main_vbox->addWidget(archive_packages.makeCheckbox(i18n("Archive downloaded packages"), this));
+		// apply options in R
+		QStringList commands = RKSettingsModuleRPackages::makeRRunTimeOptionCommands();
+		for (QStringList::const_iterator it = commands.cbegin(); it != commands.cend(); ++it) {
+			RInterface::issueCommand(new RCommand(*it, RCommand::App), parentModule()->commandChain());
+		}
+	}
+private:
+	MultiStringSelector *libloc_selector;
+	MultiStringSelector *repository_selector;
+	QLineEdit* cran_mirror_input;
+};
 
-	auto source_packages_box = source_packages.makeCheckbox(i18n ("Build packages from source"), this);
-#if !(defined Q_OS_WIN || defined Q_OS_MACOS)
-	source_packages_box->setText(i18n("Build packages from source (not configurable on this platform)"));
-	source_packages_box->setChecked (true);
-	source_packages_box->setEnabled (false);
-#endif
-	RKCommonFunctions::setTips (QString ("<p>%1</p>").arg (i18n ("Installing packages from pre-compiled binaries (if available) is generally faster, and does not require an installation of development tools and libraries. On the other hand, building packages from source provides best compatibility.")), source_packages_box);
-	main_vbox->addWidget (source_packages_box);
-
-	hbox = new QHBoxLayout();
-	main_vbox->addLayout(hbox);
-	auto button = new QPushButton(i18n("Install from git"));
-	auto label = RKCommonFunctions::wordWrappedLabel(i18n("Some add-on packages are not available in the CRAN repository, but can be installed from development repositories. Use the button \"%1\", to install such packages, comfortably.", button->text()));
-	hbox->addWidget(label);
-	hbox->setStretchFactor(label, 2);
-	connect(button, &QPushButton::clicked, this, []() { RKComponentMap::getMap()->invokeComponent("rkward::install_from_git", QStringList()); });
-	hbox->addWidget(button);
-
-	main_vbox->addStretch ();
-
-	libloc_selector = new MultiStringSelector (i18n ("R Library locations (where libraries get installed to, locally)"), this);
-	libloc_selector->setValues (liblocs);
-	connect (libloc_selector, &MultiStringSelector::listChanged, this, &RKSettingsModuleRPackages::settingChanged);
-	connect (libloc_selector, &MultiStringSelector::getNewStrings, this, &RKSettingsModuleRPackages::addLibLoc);
-	main_vbox->addWidget (libloc_selector);
-	label = new QLabel (i18n ("Note: The startup defaults will always be used in addition to the locations you specify in this list"), this);
-	main_vbox->addWidget (label);
-
-	main_vbox->addStretch ();
+RKSettingsModuleRPackages::RKSettingsModuleRPackages(QObject *parent) : RKSettingsModule(parent) {
+	RK_TRACE(SETTINGS);
 }
 
 RKSettingsModuleRPackages::~RKSettingsModuleRPackages () {
 	RK_TRACE (SETTINGS);
 }
 
+QList<RKSettingsModuleWidget*> RKSettingsModuleRPackages::createPages(QWidget *parent) {
+	return QList<RKSettingsModuleWidget*>{ new RKSettingsPageRPackages(parent, this) };
+}
+
 void RKSettingsModuleRPackages::addLibraryLocation (const QString& new_loc, RCommandChain *chain) {
 	RK_TRACE (SETTINGS);
 
@@ -405,72 +479,6 @@ QStringList RKSettingsModuleRPackages::addUserLibLocTo (const QStringList& liblo
 	return liblocs;
 }
 
-void RKSettingsModuleRPackages::settingChanged () {
-	RK_TRACE (SETTINGS);
-	change ();
-}
-
-void RKSettingsModuleRPackages::addLibLoc (QStringList *string_list) {
-	RK_TRACE (SETTINGS);
-
-	QDialog dialog (this);
-	dialog.setWindowTitle (i18n ("Add R Library Directory"));
-	QVBoxLayout *layout = new QVBoxLayout (&dialog);
-	layout->addWidget (RKCommonFunctions::wordWrappedLabel (i18n ("Specify or select library location to add.\nNote that locations may contain a '%v', which will expand to the first "
-	                                  "two components of the R version number (e.g. to 3.5), automatically. Including this is recommended, because R packages "
-	                                  "compiled for one version of R will often fail to work correctly in a different version of R.")));
-
-	KUrlRequester *req = new KUrlRequester ();
-	req->setText (QDir (RKSettingsModuleGeneral::filesPath ()).absoluteFilePath ("library/%v"));
-	req->setMode (KFile::Directory);
-	layout->addWidget (req);
-
-	QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
-	buttons->button(QDialogButtonBox::Ok)->setText (i18nc ("Add file to list", "Add"));
-	connect (buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
-	connect (buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
-	layout->addWidget (buttons);
-
-	if (dialog.exec () == QDialog::Accepted) {
-		if (!req->text ().isEmpty ()) (*string_list).append (req->text ());
-	}
-}
-
-void RKSettingsModuleRPackages::addRepository (QStringList *string_list) {
-	RK_TRACE (SETTINGS);
-	bool ok;
-	QString new_string = QInputDialog::getText (this, i18n ("Add repository"), i18n ("Add URL of new repository"), QLineEdit::Normal, QString (), &ok);
-	if (ok) (*string_list).append (new_string);
-}
-
-QString RKSettingsModuleRPackages::caption() const {
-	RK_TRACE(SETTINGS);
-	return(i18n("R-Packages"));
-}
-
-QIcon RKSettingsModuleRPackages::icon() const {
-	RK_TRACE(SETTINGS);
-	return RKStandardIcons::getIcon(RKStandardIcons::ObjectPackageEnvironment);
-}
-
-void RKSettingsModuleRPackages::selectCRANMirror () {
-	RK_TRACE (SETTINGS);
-	QString title = i18n ("Select CRAN mirror");
-	
-	RCommand* command = new RCommand ("rk.select.CRAN.mirror()\n", RCommand::App | RCommand::GetStringVector, title);
-	connect(command->notifier(), &RCommandNotifier::commandFinished, this, [this](RCommand *command) {
-		if (command->succeeded()) {
-			RK_ASSERT(command->getDataLength() >= 1);
-			cran_mirror_input->setText(command->stringVector().value(0));
-		}
-	});
-
-	RKProgressControl* control = new RKProgressControl (this, title, title, RKProgressControl::CancellableProgress);
-	control->addRCommand (command, true);
-	RInterface::issueCommand (command, commandChain ());
-	control->doModal (true);
-}
-
 QString RKSettingsModuleRPackages::libLocsCommand () {
 	RK_TRACE (SETTINGS);
 
@@ -537,22 +545,6 @@ QStringList RKSettingsModuleRPackages::makeRRunTimeOptionCommands () {
 	return list;
 }
 
-void RKSettingsModuleRPackages::applyChanges () {
-	RK_TRACE (SETTINGS);
-
-	cran_mirror_url = cran_mirror_input->text ();
-	if (cran_mirror_url.get().isEmpty ()) cran_mirror_url = "@CRAN@";
-
-	package_repositories = repository_selector->getValues ();
-	liblocs = libloc_selector->getValues ();
-
-// apply options in R
-	QStringList commands = makeRRunTimeOptionCommands ();
-	for (QStringList::const_iterator it = commands.cbegin (); it != commands.cend (); ++it) {
-		RInterface::issueCommand(new RCommand(*it, RCommand::App), commandChain());
-	}
-}
-
 void RKSettingsModuleRPackages::syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a) {
 	RK_TRACE(SETTINGS);
 
diff --git a/rkward/settings/rksettingsmoduler.h b/rkward/settings/rksettingsmoduler.h
index 365d0ff59..94d43998a 100644
--- a/rkward/settings/rksettingsmoduler.h
+++ b/rkward/settings/rksettingsmoduler.h
@@ -26,16 +26,12 @@ Configure the R-backend
 class RKSettingsModuleR : public RKSettingsModule {
 	Q_OBJECT
 public:
-	RKSettingsModuleR (RKSettings *gui, QWidget *parent);
-	~RKSettingsModuleR ();
+	RKSettingsModuleR(QObject *parent);
+	~RKSettingsModuleR();
 
-	void applyChanges () override;
-	void save(KConfig *config) override { syncConfig(config, RKConfigBase::SaveConfig); };
-	static void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a);
-	static void validateSettingsInteractive (QList<RKSetupWizardItem*>*) {};
-
-	QString caption() const override;
-	QIcon icon() const override;
+	void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) override;
+	QList<RKSettingsModuleWidget*> createPages(QWidget *parent) override;
+	static constexpr PageId page_id = QLatin1String("rbackend");
 
 /** generate the commands needed to set the R run time options */
 	static QStringList makeRRunTimeOptionCommands ();
@@ -45,17 +41,8 @@ public:
 
 	static int getDefaultWidth () { return options_width; };
 	static QString userConfiguredRBinary() { return options_r_binary; };
-public Q_SLOTS:
-	void settingChanged ();
-private Q_SLOTS:
-	void addPaths (QStringList *string_list);
 private:
-	QLineEdit *outdec_input;
-	QComboBox *editor_input;
-	QComboBox *pager_input;
-	QTextEdit *further_input;
-	MultiStringSelector *addpaths_selector;
-
+friend class RKSettingsPageR;
 	static RKConfigValue<QString> options_outdec;
 	static RKConfigValue<int> options_width;
 	static RKConfigValue<int> options_warn;
@@ -89,13 +76,13 @@ Configure packages and library paths
 class RKSettingsModuleRPackages : public RKSettingsModule {
 	Q_OBJECT
 public:
-	RKSettingsModuleRPackages (RKSettings *gui, QWidget *parent);
-	~RKSettingsModuleRPackages ();
+	RKSettingsModuleRPackages(QObject *parent);
+	~RKSettingsModuleRPackages();
 
-	void applyChanges () override;
-	void save(KConfig *config) override { syncConfig(config, RKConfigBase::SaveConfig); };
-	static void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a);
-	static void validateSettingsInteractive (QList<RKSetupWizardItem*>*);
+	void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) override;
+	QList<RKSettingsModuleWidget*> createPages(QWidget *parent) override;
+	void validateSettingsInteractive (QList<RKSetupWizardItem*>*) override;
+	static constexpr PageId page_id = QLatin1String("rpackages");
 
 /** generate the commands needed to set the R run time options */
 	static QStringList makeRRunTimeOptionCommands ();
@@ -110,22 +97,11 @@ public:
 
 /** returns the list of packages which are essential to rkward. This is hard-coded. */
 	static QStringList essentialPackages () { return essential_packages.split ("\n"); };
-
-	QString caption() const override;
-	QIcon icon() const override;
-public Q_SLOTS:
-	void settingChanged ();
-	void addLibLoc (QStringList *string_list);
-	void addRepository (QStringList *string_list);
-	void selectCRANMirror ();
 private:
 friend class RKLoadLibsDialog;
+friend class RKSettingsPageRPackages;
 	static QString libLocsCommand ();
 
-	MultiStringSelector *libloc_selector;
-	MultiStringSelector *repository_selector;
-	QLineEdit* cran_mirror_input;
-
 	static RKConfigValue<QString> cran_mirror_url;
 	static RKConfigValue<QStringList> liblocs;
 	static RKConfigValue<bool> archive_packages;
diff --git a/rkward/settings/rksettingsmodulewatch.cpp b/rkward/settings/rksettingsmodulewatch.cpp
index d4352202d..05e7007b0 100644
--- a/rkward/settings/rksettingsmodulewatch.cpp
+++ b/rkward/settings/rksettingsmodulewatch.cpp
@@ -108,112 +108,138 @@ bool RKSettingsModuleWatch::shouldRaiseWindow (RCommand *command) {
 	return true;
 }
 
-RKSettingsModuleWatch::RKSettingsModuleWatch (RKSettings *gui, QWidget *parent) : RKSettingsModule (gui, parent) {
-	RK_TRACE (SETTINGS);
+class RKSettingsPageWatch : public RKSettingsModuleWidget {
+public:
+	RKSettingsPageWatch(QWidget* parent, RKSettingsModule* parent_module) : RKSettingsModuleWidget(parent, parent_module, RKSettingsModuleWatch::page_id) {
+		RK_TRACE(SETTINGS);
+
+		setWindowTitle(i18n("Command log"));
+		setWindowIcon(RKStandardIcons::getIcon(RKStandardIcons::WindowCommandLog));
+
+		QVBoxLayout *vbox = new QVBoxLayout(this);
+
+		QLabel *label = RKCommonFunctions::wordWrappedLabel(i18n("For now, settings only apply to new commands. All previous commands remain visible/invisible."));
+		vbox->addWidget(label);
+		vbox->addSpacing(10);
+		
+		QGridLayout *grid = new QGridLayout();
+		vbox->addLayout(grid);
+
+		label = RKCommonFunctions::wordWrappedLabel(i18n("always show command"));
+		grid->addWidget(label, 0, 1);
+		label = RKCommonFunctions::wordWrappedLabel(i18n("always show result"));
+		grid->addWidget(label, 0, 2);
+		label = RKCommonFunctions::wordWrappedLabel(i18n("show errors"));
+		grid->addWidget(label, 0, 3);
+		label = RKCommonFunctions::wordWrappedLabel(i18n("show/raise window"));
+		grid->addWidget(label, 0, 4);
+		
+		user_filter_boxes = addFilterSettings(grid, 1, i18n("User commands"), RKSettingsModuleWatch::user_filter);
+		plugin_filter_boxes = addFilterSettings(grid, 2, i18n("Plugin generated commands"), RKSettingsModuleWatch::plugin_filter);
+		app_filter_boxes = addFilterSettings(grid, 3, i18n("Application commands"), RKSettingsModuleWatch::app_filter);
+		sync_filter_boxes = addFilterSettings(grid, 4, i18n("Synchronization commands"), RKSettingsModuleWatch::sync_filter);
+
+		vbox->addSpacing(2*RKStyle::spacingHint());
+
+		vbox->addWidget(new QLabel(i18n("Maximum number of paragraphs/lines to display in the Command Log (0 for no limit)")));
+		vbox->addWidget(RKSettingsModuleWatch::max_log_lines.makeSpinBox(0, INT_MAX, this));
+
+		vbox->addStretch();
+
+		validateGUI();
+	}
+	void changedSetting(int) {
+		RK_TRACE(SETTINGS);
+		validateGUI();
+		change();
+	}
+	void validateGUI() {
+		RK_TRACE(SETTINGS);
 
-	QVBoxLayout *vbox = new QVBoxLayout (this);
+		user_filter_boxes.output->setEnabled(user_filter_boxes.input->isChecked());
+		plugin_filter_boxes.output->setEnabled(plugin_filter_boxes.input->isChecked());
+		app_filter_boxes.output->setEnabled(app_filter_boxes.input->isChecked());
+		sync_filter_boxes.output->setEnabled(sync_filter_boxes.input->isChecked());
+	}
+	void applyChanges() {
+		RK_TRACE(SETTINGS);
 
-	QLabel *label = RKCommonFunctions::wordWrappedLabel (i18n ("For now, settings only apply to new commands. All previous commands remain visible/invisible."));
-	vbox->addWidget (label);
-	vbox->addSpacing (10);
-	
-	QGridLayout *grid = new QGridLayout ();
-	vbox->addLayout (grid);
-
-	label = RKCommonFunctions::wordWrappedLabel (i18n ("always show command"));
-	grid->addWidget (label, 0, 1);
-	label = RKCommonFunctions::wordWrappedLabel (i18n ("always show result"));
-	grid->addWidget (label, 0, 2);
-	label = RKCommonFunctions::wordWrappedLabel (i18n ("show errors"));
-	grid->addWidget (label, 0, 3);
-	label = RKCommonFunctions::wordWrappedLabel (i18n ("show/raise window"));
-	grid->addWidget (label, 0, 4);
+		RKSettingsModuleWatch::user_filter = getFilterSettings(&user_filter_boxes);
+		RKSettingsModuleWatch::plugin_filter = getFilterSettings(&plugin_filter_boxes);
+		RKSettingsModuleWatch::app_filter = getFilterSettings(&app_filter_boxes);
+		RKSettingsModuleWatch::sync_filter = getFilterSettings(&sync_filter_boxes);
+	}
+private:
+	struct FilterBoxes {
+		QCheckBox *input;
+		QCheckBox *output;
+		QCheckBox *error;
+		QCheckBox *raise;
+	};
 	
-	user_filter_boxes = addFilterSettings (this, grid, 1, i18n ("User commands"), user_filter);
-	plugin_filter_boxes = addFilterSettings (this, grid, 2, i18n ("Plugin generated commands"), plugin_filter);
-	app_filter_boxes = addFilterSettings (this, grid, 3, i18n ("Application commands"), app_filter);
-	sync_filter_boxes = addFilterSettings (this, grid, 4, i18n ("Synchronization commands"), sync_filter);
+	FilterBoxes plugin_filter_boxes;
+	FilterBoxes app_filter_boxes;
+	FilterBoxes sync_filter_boxes;
+	FilterBoxes user_filter_boxes;
+
+	int getFilterSettings (FilterBoxes *boxes) {
+		RK_TRACE(SETTINGS);
+
+		int ret=0;
+		if (boxes->input->isChecked()) ret |= RKSettingsModuleWatch::ShowInput;
+		if (boxes->output->isChecked()) ret |= RKSettingsModuleWatch::ShowOutput;
+		if (boxes->error->isChecked()) ret |= RKSettingsModuleWatch::ShowError;
+		if (boxes->raise->isChecked()) ret |= RKSettingsModuleWatch::RaiseWindow;
+		return ret;
+	}
+	FilterBoxes addFilterSettings(QGridLayout *layout, int row, const QString &label, int state) {
+		RK_TRACE(SETTINGS);
 
-	vbox->addSpacing (2*RKStyle::spacingHint ());
+		FilterBoxes filter_boxes;
 
-	vbox->addWidget(new QLabel(i18n("Maximum number of paragraphs/lines to display in the Command Log (0 for no limit)")));
-	vbox->addWidget(max_log_lines.makeSpinBox(0, INT_MAX, this));
+		layout->addWidget(new QLabel(label), row, 0);
 
-	vbox->addStretch ();
+		filter_boxes.input = new QCheckBox();
+		filter_boxes.input->setChecked(state & RKSettingsModuleWatch::ShowInput);
+		connect(filter_boxes.input, &QCheckBox::stateChanged, this, &RKSettingsPageWatch::changedSetting);
+		layout->addWidget(filter_boxes.input, row, 1);
 
-	validateGUI ();
-}
+		filter_boxes.output = new QCheckBox();
+		filter_boxes.output->setChecked(state & RKSettingsModuleWatch::ShowOutput);
+		connect(filter_boxes.output, &QCheckBox::stateChanged, this, &RKSettingsPageWatch::changedSetting);
+		layout->addWidget(filter_boxes.output, row, 2);
 
-RKSettingsModuleWatch::~RKSettingsModuleWatch () {
-	RK_TRACE (SETTINGS);
+		filter_boxes.error = new QCheckBox();
+		filter_boxes.error->setChecked(state & RKSettingsModuleWatch::ShowError);
+		connect(filter_boxes.error, &QCheckBox::stateChanged, this, &RKSettingsPageWatch::changedSetting);
+		layout->addWidget(filter_boxes.error, row, 3);
 
-	delete user_filter_boxes;
-	delete plugin_filter_boxes;
-	delete app_filter_boxes;
-	delete sync_filter_boxes;
-}
+		filter_boxes.raise = new QCheckBox();
+		filter_boxes.raise->setChecked(state & RKSettingsModuleWatch::RaiseWindow);
+		connect(filter_boxes.raise, &QCheckBox::stateChanged, this, &RKSettingsPageWatch::changedSetting);
+		layout->addWidget (filter_boxes.raise, row, 4);
 
-int RKSettingsModuleWatch::getFilterSettings (FilterBoxes *boxes) {
-	RK_TRACE (SETTINGS);
-
-	int ret=0;
-	if (boxes->input->isChecked ()) ret |= ShowInput;
-	if (boxes->output->isChecked ()) ret |= ShowOutput;
-	if (boxes->error->isChecked ()) ret |= ShowError;
-	if (boxes->raise->isChecked ()) ret |= RaiseWindow;
-	return ret;
-}
-
-RKSettingsModuleWatch::FilterBoxes *RKSettingsModuleWatch::addFilterSettings (QWidget *parent, QGridLayout *layout, int row, const QString &label, int state) {
-	RK_TRACE (SETTINGS);
+		return filter_boxes;
+	}
+};
 
-	FilterBoxes *filter_boxes = new FilterBoxes;
-	
-	layout->addWidget (new QLabel (label, parent), row, 0);
-	
-	filter_boxes->input = new QCheckBox (parent);
-	filter_boxes->input->setChecked (state & ShowInput);
-	connect (filter_boxes->input, &QCheckBox::stateChanged, this, &RKSettingsModuleWatch::changedSetting);
-	layout->addWidget (filter_boxes->input, row, 1);
-	
-	filter_boxes->output = new QCheckBox (parent);
-	filter_boxes->output->setChecked (state & ShowOutput);
-	connect (filter_boxes->output, &QCheckBox::stateChanged, this, &RKSettingsModuleWatch::changedSetting);
-	layout->addWidget (filter_boxes->output, row, 2);
-	
-	filter_boxes->error = new QCheckBox (parent);
-	filter_boxes->error->setChecked (state & ShowError);
-	connect (filter_boxes->error, &QCheckBox::stateChanged, this, &RKSettingsModuleWatch::changedSetting);
-	layout->addWidget (filter_boxes->error, row, 3);
-	
-	filter_boxes->raise = new QCheckBox (parent);
-	filter_boxes->raise->setChecked (state & RaiseWindow);
-	connect (filter_boxes->raise, &QCheckBox::stateChanged, this, &RKSettingsModuleWatch::changedSetting);
-	layout->addWidget (filter_boxes->raise, row, 4);
-	
-	return filter_boxes;
+RKSettingsModuleWatch* RKSettingsModuleWatch::_instance = nullptr;
+RKSettingsModuleWatch::RKSettingsModuleWatch(QObject *parent) : RKSettingsModule(parent) {
+	RK_TRACE(SETTINGS);
+	_instance = this;
 }
 
-void RKSettingsModuleWatch::changedSetting (int) {
-	RK_TRACE (SETTINGS);
-
-	validateGUI ();
-
-	change ();
+RKSettingsModuleWatch::~RKSettingsModuleWatch() {
+	RK_TRACE(SETTINGS);
 }
 
-void RKSettingsModuleWatch::validateGUI () {
-	RK_TRACE (SETTINGS);
-
-	user_filter_boxes->output->setEnabled (user_filter_boxes->input->isChecked ());
-	plugin_filter_boxes->output->setEnabled (plugin_filter_boxes->input->isChecked ());
-	app_filter_boxes->output->setEnabled (app_filter_boxes->input->isChecked ());
-	sync_filter_boxes->output->setEnabled (sync_filter_boxes->input->isChecked ());
+QList<RKSettingsModuleWidget*> RKSettingsModuleWatch::createPages(QWidget *parent) {
+	return QList<RKSettingsModuleWidget*>{ new RKSettingsPageWatch(parent, this) };
 }
 
 //static
 void RKSettingsModuleWatch::syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a) {
-	RK_TRACE (SETTINGS);
+	RK_TRACE(SETTINGS);
 
 	KConfigGroup cg = config->group("RInterface Watch Settings");
 	user_filter.syncConfig(cg, a);
@@ -222,23 +248,3 @@ void RKSettingsModuleWatch::syncConfig(KConfig *config, RKConfigBase::ConfigSync
 	sync_filter.syncConfig(cg, a);
 	max_log_lines.syncConfig(cg, a);
 }
-
-void RKSettingsModuleWatch::applyChanges () {
-	RK_TRACE (SETTINGS);
-
-	user_filter = getFilterSettings (user_filter_boxes);
-	plugin_filter = getFilterSettings (plugin_filter_boxes);
-	app_filter = getFilterSettings (app_filter_boxes);
-	sync_filter = getFilterSettings (sync_filter_boxes);
-}
-	
-QString RKSettingsModuleWatch::caption() const {
-	RK_TRACE(SETTINGS);
-	return(i18n("Command log"));
-}
-
-QIcon RKSettingsModuleWatch::icon() const {
-	RK_TRACE(SETTINGS);
-	return RKStandardIcons::getIcon(RKStandardIcons::WindowCommandLog);
-}
-
diff --git a/rkward/settings/rksettingsmodulewatch.h b/rkward/settings/rksettingsmodulewatch.h
index 6b68235c8..2cee90c2b 100644
--- a/rkward/settings/rksettingsmodulewatch.h
+++ b/rkward/settings/rksettingsmodulewatch.h
@@ -23,14 +23,13 @@ class RKSettingsModuleWatch : public RKSettingsModule
 {
 Q_OBJECT
 public:
-	RKSettingsModuleWatch (RKSettings *gui, QWidget *parent);
-	~RKSettingsModuleWatch ();
+	RKSettingsModuleWatch(QObject *parent);
+	~RKSettingsModuleWatch();
 
-	void save(KConfig *config) override { syncConfig(config, RKConfigBase::SaveConfig); };
-	static void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a);
-	static void validateSettingsInteractive (QList<RKSetupWizardItem*>*) {};
+	void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) override;
+	QList<RKSettingsModuleWidget*> createPages(QWidget *parent) override;
+	static constexpr PageId page_id = QLatin1String("commandlog");
 
-	void applyChanges () override;
 	void validateGUI ();
 
 	static bool shouldShowInput (RCommand *command);
@@ -40,34 +39,18 @@ public:
 
 	static uint maxLogLines () { return max_log_lines; };
 
-	QString caption() const override;
-	QIcon icon() const override;
-public Q_SLOTS:
-	void changedSetting (int);
+	static RKSettingsModuleWatch *instance() { return _instance; };
 private:
+friend class RKSettingsPageWatch;
 	enum FilterType { ShowInput=1, ShowOutput=2, ShowError=4, RaiseWindow=8 };
 
 	static RKConfigValue<int> plugin_filter;
 	static RKConfigValue<int> app_filter;
 	static RKConfigValue<int> sync_filter;
 	static RKConfigValue<int> user_filter;
-	
-	struct FilterBoxes {
-		QCheckBox *input;
-		QCheckBox *output;
-		QCheckBox *error;
-		QCheckBox *raise;
-	};
-	
-	FilterBoxes *plugin_filter_boxes;
-	FilterBoxes *app_filter_boxes;
-	FilterBoxes *sync_filter_boxes;
-	FilterBoxes *user_filter_boxes;
-
-	int getFilterSettings (FilterBoxes *boxes);
-	FilterBoxes *addFilterSettings (QWidget *parent, QGridLayout *layout, int row, const QString &label, int state);
 
 	static RKConfigValue<uint> max_log_lines;
+	static RKSettingsModuleWatch* _instance;
 };
 
 #endif
diff --git a/rkward/windows/katepluginintegration.h b/rkward/windows/katepluginintegration.h
index 4307f4952..4f4b7f35f 100644
--- a/rkward/windows/katepluginintegration.h
+++ b/rkward/windows/katepluginintegration.h
@@ -49,7 +49,7 @@ friend class KatePluginIntegrationWindow;
 	bool closeDocuments(const QList<KTextEditor::Document *> &documents);
 	KTextEditor::Plugin *plugin(const QString &name);
 private:
-friend class RKSettingsModuleKatePlugins;
+friend class RKSettingsPageKatePlugins;
 	KatePluginIntegrationWindow *window;  // For now, only one main window
 	KTextEditor::Application *app;
 /** Provides a hidden dummy view (created on the fly as needed), for plugins that assume there is always at least one view/document around. */
diff --git a/rkward/windows/rcontrolwindow.cpp b/rkward/windows/rcontrolwindow.cpp
index 88b3132fa..c41c31912 100644
--- a/rkward/windows/rcontrolwindow.cpp
+++ b/rkward/windows/rcontrolwindow.cpp
@@ -16,6 +16,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <kmessagebox.h>
 
 #include "../settings/rksettings.h"
+#include "../settings/rksettingsmoduler.h"
 #include "../misc/rkdummypart.h"
 #include "../rbackend/rkrinterface.h"
 #include "../rbackend/rcommand.h"
@@ -140,6 +141,6 @@ void RControlWindow::pauseButtonClicked () {
 void RControlWindow::configureButtonClicked () {
 	RK_TRACE (APP);
 
-	RKSettings::configureSettings (RKSettings::PageR, this);
+	RKSettings::configureSettings(RKSettingsModuleR::page_id, this);
 }
 
diff --git a/rkward/windows/rkcommandeditorwindow.cpp b/rkward/windows/rkcommandeditorwindow.cpp
index e96ce0a3f..cf9cf4c3c 100644
--- a/rkward/windows/rkcommandeditorwindow.cpp
+++ b/rkward/windows/rkcommandeditorwindow.cpp
@@ -201,7 +201,7 @@ RKCommandEditorWindow::RKCommandEditorWindow (QWidget *parent, const QUrl &_url,
 	part->insertChildClient (m_view);
 	setPart (part);
 	fixupPartGUI ();
-	setMetaInfo (i18n ("Script Editor"), QUrl (), RKSettings::PageCommandEditor);
+	setMetaInfo(i18n("Script Editor"), QUrl(), RKSettingsModuleCommandEditor::page_id);
 	initializeActions (part->actionCollection ());
 	// The kate part is quite a beast to embed, when it comes to shortcuts. New ones get added, conflicting with ours.
 	// In this context we show no mercy, and rip out any conflicting shortcuts.
diff --git a/rkward/windows/rkcommandlog.cpp b/rkward/windows/rkcommandlog.cpp
index 8877cb50a..8e654e2e1 100644
--- a/rkward/windows/rkcommandlog.cpp
+++ b/rkward/windows/rkcommandlog.cpp
@@ -53,8 +53,8 @@ RKCommandLog::RKCommandLog (QWidget *parent, bool tool_window, const char *name)
 	initializeActivationSignals ();
 	setFocusPolicy (Qt::ClickFocus);
 
-	connect (RKSettings::tracker (), &RKSettingsTracker::settingsChanged, this, &RKCommandLog::settingsChanged);
-	settingsChanged (RKSettings::PageWatch);
+	connect(RKSettingsModuleWatch::instance(), &RKSettingsModuleWatch::settingsChanged, this, &RKCommandLog::settingsChanged);
+	settingsChanged();
 }
 
 RKCommandLog::~RKCommandLog(){
@@ -210,12 +210,10 @@ void RKCommandLog::rCommandDone (RCommand *command) {
 	command_input_shown.removeAll (command);
 }
 
-void RKCommandLog::settingsChanged (RKSettings::SettingsPage page) {
-	if (page != RKSettings::PageWatch) return;
-
+void RKCommandLog::settingsChanged() {
 	RK_TRACE (APP);
 
-	log_view->document ()->setMaximumBlockCount (RKSettingsModuleWatch::maxLogLines ());
+	log_view->document()->setMaximumBlockCount(RKSettingsModuleWatch::maxLogLines());
 }
 
 void RKCommandLog::linesAdded () {
@@ -228,7 +226,7 @@ void RKCommandLog::linesAdded () {
 
 void RKCommandLog::configureLog () {
 	RK_TRACE (APP);
-	RKSettings::configureSettings (RKSettings::PageWatch, this);
+	RKSettings::configureSettings(RKSettingsModuleWatch::page_id, this);
 }
 
 void RKCommandLog::clearLog () {
diff --git a/rkward/windows/rkcommandlog.h b/rkward/windows/rkcommandlog.h
index 652a9efaf..517dfe4f3 100644
--- a/rkward/windows/rkcommandlog.h
+++ b/rkward/windows/rkcommandlog.h
@@ -48,7 +48,7 @@ public Q_SLOTS:
 /** clears the log_view-window */
 	void clearLog ();
 	void runSelection ();
-	void settingsChanged (RKSettings::SettingsPage page);
+	void settingsChanged();
 private:
 	void addInputNoCheck (RCommand *command);
 	void addOutputNoCheck (RCommand *command, ROutput *output);
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index 41ac74ef2..5797ddfe5 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -511,7 +511,7 @@ bool RKHTMLWindow::handleRKWardURL (const QUrl &url, RKHTMLWindow *window) {
 		} else if (url.host () == "settings") {
 			QString path = url.path ();
 			if (path.startsWith ('/')) path = path.mid (1);
-			RKSettings::configureSettings(path);
+			RKSettings::configureSettings(RKSettingsModule::PageId(path.toLatin1()));
 		} else if (url.host () == "open") {
 			QString path = url.path().mid(1); // skip initial '/'
 			int sep = path.indexOf('/');
@@ -801,7 +801,7 @@ void RKHTMLWindow::useMode (WindowMode new_mode) {
 		type = RKMDIWindow::OutputWindow | RKMDIWindow::DocumentWindow;
 		setWindowIcon (RKStandardIcons::getIcon (RKStandardIcons::WindowOutput));
 		part->setOutputWindowSkin ();
-		setMetaInfo (i18n ("Output Window"), QUrl ("rkward://page/rkward_output"), RKSettings::PageOutput);
+		setMetaInfo(i18n("Output Window"), QUrl("rkward://page/rkward_output"), RKSettingsModuleOutput::page_id);
 		connect (page, &RKWebPage::loadFinished, this, &RKHTMLWindow::scrollToBottom);
 		page->action (RKWebPage::Reload)->setText (i18n ("&Refresh Output"));
 
diff --git a/rkward/windows/rkmdiwindow.cpp b/rkward/windows/rkmdiwindow.cpp
index 754c526a5..fe0a84a4f 100644
--- a/rkward/windows/rkmdiwindow.cpp
+++ b/rkward/windows/rkmdiwindow.cpp
@@ -415,7 +415,7 @@ void RKMDIWindow::setWindowStyleHint (const QString& hint) {
 	}
 }
 
-void RKMDIWindow::setMetaInfo (const QString& _generic_window_name, const QUrl& _help_url, RKSettings::SettingsPage _settings_page) {
+void RKMDIWindow::setMetaInfo(const QString& _generic_window_name, const QUrl& _help_url, RKSettingsModule::PageId _settings_page) {
 	RK_TRACE (APP);
 
 	// only meant to be called once
@@ -428,7 +428,7 @@ void RKMDIWindow::setMetaInfo (const QString& _generic_window_name, const QUrl&
 		QAction *action = standardActionCollection()->addAction("window_help", this, &RKMDIWindow::showWindowHelp);
 		action->setText (i18n ("Help on %1", generic_window_name));
 	}
-	if (settings_page != RKSettings::NoPage) {
+	if (settings_page != RKSettingsModule::no_page_id) {
 		QAction *action = standardActionCollection()->addAction("window_configure", this, &RKMDIWindow::showWindowSettings);
 		action->setText (i18n ("Configure %1", generic_window_name));
 	}
@@ -444,8 +444,8 @@ void RKMDIWindow::showWindowHelp () {
 void RKMDIWindow::showWindowSettings () {
 	RK_TRACE (APP);
 
-	RK_ASSERT (settings_page != RKSettings::NoPage);
-	RKSettings::configureSettings (settings_page, this);
+	RK_ASSERT(settings_page != RKSettingsModule::no_page_id);
+	RKSettings::configureSettings(settings_page, this);
 }
 
 void RKMDIWindow::addUiBuddy(KXMLGUIClient* buddy) {
diff --git a/rkward/windows/rkmdiwindow.h b/rkward/windows/rkmdiwindow.h
index 9c730232f..2f28516e6 100644
--- a/rkward/windows/rkmdiwindow.h
+++ b/rkward/windows/rkmdiwindow.h
@@ -143,7 +143,7 @@ protected Q_SLOTS:
 	void clearStatusMessage ();
 protected:
 	void setPart (KParts::Part *p) { part = p; };
-	void setMetaInfo (const QString& generic_window_name, const QUrl& help_url, RKSettings::SettingsPage settings_page=RKSettings::NoPage);
+	void setMetaInfo (const QString& generic_window_name, const QUrl& help_url, RKSettingsModule::PageId settings_page=RKSettingsModule::no_page_id);
 	void initializeActivationSignals ();
 	void paintEvent (QPaintEvent *e) override;
 	void changeEvent (QEvent *event) override;
@@ -179,7 +179,7 @@ friend class RKToolWindowBar;
 	QMap<QString, QString> global_context_properties;
 	QString generic_window_name;
 	QUrl help_url;
-	RKSettings::SettingsPage settings_page;
+	RKSettingsModule::PageId settings_page;
 	KXMLGUIClient* ui_buddy;
 	void showStatusMessageNow();
 	QTimer status_message_timer;
diff --git a/rkward/windows/rkwindowcatcher.cpp b/rkward/windows/rkwindowcatcher.cpp
index 93249d88e..29ab8407e 100644
--- a/rkward/windows/rkwindowcatcher.cpp
+++ b/rkward/windows/rkwindowcatcher.cpp
@@ -309,7 +309,7 @@ void RKCaughtX11Window::commonInit (int device_number) {
 
 	error_dialog = new RKProgressControl(nullptr, i18n("An error occurred"), i18n("An error occurred"), RKProgressControl::DetailedError);
 	setPart (new RKCaughtX11WindowPart (this));
-	setMetaInfo (i18n ("Graphics Device Window"), QUrl ("rkward://page/rkward_plot_history"), RKSettings::PageX11);
+	setMetaInfo(i18n("Graphics Device Window"), QUrl("rkward://page/rkward_plot_history"), RKSettingsModuleGraphics::page_id);
 	initializeActivationSignals ();
 	setFocusPolicy (Qt::ClickFocus);
 	updateHistoryActions (0, 0, QStringList ());
diff --git a/rkward/windows/robjectbrowser.cpp b/rkward/windows/robjectbrowser.cpp
index ea765a45f..55c7ea556 100644
--- a/rkward/windows/robjectbrowser.cpp
+++ b/rkward/windows/robjectbrowser.cpp
@@ -29,6 +29,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include "../misc/rkstandardicons.h"
 #include "../misc/rkstandardactions.h"
 #include "../misc/rkspecialactions.h"
+#include "../settings/rksettingsmoduleobjectbrowser.h"
 #include "rkworkplace.h"
 #include "../dataeditor/rkeditor.h"
 
@@ -51,7 +52,7 @@ RObjectBrowser::RObjectBrowser (QWidget *parent, bool tool_window, const char *n
 
 	RKDummyPart *part = new RKDummyPart (this, layout_widget);
 	setPart (part);
-	setMetaInfo (i18n ("R workspace browser"), QUrl ("rkward://page/rkward_workspace_browser"), RKSettings::PageObjectBrowser);
+	setMetaInfo(i18n("R workspace browser"), QUrl("rkward://page/rkward_workspace_browser"), RKSettingsModuleObjectBrowser::page_id);
 	initializeActivationSignals ();
 
 	setCaption (i18n ("R Workspace"));



More information about the rkward-tracker mailing list