feat: add "tags" or "keyword" support for settings (#5585)

this allows us to search for both "ffz" and "frankerfacez" when searching for FrankerFaceZ settings, without trying to force add both into the setting name
This commit is contained in:
pajlada
2024-11-30 13:41:49 +01:00
committed by GitHub
parent e1aded057d
commit 92c921c2a1
8 changed files with 364 additions and 110 deletions
+1
View File
@@ -90,6 +90,7 @@
- Dev: Refactor and document `Scrollbar`. (#5334, #5393)
- Dev: Refactor `TwitchIrcServer`, making it abstracted. (#5421, #5435)
- Dev: Reduced the amount of scale events. (#5404, #5406)
- Dev: Refactored settings widget creation. (#5585)
- Dev: Removed unused timegate settings. (#5361)
- Dev: Add `Channel::addSystemMessage` helper function, allowing us to avoid the common `channel->addMessage(makeSystemMessage(...));` pattern. (#5500)
- Dev: Unsingletonize `Resources2`. (#5460)
+5
View File
@@ -61,6 +61,11 @@ chatterino--DescriptionLabel {
color: #999;
}
QLabel#description {
color: #999;
padding-left: 10px;
}
chatterino--NavigationLabel {
font-family: "Segoe UI light";
font-size: 15px;
+2
View File
@@ -723,6 +723,8 @@ set(SOURCE_FILES
widgets/settingspages/PluginsPage.hpp
widgets/settingspages/SettingsPage.cpp
widgets/settingspages/SettingsPage.hpp
widgets/settingspages/SettingWidget.cpp
widgets/settingspages/SettingWidget.hpp
widgets/splits/ClosedSplits.cpp
widgets/splits/ClosedSplits.hpp
+92 -60
View File
@@ -20,6 +20,7 @@
#include "util/IncognitoBrowser.hpp"
#include "widgets/BaseWindow.hpp"
#include "widgets/settingspages/GeneralPageView.hpp"
#include "widgets/settingspages/SettingWidget.hpp"
#include <magic_enum/magic_enum.hpp>
#include <QDesktopServices>
@@ -265,10 +266,13 @@ void GeneralPage::initLayout(GeneralPageView &layout)
},
false, "Choose which tabs are visible in the notebook");
layout.addCheckbox(
"Show message reply context", s.hideReplyContext, true,
"This setting will only affect how messages are shown. You can reply "
"to a message regardless of this setting.");
SettingWidget::inverseCheckbox("Show message reply context",
s.hideReplyContext)
->setTooltip(
"This setting will only affect how messages are shown. You can "
"reply to a message regardless of this setting.")
->addTo(layout);
layout.addCheckbox("Show message reply button", s.showReplyButton, false,
"Show a reply button next to every chat message");
@@ -571,7 +575,10 @@ void GeneralPage::initLayout(GeneralPageView &layout)
s.removeSpacesBetweenEmotes, false,
"When enabled, adjacent emotes will no longer have an "
"added space seperating them.");
layout.addCheckbox("Show unlisted 7TV emotes", s.showUnlistedSevenTVEmotes);
SettingWidget::checkbox("Show unlisted 7TV emotes",
s.showUnlistedSevenTVEmotes)
->addKeywords({"seventv"})
->addTo(layout);
// TODO: Add a tooltip explaining what an unlisted 7TV emote is
// but wait until https://github.com/Chatterino/wiki/pull/255 is resolved,
// as an official description from 7TV devs is best
@@ -622,25 +629,48 @@ void GeneralPage::initLayout(GeneralPageView &layout)
"Google",
},
s.emojiSet);
layout.addCheckbox("Show BetterTTV global emotes",
s.enableBTTVGlobalEmotes);
layout.addCheckbox("Show BetterTTV channel emotes",
s.enableBTTVChannelEmotes);
layout.addCheckbox("Enable BetterTTV live emote updates (requires restart)",
s.enableBTTVLiveUpdates);
layout.addCheckbox("Show FrankerFaceZ global emotes",
s.enableFFZGlobalEmotes);
layout.addCheckbox("Show FrankerFaceZ channel emotes",
s.enableFFZChannelEmotes);
layout.addCheckbox("Show 7TV global emotes", s.enableSevenTVGlobalEmotes);
layout.addCheckbox("Show 7TV channel emotes", s.enableSevenTVChannelEmotes);
layout.addCheckbox("Enable 7TV live emote updates (requires restart)",
s.enableSevenTVEventAPI);
layout.addCheckbox("Send activity to 7TV", s.sendSevenTVActivity, false,
"When enabled, Chatterino will signal an activity to "
"7TV when you send a chat mesage. This is used for "
"badges, paints, and personal emotes. When disabled, no "
"activity is sent and others won't see your cosmetics.");
SettingWidget::checkbox("Show BetterTTV global emotes",
s.enableBTTVGlobalEmotes)
->addKeywords({"bttv"})
->addTo(layout);
SettingWidget::checkbox("Show BetterTTV channel emotes",
s.enableBTTVChannelEmotes)
->addKeywords({"bttv"})
->addTo(layout);
SettingWidget::checkbox(
"Enable BetterTTV live emote updates (requires restart)",
s.enableBTTVLiveUpdates)
->addKeywords({"bttv"})
->addTo(layout);
SettingWidget::checkbox("Show FrankerFaceZ global emotes",
s.enableFFZGlobalEmotes)
->addKeywords({"ffz"})
->addTo(layout);
SettingWidget::checkbox("Show FrankerFaceZ channel emotes",
s.enableFFZChannelEmotes)
->addKeywords({"ffz"})
->addTo(layout);
SettingWidget::checkbox("Show 7TV global emotes",
s.enableSevenTVGlobalEmotes)
->addKeywords({"seventv"})
->addTo(layout);
SettingWidget::checkbox("Show 7TV channel emotes",
s.enableSevenTVChannelEmotes)
->addKeywords({"seventv"})
->addTo(layout);
SettingWidget::checkbox("Enable 7TV live emote updates (requires restart)",
s.enableSevenTVEventAPI)
->addKeywords({"seventv"})
->addTo(layout);
SettingWidget::checkbox("Send activity to 7TV", s.sendSevenTVActivity)
->setTooltip("When enabled, Chatterino will signal an activity to 7TV "
"when you send a chat mesage. This is used for badges, "
"paints, and personal emotes. When disabled, no activity "
"is sent and others won't see your cosmetics.")
->addKeywords({"seventv"})
->addTo(layout);
layout.addTitle("Streamer Mode");
layout.addDescription(
@@ -976,15 +1006,23 @@ void GeneralPage::initLayout(GeneralPageView &layout)
"e.g. prime, bits, sub gifter");
layout.addCheckbox("Chatterino", s.showBadgesChatterino, false,
"e.g. Chatterino Supporter/Contributor/Developer");
layout.addCheckbox("FrankerFaceZ", s.showBadgesFfz, false,
"e.g. Bot, FFZ supporter, FFZ developer");
layout.addCheckbox("7TV", s.showBadgesSevenTV, false,
"Badges for 7TV admins, developers, and supporters");
SettingWidget::checkbox("FrankerFaceZ", s.showBadgesFfz)
->addKeywords({"ffz"})
->setTooltip("e.g. Bot, FrankerFaceZ supporter, FrankerFaceZ developer")
->addTo(layout);
SettingWidget::checkbox("7TV", s.showBadgesSevenTV)
->addKeywords({"seventv"})
->setTooltip("Badges for 7TV admins, developers, and supporters")
->addTo(layout);
layout.addSeperator();
layout.addCheckbox("Use custom FrankerFaceZ moderator badges",
s.useCustomFfzModeratorBadges);
layout.addCheckbox("Use custom FrankerFaceZ VIP badges",
s.useCustomFfzVipBadges);
SettingWidget::checkbox("Use custom FrankerFaceZ moderator badges",
s.useCustomFfzModeratorBadges)
->addKeywords({"ffz"})
->addTo(layout);
SettingWidget::checkbox("Use custom FrankerFaceZ VIP badges",
s.useCustomFfzVipBadges)
->addKeywords({"ffz"})
->addTo(layout);
layout.addSubtitle("Overlay");
layout.addIntInput(
@@ -1080,10 +1118,11 @@ void GeneralPage::initLayout(GeneralPageView &layout)
false,
"Make all clickable links lowercase to deter "
"phishing attempts.");
layout.addCheckbox(
"Show user's pronouns in user card", s.showPronouns, false,
"Shows users' pronouns in their user card. "
"Pronouns are retrieved from alejo.io when the user card is opened.");
SettingWidget::checkbox("Show user's pronouns in user card", s.showPronouns)
->setDescription(
R"(Pronouns are retrieved from <a href="https://pr.alejo.io">pr.alejo.io</a> when a user card is opened.)")
->addTo(layout);
layout.addCheckbox("Bold @usernames", s.boldUsernames, false,
"Bold @mentions to make them more noticable.");
layout.addCheckbox("Color @usernames", s.colorUsernames, false,
@@ -1184,13 +1223,11 @@ void GeneralPage::initLayout(GeneralPageView &layout)
layout.addIntInput("Usercard scrollback limit (requires restart)",
s.scrollbackUsercardLimit, 100, 100000, 100);
layout.addDropdownEnumClass<ShowModerationState>(
"Show blocked term automod messages",
qmagicenum::enumNames<ShowModerationState>(),
s.showBlockedTermAutomodMessages,
"Show messages that are blocked by AutoMod for containing a public "
"blocked term in the current channel.",
{});
SettingWidget::dropdown("Show blocked term automod messages",
s.showBlockedTermAutomodMessages)
->setTooltip("Show messages that are blocked by AutoMod for containing "
"a public blocked term in the current channel.")
->addTo(layout);
layout.addDropdown<int>(
"Stack timeouts", {"Stack", "Stack until timeout", "Don't stack"},
@@ -1216,25 +1253,20 @@ void GeneralPage::initLayout(GeneralPageView &layout)
"@mention for the related thread. If the reply context is hidden, "
"these mentions will never be stripped.");
layout.addDropdownEnumClass<ChatSendProtocol>(
"Chat send protocol", qmagicenum::enumNames<ChatSendProtocol>(),
s.chatSendProtocol,
"'Helix' will use Twitch's Helix API to send message. 'IRC' will use "
"IRC to send messages.",
{});
SettingWidget::dropdown("Chat send protocol", s.chatSendProtocol)
->setTooltip("'Helix' will use Twitch's Helix API to send message. "
"'IRC' will use IRC to send messages.")
->addTo(layout);
layout.addCheckbox(
"Show send message button", s.showSendButton, false,
"Show a Send button next to each split input that can be "
"clicked to send the message");
SettingWidget::checkbox("Show send message button", s.showSendButton)
->setTooltip("Show a Send button next to each split input that can be "
"clicked to send the message")
->addTo(layout);
auto *soundBackend = layout.addDropdownEnumClass<SoundBackend>(
"Sound backend (requires restart)",
qmagicenum::enumNames<SoundBackend>(), s.soundBackend,
"Change this only if you're noticing issues with sound playback on "
"your system",
{});
soundBackend->setMinimumWidth(soundBackend->minimumSizeHint().width());
SettingWidget::dropdown("Sound backend (requires restart)", s.soundBackend)
->setTooltip("Change this only if you're noticing issues "
"with sound playback on your system")
->addTo(layout);
layout.addStretch();
+11 -4
View File
@@ -6,6 +6,7 @@
#include "widgets/dialogs/ColorPickerDialog.hpp"
#include "widgets/helper/color/ColorButton.hpp"
#include "widgets/helper/Line.hpp"
#include "widgets/settingspages/SettingWidget.hpp"
#include <QRegularExpression>
#include <QScrollArea>
@@ -44,9 +45,16 @@ GeneralPageView::GeneralPageView(QWidget *parent)
});
}
void GeneralPageView::addWidget(QWidget *widget)
void GeneralPageView::addWidget(QWidget *widget, QStringList keywords)
{
this->contentLayout_->addWidget(widget);
if (!keywords.isEmpty())
{
this->groups_.back().widgets.push_back({
.element = widget,
.keywords = keywords,
});
}
}
void GeneralPageView::addLayout(QLayout *layout)
@@ -376,13 +384,12 @@ bool GeneralPageView::filterElements(const QString &query)
currentSubtitleVisible = true;
widget.element->show();
groupAny = true;
break;
}
else
{
widget.element->hide();
}
}
}
if (currentSubtitle)
{
+1 -45
View File
@@ -95,7 +95,7 @@ class GeneralPageView : public QWidget
public:
GeneralPageView(QWidget *parent = nullptr);
void addWidget(QWidget *widget);
void addWidget(QWidget *widget, QStringList keywords = {});
void addLayout(QLayout *layout);
void addStretch();
@@ -274,50 +274,6 @@ public:
return combo;
}
template <typename T, std::size_t N>
ComboBox *addDropdownEnumClass(const QString &text,
const std::array<QStringView, N> &items,
EnumStringSetting<T> &setting,
QString toolTipText,
const QString &defaultValueText)
{
auto *combo = this->addDropdown(text, {}, std::move(toolTipText));
for (const auto &item : items)
{
combo->addItem(item.toString());
}
if (!defaultValueText.isEmpty())
{
combo->setCurrentText(defaultValueText);
}
setting.connect(
[&setting, combo](const QString &value) {
auto enumValue =
qmagicenum::enumCast<T>(value, qmagicenum::CASE_INSENSITIVE)
.value_or(setting.defaultValue);
auto i = magic_enum::enum_integer(enumValue);
combo->setCurrentIndex(i);
},
this->managedConnections_);
QObject::connect(
combo, &QComboBox::currentTextChanged,
[&setting](const auto &newText) {
// The setter for EnumStringSetting does not check that this value is valid
// Instead, it's up to the getters to make sure that the setting is legic - see the enum_cast above
// You could also use the settings `getEnum` function
setting = newText;
getApp()->getWindows()->forceLayoutChannelViews();
});
return combo;
}
void enableIf(QComboBox *widget, auto &setting, auto cb)
{
auto updateVisibility = [cb = std::move(cb), &setting, widget]() {
+144
View File
@@ -0,0 +1,144 @@
#include "widgets/settingspages/SettingWidget.hpp"
#include "widgets/settingspages/GeneralPageView.hpp"
#include <QBoxLayout>
#include <QCheckBox>
#include <QLabel>
namespace {
constexpr int MAX_TOOLTIP_LINE_LENGTH = 50;
const auto MAX_TOOLTIP_LINE_LENGTH_PATTERN =
QStringLiteral(R"(.{%1}\S*\K(\s+))").arg(MAX_TOOLTIP_LINE_LENGTH);
const QRegularExpression MAX_TOOLTIP_LINE_LENGTH_REGEX(
MAX_TOOLTIP_LINE_LENGTH_PATTERN);
} // namespace
namespace chatterino {
SettingWidget::SettingWidget(const QString &mainKeyword)
: vLayout(new QVBoxLayout(this))
, hLayout(new QHBoxLayout)
{
this->vLayout->setContentsMargins(0, 0, 0, 0);
this->hLayout->setContentsMargins(0, 0, 0, 0);
this->vLayout->addLayout(hLayout);
this->keywords.append(mainKeyword);
}
SettingWidget *SettingWidget::checkbox(const QString &label,
BoolSetting &setting)
{
auto *widget = new SettingWidget(label);
auto *check = new QCheckBox(label);
widget->hLayout->addWidget(check);
// update when setting changes
setting.connect(
[check](const bool &value, auto) {
check->setChecked(value);
},
widget->managedConnections);
// update setting on toggle
QObject::connect(check, &QCheckBox::toggled, widget,
[&setting](bool state) {
setting = state;
});
widget->actionWidget = check;
widget->label = check;
return widget;
}
SettingWidget *SettingWidget::inverseCheckbox(const QString &label,
BoolSetting &setting)
{
auto *widget = new SettingWidget(label);
auto *check = new QCheckBox(label);
widget->hLayout->addWidget(check);
// update when setting changes
setting.connect(
[check](const bool &value, auto) {
check->setChecked(!value);
},
widget->managedConnections);
// update setting on toggle
QObject::connect(check, &QCheckBox::toggled, widget,
[&setting](bool state) {
setting = !state;
});
widget->actionWidget = check;
widget->label = check;
return widget;
}
SettingWidget *SettingWidget::setTooltip(QString tooltip)
{
assert(!tooltip.isEmpty());
if (tooltip.length() > MAX_TOOLTIP_LINE_LENGTH)
{
// match MAX_TOOLTIP_LINE_LENGTH characters, any remaining
// non-space, and then capture the following space for
// replacement with newline
tooltip.replace(MAX_TOOLTIP_LINE_LENGTH_REGEX, "\n");
}
if (this->label != nullptr)
{
this->label->setToolTip(tooltip);
}
if (this->actionWidget != nullptr)
{
this->actionWidget->setToolTip(tooltip);
}
this->keywords.append(tooltip);
return this;
}
SettingWidget *SettingWidget::setDescription(const QString &text)
{
auto *lbl = new QLabel(text);
lbl->setTextInteractionFlags(Qt::TextBrowserInteraction |
Qt::LinksAccessibleByKeyboard);
lbl->setOpenExternalLinks(true);
lbl->setWordWrap(true);
lbl->setObjectName("description");
this->vLayout->insertWidget(0, lbl);
this->keywords.append(text);
return this;
}
SettingWidget *SettingWidget::addKeywords(const QStringList &newKeywords)
{
this->keywords.append(newKeywords);
return this;
}
void SettingWidget::addTo(GeneralPageView &view)
{
view.addWidget(this, this->keywords);
}
} // namespace chatterino
+107
View File
@@ -0,0 +1,107 @@
#pragma once
#include "common/ChatterinoSetting.hpp"
#include "util/QMagicEnum.hpp"
#include "widgets/settingspages/GeneralPageView.hpp"
#include <pajlada/signals/signalholder.hpp>
#include <QBoxLayout>
#include <QComboBox>
#include <QLabel>
#include <QObject>
#include <QString>
#include <QStringBuilder>
#include <QStringList>
#include <QtContainerFwd>
#include <QWidget>
namespace chatterino {
class GeneralPageView;
class SettingWidget : QWidget
{
Q_OBJECT
explicit SettingWidget(const QString &mainKeyword);
public:
~SettingWidget() override = default;
SettingWidget &operator=(const SettingWidget &) = delete;
SettingWidget &operator=(SettingWidget &&) = delete;
SettingWidget(const SettingWidget &other) = delete;
SettingWidget(SettingWidget &&other) = delete;
static SettingWidget *checkbox(const QString &label, BoolSetting &setting);
static SettingWidget *inverseCheckbox(const QString &label,
BoolSetting &setting);
template <typename T>
static SettingWidget *dropdown(const QString &label,
EnumStringSetting<T> &setting)
{
auto *widget = new SettingWidget(label);
auto *lbl = new QLabel(label % ":");
auto *combo = new ComboBox;
combo->setFocusPolicy(Qt::StrongFocus);
for (const auto &item : qmagicenum::enumNames<T>())
{
combo->addItem(item.toString());
}
// TODO: this can probably use some other size hint/size strategy
combo->setMinimumWidth(combo->minimumSizeHint().width());
widget->actionWidget = combo;
widget->label = lbl;
widget->hLayout->addWidget(lbl);
widget->hLayout->addStretch(1);
widget->hLayout->addWidget(combo);
setting.connect(
[&setting, combo](const QString &value) {
auto enumValue =
qmagicenum::enumCast<T>(value, qmagicenum::CASE_INSENSITIVE)
.value_or(setting.defaultValue);
auto i = magic_enum::enum_integer(enumValue);
combo->setCurrentIndex(i);
},
widget->managedConnections);
QObject::connect(
combo, &QComboBox::currentTextChanged,
[&setting](const auto &newText) {
// The setter for EnumStringSetting does not check that this value is valid
// Instead, it's up to the getters to make sure that the setting is legic - see the enum_cast above
// You could also use the settings `getEnum` function
setting = newText;
});
return widget;
}
SettingWidget *setTooltip(QString tooltip);
SettingWidget *setDescription(const QString &text);
/// Add extra keywords to the widget
///
/// All text from the tooltip, description, and label are already keywords
SettingWidget *addKeywords(const QStringList &newKeywords);
void addTo(GeneralPageView &view);
private:
QWidget *label = nullptr;
QWidget *actionWidget = nullptr;
QVBoxLayout *vLayout;
QHBoxLayout *hLayout;
pajlada::Signals::SignalHolder managedConnections;
QStringList keywords;
};
} // namespace chatterino