Vladimir Golovnev
2 years ago
committed by
GitHub
15 changed files with 1381 additions and 380 deletions
@ -0,0 +1,181 @@
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent. |
||||
* Copyright (C) 2023 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 |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, |
||||
* USA. |
||||
* |
||||
* In addition, as a special exception, the copyright holders give permission to |
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with |
||||
* modified versions of it that use the same license as the "OpenSSL" library), |
||||
* and distribute the linked executables. You must obey the GNU General Public |
||||
* License in all respects for all of the code used other than "OpenSSL". If |
||||
* you modify file(s), you may extend this exception to your version of the |
||||
* file(s), but you are not obligated to do so. If you do not wish to do so, |
||||
* delete this exception statement from your version. |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <QtGlobal> |
||||
#include <QApplication> |
||||
#include <QColor> |
||||
#include <QHash> |
||||
#include <QPalette> |
||||
#include <QSet> |
||||
#include <QString> |
||||
|
||||
#include "base/global.h" |
||||
#include "color.h" |
||||
|
||||
inline const QString CONFIG_FILE_NAME = u"config.json"_qs; |
||||
inline const QString STYLESHEET_FILE_NAME = u"stylesheet.qss"_qs; |
||||
inline const QString KEY_COLORS = u"colors"_qs; |
||||
inline const QString KEY_COLORS_LIGHT = u"colors.light"_qs; |
||||
inline const QString KEY_COLORS_DARK = u"colors.dark"_qs; |
||||
|
||||
struct UIThemeColor |
||||
{ |
||||
QColor light; |
||||
QColor dark; |
||||
}; |
||||
|
||||
inline QHash<QString, UIThemeColor> defaultUIThemeColors() |
||||
{ |
||||
const QPalette palette = QApplication::palette(); |
||||
return { |
||||
{u"Log.TimeStamp"_qs, {Color::Primer::Light::fgSubtle, Color::Primer::Dark::fgSubtle}}, |
||||
{u"Log.Normal"_qs, {palette.color(QPalette::Active, QPalette::WindowText), palette.color(QPalette::Active, QPalette::WindowText)}}, |
||||
{u"Log.Info"_qs, {Color::Primer::Light::accentFg, Color::Primer::Dark::accentFg}}, |
||||
{u"Log.Warning"_qs, {Color::Primer::Light::severeFg, Color::Primer::Dark::severeFg}}, |
||||
{u"Log.Critical"_qs, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}}, |
||||
{u"Log.BannedPeer"_qs, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}}, |
||||
|
||||
{u"RSS.ReadArticle"_qs, {palette.color(QPalette::Inactive, QPalette::WindowText), palette.color(QPalette::Inactive, QPalette::WindowText)}}, |
||||
{u"RSS.UnreadArticle"_qs, {palette.color(QPalette::Active, QPalette::Link), palette.color(QPalette::Active, QPalette::Link)}}, |
||||
|
||||
{u"TransferList.Downloading"_qs, {Color::Primer::Light::successFg, Color::Primer::Dark::successFg}}, |
||||
{u"TransferList.StalledDownloading"_qs, {Color::Primer::Light::successEmphasis, Color::Primer::Dark::successEmphasis}}, |
||||
{u"TransferList.DownloadingMetadata"_qs, {Color::Primer::Light::successFg, Color::Primer::Dark::successFg}}, |
||||
{u"TransferList.ForcedDownloadingMetadata"_qs, {Color::Primer::Light::successFg, Color::Primer::Dark::successFg}}, |
||||
{u"TransferList.ForcedDownloading"_qs, {Color::Primer::Light::successFg, Color::Primer::Dark::successFg}}, |
||||
{u"TransferList.Uploading"_qs, {Color::Primer::Light::accentFg, Color::Primer::Dark::accentFg}}, |
||||
{u"TransferList.StalledUploading"_qs, {Color::Primer::Light::accentEmphasis, Color::Primer::Dark::accentEmphasis}}, |
||||
{u"TransferList.ForcedUploading"_qs, {Color::Primer::Light::accentFg, Color::Primer::Dark::accentFg}}, |
||||
{u"TransferList.QueuedDownloading"_qs, {Color::Primer::Light::scaleYellow6, Color::Primer::Dark::scaleYellow6}}, |
||||
{u"TransferList.QueuedUploading"_qs, {Color::Primer::Light::scaleYellow6, Color::Primer::Dark::scaleYellow6}}, |
||||
{u"TransferList.CheckingDownloading"_qs, {Color::Primer::Light::successFg, Color::Primer::Dark::successFg}}, |
||||
{u"TransferList.CheckingUploading"_qs, {Color::Primer::Light::successFg, Color::Primer::Dark::successFg}}, |
||||
{u"TransferList.CheckingResumeData"_qs, {Color::Primer::Light::successFg, Color::Primer::Dark::successFg}}, |
||||
{u"TransferList.PausedDownloading"_qs, {Color::Primer::Light::fgMuted, Color::Primer::Dark::fgMuted}}, |
||||
{u"TransferList.PausedUploading"_qs, {Color::Primer::Light::doneFg, Color::Primer::Dark::doneFg}}, |
||||
{u"TransferList.Moving"_qs, {Color::Primer::Light::successFg, Color::Primer::Dark::successFg}}, |
||||
{u"TransferList.MissingFiles"_qs, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}}, |
||||
{u"TransferList.Error"_qs, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}} |
||||
}; |
||||
} |
||||
|
||||
inline QSet<QString> defaultUIThemeIcons() |
||||
{ |
||||
return { |
||||
u"application-exit"_qs, |
||||
u"application-rss"_qs, |
||||
u"application-url"_qs, |
||||
u"browser-cookies"_qs, |
||||
u"chart-line"_qs, |
||||
u"checked-completed"_qs, |
||||
u"configure"_qs, |
||||
u"connected"_qs, |
||||
u"dialog-warning"_qs, |
||||
u"directory"_qs, |
||||
u"disconnected"_qs, |
||||
u"download"_qs, |
||||
u"downloading"_qs, |
||||
u"edit-clear"_qs, |
||||
u"edit-copy"_qs, |
||||
u"edit-find"_qs, |
||||
u"edit-rename"_qs, |
||||
u"error"_qs, |
||||
u"fileicon"_qs, |
||||
u"filter-active"_qs, |
||||
u"filter-all"_qs, |
||||
u"filter-inactive"_qs, |
||||
u"filter-stalled"_qs, |
||||
u"firewalled"_qs, |
||||
u"folder-documents"_qs, |
||||
u"folder-new"_qs, |
||||
u"folder-remote"_qs, |
||||
u"force-recheck"_qs, |
||||
u"go-bottom"_qs, |
||||
u"go-down"_qs, |
||||
u"go-top"_qs, |
||||
u"go-up"_qs, |
||||
u"hash"_qs, |
||||
u"help-about"_qs, |
||||
u"help-contents"_qs, |
||||
u"insert-link"_qs, |
||||
u"ip-blocked"_qs, |
||||
u"list-add"_qs, |
||||
u"list-remove"_qs, |
||||
u"loading"_qs, |
||||
u"mail-inbox"_qs, |
||||
u"name"_qs, |
||||
u"network-connect"_qs, |
||||
u"network-server"_qs, |
||||
u"object-locked"_qs, |
||||
u"peers"_qs, |
||||
u"peers-add"_qs, |
||||
u"peers-remove"_qs, |
||||
u"plugins"_qs, |
||||
u"preferences-advanced"_qs, |
||||
u"preferences-bittorrent"_qs, |
||||
u"preferences-desktop"_qs, |
||||
u"preferences-webui"_qs, |
||||
u"qbittorrent-tray"_qs, |
||||
u"qbittorrent-tray-dark"_qs, |
||||
u"qbittorrent-tray-light"_qs, |
||||
u"queued"_qs, |
||||
u"ratio"_qs, |
||||
u"reannounce"_qs, |
||||
u"security-high"_qs, |
||||
u"security-low"_qs, |
||||
u"set-location"_qs, |
||||
u"slow"_qs, |
||||
u"slow_off"_qs, |
||||
u"speedometer"_qs, |
||||
u"stalledDL"_qs, |
||||
u"stalledUP"_qs, |
||||
u"stopped"_qs, |
||||
u"system-log-out"_qs, |
||||
u"tags"_qs, |
||||
u"task-complete"_qs, |
||||
u"task-reject"_qs, |
||||
u"torrent-creator"_qs, |
||||
u"torrent-magnet"_qs, |
||||
u"torrent-start"_qs, |
||||
u"torrent-start-forced"_qs, |
||||
u"torrent-stop"_qs, |
||||
u"tracker-error"_qs, |
||||
u"tracker-warning"_qs, |
||||
u"trackerless"_qs, |
||||
u"trackers"_qs, |
||||
u"upload"_qs, |
||||
u"view-categories"_qs, |
||||
u"view-preview"_qs, |
||||
u"view-refresh"_qs, |
||||
u"view-statistics"_qs, |
||||
u"wallet-open"_qs |
||||
}; |
||||
} |
@ -0,0 +1,388 @@
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent. |
||||
* Copyright (C) 2023 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 |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
* |
||||
* In addition, as a special exception, the copyright holders give permission to |
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with |
||||
* modified versions of it that use the same license as the "OpenSSL" library), |
||||
* and distribute the linked executables. You must obey the GNU General Public |
||||
* License in all respects for all of the code used other than "OpenSSL". If you |
||||
* modify file(s), you may extend this exception to your version of the file(s), |
||||
* but you are not obligated to do so. If you do not wish to do so, delete this |
||||
* exception statement from your version. |
||||
*/ |
||||
|
||||
#include "uithemedialog.h" |
||||
|
||||
#include <QColor> |
||||
#include <QColorDialog> |
||||
#include <QFile> |
||||
#include <QFileDialog> |
||||
#include <QJsonDocument> |
||||
#include <QJsonObject> |
||||
#include <QLabel> |
||||
#include <QMenu> |
||||
#include <QMessageBox> |
||||
|
||||
#include "base/3rdparty/expected.hpp" |
||||
#include "base/global.h" |
||||
#include "base/logger.h" |
||||
#include "base/path.h" |
||||
#include "base/profile.h" |
||||
#include "base/utils/fs.h" |
||||
#include "base/utils/io.h" |
||||
#include "uithemecommon.h" |
||||
#include "utils.h" |
||||
|
||||
#include "ui_uithemedialog.h" |
||||
|
||||
namespace |
||||
{ |
||||
Path userConfigPath() |
||||
{ |
||||
return specialFolderLocation(SpecialFolder::Config) / Path(u"themes/default"_qs); |
||||
} |
||||
|
||||
Path defaultIconPath(const QString &iconID, [[maybe_unused]] const ColorMode colorMode) |
||||
{ |
||||
return Path(u":icons"_qs) / Path(iconID + u".svg"); |
||||
} |
||||
} |
||||
|
||||
class ColorWidget final : public QFrame |
||||
{ |
||||
Q_DISABLE_COPY_MOVE(ColorWidget) |
||||
|
||||
public: |
||||
explicit ColorWidget(const QColor ¤tColor, const QColor &defaultColor, QWidget *parent = nullptr) |
||||
: QFrame(parent) |
||||
, m_defaultColor {defaultColor} |
||||
{ |
||||
setObjectName(u"colorWidget"_qs); |
||||
setFrameShape(QFrame::Box); |
||||
setFrameShadow(QFrame::Plain); |
||||
|
||||
setCurrentColor(currentColor); |
||||
} |
||||
|
||||
QColor currentColor() const |
||||
{ |
||||
return m_currentColor; |
||||
} |
||||
|
||||
private: |
||||
void mouseDoubleClickEvent([[maybe_unused]] QMouseEvent *event) override |
||||
{ |
||||
showColorDialog(); |
||||
} |
||||
|
||||
void contextMenuEvent([[maybe_unused]] QContextMenuEvent *event) override |
||||
{ |
||||
QMenu *menu = new QMenu(this); |
||||
menu->setAttribute(Qt::WA_DeleteOnClose); |
||||
|
||||
menu->addAction(tr("Edit..."), this, &ColorWidget::showColorDialog); |
||||
menu->addAction(tr("Reset"), this, &ColorWidget::resetColor); |
||||
|
||||
menu->popup(QCursor::pos()); |
||||
} |
||||
|
||||
void setCurrentColor(const QColor &color) |
||||
{ |
||||
if (m_currentColor == color) |
||||
return; |
||||
|
||||
m_currentColor = color; |
||||
applyColor(m_currentColor); |
||||
} |
||||
|
||||
void resetColor() |
||||
{ |
||||
setCurrentColor(m_defaultColor); |
||||
} |
||||
|
||||
void applyColor(const QColor &color) |
||||
{ |
||||
setStyleSheet(u"#colorWidget { background-color: %1; }"_qs.arg(color.name())); |
||||
} |
||||
|
||||
void showColorDialog() |
||||
{ |
||||
auto dialog = new QColorDialog(m_currentColor, this); |
||||
dialog->setAttribute(Qt::WA_DeleteOnClose); |
||||
connect(dialog, &QDialog::accepted, this, [this, dialog] |
||||
{ |
||||
setCurrentColor(dialog->currentColor()); |
||||
}); |
||||
|
||||
dialog->open(); |
||||
} |
||||
|
||||
const QColor m_defaultColor; |
||||
QColor m_currentColor; |
||||
}; |
||||
|
||||
class IconWidget final : public QLabel |
||||
{ |
||||
Q_DISABLE_COPY_MOVE(IconWidget) |
||||
|
||||
public: |
||||
explicit IconWidget(const Path ¤tPath, const Path &defaultPath, QWidget *parent = nullptr) |
||||
: QLabel(parent) |
||||
, m_defaultPath {defaultPath} |
||||
{ |
||||
setObjectName(u"iconWidget"_qs); |
||||
setAlignment(Qt::AlignCenter); |
||||
|
||||
setCurrentPath(currentPath); |
||||
} |
||||
|
||||
Path currentPath() const |
||||
{ |
||||
return m_currentPath; |
||||
} |
||||
|
||||
private: |
||||
void mouseDoubleClickEvent([[maybe_unused]] QMouseEvent *event) override |
||||
{ |
||||
showFileDialog(); |
||||
} |
||||
|
||||
void contextMenuEvent([[maybe_unused]] QContextMenuEvent *event) override |
||||
{ |
||||
QMenu *menu = new QMenu(this); |
||||
menu->setAttribute(Qt::WA_DeleteOnClose); |
||||
|
||||
menu->addAction(tr("Browse..."), this, &IconWidget::showFileDialog); |
||||
menu->addAction(tr("Reset"), this, &IconWidget::resetIcon); |
||||
|
||||
menu->popup(QCursor::pos()); |
||||
} |
||||
|
||||
void setCurrentPath(const Path &path) |
||||
{ |
||||
if (m_currentPath == path) |
||||
return; |
||||
|
||||
m_currentPath = path; |
||||
showIcon(m_currentPath); |
||||
} |
||||
|
||||
void resetIcon() |
||||
{ |
||||
setCurrentPath(m_defaultPath); |
||||
} |
||||
|
||||
void showIcon(const Path &iconPath) |
||||
{ |
||||
const QIcon icon {iconPath.data()}; |
||||
setPixmap(icon.pixmap(Utils::Gui::smallIconSize())); |
||||
} |
||||
|
||||
void showFileDialog() |
||||
{ |
||||
auto *dialog = new QFileDialog(this, tr("Select icon") |
||||
, QDir::homePath(), (tr("Supported image files") + u" (*.svg *.png)")); |
||||
dialog->setFileMode(QFileDialog::ExistingFile); |
||||
dialog->setAttribute(Qt::WA_DeleteOnClose); |
||||
connect(dialog, &QDialog::accepted, this, [this, dialog] |
||||
{ |
||||
const Path iconPath {dialog->selectedFiles().value(0)}; |
||||
setCurrentPath(iconPath); |
||||
}); |
||||
|
||||
dialog->open(); |
||||
} |
||||
|
||||
const Path m_defaultPath; |
||||
Path m_currentPath; |
||||
}; |
||||
|
||||
UIThemeDialog::UIThemeDialog(QWidget *parent) |
||||
: QDialog(parent) |
||||
, m_ui {new Ui::UIThemeDialog} |
||||
{ |
||||
m_ui->setupUi(this); |
||||
|
||||
loadColors(); |
||||
loadIcons(); |
||||
} |
||||
|
||||
UIThemeDialog::~UIThemeDialog() |
||||
{ |
||||
delete m_ui; |
||||
} |
||||
|
||||
void UIThemeDialog::accept() |
||||
{ |
||||
QDialog::accept(); |
||||
|
||||
bool hasError = false; |
||||
if (!storeColors()) |
||||
hasError = true; |
||||
if (!storeIcons()) |
||||
hasError = true; |
||||
|
||||
if (hasError) |
||||
{ |
||||
QMessageBox::critical(this, tr("UI Theme Configuration.") |
||||
, tr("The UI Theme changes could not be fully applied. The details can be found in the Log.")); |
||||
} |
||||
} |
||||
|
||||
void UIThemeDialog::loadColors() |
||||
{ |
||||
const QHash<QString, UIThemeColor> defaultColors = defaultUIThemeColors(); |
||||
const QList<QString> colorIDs = std::invoke([](auto &&list) { list.sort(); return list; }, defaultColors.keys()); |
||||
int row = 2; |
||||
for (const QString &id : colorIDs) |
||||
{ |
||||
m_ui->colorsLayout->addWidget(new QLabel(id), row, 0); |
||||
|
||||
const UIThemeColor &defaultColor = defaultColors.value(id); |
||||
|
||||
auto *lightColorWidget = new ColorWidget(m_defaultThemeSource.getColor(id, ColorMode::Light), defaultColor.light, this); |
||||
m_lightColorWidgets.insert(id, lightColorWidget); |
||||
m_ui->colorsLayout->addWidget(lightColorWidget, row, 2); |
||||
|
||||
auto *darkColorWidget = new ColorWidget(m_defaultThemeSource.getColor(id, ColorMode::Dark), defaultColor.dark, this); |
||||
m_darkColorWidgets.insert(id, darkColorWidget); |
||||
m_ui->colorsLayout->addWidget(darkColorWidget, row, 4); |
||||
|
||||
++row; |
||||
} |
||||
} |
||||
|
||||
void UIThemeDialog::loadIcons() |
||||
{ |
||||
const QSet<QString> defaultIcons = defaultUIThemeIcons(); |
||||
const QList<QString> iconIDs = std::invoke([](auto &&list) { list.sort(); return list; } |
||||
, QList<QString>(defaultIcons.cbegin(), defaultIcons.cend())); |
||||
int row = 2; |
||||
for (const QString &id : iconIDs) |
||||
{ |
||||
m_ui->iconsLayout->addWidget(new QLabel(id), row, 0); |
||||
|
||||
auto *lightIconWidget = new IconWidget(m_defaultThemeSource.getIconPath(id, ColorMode::Light) |
||||
, defaultIconPath(id, ColorMode::Light), this); |
||||
m_lightIconWidgets.insert(id, lightIconWidget); |
||||
m_ui->iconsLayout->addWidget(lightIconWidget, row, 2); |
||||
|
||||
auto *darkIconWidget = new IconWidget(m_defaultThemeSource.getIconPath(id, ColorMode::Dark) |
||||
, defaultIconPath(id, ColorMode::Dark), this); |
||||
m_darkIconWidgets.insert(id, darkIconWidget); |
||||
m_ui->iconsLayout->addWidget(darkIconWidget, row, 4); |
||||
|
||||
++row; |
||||
} |
||||
} |
||||
|
||||
bool UIThemeDialog::storeColors() |
||||
{ |
||||
QJsonObject userConfig; |
||||
userConfig.insert(u"version", 2); |
||||
|
||||
const QHash<QString, UIThemeColor> defaultColors = defaultUIThemeColors(); |
||||
const auto addColorOverrides = [this, &defaultColors, &userConfig](const ColorMode colorMode) |
||||
{ |
||||
const QHash<QString, ColorWidget *> &colorWidgets = (colorMode == ColorMode::Light) |
||||
? m_lightColorWidgets : m_darkColorWidgets; |
||||
|
||||
QJsonObject colors; |
||||
for (auto it = colorWidgets.cbegin(); it != colorWidgets.cend(); ++it) |
||||
{ |
||||
const QString &colorID = it.key(); |
||||
const QColor &defaultColor = (colorMode == ColorMode::Light) |
||||
? defaultColors.value(colorID).light : defaultColors.value(colorID).dark; |
||||
const QColor &color = it.value()->currentColor(); |
||||
if (color != defaultColor) |
||||
colors.insert(it.key(), color.name()); |
||||
} |
||||
|
||||
if (!colors.isEmpty()) |
||||
userConfig.insert(((colorMode == ColorMode::Light) ? KEY_COLORS_LIGHT : KEY_COLORS_DARK), colors); |
||||
}; |
||||
|
||||
addColorOverrides(ColorMode::Light); |
||||
addColorOverrides(ColorMode::Dark); |
||||
|
||||
const QByteArray configData = QJsonDocument(userConfig).toJson(); |
||||
const nonstd::expected<void, QString> result = Utils::IO::saveToFile((userConfigPath() / Path(CONFIG_FILE_NAME)), configData); |
||||
if (!result) |
||||
{ |
||||
const QString error = tr("Couldn't save UI Theme configuration. Reason: %1").arg(result.error()); |
||||
LogMsg(error, Log::WARNING); |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool UIThemeDialog::storeIcons() |
||||
{ |
||||
bool hasError = false; |
||||
|
||||
const auto updateIcons = [this, &hasError](const ColorMode colorMode) |
||||
{ |
||||
const QHash<QString, IconWidget *> &iconWidgets = (colorMode == ColorMode::Light) |
||||
? m_lightIconWidgets : m_darkIconWidgets; |
||||
const Path subdirPath = (colorMode == ColorMode::Light) |
||||
? Path(u"icons/light"_qs) : Path(u"icons/dark"_qs); |
||||
|
||||
for (auto it = iconWidgets.cbegin(); it != iconWidgets.cend(); ++it) |
||||
{ |
||||
const QString &id = it.key(); |
||||
const Path &path = it.value()->currentPath(); |
||||
if (path == m_defaultThemeSource.getIconPath(id, colorMode)) |
||||
continue; |
||||
|
||||
const Path &userIconPathBase = userConfigPath() / subdirPath / Path(id); |
||||
|
||||
if (const Path oldIconPath = userIconPathBase + u".svg" |
||||
; path.exists() && !Utils::Fs::removeFile(oldIconPath)) |
||||
{ |
||||
const QString error = tr("Couldn't remove icon file. File: %1.").arg(oldIconPath.toString()); |
||||
LogMsg(error, Log::WARNING); |
||||
hasError = true; |
||||
continue; |
||||
} |
||||
|
||||
if (const Path oldIconPath = userIconPathBase + u".png" |
||||
; path.exists() && !Utils::Fs::removeFile(oldIconPath)) |
||||
{ |
||||
const QString error = tr("Couldn't remove icon file. File: %1.").arg(oldIconPath.toString()); |
||||
LogMsg(error, Log::WARNING); |
||||
hasError = true; |
||||
continue; |
||||
} |
||||
|
||||
if (const Path targetPath = userIconPathBase + path.extension() |
||||
; !Utils::Fs::copyFile(path, targetPath)) |
||||
{ |
||||
const QString error = tr("Couldn't copy icon file. Source: %1. Destination: %2.") |
||||
.arg(path.toString(), targetPath.toString()); |
||||
LogMsg(error, Log::WARNING); |
||||
hasError = true; |
||||
} |
||||
} |
||||
}; |
||||
|
||||
updateIcons(ColorMode::Light); |
||||
updateIcons(ColorMode::Dark); |
||||
|
||||
return !hasError; |
||||
} |
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent. |
||||
* Copyright (C) 2023 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 |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
* |
||||
* In addition, as a special exception, the copyright holders give permission to |
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with |
||||
* modified versions of it that use the same license as the "OpenSSL" library), |
||||
* and distribute the linked executables. You must obey the GNU General Public |
||||
* License in all respects for all of the code used other than "OpenSSL". If you |
||||
* modify file(s), you may extend this exception to your version of the file(s), |
||||
* but you are not obligated to do so. If you do not wish to do so, delete this |
||||
* exception statement from your version. |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <QDialog> |
||||
#include <QHash> |
||||
#include <QString> |
||||
|
||||
#include "uithemesource.h" |
||||
|
||||
namespace Ui |
||||
{ |
||||
class UIThemeDialog; |
||||
} |
||||
|
||||
class ColorWidget; |
||||
class IconWidget; |
||||
|
||||
class UIThemeDialog final : public QDialog |
||||
{ |
||||
Q_OBJECT |
||||
Q_DISABLE_COPY_MOVE(UIThemeDialog) |
||||
|
||||
public: |
||||
explicit UIThemeDialog(QWidget *parent = nullptr); |
||||
~UIThemeDialog() override; |
||||
|
||||
void accept() override; |
||||
|
||||
private: |
||||
void loadColors(); |
||||
void loadIcons(); |
||||
bool storeColors(); |
||||
bool storeIcons(); |
||||
|
||||
Ui::UIThemeDialog *m_ui; |
||||
|
||||
DefaultThemeSource m_defaultThemeSource; |
||||
QHash<QString, ColorWidget *> m_lightColorWidgets; |
||||
QHash<QString, ColorWidget *> m_darkColorWidgets; |
||||
QHash<QString, IconWidget *> m_lightIconWidgets; |
||||
QHash<QString, IconWidget *> m_darkIconWidgets; |
||||
}; |
@ -0,0 +1,286 @@
@@ -0,0 +1,286 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<ui version="4.0"> |
||||
<class>UIThemeDialog</class> |
||||
<widget class="QDialog" name="UIThemeDialog"> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>451</width> |
||||
<height>348</height> |
||||
</rect> |
||||
</property> |
||||
<property name="windowTitle"> |
||||
<string>UI Theme Configuration</string> |
||||
</property> |
||||
<layout class="QVBoxLayout" name="verticalLayout"> |
||||
<item> |
||||
<widget class="QTabWidget" name="tabWidget"> |
||||
<property name="sizePolicy"> |
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> |
||||
<horstretch>0</horstretch> |
||||
<verstretch>0</verstretch> |
||||
</sizepolicy> |
||||
</property> |
||||
<property name="currentIndex"> |
||||
<number>0</number> |
||||
</property> |
||||
<property name="tabBarAutoHide"> |
||||
<bool>true</bool> |
||||
</property> |
||||
<widget class="QWidget" name="colorsTab"> |
||||
<attribute name="title"> |
||||
<string>Colors</string> |
||||
</attribute> |
||||
<layout class="QVBoxLayout" name="verticalLayout_2"> |
||||
<property name="leftMargin"> |
||||
<number>0</number> |
||||
</property> |
||||
<property name="topMargin"> |
||||
<number>0</number> |
||||
</property> |
||||
<property name="rightMargin"> |
||||
<number>0</number> |
||||
</property> |
||||
<property name="bottomMargin"> |
||||
<number>0</number> |
||||
</property> |
||||
<item> |
||||
<widget class="QScrollArea" name="colorsScrollArea"> |
||||
<property name="sizePolicy"> |
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> |
||||
<horstretch>0</horstretch> |
||||
<verstretch>0</verstretch> |
||||
</sizepolicy> |
||||
</property> |
||||
<property name="frameShape"> |
||||
<enum>QFrame::NoFrame</enum> |
||||
</property> |
||||
<property name="sizeAdjustPolicy"> |
||||
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum> |
||||
</property> |
||||
<property name="widgetResizable"> |
||||
<bool>true</bool> |
||||
</property> |
||||
<widget class="QWidget" name="scrollAreaWidgetContents"> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>427</width> |
||||
<height>271</height> |
||||
</rect> |
||||
</property> |
||||
<layout class="QVBoxLayout" name="verticalLayout_3"> |
||||
<item> |
||||
<layout class="QGridLayout" name="colorsLayout" columnstretch="1,0,0,0,0" columnminimumwidth="0,15,0,15,0"> |
||||
<item row="0" column="0"> |
||||
<widget class="QLabel" name="colorIDLabel"> |
||||
<property name="font"> |
||||
<font> |
||||
<bold>true</bold> |
||||
</font> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Color ID</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="0" column="2"> |
||||
<widget class="QLabel" name="lightModeColorLabel"> |
||||
<property name="font"> |
||||
<font> |
||||
<bold>true</bold> |
||||
</font> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Light Mode</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="0" column="4"> |
||||
<widget class="QLabel" name="darkModeColorLabel"> |
||||
<property name="font"> |
||||
<font> |
||||
<bold>true</bold> |
||||
</font> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Dark Mode</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<spacer name="colorsSpacer"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Vertical</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>20</width> |
||||
<height>203</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<widget class="QWidget" name="iconsTab"> |
||||
<attribute name="title"> |
||||
<string>Icons</string> |
||||
</attribute> |
||||
<layout class="QVBoxLayout" name="verticalLayout_5"> |
||||
<property name="leftMargin"> |
||||
<number>0</number> |
||||
</property> |
||||
<property name="topMargin"> |
||||
<number>0</number> |
||||
</property> |
||||
<property name="rightMargin"> |
||||
<number>0</number> |
||||
</property> |
||||
<property name="bottomMargin"> |
||||
<number>0</number> |
||||
</property> |
||||
<item> |
||||
<widget class="QScrollArea" name="iconsScrollArea"> |
||||
<property name="sizePolicy"> |
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> |
||||
<horstretch>0</horstretch> |
||||
<verstretch>0</verstretch> |
||||
</sizepolicy> |
||||
</property> |
||||
<property name="frameShape"> |
||||
<enum>QFrame::NoFrame</enum> |
||||
</property> |
||||
<property name="sizeAdjustPolicy"> |
||||
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum> |
||||
</property> |
||||
<property name="widgetResizable"> |
||||
<bool>true</bool> |
||||
</property> |
||||
<widget class="QWidget" name="scrollAreaWidgetContents_2"> |
||||
<property name="geometry"> |
||||
<rect> |
||||
<x>0</x> |
||||
<y>0</y> |
||||
<width>427</width> |
||||
<height>271</height> |
||||
</rect> |
||||
</property> |
||||
<layout class="QVBoxLayout" name="verticalLayout_4"> |
||||
<item> |
||||
<layout class="QGridLayout" name="iconsLayout" columnstretch="1,0,0,0,0" columnminimumwidth="0,15,0,15,0"> |
||||
<item row="0" column="0"> |
||||
<widget class="QLabel" name="iconIDLabel"> |
||||
<property name="font"> |
||||
<font> |
||||
<bold>true</bold> |
||||
</font> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Icon ID</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="0" column="2"> |
||||
<widget class="QLabel" name="lightModeIconLabel"> |
||||
<property name="font"> |
||||
<font> |
||||
<bold>true</bold> |
||||
</font> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Light Mode</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
<item row="0" column="4"> |
||||
<widget class="QLabel" name="darkModeIconLabel"> |
||||
<property name="font"> |
||||
<font> |
||||
<bold>true</bold> |
||||
</font> |
||||
</property> |
||||
<property name="text"> |
||||
<string>Dark Mode</string> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</item> |
||||
<item> |
||||
<spacer name="iconsSpacer"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Vertical</enum> |
||||
</property> |
||||
<property name="sizeHint" stdset="0"> |
||||
<size> |
||||
<width>20</width> |
||||
<height>203</height> |
||||
</size> |
||||
</property> |
||||
</spacer> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
</widget> |
||||
</item> |
||||
<item> |
||||
<widget class="QDialogButtonBox" name="buttonBox"> |
||||
<property name="orientation"> |
||||
<enum>Qt::Horizontal</enum> |
||||
</property> |
||||
<property name="standardButtons"> |
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> |
||||
</property> |
||||
</widget> |
||||
</item> |
||||
</layout> |
||||
</widget> |
||||
<resources/> |
||||
<connections> |
||||
<connection> |
||||
<sender>buttonBox</sender> |
||||
<signal>accepted()</signal> |
||||
<receiver>UIThemeDialog</receiver> |
||||
<slot>accept()</slot> |
||||
<hints> |
||||
<hint type="sourcelabel"> |
||||
<x>248</x> |
||||
<y>254</y> |
||||
</hint> |
||||
<hint type="destinationlabel"> |
||||
<x>157</x> |
||||
<y>274</y> |
||||
</hint> |
||||
</hints> |
||||
</connection> |
||||
<connection> |
||||
<sender>buttonBox</sender> |
||||
<signal>rejected()</signal> |
||||
<receiver>UIThemeDialog</receiver> |
||||
<slot>reject()</slot> |
||||
<hints> |
||||
<hint type="sourcelabel"> |
||||
<x>316</x> |
||||
<y>260</y> |
||||
</hint> |
||||
<hint type="destinationlabel"> |
||||
<x>286</x> |
||||
<y>274</y> |
||||
</hint> |
||||
</hints> |
||||
</connection> |
||||
</connections> |
||||
</ui> |
@ -0,0 +1,280 @@
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent. |
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru> |
||||
* Copyright (C) 2019, 2021 Prince Gupta <jagannatharjun11@gmail.com> |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, |
||||
* USA. |
||||
* |
||||
* In addition, as a special exception, the copyright holders give permission to |
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with |
||||
* modified versions of it that use the same license as the "OpenSSL" library), |
||||
* and distribute the linked executables. You must obey the GNU General Public |
||||
* License in all respects for all of the code used other than "OpenSSL". If |
||||
* you modify file(s), you may extend this exception to your version of the |
||||
* file(s), but you are not obligated to do so. If you do not wish to do so, |
||||
* delete this exception statement from your version. |
||||
*/ |
||||
|
||||
#include "uithemesource.h" |
||||
|
||||
#include <QFile> |
||||
#include <QJsonDocument> |
||||
#include <QJsonObject> |
||||
|
||||
#include "base/global.h" |
||||
#include "base/logger.h" |
||||
#include "base/profile.h" |
||||
|
||||
namespace |
||||
{ |
||||
QByteArray readFile(const Path &filePath) |
||||
{ |
||||
QFile file {filePath.data()}; |
||||
if (!file.exists()) |
||||
return {}; |
||||
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) |
||||
return file.readAll(); |
||||
|
||||
LogMsg(UIThemeSource::tr("UITheme - Failed to open \"%1\". Reason: %2") |
||||
.arg(filePath.filename(), file.errorString()) |
||||
, Log::WARNING); |
||||
return {}; |
||||
} |
||||
|
||||
QJsonObject parseThemeConfig(const QByteArray &data) |
||||
{ |
||||
if (data.isEmpty()) |
||||
return {}; |
||||
|
||||
QJsonParseError jsonError; |
||||
const QJsonDocument configJsonDoc = QJsonDocument::fromJson(data, &jsonError); |
||||
if (jsonError.error != QJsonParseError::NoError) |
||||
{ |
||||
LogMsg(UIThemeSource::tr("Couldn't parse UI Theme configuration file. Reason: %1") |
||||
.arg(jsonError.errorString()), Log::WARNING); |
||||
return {}; |
||||
} |
||||
|
||||
if (!configJsonDoc.isObject()) |
||||
{ |
||||
LogMsg(UIThemeSource::tr("UI Theme configuration file has invalid format. Reason: %1") |
||||
.arg(UIThemeSource::tr("Root JSON value is not an object")), Log::WARNING); |
||||
return {}; |
||||
} |
||||
|
||||
return configJsonDoc.object(); |
||||
} |
||||
|
||||
QHash<QString, QColor> colorsFromJSON(const QJsonObject &jsonObj) |
||||
{ |
||||
QHash<QString, QColor> colors; |
||||
for (auto colorNode = jsonObj.constBegin(); colorNode != jsonObj.constEnd(); ++colorNode) |
||||
{ |
||||
const QColor color {colorNode.value().toString()}; |
||||
if (!color.isValid()) |
||||
{ |
||||
LogMsg(UIThemeSource::tr("Invalid color for ID \"%1\" is provided by theme") |
||||
.arg(colorNode.key()), Log::WARNING); |
||||
continue; |
||||
} |
||||
|
||||
colors.insert(colorNode.key(), color); |
||||
} |
||||
|
||||
return colors; |
||||
} |
||||
|
||||
Path findIcon(const QString &iconId, const Path &dir) |
||||
{ |
||||
const Path pathSvg = dir / Path(iconId + u".svg"); |
||||
if (pathSvg.exists()) |
||||
return pathSvg; |
||||
|
||||
const Path pathPng = dir / Path(iconId + u".png"); |
||||
if (pathPng.exists()) |
||||
return pathPng; |
||||
|
||||
return {}; |
||||
} |
||||
} |
||||
|
||||
DefaultThemeSource::DefaultThemeSource() |
||||
: m_defaultPath {u":"_qs} |
||||
, m_userPath {specialFolderLocation(SpecialFolder::Config) / Path(u"themes/default"_qs)} |
||||
, m_colors {defaultUIThemeColors()} |
||||
{ |
||||
loadColors(); |
||||
} |
||||
|
||||
QByteArray DefaultThemeSource::readStyleSheet() |
||||
{ |
||||
return {}; |
||||
} |
||||
|
||||
QColor DefaultThemeSource::getColor(const QString &colorId, const ColorMode colorMode) const |
||||
{ |
||||
return (colorMode == ColorMode::Light) |
||||
? m_colors.value(colorId).light : m_colors.value(colorId).dark; |
||||
} |
||||
|
||||
Path DefaultThemeSource::getIconPath(const QString &iconId, const ColorMode colorMode) const |
||||
{ |
||||
const Path iconsPath {u"icons"_qs}; |
||||
const Path lightModeIconsPath = iconsPath / Path(u"light"_qs); |
||||
const Path darkModeIconsPath = iconsPath / Path(u"dark"_qs); |
||||
|
||||
if (colorMode == ColorMode::Dark) |
||||
{ |
||||
if (const Path iconPath = findIcon(iconId, (m_userPath / darkModeIconsPath)) |
||||
; !iconPath.isEmpty()) |
||||
{ |
||||
return iconPath; |
||||
} |
||||
|
||||
if (const Path iconPath = findIcon(iconId, (m_defaultPath / darkModeIconsPath)) |
||||
; !iconPath.isEmpty()) |
||||
{ |
||||
return iconPath; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
if (const Path iconPath = findIcon(iconId, (m_userPath / lightModeIconsPath)) |
||||
; !iconPath.isEmpty()) |
||||
{ |
||||
return iconPath; |
||||
} |
||||
} |
||||
|
||||
return findIcon(iconId, (m_defaultPath / iconsPath)); |
||||
} |
||||
|
||||
void DefaultThemeSource::loadColors() |
||||
{ |
||||
const QByteArray configData = readFile(m_userPath / Path(CONFIG_FILE_NAME)); |
||||
if (configData.isEmpty()) |
||||
return; |
||||
|
||||
const QJsonObject config = parseThemeConfig(configData); |
||||
|
||||
QHash<QString, QColor> lightModeColorOverrides = colorsFromJSON(config.value(KEY_COLORS_LIGHT).toObject()); |
||||
for (auto overridesIt = lightModeColorOverrides.cbegin(); overridesIt != lightModeColorOverrides.cend(); ++overridesIt) |
||||
{ |
||||
auto it = m_colors.find(overridesIt.key()); |
||||
if (it != m_colors.end()) |
||||
it.value().light = overridesIt.value(); |
||||
} |
||||
|
||||
QHash<QString, QColor> darkModeColorOverrides = colorsFromJSON(config.value(KEY_COLORS_DARK).toObject()); |
||||
for (auto overridesIt = darkModeColorOverrides.cbegin(); overridesIt != darkModeColorOverrides.cend(); ++overridesIt) |
||||
{ |
||||
auto it = m_colors.find(overridesIt.key()); |
||||
if (it != m_colors.end()) |
||||
it.value().dark = overridesIt.value(); |
||||
} |
||||
} |
||||
|
||||
QColor CustomThemeSource::getColor(const QString &colorId, const ColorMode colorMode) const |
||||
{ |
||||
if (colorMode == ColorMode::Dark) |
||||
{ |
||||
if (const QColor color = m_darkModeColors.value(colorId) |
||||
; color.isValid()) |
||||
{ |
||||
return color; |
||||
} |
||||
} |
||||
|
||||
if (const QColor color = m_colors.value(colorId) |
||||
; color.isValid()) |
||||
{ |
||||
return color; |
||||
} |
||||
|
||||
return defaultThemeSource()->getColor(colorId, colorMode); |
||||
} |
||||
|
||||
Path CustomThemeSource::getIconPath(const QString &iconId, const ColorMode colorMode) const |
||||
{ |
||||
const Path iconsPath {u"icons"_qs}; |
||||
const Path darkModeIconsPath = iconsPath / Path(u"dark"_qs); |
||||
|
||||
if (colorMode == ColorMode::Dark) |
||||
{ |
||||
if (const Path iconPath = findIcon(iconId, (themeRootPath() / darkModeIconsPath)) |
||||
; !iconPath.isEmpty()) |
||||
{ |
||||
return iconPath; |
||||
} |
||||
} |
||||
|
||||
if (const Path iconPath = findIcon(iconId, (themeRootPath() / iconsPath)) |
||||
; !iconPath.isEmpty()) |
||||
{ |
||||
return iconPath; |
||||
} |
||||
|
||||
return defaultThemeSource()->getIconPath(iconId, colorMode); |
||||
} |
||||
|
||||
QByteArray CustomThemeSource::readStyleSheet() |
||||
{ |
||||
return readFile(themeRootPath() / Path(STYLESHEET_FILE_NAME)); |
||||
} |
||||
|
||||
DefaultThemeSource *CustomThemeSource::defaultThemeSource() const |
||||
{ |
||||
return m_defaultThemeSource.get(); |
||||
} |
||||
|
||||
void CustomThemeSource::loadColors() |
||||
{ |
||||
const QByteArray configData = readFile(themeRootPath() / Path(CONFIG_FILE_NAME)); |
||||
if (configData.isEmpty()) |
||||
return; |
||||
|
||||
const QJsonObject config = parseThemeConfig(configData); |
||||
|
||||
m_colors.insert(colorsFromJSON(config.value(KEY_COLORS).toObject())); |
||||
m_darkModeColors.insert(colorsFromJSON(config.value(KEY_COLORS_DARK).toObject())); |
||||
} |
||||
|
||||
Path QRCThemeSource::themeRootPath() const |
||||
{ |
||||
return Path(u":/uitheme"_qs); |
||||
} |
||||
|
||||
FolderThemeSource::FolderThemeSource(const Path &folderPath) |
||||
: m_folder {folderPath} |
||||
{ |
||||
} |
||||
|
||||
QByteArray FolderThemeSource::readStyleSheet() |
||||
{ |
||||
// Directory used by stylesheet to reference internal resources
|
||||
// for example `icon: url(:/uitheme/file.svg)` will be expected to
|
||||
// point to a file `file.svg` in root directory of CONFIG_FILE_NAME
|
||||
const QString stylesheetResourcesDir = u":/uitheme"_qs; |
||||
|
||||
QByteArray styleSheetData = CustomThemeSource::readStyleSheet(); |
||||
return styleSheetData.replace(stylesheetResourcesDir.toUtf8(), themeRootPath().data().toUtf8()); |
||||
} |
||||
|
||||
Path FolderThemeSource::themeRootPath() const |
||||
{ |
||||
return m_folder; |
||||
} |
@ -0,0 +1,115 @@
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent. |
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru> |
||||
* Copyright (C) 2019 Prince Gupta <jagannatharjun11@gmail.com> |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, |
||||
* USA. |
||||
* |
||||
* In addition, as a special exception, the copyright holders give permission to |
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with |
||||
* modified versions of it that use the same license as the "OpenSSL" library), |
||||
* and distribute the linked executables. You must obey the GNU General Public |
||||
* License in all respects for all of the code used other than "OpenSSL". If |
||||
* you modify file(s), you may extend this exception to your version of the |
||||
* file(s), but you are not obligated to do so. If you do not wish to do so, |
||||
* delete this exception statement from your version. |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <memory> |
||||
|
||||
#include <QtGlobal> |
||||
#include <QColor> |
||||
#include <QHash> |
||||
#include <QIcon> |
||||
#include <QString> |
||||
|
||||
#include "base/path.h" |
||||
#include "uithemecommon.h" |
||||
|
||||
enum class ColorMode |
||||
{ |
||||
Light, |
||||
Dark |
||||
}; |
||||
|
||||
class UIThemeSource |
||||
{ |
||||
Q_DECLARE_TR_FUNCTIONS(UIThemeSource) |
||||
|
||||
public: |
||||
virtual ~UIThemeSource() = default; |
||||
|
||||
virtual QColor getColor(const QString &colorId, const ColorMode colorMode) const = 0; |
||||
virtual Path getIconPath(const QString &iconId, const ColorMode colorMode) const = 0; |
||||
virtual QByteArray readStyleSheet() = 0; |
||||
}; |
||||
|
||||
class DefaultThemeSource final : public UIThemeSource |
||||
{ |
||||
public: |
||||
DefaultThemeSource(); |
||||
|
||||
QByteArray readStyleSheet() override; |
||||
QColor getColor(const QString &colorId, const ColorMode colorMode) const override; |
||||
Path getIconPath(const QString &iconId, const ColorMode colorMode) const override; |
||||
|
||||
private: |
||||
void loadColors(); |
||||
|
||||
const Path m_defaultPath; |
||||
const Path m_userPath; |
||||
QHash<QString, UIThemeColor> m_colors; |
||||
}; |
||||
|
||||
class CustomThemeSource : public UIThemeSource |
||||
{ |
||||
public: |
||||
QColor getColor(const QString &colorId, const ColorMode colorMode) const override; |
||||
Path getIconPath(const QString &iconId, const ColorMode colorMode) const override; |
||||
QByteArray readStyleSheet() override; |
||||
|
||||
protected: |
||||
virtual Path themeRootPath() const = 0; |
||||
DefaultThemeSource *defaultThemeSource() const; |
||||
|
||||
private: |
||||
void loadColors(); |
||||
|
||||
const std::unique_ptr<DefaultThemeSource> m_defaultThemeSource = std::make_unique<DefaultThemeSource>(); |
||||
QHash<QString, QColor> m_colors; |
||||
QHash<QString, QColor> m_darkModeColors; |
||||
}; |
||||
|
||||
class QRCThemeSource final : public CustomThemeSource |
||||
{ |
||||
private: |
||||
Path themeRootPath() const override; |
||||
}; |
||||
|
||||
class FolderThemeSource : public CustomThemeSource |
||||
{ |
||||
public: |
||||
explicit FolderThemeSource(const Path &folderPath); |
||||
|
||||
QByteArray readStyleSheet() override; |
||||
|
||||
private: |
||||
Path themeRootPath() const override; |
||||
|
||||
const Path m_folder; |
||||
}; |
Loading…
Reference in new issue