refactor: use some more SettingWidget & restyle external tools page (#6023)

This commit is contained in:
pajlada
2025-03-03 16:33:13 +01:00
committed by GitHub
parent cd8247f921
commit 34429671ba
10 changed files with 545 additions and 265 deletions
+1
View File
@@ -51,6 +51,7 @@
- Dev: Fixed duplicate CMake configure in clean builds. (#5940)
- Dev: BTTV emotes are now loaded as WEBP. (#5957)
- Dev: Reduced time we wait for PubSub connections to cleanly exit from 1s to 100ms. (#6019)
- Dev: Refactored some settings styles/APIs. (#6023)
- Dev: Added snapshot tests for EventSub. (#5965)
- Dev: Removed dead code and some MSVC warnings. (#6024)
+36 -2
View File
@@ -76,6 +76,15 @@ enum class ShowModerationState : int {
Never = 1,
};
enum class StreamLinkPreferredQuality : std::uint8_t {
Choose,
Source,
High,
Medium,
Low,
AudioOnly,
};
enum StreamerModeSetting {
Disabled = 0,
Enabled = 1,
@@ -538,8 +547,10 @@ public:
BoolSetting streamlinkUseCustomPath = {"/external/streamlink/useCustomPath",
false};
QStringSetting streamlinkPath = {"/external/streamlink/customPath", ""};
QStringSetting preferredQuality = {"/external/streamlink/quality",
"Choose"};
EnumStringSetting<StreamLinkPreferredQuality> preferredQuality = {
"/external/streamlink/quality",
StreamLinkPreferredQuality::Choose,
};
QStringSetting streamlinkOpts = {"/external/streamlink/options", ""};
// Custom URI Scheme
@@ -700,3 +711,26 @@ private:
Settings *getSettings();
} // namespace chatterino
template <>
constexpr magic_enum::customize::customize_t
magic_enum::customize::enum_name<chatterino::StreamLinkPreferredQuality>(
chatterino::StreamLinkPreferredQuality value) noexcept
{
using chatterino::StreamLinkPreferredQuality;
switch (value)
{
case chatterino::StreamLinkPreferredQuality::Choose:
case chatterino::StreamLinkPreferredQuality::Source:
case chatterino::StreamLinkPreferredQuality::High:
case chatterino::StreamLinkPreferredQuality::Medium:
case chatterino::StreamLinkPreferredQuality::Low:
return default_tag;
case chatterino::StreamLinkPreferredQuality::AudioOnly:
return "Audio only";
default:
return default_tag;
}
}
+6 -7
View File
@@ -199,10 +199,9 @@ void openStreamlinkForChannel(const QString &channel)
QString channelURL = "twitch.tv/" + channel;
QString preferredQuality = getSettings()->preferredQuality.getValue();
preferredQuality = preferredQuality.toLower();
auto preferredQuality = getSettings()->preferredQuality.getEnum();
if (preferredQuality == "choose")
if (preferredQuality == StreamLinkPreferredQuality::Choose)
{
getStreamQualities(channelURL, [=](QStringList qualityOptions) {
QualityPopup::showDialog(channelURL, qualityOptions);
@@ -218,22 +217,22 @@ void openStreamlinkForChannel(const QString &channel)
// Streamlink qualities to exclude
QString exclude;
if (preferredQuality == "high")
if (preferredQuality == StreamLinkPreferredQuality::High)
{
exclude = ">720p30";
quality = "high,best";
}
else if (preferredQuality == "medium")
else if (preferredQuality == StreamLinkPreferredQuality::Medium)
{
exclude = ">540p30";
quality = "medium,best";
}
else if (preferredQuality == "low")
else if (preferredQuality == StreamLinkPreferredQuality::Low)
{
exclude = ">360p30";
quality = "low,best";
}
else if (preferredQuality == "audio only")
else if (preferredQuality == StreamLinkPreferredQuality::AudioOnly)
{
quality = "audio,audio_only";
}
+89 -106
View File
@@ -2,9 +2,8 @@
#include "singletons/Settings.hpp"
#include "util/Helpers.hpp"
#include "util/LayoutCreator.hpp"
#include "util/RemoveScrollAreaBackground.hpp"
#include "util/StreamLink.hpp"
#include "widgets/settingspages/SettingWidget.hpp"
#include <QFormLayout>
#include <QGroupBox>
@@ -13,31 +12,47 @@
namespace chatterino {
inline const QStringList STREAMLINK_QUALITY = {
"Choose", "Source", "High", "Medium", "Low", "Audio only"};
"Choose", "Source", "High", "Medium", "Low", "Audio only",
};
ExternalToolsPage::ExternalToolsPage()
: view(GeneralPageView::withoutNavigation(this))
{
LayoutCreator<ExternalToolsPage> layoutCreator(this);
auto *y = new QVBoxLayout;
auto *x = new QHBoxLayout;
x->addWidget(this->view);
auto *z = new QFrame;
z->setLayout(x);
y->addWidget(z);
this->setLayout(y);
auto scroll = layoutCreator.emplace<QScrollArea>();
auto widget = scroll.emplaceScrollAreaWidget();
removeScrollAreaBackground(scroll.getElement(), widget.getElement());
this->initLayout(*view);
}
auto layout = widget.setLayoutType<QVBoxLayout>();
bool ExternalToolsPage::filterElements(const QString &query)
{
if (this->view)
{
return this->view->filterElements(query) || query.isEmpty();
}
return false;
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
void ExternalToolsPage::initLayout(GeneralPageView &layout)
{
auto &s = *getSettings();
{
auto group = layout.emplace<QGroupBox>("Streamlink");
auto groupLayout = group.setLayoutType<QFormLayout>();
auto *description = new QLabel(
"Streamlink is a command-line utility that pipes video streams "
"from various "
"services into a video player, such as VLC. Make sure to edit the "
"configuration file before you use it!");
description->setWordWrap(true);
description->setStyleSheet("color: #bbb");
auto *links = new QLabel(
auto *form = new QFormLayout;
layout.addTitle("Streamlink");
layout.addDescription("Streamlink is a command-line utility that pipes "
"video streams from "
"various services into a video player, such as "
"VLC. Make sure to edit "
"the configuration file before you use it!");
layout.addDescription(
formatRichNamedLink("https://streamlink.github.io/", "Website") +
" " +
formatRichNamedLink(
@@ -46,115 +61,83 @@ ExternalToolsPage::ExternalToolsPage()
" " +
formatRichNamedLink("https://streamlink.github.io/cli.html#twitch",
"Documentation"));
links->setTextFormat(Qt::RichText);
links->setTextInteractionFlags(Qt::TextBrowserInteraction |
Qt::LinksAccessibleByKeyboard |
Qt::LinksAccessibleByMouse);
links->setOpenExternalLinks(true);
groupLayout->setWidget(0, QFormLayout::SpanningRole, description);
groupLayout->setWidget(1, QFormLayout::SpanningRole, links);
SettingWidget::checkbox("Use custom path (Enable if using non-standard "
"streamlink installation path)",
s.streamlinkUseCustomPath)
->addTo(layout);
auto *customPathCb =
this->createCheckBox("Use custom path (Enable if using "
"non-standard streamlink installation path)",
getSettings()->streamlinkUseCustomPath);
groupLayout->setWidget(2, QFormLayout::SpanningRole, customPathCb);
auto *note = new QLabel(
layout.addDescription(
QStringLiteral(
"Chatterino expects the executable to be called \"%1\".")
.arg(STREAMLINK_BINARY_NAME));
note->setWordWrap(true);
note->setStyleSheet("color: #bbb");
groupLayout->setWidget(3, QFormLayout::SpanningRole, note);
auto *customPath = this->createLineEdit(getSettings()->streamlinkPath);
customPath->setPlaceholderText(
"Path to folder where Streamlink executable can be found");
groupLayout->addRow("Custom streamlink path:", customPath);
groupLayout->addRow(
"Preferred quality:",
this->createComboBox(STREAMLINK_QUALITY,
getSettings()->preferredQuality));
groupLayout->addRow(
"Additional options:",
this->createLineEdit(getSettings()->streamlinkOpts));
layout.addLayout(form);
getSettings()->streamlinkUseCustomPath.connect(
[=](const auto &value, auto) {
customPath->setEnabled(value);
},
this->managedConnections_);
SettingWidget::lineEdit(
"Custom streamlink path", s.streamlinkPath,
"Path to folder where Streamlink executable can be found")
->conditionallyEnabledBy(s.streamlinkUseCustomPath)
->addTo(layout, form);
SettingWidget::dropdown("Preferred quality", s.preferredQuality)
->addTo(layout, form);
SettingWidget::lineEdit("Additional options", s.streamlinkOpts, "")
->addTo(layout, form);
}
layout->addSpacing(16);
{
auto group = layout.emplace<QGroupBox>("Custom stream player");
auto groupLayout = group.setLayoutType<QFormLayout>();
layout.addTitle("Custom stream player");
layout.addDescription(
"You can open Twitch streams directly in any video player that has "
"built-in Twitch support and has own URI Scheme.\nE.g.: IINA for "
"macOS and Potplayer (with extension) for Windows.\n\nWith this "
"value set, you will get the option to \"Open in custom player\" "
"when right-clicking a channel header.");
auto *description = new QLabel(
"You can open Twitch streams directly in any video player that "
"has built-in Twitch support and has own URI Scheme.\nE.g.: "
"IINA for macOS and Potplayer (with extension) for "
"Windows.\n\nWith this value set, you will get the option to "
"\"Open in custom player\" when "
"right-clicking a channel header.");
description->setWordWrap(true);
description->setStyleSheet("color: #bbb");
groupLayout->setWidget(0, QFormLayout::SpanningRole, description);
auto *lineEdit = this->createLineEdit(getSettings()->customURIScheme);
lineEdit->setPlaceholderText("custom-player-scheme://");
groupLayout->addRow("Custom stream player URI Scheme:", lineEdit);
SettingWidget::lineEdit("Custom stream player URI Scheme",
s.customURIScheme, "custom-player-scheme://")
->addTo(layout);
}
layout->addSpacing(16);
{
auto group = layout.emplace<QGroupBox>("Image Uploader");
auto groupLayout = group.setLayoutType<QFormLayout>();
auto *form = new QFormLayout;
layout.addTitle("Image Uploader");
auto *description = new QLabel(
"You can set custom host for uploading images, like "
"imgur.com or s-ul.eu.<br>Check " +
layout.addDescription(
"You can set custom host for uploading images, like imgur.com or "
"s-ul.eu.<br>Check " +
formatRichNamedLink("https://chatterino.com/help/image-uploader",
"this guide") +
" for help.");
description->setWordWrap(true);
description->setStyleSheet("color: #bbb");
description->setTextFormat(Qt::RichText);
description->setTextInteractionFlags(Qt::TextBrowserInteraction |
Qt::LinksAccessibleByKeyboard |
Qt::LinksAccessibleByMouse);
description->setOpenExternalLinks(true);
groupLayout->setWidget(0, QFormLayout::SpanningRole, description);
SettingWidget::checkbox("Enable image uploader", s.imageUploaderEnabled)
->addTo(layout);
groupLayout->addRow(this->createCheckBox(
"Enable image uploader", getSettings()->imageUploaderEnabled));
groupLayout->addRow(
this->createCheckBox("Ask for confirmation when uploading an image",
getSettings()->askOnImageUpload));
SettingWidget::checkbox("Ask for confirmation when uploading an image",
s.askOnImageUpload)
->addTo(layout);
groupLayout->addRow(
"Request URL: ",
this->createLineEdit(getSettings()->imageUploaderUrl));
groupLayout->addRow(
"Form field: ",
this->createLineEdit(getSettings()->imageUploaderFormField));
groupLayout->addRow(
"Extra Headers: ",
this->createLineEdit(getSettings()->imageUploaderHeaders));
groupLayout->addRow(
"Image link: ",
this->createLineEdit(getSettings()->imageUploaderLink));
groupLayout->addRow(
"Deletion link: ",
this->createLineEdit(getSettings()->imageUploaderDeletionLink));
layout.addLayout(form);
SettingWidget::lineEdit("Request URL", s.imageUploaderUrl)
->addTo(layout, form);
SettingWidget::lineEdit("Form field", s.imageUploaderFormField)
->addTo(layout, form);
SettingWidget::lineEdit("Extra Headers", s.imageUploaderHeaders)
->addTo(layout, form);
SettingWidget::lineEdit("Image link", s.imageUploaderLink)
->addTo(layout, form);
SettingWidget::lineEdit("Deletion link", s.imageUploaderDeletionLink)
->addTo(layout, form);
}
layout->addStretch(1);
layout.addStretch();
}
} // namespace chatterino
@@ -1,5 +1,6 @@
#pragma once
#include "widgets/settingspages/GeneralPageView.hpp"
#include "widgets/settingspages/SettingsPage.hpp"
namespace chatterino {
@@ -8,6 +9,13 @@ class ExternalToolsPage : public SettingsPage
{
public:
ExternalToolsPage();
bool filterElements(const QString &query) override;
private:
void initLayout(GeneralPageView &layout);
GeneralPageView *view;
};
} // namespace chatterino
+74 -46
View File
@@ -95,7 +95,7 @@ GeneralPage::GeneralPage()
{
auto *y = new QVBoxLayout;
auto *x = new QHBoxLayout;
auto *view = new GeneralPageView;
auto *view = GeneralPageView::withNavigation(this);
this->view_ = view;
x->addWidget(view);
auto *z = new QFrame;
@@ -275,8 +275,9 @@ void GeneralPage::initLayout(GeneralPageView &layout)
"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");
SettingWidget::checkbox("Show message reply button", s.showReplyButton)
->setTooltip("Show a reply button next to every chat message")
->addTo(layout);
auto removeTabSeq = getApp()->getHotkeys()->getDisplaySequence(
HotkeyCategory::Window, "removeTab");
@@ -309,9 +310,13 @@ void GeneralPage::initLayout(GeneralPageView &layout)
.arg(settingsSeq.toString(
QKeySequence::SequenceFormat::NativeText));
}
layout.addCheckbox("Show preferences button" + shortcut,
s.hidePreferencesButton, true);
layout.addCheckbox("Show user button", s.hideUserButton, true);
SettingWidget::inverseCheckbox("Show preferences button" + shortcut,
s.hidePreferencesButton)
->addTo(layout);
SettingWidget::inverseCheckbox("Show user button", s.hideUserButton)
->addTo(layout);
}
layout.addCheckbox("Mark tabs with live channels", s.showTabLive, false,
"Shows a red dot in the top right corner of a tab to "
@@ -464,24 +469,34 @@ void GeneralPage::initLayout(GeneralPageView &layout)
},
false);
layout.addCheckbox(
"Hide scrollbar thumb", s.hideScrollbarThumb, false,
"Hiding the scrollbar thumb (the handle you can drag) will disable "
"all mouse interaction in the scrollbar.");
SettingWidget::checkbox("Hide scrollbar thumb", s.hideScrollbarThumb)
->setTooltip("Hiding the scrollbar thumb (the handle you can drag) "
"will disable all mouse interaction in the scrollbar.")
->addKeywords({"scroll bar"})
->addTo(layout);
layout.addCheckbox("Hide scrollbar highlights", s.hideScrollbarHighlights,
false);
SettingWidget::checkbox("Hide scrollbar highlights",
s.hideScrollbarHighlights)
->addKeywords({"scroll bar"})
->addTo(layout);
layout.addTitle("Messages");
layout.addCheckbox(
"Separate with lines", s.separateMessages, false,
"Adds a line between each message to help better tell them apart.");
layout.addCheckbox("Alternate background color", s.alternateMessages, false,
"Slightly change the background behind every other "
"message to help better tell them apart.");
layout.addCheckbox("Hide deleted messages", s.hideModerated, false,
"When enabled, messages deleted by moderators will "
"be hidden.");
SettingWidget::checkbox("Separate with lines", s.separateMessages)
->setTooltip(
"Adds a line between each message to help better tell them apart.")
->addTo(layout);
SettingWidget::checkbox("Alternate background color", s.alternateMessages)
->setTooltip("Slightly change the background behind every other "
"message to help better tell them apart.")
->addTo(layout);
SettingWidget::checkbox("Hide deleted messages", s.hideModerated)
->setTooltip(
"When enabled, messages deleted by moderators will be hidden.")
->addTo(layout);
layout.addDropdown<QString>(
"Timestamp format",
{"Disable", "h:mm", "hh:mm", "h:mm a", "hh:mm a", "h:mm:ss", "hh:mm:ss",
@@ -539,9 +554,8 @@ void GeneralPage::initLayout(GeneralPageView &layout)
}
},
false);
layout.addColorButton("Line color",
QColor(getSettings()->lastMessageColor.getValue()),
getSettings()->lastMessageColor);
SettingWidget::colorButton("Line color", s.lastMessageColor)->addTo(layout);
layout.addTitle("Emotes");
layout.addCheckbox("Enable", s.enableEmoteImages);
@@ -1047,11 +1061,20 @@ void GeneralPage::initLayout(GeneralPageView &layout)
true,
"The final scale of the messages in the overlay is computed by "
"multiplying this zoom factor with the global zoom level.");
layout.addIntInput(
"Background opacity (0-255)", s.overlayBackgroundOpacity, 0, 255, 1,
"Controls the opacity of the (possibly alternating) background behind "
"messages. The color is set through the current theme. 255 corresponds "
"to a fully opaque background.");
SettingWidget::intInput("Background opacity (0-255)",
s.overlayBackgroundOpacity,
{
.min = 0,
.max = 255,
.singleStep = 1,
})
->setTooltip(
"Controls the opacity of the (possibly alternating) background "
"behind messages. The color is set through the current theme. 255 "
"corresponds to a fully opaque background.")
->addTo(layout);
layout.addCheckbox("Enable Shadow", s.enableOverlayShadow, false,
"Enables a drop shadow on the overlay. This will use "
"more processing power.");
@@ -1059,9 +1082,10 @@ void GeneralPage::initLayout(GeneralPageView &layout)
1,
"Controls the opacity of the added drop shadow. 255 "
"corresponds to a fully opaque shadow.");
layout.addColorButton("Shadow color",
QColor(getSettings()->overlayShadowColor.getValue()),
getSettings()->overlayShadowColor);
SettingWidget::colorButton("Shadow color", s.overlayShadowColor)
->addTo(layout);
layout
.addIntInput("Shadow radius", s.overlayShadowRadius, 0, 40, 1,
"Controls how far the shadow is spread (the blur "
@@ -1088,15 +1112,14 @@ void GeneralPage::initLayout(GeneralPageView &layout)
s.openLinksIncognito);
}
layout.addCustomCheckbox(
SettingWidget::customCheckbox(
"Restart on crash (requires restart)",
[] {
return getApp()->getCrashHandler()->shouldRecover();
},
getApp()->getCrashHandler()->shouldRecover(),
[](bool on) {
return getApp()->getCrashHandler()->saveShouldRecover(on);
},
"When possible, restart Chatterino if the program crashes");
getApp()->getCrashHandler()->saveShouldRecover(on);
})
->setTooltip("When possible, restart Chatterino if the program crashes")
->addTo(layout);
#if defined(Q_OS_LINUX) && !defined(NO_QTKEYCHAIN)
if (!getApp()->getPaths().isPortable())
@@ -1107,13 +1130,18 @@ void GeneralPage::initLayout(GeneralPageView &layout)
}
#endif
layout.addCheckbox(
"Show moderation messages", s.hideModerationActions, true,
"Show messages for timeouts, bans, and other moderator actions.");
layout.addCheckbox("Show deletions of single messages",
s.hideDeletionActions, true,
"Show when a single message is deleted.\ne.g. A message "
"from TreuKS was deleted: abc");
SettingWidget::inverseCheckbox("Show moderation messages",
s.hideModerationActions)
->setTooltip(
"Show messages for timeouts, bans, and other moderator actions.")
->addTo(layout);
SettingWidget::inverseCheckbox("Show deletions of single messages",
s.hideDeletionActions)
->setTooltip("Show when a single message is deleted.\ne.g. A message "
"from TreuKS was deleted: abc")
->addTo(layout);
layout.addCheckbox(
"Colorize users without color set (gray names)", s.colorizeNicknames,
false,
+112 -89
View File
@@ -1,10 +1,9 @@
#include "widgets/settingspages/GeneralPageView.hpp"
#include "Application.hpp"
#include "common/QLogging.hpp"
#include "util/LayoutHelper.hpp"
#include "util/RapidJsonSerializeQString.hpp"
#include "widgets/dialogs/ColorPickerDialog.hpp"
#include "widgets/helper/color/ColorButton.hpp"
#include "widgets/helper/Line.hpp"
#include "widgets/settingspages/SettingWidget.hpp"
@@ -25,30 +24,55 @@ namespace chatterino {
GeneralPageView::GeneralPageView(QWidget *parent)
: QWidget(parent)
, contentScrollArea_(new QScrollArea)
, contentLayout_(new QVBoxLayout)
{
auto *scrollArea = this->contentScrollArea_ =
makeScrollArea(this->contentLayout_ = new QVBoxLayout);
scrollArea->setObjectName("generalSettingsScrollContent");
auto *navigation =
wrapLayout(this->navigationLayout_ = makeLayout<QVBoxLayout>({}));
navigation->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum);
this->navigationLayout_->setAlignment(Qt::AlignTop);
this->navigationLayout_->addSpacing(6);
this->setLayout(makeLayout<QHBoxLayout>(
{scrollArea, new QSpacerItem(16, 1), navigation}));
QObject::connect(scrollArea->verticalScrollBar(), &QScrollBar::valueChanged,
this, [this] {
this->updateNavigationHighlighting();
});
auto *contentWidget = new QWidget;
contentWidget->setLayout(this->contentLayout_);
this->contentScrollArea_->setWidget(contentWidget);
this->contentScrollArea_->setObjectName("generalSettingsScrollContent");
this->contentScrollArea_->setWidgetResizable(true);
}
void GeneralPageView::addWidget(QWidget *widget, QStringList keywords)
GeneralPageView *GeneralPageView::withoutNavigation(QWidget *parent)
{
auto *view = new GeneralPageView(parent);
view->setLayout(makeLayout<QHBoxLayout>({
view->contentScrollArea_,
}));
return view;
}
GeneralPageView *GeneralPageView::withNavigation(QWidget *parent)
{
auto *view = new GeneralPageView(parent);
auto *navigation =
wrapLayout(view->navigationLayout_ = makeLayout<QVBoxLayout>({}));
navigation->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum);
view->navigationLayout_->setAlignment(Qt::AlignTop);
view->navigationLayout_->addSpacing(6);
view->setLayout(makeLayout<QHBoxLayout>({
view->contentScrollArea_,
new QSpacerItem(16, 1),
navigation,
}));
QObject::connect(view->contentScrollArea_->verticalScrollBar(),
&QScrollBar::valueChanged, view, [view] {
view->updateNavigationHighlighting();
});
return view;
}
void GeneralPageView::addWidget(QWidget *widget, const QStringList &keywords)
{
this->contentLayout_->addWidget(widget);
if (!keywords.isEmpty())
if (!this->groups_.empty())
{
this->groups_.back().widgets.push_back({
.element = widget,
@@ -57,6 +81,25 @@ void GeneralPageView::addWidget(QWidget *widget, QStringList keywords)
}
}
void GeneralPageView::registerWidget(QWidget *widget,
const QStringList &keywords,
QWidget *parentElement)
{
if (!this->groups_.empty())
{
this->groups_.back().widgets.push_back({
.element = widget,
.keywords = keywords,
.parentElement = parentElement,
});
}
}
void GeneralPageView::pushWidget(QWidget *widget)
{
this->contentLayout_->addWidget(widget);
}
void GeneralPageView::addLayout(QLayout *layout)
{
this->contentLayout_->addLayout(layout);
@@ -79,14 +122,21 @@ TitleLabel *GeneralPageView::addTitle(const QString &title)
auto *label = new TitleLabel(title + ":");
this->addWidget(label);
// navigation item
auto *navLabel = new NavigationLabel(title);
navLabel->setCursor(Qt::PointingHandCursor);
this->navigationLayout_->addWidget(navLabel);
NavigationLabel *navLabel = nullptr;
QObject::connect(navLabel, &NavigationLabel::leftMouseUp, label, [=, this] {
this->contentScrollArea_->verticalScrollBar()->setValue(label->y());
});
// navigation item
if (this->navigationLayout_ != nullptr)
{
navLabel = new NavigationLabel(title);
navLabel->setCursor(Qt::PointingHandCursor);
this->navigationLayout_->addWidget(navLabel);
QObject::connect(
navLabel, &NavigationLabel::leftMouseUp, label, [this, label] {
this->contentScrollArea_->verticalScrollBar()->setValue(
label->y());
});
}
// groups
this->groups_.push_back(Group{title, label, navLabel, nullptr, {}});
@@ -113,6 +163,12 @@ QCheckBox *GeneralPageView::addCheckbox(const QString &text,
BoolSetting &setting, bool inverse,
QString toolTipText)
{
if (inverse)
{
qCWarning(chatterinoWidget)
<< "use SettingWidget::inverseCheckbox instead";
}
auto *check = new QCheckBox(text);
this->addToolTip(*check, toolTipText);
@@ -137,28 +193,6 @@ QCheckBox *GeneralPageView::addCheckbox(const QString &text,
return check;
}
QCheckBox *GeneralPageView::addCustomCheckbox(const QString &text,
const std::function<bool()> &load,
std::function<void(bool)> save,
const QString &toolTipText)
{
auto *check = new QCheckBox(text);
this->addToolTip(*check, toolTipText);
check->setChecked(load());
QObject::connect(check, &QCheckBox::toggled, this,
[save = std::move(save)](bool state) {
save(state);
});
this->addWidget(check);
this->groups_.back().widgets.push_back({check, {text}});
return check;
}
ComboBox *GeneralPageView::addDropdown(const QString &text,
const QStringList &list,
QString toolTipText)
@@ -211,43 +245,6 @@ ComboBox *GeneralPageView::addDropdown(
return combo;
}
ColorButton *GeneralPageView::addColorButton(
const QString &text, const QColor &color,
pajlada::Settings::Setting<QString> &setting, QString toolTipText)
{
auto *colorButton = new ColorButton(color);
auto *layout = new QHBoxLayout();
auto *label = new QLabel(text + ":");
layout->addWidget(label);
layout->addStretch(1);
layout->addWidget(colorButton);
this->addToolTip(*label, toolTipText);
this->addLayout(layout);
QObject::connect(
colorButton, &ColorButton::clicked, [this, &setting, colorButton]() {
auto *dialog = new ColorPickerDialog(QColor(setting), this);
// colorButton & setting are never deleted and the signal is deleted
// once the dialog is closed
QObject::connect(dialog, &ColorPickerDialog::colorConfirmed, this,
[&setting, colorButton](auto selected) {
if (selected.isValid())
{
setting = selected.name(QColor::HexArgb);
colorButton->setColor(selected);
}
});
dialog->show();
});
this->groups_.back().widgets.push_back({label, {text}});
this->groups_.back().widgets.push_back({colorButton, {text}});
return colorButton;
}
QSpinBox *GeneralPageView::addIntInput(const QString &text, IntSetting &setting,
int min, int max, int step,
QString toolTipText)
@@ -289,6 +286,9 @@ QSpinBox *GeneralPageView::addIntInput(const QString &text, IntSetting &setting,
void GeneralPageView::addNavigationSpacing()
{
assert(this->navigationLayout_ != nullptr &&
"addNavigationSpacing used without navigation");
this->navigationLayout_->addSpacing(24);
}
@@ -341,10 +341,17 @@ bool GeneralPageView::filterElements(const QString &query)
for (auto &&widget : group.widgets)
{
widget.element->show();
if (widget.parentElement != nullptr)
{
widget.parentElement->show();
}
}
group.title->show();
group.navigationLink->show();
if (group.navigationLink != nullptr)
{
group.navigationLink->show();
}
any = true;
}
// check if any match
@@ -383,11 +390,19 @@ bool GeneralPageView::filterElements(const QString &query)
{
currentSubtitleVisible = true;
widget.element->show();
if (widget.parentElement != nullptr)
{
widget.parentElement->show();
}
groupAny = true;
break;
}
widget.element->hide();
if (widget.parentElement != nullptr)
{
widget.parentElement->hide();
}
}
}
@@ -402,7 +417,10 @@ bool GeneralPageView::filterElements(const QString &query)
}
group.title->setVisible(groupAny);
group.navigationLink->setVisible(groupAny);
if (group.navigationLink != nullptr)
{
group.navigationLink->setVisible(groupAny);
}
any |= groupAny;
}
}
@@ -412,6 +430,11 @@ bool GeneralPageView::filterElements(const QString &query)
void GeneralPageView::updateNavigationHighlighting()
{
if (this->navigationLayout_ == nullptr)
{
return;
}
auto scrollY = this->contentScrollArea_->verticalScrollBar()->value();
auto first = true;
+23 -14
View File
@@ -91,11 +91,22 @@ struct DropdownArgs {
class GeneralPageView : public QWidget
{
Q_OBJECT
public:
GeneralPageView(QWidget *parent = nullptr);
void addWidget(QWidget *widget, QStringList keywords = {});
public:
static GeneralPageView *withNavigation(QWidget *parent);
static GeneralPageView *withoutNavigation(QWidget *parent);
void addWidget(QWidget *widget, const QStringList &keywords = {});
/// Register the widget with the given keywords.
/// This assumes that the widget is being held by a layout that has been added previously
void registerWidget(QWidget *widget, const QStringList &keywords,
QWidget *parentElement);
/// Pushes the widget into the current layout
void pushWidget(QWidget *widget);
void addLayout(QLayout *layout);
void addStretch();
@@ -104,19 +115,12 @@ public:
/// @param inverse Inverses true to false and vice versa
QCheckBox *addCheckbox(const QString &text, BoolSetting &setting,
bool inverse = false, QString toolTipText = {});
QCheckBox *addCustomCheckbox(const QString &text,
const std::function<bool()> &load,
std::function<void(bool)> save,
const QString &toolTipText = {});
ComboBox *addDropdown(const QString &text, const QStringList &items,
QString toolTipText = {});
ComboBox *addDropdown(const QString &text, const QStringList &items,
pajlada::Settings::Setting<QString> &setting,
bool editable = false, QString toolTipText = {});
ColorButton *addColorButton(const QString &text, const QColor &color,
pajlada::Settings::Setting<QString> &setting,
QString toolTipText = {});
QSpinBox *addIntInput(const QString &text, IntSetting &setting, int min,
int max, int step, QString toolTipText = {});
void addNavigationSpacing();
@@ -299,8 +303,13 @@ private:
void addToolTip(QWidget &widget, QString text) const;
struct Widget {
QWidget *element;
/// The element of the register widget
/// This can point to the label of the widget, or the action widget (e.g. the spinbox)
QWidget *element{};
QStringList keywords;
/// The optional parent element of the widget (usually pointing at a SettingWidget)
QWidget *parentElement{};
};
struct Group {
@@ -311,9 +320,9 @@ private:
std::vector<Widget> widgets;
};
QScrollArea *contentScrollArea_;
QVBoxLayout *contentLayout_;
QVBoxLayout *navigationLayout_;
QScrollArea *contentScrollArea_ = nullptr;
QVBoxLayout *contentLayout_ = nullptr;
QVBoxLayout *navigationLayout_ = nullptr;
std::vector<Group> groups_;
pajlada::Signals::SignalHolder managedConnections_;
+173 -1
View File
@@ -1,10 +1,15 @@
#include "widgets/settingspages/SettingWidget.hpp"
#include "util/RapidJsonSerializeQString.hpp" // IWYU pragma: keep
#include "widgets/dialogs/ColorPickerDialog.hpp"
#include "widgets/helper/color/ColorButton.hpp"
#include "widgets/settingspages/GeneralPageView.hpp"
#include <QBoxLayout>
#include <QCheckBox>
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
namespace {
@@ -86,6 +91,145 @@ SettingWidget *SettingWidget::inverseCheckbox(const QString &label,
return widget;
}
SettingWidget *SettingWidget::customCheckbox(
const QString &label, bool initialValue,
const std::function<void(bool)> &save)
{
auto *widget = new SettingWidget(label);
auto *check = new QCheckBox(label);
widget->hLayout->addWidget(check);
check->setChecked(initialValue);
QObject::connect(check, &QCheckBox::toggled, widget, save);
widget->actionWidget = check;
widget->label = check;
return widget;
}
SettingWidget *SettingWidget::intInput(const QString &label,
IntSetting &setting,
IntInputParams params)
{
auto *widget = new SettingWidget(label);
auto *lbl = new QLabel(label + ":");
auto *input = new QSpinBox;
if (params.min.has_value())
{
input->setMinimum(params.min.value());
}
if (params.max.has_value())
{
input->setMaximum(params.max.value());
}
if (params.singleStep.has_value())
{
input->setSingleStep(params.singleStep.value());
}
widget->hLayout->addWidget(lbl);
widget->hLayout->addStretch(1);
widget->hLayout->addWidget(input);
// update when setting changes
setting.connect(
[input](const int &value, const auto &) {
input->setValue(value);
},
widget->managedConnections);
// update setting on value changed
QObject::connect(input, QOverload<int>::of(&QSpinBox::valueChanged), widget,
[&setting](int newValue) {
setting = newValue;
});
widget->actionWidget = input;
widget->label = lbl;
return widget;
}
SettingWidget *SettingWidget::colorButton(const QString &label,
QStringSetting &setting)
{
QColor color(setting.getValue());
auto *widget = new SettingWidget(label);
auto *lbl = new QLabel(label + ":");
auto *colorButton = new ColorButton(color);
widget->hLayout->addWidget(lbl);
widget->hLayout->addStretch(1);
widget->hLayout->addWidget(colorButton);
// update when setting changes
setting.connect(
[colorButton](const QString &value, const auto &) {
colorButton->setColor(QColor(value));
},
widget->managedConnections);
QObject::connect(colorButton, &ColorButton::clicked, [widget, &setting]() {
auto *dialog = new ColorPickerDialog(QColor(setting), widget);
// colorButton & setting are never deleted and the signal is deleted
// once the dialog is closed
QObject::connect(
dialog, &ColorPickerDialog::colorConfirmed, widget,
[&setting](auto selected) {
if (selected.isValid())
{
setting.setValue(selected.name(QColor::HexArgb));
}
});
dialog->show();
});
widget->actionWidget = colorButton;
widget->label = lbl;
return widget;
}
SettingWidget *SettingWidget::lineEdit(const QString &label,
QStringSetting &setting,
const QString &placeholderText)
{
QColor color(setting.getValue());
auto *widget = new SettingWidget(label);
auto *lbl = new QLabel(label + ":");
auto *edit = new QLineEdit;
edit->setText(setting);
if (!placeholderText.isEmpty())
{
edit->setPlaceholderText(placeholderText);
}
widget->hLayout->addWidget(lbl);
// widget->hLayout->addStretch(1);
widget->hLayout->addWidget(edit);
// update when setting changes
QObject::connect(edit, &QLineEdit::textChanged,
[&setting](const QString &newValue) {
setting = newValue;
});
widget->actionWidget = edit;
widget->label = lbl;
return widget;
}
SettingWidget *SettingWidget::setTooltip(QString tooltip)
{
assert(!tooltip.isEmpty());
@@ -136,9 +280,37 @@ SettingWidget *SettingWidget::addKeywords(const QStringList &newKeywords)
return this;
}
SettingWidget *SettingWidget::conditionallyEnabledBy(BoolSetting &setting)
{
setting.connect(
[this](const auto &value, const auto &) {
this->actionWidget->setEnabled(value);
},
this->managedConnections);
return this;
}
void SettingWidget::addTo(GeneralPageView &view)
{
view.addWidget(this, this->keywords);
view.pushWidget(this);
if (this->label != nullptr)
{
view.registerWidget(this->label, this->keywords, this);
}
view.registerWidget(this->actionWidget, this->keywords, this);
}
void SettingWidget::addTo(GeneralPageView &view, QFormLayout *formLayout)
{
if (this->label != nullptr)
{
view.registerWidget(this->label, this->keywords, this);
}
view.registerWidget(this->actionWidget, this->keywords, this);
formLayout->addRow(this->label, this->actionWidget);
}
} // namespace chatterino
@@ -15,6 +15,8 @@
#include <QtContainerFwd>
#include <QWidget>
class QFormLayout;
namespace chatterino {
class GeneralPageView;
@@ -26,6 +28,12 @@ class SettingWidget : QWidget
explicit SettingWidget(const QString &mainKeyword);
public:
struct IntInputParams {
std::optional<int> min;
std::optional<int> max;
std::optional<int> singleStep;
};
~SettingWidget() override = default;
SettingWidget &operator=(const SettingWidget &) = delete;
SettingWidget &operator=(SettingWidget &&) = delete;
@@ -35,6 +43,13 @@ public:
static SettingWidget *checkbox(const QString &label, BoolSetting &setting);
static SettingWidget *inverseCheckbox(const QString &label,
BoolSetting &setting);
static SettingWidget *customCheckbox(const QString &label,
bool initialValue,
const std::function<void(bool)> &save);
static SettingWidget *intInput(const QString &label, IntSetting &setting,
IntInputParams params);
template <typename T>
static SettingWidget *dropdown(const QString &label,
EnumStringSetting<T> &setting)
@@ -81,6 +96,11 @@ public:
return widget;
}
static SettingWidget *colorButton(const QString &label,
QStringSetting &setting);
static SettingWidget *lineEdit(const QString &label,
QStringSetting &setting,
const QString &placeholderText = {});
SettingWidget *setTooltip(QString tooltip);
SettingWidget *setDescription(const QString &text);
@@ -90,7 +110,10 @@ public:
/// All text from the tooltip, description, and label are already keywords
SettingWidget *addKeywords(const QStringList &newKeywords);
SettingWidget *conditionallyEnabledBy(BoolSetting &setting);
void addTo(GeneralPageView &view);
void addTo(GeneralPageView &view, QFormLayout *formLayout);
private:
QWidget *label = nullptr;