diff --git a/Telegram/SourceFiles/boxes/addcontactbox.cpp b/Telegram/SourceFiles/boxes/addcontactbox.cpp
index 6abd3c3b5..78d9c1274 100644
--- a/Telegram/SourceFiles/boxes/addcontactbox.cpp
+++ b/Telegram/SourceFiles/boxes/addcontactbox.cpp
@@ -197,7 +197,7 @@ void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) {
 	auto &v = d.vimported.v;
 	UserData *user = nullptr;
 	if (!v.isEmpty()) {
-		const auto &c(v.front().c_importedContact());
+		auto &c = v.front().c_importedContact();
 		if (c.vclient_id.v != _contactId) return;
 
 		user = App::userLoaded(c.vuser_id.v);
@@ -419,8 +419,9 @@ void GroupInfoBox::onPhotoReady(const QImage &img) {
 SetupChannelBox::SetupChannelBox(QWidget*, ChannelData *channel, bool existing)
 : _channel(channel)
 , _existing(existing)
-, _public(this, qsl("channel_privacy"), 0, lang(channel->isMegagroup() ? lng_create_public_group_title : lng_create_public_channel_title), true, st::defaultBoxCheckbox)
-, _private(this, qsl("channel_privacy"), 1, lang(channel->isMegagroup() ? lng_create_private_group_title : lng_create_private_channel_title), false, st::defaultBoxCheckbox)
+, _privacyGroup(std::make_shared<Ui::RadioenumGroup<Privacy>>(Privacy::Public))
+, _public(this, _privacyGroup, Privacy::Public, lang(channel->isMegagroup() ? lng_create_public_group_title : lng_create_public_channel_title), st::defaultBoxCheckbox)
+, _private(this, _privacyGroup, Privacy::Private, lang(channel->isMegagroup() ? lng_create_private_group_title : lng_create_private_channel_title), st::defaultBoxCheckbox)
 , _aboutPublicWidth(st::boxWideWidth - st::boxPadding.left() - st::boxButtonPadding.right() - st::newGroupPadding.left() - st::defaultBoxCheckbox.textPosition.x())
 , _aboutPublic(st::defaultTextStyle, lang(channel->isMegagroup() ? lng_create_public_group_about : lng_create_public_channel_about), _defaultOptions, _aboutPublicWidth)
 , _aboutPrivate(st::defaultTextStyle, lang(channel->isMegagroup() ? lng_create_private_group_about : lng_create_private_channel_about), _defaultOptions, _aboutPublicWidth)
@@ -438,13 +439,12 @@ void SetupChannelBox::prepare() {
 	addButton(lang(_existing ? lng_cancel : lng_create_group_skip), [this] { closeBox(); });
 
 	connect(_link, SIGNAL(changed()), this, SLOT(onChange()));
-	_link->setVisible(_public->checked());
+	_link->setVisible(_privacyGroup->value() == Privacy::Public);
 
 	_checkTimer.setSingleShot(true);
 	connect(&_checkTimer, SIGNAL(timeout()), this, SLOT(onCheck()));
 
-	connect(_public, SIGNAL(changed()), this, SLOT(onPrivacyChange()));
-	connect(_private, SIGNAL(changed()), this, SLOT(onPrivacyChange()));
+	_privacyGroup->setChangedCallback([this](Privacy value) { privacyChanged(value); });
 
 	updateMaxHeight();
 }
@@ -459,7 +459,7 @@ void SetupChannelBox::setInnerFocus() {
 
 void SetupChannelBox::updateMaxHeight() {
 	auto newHeight = st::boxPadding.top() + st::newGroupPadding.top() + _public->heightNoMargins() + _aboutPublicHeight + st::newGroupSkip + _private->heightNoMargins() + _aboutPrivate.countHeight(_aboutPublicWidth) + st::newGroupSkip + st::newGroupPadding.bottom();
-	if (!_channel->isMegagroup() || _public->checked()) {
+	if (!_channel->isMegagroup() || _privacyGroup->value() == Privacy::Public) {
 		newHeight += st::newGroupLinkPadding.top() + _link->height() + st::newGroupLinkPadding.bottom();
 	}
 	setDimensions(st::boxWideWidth, newHeight);
@@ -563,7 +563,7 @@ void SetupChannelBox::closeHook() {
 }
 
 void SetupChannelBox::onSave() {
-	if (!_public->checked()) {
+	if (_privacyGroup->value() == Privacy::Private) {
 		if (_existing) {
 			_sentUsername = QString();
 			_saveRequestId = MTP::send(MTPchannels_UpdateUsername(_channel->inputChannel, MTP_string(_sentUsername)), rpcDone(&SetupChannelBox::onUpdateDone), rpcFail(&SetupChannelBox::onUpdateFail));
@@ -633,13 +633,13 @@ void SetupChannelBox::onCheck() {
 	}
 }
 
-void SetupChannelBox::onPrivacyChange() {
-	if (_public->checked()) {
+void SetupChannelBox::privacyChanged(Privacy value) {
+	if (value == Privacy::Public) {
 		if (_tooMuchUsernames) {
-			_private->setChecked(true);
+			_privacyGroup->setValue(Privacy::Private);
 			Ui::show(Box<RevokePublicLinkBox>(base::lambda_guarded(this, [this] {
 				_tooMuchUsernames = false;
-				_public->setChecked(true);
+				_privacyGroup->setValue(Privacy::Public);
 				onCheck();
 			})), KeepOtherLayers);
 			return;
@@ -712,8 +712,7 @@ bool SetupChannelBox::onCheckFail(const RPCError &error) {
 			showRevokePublicLinkBoxForEdit();
 		} else {
 			_tooMuchUsernames = true;
-			_private->setChecked(true);
-			onPrivacyChange();
+			_privacyGroup->setValue(Privacy::Private);
 		}
 		return true;
 	} else if (err == qstr("USERNAME_INVALID")) {
@@ -750,8 +749,7 @@ bool SetupChannelBox::onFirstCheckFail(const RPCError &error) {
 			showRevokePublicLinkBoxForEdit();
 		} else {
 			_tooMuchUsernames = true;
-			_private->setChecked(true);
-			onPrivacyChange();
+			_privacyGroup->setValue(Privacy::Private);
 		}
 		return true;
 	}
diff --git a/Telegram/SourceFiles/boxes/addcontactbox.h b/Telegram/SourceFiles/boxes/addcontactbox.h
index d002e4084..0fa4976c8 100644
--- a/Telegram/SourceFiles/boxes/addcontactbox.h
+++ b/Telegram/SourceFiles/boxes/addcontactbox.h
@@ -31,7 +31,10 @@ class PhoneInput;
 class InputArea;
 class UsernameInput;
 class Checkbox;
-class Radiobutton;
+template <typename Enum>
+class RadioenumGroup;
+template <typename Enum>
+class Radioenum;
 class LinkButton;
 class NewAvatarButton;
 } // namespace Ui
@@ -150,9 +153,12 @@ private slots:
 	void onChange();
 	void onCheck();
 
-	void onPrivacyChange();
-
 private:
+	enum class Privacy {
+		Public,
+		Private,
+	};
+	void privacyChanged(Privacy value);
 	void updateSelected(const QPoint &cursorGlobalPosition);
 
 	void onUpdateDone(const MTPBool &result);
@@ -169,8 +175,9 @@ private:
 	ChannelData *_channel = nullptr;
 	bool _existing = false;
 
-	object_ptr<Ui::Radiobutton> _public;
-	object_ptr<Ui::Radiobutton> _private;
+	std::shared_ptr<Ui::RadioenumGroup<Privacy>> _privacyGroup;
+	object_ptr<Ui::Radioenum<Privacy>> _public;
+	object_ptr<Ui::Radioenum<Privacy>> _private;
 	int32 _aboutPublicWidth, _aboutPublicHeight;
 	Text _aboutPublic, _aboutPrivate;
 
diff --git a/Telegram/SourceFiles/boxes/autolockbox.cpp b/Telegram/SourceFiles/boxes/autolockbox.cpp
index be7b1c558..615fa537d 100644
--- a/Telegram/SourceFiles/boxes/autolockbox.cpp
+++ b/Telegram/SourceFiles/boxes/autolockbox.cpp
@@ -34,31 +34,27 @@ void AutoLockBox::prepare() {
 
 	addButton(lang(lng_box_ok), [this] { closeBox(); });
 
-	int opts[] = { 60, 300, 3600, 18000 }, cnt = sizeof(opts) / sizeof(opts[0]);
+	auto options = { 60, 300, 3600, 18000 };
+
+	auto group = std::make_shared<Ui::RadiobuttonGroup>(Global::AutoLock());
 	auto y = st::boxOptionListPadding.top();
-	_options.reserve(cnt);
-	for (auto i = 0; i != cnt; ++i) {
-		auto v = opts[i];
-		_options.push_back(new Ui::Radiobutton(this, qsl("autolock"), v, (v % 3600) ? lng_passcode_autolock_minutes(lt_count, v / 60) : lng_passcode_autolock_hours(lt_count, v / 3600), (Global::AutoLock() == v), st::langsButton));
+	auto count = int(options.size());
+	_options.reserve(count);
+	for (auto seconds : options) {
+		_options.emplace_back(this, group, seconds, (seconds % 3600) ? lng_passcode_autolock_minutes(lt_count, seconds / 60) : lng_passcode_autolock_hours(lt_count, seconds / 3600), st::langsButton);
 		_options.back()->move(st::boxPadding.left() + st::boxOptionListPadding.left(), y);
 		y += _options.back()->heightNoMargins() + st::boxOptionListSkip;
-		connect(_options.back(), SIGNAL(changed()), this, SLOT(onChange()));
 	}
+	group->setChangedCallback([this](int value) { durationChanged(value); });
 
-	setDimensions(st::langsWidth, st::boxOptionListPadding.top() + cnt * st::langsButton.height + (cnt - 1) * st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom());
+	setDimensions(st::langsWidth, st::boxOptionListPadding.top() + count * st::langsButton.height + (count - 1) * st::boxOptionListSkip + st::boxOptionListPadding.bottom() + st::boxPadding.bottom());
 }
 
-void AutoLockBox::onChange() {
-	if (!isBoxShown()) return;
+void AutoLockBox::durationChanged(int seconds) {
+	Global::SetAutoLock(seconds);
+	Local::writeUserSettings();
+	Global::RefLocalPasscodeChanged().notify();
 
-	for (int32 i = 0, l = _options.size(); i < l; ++i) {
-		int32 v = _options[i]->val();
-		if (_options[i]->checked()) {
-			Global::SetAutoLock(v);
-			Local::writeUserSettings();
-			Global::RefLocalPasscodeChanged().notify();
-		}
-	}
 	App::wnd()->checkAutoLock();
 	closeBox();
 }
diff --git a/Telegram/SourceFiles/boxes/autolockbox.h b/Telegram/SourceFiles/boxes/autolockbox.h
index 23831274d..a772d30d5 100644
--- a/Telegram/SourceFiles/boxes/autolockbox.h
+++ b/Telegram/SourceFiles/boxes/autolockbox.h
@@ -36,10 +36,9 @@ public:
 protected:
 	void prepare() override;
 
-private slots:
-	void onChange();
-
 private:
-	QVector<Ui::Radiobutton*> _options;
+	void durationChanged(int seconds);
+
+	std::vector<object_ptr<Ui::Radiobutton>> _options;
 
 };
diff --git a/Telegram/SourceFiles/boxes/connectionbox.cpp b/Telegram/SourceFiles/boxes/connectionbox.cpp
index bc86d62c2..8f37c45dd 100644
--- a/Telegram/SourceFiles/boxes/connectionbox.cpp
+++ b/Telegram/SourceFiles/boxes/connectionbox.cpp
@@ -35,9 +35,10 @@ ConnectionBox::ConnectionBox(QWidget *parent)
 , _portInput(this, st::connectionPortInputField, lang(lng_connection_port_ph), QString::number(Global::ConnectionProxy().port))
 , _userInput(this, st::connectionUserInputField, lang(lng_connection_user_ph), Global::ConnectionProxy().user)
 , _passwordInput(this, st::connectionPasswordInputField, lang(lng_connection_password_ph), Global::ConnectionProxy().password)
-, _autoRadio(this, qsl("conn_type"), dbictAuto, lang(lng_connection_auto_rb), (Global::ConnectionType() == dbictAuto), st::defaultBoxCheckbox)
-, _httpProxyRadio(this, qsl("conn_type"), dbictHttpProxy, lang(lng_connection_http_proxy_rb), (Global::ConnectionType() == dbictHttpProxy), st::defaultBoxCheckbox)
-, _tcpProxyRadio(this, qsl("conn_type"), dbictTcpProxy, lang(lng_connection_tcp_proxy_rb), (Global::ConnectionType() == dbictTcpProxy), st::defaultBoxCheckbox)
+, _typeGroup(std::make_shared<Ui::RadioenumGroup<DBIConnectionType>>(Global::ConnectionType()))
+, _autoRadio(this, _typeGroup, dbictAuto, lang(lng_connection_auto_rb), st::defaultBoxCheckbox)
+, _httpProxyRadio(this, _typeGroup, dbictHttpProxy, lang(lng_connection_http_proxy_rb), st::defaultBoxCheckbox)
+, _tcpProxyRadio(this, _typeGroup, dbictTcpProxy, lang(lng_connection_tcp_proxy_rb), st::defaultBoxCheckbox)
 , _tryIPv6(this, lang(lng_connection_try_ipv6), Global::TryIPv6(), st::defaultBoxCheckbox) {
 }
 
@@ -47,9 +48,7 @@ void ConnectionBox::prepare() {
 	addButton(lang(lng_connection_save), [this] { onSave(); });
 	addButton(lang(lng_cancel), [this] { closeBox(); });
 
-	connect(_autoRadio, SIGNAL(changed()), this, SLOT(onChange()));
-	connect(_httpProxyRadio, SIGNAL(changed()), this, SLOT(onChange()));
-	connect(_tcpProxyRadio, SIGNAL(changed()), this, SLOT(onChange()));
+	_typeGroup->setChangedCallback([this](DBIConnectionType value) { typeChanged(value); });
 
 	connect(_hostInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
 	connect(_portInput, SIGNAL(submitted(bool)), this, SLOT(onSubmit()));
@@ -61,17 +60,17 @@ void ConnectionBox::prepare() {
 
 void ConnectionBox::updateControlsVisibility() {
 	auto newHeight = st::boxOptionListPadding.top() + _autoRadio->heightNoMargins() + st::boxOptionListSkip + _httpProxyRadio->heightNoMargins() + st::boxOptionListSkip + _tcpProxyRadio->heightNoMargins() + st::boxOptionListSkip + st::connectionIPv6Skip + _tryIPv6->heightNoMargins() + st::boxOptionListPadding.bottom() + st::boxPadding.bottom();
-	if (_httpProxyRadio->checked() || _tcpProxyRadio->checked()) {
+	if (_typeGroup->value() == dbictAuto) {
+		_hostInput->hide();
+		_portInput->hide();
+		_userInput->hide();
+		_passwordInput->hide();
+	} else {
 		newHeight += 2 * st::boxOptionInputSkip + 2 * _hostInput->height();
 		_hostInput->show();
 		_portInput->show();
 		_userInput->show();
 		_passwordInput->show();
-	} else {
-		_hostInput->hide();
-		_portInput->hide();
-		_userInput->hide();
-		_passwordInput->hide();
 	}
 
 	setDimensions(st::boxWidth, newHeight);
@@ -93,16 +92,17 @@ void ConnectionBox::resizeEvent(QResizeEvent *e) {
 }
 
 void ConnectionBox::updateControlsPosition() {
+	auto type = _typeGroup->value();
 	_autoRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), st::boxOptionListPadding.top());
 	_httpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _autoRadio->bottomNoMargins() + st::boxOptionListSkip);
 
-	int32 inputy = 0;
-	if (_httpProxyRadio->checked()) {
+	auto inputy = 0;
+	if (type == dbictHttpProxy) {
 		inputy = _httpProxyRadio->bottomNoMargins() + st::boxOptionInputSkip;
 		_tcpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), inputy + st::boxOptionInputSkip + 2 * _hostInput->height() + st::boxOptionListSkip);
 	} else {
 		_tcpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _httpProxyRadio->bottomNoMargins() + st::boxOptionListSkip);
-		if (_tcpProxyRadio->checked()) {
+		if (type == dbictTcpProxy) {
 			inputy = _tcpProxyRadio->bottomNoMargins() + st::boxOptionInputSkip;
 		}
 	}
@@ -114,17 +114,17 @@ void ConnectionBox::updateControlsPosition() {
 		_passwordInput->moveToRight(st::boxPadding.right(), _userInput->y());
 	}
 
-	auto tryipv6y = (_tcpProxyRadio->checked() ? _userInput->bottomNoMargins() : _tcpProxyRadio->bottomNoMargins()) + st::boxOptionListSkip + st::connectionIPv6Skip;
+	auto tryipv6y = ((type == dbictTcpProxy) ? _userInput->bottomNoMargins() : _tcpProxyRadio->bottomNoMargins()) + st::boxOptionListSkip + st::connectionIPv6Skip;
 	_tryIPv6->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), tryipv6y);
 }
 
-void ConnectionBox::onChange() {
+void ConnectionBox::typeChanged(DBIConnectionType type) {
 	updateControlsVisibility();
-	if (_httpProxyRadio->checked() || _tcpProxyRadio->checked()) {
+	if (type != dbictAuto) {
 		if (!_hostInput->hasFocus() && !_portInput->hasFocus() && !_userInput->hasFocus() && !_passwordInput->hasFocus()) {
 			_hostInput->setFocusFast();
 		}
-		if (_httpProxyRadio->checked() && !_portInput->getLastText().toInt()) {
+		if ((type == dbictHttpProxy) && !_portInput->getLastText().toInt()) {
 			_portInput->setText(qsl("80"));
 			_portInput->finishAnimations();
 		}
@@ -161,7 +161,15 @@ void ConnectionBox::onSubmit() {
 }
 
 void ConnectionBox::onSave() {
-	if (_httpProxyRadio->checked() || _tcpProxyRadio->checked()) {
+	auto type = _typeGroup->value();
+	if (type == dbictAuto) {
+		Global::SetConnectionType(type);
+		Global::SetConnectionProxy(ProxyData());
+#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
+		QNetworkProxyFactory::setUseSystemConfiguration(false);
+		QNetworkProxyFactory::setUseSystemConfiguration(true);
+#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
+	} else {
 		ProxyData p;
 		p.host = _hostInput->getLastText().trimmed();
 		p.user = _userInput->getLastText().trimmed();
@@ -174,19 +182,8 @@ void ConnectionBox::onSave() {
 			_portInput->setFocus();
 			return;
 		}
-		if (_httpProxyRadio->checked()) {
-			Global::SetConnectionType(dbictHttpProxy);
-		} else {
-			Global::SetConnectionType(dbictTcpProxy);
-		}
+		Global::SetConnectionType(type);
 		Global::SetConnectionProxy(p);
-	} else {
-		Global::SetConnectionType(dbictAuto);
-		Global::SetConnectionProxy(ProxyData());
-#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
-		QNetworkProxyFactory::setUseSystemConfiguration(false);
-		QNetworkProxyFactory::setUseSystemConfiguration(true);
-#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
 	}
 	if (cPlatform() == dbipWindows && Global::TryIPv6() != _tryIPv6->checked()) {
 		Global::SetTryIPv6(_tryIPv6->checked());
diff --git a/Telegram/SourceFiles/boxes/connectionbox.h b/Telegram/SourceFiles/boxes/connectionbox.h
index 4d486ed02..5ef85ded3 100644
--- a/Telegram/SourceFiles/boxes/connectionbox.h
+++ b/Telegram/SourceFiles/boxes/connectionbox.h
@@ -27,7 +27,10 @@ class InputField;
 class PortInput;
 class PasswordInput;
 class Checkbox;
-class Radiobutton;
+template <typename Enum>
+class RadioenumGroup;
+template <typename Enum>
+class Radioenum;
 } // namespace Ui
 
 class ConnectionBox : public BoxContent {
@@ -43,11 +46,11 @@ protected:
 	void resizeEvent(QResizeEvent *e) override;
 
 private slots:
-	void onChange();
 	void onSubmit();
 	void onSave();
 
 private:
+	void typeChanged(DBIConnectionType type);
 	void updateControlsVisibility();
 	void updateControlsPosition();
 
@@ -55,9 +58,10 @@ private:
 	object_ptr<Ui::PortInput> _portInput;
 	object_ptr<Ui::InputField> _userInput;
 	object_ptr<Ui::PasswordInput> _passwordInput;
-	object_ptr<Ui::Radiobutton> _autoRadio;
-	object_ptr<Ui::Radiobutton> _httpProxyRadio;
-	object_ptr<Ui::Radiobutton> _tcpProxyRadio;
+	std::shared_ptr<Ui::RadioenumGroup<DBIConnectionType>> _typeGroup;
+	object_ptr<Ui::Radioenum<DBIConnectionType>> _autoRadio;
+	object_ptr<Ui::Radioenum<DBIConnectionType>> _httpProxyRadio;
+	object_ptr<Ui::Radioenum<DBIConnectionType>> _tcpProxyRadio;
 	object_ptr<Ui::Checkbox> _tryIPv6;
 
 };
diff --git a/Telegram/SourceFiles/boxes/downloadpathbox.cpp b/Telegram/SourceFiles/boxes/downloadpathbox.cpp
index 6aa99f5c0..157a50c51 100644
--- a/Telegram/SourceFiles/boxes/downloadpathbox.cpp
+++ b/Telegram/SourceFiles/boxes/downloadpathbox.cpp
@@ -31,9 +31,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 DownloadPathBox::DownloadPathBox(QWidget *parent)
 : _path(Global::DownloadPath())
 , _pathBookmark(Global::DownloadPathBookmark())
-, _default(this, qsl("dir_type"), 0, lang(lng_download_path_default_radio), _path.isEmpty(), st::defaultBoxCheckbox)
-, _temp(this, qsl("dir_type"), 1, lang(lng_download_path_temp_radio), (_path == qsl("tmp")), st::defaultBoxCheckbox)
-, _dir(this, qsl("dir_type"), 2, lang(lng_download_path_dir_radio), (!_path.isEmpty() && _path != qsl("tmp")), st::defaultBoxCheckbox)
+, _group(std::make_shared<Ui::RadioenumGroup<Directory>>(typeFromPath(_path)))
+, _default(this, _group, Directory::Downloads, lang(lng_download_path_default_radio), st::defaultBoxCheckbox)
+, _temp(this, _group, Directory::Temp, lang(lng_download_path_temp_radio), st::defaultBoxCheckbox)
+, _dir(this, _group, Directory::Custom, lang(lng_download_path_dir_radio), st::defaultBoxCheckbox)
 , _pathLink(this, QString(), st::boxLinkButton) {
 }
 
@@ -43,9 +44,7 @@ void DownloadPathBox::prepare() {
 
 	setTitle(lang(lng_download_path_header));
 
-	connect(_default, SIGNAL(changed()), this, SLOT(onChange()));
-	connect(_temp, SIGNAL(changed()), this, SLOT(onChange()));
-	connect(_dir, SIGNAL(changed()), this, SLOT(onChange()));
+	_group->setChangedCallback([this](Directory value) { radioChanged(value); });
 
 	connect(_pathLink, SIGNAL(clicked()), this, SLOT(onEditPath()));
 	if (!_path.isEmpty() && _path != qsl("tmp")) {
@@ -55,10 +54,11 @@ void DownloadPathBox::prepare() {
 }
 
 void DownloadPathBox::updateControlsVisibility() {
-	_pathLink->setVisible(_dir->checked());
+	auto custom = (_group->value() == Directory::Custom);
+	_pathLink->setVisible(custom);
 
 	auto newHeight = st::boxOptionListPadding.top() + _default->heightNoMargins() + st::boxOptionListSkip + _temp->heightNoMargins() + st::boxOptionListSkip + _dir->heightNoMargins();
-	if (_dir->checked()) {
+	if (custom) {
 		newHeight += st::downloadPathSkip + _pathLink->height();
 	}
 	newHeight += st::boxOptionListPadding.bottom();
@@ -78,18 +78,15 @@ void DownloadPathBox::resizeEvent(QResizeEvent *e) {
 	_pathLink->moveToLeft(inputx, inputy);
 }
 
-void DownloadPathBox::onChange() {
-	if (_dir->checked()) {
+void DownloadPathBox::radioChanged(Directory value) {
+	if (value == Directory::Custom) {
 		if (_path.isEmpty() || _path == qsl("tmp")) {
-			(_path.isEmpty() ? _default : _temp)->setChecked(true);
+			_group->setValue(_path.isEmpty() ? Directory::Downloads : Directory::Temp);
 			onEditPath();
-			if (!_path.isEmpty() && _path != qsl("tmp")) {
-				_dir->setChecked(true);
-			}
 		} else {
 			setPathText(QDir::toNativeSeparators(_path));
 		}
-	} else if (_temp->checked()) {
+	} else if (value == Directory::Temp) {
 		_path = qsl("tmp");
 	} else {
 		_path = QString();
@@ -110,14 +107,24 @@ void DownloadPathBox::onEditPath() {
 			_path = result + '/';
 			_pathBookmark = psDownloadPathBookmark(_path);
 			setPathText(QDir::toNativeSeparators(_path));
+			_group->setValue(Directory::Custom);
 		}
 	}));
 }
 
 void DownloadPathBox::save() {
 #ifndef OS_WIN_STORE
-	Global::SetDownloadPath(_default->checked() ? QString() : (_temp->checked() ? qsl("tmp") : _path));
-	Global::SetDownloadPathBookmark((_default->checked() || _temp->checked()) ? QByteArray() : _pathBookmark);
+	auto value = _group->value();
+	auto computePath = [this, value] {
+		if (value == Directory::Custom) {
+			return _path;
+		} else if (value == Directory::Temp) {
+			return qsl("tmp");
+		}
+		return QString();
+	};
+	Global::SetDownloadPath(computePath());
+	Global::SetDownloadPathBookmark((value == Directory::Custom) ? _pathBookmark : QByteArray());
 	Local::writeUserSettings();
 	Global::RefDownloadPathChanged().notify();
 	closeBox();
diff --git a/Telegram/SourceFiles/boxes/downloadpathbox.h b/Telegram/SourceFiles/boxes/downloadpathbox.h
index 7fc954d6b..98b3b1cfa 100644
--- a/Telegram/SourceFiles/boxes/downloadpathbox.h
+++ b/Telegram/SourceFiles/boxes/downloadpathbox.h
@@ -24,7 +24,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include "core/observer.h"
 
 namespace Ui {
-class Radiobutton;
+template <typename Enum>
+class RadioenumGroup;
+template <typename Enum>
+class Radioenum;
 class LinkButton;
 } // namespace Ui
 
@@ -40,10 +43,24 @@ protected:
 	void resizeEvent(QResizeEvent *e) override;
 
 private slots:
-	void onChange();
 	void onEditPath();
 
 private:
+	enum class Directory {
+		Downloads,
+		Temp,
+		Custom,
+	};
+	void radioChanged(Directory value);
+	Directory typeFromPath(const QString &path) {
+		if (path.isEmpty()) {
+			return Directory::Downloads;
+		} else if (path == qsl("tmp")) {
+			return Directory::Temp;
+		}
+		return Directory::Custom;
+	}
+
 	void save();
 	void updateControlsVisibility();
 	void setPathText(const QString &text);
@@ -51,9 +68,10 @@ private:
 	QString _path;
 	QByteArray _pathBookmark;
 
-	object_ptr<Ui::Radiobutton> _default;
-	object_ptr<Ui::Radiobutton> _temp;
-	object_ptr<Ui::Radiobutton> _dir;
+	std::shared_ptr<Ui::RadioenumGroup<Directory>> _group;
+	object_ptr<Ui::Radioenum<Directory>> _default;
+	object_ptr<Ui::Radioenum<Directory>> _temp;
+	object_ptr<Ui::Radioenum<Directory>> _dir;
 	object_ptr<Ui::LinkButton> _pathLink;
 
 };
diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
index 8a25c347d..63aa3007e 100644
--- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
@@ -90,7 +90,7 @@ std::unique_ptr<PrivacyExceptionsBoxController::Row> PrivacyExceptionsBoxControl
 
 class EditPrivacyBox::OptionWidget : public TWidget {
 public:
-	OptionWidget(QWidget *parent, const std::shared_ptr<Ui::RadiobuttonGroup> &group, int value, const QString &text, const QString &description);
+	OptionWidget(QWidget *parent, const std::shared_ptr<Ui::RadioenumGroup<Option>> &group, Option value, const QString &text, const QString &description);
 
 	QMargins getMargins() const override {
 		return _option->getMargins();
@@ -100,12 +100,12 @@ protected:
 	int resizeGetHeight(int newWidth) override;
 
 private:
-	object_ptr<Ui::Radiobutton> _option;
+	object_ptr<Ui::Radioenum<Option>> _option;
 	object_ptr<Ui::FlatLabel> _description;
 
 };
 
-EditPrivacyBox::OptionWidget::OptionWidget(QWidget *parent, const std::shared_ptr<Ui::RadiobuttonGroup> &group, int value, const QString &text, const QString &description) : TWidget(parent)
+EditPrivacyBox::OptionWidget::OptionWidget(QWidget *parent, const std::shared_ptr<Ui::RadioenumGroup<Option>> &group, Option value, const QString &text, const QString &description) : TWidget(parent)
 , _option(this, group, value, text, st::defaultBoxCheckbox)
 , _description(this, description, Ui::FlatLabel::InitType::Simple, st::editPrivacyLabel) {
 }
@@ -178,15 +178,14 @@ int EditPrivacyBox::resizeGetHeight(int newWidth) {
 
 int EditPrivacyBox::countDefaultHeight(int newWidth) {
 	auto height = 0;
-	auto fakeGroup = std::make_shared<Ui::RadiobuttonGroup>(0);
+	auto fakeGroup = std::make_shared<Ui::RadioenumGroup<Option>>(Option::Everyone);
 	auto optionHeight = [this, newWidth, &fakeGroup](Option option, const QString &label) {
 		auto description = _controller->optionDescription(option);
 		if (description.isEmpty()) {
 			return 0;
 		}
 
-		auto value = static_cast<int>(Option::Everyone);
-		auto fake = object_ptr<OptionWidget>(nullptr, fakeGroup, value, label, description);
+		auto fake = object_ptr<OptionWidget>(nullptr, fakeGroup, Option::Everyone, label, description);
 		fake->resizeToNaturalWidth(newWidth - st::editPrivacyOptionMargin.left() - st::editPrivacyOptionMargin.right());
 		return st::editPrivacyOptionMargin.top() + fake->heightNoMargins() + st::editPrivacyOptionMargin.bottom();
 	};
@@ -296,20 +295,19 @@ void EditPrivacyBox::createOption(Option option, object_ptr<OptionWidget> &widge
 	auto description = _controller->optionDescription(option);
 	auto selected = (_option == option);
 	if (!description.isEmpty() || selected) {
-		auto value = static_cast<int>(option);
-		widget.create(this, _optionGroup, value, label, description);
+		widget.create(this, _optionGroup, option, label, description);
 	}
 }
 
 void EditPrivacyBox::createWidgets() {
 	_loading.destroy();
 
-	_optionGroup = std::make_shared<Ui::RadiobuttonGroup>(static_cast<int>(_option));
+	_optionGroup = std::make_shared<Ui::RadioenumGroup<Option>>(_option);
 	createOption(Option::Everyone, _everyone, lang(lng_edit_privacy_everyone));
 	createOption(Option::Contacts, _contacts, lang(lng_edit_privacy_contacts));
 	createOption(Option::Nobody, _nobody, lang(lng_edit_privacy_nobody));
-	_optionGroup->setChangedCallback([this](int value) {
-		_option = static_cast<Option>(value);
+	_optionGroup->setChangedCallback([this](Option value) {
+		_option = value;
 		_alwaysLink->toggleAnimated(showExceptionLink(Exception::Always));
 		_neverLink->toggleAnimated(showExceptionLink(Exception::Never));
 	});
diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.h b/Telegram/SourceFiles/boxes/edit_privacy_box.h
index 5325e1051..a193053c9 100644
--- a/Telegram/SourceFiles/boxes/edit_privacy_box.h
+++ b/Telegram/SourceFiles/boxes/edit_privacy_box.h
@@ -25,7 +25,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 namespace Ui {
 class FlatLabel;
 class LinkButton;
-class RadiobuttonGroup;
+template <typename Enum>
+class RadioenumGroup;
 template <typename Widget>
 class WidgetSlideWrap;
 } // namespace Ui
@@ -104,7 +105,7 @@ private:
 	std::unique_ptr<Controller> _controller;
 	Option _option = Option::Everyone;
 
-	std::shared_ptr<Ui::RadiobuttonGroup> _optionGroup;
+	std::shared_ptr<Ui::RadioenumGroup<Option>> _optionGroup;
 	object_ptr<Ui::FlatLabel> _loading;
 	object_ptr<OptionWidget> _everyone = { nullptr };
 	object_ptr<OptionWidget> _contacts = { nullptr };
diff --git a/Telegram/SourceFiles/boxes/report_box.cpp b/Telegram/SourceFiles/boxes/report_box.cpp
index 048565e79..0733e443c 100644
--- a/Telegram/SourceFiles/boxes/report_box.cpp
+++ b/Telegram/SourceFiles/boxes/report_box.cpp
@@ -30,11 +30,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include "mainwindow.h"
 
 ReportBox::ReportBox(QWidget*, PeerData *peer) : _peer(peer)
-, _reasonGroup(std::make_shared<Ui::RadiobuttonGroup>(ReasonSpam))
-, _reasonSpam(this, _reasonGroup, ReasonSpam, lang(lng_report_reason_spam), st::defaultBoxCheckbox)
-, _reasonViolence(this, _reasonGroup, ReasonViolence, lang(lng_report_reason_violence), st::defaultBoxCheckbox)
-, _reasonPornography(this, _reasonGroup, ReasonPornography, lang(lng_report_reason_pornography), st::defaultBoxCheckbox)
-, _reasonOther(this, _reasonGroup, ReasonOther, lang(lng_report_reason_other), st::defaultBoxCheckbox) {
+, _reasonGroup(std::make_shared<Ui::RadioenumGroup<Reason>>(Reason::Spam))
+, _reasonSpam(this, _reasonGroup, Reason::Spam, lang(lng_report_reason_spam), st::defaultBoxCheckbox)
+, _reasonViolence(this, _reasonGroup, Reason::Violence, lang(lng_report_reason_violence), st::defaultBoxCheckbox)
+, _reasonPornography(this, _reasonGroup, Reason::Pornography, lang(lng_report_reason_pornography), st::defaultBoxCheckbox)
+, _reasonOther(this, _reasonGroup, Reason::Other, lang(lng_report_reason_other), st::defaultBoxCheckbox) {
 }
 
 void ReportBox::prepare() {
@@ -43,7 +43,7 @@ void ReportBox::prepare() {
 	addButton(lang(lng_report_button), [this] { onReport(); });
 	addButton(lang(lng_cancel), [this] { closeBox(); });
 
-	_reasonGroup->setChangedCallback([this](int value) { reasonChanged(value); });
+	_reasonGroup->setChangedCallback([this](Reason value) { reasonChanged(value); });
 
 	updateMaxHeight();
 }
@@ -61,8 +61,8 @@ void ReportBox::resizeEvent(QResizeEvent *e) {
 	}
 }
 
-void ReportBox::reasonChanged(int reason) {
-	if (reason == ReasonOther) {
+void ReportBox::reasonChanged(Reason reason) {
+	if (reason == Reason::Other) {
 		if (!_reasonOtherText) {
 			_reasonOtherText.create(this, st::profileReportReasonOther, lang(lng_report_reason_description));
 			_reasonOtherText->show();
@@ -105,10 +105,10 @@ void ReportBox::onReport() {
 
 	auto getReason = [this]() {
 		switch (_reasonGroup->value()) {
-		case ReasonSpam: return MTP_inputReportReasonSpam();
-		case ReasonViolence: return MTP_inputReportReasonViolence();
-		case ReasonPornography: return MTP_inputReportReasonPornography();
-		case ReasonOther: return MTP_inputReportReasonOther(MTP_string(_reasonOtherText->getLastText()));
+		case Reason::Spam: return MTP_inputReportReasonSpam();
+		case Reason::Violence: return MTP_inputReportReasonViolence();
+		case Reason::Pornography: return MTP_inputReportReasonPornography();
+		case Reason::Other: return MTP_inputReportReasonOther(MTP_string(_reasonOtherText->getLastText()));
 		}
 		Unexpected("Bad reason group value.");
 	};
diff --git a/Telegram/SourceFiles/boxes/report_box.h b/Telegram/SourceFiles/boxes/report_box.h
index 4296af821..701590613 100644
--- a/Telegram/SourceFiles/boxes/report_box.h
+++ b/Telegram/SourceFiles/boxes/report_box.h
@@ -23,8 +23,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include "boxes/abstractbox.h"
 
 namespace Ui {
-class RadiobuttonGroup;
-class Radiobutton;
+template <typename Enum>
+class RadioenumGroup;
+template <typename Enum>
+class Radioenum;
 class InputArea;
 } // namespace Ui
 
@@ -48,7 +50,13 @@ protected:
 	void resizeEvent(QResizeEvent *e) override;
 
 private:
-	void reasonChanged(int reason);
+	enum class Reason {
+		Spam,
+		Violence,
+		Pornography,
+		Other,
+	};
+	void reasonChanged(Reason reason);
 	void updateMaxHeight();
 
 	void reportDone(const MTPBool &result);
@@ -56,19 +64,13 @@ private:
 
 	PeerData *_peer;
 
-	std::shared_ptr<Ui::RadiobuttonGroup> _reasonGroup;
-	object_ptr<Ui::Radiobutton> _reasonSpam;
-	object_ptr<Ui::Radiobutton> _reasonViolence;
-	object_ptr<Ui::Radiobutton> _reasonPornography;
-	object_ptr<Ui::Radiobutton> _reasonOther;
+	std::shared_ptr<Ui::RadioenumGroup<Reason>> _reasonGroup;
+	object_ptr<Ui::Radioenum<Reason>> _reasonSpam;
+	object_ptr<Ui::Radioenum<Reason>> _reasonViolence;
+	object_ptr<Ui::Radioenum<Reason>> _reasonPornography;
+	object_ptr<Ui::Radioenum<Reason>> _reasonOther;
 	object_ptr<Ui::InputArea> _reasonOtherText = { nullptr };
 
-	enum Reason {
-		ReasonSpam,
-		ReasonViolence,
-		ReasonPornography,
-		ReasonOther,
-	};
 	mtpRequestId _requestId = 0;
 
 };
diff --git a/Telegram/SourceFiles/settings/settings_block_widget.cpp b/Telegram/SourceFiles/settings/settings_block_widget.cpp
index 62f3c9e27..7bc950126 100644
--- a/Telegram/SourceFiles/settings/settings_block_widget.cpp
+++ b/Telegram/SourceFiles/settings/settings_block_widget.cpp
@@ -90,12 +90,8 @@ void BlockWidget::createChildRow(object_ptr<Ui::Checkbox> &child, style::margins
 	connect(child, SIGNAL(changed()), this, slot);
 }
 
-void BlockWidget::createChildRow(object_ptr<Ui::Radiobutton> &child, style::margins &margin, const std::shared_ptr<Ui::RadiobuttonGroup> &group, int value, const QString &text) {
-	child.create(this, group, value, text, st::defaultBoxCheckbox);
-}
-
 void BlockWidget::createChildRow(object_ptr<Ui::LinkButton> &child, style::margins &margin, const QString &text, const char *slot, const style::LinkButton &st) {
-	child .create(this, text, st);
+	child.create(this, text, st);
 	connect(child, SIGNAL(clicked()), this, slot);
 }
 
diff --git a/Telegram/SourceFiles/settings/settings_block_widget.h b/Telegram/SourceFiles/settings/settings_block_widget.h
index 25c0ef33d..79b6abeab 100644
--- a/Telegram/SourceFiles/settings/settings_block_widget.h
+++ b/Telegram/SourceFiles/settings/settings_block_widget.h
@@ -28,6 +28,10 @@ class Checkbox;
 class RadiobuttonGroup;
 class Radiobutton;
 class LinkButton;
+template <typename Enum>
+class RadioenumGroup;
+template <typename Enum>
+class Radioenum;
 template <typename Widget>
 class WidgetSlideWrap;
 } // namespace Ui
@@ -88,27 +92,35 @@ private:
 		margin.setBottom(margin.bottom() - padding.bottom());
 	}
 	void createChildRow(object_ptr<Ui::Checkbox> &child, style::margins &margin, const QString &text, const char *slot, bool checked);
-	void createChildRow(object_ptr<Ui::Radiobutton> &child, style::margins &margin, const Ui::RadiobuttonGroup &group, int value, const QString &text);
 	void createChildRow(object_ptr<Ui::LinkButton> &child, style::margins &margin, const QString &text, const char *slot, const style::LinkButton &st = st::boxLinkButton);
 
+	template <typename Enum>
+	void createChildRow(object_ptr<Ui::Radioenum<Enum>> &child, style::margins &margin, const std::shared_ptr<Ui::RadioenumGroup<Enum>> &group, Enum value, const QString &text) {
+		child.create(this, group, value, text, st::defaultBoxCheckbox);
+	}
+
 	void addCreatedRow(TWidget *child, const style::margins &margin);
 	void rowHeightUpdated();
 
 	template <typename Widget>
-	struct IsWidgetSlideWrap {
-		static constexpr bool value = false;
+	struct IsWidgetSlideWrap : std::false_type {
 	};
 	template <typename Widget>
-	struct IsWidgetSlideWrap<Ui::WidgetSlideWrap<Widget>> {
-		static constexpr bool value = true;
+	struct IsWidgetSlideWrap<Ui::WidgetSlideWrap<Widget>> : std::true_type {
+	};
+	template <typename Widget>
+	struct IsRadioenum : std::false_type {
+	};
+	template <typename Enum>
+	struct IsRadioenum<Ui::Radioenum<Enum>> : std::true_type {
 	};
 
 	template <typename Widget>
 	using NotImplementedYet = std::enable_if_t<
 		!IsWidgetSlideWrap<Widget>::value &&
-		!std::is_same<Widget, Ui::Checkbox>::value &&
-		!std::is_same<Widget, Ui::Radiobutton>::value &&
-		!std::is_same<Widget, Ui::LinkButton>::value>;
+		!IsRadioenum<Widget>::value &&
+		!std::is_same<Ui::Checkbox, Widget>::value &&
+		!std::is_same<Ui::LinkButton, Widget>::value>;
 
 	template <typename Widget, typename... Args, typename = NotImplementedYet<Widget>>
 	void createChildRow(object_ptr<Widget> &child, style::margins &margin, Args&&... args) {
diff --git a/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp b/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp
index 7a3a46463..c02dcef49 100644
--- a/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp
+++ b/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp
@@ -180,10 +180,10 @@ void ChatSettingsWidget::createControls() {
 	}
 #endif // OS_WIN_STORE
 
-	auto group = std::make_shared<Ui::RadiobuttonGroup>(cCtrlEnter() ? 1 : 0);
-	addChildRow(_sendByEnter, marginSmall, group, 0, lang(lng_settings_send_enter));
-	addChildRow(_sendByCtrlEnter, marginSkip, group, 1, lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_settings_send_cmdenter : lng_settings_send_ctrlenter), SLOT(onSendByCtrlEnter()), cCtrlEnter());
-	group->setChangedCallback([this](int value) {
+	auto group = std::make_shared<Ui::RadioenumGroup<SendByType>>(cCtrlEnter() ? SendByType::CtrlEnter : SendByType::Enter);
+	addChildRow(_sendByEnter, marginSmall, group, SendByType::Enter, lang(lng_settings_send_enter));
+	addChildRow(_sendByCtrlEnter, marginSkip, group, SendByType::CtrlEnter, lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_settings_send_cmdenter : lng_settings_send_ctrlenter));
+	group->setChangedCallback([this](SendByType value) {
 		sendByChanged(value);
 	});
 
@@ -210,8 +210,8 @@ void ChatSettingsWidget::onDontAskDownloadPath() {
 #endif // OS_WIN_STORE
 }
 
-void ChatSettingsWidget::sendByChanged(int value) {
-	cSetCtrlEnter(value == 1);
+void ChatSettingsWidget::sendByChanged(SendByType value) {
+	cSetCtrlEnter(value == SendByType::CtrlEnter);
 	if (App::main()) App::main()->ctrlEnterSubmitUpdated();
 	Local::writeUserSettings();
 }
diff --git a/Telegram/SourceFiles/settings/settings_chat_settings_widget.h b/Telegram/SourceFiles/settings/settings_chat_settings_widget.h
index d5def43dd..33a71a63b 100644
--- a/Telegram/SourceFiles/settings/settings_chat_settings_widget.h
+++ b/Telegram/SourceFiles/settings/settings_chat_settings_widget.h
@@ -102,7 +102,11 @@ private slots:
 	void onManageStickerSets();
 
 private:
-	void sendByChanged(int value);
+	enum class SendByType {
+		Enter,
+		CtrlEnter,
+	};
+	void sendByChanged(SendByType value);
 	void createControls();
 
 	object_ptr<Ui::Checkbox> _replaceEmoji = { nullptr };
@@ -113,8 +117,8 @@ private:
 	object_ptr<Ui::WidgetSlideWrap<DownloadPathState>> _downloadPath = { nullptr };
 #endif // OS_WIN_STORE
 
-	object_ptr<Ui::Radiobutton> _sendByEnter = { nullptr };
-	object_ptr<Ui::Radiobutton> _sendByCtrlEnter = { nullptr };
+	object_ptr<Ui::Radioenum<SendByType>> _sendByEnter = { nullptr };
+	object_ptr<Ui::Radioenum<SendByType>> _sendByCtrlEnter = { nullptr };
 	object_ptr<Ui::LinkButton> _automaticMediaDownloadSettings = { nullptr };
 	object_ptr<Ui::LinkButton> _manageStickerSets = { nullptr };
 
diff --git a/Telegram/SourceFiles/ui/widgets/checkbox.h b/Telegram/SourceFiles/ui/widgets/checkbox.h
index a4ca803d9..0d90a5fb0 100644
--- a/Telegram/SourceFiles/ui/widgets/checkbox.h
+++ b/Telegram/SourceFiles/ui/widgets/checkbox.h
@@ -91,6 +91,8 @@ public:
 	}
 	void setValue(int value);
 
+private:
+	friend class Radiobutton;
 	void registerButton(Radiobutton *button) {
 		if (!base::contains(_buttons, button)) {
 			_buttons.push_back(button);
@@ -100,11 +102,6 @@ public:
 		_buttons.erase(std::remove(_buttons.begin(), _buttons.end(), button), _buttons.end());
 	}
 
-private:
-	friend class Radiobutton;
-	void registerButton(Radiobutton *button);
-	void unregisterButton(Radiobutton *button);
-
 	int _value = 0;
 	bool _hasValue = false;
 	base::lambda<void(int value)> _changedCallback;
@@ -116,9 +113,6 @@ class Radiobutton : public RippleButton {
 public:
 	Radiobutton(QWidget *parent, const std::shared_ptr<RadiobuttonGroup> &group, int value, const QString &text, const style::Checkbox &st = st::defaultCheckbox);
 
-	RadiobuttonGroup *group() const {
-		return _group.get();
-	}
 	QMargins getMargins() const override {
 		return _st.margin;
 	}
@@ -152,4 +146,48 @@ private:
 
 };
 
+template <typename Enum>
+class Radioenum;
+
+template <typename Enum>
+class RadioenumGroup {
+public:
+	RadioenumGroup() = default;
+	RadioenumGroup(Enum value) : _group(static_cast<int>(value)) {
+	}
+
+	template <typename Callback>
+	void setChangedCallback(Callback &&callback) {
+		_group.setChangedCallback([callback](int value) {
+			callback(static_cast<Enum>(value));
+		});
+	}
+
+	bool hasValue() const {
+		return _group.hasValue();
+	}
+	Enum value() const {
+		return static_cast<Enum>(_group.value());
+	}
+	void setValue(Enum value) {
+		_group.setValue(static_cast<int>(value));
+	}
+
+private:
+	template <typename OtherEnum>
+	friend class Radioenum;
+
+	RadiobuttonGroup _group;
+
+};
+
+template <typename Enum>
+class Radioenum : public Radiobutton {
+public:
+	Radioenum(QWidget *parent, const std::shared_ptr<RadioenumGroup<Enum>> &group, Enum value, const QString &text, const style::Checkbox &st = st::defaultCheckbox)
+		: Radiobutton(parent, std::shared_ptr<RadiobuttonGroup>(group, &group->_group), static_cast<int>(value), text, st) {
+	}
+
+};
+
 } // namespace Ui