[education/rkward] rkward: Port setup dialog to RKRadioGroup, too

Thomas Friedrichsmeier null at kde.org
Sat Aug 10 11:45:13 BST 2024


Git commit 75d27860845a7d790a816d5a5c8d0dc1602de5a8 by Thomas Friedrichsmeier.
Committed on 10/08/2024 at 10:45.
Pushed by tfry into branch 'master'.

Port setup dialog to RKRadioGroup, too

M  +16   -31   rkward/dialogs/rksetupwizard.cpp
M  +40   -24   rkward/misc/rkradiogroup.cpp
M  +1    -3    rkward/misc/rkradiogroup.h

https://invent.kde.org/education/rkward/-/commit/75d27860845a7d790a816d5a5c8d0dc1602de5a8

diff --git a/rkward/dialogs/rksetupwizard.cpp b/rkward/dialogs/rksetupwizard.cpp
index 966850387..d61b367c0 100644
--- a/rkward/dialogs/rksetupwizard.cpp
+++ b/rkward/dialogs/rksetupwizard.cpp
@@ -18,9 +18,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <QTimer>
 #include <QFileInfo>
 #include <QStandardPaths>
-#include <QRadioButton>
-#include <QGroupBox>
-#include <QButtonGroup>
 
 #include <KLocalizedString>
 #include <KUrlRequester>
@@ -32,6 +29,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include "../misc/rkcommonfunctions.h"
 #include "../misc/rkcommandlineargs.h"
 #include "../misc/rkstandardicons.h"
+#include "../misc/rkradiogroup.h"
 #include "../dialogs/rkloadlibsdialog.h"
 #include "../windows/katepluginintegration.h"
 #include "../rbackend/rksessionvars.h"
@@ -175,26 +173,19 @@ private:
 	RInterface::BackendError backend_error;
 };
 
-class RBackendSelectionWidget : public QGroupBox {
+class RBackendSelectionWidget : public RKRadioGroup {
 public:
-	RBackendSelectionWidget(QWidget *parent) : QGroupBox(parent) {
-		group = new QButtonGroup(this);
-		auto l = new QVBoxLayout(this);
-		bl = new QVBoxLayout();
-		l->addLayout(bl);
-
-		auto button = new QRadioButton(i18n("Use R at:")); l->addWidget(button); group->addButton(button, -1);
-		req = new KUrlRequester(); l->addWidget(req);
+	RBackendSelectionWidget(QWidget *parent) : RKRadioGroup(parent) {
+		req = new KUrlRequester();
 		req->setPlaceholderText(i18n("Select another R executable"));
-		req->setEnabled(false);
 		req->setWindowTitle(i18n("Select R executable"));
-		connect(button, &QAbstractButton::toggled, req, &QWidget::setEnabled);
 	}
 
 	void updateOptions() {
 		// clear previous buttons, if any
-		for (int i = 0; i < r_installations.size(); ++i) {
-			delete (group->button(i));
+		const auto buttons = group()->buttons();
+		for (auto button : buttons) {
+			delete button;
 		}
 
 		r_installations = RKSessionVars::findRInstallations();
@@ -205,29 +196,23 @@ public:
 		for(int i = 0; i < r_installations.size(); ++i) {
 			addButton(i18n("Use R at %1", r_installations[i]), i);
 		}
-		auto button = group->button(0);
-		if (RInterface::instance()->backendIsDead()) button->setText(i18n("Attempt to restart R at %1", RKSessionVars::RBinary()));
-		else button->setText(i18n("Keep current version (%1)", RKSessionVars::RBinary()));
+		addButton(i18n("Use R at:"), -1, req);
+
+		auto button0 = group()->button(0);
+		if (RInterface::instance()->backendIsDead()) button0->setText(i18n("Attempt to restart R at %1", RKSessionVars::RBinary()));
+		else button0->setText(i18n("Keep current version (%1)", RKSessionVars::RBinary()));
 		if (RKSessionVars::isPathInAppImage(RKSessionVars::RBinary()) && (r_installations.size() > 1)) {
-			group->button(1)->setChecked(true);
+			setButtonChecked(1, true);
 		} else {
-			button->setChecked(true);
+			button0->setChecked(true);
 		}
 	}
-	QRadioButton *addButton(const QString &text, int index) {
-		auto button = new QRadioButton(text);
-		bl->addWidget(button);
-		group->addButton(button, index);
-		return button;
-	}
 	QString selectedOpt() {
-		int index = group->checkedId();
+		int index = group()->checkedId();
 		if (index >= 0) return r_installations[index];
 		return req->text();
 	}
-	QButtonGroup *group;
 private:
-	QVBoxLayout *bl;
 	QStringList r_installations;
 	KUrlRequester *req;
 };
@@ -409,7 +394,7 @@ RKSetupWizard::RKSetupWizard(QWidget* parent, InvokationReason reason, const QLi
 			setValid(pageref, true);
 
 			next_callbacks.insert(pageref, [select, pageref, this]() -> bool {
-				bool restart_needed = (select->group->checkedId() != 0) || RInterface::instance()->backendIsDead();
+				bool restart_needed = (select->group()->checkedId() != 0) || RInterface::instance()->backendIsDead();
 				if (restart_needed) {
 					RKSessionVars::r_binary = select->selectedOpt();
 					RKCommandLineArgs::instance->set(RKCommandLineArgs::Setup, QVariant(true));
diff --git a/rkward/misc/rkradiogroup.cpp b/rkward/misc/rkradiogroup.cpp
index 0d088b208..f2b606bbd 100644
--- a/rkward/misc/rkradiogroup.cpp
+++ b/rkward/misc/rkradiogroup.cpp
@@ -11,6 +11,43 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "../debug.h"
 
+/** Internal radio button class, which allows to be installed as an event filter on "controlled" widgets.
+ *  Using a separate class rather than filtering in RKRadioGroup has the advantage, that the filter will
+ *  automatically be removed, in case the button is deleted independent of the controlled widget. */
+class RKRadioGroupButton : public QRadioButton {
+public:
+	explicit RKRadioGroupButton(const QString &label) : QRadioButton(label) {};
+	void control(QWidget *controlled) {
+		RKRadioGroupButton::controlled = controlled;
+		controlled->installEventFilter(this);
+		const auto children = controlled->findChildren<QWidget*>();
+		for (auto child : children) child->installEventFilter(this);  // need to receive clicks on all child widgets!
+		controlled->setEnabled(isChecked());
+		// TODO (see EditFormatDialog: it may also make sense the other way around: if the associated widget is clicked, set this button as active)
+		connect(this, &QAbstractButton::toggled, controlled, &QWidget::setEnabled);
+	}
+private:
+	bool eventFilter(QObject* obj, QEvent *ev) override {
+		if (ev->isSinglePointEvent()) {
+			// When clicking in the widget controlled by a button, automatically check the button (enabling the controlled widget).
+			// In effect, the appearance is that the widget "belongs" to the button.
+			// NOTE: This does not wait for a full click to occur, only a mouse press. The reason is that some widgets will not react on the click,
+			//       if they have been disabled during the press.
+			auto e = static_cast<QSinglePointEvent *>(ev);
+			if (e->isBeginEvent()) {
+				// Perhaps a bit paranoid: Only react on controlled and its children
+				while (obj) {
+					if (obj == controlled) break;
+					obj = obj->parent();
+				}
+				if (obj && isEnabled()) setChecked(true);
+			}
+		}
+		return false;
+	}
+	QWidget *controlled;
+};
+
 RKRadioGroup::RKRadioGroup(QWidget *parent) : RKRadioGroup(QString(), parent) {
 }
 
@@ -24,7 +61,7 @@ RKRadioGroup::~RKRadioGroup() {
 
 QRadioButton* RKRadioGroup::addButton(const QString &label, int id) {
 	RK_TRACE(MISC);
-	auto button = new QRadioButton(label);
+	auto button = new RKRadioGroupButton(label);
 	_group->addButton(button, id);
 	_layout->addWidget(button);
 	return button;
@@ -32,20 +69,15 @@ QRadioButton* RKRadioGroup::addButton(const QString &label, int id) {
 
 QRadioButton* RKRadioGroup::addButton(const QString &label, int id, QWidget* controlled, QBoxLayout::Direction dir) {
 	RK_TRACE(MISC);
-	QRadioButton *button;
 	auto old_layout = _layout;
 	if (dir != _layout->direction()) {
 		_layout = new QBoxLayout(dir);
 		old_layout->addLayout(_layout);
 	}
-	button = addButton(label, id);
+	RKRadioGroupButton *button = static_cast<RKRadioGroupButton*>(addButton(label, id));
 	_layout->addWidget(controlled);
-	controlled->installEventFilter(this);
-	controlled->setProperty(property, QVariant::fromValue(button));
 	_layout = old_layout;
-	controlled->setEnabled(false);
-	// TODO (see EditFormatDialog: it may also make sense the other way around: if the associated widget is clicked, set this button as active)
-	connect(button, &QAbstractButton::toggled, controlled, &QWidget::setEnabled);
+	button->control(controlled);
 	return button;
 }
 
@@ -58,19 +90,3 @@ bool RKRadioGroup::setButtonChecked(int id, bool checked) {
 	}
 	return false;
 }
-
-bool RKRadioGroup::eventFilter(QObject *obj, QEvent *ev) {
-	if (ev->isSinglePointEvent()) {
-		// When clicking in the widget controlled by a button, automatically check the button (enabling the controlled widget).
-		// In effect, the appearance is that the widget "belongs" to the button.
-		// NOTE: This does not wait for a full click to occur, only a mouse press. The reason is that some widgets will not react on the click,
-		//       if they have been disabled during the press.
-		auto e = static_cast<QSinglePointEvent *>(ev);
-		auto w = static_cast<QWidget *>(obj);
-		if (!w->isEnabled() && e->isBeginEvent()) {
-			auto button = w->property(property).value<QRadioButton*>();
-			if (button && button->isEnabled()) button->setChecked(true);
-		}
-	}
-	return false;
-}
diff --git a/rkward/misc/rkradiogroup.h b/rkward/misc/rkradiogroup.h
index bb36d7cb1..a05f6a3e7 100644
--- a/rkward/misc/rkradiogroup.h
+++ b/rkward/misc/rkradiogroup.h
@@ -22,6 +22,7 @@ public:
 	~RKRadioGroup() override;
 
 	QRadioButton* addButton(const QString &label, int id = -1);
+	/** NOTE: the group, but not the associated button assumes ownership over controlled! */
 	QRadioButton* addButton(const QString &label, int id, QWidget* controlled, QBoxLayout::Direction dir=QBoxLayout::TopToBottom);
 	QRadioButton* addButton(const QString &label, QWidget* controlled, QBoxLayout::Direction dir=QBoxLayout::TopToBottom) {
 		return addButton(label, -1, controlled, dir);
@@ -30,12 +31,9 @@ public:
 	 *  @return false, if there is no such button, true otherwise. */
 	bool setButtonChecked(int id, bool checked);
 	QButtonGroup* group() const { return _group; };
-protected:
-	bool eventFilter(QObject *obj, QEvent *ev) override;
 private:
 	QButtonGroup *_group;
 	QBoxLayout *_layout;
-	static constexpr const char property[] = "_rkradiogroup_";
 };
 
 #endif


More information about the rkward-tracker mailing list