Compare commits

...

5 commits

Author SHA1 Message Date
pajlada
dec2ec8ad6
Merge f54bdf939a into 74a385dfee 2024-10-27 13:01:07 +01:00
nerix
74a385dfee
fix: escape 7TV emote names (#5677) 2024-10-27 12:56:37 +01:00
Rasmus Karlsson
f54bdf939a
add changelog entry 2024-09-08 14:54:02 +02:00
Rasmus Karlsson
adf124c11b
feat: add SettingWidget
bit more like a builder pattern for adding settings to settings pages

i have migrated a few settings, but don't want to do all of them
2024-09-08 14:52:11 +02:00
Rasmus Karlsson
74101d40f5
fix: only require one of the keywords to match when searching for
settings
2024-09-08 14:30:16 +02:00
9 changed files with 315 additions and 81 deletions

View file

@ -62,6 +62,7 @@
- Bugfix: Fixed incorrect message being disabled in some cases upon approving or denying an automod caught message. (#5611) - Bugfix: Fixed incorrect message being disabled in some cases upon approving or denying an automod caught message. (#5611)
- Bugfix: Fixed double-click selection not working when clicking outside a message. (#5617) - Bugfix: Fixed double-click selection not working when clicking outside a message. (#5617)
- Bugfix: Fixed emotes starting with ":" not tab-completing. (#5603) - Bugfix: Fixed emotes starting with ":" not tab-completing. (#5603)
- Bugfix: Fixed 7TV emotes messing with Qt's HTML. (#5677)
- Dev: Update Windows build from Qt 6.5.0 to Qt 6.7.1. (#5420) - Dev: Update Windows build from Qt 6.5.0 to Qt 6.7.1. (#5420)
- Dev: Update vcpkg build Qt from 6.5.0 to 6.7.0, boost from 1.83.0 to 1.85.0, openssl from 3.1.3 to 3.3.0. (#5422) - Dev: Update vcpkg build Qt from 6.5.0 to 6.7.0, boost from 1.83.0 to 1.85.0, openssl from 3.1.3 to 3.3.0. (#5422)
- Dev: Unsingletonize `ISoundController`. (#5462) - Dev: Unsingletonize `ISoundController`. (#5462)
@ -71,6 +72,7 @@
- Dev: Refactor and document `Scrollbar`. (#5334, #5393) - Dev: Refactor and document `Scrollbar`. (#5334, #5393)
- Dev: Refactor `TwitchIrcServer`, making it abstracted. (#5421, #5435) - Dev: Refactor `TwitchIrcServer`, making it abstracted. (#5421, #5435)
- Dev: Reduced the amount of scale events. (#5404, #5406) - Dev: Reduced the amount of scale events. (#5404, #5406)
- Dev: Refactored settings widget creation. (#5585)
- Dev: Removed unused timegate settings. (#5361) - Dev: Removed unused timegate settings. (#5361)
- Dev: Add `Channel::addSystemMessage` helper function, allowing us to avoid the common `channel->addMessage(makeSystemMessage(...));` pattern. (#5500) - Dev: Add `Channel::addSystemMessage` helper function, allowing us to avoid the common `channel->addMessage(makeSystemMessage(...));` pattern. (#5500)
- Dev: Unsingletonize `Resources2`. (#5460) - Dev: Unsingletonize `Resources2`. (#5460)

View file

@ -61,6 +61,11 @@ chatterino--DescriptionLabel {
color: #999; color: #999;
} }
QLabel#description {
color: #999;
padding-left: 10px;
}
chatterino--NavigationLabel { chatterino--NavigationLabel {
font-family: "Segoe UI light"; font-family: "Segoe UI light";
font-size: 15px; font-size: 15px;

View file

@ -713,6 +713,8 @@ set(SOURCE_FILES
widgets/settingspages/PluginsPage.hpp widgets/settingspages/PluginsPage.hpp
widgets/settingspages/SettingsPage.cpp widgets/settingspages/SettingsPage.cpp
widgets/settingspages/SettingsPage.hpp widgets/settingspages/SettingsPage.hpp
widgets/settingspages/SettingWidget.cpp
widgets/settingspages/SettingWidget.hpp
widgets/splits/ClosedSplits.cpp widgets/splits/ClosedSplits.cpp
widgets/splits/ClosedSplits.hpp widgets/splits/ClosedSplits.hpp

View file

@ -79,7 +79,8 @@ bool isZeroWidthRecommended(const QJsonObject &emoteData)
Tooltip createTooltip(const QString &name, const QString &author, bool isGlobal) Tooltip createTooltip(const QString &name, const QString &author, bool isGlobal)
{ {
return Tooltip{QString("%1<br>%2 7TV Emote<br>By: %3") return Tooltip{QString("%1<br>%2 7TV Emote<br>By: %3")
.arg(name, isGlobal ? "Global" : "Channel", .arg(name.toHtmlEscaped(),
isGlobal ? "Global" : "Channel",
author.isEmpty() ? "<deleted>" : author)}; author.isEmpty() ? "<deleted>" : author)};
} }
@ -87,7 +88,8 @@ Tooltip createAliasedTooltip(const QString &name, const QString &baseName,
const QString &author, bool isGlobal) const QString &author, bool isGlobal)
{ {
return Tooltip{QString("%1<br>Alias of %2<br>%3 7TV Emote<br>By: %4") return Tooltip{QString("%1<br>Alias of %2<br>%3 7TV Emote<br>By: %4")
.arg(name, baseName, isGlobal ? "Global" : "Channel", .arg(name.toHtmlEscaped(), baseName.toHtmlEscaped(),
isGlobal ? "Global" : "Channel",
author.isEmpty() ? "<deleted>" : author)}; author.isEmpty() ? "<deleted>" : author)};
} }

View file

@ -20,6 +20,7 @@
#include "util/IncognitoBrowser.hpp" #include "util/IncognitoBrowser.hpp"
#include "widgets/BaseWindow.hpp" #include "widgets/BaseWindow.hpp"
#include "widgets/settingspages/GeneralPageView.hpp" #include "widgets/settingspages/GeneralPageView.hpp"
#include "widgets/settingspages/SettingWidget.hpp"
#include <magic_enum/magic_enum.hpp> #include <magic_enum/magic_enum.hpp>
#include <QDesktopServices> #include <QDesktopServices>
@ -265,10 +266,13 @@ void GeneralPage::initLayout(GeneralPageView &layout)
}, },
false, "Choose which tabs are visible in the notebook"); false, "Choose which tabs are visible in the notebook");
layout.addCheckbox( SettingWidget::inverseCheckbox("Show message reply context",
"Show message reply context", s.hideReplyContext, true, s.hideReplyContext)
"This setting will only affect how messages are shown. You can reply " ->setTooltip(
"to a message regardless of this setting."); "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, layout.addCheckbox("Show message reply button", s.showReplyButton, false,
"Show a reply button next to every chat message"); "Show a reply button next to every chat message");
@ -614,10 +618,19 @@ void GeneralPage::initLayout(GeneralPageView &layout)
"Google", "Google",
}, },
s.emojiSet); s.emojiSet);
layout.addCheckbox("Show BTTV global emotes", s.enableBTTVGlobalEmotes); SettingWidget::checkbox("Show BetterTTV global emotes",
layout.addCheckbox("Show BTTV channel emotes", s.enableBTTVChannelEmotes); s.enableBTTVGlobalEmotes)
layout.addCheckbox("Enable BTTV live emote updates (requires restart)", ->addKeywords({"bttv"})
s.enableBTTVLiveUpdates); ->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);
layout.addCheckbox("Show FFZ global emotes", s.enableFFZGlobalEmotes); layout.addCheckbox("Show FFZ global emotes", s.enableFFZGlobalEmotes);
layout.addCheckbox("Show FFZ channel emotes", s.enableFFZChannelEmotes); layout.addCheckbox("Show FFZ channel emotes", s.enableFFZChannelEmotes);
layout.addCheckbox("Show 7TV global emotes", s.enableSevenTVGlobalEmotes); layout.addCheckbox("Show 7TV global emotes", s.enableSevenTVGlobalEmotes);
@ -1063,10 +1076,11 @@ void GeneralPage::initLayout(GeneralPageView &layout)
false, false,
"Make all clickable links lowercase to deter " "Make all clickable links lowercase to deter "
"phishing attempts."); "phishing attempts.");
layout.addCheckbox( SettingWidget::checkbox("Show user's pronouns in user card", s.showPronouns)
"Show user's pronouns in user card", s.showPronouns, false, ->setDescription(
"Shows users' pronouns in their user card. " R"(Pronouns are retrieved from <a href="https://pr.alejo.io">pr.alejo.io</a> when a user card is opened.)")
"Pronouns are retrieved from alejo.io when the user card is opened."); ->addTo(layout);
layout.addCheckbox("Bold @usernames", s.boldUsernames, false, layout.addCheckbox("Bold @usernames", s.boldUsernames, false,
"Bold @mentions to make them more noticable."); "Bold @mentions to make them more noticable.");
layout.addCheckbox("Color @usernames", s.colorUsernames, false, layout.addCheckbox("Color @usernames", s.colorUsernames, false,
@ -1191,25 +1205,20 @@ void GeneralPage::initLayout(GeneralPageView &layout)
"@mention for the related thread. If the reply context is hidden, " "@mention for the related thread. If the reply context is hidden, "
"these mentions will never be stripped."); "these mentions will never be stripped.");
layout.addDropdownEnumClass<ChatSendProtocol>( SettingWidget::dropdown("Chat send protocol", s.chatSendProtocol)
"Chat send protocol", qmagicenum::enumNames<ChatSendProtocol>(), ->setTooltip("'Helix' will use Twitch's Helix API to send message. "
s.chatSendProtocol, "'IRC' will use IRC to send messages.")
"'Helix' will use Twitch's Helix API to send message. 'IRC' will use " ->addTo(layout);
"IRC to send messages.",
{});
layout.addCheckbox( SettingWidget::checkbox("Show send message button", s.showSendButton)
"Show send message button", s.showSendButton, false, ->setTooltip("Show a Send button next to each split input that can be "
"Show a Send button next to each split input that can be " "clicked to send the message")
"clicked to send the message"); ->addTo(layout);
auto *soundBackend = layout.addDropdownEnumClass<SoundBackend>( SettingWidget::dropdown("Sound backend (requires restart)", s.soundBackend)
"Sound backend (requires restart)", ->setTooltip("Change this only if you're noticing issues "
qmagicenum::enumNames<SoundBackend>(), s.soundBackend, "with sound playback on your system")
"Change this only if you're noticing issues with sound playback on " ->addTo(layout);
"your system",
{});
soundBackend->setMinimumWidth(soundBackend->minimumSizeHint().width());
layout.addStretch(); layout.addStretch();

View file

@ -6,6 +6,7 @@
#include "widgets/dialogs/ColorPickerDialog.hpp" #include "widgets/dialogs/ColorPickerDialog.hpp"
#include "widgets/helper/color/ColorButton.hpp" #include "widgets/helper/color/ColorButton.hpp"
#include "widgets/helper/Line.hpp" #include "widgets/helper/Line.hpp"
#include "widgets/settingspages/SettingWidget.hpp"
#include <QRegularExpression> #include <QRegularExpression>
#include <QScrollArea> #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); this->contentLayout_->addWidget(widget);
if (!keywords.isEmpty())
{
this->groups_.back().widgets.push_back({
.element = widget,
.keywords = keywords,
});
}
} }
void GeneralPageView::addLayout(QLayout *layout) void GeneralPageView::addLayout(QLayout *layout)
@ -376,11 +384,10 @@ bool GeneralPageView::filterElements(const QString &query)
currentSubtitleVisible = true; currentSubtitleVisible = true;
widget.element->show(); widget.element->show();
groupAny = true; groupAny = true;
break;
} }
else
{ widget.element->hide();
widget.element->hide();
}
} }
} }

View file

@ -21,6 +21,7 @@ class QScrollArea;
namespace chatterino { namespace chatterino {
class ColorButton; class ColorButton;
class SettingWidget;
class Space : public QLabel class Space : public QLabel
{ {
@ -95,7 +96,7 @@ class GeneralPageView : public QWidget
public: public:
GeneralPageView(QWidget *parent = nullptr); GeneralPageView(QWidget *parent = nullptr);
void addWidget(QWidget *widget); void addWidget(QWidget *widget, QStringList keywords = {});
void addLayout(QLayout *layout); void addLayout(QLayout *layout);
void addStretch(); void addStretch();
@ -274,50 +275,6 @@ public:
return combo; 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) void enableIf(QComboBox *widget, auto &setting, auto cb)
{ {
auto updateVisibility = [cb = std::move(cb), &setting, widget]() { auto updateVisibility = [cb = std::move(cb), &setting, widget]() {

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

View file

@ -0,0 +1,106 @@
#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 <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