diff --git a/CHANGELOG.md b/CHANGELOG.md index 8886b4ee..56daa429 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,7 @@ - Minor: Migrated /uniquechat and /r9kbeta to Helix API. (#4057) - Minor: Migrated /uniquechatoff and /r9kbetaoff to Helix API. (#4057) - Minor: Make menus and placeholders display appropriate custom key combos. (#4045) +- Minor: Add settings tooltips. (#3437) - Bugfix: Connection to Twitch PubSub now recovers more reliably. (#3643, #3716) - Bugfix: Fixed `Smooth scrolling on new messages` setting sometimes hiding messages. (#4028) - Bugfix: Fixed a crash that can occur when closing and quickly reopening a split, then running a command. (#3852) diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index e3e12192..3a3c6afe 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -191,9 +191,11 @@ void GeneralPage::initLayout(GeneralPageView &layout) layout.addCheckbox("Show message reply button", s.showReplyButton); layout.addCheckbox("Show tab close button", s.showTabCloseButton); - layout.addCheckbox("Always on top", s.windowTopMost); + layout.addCheckbox("Always on top", s.windowTopMost, false, + "Always keep Chatterino as the top window."); #ifdef USEWINSDK - layout.addCheckbox("Start with Windows", s.autorun); + layout.addCheckbox("Start with Windows", s.autorun, false, + "Start Chatterino when your computer starts."); #endif if (!BaseWindow::supportsCustomWindowFrame()) { @@ -253,17 +255,19 @@ void GeneralPage::initLayout(GeneralPageView &layout) layout.addCheckbox("Smooth scrolling", s.enableSmoothScrolling); layout.addCheckbox("Smooth scrolling on new messages", s.enableSmoothScrollingNewMessages); - layout.addCheckbox("Show input when it's empty", s.showEmptyInput); + layout.addCheckbox("Show input when it's empty", s.showEmptyInput, false, + "Show the chat box even when there is nothing typed."); layout.addCheckbox("Show message length while typing", s.showMessageLength); - layout.addCheckbox("Allow sending duplicate messages", - s.allowDuplicateMessages); + layout.addCheckbox( + "Allow sending duplicate messages", s.allowDuplicateMessages, false, + "Allow a single message to be repeatedly sent without any changes."); layout.addTitle("Messages"); layout.addCheckbox("Separate with lines", s.separateMessages); layout.addCheckbox("Alternate background color", s.alternateMessages); layout.addCheckbox("Show deleted messages", s.hideModerated, true); layout.addDropdown( - "Timestamp format (a = am/pm, zzz = milliseconds)", + "Timestamp format", {"Disable", "h:mm", "hh:mm", "h:mm a", "hh:mm a", "h:mm:ss", "hh:mm:ss", "h:mm:ss a", "hh:mm:ss a", "h:mm:ss.zzz", "h:mm:ss.zzz a", "hh:mm:ss.zzz", "hh:mm:ss.zzz a"}, @@ -278,7 +282,8 @@ void GeneralPage::initLayout(GeneralPageView &layout) return args.index == 0 ? getSettings()->timestampFormat.getValue() : args.value; - }); + }, + true, "a = am/pm, zzz = milliseconds"); layout.addDropdown( "Limit message height", {"Never", "2 lines", "3 lines", "4 lines", "5 lines"}, @@ -367,7 +372,7 @@ void GeneralPage::initLayout(GeneralPageView &layout) [](auto args) { return args.index; }, - false); + false, "Show emote name, provider, and author on hover."); layout.addDropdown("Emoji style", { "Twitter", @@ -404,7 +409,8 @@ void GeneralPage::initLayout(GeneralPageView &layout) dankDropdown->setMinimumWidth(dankDropdown->minimumSizeHint().width() + 30); layout.addCheckbox("Hide usercard avatars", - s.streamerModeHideUsercardAvatars); + s.streamerModeHideUsercardAvatars, false, + "Prevent potentially explicit avatars from showing."); layout.addCheckbox("Hide link thumbnails", s.streamerModeHideLinkThumbnails); layout.addCheckbox( @@ -644,16 +650,19 @@ void GeneralPage::initLayout(GeneralPageView &layout) }); layout.addSubtitle("Visible badges"); - layout.addCheckbox("Authority (staff, admin)", s.showBadgesGlobalAuthority); + layout.addCheckbox("Authority", s.showBadgesGlobalAuthority, false, + "e.g., staff, admin"); layout.addCheckbox("Predictions", s.showBadgesPredictions); - layout.addCheckbox("Channel (broadcaster, moderator)", - s.showBadgesChannelAuthority); + layout.addCheckbox("Channel", s.showBadgesChannelAuthority, false, + "e.g., broadcaster, moderator"); layout.addCheckbox("Subscriber ", s.showBadgesSubscription); - layout.addCheckbox("Vanity (prime, bits, subgifter)", s.showBadgesVanity); + layout.addCheckbox("Vanity", s.showBadgesVanity, false, + "e.g., prime, bits, sub gifter"); layout.addCheckbox("Chatterino", s.showBadgesChatterino); - layout.addCheckbox("FrankerFaceZ (Bot, FFZ Supporter, FFZ Developer)", - s.showBadgesFfz); - layout.addCheckbox("7TV", s.showBadgesSevenTV); + 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"); layout.addSeperator(); layout.addCheckbox("Use custom FrankerFaceZ moderator badges", s.useCustomFfzModeratorBadges); @@ -679,8 +688,9 @@ void GeneralPage::initLayout(GeneralPageView &layout) } #endif - layout.addCheckbox("Show moderation messages", s.hideModerationActions, - true); + 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); layout.addCheckbox("Colorize users without color set (gray names)", @@ -694,11 +704,15 @@ void GeneralPage::initLayout(GeneralPageView &layout) layout.addCheckbox( "Automatically close reply thread popup when it loses focus", s.autoCloseThreadPopup); - layout.addCheckbox("Lowercase domains (anti-phishing)", s.lowercaseDomains); + layout.addCheckbox("Lowercase domains (anti-phishing)", s.lowercaseDomains, + false, + "Make all clickable links lowercase to deter " + "phishing attempts."); layout.addCheckbox("Bold @usernames", s.boldUsernames); layout.addCheckbox("Color @usernames", s.colorUsernames); layout.addCheckbox("Try to find usernames without @ prefix", - s.findAllUsernames); + s.findAllUsernames, false, + "Find mentions of users in chat without the @ prefix."); layout.addCheckbox("Show username autocompletion popup menu", s.showUsernameCompletionMenu); const QStringList usernameDisplayModes = {"Username", "Localized name", @@ -734,11 +748,16 @@ void GeneralPage::initLayout(GeneralPageView &layout) layout.addCheckbox( "Only search for emote autocompletion at the start of emote names", - s.prefixOnlyEmoteCompletion); + s.prefixOnlyEmoteCompletion, false, + "When disabled, emote tab-completion will complete based on any part " + "of the name." + "\ne.g., sheffy -> DatSheffy"); layout.addCheckbox("Only search for username autocompletion with an @", s.userCompletionOnlyWithAt); - layout.addCheckbox("Show Twitch whispers inline", s.inlineWhispers); + layout.addCheckbox("Show Twitch whispers inline", s.inlineWhispers, false, + "Show whispers as messages in all splits instead " + "of just /whispers."); layout.addCheckbox("Highlight received inline whispers", s.highlightInlineWhispers); layout.addCheckbox("Load message history on connect", @@ -760,8 +779,10 @@ void GeneralPage::initLayout(GeneralPageView &layout) [](auto args) { return args.index; }, - false); - layout.addCheckbox("Combine multiple bit tips into one", s.stackBits); + false, "Combine consecutive timeout messages into a single message."); + layout.addCheckbox("Combine multiple bit tips into one", s.stackBits, false, + "Combine consecutive cheermotes (sent in a single " + "message) into one cheermote."); layout.addCheckbox("Messages in /mentions highlights tab", s.highlightMentions); layout.addCheckbox("Strip leading mention in replies", s.stripReplyMention); diff --git a/src/widgets/settingspages/GeneralPageView.cpp b/src/widgets/settingspages/GeneralPageView.cpp index f9accba3..60c66b4b 100644 --- a/src/widgets/settingspages/GeneralPageView.cpp +++ b/src/widgets/settingspages/GeneralPageView.cpp @@ -1,13 +1,33 @@ -#include "GeneralPageView.hpp" -#include +#include "widgets/settingspages/GeneralPageView.hpp" + #include "Application.hpp" -#include "singletons/Settings.hpp" #include "singletons/WindowManager.hpp" #include "util/LayoutHelper.hpp" +#include "util/RapidJsonSerializeQString.hpp" #include "widgets/dialogs/ColorPickerDialog.hpp" #include "widgets/helper/ColorButton.hpp" #include "widgets/helper/Line.hpp" +#include +#include + +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); + +const auto TOOLTIP_STYLE_SHEET = QStringLiteral(R"(QToolTip { +padding: 2px; +background-color: #333333; +border: 1px solid #545454; +} +)"); + +} // namespace + namespace chatterino { GeneralPageView::GeneralPageView(QWidget *parent) @@ -86,9 +106,11 @@ SubtitleLabel *GeneralPageView::addSubtitle(const QString &title) } QCheckBox *GeneralPageView::addCheckbox(const QString &text, - BoolSetting &setting, bool inverse) + BoolSetting &setting, bool inverse, + QString toolTipText) { auto check = new QCheckBox(text); + this->addToolTip(*check, toolTipText); // update when setting changes setting.connect( @@ -112,7 +134,8 @@ QCheckBox *GeneralPageView::addCheckbox(const QString &text, } ComboBox *GeneralPageView::addDropdown(const QString &text, - const QStringList &list) + const QStringList &list, + QString toolTipText) { auto layout = new QHBoxLayout; auto combo = new ComboBox; @@ -124,6 +147,7 @@ ComboBox *GeneralPageView::addDropdown(const QString &text, layout->addStretch(1); layout->addWidget(combo); + this->addToolTip(*label, toolTipText); this->addLayout(layout); // groups @@ -135,9 +159,10 @@ ComboBox *GeneralPageView::addDropdown(const QString &text, ComboBox *GeneralPageView::addDropdown( const QString &text, const QStringList &items, - pajlada::Settings::Setting &setting, bool editable) + pajlada::Settings::Setting &setting, bool editable, + QString toolTipText) { - auto combo = this->addDropdown(text, items); + auto combo = this->addDropdown(text, items, toolTipText); if (editable) combo->setEditable(true); @@ -160,15 +185,19 @@ ComboBox *GeneralPageView::addDropdown( ColorButton *GeneralPageView::addColorButton( const QString &text, const QColor &color, - pajlada::Settings::Setting &setting) + pajlada::Settings::Setting &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); @@ -190,11 +219,13 @@ ColorButton *GeneralPageView::addColorButton( } QSpinBox *GeneralPageView::addIntInput(const QString &text, IntSetting &setting, - int min, int max, int step) + int min, int max, int step, + QString toolTipText) { auto layout = new QHBoxLayout; auto label = new QLabel(text + ":"); + this->addToolTip(*label, toolTipText); auto input = new QSpinBox; input->setMinimum(min); @@ -362,4 +393,23 @@ void GeneralPageView::updateNavigationHighlighting() } } +void GeneralPageView::addToolTip(QWidget &widget, QString text) const +{ + if (text.isEmpty()) + { + return; + } + + if (text.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 + text.replace(MAX_TOOLTIP_LINE_LENGTH_REGEX, "\n"); + } + + widget.setToolTip(text); + widget.setStyleSheet(TOOLTIP_STYLE_SHEET); +} + } // namespace chatterino diff --git a/src/widgets/settingspages/GeneralPageView.hpp b/src/widgets/settingspages/GeneralPageView.hpp index 0a4ff57b..bbfded9f 100644 --- a/src/widgets/settingspages/GeneralPageView.hpp +++ b/src/widgets/settingspages/GeneralPageView.hpp @@ -99,15 +99,17 @@ public: SubtitleLabel *addSubtitle(const QString &text); /// @param inverse Inverses true to false and vice versa QCheckBox *addCheckbox(const QString &text, BoolSetting &setting, - bool inverse = false); - ComboBox *addDropdown(const QString &text, const QStringList &items); + bool inverse = false, QString toolTipText = {}); + ComboBox *addDropdown(const QString &text, const QStringList &items, + QString toolTipText = {}); ComboBox *addDropdown(const QString &text, const QStringList &items, pajlada::Settings::Setting &setting, - bool editable = false); + bool editable = false, QString toolTipText = {}); ColorButton *addColorButton(const QString &text, const QColor &color, - pajlada::Settings::Setting &setting); + pajlada::Settings::Setting &setting, + QString toolTipText = {}); QSpinBox *addIntInput(const QString &text, IntSetting &setting, int min, - int max, int step); + int max, int step, QString toolTipText = {}); void addNavigationSpacing(); template @@ -135,7 +137,8 @@ public: const QString &text, const QStringList &items, pajlada::Settings::Setting &setting, std::function(T)> getValue, - std::function setValue, bool editable = true) + std::function setValue, bool editable = true, + QString toolTipText = {}) { auto items2 = items; auto selected = getValue(setting.getValue()); @@ -147,7 +150,7 @@ public: items2.insert(0, boost::get(selected)); } - auto combo = this->addDropdown(text, items2); + auto combo = this->addDropdown(text, items2, toolTipText); if (editable) combo->setEditable(true); @@ -200,6 +203,7 @@ protected: private: void updateNavigationHighlighting(); + void addToolTip(QWidget &widget, QString text) const; struct Widget { QWidget *element;