From ff040fb23c407ee34e6d8796a3f058a455f40cb1 Mon Sep 17 00:00:00 2001 From: Robert Adam Date: Fri, 11 Dec 2020 20:47:38 +0100 Subject: [PATCH] REFAC(client, talking-ui): Stylable widget wrapper It turned out that it is absolutely impossible to only overwrite specific properties via a styleSheet without also overwriting all other properties that have been set via styleSheet on that object before. Therefore a myWidget->setStyleSheet("background: red") will not only set the background color to red but also erase everything that has been written to the StyleSheet before this point. As this is very inconvenient and always appending the most recent property to the existing StyleSheet is not an option (the StyleSheet will just keep growing in size and also this doesn't work well once one starts using selectors), this commit introduces a wrapper class that can be used for fine-tuned control of a widget's style. This works by storing the desired properties in a separate class and re-generate and re-apply the StyleSheet every time one of these properties changes. Currently the only implemented properties are font-size and background-color. NOTE: The wrapper only works as long as no other calls to setStyleSheet are made to the object the wrapper acts upon. The mentioned wrapper was then incorporated into the UI elements of the TalkingUI. --- src/mumble/CMakeLists.txt | 3 + src/mumble/TalkingUI.cpp | 15 ++-- src/mumble/TalkingUI.h | 6 +- src/mumble/TalkingUIComponent.h | 6 +- src/mumble/TalkingUIContainer.cpp | 10 ++- src/mumble/TalkingUIContainer.h | 4 + src/mumble/TalkingUIEntry.cpp | 22 +++-- src/mumble/TalkingUIEntry.h | 5 ++ .../widgets/MultiStyleWidgetWrapper.cpp | 90 +++++++++++++++++++ src/mumble/widgets/MultiStyleWidgetWrapper.h | 52 +++++++++++ 10 files changed, 195 insertions(+), 18 deletions(-) create mode 100644 src/mumble/widgets/MultiStyleWidgetWrapper.cpp create mode 100644 src/mumble/widgets/MultiStyleWidgetWrapper.h diff --git a/src/mumble/CMakeLists.txt b/src/mumble/CMakeLists.txt index 7fc604127..0da75187d 100644 --- a/src/mumble/CMakeLists.txt +++ b/src/mumble/CMakeLists.txt @@ -232,6 +232,9 @@ set(MUMBLE_SOURCES "widgets/MUComboBox.cpp" "widgets/MUComboBox.h" + "widgets/MultiStyleWidgetWrapper.cpp" + "widgets/MultiStyleWidgetWrapper.h" + "${SHARED_SOURCE_DIR}/ACL.cpp" "${SHARED_SOURCE_DIR}/ACL.h" diff --git a/src/mumble/TalkingUI.cpp b/src/mumble/TalkingUI.cpp index 16737f7d0..8725a80b0 100644 --- a/src/mumble/TalkingUI.cpp +++ b/src/mumble/TalkingUI.cpp @@ -10,6 +10,7 @@ #include "MainWindow.h" #include "TalkingUIComponent.h" #include "UserModel.h" +#include "widgets/MultiStyleWidgetWrapper.h" #include #include @@ -236,18 +237,18 @@ void TalkingUI::setupUI() { &TalkingUI::on_mainWindowSelectionChanged); } -void TalkingUI::setFontSize(QWidget *widget) { +void TalkingUI::setFontSize(MultiStyleWidgetWrapper &widgetWrapper) { const double fontFactor = g.s.iTalkingUI_RelativeFontSize / 100.0; const int origLineHeight = QFontMetrics(font()).height(); if (font().pixelSize() >= 0) { // font specified in pixels - widget->setStyleSheet(QString::fromLatin1("font-size: %1px;") - .arg(static_cast< int >(std::max(fontFactor * font().pixelSize(), 1.0)))); + uint32_t pixelSize = static_cast< uint32_t >(std::max(fontFactor * font().pixelSize(), 1.0)); + widgetWrapper.setFontSize(pixelSize, true); } else { // font specified in points - widget->setStyleSheet(QString::fromLatin1("font-size: %1pt;") - .arg(static_cast< int >(std::max(fontFactor * font().pointSize(), 1.0)))); + uint32_t pointSize = static_cast< uint32_t >(std::max(fontFactor * font().pointSize(), 1.0)); + widgetWrapper.setFontSize(pointSize, false); } m_currentLineHeight = static_cast< int >(std::max(origLineHeight * fontFactor, 1.0)); @@ -342,7 +343,7 @@ void TalkingUI::addChannel(const Channel *channel) { QWidget *channelWidget = channelContainer->getWidget(); - setFontSize(channelWidget); + setFontSize(channelContainer->getStylableWidget()); layout()->addWidget(channelWidget); @@ -713,7 +714,7 @@ void TalkingUI::on_settingsChanged() { // The font size might have changed as well -> update it // By the hierarchy in the UI the font-size should propagate to all // sub-elements (entries) as well. - setFontSize(currentContainer->getWidget()); + setFontSize(currentContainer->getStylableWidget()); if (currentContainer->getType() != ContainerType::CHANNEL) { continue; diff --git a/src/mumble/TalkingUI.h b/src/mumble/TalkingUI.h index 8ceeca435..cad6b631d 100644 --- a/src/mumble/TalkingUI.h +++ b/src/mumble/TalkingUI.h @@ -27,6 +27,7 @@ class QMouseEvent; class Channel; class ClientUser; class TalkingUIComponent; +class MultiStyleWidgetWrapper; /// The talking UI is a widget that will display the users you are currently /// hearing to you. @@ -87,8 +88,9 @@ private: /// Sets the font size according to the settings /// - /// @param widget a pointer to the widget to set the font size for - void setFontSize(QWidget *widget); + /// @param widgetWrapper An instance of MultiStyleWidgetWrapper that wraps the widget whose font size + /// shall be set + void setFontSize(MultiStyleWidgetWrapper &widgetWrapper); /// Updates the user's status icons (reflecting e.g. its mut-state) /// diff --git a/src/mumble/TalkingUIComponent.h b/src/mumble/TalkingUIComponent.h index e21105f67..7f0035343 100644 --- a/src/mumble/TalkingUIComponent.h +++ b/src/mumble/TalkingUIComponent.h @@ -7,11 +7,13 @@ #define MUMBLE_MUMBLE_TALKINGUICOMPONENT_H_ class QWidget; +class MultiStyleWidgetWrapper; class TalkingUIComponent { public: - virtual QWidget *getWidget() = 0; - virtual const QWidget *getWidget() const = 0; + virtual QWidget *getWidget() = 0; + virtual const QWidget *getWidget() const = 0; + virtual MultiStyleWidgetWrapper &getStylableWidget() = 0; }; #endif // MUMBLE_MUMBLE_TALKINGUICOMPONENT_H_ diff --git a/src/mumble/TalkingUIContainer.cpp b/src/mumble/TalkingUIContainer.cpp index 0bf36c748..8fbedef20 100644 --- a/src/mumble/TalkingUIContainer.cpp +++ b/src/mumble/TalkingUIContainer.cpp @@ -135,10 +135,14 @@ bool TalkingUIContainer::operator<=(const TalkingUIContainer &other) const { const int CHANNEL_LAYOUT_TOP_MARGIN = 10; TalkingUIChannel::TalkingUIChannel(int associatedChannelID, QString name, TalkingUI &talkingUI) - : TalkingUIContainer(associatedChannelID, talkingUI), m_channelBox(new QGroupBox()) { + : TalkingUIContainer(associatedChannelID, talkingUI), m_channelBox(new QGroupBox()), + m_channelBoxStyleWrapper(m_channelBox) { // Set name m_channelBox->setTitle(name); + // Don't inherit the background color set on the channel box to its children. + m_channelBoxStyleWrapper.setBackgroundColorSelector("TalkingUI > QGroupBox"); + // Set layout QVBoxLayout *layout = new QVBoxLayout(); layout->setContentsMargins(0, CHANNEL_LAYOUT_TOP_MARGIN, 0, 0); @@ -198,6 +202,10 @@ const QWidget *TalkingUIChannel::getWidget() const { return m_channelBox; } +MultiStyleWidgetWrapper &TalkingUIChannel::getStylableWidget() { + return m_channelBoxStyleWrapper; +} + ContainerType TalkingUIChannel::getType() const { return ContainerType::CHANNEL; } diff --git a/src/mumble/TalkingUIContainer.h b/src/mumble/TalkingUIContainer.h index 8c4c946be..55526755f 100644 --- a/src/mumble/TalkingUIContainer.h +++ b/src/mumble/TalkingUIContainer.h @@ -9,6 +9,8 @@ #include "TalkingUIComponent.h" #include "TalkingUIEntry.h" +#include "widgets/MultiStyleWidgetWrapper.h" + #include #include @@ -74,6 +76,7 @@ public: class TalkingUIChannel : public TalkingUIContainer { protected: QGroupBox *m_channelBox; + MultiStyleWidgetWrapper m_channelBoxStyleWrapper; EntryPriority m_highestUserPriority = EntryPriority::LOWEST; @@ -90,6 +93,7 @@ public: virtual QWidget *getWidget() override; virtual const QWidget *getWidget() const override; + virtual MultiStyleWidgetWrapper &getStylableWidget() override; virtual ContainerType getType() const override; diff --git a/src/mumble/TalkingUIEntry.cpp b/src/mumble/TalkingUIEntry.cpp index a55cf1e32..5945dccca 100644 --- a/src/mumble/TalkingUIEntry.cpp +++ b/src/mumble/TalkingUIEntry.cpp @@ -91,10 +91,11 @@ static const int USER_CONTAINER_HORIZONTAL_MARGIN = 2; static const int USER_CONTAINER_VERTICAL_MARGIN = 3; -TalkingUIUser::TalkingUIUser(const ClientUser &user) : TalkingUIEntry(user.uiSession), m_name(user.qsName), m_timer() { +TalkingUIUser::TalkingUIUser(const ClientUser &user) + : TalkingUIEntry(user.uiSession), m_backgroundWidget(new QWidget()), + m_backgroundWidgetStyleWrapper(m_backgroundWidget), m_name(user.qsName), m_timer() { // Create background widget and its layout that we'll use to place all other // components on - m_backgroundWidget = new QWidget(); m_backgroundWidget->setProperty("selected", false); QLayout *backgroundLayout = new QHBoxLayout(); backgroundLayout->setContentsMargins(USER_CONTAINER_HORIZONTAL_MARGIN, USER_CONTAINER_VERTICAL_MARGIN, @@ -115,6 +116,7 @@ TalkingUIUser::TalkingUIUser(const ClientUser &user) : TalkingUIEntry(user.uiSes // Create the label we'll use to display any status icons for the user m_statusIcons = new QLabel(m_backgroundWidget); m_statusIcons->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + m_statusIcons->hide(); // hide by default // Uncomment the line below if you want to debug the icons // m_statusIcons->setStyleSheet(QLatin1String("border: 1px solid black;")); @@ -184,6 +186,10 @@ const QWidget *TalkingUIUser::getWidget() const { return m_backgroundWidget; } +MultiStyleWidgetWrapper &TalkingUIUser::getStylableWidget() { + return m_backgroundWidgetStyleWrapper; +} + EntryType TalkingUIUser::getType() const { return EntryType::USER; } @@ -307,13 +313,13 @@ void TalkingUIUser::setStatus(UserStatus status) { m_backgroundWidget->layout()->removeWidget(m_statusIcons); } else { // Create a Pixmap that'll hold all icons - const QSize size(m_iconSize * static_cast(icons.size()), m_iconSize); + const QSize size(m_iconSize * static_cast< int >(icons.size()), m_iconSize); QPixmap pixmap(size); pixmap.fill(Qt::transparent); // Draw the icons to the Pixmap QPainter painter(&pixmap); - for (int i = 0; i < static_cast(icons.size()); i++) { + for (int i = 0; i < static_cast< int >(icons.size()); i++) { painter.drawPixmap(i * m_iconSize, 0, icons[i].get().pixmap(QSize(m_iconSize, m_iconSize), QIcon::Normal, QIcon::On)); } @@ -331,10 +337,10 @@ void TalkingUIUser::setStatus(UserStatus status) { TalkingUIChannelListener::TalkingUIChannelListener(const ClientUser &user, const Channel &channel) - : TalkingUIEntry(user.uiSession), m_channelID(channel.iId), m_name(user.qsName) { + : TalkingUIEntry(user.uiSession), m_backgroundWidget(new QWidget()), + m_backgroundWidgetStyleWrapper(m_backgroundWidget), m_channelID(channel.iId), m_name(user.qsName) { // Create background widget and its layout that we'll use to place all other // components on - m_backgroundWidget = new QWidget(); m_backgroundWidget->setProperty("selected", false); QLayout *backgroundLayout = new QHBoxLayout(); backgroundLayout->setContentsMargins(USER_CONTAINER_HORIZONTAL_MARGIN, USER_CONTAINER_VERTICAL_MARGIN, @@ -379,6 +385,10 @@ const QWidget *TalkingUIChannelListener::getWidget() const { return m_backgroundWidget; } +MultiStyleWidgetWrapper &TalkingUIChannelListener::getStylableWidget() { + return m_backgroundWidgetStyleWrapper; +} + void TalkingUIChannelListener::setIconSize(int size) { static const QIcon s_earIcon = QIcon(QLatin1String("skin:ear.svg")); diff --git a/src/mumble/TalkingUIEntry.h b/src/mumble/TalkingUIEntry.h index b30051cd6..e7eb723d3 100644 --- a/src/mumble/TalkingUIEntry.h +++ b/src/mumble/TalkingUIEntry.h @@ -8,6 +8,7 @@ #include "Settings.h" #include "TalkingUIComponent.h" +#include "widgets/MultiStyleWidgetWrapper.h" #include #include @@ -70,6 +71,7 @@ public: class TalkingUIUser : public TalkingUIEntry { protected: QWidget *m_backgroundWidget = nullptr; + MultiStyleWidgetWrapper m_backgroundWidgetStyleWrapper; QLabel *m_talkingIcon = nullptr; QLabel *m_nameLabel = nullptr; @@ -97,6 +99,7 @@ public: virtual QWidget *getWidget() override; virtual const QWidget *getWidget() const override; + virtual MultiStyleWidgetWrapper &getStylableWidget() override; virtual EntryType getType() const override; @@ -120,6 +123,7 @@ public: class TalkingUIChannelListener : public TalkingUIEntry { protected: QWidget *m_backgroundWidget = nullptr; + MultiStyleWidgetWrapper m_backgroundWidgetStyleWrapper; QLabel *m_icon = nullptr; QLabel *m_nameLabel = nullptr; @@ -135,6 +139,7 @@ public: virtual QWidget *getWidget() override; virtual const QWidget *getWidget() const override; + MultiStyleWidgetWrapper &getStylableWidget() override; virtual void setIconSize(int size) override; diff --git a/src/mumble/widgets/MultiStyleWidgetWrapper.cpp b/src/mumble/widgets/MultiStyleWidgetWrapper.cpp new file mode 100644 index 000000000..fd7795cbf --- /dev/null +++ b/src/mumble/widgets/MultiStyleWidgetWrapper.cpp @@ -0,0 +1,90 @@ +// Copyright 2020 The Mumble Developers. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file at the root of the +// Mumble source tree or at . + +#include "MultiStyleWidgetWrapper.h" + +#include +#include +#include + +const uint32_t MultiStyleWidgetWrapper::UNSET_FONTSIZE = 0; +const QString MultiStyleWidgetWrapper::UNSET_COLOR = ""; +const QString MultiStyleWidgetWrapper::UNSET_SELECTOR = "*"; + +MultiStyleWidgetWrapper::MultiStyleWidgetWrapper(QWidget *widget) : m_widget(widget) { +} + +void MultiStyleWidgetWrapper::setFontSize(uint32_t fontSize, bool isPixels) { + if (!isPixels) { + // Convert the font size to pixels + QFont font; + font.setPixelSize(fontSize); + + fontSize = QFontMetrics(font).height(); + } + + if (fontSize != m_fontSize) { + m_fontSize = fontSize; + + updateStyleSheet(); + } +} + +void MultiStyleWidgetWrapper::setFontSizeSelector(const QString &selector) { + if (m_fontSizeSelector != selector) { + m_fontSizeSelector = selector; + + updateStyleSheet(); + } +} + +void MultiStyleWidgetWrapper::setBackgroundColor(const QString &color) { + if (m_backgroundColor != color) { + m_backgroundColor = color; + + updateStyleSheet(); + } +} + +void MultiStyleWidgetWrapper::setBackgroundColorSelector(const QString &selector) { + if (m_backgroundColorSelector != selector) { + m_backgroundColorSelector = selector; + + updateStyleSheet(); + } +} + +void MultiStyleWidgetWrapper::clearFontSize() { + setFontSize(UNSET_FONTSIZE); +} + +void MultiStyleWidgetWrapper::clearFontSizeSelector() { + setFontSizeSelector(UNSET_SELECTOR); +} + +void MultiStyleWidgetWrapper::clearBackgroundColor() { + setBackgroundColor(UNSET_COLOR); +} + +void MultiStyleWidgetWrapper::clearBackgroundColorSelector() { + setBackgroundColorSelector(UNSET_SELECTOR); +} + +void MultiStyleWidgetWrapper::updateStyleSheet() { + QString styleSheet; + + if (m_fontSize != UNSET_FONTSIZE) { + styleSheet += QString(" %1 { font-size: %2px; }").arg(m_fontSizeSelector).arg(m_fontSize); + } + if (m_backgroundColor != UNSET_COLOR) { + styleSheet += QString(" %1 { background-color: %2; }").arg(m_backgroundColorSelector).arg(m_backgroundColor); + } + + m_widget->setStyleSheet(styleSheet); +} + +QWidget *MultiStyleWidgetWrapper::operator->() { + return m_widget; +} diff --git a/src/mumble/widgets/MultiStyleWidgetWrapper.h b/src/mumble/widgets/MultiStyleWidgetWrapper.h new file mode 100644 index 000000000..3ca40af5e --- /dev/null +++ b/src/mumble/widgets/MultiStyleWidgetWrapper.h @@ -0,0 +1,52 @@ +// Copyright 2020 The Mumble Developers. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file at the root of the +// Mumble source tree or at . + +#ifndef MUMBLE_MUMBLE_WIDGETS_MULTISTYLEWIDGETWRAPPER_H_ +#define MUMBLE_MUMBLE_WIDGETS_MULTISTYLEWIDGETWRAPPER_H_ + +#include + +#include + +class QWidget; + +/// Purpose of this class is to work around the fact that normally it is not possible to +/// set single properties within a widget's stylesheet without potentially overwriting other +/// properties set through it. +class MultiStyleWidgetWrapper { + public: + void setFontSize(uint32_t fontSize, bool isPixels = true); + void setFontSizeSelector(const QString &selector); + void setBackgroundColor(const QString &color); + void setBackgroundColorSelector(const QString &selector); + + void clearFontSize(); + void clearFontSizeSelector(); + void clearBackgroundColor(); + void clearBackgroundColorSelector(); + + QWidget *operator->(); + + MultiStyleWidgetWrapper(QWidget *widget); + + protected: + static const uint32_t UNSET_FONTSIZE; + static const QString UNSET_COLOR; + static const QString UNSET_SELECTOR; + /// The wrapped widget + QWidget *m_widget; + /// The current font size in pixels + uint32_t m_fontSize = UNSET_FONTSIZE; + /// The CSS selector to be applied to the font size + QString m_fontSizeSelector = UNSET_SELECTOR; + /// The current background color + QString m_backgroundColor = UNSET_COLOR; + /// The CSS selector to be applied to the background color + QString m_backgroundColorSelector = UNSET_SELECTOR; + + void updateStyleSheet(); +}; + +#endif // MUMBLE_MUMBLE_WIDGETS_MULTISTYLEWIDGETWRAPPER_H_