From e7ec59e39ecd4bc4cf76b7e65ec5f1267ac12fea Mon Sep 17 00:00:00 2001 From: maxer137 Date: Mon, 19 May 2025 12:58:06 +0200 Subject: [PATCH] FEAT(client): Add option to change TalkingUI background color This is a revisit to issue #5411. This commit adds an optional QColor in settings to set the talkingUI background color. --- src/mumble/JSONSerialization.cpp | 16 + src/mumble/LookConfig.cpp | 30 ++ src/mumble/LookConfig.h | 7 + src/mumble/LookConfig.ui | 439 +++++++++++------- src/mumble/Settings.h | 16 +- src/mumble/SettingsKeys.h | 1 + src/mumble/SettingsMacros.h | 3 +- src/mumble/TalkingUI.cpp | 21 + src/mumble/TalkingUI.h | 15 +- .../generate_test_case.py | 2 + 10 files changed, 380 insertions(+), 170 deletions(-) diff --git a/src/mumble/JSONSerialization.cpp b/src/mumble/JSONSerialization.cpp index 953ac32a0..c0abd40c1 100644 --- a/src/mumble/JSONSerialization.cpp +++ b/src/mumble/JSONSerialization.cpp @@ -436,3 +436,19 @@ void from_json(const nlohmann::json &j, QSizeF &size) { size.setWidth(j.at("width").get< qreal >()); size.setHeight(j.at("height").get< qreal >()); } + +template< typename T > void to_json(nlohmann::json &j, const std::optional< T > &opt) { + if (opt.has_value()) { + j = *opt; + } else { + j = nullptr; + } +} + +template< typename T > void from_json(const nlohmann::json &j, std::optional< T > &opt) { + if (j.is_null()) { + opt = std::nullopt; + } else { + opt = j.get< T >(); + } +} diff --git a/src/mumble/LookConfig.cpp b/src/mumble/LookConfig.cpp index 6c281dee3..f9a4b6c96 100644 --- a/src/mumble/LookConfig.cpp +++ b/src/mumble/LookConfig.cpp @@ -12,6 +12,7 @@ #include "SearchDialog.h" #include "Global.h" +#include #include #include #include @@ -69,6 +70,8 @@ LookConfig::LookConfig(Settings &st) : ConfigWidget(st) { qcbUserDrag->insertItem(Settings::Move, tr("Move"), Settings::Move); connect(qrbLCustom, SIGNAL(toggled(bool)), qcbLockLayout, SLOT(setEnabled(bool))); + connect(qbClearBackgroundColor, &QPushButton::clicked, this, &LookConfig::talkinguiBackgroundCleared); + connect(qbBackgroundColor, &QPushButton::clicked, this, &LookConfig::qbBackgroundColor_clicked); QDir userThemeDirectory = Themes::getUserThemesDirectory(); if (userThemeDirectory.exists()) { @@ -109,6 +112,27 @@ QString LookConfig::title() const { return tr("User Interface"); } +void LookConfig::qbBackgroundColor_clicked() { + QColor color = QColorDialog::getColor(Qt::white, this, tr("Choose a Color")); + if (color.isValid()) { + talkinguiBackgroundSet(color); + } +} + +void LookConfig::talkinguiBackgroundSet(QColor color) { + selectedBackgroundColor = color; + QString style = QString("background-color: %1;").arg(color.name()); + qccolorPreview->setStyleSheet(style); + swBackgroundColor->setCurrentIndex(0); + qlBackgroundColor->setBuddy(qbClearBackgroundColor); +} + +void LookConfig::talkinguiBackgroundCleared() { + selectedBackgroundColor = std::nullopt; + swBackgroundColor->setCurrentIndex(1); + qlBackgroundColor->setBuddy(qbBackgroundColor); +} + const QString &LookConfig::getName() const { return LookConfig::name; } @@ -208,6 +232,11 @@ void LookConfig::load(const Settings &r) { qsbPrefixCharCount->setValue(r.iTalkingUI_PrefixCharCount); qsbPostfixCharCount->setValue(r.iTalkingUI_PostfixCharCount); qleAbbreviationReplacement->setText(r.qsTalkingUI_AbbreviationReplacement); + if (r.talkingUI_BackgroundColor.has_value()) { + talkinguiBackgroundSet(*r.talkingUI_BackgroundColor); + } else { + talkinguiBackgroundCleared(); + } qleChannelSeparator->setText(r.qsHierarchyChannelSeparator); @@ -280,6 +309,7 @@ void LookConfig::save() const { s.iTalkingUI_PrefixCharCount = qsbPrefixCharCount->value(); s.iTalkingUI_PostfixCharCount = qsbPostfixCharCount->value(); s.qsTalkingUI_AbbreviationReplacement = qleAbbreviationReplacement->text(); + s.talkingUI_BackgroundColor = selectedBackgroundColor; s.qsHierarchyChannelSeparator = qleChannelSeparator->text(); diff --git a/src/mumble/LookConfig.h b/src/mumble/LookConfig.h index 49d81f1b1..2b8d62fb7 100644 --- a/src/mumble/LookConfig.h +++ b/src/mumble/LookConfig.h @@ -6,6 +6,8 @@ #ifndef MUMBLE_MUMBLE_LOOKCONFIG_H_ #define MUMBLE_MUMBLE_LOOKCONFIG_H_ +#include + #include "ConfigDialog.h" #include "ThemeInfo.h" @@ -18,6 +20,8 @@ class LookConfig : public ConfigWidget, Ui::LookConfig { private: Q_OBJECT Q_DISABLE_COPY(LookConfig) + std::optional< QColor > selectedBackgroundColor; + public: /// The unique name of this ConfigWidget static const QString name; @@ -30,9 +34,12 @@ public slots: void accept() const Q_DECL_OVERRIDE; void save() const Q_DECL_OVERRIDE; void load(const Settings &r) Q_DECL_OVERRIDE; + void talkinguiBackgroundSet(QColor color); + void talkinguiBackgroundCleared(); void themeDirectoryChanged(); void on_qcbAbbreviateChannelNames_stateChanged(int state); void on_qcbUsersAlwaysVisible_stateChanged(int state); + void qbBackgroundColor_clicked(); private: /// Reload themes combobox and select given configuredStyle in it diff --git a/src/mumble/LookConfig.ui b/src/mumble/LookConfig.ui index f87994cc2..0aa4ba8ca 100644 --- a/src/mumble/LookConfig.ui +++ b/src/mumble/LookConfig.ui @@ -6,7 +6,7 @@ 0 0 - 728 + 777 1273 @@ -152,7 +152,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -162,19 +162,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -283,7 +270,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -325,7 +312,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -338,7 +325,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -351,7 +338,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -364,7 +351,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -598,59 +585,152 @@ false - - 6 - - - - - How many characters from the original name to display at the end of an abbreviated name. - - - Abbreviated postfix characters - - - qsbPostfixCharCount - - - - - - - The preferred maximum length of a channel (hierarchy) name in the Talking UI. Note that this is not a hard limit though. - - - Maximum channel name length - - - - - + + - + 0 0 + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + Clears the TalkingUI background setting. + + + Clear Background Color + + + Clear + + + + + + + + 50 + 0 + + + + background-color: white + + + QFrame::Shape::Box + + + QFrame::Shadow::Raised + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Color that gets used for the background of the talkingUI. + + + Choose + + + + + + + + + + + If this is checked, users will always be visible in the TalkingUI (regardless of talking state). + + + Always keep users visible + + + + + + + Relative font size to use in the Talking UI in percent. + + + Rel. font size (%) + + + qsbRelFontSize + + + + + String that gets used instead of the cut-out part of an abbreviated name. - - Abbreviation replacement characters + + Abbreviation replacement + + + qleAbbreviationReplacement - - - - How many characters from the original name to display at the end of an abbreviated name. - - - Postfix character count - - - - + Relative font size to use in the Talking UI in percent. @@ -666,7 +746,17 @@ - + + + + The preferred maximum length of a channel (hierarchy) name in the Talking UI. Note that this is not a hard limit though. + + + Maximum channel name length + + + + The preferred maximum length of a channel (hierarchy) name in the Talking UI. Note that this is not a hard limit though. @@ -679,7 +769,92 @@ - + + + + If this is checked, the local user (yourself) will always be visible in the TalkingUI (regardless of talking state). + + + Always keep local user visible + + + + + + + How many characters from the original name to display at the end of an abbreviated name. + + + Postfix character count + + + + + + + Whether the channel (hierarchy) name should be abbreviated, if it exceeds the specified maximum length. + + + Abbreviate channel names + + + + + + + Background color that, if set, overrides the theme background color. + + + Background Color + + + qbBackgroundColor + + + + + + + How many characters from the original name to display at the end of an abbreviated name. + + + Abbreviated postfix characters + + + qsbPostfixCharCount + + + + + + + + 0 + 0 + + + + String that gets used instead of the cut-out part of an abbreviated name. + + + Abbreviation replacement characters + + + + + + + The names of how many parent channels should be included in the channel's name when displaying it in the TalkingUI? + + + Channel hierarchy depth + + + qsbChannelHierarchyDepth + + + + @@ -701,7 +876,27 @@ - + + + + The names of how many parent channels should be included in the channel's name when displaying it in the TalkingUI? + + + Channel hierarchy depth + + + + + + + How many characters from the original name to display at the beginning of an abbreviated name. + + + Prefix character count + + + + Whether to also allow abbreviating the current channel of a user (instead of only its parent channels). @@ -730,27 +925,7 @@ - - - - Whether the channel (hierarchy) name should be abbreviated, if it exceeds the specified maximum length. - - - Abbreviate channel names - - - - - - - The names of how many parent channels should be included in the channel's name when displaying it in the TalkingUI? - - - Channel hierarchy depth - - - - + Whether to show all of the local user's listeners (ears) in the TalkingUI (and thereby also the channels they are in). @@ -760,20 +935,7 @@ - - - - String that gets used instead of the cut-out part of an abbreviated name. - - - Abbreviation replacement - - - qleAbbreviationReplacement - - - - + How many characters from the original name to display at the beginning of an abbreviated name. @@ -786,62 +948,6 @@ - - - - If this is checked, the local user (yourself) will always be visible in the TalkingUI (regardless of talking state). - - - Always keep local user visible - - - - - - - How many characters from the original name to display at the beginning of an abbreviated name. - - - Prefix character count - - - - - - - Relative font size to use in the Talking UI in percent. - - - Rel. font size (%) - - - qsbRelFontSize - - - - - - - The names of how many parent channels should be included in the channel's name when displaying it in the TalkingUI? - - - Channel hierarchy depth - - - qsbChannelHierarchyDepth - - - - - - - If this is checked, users will always be visible in the TalkingUI (regardless of talking state). - - - Always keep users visible - - - @@ -913,7 +1019,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -939,7 +1045,7 @@ - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -1013,7 +1119,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1026,6 +1132,19 @@ + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + @@ -1074,6 +1193,8 @@ qsbPrefixCharCount qsbPostfixCharCount qleAbbreviationReplacement + qbBackgroundColor + qbClearBackgroundColor qleChannelSeparator diff --git a/src/mumble/Settings.h b/src/mumble/Settings.h index 195356890..2d37317a3 100644 --- a/src/mumble/Settings.h +++ b/src/mumble/Settings.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "Channel.h" #include "EchoCancelOption.h" @@ -382,13 +383,14 @@ struct Settings { bool bTalkingUI_AbbreviateCurrentChannel = false; bool bTalkingUI_ShowLocalListeners = false; /// relative font size in % - int iTalkingUI_RelativeFontSize = 100; - int iTalkingUI_SilentUserLifeTime = 10; - int iTalkingUI_ChannelHierarchyDepth = 1; - int iTalkingUI_MaxChannelNameLength = 20; - int iTalkingUI_PrefixCharCount = 3; - int iTalkingUI_PostfixCharCount = 2; - QString qsTalkingUI_AbbreviationReplacement = QStringLiteral("..."); + int iTalkingUI_RelativeFontSize = 100; + int iTalkingUI_SilentUserLifeTime = 10; + int iTalkingUI_ChannelHierarchyDepth = 1; + int iTalkingUI_MaxChannelNameLength = 20; + int iTalkingUI_PrefixCharCount = 3; + int iTalkingUI_PostfixCharCount = 2; + QString qsTalkingUI_AbbreviationReplacement = QStringLiteral("..."); + std::optional< QColor > talkingUI_BackgroundColor = std::nullopt; QString qsHierarchyChannelSeparator = QStringLiteral("/"); diff --git a/src/mumble/SettingsKeys.h b/src/mumble/SettingsKeys.h index 3e1f59e52..a1acd337c 100644 --- a/src/mumble/SettingsKeys.h +++ b/src/mumble/SettingsKeys.h @@ -237,6 +237,7 @@ const SettingsKey TALKINGUI_MAX_CHANNEL_NAME_LENGTH_KEY = { "max_channel_name const SettingsKey TALKINGUI_NAME_PREFIX_COUNT_KEY = { "name_prefix_count" }; const SettingsKey TALKINGUI_NAME_POSTFIX_COUNT_KEY = { "name_postfix_count" }; const SettingsKey TALKINGUI_ABBREVIATION_REPLACEMENT_KEY = { "abbreviation_replacement" }; +const SettingsKey TALKINGUI_BACKGROUND_COLOR_KEY = { "background_color" }; // Channel hierarchy const SettingsKey CHANNEL_NAME_SEPARATOR_KEY = { "channel_name_separator" }; diff --git a/src/mumble/SettingsMacros.h b/src/mumble/SettingsMacros.h index dfcbee899..83402bda5 100644 --- a/src/mumble/SettingsMacros.h +++ b/src/mumble/SettingsMacros.h @@ -204,7 +204,8 @@ PROCESS(talkingui, TALKINGUI_MAX_CHANNEL_NAME_LENGTH_KEY, iTalkingUI_MaxChannelNameLength) \ PROCESS(talkingui, TALKINGUI_NAME_PREFIX_COUNT_KEY, iTalkingUI_PrefixCharCount) \ PROCESS(talkingui, TALKINGUI_NAME_POSTFIX_COUNT_KEY, iTalkingUI_PostfixCharCount) \ - PROCESS(talkingui, TALKINGUI_ABBREVIATION_REPLACEMENT_KEY, qsTalkingUI_AbbreviationReplacement) + PROCESS(talkingui, TALKINGUI_ABBREVIATION_REPLACEMENT_KEY, qsTalkingUI_AbbreviationReplacement) \ + PROCESS(talkingui, TALKINGUI_BACKGROUND_COLOR_KEY, talkingUI_BackgroundColor) #define CHANNEL_HIERARCHY_SETTINGS PROCESS(channel_hierarchy, CHANNEL_NAME_SEPARATOR_KEY, qsHierarchyChannelSeparator) diff --git a/src/mumble/TalkingUI.cpp b/src/mumble/TalkingUI.cpp index 7ad992d13..a6838271b 100644 --- a/src/mumble/TalkingUI.cpp +++ b/src/mumble/TalkingUI.cpp @@ -232,6 +232,10 @@ void TalkingUI::setupUI() { // properly and as the TalkingUI doesn't provide context help anyways, this is not a big loss. setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + if (Global::get().s.talkingUI_BackgroundColor.has_value()) { + setBackgroundColor(*Global::get().s.talkingUI_BackgroundColor); + } + connect(Global::get().mw->qtvUsers->selectionModel(), &QItemSelectionModel::currentChanged, this, &TalkingUI::on_mainWindowSelectionChanged); } @@ -805,6 +809,13 @@ void TalkingUI::on_settingsChanged() { const ClientUser *self = ClientUser::get(Global::get().uiSession); + // Update Background Color + if (Global::get().s.talkingUI_BackgroundColor.has_value()) { + setBackgroundColor(*Global::get().s.talkingUI_BackgroundColor); + } else { + clearBackgroundColor(); + } + // Whether or not the current user should always be displayed might also have changed, // so we'll have to update that as well. TalkingUIUser *localUserEntry = findUser(Global::get().uiSession); @@ -882,3 +893,13 @@ void TalkingUI::on_channelListenerLocalVolumeAdjustmentChanged(unsigned int chan listenerEntry->setDisplayString(UserModel::createDisplayString(*self, true, channel)); } } + +void TalkingUI::setBackgroundColor(QColor backgroundColor) { + if (backgroundColor.isValid()) { + setStyleSheet(QString("background-color: %1;").arg(backgroundColor.name())); + } +} + +void TalkingUI::clearBackgroundColor() { + setStyleSheet(""); +} diff --git a/src/mumble/TalkingUI.h b/src/mumble/TalkingUI.h index 3c3cde7a4..c28c39114 100644 --- a/src/mumble/TalkingUI.h +++ b/src/mumble/TalkingUI.h @@ -6,10 +6,11 @@ #ifndef MUMBLE_MUMBLE_TALKINGUI_H_ #define MUMBLE_MUMBLE_TALKINGUI_H_ +#include +#include +#include +#include #include -#include -#include -#include #include #include @@ -91,6 +92,14 @@ private: /// shall be set void setFontSize(MultiStyleWidgetWrapper &widgetWrapper); + /// Sets the background color to to a color + /// + /// @param backgroundColor An instance of QColor that will be set as the background color + void setBackgroundColor(QColor backgroundColor); + + /// Clears the stylesheet of the TalkingUI window + void clearBackgroundColor(); + /// Updates the user's status icons (reflecting e.g. its mut-state) /// /// @param user A pointer to the user that shall be processed diff --git a/src/tests/TestSettingsJSONSerialization/generate_test_case.py b/src/tests/TestSettingsJSONSerialization/generate_test_case.py index 479909dff..180b15ce3 100755 --- a/src/tests/TestSettingsJSONSerialization/generate_test_case.py +++ b/src/tests/TestSettingsJSONSerialization/generate_test_case.py @@ -104,6 +104,8 @@ def getDefaultValueForType(dataType): if dataType in ["int", "short", "long", "float", "double", "qreal"] or dataType.startswith("qint") or dataType.startswith("quint") or \ dataType.startswith("uint"): return "42" + elif match := re.search(r"optional<(.*)>", dataType): + return getDefaultValueForType(match.group(1)) elif dataType in ["bool"]: return "true" elif dataType in ["QString", "std::string"]: