Refresh custom colors once color scheme is changed

PR #20588.
This commit is contained in:
Vladimir Golovnev 2024-03-23 11:32:07 +03:00 committed by GitHub
parent 25b7972f88
commit ce013f132f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 195 additions and 126 deletions

View file

@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2020 Prince Gupta <jagannatharjun11@gmail.com>
* Copyright (C) 2019 sledgehammer999 <hammered999@gmail.com>
*
@ -36,65 +37,26 @@
#include "base/global.h"
#include "gui/uithememanager.h"
namespace
{
const int MAX_VISIBLE_MESSAGES = 20000;
const int MAX_VISIBLE_MESSAGES = 20000;
QColor getTimestampColor()
{
return UIThemeManager::instance()->getColor(u"Log.TimeStamp"_s);
}
QColor getLogNormalColor()
{
return UIThemeManager::instance()->getColor(u"Log.Normal"_s);
}
QColor getLogInfoColor()
{
return UIThemeManager::instance()->getColor(u"Log.Info"_s);
}
QColor getLogWarningColor()
{
return UIThemeManager::instance()->getColor(u"Log.Warning"_s);
}
QColor getLogCriticalColor()
{
return UIThemeManager::instance()->getColor(u"Log.Critical"_s);
}
QColor getPeerBannedColor()
{
return UIThemeManager::instance()->getColor(u"Log.BannedPeer"_s);
}
}
BaseLogModel::Message::Message(const QString &time, const QString &message, const QColor &foreground, const Log::MsgType type)
: m_time(time)
, m_message(message)
, m_foreground(foreground)
, m_type(type)
BaseLogModel::Message::Message(const QString &time, const QString &message, const Log::MsgType type)
: m_time {time}
, m_message {message}
, m_type {type}
{
}
QVariant BaseLogModel::Message::time() const
QString BaseLogModel::Message::time() const
{
return m_time;
}
QVariant BaseLogModel::Message::message() const
QString BaseLogModel::Message::message() const
{
return m_message;
}
QVariant BaseLogModel::Message::foreground() const
{
return m_foreground;
}
QVariant BaseLogModel::Message::type() const
Log::MsgType BaseLogModel::Message::type() const
{
return m_type;
}
@ -102,8 +64,9 @@ QVariant BaseLogModel::Message::type() const
BaseLogModel::BaseLogModel(QObject *parent)
: QAbstractListModel(parent)
, m_messages(MAX_VISIBLE_MESSAGES)
, m_timeForeground(getTimestampColor())
{
loadColors();
connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, &BaseLogModel::onUIThemeChanged);
}
int BaseLogModel::rowCount(const QModelIndex &) const
@ -135,7 +98,7 @@ QVariant BaseLogModel::data(const QModelIndex &index, const int role) const
case TimeForegroundRole:
return m_timeForeground;
case MessageForegroundRole:
return message.foreground();
return messageForeground(message);
case TypeRole:
return message.type();
default:
@ -160,6 +123,17 @@ void BaseLogModel::addNewMessage(const BaseLogModel::Message &message)
endInsertRows();
}
void BaseLogModel::onUIThemeChanged()
{
loadColors();
emit dataChanged(index(0, 0), index((rowCount() - 1), (columnCount() - 1)), {TimeForegroundRole, MessageForegroundRole});
}
void BaseLogModel::loadColors()
{
m_timeForeground = UIThemeManager::instance()->getColor(u"Log.TimeStamp"_s);
}
void BaseLogModel::reset()
{
beginResetModel();
@ -169,14 +143,9 @@ void BaseLogModel::reset()
LogMessageModel::LogMessageModel(QObject *parent)
: BaseLogModel(parent)
, m_foregroundForMessageTypes
{
{Log::NORMAL, getLogNormalColor()},
{Log::INFO, getLogInfoColor()},
{Log::WARNING, getLogWarningColor()},
{Log::CRITICAL, getLogCriticalColor()}
}
{
loadColors();
for (const Log::Msg &msg : asConst(Logger::instance()->getMessages()))
handleNewMessage(msg);
connect(Logger::instance(), &Logger::newLogMessage, this, &LogMessageModel::handleNewMessage);
@ -185,16 +154,38 @@ LogMessageModel::LogMessageModel(QObject *parent)
void LogMessageModel::handleNewMessage(const Log::Msg &message)
{
const QString time = QLocale::system().toString(QDateTime::fromSecsSinceEpoch(message.timestamp), QLocale::ShortFormat);
const QString messageText = message.message;
const QColor foreground = m_foregroundForMessageTypes[message.type];
addNewMessage({time, message.message, message.type});
}
addNewMessage({time, messageText, foreground, message.type});
QColor LogMessageModel::messageForeground(const Message &message) const
{
return m_foregroundForMessageTypes.value(message.type());
}
void LogMessageModel::onUIThemeChanged()
{
loadColors();
BaseLogModel::onUIThemeChanged();
}
void LogMessageModel::loadColors()
{
const auto *themeManager = UIThemeManager::instance();
const QColor normalColor = themeManager->getColor(u"Log.Normal"_s);
m_foregroundForMessageTypes =
{
{Log::NORMAL, normalColor.isValid() ? normalColor : QApplication::palette().color(QPalette::Active, QPalette::WindowText)},
{Log::INFO, themeManager->getColor(u"Log.Info"_s)},
{Log::WARNING, themeManager->getColor(u"Log.Warning"_s)},
{Log::CRITICAL, themeManager->getColor(u"Log.Critical"_s)}
};
}
LogPeerModel::LogPeerModel(QObject *parent)
: BaseLogModel(parent)
, m_bannedPeerForeground(getPeerBannedColor())
{
loadColors();
for (const Log::Peer &peer : asConst(Logger::instance()->getPeers()))
handleNewMessage(peer);
connect(Logger::instance(), &Logger::newLogPeer, this, &LogPeerModel::handleNewMessage);
@ -207,5 +198,21 @@ void LogPeerModel::handleNewMessage(const Log::Peer &peer)
? tr("%1 was blocked. Reason: %2.", "0.0.0.0 was blocked. Reason: reason for blocking.").arg(peer.ip, peer.reason)
: tr("%1 was banned", "0.0.0.0 was banned").arg(peer.ip);
addNewMessage({time, message, m_bannedPeerForeground, Log::NORMAL});
addNewMessage({time, message, Log::NORMAL});
}
QColor LogPeerModel::messageForeground([[maybe_unused]] const Message &message) const
{
return m_bannedPeerForeground;
}
void LogPeerModel::onUIThemeChanged()
{
loadColors();
BaseLogModel::onUIThemeChanged();
}
void LogPeerModel::loadColors()
{
m_bannedPeerForeground = UIThemeManager::instance()->getColor(u"Log.BannedPeer"_s);
}

View file

@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2020 Prince Gupta <jagannatharjun11@gmail.com>
* Copyright (C) 2019 sledgehammer999 <hammered999@gmail.com>
*
@ -62,25 +63,27 @@ protected:
class Message
{
public:
Message(const QString &time, const QString &message, const QColor &foreground, Log::MsgType type);
Message(const QString &time, const QString &message, Log::MsgType type);
QVariant time() const;
QVariant message() const;
QVariant foreground() const;
QVariant type() const;
QString time() const;
QString message() const;
Log::MsgType type() const;
private:
QVariant m_time;
QVariant m_message;
QVariant m_foreground;
QVariant m_type;
QString m_time;
QString m_message;
Log::MsgType m_type;
};
void addNewMessage(const Message &message);
virtual QColor messageForeground(const Message &message) const = 0;
virtual void onUIThemeChanged();
private:
void loadColors();
boost::circular_buffer_space_optimized<Message> m_messages;
const QColor m_timeForeground;
QColor m_timeForeground;
};
class LogMessageModel : public BaseLogModel
@ -95,7 +98,11 @@ private slots:
void handleNewMessage(const Log::Msg &message);
private:
const QHash<int, QColor> m_foregroundForMessageTypes;
QColor messageForeground(const Message &message) const override;
void onUIThemeChanged() override;
void loadColors();
QHash<int, QColor> m_foregroundForMessageTypes;
};
class LogPeerModel : public BaseLogModel
@ -110,5 +117,9 @@ private slots:
void handleNewMessage(const Log::Peer &peer);
private:
const QColor m_bannedPeerForeground;
QColor messageForeground(const Message &message) const override;
void onUIThemeChanged() override;
void loadColors();
QColor m_bannedPeerForeground;
};

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2017-2024 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -28,6 +28,7 @@
#include "articlelistwidget.h"
#include <QApplication>
#include <QListWidgetItem>
#include "base/global.h"
@ -42,6 +43,12 @@ ArticleListWidget::ArticleListWidget(QWidget *parent)
setSelectionMode(QAbstractItemView::ExtendedSelection);
checkInvariant();
connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, [this]
{
for (int row = 0; row < count(); ++row)
applyUITheme(item(row));
});
}
RSS::Article *ArticleListWidget::getRSSArticle(QListWidgetItem *item) const
@ -100,11 +107,10 @@ void ArticleListWidget::handleArticleAdded(RSS::Article *rssArticle)
void ArticleListWidget::handleArticleRead(RSS::Article *rssArticle)
{
auto *item = mapRSSArticle(rssArticle);
if (!item) return;
if (!item)
return;
const QBrush foregroundBrush {UIThemeManager::instance()->getColor(u"RSS.ReadArticle"_s)};
item->setData(Qt::ForegroundRole, foregroundBrush);
item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"rss_read_article"_s, u"sphere"_s));
applyUITheme(item);
checkInvariant();
}
@ -127,18 +133,25 @@ QListWidgetItem *ArticleListWidget::createItem(RSS::Article *article) const
item->setData(Qt::DisplayRole, article->title());
item->setData(Qt::UserRole, QVariant::fromValue(article));
if (article->isRead())
{
const QBrush foregroundBrush {UIThemeManager::instance()->getColor(u"RSS.ReadArticle"_s)};
item->setData(Qt::ForegroundRole, foregroundBrush);
item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"rss_read_article"_s, u"sphere"_s));
}
else
{
const QBrush foregroundBrush {UIThemeManager::instance()->getColor(u"RSS.UnreadArticle"_s)};
item->setData(Qt::ForegroundRole, foregroundBrush);
item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"rss_unread_article"_s, u"sphere"_s));
}
applyUITheme(item);
return item;
}
void ArticleListWidget::applyUITheme(QListWidgetItem *item) const
{
const bool isRead = getRSSArticle(item)->isRead();
const auto *themeManager = UIThemeManager::instance();
if (isRead)
{
const QColor color = themeManager->getColor(u"RSS.ReadArticle"_s);
item->setData(Qt::ForegroundRole, (color.isValid() ? color : QApplication::palette().color(QPalette::Inactive, QPalette::WindowText)));
item->setData(Qt::DecorationRole, themeManager->getIcon(u"rss_read_article"_s, u"sphere"_s));
}
else
{
const QColor color = themeManager->getColor(u"RSS.UnreadArticle"_s);
item->setData(Qt::ForegroundRole, (color.isValid() ? color : QApplication::palette().color(QPalette::Active, QPalette::Link)));
item->setData(Qt::DecorationRole, themeManager->getIcon(u"rss_unread_article"_s, u"sphere"_s));
}
}

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2017-2024 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -58,6 +58,7 @@ private slots:
private:
void checkInvariant() const;
QListWidgetItem *createItem(RSS::Article *article) const;
void applyUITheme(QListWidgetItem *item) const;
RSS::Item *m_rssItem = nullptr;
bool m_unreadOnly = false;

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -112,21 +112,17 @@ TransferListModel::TransferListModel(QObject *parent)
{BitTorrent::TorrentState::MissingFiles, tr("Missing Files")},
{BitTorrent::TorrentState::Error, tr("Errored", "Torrent status, the torrent has an error")}
}
, m_stateThemeColors {torrentStateColorsFromUITheme()}
, m_checkingIcon {UIThemeManager::instance()->getIcon(u"force-recheck"_s, u"checking"_s)}
, m_completedIcon {UIThemeManager::instance()->getIcon(u"checked-completed"_s, u"completed"_s)}
, m_downloadingIcon {UIThemeManager::instance()->getIcon(u"downloading"_s)}
, m_errorIcon {UIThemeManager::instance()->getIcon(u"error"_s)}
, m_movingIcon {UIThemeManager::instance()->getIcon(u"set-location"_s)}
, m_pausedIcon {UIThemeManager::instance()->getIcon(u"stopped"_s, u"media-playback-pause"_s)}
, m_queuedIcon {UIThemeManager::instance()->getIcon(u"queued"_s)}
, m_stalledDLIcon {UIThemeManager::instance()->getIcon(u"stalledDL"_s)}
, m_stalledUPIcon {UIThemeManager::instance()->getIcon(u"stalledUP"_s)}
, m_uploadingIcon {UIThemeManager::instance()->getIcon(u"upload"_s, u"uploading"_s)}
{
configure();
connect(Preferences::instance(), &Preferences::changed, this, &TransferListModel::configure);
loadUIThemeResources();
connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, [this]
{
loadUIThemeResources();
emit dataChanged(index(0, 0), index((rowCount() - 1), (columnCount() - 1)), {Qt::DecorationRole, Qt::ForegroundRole});
});
// Load the torrents
using namespace BitTorrent;
addTorrents(Session::instance()->torrents());
@ -699,6 +695,23 @@ void TransferListModel::configure()
}
}
void TransferListModel::loadUIThemeResources()
{
m_stateThemeColors = torrentStateColorsFromUITheme();
const auto *themeManager = UIThemeManager::instance();
m_checkingIcon = themeManager->getIcon(u"force-recheck"_s, u"checking"_s);
m_completedIcon = themeManager->getIcon(u"checked-completed"_s, u"completed"_s);
m_downloadingIcon = themeManager->getIcon(u"downloading"_s);
m_errorIcon = themeManager->getIcon(u"error"_s);
m_movingIcon = themeManager->getIcon(u"set-location"_s);
m_pausedIcon = themeManager->getIcon(u"stopped"_s, u"media-playback-pause"_s);
m_queuedIcon = themeManager->getIcon(u"queued"_s);
m_stalledDLIcon = themeManager->getIcon(u"stalledDL"_s);
m_stalledUPIcon = themeManager->getIcon(u"stalledUP"_s);
m_uploadingIcon = themeManager->getIcon(u"upload"_s, u"uploading"_s);
}
QIcon TransferListModel::getIconByState(const BitTorrent::TorrentState state) const
{
switch (state)

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -114,6 +114,7 @@ private slots:
private:
void configure();
void loadUIThemeResources();
QString displayValue(const BitTorrent::Torrent *torrent, int column) const;
QVariant internalValue(const BitTorrent::Torrent *torrent, int column, bool alt) const;
QIcon getIconByState(BitTorrent::TorrentState state) const;
@ -122,7 +123,7 @@ private:
QHash<BitTorrent::Torrent *, int> m_torrentMap; // maps torrent handle to row number
const QHash<BitTorrent::TorrentState, QString> m_statusStrings;
// row text colors
const QHash<BitTorrent::TorrentState, QColor> m_stateThemeColors;
QHash<BitTorrent::TorrentState, QColor> m_stateThemeColors;
enum class HideZeroValuesMode
{

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2023-2024 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -29,7 +29,6 @@
#pragma once
#include <QApplication>
#include <QColor>
#include <QHash>
#include <QPalette>
@ -53,17 +52,16 @@ struct UIThemeColor
inline QHash<QString, UIThemeColor> defaultUIThemeColors()
{
const QPalette palette = QApplication::palette();
return {
{u"Log.TimeStamp"_s, {Color::Primer::Light::fgSubtle, Color::Primer::Dark::fgSubtle}},
{u"Log.Normal"_s, {palette.color(QPalette::Active, QPalette::WindowText), palette.color(QPalette::Active, QPalette::WindowText)}},
{u"Log.Normal"_s, {{}, {}}},
{u"Log.Info"_s, {Color::Primer::Light::accentFg, Color::Primer::Dark::accentFg}},
{u"Log.Warning"_s, {Color::Primer::Light::severeFg, Color::Primer::Dark::severeFg}},
{u"Log.Critical"_s, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}},
{u"Log.BannedPeer"_s, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}},
{u"RSS.ReadArticle"_s, {palette.color(QPalette::Inactive, QPalette::WindowText), palette.color(QPalette::Inactive, QPalette::WindowText)}},
{u"RSS.UnreadArticle"_s, {palette.color(QPalette::Active, QPalette::Link), palette.color(QPalette::Active, QPalette::Link)}},
{u"RSS.ReadArticle"_s, {{}, {}}},
{u"RSS.UnreadArticle"_s, {{}, {}}},
{u"TransferList.Downloading"_s, {Color::Primer::Light::successFg, Color::Primer::Dark::successFg}},
{u"TransferList.StalledDownloading"_s, {Color::Primer::Light::successEmphasis, Color::Primer::Dark::successEmphasis}},

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2023-2024 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -64,21 +64,23 @@ namespace
}
}
class ColorWidget final : public QFrame
class ColorWidget final : public QLabel
{
Q_DISABLE_COPY_MOVE(ColorWidget)
Q_DECLARE_TR_FUNCTIONS(ColorWidget)
public:
explicit ColorWidget(const QColor &currentColor, const QColor &defaultColor, QWidget *parent = nullptr)
: QFrame(parent)
: QLabel(parent)
, m_defaultColor {defaultColor}
, m_currentColor {currentColor}
{
setObjectName(u"colorWidget"_s);
setFrameShape(QFrame::Box);
setFrameShadow(QFrame::Plain);
setAlignment(Qt::AlignCenter);
setCurrentColor(currentColor);
applyColor(currentColor);
}
QColor currentColor() const
@ -118,8 +120,17 @@ private:
}
void applyColor(const QColor &color)
{
if (color.isValid())
{
setStyleSheet(u"#colorWidget { background-color: %1; }"_s.arg(color.name()));
setText({});
}
else
{
setStyleSheet({});
setText(tr("System"));
}
}
void showColorDialog()
@ -262,6 +273,8 @@ void UIThemeDialog::loadColors()
int row = 2;
for (const QString &id : colorIDs)
{
if (id == u"Log.Normal")
qDebug() << "!!!!!!!";
m_ui->colorsLayout->addWidget(new QLabel(id), row, 0);
const UIThemeColor &defaultColor = defaultColors.value(id);

View file

@ -30,6 +30,7 @@
#include "uithememanager.h"
#include <QApplication>
#include <QPalette>
#include <QPixmapCache>
#include <QResource>
@ -79,11 +80,8 @@ UIThemeManager::UIThemeManager()
, m_useSystemIcons {Preferences::instance()->useSystemIcons()}
#endif
{
connect(QApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, []
{
// workaround to refresh styled controls once color scheme is changed
QApplication::setStyle(QApplication::style()->name());
});
// NOTE: Qt::QueuedConnection can be omitted as soon as support for Qt 6.5 is dropped
connect(QApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, &UIThemeManager::onColorSchemeChanged, Qt::QueuedConnection);
if (m_useCustomTheme)
{
@ -122,6 +120,14 @@ void UIThemeManager::applyStyleSheet() const
qApp->setStyleSheet(QString::fromUtf8(m_themeSource->readStyleSheet()));
}
void UIThemeManager::onColorSchemeChanged()
{
emit themeChanged();
// workaround to refresh styled controls once color scheme is changed
QApplication::setStyle(QApplication::style()->name());
}
QIcon UIThemeManager::getIcon(const QString &iconId, [[maybe_unused]] const QString &fallback) const
{
const auto colorMode = isDarkTheme() ? ColorMode::Dark : ColorMode::Light;
@ -184,8 +190,6 @@ QPixmap UIThemeManager::getScaledPixmap(const QString &iconId, const int height)
QColor UIThemeManager::getColor(const QString &id) const
{
const QColor color = m_themeSource->getColor(id, (isDarkTheme() ? ColorMode::Dark : ColorMode::Light));
Q_ASSERT(color.isValid());
return color;
}

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2023-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2019 Prince Gupta <jagannatharjun11@gmail.com>
*
* This program is free software; you can redistribute it and/or
@ -38,7 +38,6 @@
#include <QPixmap>
#include <QString>
#include "base/pathfwd.h"
#include "uithemesource.h"
class UIThemeManager final : public QObject
@ -57,11 +56,15 @@ public:
QColor getColor(const QString &id) const;
signals:
void themeChanged();
private:
UIThemeManager(); // singleton class
void applyPalette() const;
void applyStyleSheet() const;
void onColorSchemeChanged();
static UIThemeManager *m_instance;
const bool m_useCustomTheme;

View file

@ -114,8 +114,13 @@ QByteArray DefaultThemeSource::readStyleSheet()
QColor DefaultThemeSource::getColor(const QString &colorId, const ColorMode colorMode) const
{
const auto iter = m_colors.constFind(colorId);
Q_ASSERT(iter != m_colors.constEnd());
if (iter == m_colors.constEnd()) [[unlikely]]
return {};
return (colorMode == ColorMode::Light)
? m_colors.value(colorId).light : m_colors.value(colorId).dark;
? iter.value().light : iter.value().dark;
}
Path DefaultThemeSource::getIconPath(const QString &iconId, const ColorMode colorMode) const