From 900cce213a6ed000b7131a05a0dec7d04b35b023 Mon Sep 17 00:00:00 2001 From: Manuel Nickschas Date: Mon, 17 Sep 2018 23:42:37 +0200 Subject: [PATCH] uisupport: Provide helpers for dealing with widget changes In many places, we want to connect to a widget's changed signal, i.e. a signal that is emitted whenever the widget changes. This requires a great deal of boilerplate especially in settingspages, since the signal to listen to varies between widget types. Provide helpers that automate most of this by matching the given widget against a static list of supported widgets' changed signals, and connecting automatically. A sprinkle of template magic makes it very easy to support a bunch of different widget types in a generic way. Make use of this feature to get rid of the remaining old-style connects in SettingsPage. Also migrate a couple of specific settingspages as proof-of-concept and API validation; others can follow over time. --- .../settingspages/backlogsettingspage.cpp | 7 +- .../settingspages/networkssettingspage.cpp | 54 ++++--- src/qtui/settingspages/networkssettingspage.h | 2 +- src/uisupport/CMakeLists.txt | 1 + src/uisupport/settingspage.cpp | 19 +-- src/uisupport/widgethelpers.h | 136 ++++++++++++++++++ 6 files changed, 170 insertions(+), 49 deletions(-) create mode 100644 src/uisupport/widgethelpers.h diff --git a/src/qtui/settingspages/backlogsettingspage.cpp b/src/qtui/settingspages/backlogsettingspage.cpp index f0ec4ab6..541fb558 100644 --- a/src/qtui/settingspages/backlogsettingspage.cpp +++ b/src/qtui/settingspages/backlogsettingspage.cpp @@ -20,11 +20,10 @@ #include "backlogsettingspage.h" -#include "qtui.h" #include "backlogsettings.h" - -// For backlog requester types #include "backlogrequester.h" +#include "qtui.h" +#include "widgethelpers.h" BacklogSettingsPage::BacklogSettingsPage(QWidget *parent) : SettingsPage(tr("Interface"), tr("Backlog Fetching"), parent) @@ -36,7 +35,7 @@ BacklogSettingsPage::BacklogSettingsPage(QWidget *parent) // FIXME: global backlog requester disabled until issues ruled out ui.requesterType->removeItem(2); - connect(ui.requesterType, selectOverload(&QComboBox::currentIndexChanged), this, &BacklogSettingsPage::widgetHasChanged); + connectToWidgetChangedSignal(ui.requesterType, this, &BacklogSettingsPage::widgetHasChanged); } diff --git a/src/qtui/settingspages/networkssettingspage.cpp b/src/qtui/settingspages/networkssettingspage.cpp index 050ffc55..a886d92e 100644 --- a/src/qtui/settingspages/networkssettingspage.cpp +++ b/src/qtui/settingspages/networkssettingspage.cpp @@ -32,6 +32,7 @@ #include "presetnetworks.h" #include "settingspagedlg.h" #include "util.h" +#include "widgethelpers.h" // IRCv3 capabilities #include "irccap.h" @@ -63,8 +64,6 @@ NetworksSettingsPage::NetworksSettingsPage(QWidget *parent) ui.downServer->setIcon(icon::get("go-down")); ui.editIdentities->setIcon(icon::get("configure")); - _ignoreWidgetChanges = false; - connectedIcon = icon::get("network-connect"); connectingIcon = icon::get("network-wired"); // FIXME network-connecting disconnectedIcon = icon::get("network-disconnect"); @@ -85,38 +84,37 @@ NetworksSettingsPage::NetworksSettingsPage(QWidget *parent) currentId = 0; setEnabled(Client::isConnected()); // need a core connection! setWidgetStates(); + + connectToWidgetsChangedSignals({ + ui.identityList, + ui.performEdit, + ui.sasl, + ui.saslAccount, + ui.saslPassword, + ui.autoIdentify, + ui.autoIdentifyService, + ui.autoIdentifyPassword, + ui.useCustomEncodings, + ui.sendEncoding, + ui.recvEncoding, + ui.serverEncoding, + ui.autoReconnect, + ui.reconnectInterval, + ui.reconnectRetries, + ui.unlimitedRetries, + ui.rejoinOnReconnect, + ui.useCustomMessageRate, + ui.messageRateBurstSize, + ui.messageRateDelay, + ui.unlimitedMessageRate + }, this, &NetworksSettingsPage::widgetHasChanged); + connect(Client::instance(), &Client::coreConnectionStateChanged, this, &NetworksSettingsPage::coreConnectionStateChanged); connect(Client::instance(), &Client::networkCreated, this, &NetworksSettingsPage::clientNetworkAdded); connect(Client::instance(), &Client::networkRemoved, this, &NetworksSettingsPage::clientNetworkRemoved); connect(Client::instance(), &Client::identityCreated, this, &NetworksSettingsPage::clientIdentityAdded); connect(Client::instance(), &Client::identityRemoved, this, &NetworksSettingsPage::clientIdentityRemoved); - connect(ui.identityList, selectOverload(&QComboBox::currentIndexChanged), this, &NetworksSettingsPage::widgetHasChanged); - //connect(ui.randomServer, &QAbstractButton::clicked, this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.performEdit, &QTextEdit::textChanged, this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.sasl, &QGroupBox::clicked, this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.saslAccount, &QLineEdit::textEdited, this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.saslPassword, &QLineEdit::textEdited, this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.autoIdentify, &QGroupBox::clicked, this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.autoIdentifyService, &QLineEdit::textEdited, this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.autoIdentifyPassword, &QLineEdit::textEdited, this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.useCustomEncodings, &QGroupBox::clicked, this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.sendEncoding, selectOverload(&QComboBox::currentIndexChanged), this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.recvEncoding, selectOverload(&QComboBox::currentIndexChanged), this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.serverEncoding, selectOverload(&QComboBox::currentIndexChanged), this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.autoReconnect, &QGroupBox::clicked, this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.reconnectInterval, selectOverload(&QSpinBox::valueChanged), this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.reconnectRetries, selectOverload(&QSpinBox::valueChanged), this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.unlimitedRetries, &QAbstractButton::clicked, this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.rejoinOnReconnect, &QAbstractButton::clicked, this, &NetworksSettingsPage::widgetHasChanged); - - // Core features can change during a reconnect. Always connect these here, delaying testing for - // the core feature flag in load(). - connect(ui.useCustomMessageRate, &QGroupBox::clicked, this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.messageRateBurstSize, selectOverload(&QSpinBox::valueChanged), this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.messageRateDelay, selectOverload(&QDoubleSpinBox::valueChanged), this, &NetworksSettingsPage::widgetHasChanged); - connect(ui.unlimitedMessageRate, &QAbstractButton::clicked, this, &NetworksSettingsPage::widgetHasChanged); - foreach(IdentityId id, Client::identityIds()) { clientIdentityAdded(id); } diff --git a/src/qtui/settingspages/networkssettingspage.h b/src/qtui/settingspages/networkssettingspage.h index 965540e0..74b21d66 100644 --- a/src/qtui/settingspages/networkssettingspage.h +++ b/src/qtui/settingspages/networkssettingspage.h @@ -132,7 +132,7 @@ private: NetworkId currentId; QHash networkInfos; - bool _ignoreWidgetChanges; + bool _ignoreWidgetChanges{false}; #ifdef HAVE_SSL CertIdentity *_cid{nullptr}; #endif diff --git a/src/uisupport/CMakeLists.txt b/src/uisupport/CMakeLists.txt index 839ecc8c..3b19471d 100644 --- a/src/uisupport/CMakeLists.txt +++ b/src/uisupport/CMakeLists.txt @@ -32,6 +32,7 @@ target_sources(${TARGET} PRIVATE treeviewtouch.cpp uisettings.cpp uistyle.cpp + widgethelpers.h # needed for automoc abstractnotificationbackend.h diff --git a/src/uisupport/settingspage.cpp b/src/uisupport/settingspage.cpp index ed48f079..152428d4 100644 --- a/src/uisupport/settingspage.cpp +++ b/src/uisupport/settingspage.cpp @@ -27,8 +27,8 @@ #include #include "fontselector.h" - #include "uisettings.h" +#include "widgethelpers.h" SettingsPage::SettingsPage(QString category, QString title, QWidget *parent) : QWidget(parent), @@ -113,21 +113,8 @@ void SettingsPage::initAutoWidgets() // we need to climb the QObject tree recursively findAutoWidgets(this, &_autoWidgets); - foreach(QObject *widget, _autoWidgets) { - if (widget->inherits("ColorButton")) - connect(widget, SIGNAL(colorChanged(QColor)), SLOT(autoWidgetHasChanged())); - else if (widget->inherits("QAbstractButton") || widget->inherits("QGroupBox")) - connect(widget, SIGNAL(toggled(bool)), SLOT(autoWidgetHasChanged())); - else if (widget->inherits("QLineEdit") || widget->inherits("QTextEdit")) - connect(widget, SIGNAL(textChanged(const QString &)), SLOT(autoWidgetHasChanged())); - else if (widget->inherits("QComboBox")) - connect(widget, SIGNAL(currentIndexChanged(int)), SLOT(autoWidgetHasChanged())); - else if (widget->inherits("QSpinBox")) - connect(widget, SIGNAL(valueChanged(int)), SLOT(autoWidgetHasChanged())); - else if (widget->inherits("FontSelector")) - connect(widget, SIGNAL(fontChanged(QFont)), SLOT(autoWidgetHasChanged())); - else - qWarning() << "SettingsPage::init(): Unknown autoWidget type" << widget->metaObject()->className(); + if (!connectToWidgetsChangedSignals(_autoWidgets, this, &SettingsPage::autoWidgetHasChanged)) { + qWarning() << "SettingsPage::initAutoWidgets(): Unsupported auto widget type(s)!"; } } diff --git a/src/uisupport/widgethelpers.h b/src/uisupport/widgethelpers.h new file mode 100644 index 00000000..22f9f9ce --- /dev/null +++ b/src/uisupport/widgethelpers.h @@ -0,0 +1,136 @@ +/*************************************************************************** + * Copyright (C) 2005-2018 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "colorbutton.h" +#include "fontselector.h" +#include "funchelpers.h" +#include "util.h" + +namespace detail { + +/** + * Contains all supported widget changed signals. + */ +static const auto supportedWidgetChangedSignals = std::make_tuple( + &ColorButton::colorChanged, + &FontSelector::fontChanged, + &QAbstractButton::toggled, + selectOverload(&QComboBox::currentIndexChanged), + selectOverload(&QDoubleSpinBox::valueChanged), + &QGroupBox::toggled, + &QLineEdit::textChanged, + selectOverload(&QSpinBox::valueChanged), + &QTextEdit::textChanged +); + +/** + * Tries to find a changed signal that matches the given widget type, and connects that to the given receiver/slot. + */ +template +bool tryConnectChangedSignal(const QObject* widget, const Receiver* receiver, Slot slot, std::index_sequence) +{ + // Tries to cast the given QObject to the given signal's class type, and connects to receiver/slot if successful. + // If *alreadyConnected is true, just returns false to prevent multiple connections. + static const auto tryConnect = [](const QObject* object, auto sig, auto receiver, auto slot, bool* alreadyConnected) { + if (!*alreadyConnected) { + auto widget = qobject_cast::ClassType *>(object); + if (widget) { + *alreadyConnected = QObject::connect(widget, sig, receiver, slot); + return *alreadyConnected; + } + } + return false; + }; + + // Unpack the tuple and try to connect to each contained signal in order + bool alreadyConnected{false}; + auto results = { tryConnect(widget, std::get(supportedWidgetChangedSignals), receiver, slot, &alreadyConnected)... }; + return std::any_of(results.begin(), results.end(), [](bool result) { return result; }); +} + +} //detail + +/** + * Connects the given widget's changed signal to the given receiver/context and slot. + * + * Changed signals for supported widgets are listed in detail::supportedWidgetChangedSignals. + * + * @note The slot must not take any arguments. + * + * @param widget Pointer to the widget + * @param receiver Receiver of the signal (or context for the connection) + * @param slot Receiving slot (or functor, or whatever) + * @returns true if the widget type is supported, false otherwise + */ +template +bool connectToWidgetChangedSignal(const QObject* widget, const Receiver* receiver, Slot slot) +{ + return detail::tryConnectChangedSignal(widget, receiver, slot, + std::make_index_sequence::value>{}); +} + +/** + * Connects the given widgets' changed signals to the given receiver/context and slot. + * + * Changed signals for supported widgets are listed in detail::supportedWidgetChangedSignals. + * + * @note The slot must not take any arguments. + * + * @param widget Pointer to the widget + * @param receiver Receiver of the signal (or context for the connection) + * @param slot Receiving slot (or functor, or whatever) + * @returns true if all given widget types are supported, false otherwise + */ +template +bool connectToWidgetsChangedSignals(const Container& widgets, const Receiver* receiver, Slot slot) +{ + bool success = true; + for (auto&& widget : widgets) { + success &= detail::tryConnectChangedSignal(widget, receiver, slot, + std::make_index_sequence::value>{}); + } + return success; +} + +/** + * @overload + * Convenience overload that allows brace-initializing the list of widgets. + */ +template +bool connectToWidgetsChangedSignals(const std::initializer_list& widgets, const Receiver* receiver, Slot slot) +{ + return connectToWidgetsChangedSignals(std::vector{widgets}, receiver, slot); +} -- 2.20.1