Merge pull request #4634: 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.
This commit is contained in:
Robert Adam 2020-12-19 22:54:59 +01:00 committed by GitHub
commit 8c0f88c4f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 195 additions and 18 deletions

View File

@ -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"

View File

@ -10,6 +10,7 @@
#include "MainWindow.h"
#include "TalkingUIComponent.h"
#include "UserModel.h"
#include "widgets/MultiStyleWidgetWrapper.h"
#include <QGroupBox>
#include <QGuiApplication>
@ -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;

View File

@ -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)
///

View File

@ -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_

View File

@ -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;
}

View File

@ -9,6 +9,8 @@
#include "TalkingUIComponent.h"
#include "TalkingUIEntry.h"
#include "widgets/MultiStyleWidgetWrapper.h"
#include <QString>
#include <memory>
@ -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;

View File

@ -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<int>(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<int>(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"));

View File

@ -8,6 +8,7 @@
#include "Settings.h"
#include "TalkingUIComponent.h"
#include "widgets/MultiStyleWidgetWrapper.h"
#include <QIcon>
#include <QLatin1String>
@ -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;

View File

@ -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 <https://www.mumble.info/LICENSE>.
#include "MultiStyleWidgetWrapper.h"
#include <QWidget>
#include <QFont>
#include <QFontMetrics>
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;
}

View File

@ -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 <https://www.mumble.info/LICENSE>.
#ifndef MUMBLE_MUMBLE_WIDGETS_MULTISTYLEWIDGETWRAPPER_H_
#define MUMBLE_MUMBLE_WIDGETS_MULTISTYLEWIDGETWRAPPER_H_
#include <cstdint>
#include <QString>
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_