From c76fbcc0cfff9a9c7a1abe18ceb0608385a580f7 Mon Sep 17 00:00:00 2001 From: Tim Delaney Date: Sun, 19 Feb 2017 11:47:19 +1100 Subject: [PATCH 1/2] Add appearance options tab by @magao: Consolidate appearance options in their own tab. by @evsh: Add preferences-desktop-theme icon, copied from fa-desktop (Unicode f108) symbol of FontAwesome --- src/base/preferences.cpp | 72 ++++++++---- src/base/preferences.h | 16 +-- src/gui/advancedsettings.cpp | 12 -- src/gui/advancedsettings.h | 4 - src/gui/optionsdialog.cpp | 21 +++- src/gui/optionsdialog.h | 3 +- src/gui/optionsdialog.ui | 109 ++++++++++++++++-- .../build-icons/preferences-desktop-theme.svg | 4 + .../qbt-theme/preferences-desktop-theme.png | Bin 0 -> 3017 bytes 9 files changed, 178 insertions(+), 63 deletions(-) create mode 100644 src/icons/qbt-theme/build-icons/preferences-desktop-theme.svg create mode 100644 src/icons/qbt-theme/preferences-desktop-theme.png diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index d0a37c21a4b..77fb01a9105 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -30,6 +30,7 @@ #include "preferences.h" #include +#include #include #include #include @@ -89,17 +90,40 @@ void Preferences::setValue(const QString &key, const QVariant &value) SettingsStorage::instance()->storeValue(key, value); } -// General options +// Appearance/Language options QString Preferences::getLocale() const { - return value("Preferences/General/Locale", QLocale::system().name()).toString(); + return value("Appearance/Locale", QLocale::system().name()).toString(); } void Preferences::setLocale(const QString &locale) { - setValue("Preferences/General/Locale", locale); + setValue("Appearance/Locale", locale); +} + +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) +bool Preferences::useSystemIconTheme() const +{ + return value("Appearance/useSystemIconTheme", true).toBool(); +} + +void Preferences::setSystemIconTheme(bool enabled) +{ + setValue("Appearance/useSystemIconTheme", enabled); +} +#endif + +bool Preferences::useAlternatingRowColors() const +{ + return value("Appearance/AlternatingRowColors", true).toBool(); } +void Preferences::setAlternatingRowColors(bool b) +{ + setValue("Appearance/AlternatingRowColors", b); +} + +// General options bool Preferences::deleteTorrentFilesAsDefault() const { return value("Preferences/General/DeleteTorrentsFilesAsDefault", false).toBool(); @@ -130,16 +154,6 @@ void Preferences::showSpeedInTitleBar(bool show) setValue("Preferences/General/SpeedInTitleBar", show); } -bool Preferences::useAlternatingRowColors() const -{ - return value("Preferences/General/AlternatingRowColors", true).toBool(); -} - -void Preferences::setAlternatingRowColors(bool b) -{ - setValue("Preferences/General/AlternatingRowColors", b); -} - bool Preferences::getHideZeroValues() const { return value("Preferences/General/HideZeroValues", false).toBool(); @@ -825,18 +839,6 @@ void Preferences::resolvePeerHostNames(bool resolve) setValue("Preferences/Connection/ResolvePeerHostNames", resolve); } -#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) -bool Preferences::useSystemIconTheme() const -{ - return value("Preferences/Advanced/useSystemIconTheme", true).toBool(); -} - -void Preferences::useSystemIconTheme(bool enabled) -{ - setValue("Preferences/Advanced/useSystemIconTheme", enabled); -} -#endif - bool Preferences::recursiveDownloadDisabled() const { return value("Preferences/Advanced/DisableRecursiveDownload", false).toBool(); @@ -1552,6 +1554,26 @@ void Preferences::setSpeedWidgetGraphEnable(int id, const bool enable) void Preferences::upgrade() { + // Migrate single-value prefs to new section/name + QList> prefsToMigrate = { + { "Preferences/General/Locale", "Appearance/Locale" }, + { "Preferences/General/AlternatingRowColors", "Appearance/AlternatingRowColors" }, + { "Preferences/Advanced/useSystemIconTheme", "Appearance/useSystemIconTheme" }, + }; + + for (auto iter = prefsToMigrate.begin(); iter != prefsToMigrate.end(); ++iter) { + QString pre(iter->first); + QString post(iter->second); + + QVariant preValue = value(pre); + + if (!preValue.isNull()) { + qDebug() << "Migrating preference" << pre << "->" << post; + setValue(post, preValue); + SettingsStorage::instance()->removeValue(pre); + } + } + QStringList labels = value("TransferListFilters/customLabels").toStringList(); if (!labels.isEmpty()) { QVariantMap categories = value("BitTorrent/Session/Categories").toMap(); diff --git a/src/base/preferences.h b/src/base/preferences.h index 136ad106f57..c97e71375dd 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -100,17 +100,23 @@ class Preferences : public QObject static void freeInstance(); static Preferences *instance(); - // General options + // Appearance options QString getLocale() const; void setLocale(const QString &locale); + bool useAlternatingRowColors() const; + void setAlternatingRowColors(bool b); +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) + bool useSystemIconTheme() const; + void setSystemIconTheme(bool enabled); +#endif + + // General options bool deleteTorrentFilesAsDefault() const; void setDeleteTorrentFilesAsDefault(bool del); bool confirmOnExit() const; void setConfirmOnExit(bool confirm); bool speedInTitleBar() const; void showSpeedInTitleBar(bool show); - bool useAlternatingRowColors() const; - void setAlternatingRowColors(bool b); bool getHideZeroValues() const; void setHideZeroValues(bool b); int getHideZeroComboValues() const; @@ -250,10 +256,6 @@ class Preferences : public QObject void resolvePeerCountries(bool resolve); bool resolvePeerHostNames() const; void resolvePeerHostNames(bool resolve); -#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) - bool useSystemIconTheme() const; - void useSystemIconTheme(bool enabled); -#endif bool recursiveDownloadDisabled() const; void disableRecursiveDownload(bool disable = true); #ifdef Q_OS_WIN diff --git a/src/gui/advancedsettings.cpp b/src/gui/advancedsettings.cpp index e9622440a7a..f980c43044a 100644 --- a/src/gui/advancedsettings.cpp +++ b/src/gui/advancedsettings.cpp @@ -73,9 +73,6 @@ enum AdvSettingsRows CONFIRM_REMOVE_ALL_TAGS, DOWNLOAD_TRACKER_FAVICON, SAVE_PATH_HISTORY_LENGTH, -#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) - USE_ICON_THEME, -#endif // libtorrent section LIBTORRENT_HEADER, @@ -229,13 +226,8 @@ void AdvancedSettings::saveAdvancedSettings() #if defined(Q_OS_WIN) || defined(Q_OS_MAC) pref->setUpdateCheckEnabled(checkBoxUpdateCheck.isChecked()); -#endif - // Icon theme -#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) - pref->useSystemIconTheme(checkBoxUseIconTheme.isChecked()); #endif pref->setConfirmTorrentRecheck(checkBoxConfirmTorrentRecheck.isChecked()); - pref->setConfirmRemoveAllTags(checkBoxConfirmRemoveAllTags.isChecked()); session->setAnnounceToAllTrackers(checkBoxAnnounceAllTrackers.isChecked()); @@ -487,10 +479,6 @@ void AdvancedSettings::loadAdvancedSettings() #if defined(Q_OS_WIN) || defined(Q_OS_MAC) checkBoxUpdateCheck.setChecked(pref->isUpdateCheckEnabled()); addRow(UPDATE_CHECK, tr("Check for software updates"), &checkBoxUpdateCheck); -#endif -#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) - checkBoxUseIconTheme.setChecked(pref->useSystemIconTheme()); - addRow(USE_ICON_THEME, tr("Use system icon theme"), &checkBoxUseIconTheme); #endif // Torrent recheck confirmation checkBoxConfirmTorrentRecheck.setChecked(pref->confirmTorrentRecheck()); diff --git a/src/gui/advancedsettings.h b/src/gui/advancedsettings.h index be3feffaf6d..df99af4e69d 100644 --- a/src/gui/advancedsettings.h +++ b/src/gui/advancedsettings.h @@ -73,10 +73,6 @@ private slots: #if defined(Q_OS_WIN) || defined(Q_OS_MAC) QCheckBox checkBoxUpdateCheck; #endif - -#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) - QCheckBox checkBoxUseIconTheme; -#endif }; #endif // ADVANCEDSETTINGS_H diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index e64c97ab03f..e91de323bf9 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -99,7 +99,8 @@ OptionsDialog::OptionsDialog(QWidget *parent) #endif // Icons - m_ui->tabSelection->item(TAB_UI)->setIcon(GuiIconProvider::instance()->getIcon("preferences-desktop")); + m_ui->tabSelection->item(TAB_APPEARANCE)->setIcon(GuiIconProvider::instance()->getIcon("preferences-desktop-theme")); + m_ui->tabSelection->item(TAB_BEHAVIOUR)->setIcon(GuiIconProvider::instance()->getIcon("preferences-desktop")); m_ui->tabSelection->item(TAB_BITTORRENT)->setIcon(GuiIconProvider::instance()->getIcon("preferences-system-network")); m_ui->tabSelection->item(TAB_CONNECTION)->setIcon(GuiIconProvider::instance()->getIcon("network-wired")); m_ui->tabSelection->item(TAB_DOWNLOADS)->setIcon(GuiIconProvider::instance()->getIcon("folder-download")); @@ -153,6 +154,13 @@ OptionsDialog::OptionsDialog(QWidget *parent) } } +#if !((defined(Q_OS_UNIX) && !defined(Q_OS_MAC))) + m_ui->checkUseSystemTheme->setHidden(true); +#endif + + // Hide the Appearance/Desktop section if it doesn't contain any visible children. + m_ui->groupBoxAppearanceDesktop->setHidden(m_ui->groupBoxAppearanceDesktop->childrenRect().isEmpty()); + m_ui->scanFoldersView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); m_ui->scanFoldersView->setModel(ScanFoldersModel::instance()); m_ui->scanFoldersView->setItemDelegate(new ScanFoldersDelegate(this, m_ui->scanFoldersView)); @@ -211,6 +219,9 @@ OptionsDialog::OptionsDialog(QWidget *parent) // General tab connect(m_ui->comboI18n, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton); connect(m_ui->confirmDeletion, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) + connect(m_ui->checkUseSystemTheme, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); +#endif connect(m_ui->checkAltRowColors, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkHideZero, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkHideZero, &QAbstractButton::toggled, m_ui->comboHideZero, &QWidget::setEnabled); @@ -503,7 +514,7 @@ void OptionsDialog::loadSplitterState() // width has been modified, use height as width reference instead const int width = Utils::Gui::scaledSize(this - , (m_ui->tabSelection->item(TAB_UI)->sizeHint().height() * 2)); + , (m_ui->tabSelection->item(TAB_APPEARANCE)->sizeHint().height() * 2)); QList sizes {width, (m_ui->hsplitter->width() - width)}; if (sizesStr.size() == 2) sizes = {sizesStr.first().toInt(), sizesStr.last().toInt()}; @@ -544,6 +555,9 @@ void OptionsDialog::saveOptions() pref->setLocale(locale); pref->setConfirmTorrentDeletion(m_ui->confirmDeletion->isChecked()); pref->setAlternatingRowColors(m_ui->checkAltRowColors->isChecked()); +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) + pref->setSystemIconTheme(m_ui->checkUseSystemTheme->isChecked()); +#endif pref->setHideZeroValues(m_ui->checkHideZero->isChecked()); pref->setHideZeroComboValues(m_ui->comboHideZero->currentIndex()); #ifndef Q_OS_MAC @@ -775,6 +789,9 @@ void OptionsDialog::loadOptions() setLocale(pref->getLocale()); m_ui->confirmDeletion->setChecked(pref->confirmTorrentDeletion()); m_ui->checkAltRowColors->setChecked(pref->useAlternatingRowColors()); +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) + m_ui->checkUseSystemTheme->setChecked(pref->useSystemIconTheme()); +#endif m_ui->checkHideZero->setChecked(pref->getHideZeroValues()); m_ui->comboHideZero->setEnabled(m_ui->checkHideZero->isChecked()); m_ui->comboHideZero->setCurrentIndex(pref->getHideZeroComboValues()); diff --git a/src/gui/optionsdialog.h b/src/gui/optionsdialog.h index 36d34513bdf..08454a976cc 100644 --- a/src/gui/optionsdialog.h +++ b/src/gui/optionsdialog.h @@ -63,7 +63,8 @@ class OptionsDialog : public QDialog private: enum Tabs { - TAB_UI, + TAB_APPEARANCE, + TAB_BEHAVIOUR, TAB_DOWNLOADS, TAB_CONNECTION, TAB_SPEED, diff --git a/src/gui/optionsdialog.ui b/src/gui/optionsdialog.ui index fa7a0688d86..1d0a002fc90 100644 --- a/src/gui/optionsdialog.ui +++ b/src/gui/optionsdialog.ui @@ -47,6 +47,11 @@ -1 + + + Appearance + + Behavior @@ -92,8 +97,8 @@ 0 - - + + 0 @@ -107,7 +112,7 @@ 0 - + 0 @@ -117,7 +122,7 @@ true - + 0 @@ -126,7 +131,7 @@ 893 - + @@ -192,25 +197,104 @@ - + + + Desktop + + + + + + Use system icon theme + + + + + + + + Transfer List - + - + - Confirm when deleting torrents + Use alternating row colors true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + true + + + + + 0 + 0 + 504 + 849 + + + + + + + Transfer List + + - + - Use alternating row colors + Confirm when deleting torrents true @@ -3341,7 +3425,8 @@ Use ';' to split multiple entries. Can use wildcard '*'. tabOption - comboI18n + textSavePath + browseSaveDirButton checkStartPaused spinPort checkUPnP diff --git a/src/icons/qbt-theme/build-icons/preferences-desktop-theme.svg b/src/icons/qbt-theme/build-icons/preferences-desktop-theme.svg new file mode 100644 index 00000000000..64e1a906a52 --- /dev/null +++ b/src/icons/qbt-theme/build-icons/preferences-desktop-theme.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/icons/qbt-theme/preferences-desktop-theme.png b/src/icons/qbt-theme/preferences-desktop-theme.png new file mode 100644 index 0000000000000000000000000000000000000000..4c05fbb76479229232be04a37cc8373d5e92cb59 GIT binary patch literal 3017 zcmeHJYc$kb8{ad3GZThMuH!PfQ^*V>xizD58FHr)Cr!C@p=Qn?BFt1LClr}Tlw1a@ zP!cCZ{k=KIK_aCb#JG)18al}!<;;1%ykFk;sf2>wVw4{_V#p< zlTnue0Lal?_xS>Vkhnmw2`#bnjt$-tkUHk*;RwLPOxg7#D9K$r)YaDmfCNJTI2Qp} zlTe%)0Ah&%%rOA4%>zJ{b*st8ULv56y1VR?pk%G?eUc&pX_o83XaMBf{yvBt6L}2@ ziHV_kIAO+Q6cx4_YJ|cPU4}>7=jb0l`0@SWsN3OM@TY~L`a^VNgWRt)#KEwr(6Ean z+4Qy|iwl?aW776sJHEepVl+)bk3=#|D8`b$)0$t&V$+X%H1DBt_KmBBrt3%O>Hn^; zbUo@qhg$f@mmhwOPPfM^8q>o@c`P8Dnxxp&Z9$+Clj4T1&@qOgyX4ijFNFIgBq;{`G%j# zV+Qrdu)Cz}W$dNwp^P4_Rd{2`=OB{bUqm zW7O*p%5Qz#*v>NF@wIHV9PC6s0ADe0PZi(XM8?A98c%6-x}3m5Pyu7u%)9*8b2`;h z1WZWqqcOI;R(!;7%;OCi3!=ij_14EwWi+#xkFQ3bh7vq)%`sO55t$R#khO5cg64tO z?f&CZ1Qr(b5}j;+8IZ4f(GrlIU%O=>M5X>{=jxN!CmWE)WA=^7B&x6`bLP9a4l9g1fy1HJN}E53Fp@&RjH?oP}@ z)do~NVKDJSN!`F9EAsxE!p_|S$H8>?EZ=7R&McvKMw3cmozZ~QFv%Ib!zVlaHVX85 zc*2+Q8Wd4fpJw5KQm_Qvht%LBz8$yLeR<_Ca+3Hq_lMY<>>4FedEON`%${x#hw0I9D=};AqNKxWpK#6ZM+n@6QU*m)pPH) z2FknP3-Gt_j;F1~W2B-P__Ld=2Q6rsi4^H}-*$`D1#he;-@yuku+&1opq~@CIsWJ7 zDR=!4dBZgUTIY7taevBIDaXu@V#RpvTb9@G|uoO%C-#!FODZSp!7zFe9M&_SgEpW$hua&_LC>yWt-Krw7MvN#c=ZM_z){E&Ug%K#A_mNz zwx6s6#HNMw#xz6QXr4As>f0`OPE8Rx=$RdL;f{j1T%}AUB59}t7ag=O#r4eZRuBQ=>g{Cc@@E)eO zN-Zv|jk>w@F~@At%~(zSLuZG)I!vh5{S)gq{deLqlsM~(tDZHJN$yRwRMa1}BGTcDkM->*NPgMmSi~VeyK49eWm6m#`vkgqvr^T2%`G12`**IMbD63WQ%M+t# z&3OaXKCw`w;JxDSb3`a>??GRF(le6STgbfvZ=oa`;-D;=^fUu#f4m@`ChYS^Yv1Ww zTt4Ayoq_+?ek?V$-+Tk{1LoGXMElRM1tXH~v;V#I+-4}Ny^%jumJ?N-Q$~j@h(L=x zr7Q{(^bJqkba{)s1JpDJUkNjPO|aByGt_C_<*8>I?>w3e8hQ%+?7BeAh@oJj5ktZq z+B;}Hrw#S$NQMP(@2rs>ExO`(Veu4v##q)1eda1+hM(=u(O~03313Z>ZcGyP>7$FF z^5&9LMlGnoia4D`6zqOp>nN~2X2psYIhukY+Or@}Ee6=08`2@eG51NCNROz?RYK4a z64JJ$S*^iJY^;(MOnbbk@+i7!kEHPR+1lkB+{{Tr53=^^+E{ia=Omt!rfi_D^1scG^$h-K}g(vvH$7Sew zqxWxW5qiC@((}lta++dGsB%h&+TRyL!44zl+n#f8HaiZwJcd%!`wkrFb{N+Ak~o;? znu@eW6wtQHZ%I-j)<$6RSatQ(#Qkk(hAXP Date: Wed, 8 Mar 2017 08:59:01 +0100 Subject: [PATCH 2/2] Add color and font themes --- src/app/application.cpp | 8 + src/base/logger.h | 2 +- src/gui/CMakeLists.txt | 27 ++ src/gui/executionlogwidget.cpp | 47 ++-- src/gui/gui.pri | 34 ++- src/gui/loglistwidget.cpp | 21 +- src/gui/loglistwidget.h | 3 + src/gui/optionsdialog.cpp | 215 +++++++++++++++- src/gui/optionsdialog.h | 6 +- src/gui/optionsdialog.ui | 112 ++++++++- src/gui/properties/downloadedpiecesbar.cpp | 16 +- src/gui/properties/pieceavailabilitybar.cpp | 12 +- src/gui/properties/propertieswidget.cpp | 34 +++ src/gui/properties/propertieswidget.h | 2 + src/gui/theme/builtinthemes.qrc | 7 + src/gui/theme/colorprovider_p.cpp | 68 +++++ src/gui/theme/colorprovider_p.h | 72 ++++++ src/gui/theme/colorproviders.cpp | 183 ++++++++++++++ src/gui/theme/colorproviders.h | 130 ++++++++++ src/gui/theme/colortheme.cpp | 179 +++++++++++++ src/gui/theme/colortheme.h | 128 ++++++++++ src/gui/theme/colortheme_impl.h | 57 +++++ src/gui/theme/fontprovider_p.cpp | 48 ++++ src/gui/theme/fontprovider_p.h | 57 +++++ src/gui/theme/fontproviders.cpp | 105 ++++++++ src/gui/theme/fontproviders.h | 67 +++++ src/gui/theme/fonttheme.cpp | 66 +++++ src/gui/theme/fonttheme.h | 63 +++++ src/gui/theme/fonttheme_impl.h | 60 +++++ src/gui/theme/provider_p.cpp | 30 +++ src/gui/theme/provider_p.h | 168 +++++++++++++ src/gui/theme/qBittorrent.fonttheme | 8 + src/gui/theme/qBittorrentDark.colortheme | 27 ++ src/gui/theme/qBittorrentLight.colortheme | 37 +++ src/gui/theme/serializabletheme.cpp | 136 ++++++++++ src/gui/theme/serializabletheme.h | 155 ++++++++++++ src/gui/theme/themecommon.cpp | 40 +++ src/gui/theme/themecommon.h | 56 +++++ src/gui/theme/themeexceptions.cpp | 86 +++++++ src/gui/theme/themeexceptions.h | 102 ++++++++ src/gui/theme/themeinfo.cpp | 151 +++++++++++ src/gui/theme/themeinfo.h | 112 +++++++++ src/gui/theme/themeprovider.cpp | 264 ++++++++++++++++++++ src/gui/theme/themeprovider.h | 123 +++++++++ src/gui/transferlistmodel.cpp | 70 +----- src/gui/transferlistwidget.cpp | 16 ++ src/gui/transferlistwidget.h | 3 + 47 files changed, 3307 insertions(+), 106 deletions(-) create mode 100644 src/gui/theme/builtinthemes.qrc create mode 100644 src/gui/theme/colorprovider_p.cpp create mode 100644 src/gui/theme/colorprovider_p.h create mode 100644 src/gui/theme/colorproviders.cpp create mode 100644 src/gui/theme/colorproviders.h create mode 100644 src/gui/theme/colortheme.cpp create mode 100644 src/gui/theme/colortheme.h create mode 100644 src/gui/theme/colortheme_impl.h create mode 100644 src/gui/theme/fontprovider_p.cpp create mode 100644 src/gui/theme/fontprovider_p.h create mode 100644 src/gui/theme/fontproviders.cpp create mode 100644 src/gui/theme/fontproviders.h create mode 100644 src/gui/theme/fonttheme.cpp create mode 100644 src/gui/theme/fonttheme.h create mode 100644 src/gui/theme/fonttheme_impl.h create mode 100644 src/gui/theme/provider_p.cpp create mode 100644 src/gui/theme/provider_p.h create mode 100644 src/gui/theme/qBittorrent.fonttheme create mode 100644 src/gui/theme/qBittorrentDark.colortheme create mode 100644 src/gui/theme/qBittorrentLight.colortheme create mode 100644 src/gui/theme/serializabletheme.cpp create mode 100644 src/gui/theme/serializabletheme.h create mode 100644 src/gui/theme/themecommon.cpp create mode 100644 src/gui/theme/themecommon.h create mode 100644 src/gui/theme/themeexceptions.cpp create mode 100644 src/gui/theme/themeexceptions.h create mode 100644 src/gui/theme/themeinfo.cpp create mode 100644 src/gui/theme/themeinfo.h create mode 100644 src/gui/theme/themeprovider.cpp create mode 100644 src/gui/theme/themeprovider.h diff --git a/src/app/application.cpp b/src/app/application.cpp index de322790d3e..62b30793ab3 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -53,6 +53,9 @@ #endif // Q_OS_MAC #include "addnewtorrentdialog.h" #include "gui/guiiconprovider.h" +#include "gui/theme/colorproviders.h" +#include "gui/theme/fontproviders.h" +#include "gui/theme/themeprovider.h" #include "mainwindow.h" #include "shutdownconfirmdialog.h" #else // DISABLE_GUI @@ -488,9 +491,14 @@ int Application::exec(const QStringList ¶ms) { Net::ProxyConfigurationManager::initInstance(); Net::DownloadManager::initInstance(); + #ifdef DISABLE_GUI IconProvider::initInstance(); #else + Theme::Serialization::registerColorProviders(); + Theme::Serialization::registerFontProviders(); + Theme::ThemeProvider::initInstance(); + GuiIconProvider::initInstance(); #endif diff --git a/src/base/logger.h b/src/base/logger.h index 37d706c64c3..67641122da5 100644 --- a/src/base/logger.h +++ b/src/base/logger.h @@ -10,7 +10,7 @@ const int MAX_LOG_MESSAGES = 20000; namespace Log { - enum MsgType + enum MsgType: int { ALL = -1, NORMAL = 0x1, diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index b410d33c547..9141e6100af 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -60,6 +60,20 @@ transferlistsortmodel.h transferlistwidget.h updownratiodialog.h utils.h +theme/colorprovider_p.h +theme/colorproviders.h +theme/colortheme.h +theme/colortheme_impl.h +theme/fontprovider_p.h +theme/fontproviders.h +theme/fonttheme.h +theme/fonttheme_impl.h +theme/provider_p.h +theme/serializabletheme.h +theme/themecommon.h +theme/themeexceptions.h +theme/themeinfo.h +theme/themeprovider.h # sources addnewtorrentdialog.cpp @@ -106,6 +120,18 @@ transferlistsortmodel.cpp transferlistwidget.cpp updownratiodialog.cpp utils.cpp +theme/colorprovider_p.cpp +theme/colorproviders.cpp +theme/colortheme.cpp +theme/fonttheme.cpp +theme/fontprovider_p.cpp +theme/fontproviders.cpp +theme/provider_p.cpp +theme/serializabletheme.cpp +theme/themecommon.cpp +theme/themeexceptions.cpp +theme/themeinfo.cpp +theme/themeprovider.cpp # forms aboutdialog.ui @@ -135,6 +161,7 @@ target_link_libraries(qbt_gui qbt_base QtSingleApplication::QtSingleApplication ) +qbt_target_sources(qBittorrent PRIVATE about.qrc theme/builtinthemes.qrc) target_include_directories(qbt_gui PRIVATE ../app diff --git a/src/gui/executionlogwidget.cpp b/src/gui/executionlogwidget.cpp index 2a74213c54c..2f942851ad3 100644 --- a/src/gui/executionlogwidget.cpp +++ b/src/gui/executionlogwidget.cpp @@ -34,6 +34,8 @@ #include "guiiconprovider.h" #include "loglistwidget.h" +#include "theme/colortheme.h" + #include "ui_executionlogwidget.h" ExecutionLogWidget::ExecutionLogWidget(QWidget *parent, const Log::MsgTypes &types) @@ -72,40 +74,39 @@ void ExecutionLogWidget::showMsgTypes(const Log::MsgTypes &types) m_msgList->showMsgTypes(types); } -void ExecutionLogWidget::addLogMessage(const Log::Msg &msg) +namespace { - QString text; - QDateTime time = QDateTime::fromMSecsSinceEpoch(msg.timestamp); - QColor color; - - switch (msg.type) { - case Log::INFO: - color.setNamedColor("blue"); - break; - case Log::WARNING: - color.setNamedColor("orange"); - break; - case Log::CRITICAL: - color.setNamedColor("red"); - break; - default: - color = QApplication::palette().color(QPalette::WindowText); + QString coloredString(const QString &str, const QColor &color) + { + return QString(QLatin1String("%2")) + .arg(color.name(), str); } +} + +void ExecutionLogWidget::addLogMessage(const Log::Msg &msg) +{ + const QDateTime time = QDateTime::fromMSecsSinceEpoch(msg.timestamp); + const QColor messageColor = Theme::ColorTheme::current().logMessageColor(msg.type); + const QColor neutralColor = QPalette().color(QPalette::Inactive, QPalette::WindowText); - text = "" + time.toString(Qt::SystemLocaleShortDate) + " - " + msg.message + ""; + QString text = coloredString(time.toString(Qt::SystemLocaleShortDate), neutralColor) + + QLatin1String(" - ") + + coloredString(msg.message, messageColor); m_msgList->appendLine(text, msg.type); } void ExecutionLogWidget::addPeerMessage(const Log::Peer &peer) { - QString text; - QDateTime time = QDateTime::fromMSecsSinceEpoch(peer.timestamp); + const QDateTime time = QDateTime::fromMSecsSinceEpoch(peer.timestamp); + const QColor IPColor = Theme::ColorTheme::current().logMessageColor(Log::MsgType::CRITICAL); + const QColor neutralColor = QPalette().color(QPalette::Inactive, QPalette::WindowText); + QString text = coloredString(time.toString(Qt::SystemLocaleShortDate), neutralColor) + + QLatin1String(" - ") + coloredString(peer.ip, IPColor) + QLatin1Char(' '); if (peer.blocked) - text = "" + time.toString(Qt::SystemLocaleShortDate) + " - " - + tr("%1 was blocked %2", "x.y.z.w was blocked").arg(peer.ip, peer.reason); + text += tr("was blocked %1", "x.y.z.w was blocked").arg(peer.reason); else - text = "" + time.toString(Qt::SystemLocaleShortDate) + " - " + tr("%1 was banned", "x.y.z.w was banned").arg(peer.ip); + text += tr("was banned", "x.y.z.w was banned"); m_peerList->appendLine(text, Log::NORMAL); } diff --git a/src/gui/gui.pri b/src/gui/gui.pri index 603bbf10517..bac2fe5ef7e 100644 --- a/src/gui/gui.pri +++ b/src/gui/gui.pri @@ -64,7 +64,21 @@ HEADERS += \ $$PWD/transferlistsortmodel.h \ $$PWD/transferlistwidget.h \ $$PWD/updownratiodialog.h \ - $$PWD/utils.h + $$PWD/utils.h \ + $$PWD/theme/colorprovider_p.h \ + $$PWD/theme/colorproviders.h \ + $$PWD/theme/colortheme.h \ + $$PWD/theme/colortheme_impl.h \ + $$PWD/theme/fontprovider_p.h \ + $$PWD/theme/fontproviders.h \ + $$PWD/theme/fonttheme.h \ + $$PWD/theme/fonttheme_impl.h \ + $$PWD/theme/provider_p.h \ + $$PWD/theme/serializabletheme.h \ + $$PWD/theme/themecommon.h \ + $$PWD/theme/themeexceptions.h \ + $$PWD/theme/themeinfo.h \ + $$PWD/theme/themeprovider.h \ SOURCES += \ $$PWD/addnewtorrentdialog.cpp \ @@ -121,7 +135,19 @@ SOURCES += \ $$PWD/transferlistsortmodel.cpp \ $$PWD/transferlistwidget.cpp \ $$PWD/updownratiodialog.cpp \ - $$PWD/utils.cpp + $$PWD/utils.cpp \ + $$PWD/theme/colorprovider_p.cpp \ + $$PWD/theme/colortheme.cpp \ + $$PWD/theme/fonttheme.cpp \ + $$PWD/theme/fontprovider_p.cpp \ + $$PWD/theme/colorproviders.cpp \ + $$PWD/theme/fontproviders.cpp \ + $$PWD/theme/provider_p.cpp \ + $$PWD/theme/serializabletheme.cpp \ + $$PWD/theme/themecommon.cpp \ + $$PWD/theme/themeexceptions.cpp \ + $$PWD/theme/themeinfo.cpp \ + $$PWD/theme/themeprovider.cpp \ win32|macx { HEADERS += $$PWD/programupdater.h @@ -160,4 +186,6 @@ FORMS += \ $$PWD/trackerlogindialog.ui \ $$PWD/updownratiodialog.ui -RESOURCES += $$PWD/about.qrc +RESOURCES += \ + $$PWD/about.qrc \ + $$PWD/theme/builtinthemes.qrc diff --git a/src/gui/loglistwidget.cpp b/src/gui/loglistwidget.cpp index 572fa5fb10c..35fe51bfb7a 100644 --- a/src/gui/loglistwidget.cpp +++ b/src/gui/loglistwidget.cpp @@ -24,7 +24,9 @@ * 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. - */ + * + * Contact : chris@qbittorrent.org +*/ #include "loglistwidget.h" @@ -37,6 +39,8 @@ #include #include "guiiconprovider.h" +#include "theme/fonttheme.h" +#include "theme/themeprovider.h" LogListWidget::LogListWidget(int maxLines, const Log::MsgTypes &types, QWidget *parent) : QListWidget(parent) @@ -53,6 +57,9 @@ LogListWidget::LogListWidget(int maxLines, const Log::MsgTypes &types, QWidget * addAction(copyAct); addAction(clearAct); setContextMenuPolicy(Qt::ActionsContextMenu); + + connect(&Theme::ThemeProvider::instance(), &Theme::ThemeProvider::fontThemeChanged, + this, &LogListWidget::applyFontTheme); } void LogListWidget::showMsgTypes(const Log::MsgTypes &types) @@ -75,11 +82,23 @@ void LogListWidget::keyPressEvent(QKeyEvent *event) selectAll(); } +void LogListWidget::applyFontTheme() +{ + QFont font = Theme::FontTheme::current().font(Theme::FontTheme::Element::ExecutionLog); + for (int row = 0; row < count(); ++row) { + QListWidgetItem *item = this->item(row); + QLabel *label = static_cast(this->itemWidget(item)); + label->setFont(font); + item->setSizeHint(label->sizeHint()); + } +} + void LogListWidget::appendLine(const QString &line, const Log::MsgType &type) { QListWidgetItem *item = new QListWidgetItem; // We need to use QLabel here to support rich text QLabel *lbl = new QLabel(line); + lbl->setFont(Theme::FontTheme::current().font(Theme::FontTheme::Element::ExecutionLog)); lbl->setContentsMargins(4, 2, 4, 2); item->setSizeHint(lbl->sizeHint()); item->setData(Qt::UserRole, type); diff --git a/src/gui/loglistwidget.h b/src/gui/loglistwidget.h index c289ccc30f4..44ff0f2c599 100644 --- a/src/gui/loglistwidget.h +++ b/src/gui/loglistwidget.h @@ -52,6 +52,9 @@ protected slots: protected: void keyPressEvent(QKeyEvent *event) override; +private slots: + void applyFontTheme(); + private: int m_maxLines; Log::MsgTypes m_types; diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index e91de323bf9..1d59050167d 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -28,7 +28,9 @@ #include "optionsdialog.h" +#include #include +#include #include #include @@ -68,9 +70,13 @@ #include "guiiconprovider.h" #include "rss/automatedrssdownloader.h" #include "scanfoldersdelegate.h" -#include "ui_optionsdialog.h" +#include "theme/themeexceptions.h" +#include "theme/themeinfo.h" +#include "theme/themeprovider.h" #include "utils.h" +#include "ui_optionsdialog.h" + class WheelEventEater : public QObject { public: @@ -83,6 +89,106 @@ class WheelEventEater : public QObject } }; +namespace +{ + /** + * @brief Implements list model for themes of the given kind + * + * The class gets map of the themes from ThemeProvider, converts it into array sorting + * by theme localized name. + */ + class ThemeInfoListModel : public QAbstractListModel + { + public: + ThemeInfoListModel(Theme::Kind kind, QObject *parent); + + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + + struct Entry + { + Theme::ThemeInfo info; + QString filePath; + }; + + const Entry &entry(int index) const; + int indexForTheme(const Theme::ThemeInfo &info); + + private: + std::vector m_entries; + }; + + ThemeInfoListModel::ThemeInfoListModel(Theme::Kind kind, QObject *parent) + : QAbstractListModel(parent) + { + std::map themes = Theme::ThemeProvider::instance().availableThemes(kind); + + for (const auto &pair: themes) + m_entries.push_back({pair.first, pair.second}); + + std::sort(m_entries.begin(), m_entries.end(), [](const Entry &left, const Entry &right) + { + return left.info.localizedName() < right.info.localizedName(); + }); + } + + int ThemeInfoListModel::rowCount(const QModelIndex &/*parent*/) const + { + return static_cast(m_entries.size()); + } + + QVariant ThemeInfoListModel::data(const QModelIndex &index, int role) const + { + if ((index.row() >= static_cast(m_entries.size())) || (index.column() > 0)) return {}; + + switch (role) { + case Qt::DisplayRole: + return entry(index.row()).info.localizedName(); + case Qt::ToolTipRole: + return entry(index.row()).info.localizedDescription(); + } + + return {}; + } + + const ThemeInfoListModel::Entry &ThemeInfoListModel::entry(int index) const + { + return m_entries.at(static_cast(index)); + } + + int ThemeInfoListModel::indexForTheme(const Theme::ThemeInfo& info) + { + auto i = std::find_if(m_entries.begin(), m_entries.end(), [&info](const Entry &entry) + { + return (entry.info == info); + }); + if (i == m_entries.end()) { + qDebug() << "Could not find theme info in the model"; + return 0; // don't want to crash, will lead to theme re-appliance most likely + } + return std::distance(m_entries.begin(), i); + } + + //just a shortcut + const ThemeInfoListModel::Entry &themeEntry(QComboBox *comboBox, int index = -1) + { + if (index < 0) { + index = comboBox->currentIndex(); + } + return static_cast(comboBox->model())->entry(index); + } + + QString descriptionString(const ThemeInfoListModel::Entry &entry) + { + QString res = entry.info.localizedDescription() + QLatin1Char('\n'); + if (entry.filePath.startsWith(QLatin1String(":/"))) // this theme is located in resources + res += QObject::tr("Built-in theme"); + else + res += QObject::tr("Located in: ") + entry.filePath; + return res; + } +} + // Constructor OptionsDialog::OptionsDialog(QWidget *parent) : QDialog(parent) @@ -161,6 +267,17 @@ OptionsDialog::OptionsDialog(QWidget *parent) // Hide the Appearance/Desktop section if it doesn't contain any visible children. m_ui->groupBoxAppearanceDesktop->setHidden(m_ui->groupBoxAppearanceDesktop->childrenRect().isEmpty()); + ThemeInfoListModel *colorThemeModel = new ThemeInfoListModel(Theme::Kind::Color, this); + m_ui->comboBoxColorTheme->setModel(colorThemeModel); + const Theme::ThemeInfo currentColorTheme = Theme::ThemeProvider::instance().currentTheme(Theme::Kind::Color); + m_ui->comboBoxColorTheme->setCurrentIndex(colorThemeModel->indexForTheme(currentColorTheme)); + m_ui->labelColorThemeDescription->setText(descriptionString(themeEntry(m_ui->comboBoxColorTheme, -1))); + ThemeInfoListModel *fontThemeModel = new ThemeInfoListModel(Theme::Kind::Font, this); + m_ui->comboBoxFontTheme->setModel(fontThemeModel); + const Theme::ThemeInfo currentFontTheme = Theme::ThemeProvider::instance().currentTheme(Theme::Kind::Font); + m_ui->comboBoxFontTheme->setCurrentIndex(fontThemeModel->indexForTheme(currentFontTheme)); + m_ui->labelFontThemeDescription->setText(descriptionString(themeEntry(m_ui->comboBoxFontTheme, -1))); + m_ui->scanFoldersView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); m_ui->scanFoldersView->setModel(ScanFoldersModel::instance()); m_ui->scanFoldersView->setItemDelegate(new ScanFoldersDelegate(this, m_ui->scanFoldersView)); @@ -209,6 +326,7 @@ OptionsDialog::OptionsDialog(QWidget *parent) // Shortcuts for frequently used signals that have more than one overload. They would require // type casts and that is why we declare required member pointer here instead. void (QComboBox::*qComboBoxCurrentIndexChanged)(int) = &QComboBox::currentIndexChanged; + void (QComboBox::*qComboBoxActivated)(int) = &QComboBox::activated; void (QSpinBox::*qSpinBoxValueChanged)(int) = &QSpinBox::valueChanged; connect(m_ui->checkForceProxy, &QAbstractButton::toggled, this, &ThisType::enableForceProxy); @@ -216,6 +334,7 @@ OptionsDialog::OptionsDialog(QWidget *parent) connect(m_ui->checkRandomPort, &QAbstractButton::toggled, m_ui->spinPort, &ThisType::setDisabled); // Apply button is activated when a value is changed + // General tab connect(m_ui->comboI18n, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton); connect(m_ui->confirmDeletion, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); @@ -255,6 +374,13 @@ OptionsDialog::OptionsDialog(QWidget *parent) connect(m_ui->spinFileLogSize, qSpinBoxValueChanged, this, &ThisType::enableApplyButton); connect(m_ui->spinFileLogAge, qSpinBoxValueChanged, this, &ThisType::enableApplyButton); connect(m_ui->comboFileLogAgeType, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton); + + // Appearance tab + connect(m_ui->comboBoxColorTheme, qComboBoxActivated, this, &ThisType::colorThemeActivated); + connect(m_ui->comboBoxFontTheme, qComboBoxActivated, this, &ThisType::fontThemeActivated); + connect(m_ui->toolButtonExportColorTheme, &QToolButton::clicked, this, &ThisType::exportColorTheme); + connect(m_ui->toolButtonExportFontTheme, &QToolButton::clicked, this, &ThisType::exportFontTheme); + // Downloads tab connect(m_ui->textSavePath, &FileSystemPathEdit::selectedPathChanged, this, &ThisType::enableApplyButton); connect(m_ui->checkUseSubcategories, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); @@ -551,6 +677,50 @@ void OptionsDialog::saveOptions() qApp->installTranslator(translator); } + // Appearance preferences + const auto applyTheme = [this](Theme::Kind kind, const QString &themeName) + { + const auto themeKindToString = [](Theme::Kind kind) + { + switch (kind) { + case Theme::Kind::Color: + return tr("color", "e.g. 'color theme'"); + case Theme::Kind::Font: + return tr("font", "e.g. 'font theme'"); + default: + return tr("unknown", "e.g. 'unknown theme'"); + } + }; + + using namespace Theme::Serialization; + QString errorMessage; + try { + Theme::ThemeProvider::instance().setCurrentTheme(kind, themeName); + return; + } + catch (ThemeElementMissing &ex) { + errorMessage = tr("theme element '%1' is missing").arg(ex.elementName()); + } + catch (ValueParsingError &ex) { + errorMessage = tr("could not parse element value '%1'").arg(ex.valueString()); + } + catch (ParsingError &ex) { + errorMessage = tr("Could not parse element '%1'").arg(ex.serializedValue()); + } + catch (UnknownProvider &ex) { + errorMessage = tr("theme element contains unknown scheme '%1'").arg(ex.profiderName()); + } + catch (DeserializationError &ex) { + errorMessage = tr("theme loading error '%1'").arg(QString::fromUtf8(ex.what())); + } + QMessageBox::warning(this, tr("Theme applying failed"), + tr("Could not apply %1 theme due to following error:\n%2") + .arg(themeKindToString(kind), errorMessage)); + }; + + applyTheme(Theme::Kind::Color, themeEntry(m_ui->comboBoxColorTheme).info.name()); + applyTheme(Theme::Kind::Font, themeEntry(m_ui->comboBoxFontTheme).info.name()); + // General preferences pref->setLocale(locale); pref->setConfirmTorrentDeletion(m_ui->confirmDeletion->isChecked()); @@ -1510,6 +1680,49 @@ void OptionsDialog::setLocale(const QString &localeStr) m_ui->comboI18n->setCurrentIndex(index); } +void OptionsDialog::colorThemeActivated(int index) +{ + m_ui->labelColorThemeDescription->setText( + descriptionString(themeEntry(m_ui->comboBoxColorTheme, index))); + enableApplyButton(); +} + +void OptionsDialog::fontThemeActivated(int index) +{ + m_ui->labelFontThemeDescription->setText( + descriptionString(themeEntry(m_ui->comboBoxFontTheme, index))); + enableApplyButton(); +} + +void OptionsDialog::exportColorTheme() +{ + QString fileName = QFileDialog::getSaveFileName(this, tr("Save theme as..."), + QDir::homePath(), + tr("qBittorrent color theme") + + QLatin1String(" (*") + Theme::ThemeProvider::colorThemeFileExtension + QLatin1Char(')')); + if (!fileName.isEmpty()) { + Theme::ThemeProvider::instance().exportTheme(Theme::Kind::Color, + themeEntry(m_ui->comboBoxColorTheme, -1).info.name(), fileName); + } +} + +void OptionsDialog::exportFontTheme() +{ + QString fileName = QFileDialog::getSaveFileName(this, tr("Save theme as..."), + QDir::homePath(), + tr("qBittorrent font theme") + + QLatin1String(" (*") + Theme::ThemeProvider::fontThemeFileExtension + QLatin1Char(')')); + if (!fileName.isEmpty()) { + const bool explicitFontStrings = QMessageBox::question(this, tr("Exporting theme"), + tr("Font theme might contain references to default fonts. Do you want to expand them" + " to their actual values?")) == QMessageBox::Yes; + Theme::ThemeProvider::instance().exportTheme(Theme::Kind::Font, + themeEntry(m_ui->comboBoxFontTheme, -1).info.name(), fileName, + explicitFontStrings ? Theme::ThemeProvider::ExportOption::WriteExplicitValues + : Theme::ThemeProvider::ExportOption::NoOptions); + } +} + QString OptionsDialog::getTorrentExportDir() const { if (m_ui->checkExportDir->isChecked()) diff --git a/src/gui/optionsdialog.h b/src/gui/optionsdialog.h index 08454a976cc..46b73d05edb 100644 --- a/src/gui/optionsdialog.h +++ b/src/gui/optionsdialog.h @@ -106,7 +106,11 @@ private slots: void on_btnWebUiCrt_clicked(); void on_btnWebUiKey_clicked(); void on_registerDNSBtn_clicked(); - void setLocale(const QString &localeStr); + void setLocale(const QString &locale); + void colorThemeActivated(int index); + void fontThemeActivated(int index); + void exportColorTheme(); + void exportFontTheme(); private: // Methods diff --git a/src/gui/optionsdialog.ui b/src/gui/optionsdialog.ui index 1d0a002fc90..2dddf1be2b6 100644 --- a/src/gui/optionsdialog.ui +++ b/src/gui/optionsdialog.ui @@ -231,6 +231,113 @@ + + + + Themes + + + + + + Color theme: + + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + TextLabel + + + + + + + Export color theme into a file + + + ... + + + + .. + + + + + + + + + Font theme: + + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + TextLabel + + + + + + + Export font theme into a file + + + ... + + + + .. + + + + + + + + @@ -280,8 +387,8 @@ 0 0 - 504 - 849 + 448 + 887 @@ -3426,7 +3533,6 @@ Use ';' to split multiple entries. Can use wildcard '*'. tabOption textSavePath - browseSaveDirButton checkStartPaused spinPort checkUPnP diff --git a/src/gui/properties/downloadedpiecesbar.cpp b/src/gui/properties/downloadedpiecesbar.cpp index 896ce5f5750..bcae494983b 100644 --- a/src/gui/properties/downloadedpiecesbar.cpp +++ b/src/gui/properties/downloadedpiecesbar.cpp @@ -32,6 +32,8 @@ #include +#include "theme/colortheme.h" + DownloadedPiecesBar::DownloadedPiecesBar(QWidget *parent) : base {parent} , m_dlPieceColor {0, 0xd0, 0} @@ -172,7 +174,15 @@ void DownloadedPiecesBar::clear() QString DownloadedPiecesBar::simpleToolTipText() const { - return tr("White: Missing pieces") + '\n' - + tr("Green: Partial pieces") + '\n' - + tr("Blue: Completed pieces") + '\n'; + return QString(QLatin1String(R"()" + R"()" + R"()" + R"()" + R"(
     : %2
     : %4
     : %6
)")) + .arg(Theme::ColorTheme::current().downloadProgressBarColor(Theme::DownloadProgressBarElement::Background).name()) + .arg(tr("Missing pieces")) + .arg(Theme::ColorTheme::current().downloadProgressBarColor(Theme::DownloadProgressBarElement::Incomplete).name()) + .arg(tr("Partial pieces")) + .arg(Theme::ColorTheme::current().downloadProgressBarColor(Theme::DownloadProgressBarElement::Complete).name()) + .arg(tr("Completed pieces")); } diff --git a/src/gui/properties/pieceavailabilitybar.cpp b/src/gui/properties/pieceavailabilitybar.cpp index dd6411348e3..9969a0a068e 100644 --- a/src/gui/properties/pieceavailabilitybar.cpp +++ b/src/gui/properties/pieceavailabilitybar.cpp @@ -32,6 +32,8 @@ #include +#include "theme/colortheme.h" + PieceAvailabilityBar::PieceAvailabilityBar(QWidget *parent) : base {parent} { @@ -158,8 +160,14 @@ void PieceAvailabilityBar::clear() QString PieceAvailabilityBar::simpleToolTipText() const { - return tr("White: Unavailable pieces") + '\n' - + tr("Blue: Available pieces") + '\n'; + return QString(QLatin1String(R"()" + R"()" + R"()" + R"(
     : %2
     : %4
)")) + .arg(Theme::ColorTheme::current().downloadProgressBarColor(Theme::DownloadProgressBarElement::Background).name()) + .arg(tr("Unavailable pieces")) + .arg(Theme::ColorTheme::current().downloadProgressBarColor(Theme::DownloadProgressBarElement::Complete).name()) + .arg(tr("Available pieces")); } bool PieceAvailabilityBar::isFileNameCorrectionNeeded() const diff --git a/src/gui/properties/propertieswidget.cpp b/src/gui/properties/propertieswidget.cpp index fadcc367543..0e8775927c1 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -55,6 +55,9 @@ #include "proptabbar.h" #include "raisedmessagebox.h" #include "speedwidget.h" +#include "theme/colortheme.h" +#include "theme/fonttheme.h" +#include "theme/themeprovider.h" #include "torrentcontentfiltermodel.h" #include "torrentcontentmodel.h" #include "trackerlistwidget.h" @@ -164,6 +167,14 @@ PropertiesWidget::PropertiesWidget(QWidget *parent, MainWindow *mainWindow, Tran connect(m_deleteHotkeyWeb, &QShortcut::activated, this, &PropertiesWidget::deleteSelectedUrlSeeds); m_openHotkeyFile = new QShortcut(Qt::Key_Return, m_ui->filesList, nullptr, nullptr, Qt::WidgetShortcut); connect(m_openHotkeyFile, &QShortcut::activated, this, &PropertiesWidget::openSelectedFile); + + applyColorTheme(); + applyFontTheme(); + + connect(&Theme::ThemeProvider::instance(), &Theme::ThemeProvider::colorThemeChanged, + this, &PropertiesWidget::applyColorTheme); + connect(&Theme::ThemeProvider::instance(), &Theme::ThemeProvider::fontThemeChanged, + this, &PropertiesWidget::applyFontTheme); } PropertiesWidget::~PropertiesWidget() @@ -898,3 +909,26 @@ void PropertiesWidget::filterText(const QString &filter) m_ui->filesList->expandAll(); } } + +void PropertiesWidget::applyColorTheme() +{ + m_downloadedPieces->setColors( + Theme::ColorTheme::current().downloadProgressBarColor(Theme::DownloadProgressBarElement::Background), + Theme::ColorTheme::current().downloadProgressBarColor(Theme::DownloadProgressBarElement::Border), + Theme::ColorTheme::current().downloadProgressBarColor(Theme::DownloadProgressBarElement::Complete), + Theme::ColorTheme::current().downloadProgressBarColor(Theme::DownloadProgressBarElement::Incomplete)); + m_piecesAvailability->setColors( + Theme::ColorTheme::current().downloadProgressBarColor(Theme::DownloadProgressBarElement::Background), + Theme::ColorTheme::current().downloadProgressBarColor(Theme::DownloadProgressBarElement::Border), + Theme::ColorTheme::current().downloadProgressBarColor(Theme::DownloadProgressBarElement::Complete)); +} + +void PropertiesWidget::applyFontTheme() +{ + QFont font = Theme::FontTheme::current().font(Theme::FontTheme::Element::TorrentProperties); + foreach (QWidget *widget, findChildren()) + widget->setFont(font); + + foreach (QWidget *widget, findChildren()) + widget->setFont(font); +} diff --git a/src/gui/properties/propertieswidget.h b/src/gui/properties/propertieswidget.h index fc7ef72aca8..a0d942de9dd 100644 --- a/src/gui/properties/propertieswidget.h +++ b/src/gui/properties/propertieswidget.h @@ -108,6 +108,8 @@ protected slots: void openSelectedFile(); private slots: + void applyColorTheme(); + void applyFontTheme(); void filterText(const QString &filter); void updateSavePath(BitTorrent::TorrentHandle *const torrent); diff --git a/src/gui/theme/builtinthemes.qrc b/src/gui/theme/builtinthemes.qrc new file mode 100644 index 00000000000..7e2e3f57f5c --- /dev/null +++ b/src/gui/theme/builtinthemes.qrc @@ -0,0 +1,7 @@ + + + qBittorrentLight.colortheme + qBittorrentDark.colortheme + qBittorrent.fonttheme + + diff --git a/src/gui/theme/colorprovider_p.cpp b/src/gui/theme/colorprovider_p.cpp new file mode 100644 index 00000000000..dc09463d428 --- /dev/null +++ b/src/gui/theme/colorprovider_p.cpp @@ -0,0 +1,68 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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 "colorprovider_p.h" + +#include + +// -------------------------- Color ----------------------------------------------- + +QString Theme::Serialization::Color::explicitSerializedValue() const +{ + return value().name(QColor::HexRgb); +} + +// ------------------------- ColorsProviderRegistry ------------------------------ + +namespace +{ + class ColorsProviderRegistrySignletonImpl : public Theme::Serialization::ColorsProviderRegistry + { + public: + ~ColorsProviderRegistrySignletonImpl() = default; + }; + + Q_GLOBAL_STATIC(ColorsProviderRegistrySignletonImpl, colorsProviderRegistrySignletonImpl) +} + +Theme::Serialization::ColorsProviderRegistry &Theme::Serialization::ColorsProviderRegistry::instance() +{ + return *colorsProviderRegistrySignletonImpl(); +} + +void Theme::Serialization::ColorsProviderRegistry::applicationPaletteChanged() const +{ + for (const auto &providerPair : providers()) + providerPair.second->applicationPaletteChanged(); +} + +// ------------------------- ColorProvider ------------------------------ + +void Theme::Serialization::ColorProvider::applicationPaletteChanged() const +{ +} diff --git a/src/gui/theme/colorprovider_p.h b/src/gui/theme/colorprovider_p.h new file mode 100644 index 00000000000..da47cf31f46 --- /dev/null +++ b/src/gui/theme/colorprovider_p.h @@ -0,0 +1,72 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEME_COLORPROVIDER_P_H +#define QBT_THEME_COLORPROVIDER_P_H + +#include "provider_p.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Theme +{ + namespace Serialization + { + class Color : public Entity + { + public: + QString explicitSerializedValue() const override; + }; + + class ColorProvider : public Provider + { + public: + using Provider::Provider; + virtual void applicationPaletteChanged() const; + }; + + class ColorsProviderRegistry : public ProviderRegistry + { + public: + static const Kind kind = Kind::Color; + + static ColorsProviderRegistry &instance(); + void applicationPaletteChanged() const; + }; + } +} + +#endif // QBT_COLORPROVIDER_P_H diff --git a/src/gui/theme/colorproviders.cpp b/src/gui/theme/colorproviders.cpp new file mode 100644 index 00000000000..b203d2d5f8e --- /dev/null +++ b/src/gui/theme/colorproviders.cpp @@ -0,0 +1,183 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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 "colorproviders.h" + +#include + +namespace +{ + struct ColorProvidersRegistrator + { + ColorProvidersRegistrator() + { + using Registry = Theme::Serialization::ColorsProviderRegistry; + using ProviderUPtr = Registry::ProviderUPtr; + + Registry::instance().registerProvider(ProviderUPtr(new Theme::Serialization::ExplicitColorProvider())); + Registry::instance().registerProvider(ProviderUPtr(new Theme::Serialization::PaletteColorProvider())); + } + }; + + const QMetaEnum &QPaletteGroupMeta() + { + static QMetaEnum res = QMetaEnum::fromType(); + return res; + } + + const QMetaEnum &QPaletteRoleMeta() + { + static QMetaEnum res = QMetaEnum::fromType(); + return res; + } +} + +void Theme::Serialization::registerColorProviders() +{ + static ColorProvidersRegistrator colorProvidersRegistrator; +} + +Theme::Serialization::ExplicitColor::ExplicitColor(const QColor &color) + : m_value {color} +{ +} + +/* ExplicitColor is just what it sounds like: a single QColor value. + * It is serialized into a string formatted as follows: ,,, e.g.: 127,234,12 + */ +Theme::Serialization::ExplicitColor::ExplicitColor(const QString &serialized) + : m_value {QColor(serialized)} +{ + if (!m_value.isValid()) + throw ValueParsingError("QColor could not recognize color string", serialized); +} + +QColor Theme::Serialization::ExplicitColor::value() const +{ + return m_value; +} + +QString Theme::Serialization::ExplicitColor::serializedValue() const +{ + return explicitSerializedValue(); +} + +QString Theme::Serialization::ExplicitColor::serializationKey() const +{ + return QLatin1String("RGB"); +} + +QPalette Theme::Serialization::PaletteColor::m_palette; + +Theme::Serialization::PaletteColor::PaletteColor(QPalette::ColorGroup group, QPalette::ColorRole role) + : m_group(group) + , m_role(role) +{ +} + +Theme::Serialization::PaletteColor::PaletteColor(const QString &serialized) +{ + const auto list = serialized.split(QLatin1Char(':')); + if (list.size() != 2) + throw ValueParsingError("QPalette color notation should have 2 components", serialized); + + bool parsedOk = false; + const int groupRawVal = QPaletteGroupMeta().keyToValue(list[0].toLatin1().constData(), &parsedOk); + if (!parsedOk) + throw ValueParsingError("Could not parse QPalette group name", serialized); + m_group = static_cast(groupRawVal); + + const int roleRawVal = QPaletteRoleMeta().keyToValue(list[1].toLatin1().constData(), &parsedOk); + if (!parsedOk) + throw ValueParsingError("Could not parse QPalette role name", serialized); + m_role = static_cast(roleRawVal); +} + +void Theme::Serialization::PaletteColor::reloadDefaultPalette() +{ + m_palette = QPalette(); // which asks QApplication instance for the current default palette +} + +QColor Theme::Serialization::PaletteColor::value() const +{ + return m_palette.color(m_group, m_role); +} + +QString Theme::Serialization::PaletteColor::serializedValue() const +{ + return QString(QLatin1String("%1:%2")) + .arg(QPaletteGroupMeta().key(m_group)).arg(QPaletteRoleMeta().key(m_role)); +} + +QString Theme::Serialization::PaletteColor::serializationKey() const +{ + return QLatin1String("QPalette"); +} + +void Theme::Serialization::PaletteColorProvider::applicationPaletteChanged() const +{ + PaletteColor::reloadDefaultPalette(); +} + +Theme::Serialization::ExplicitColorProvider::ExplicitColorProvider() + : ColorProvider(ExplicitColor(QColor(Qt::black)).serializationKey()) +{ +} + +Theme::Serialization::ExplicitColorProvider::EntityUPtr +Theme::Serialization::ExplicitColorProvider::load(const QString &serialized) const +{ + return EntityUPtr(new ExplicitColor(serialized)); +} + +Theme::Serialization::PaletteColorProvider::PaletteColorProvider() + : ColorProvider(PaletteColor(QPalette::Active, QPalette::Window).serializationKey()) +{ +} + +Theme::Serialization::PaletteColorProvider::EntityUPtr +Theme::Serialization::PaletteColorProvider::load(const QString &serialized) const +{ + return EntityUPtr(new PaletteColor(serialized)); +} + +Theme::Serialization::ColorProviderWidgetUI::~ColorProviderWidgetUI() = default; + +Theme::Serialization::ColorPickingWidget * +Theme::Serialization::ExplicitColorProvider::createEditorWidget(QWidget *parentWidget) const +{ + Q_UNUSED(parentWidget); + throw std::logic_error("Not implemented"); +} + +Theme::Serialization::ColorPickingWidget * +Theme::Serialization::PaletteColorProvider::createEditorWidget(QWidget *parentWidget) const +{ + Q_UNUSED(parentWidget); + throw std::logic_error("Not implemented"); +} diff --git a/src/gui/theme/colorproviders.h b/src/gui/theme/colorproviders.h new file mode 100644 index 00000000000..8b9b4677508 --- /dev/null +++ b/src/gui/theme/colorproviders.h @@ -0,0 +1,130 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEME_COLORPROVIDERS_H +#define QBT_THEME_COLORPROVIDERS_H + +#include + +#include "colorprovider_p.h" + +namespace Theme +{ + namespace Serialization + { + void registerColorProviders(); + + class ExplicitColor : public Color + { + public: + explicit ExplicitColor(const QColor &color); + explicit ExplicitColor(const QString &serialized); + + QColor value() const override; + QString serializedValue() const override; + QString serializationKey() const override; + + private: + QColor m_value; + }; + + // links to a color from app default palette; + class PaletteColor : public Color + { + public: + explicit PaletteColor(QPalette::ColorGroup group, QPalette::ColorRole role); + explicit PaletteColor(const QString &serialized); + + QColor value() const override; + QString serializedValue() const override; + QString serializationKey() const override; + + static void reloadDefaultPalette(); + + private: + static QPalette m_palette; + QPalette::ColorGroup m_group; + QPalette::ColorRole m_role; + }; + + class ColorPickingWidget : public QWidget + { + public: + ColorPickingWidget(QWidget *parent); + + /** + * @brief ... + * + * @return may return nullptr + */ + virtual ColorProvider::EntityUPtr selectedColor() const = 0; + virtual void selectColor(const Color &color) = 0; + }; + + /*! \brief Interface provides Widget-based UI for color providers + * + * This interface may be used when a color theme is edited and user wants to + * select one of the supported colors using QWidget-based UI + */ + class ColorProviderWidgetUI + { + public: + virtual ~ColorProviderWidgetUI(); + virtual ColorPickingWidget *createEditorWidget(QWidget *parentWidget) const = 0; + }; + + namespace impl + { + class ExplicitColorWidget; + class PaletteColorWidget; + } + + class ExplicitColorProvider : public ColorProvider, public ColorProviderWidgetUI + { + public: + ExplicitColorProvider(); + + private: + ColorPickingWidget *createEditorWidget(QWidget *parentWidget) const override; + ColorProvider::EntityUPtr load(const QString &serialized) const override; + }; + + class PaletteColorProvider : public ColorProvider, public ColorProviderWidgetUI + { + public: + PaletteColorProvider(); + + private: + ColorPickingWidget *createEditorWidget(QWidget *parentWidget) const override; + ColorProvider::EntityUPtr load(const QString &serialized) const override; + void applicationPaletteChanged() const override; + }; + } +} + +#endif // QBT_THEME_COLORPROVIDERS_H diff --git a/src/gui/theme/colortheme.cpp b/src/gui/theme/colortheme.cpp new file mode 100644 index 00000000000..6052473e71c --- /dev/null +++ b/src/gui/theme/colortheme.cpp @@ -0,0 +1,179 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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 "colortheme.h" + +#include +#include +#include +#include + +#include "base/bittorrent/torrenthandle.h" +#include "base/logger.h" +#include "colorprovider_p.h" +#include "colortheme_impl.h" +#include "themeprovider.h" + +namespace +{ + Theme::ColorTheme::Element toThemeElement(Theme::DownloadProgressBarElement element) + { + switch (element) { + case Theme::DownloadProgressBarElement::Background: + return Theme::ColorTheme::Element::DownloadProgressBarElement_Background; + case Theme::DownloadProgressBarElement::Border: + return Theme::ColorTheme::Element::DownloadProgressBarElement_Border; + case Theme::DownloadProgressBarElement::Complete: + return Theme::ColorTheme::Element::DownloadProgressBarElement_Complete; + case Theme::DownloadProgressBarElement::Incomplete: + return Theme::ColorTheme::Element::DownloadProgressBarElement_Incomplete; + default: + throw std::logic_error("Unexpected DownloadProgressBarElement value"); + } + } + + Theme::ColorTheme::Element toThemeElement(Log::MsgType messageType) + { + switch (messageType) { + case Log::MsgType::ALL: + return Theme::ColorTheme::Element::MsgType_ALL; + case Log::MsgType::NORMAL: + return Theme::ColorTheme::Element::MsgType_NORMAL; + case Log::MsgType::INFO: + return Theme::ColorTheme::Element::MsgType_INFO; + case Log::MsgType::WARNING: + return Theme::ColorTheme::Element::MsgType_WARNING; + case Log::MsgType::CRITICAL: + return Theme::ColorTheme::Element::MsgType_CRITICAL; + default: + throw std::logic_error("Unexpected Log::MsgType value"); + } + } + + Theme::ColorTheme::Element toThemeElement(BitTorrent::TorrentState state) + { + switch (state) { + case BitTorrent::TorrentState::Unknown: + return Theme::ColorTheme::Element::TorrentState_Unknown; + case BitTorrent::TorrentState::Error: + return Theme::ColorTheme::Element::TorrentState_Error; + case BitTorrent::TorrentState::MissingFiles: + return Theme::ColorTheme::Element::TorrentState_MissingFiles; + case BitTorrent::TorrentState::Uploading: + return Theme::ColorTheme::Element::TorrentState_Uploading; + case BitTorrent::TorrentState::PausedUploading: + return Theme::ColorTheme::Element::TorrentState_PausedUploading; + case BitTorrent::TorrentState::QueuedUploading: + return Theme::ColorTheme::Element::TorrentState_QueuedUploading; + case BitTorrent::TorrentState::StalledUploading: + return Theme::ColorTheme::Element::TorrentState_StalledUploading; + case BitTorrent::TorrentState::CheckingUploading: + return Theme::ColorTheme::Element::TorrentState_CheckingUploading; + case BitTorrent::TorrentState::ForcedUploading: + return Theme::ColorTheme::Element::TorrentState_ForcedUploading; + case BitTorrent::TorrentState::Allocating: + return Theme::ColorTheme::Element::TorrentState_Allocating; + case BitTorrent::TorrentState::Downloading: + return Theme::ColorTheme::Element::TorrentState_Downloading; + case BitTorrent::TorrentState::DownloadingMetadata: + return Theme::ColorTheme::Element::TorrentState_DownloadingMetadata; + case BitTorrent::TorrentState::PausedDownloading: + return Theme::ColorTheme::Element::TorrentState_PausedDownloading; + case BitTorrent::TorrentState::QueuedDownloading: + return Theme::ColorTheme::Element::TorrentState_QueuedDownloading; + case BitTorrent::TorrentState::StalledDownloading: + return Theme::ColorTheme::Element::TorrentState_StalledDownloading; + case BitTorrent::TorrentState::CheckingDownloading: + return Theme::ColorTheme::Element::TorrentState_CheckingDownloading; + case BitTorrent::TorrentState::ForcedDownloading: + return Theme::ColorTheme::Element::TorrentState_ForcedDownloading; + #if LIBTORRENT_VERSION_NUM < 10100 + case BitTorrent::TorrentState::QueuedForChecking: + return Theme::ColorTheme::Element::TorrentState_QueuedForChecking; + #endif + case BitTorrent::TorrentState::CheckingResumeData: + return Theme::ColorTheme::Element::TorrentState_CheckingResumeData; + default: + throw std::logic_error("Unexpected TorrentState value"); + } + } +} + +const Theme::ColorTheme &Theme::ColorTheme::current() +{ + return ThemeProvider::instance().colorTheme(); +} + +QColor Theme::ColorTheme::downloadProgressBarColor(const DownloadProgressBarElement elem) const +{ + return color(toThemeElement(elem)); +} + +QColor Theme::ColorTheme::logMessageColor(const Log::MsgType messageType) const +{ + return color(toThemeElement(messageType)); +} + +QColor Theme::ColorTheme::torrentStateColor(const BitTorrent::TorrentState state) const +{ + return color(toThemeElement(state)); +} + +Theme::SerializableColorTheme::SerializableColorTheme(const QString &name) + : BaseSerializableTheme(name, elementNames()) +{ +} + +void Theme::SerializableColorTheme::applicationPaletteChanged() +{ + updateCache(); +} + +Theme::SerializableColorTheme::BaseSerializableTheme::NamesMap +Theme::SerializableColorTheme::elementNames() +{ + const QMetaEnum meta = QMetaEnum::fromType(); + NamesMap res; + for (int i = 0; i < meta.keyCount(); ++i) { + QByteArray key = meta.key(i); + Q_ASSERT(key.indexOf('_') > 0); + key.replace('_', '/'); + res.insert({key, meta.value(i)}); + } + return res; +} + +const Theme::ThemeInfo &Theme::SerializableColorTheme::info() const +{ + return BaseSerializableTheme::info(); +} + +QColor Theme::SerializableColorTheme::color(Theme::ColorTheme::Element element) const +{ + return this->element(element); +} diff --git a/src/gui/theme/colortheme.h b/src/gui/theme/colortheme.h new file mode 100644 index 00000000000..10102a7c063 --- /dev/null +++ b/src/gui/theme/colortheme.h @@ -0,0 +1,128 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEME_COLORTHEME_H +#define QBT_THEME_COLORTHEME_H + +#include +#include + +#include +#include + +#include + +#include "themecommon.h" + +class QColor; +class QSettings; + +namespace BitTorrent +{ + enum class TorrentState; +} + +namespace Log +{ + enum MsgType : int; +} + +namespace Theme +{ + enum class DownloadProgressBarElement + { + Background, + Border, + Complete, + Incomplete + }; + + /*! \brief qBittorrent color theme + * + * Contains all colors that are needed to render a qBt UI, either using widgets or Web + */ + class ColorTheme : public VisualTheme + { + Q_GADGET + + public: + /** + * @brief Elements of the color theme + * + * @note Underscores in the element names mark positions of group separators, i.e. will be + * replaced by '/' when saving to QSettings + */ + enum class Element + { + TorrentState_Unknown, + TorrentState_ForcedDownloading, + TorrentState_Downloading, + TorrentState_DownloadingMetadata, + TorrentState_Allocating, + TorrentState_StalledDownloading, + TorrentState_ForcedUploading, + TorrentState_Uploading, + TorrentState_StalledUploading, + #if LIBTORRENT_VERSION_NUM < 10100 + TorrentState_QueuedForChecking, + #endif + TorrentState_CheckingResumeData, + TorrentState_QueuedDownloading, + TorrentState_QueuedUploading, + TorrentState_CheckingUploading, + TorrentState_CheckingDownloading, + TorrentState_PausedDownloading, + TorrentState_PausedUploading, + TorrentState_MissingFiles, + TorrentState_Error, + + MsgType_ALL, + MsgType_NORMAL, + MsgType_INFO, + MsgType_WARNING, + MsgType_CRITICAL, + + DownloadProgressBarElement_Background, + DownloadProgressBarElement_Border, + DownloadProgressBarElement_Complete, + DownloadProgressBarElement_Incomplete + }; + + Q_ENUM(Element) + + virtual QColor color(Element element) const = 0; + + QColor torrentStateColor(const BitTorrent::TorrentState state) const; + QColor logMessageColor(const Log::MsgType messageType) const; + QColor downloadProgressBarColor(const DownloadProgressBarElement element) const; + + static const ColorTheme ¤t(); + }; +} + +#endif // QBT_THEME_COLORTHEME_H diff --git a/src/gui/theme/colortheme_impl.h b/src/gui/theme/colortheme_impl.h new file mode 100644 index 00000000000..40f77a22d6f --- /dev/null +++ b/src/gui/theme/colortheme_impl.h @@ -0,0 +1,57 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEME_COLORTHEME_P_H +#define QBT_THEME_COLORTHEME_P_H + +#include "colorprovider_p.h" +#include "colortheme.h" +#include "serializabletheme.h" + +namespace Theme +{ + class SerializableColorTheme : public ColorTheme, + protected SerializableTheme + { + public: + using BaseSerializableTheme = SerializableTheme; + SerializableColorTheme(const QString &name); + + using BaseSerializableTheme::save; + + const ThemeInfo &info() const override; + QColor color(Theme::ColorTheme::Element element) const override; + + void applicationPaletteChanged(); + + private: + static BaseSerializableTheme::NamesMap elementNames(); + }; +} + +#endif // QBT_THEME_COLORTHEME_P_H diff --git a/src/gui/theme/fontprovider_p.cpp b/src/gui/theme/fontprovider_p.cpp new file mode 100644 index 00000000000..e6f638124c3 --- /dev/null +++ b/src/gui/theme/fontprovider_p.cpp @@ -0,0 +1,48 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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 "fontprovider_p.h" + +#include + +// ------------------------- FontProviderRegistry ------------------------------ +namespace +{ + class FontProviderRegistrySignletonImpl : public Theme::Serialization::FontProviderRegistry + { + public: + ~FontProviderRegistrySignletonImpl() = default; + }; + + Q_GLOBAL_STATIC(FontProviderRegistrySignletonImpl, fontProviderRegistrySignletonImpl) +} + +Theme::Serialization::FontProviderRegistry &Theme::Serialization::FontProviderRegistry::instance() +{ + return *fontProviderRegistrySignletonImpl(); +} diff --git a/src/gui/theme/fontprovider_p.h b/src/gui/theme/fontprovider_p.h new file mode 100644 index 00000000000..2df1657209a --- /dev/null +++ b/src/gui/theme/fontprovider_p.h @@ -0,0 +1,57 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEME_FONTPROVIDER_P_H +#define QBT_THEME_FONTPROVIDER_P_H + +#include "provider_p.h" + +#include + +namespace Theme +{ + namespace Serialization + { + using Font = Entity; + + class FontProvider : public Provider + { + public: + using Provider::Provider; + }; + + class FontProviderRegistry : public ProviderRegistry + { + public: + static const Kind kind = Kind::Font; + static FontProviderRegistry &instance(); + }; + } +} + +#endif // QBT_THEME_FONTPROVIDER_P_H diff --git a/src/gui/theme/fontproviders.cpp b/src/gui/theme/fontproviders.cpp new file mode 100644 index 00000000000..f57fdbff433 --- /dev/null +++ b/src/gui/theme/fontproviders.cpp @@ -0,0 +1,105 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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 "fontproviders.h" + +#include "themeexceptions.h" + +namespace +{ + struct FontProvidersRegistrator + { + FontProvidersRegistrator() + { + using Registry = Theme::Serialization::FontProviderRegistry; + using ProviderUPtr = Registry::ProviderUPtr; + + Registry::instance().registerProvider(ProviderUPtr(new Theme::Serialization::ExplicitFontProvider())); + } + }; +} + +void Theme::Serialization::registerFontProviders() +{ + static FontProvidersRegistrator fontProvidersRegistrator; +} + +Theme::Serialization::ExplicitFont::ExplicitFont(const QFont &color) + : m_value {color} +{ +} + +/* ExplicitFont is just what it means: a single QFont value. + * It is serialized into a string formatted as QFont::toString() + */ +Theme::Serialization::ExplicitFont::ExplicitFont(const QString &serialized) + : m_value {fromString(serialized)} +{ +} + +QFont Theme::Serialization::ExplicitFont::fromString(const QString &str) +{ + if (str.isEmpty()) + // they want default app font + return QFont(); + QFont res; + if (!res.fromString(str)) + throw ValueParsingError("QFont could not recognize font string", str); + return res; +} + +QFont Theme::Serialization::ExplicitFont::value() const +{ + return m_value; +} + +QString Theme::Serialization::ExplicitFont::explicitSerializedValue() const +{ + return m_value.toString(); +} + +QString Theme::Serialization::ExplicitFont::serializedValue() const +{ + return explicitSerializedValue(); +} + +QString Theme::Serialization::ExplicitFont::serializationKey() const +{ + return QLatin1String("QFont"); +} + +Theme::Serialization::ExplicitFontProvider::ExplicitFontProvider() + : FontProvider(ExplicitFont(QFont()).serializationKey()) +{ +} + +Theme::Serialization::ExplicitFontProvider::EntityUPtr +Theme::Serialization::ExplicitFontProvider::load(const QString &serialized) const +{ + return EntityUPtr(new ExplicitFont(serialized)); +} diff --git a/src/gui/theme/fontproviders.h b/src/gui/theme/fontproviders.h new file mode 100644 index 00000000000..e39c469bd03 --- /dev/null +++ b/src/gui/theme/fontproviders.h @@ -0,0 +1,67 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEME_GUIFONTPROVIDERS_H +#define QBT_THEME_GUIFONTPROVIDERS_H + +#include "fontprovider_p.h" + +namespace Theme +{ + namespace Serialization + { + void registerFontProviders(); + + class ExplicitFont : public Font + { + public: + explicit ExplicitFont(const QFont &color); + explicit ExplicitFont(const QString &serialized); + + QFont value() const override; + QString serializedValue() const override; + QString explicitSerializedValue() const override; + QString serializationKey() const override; + + private: + static QFont fromString(const QString &str); + QFont m_value; + }; + + class ExplicitFontProvider : public FontProvider + { + public: + ExplicitFontProvider(); + + private: + FontProvider::EntityUPtr load(const QString &serialized) const override; + }; + } +} + +#endif // QBT_THEME_GUIFONTPROVIDERS_H diff --git a/src/gui/theme/fonttheme.cpp b/src/gui/theme/fonttheme.cpp new file mode 100644 index 00000000000..83fbe7d495a --- /dev/null +++ b/src/gui/theme/fonttheme.cpp @@ -0,0 +1,66 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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 "fonttheme.h" + +#include +#include + +#include "fonttheme_impl.h" +#include "themeexceptions.h" +#include "themeprovider.h" + +namespace +{ + const QByteArray fontsGroupName("Fonts/"); +} + +const Theme::FontTheme &Theme::FontTheme::current() +{ + return ThemeProvider::instance().fontTheme(); +} + +Theme::SerializableFontTheme::SerializableFontTheme(const QString &themeName) + : BaseSerializableTheme(themeName, elementNames()) +{ +} + +const Theme::ThemeInfo &Theme::SerializableFontTheme::info() const +{ + return BaseSerializableTheme::info(); +} + +Theme::SerializableFontTheme::BaseSerializableTheme::NamesMap Theme::SerializableFontTheme::elementNames() +{ + const QMetaEnum meta = QMetaEnum::fromType(); + NamesMap res; + for (int i = 0; i < meta.keyCount(); ++i) { + res.insert({QByteArray(fontsGroupName + QByteArray(meta.key(i))), meta.value(i)}); + } + return res; +} diff --git a/src/gui/theme/fonttheme.h b/src/gui/theme/fonttheme.h new file mode 100644 index 00000000000..843cac77a25 --- /dev/null +++ b/src/gui/theme/fonttheme.h @@ -0,0 +1,63 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEME_FONTTHEME_H +#define QBT_THEME_FONTTHEME_H + +#include +#include + +#include "themecommon.h" + +class QFont; + +namespace Theme +{ + class ThemeInfo; + + class FontTheme : public VisualTheme + { + Q_GADGET + + public: + enum class Element + { + TransferList, + TorrentProperties, + ExecutionLog + }; + + Q_ENUM(Element) + + virtual const QFont &font(Element element) const = 0; + + static const FontTheme ¤t(); + }; +} + +#endif // QBT_THEME_FONTTHEME_H diff --git a/src/gui/theme/fonttheme_impl.h b/src/gui/theme/fonttheme_impl.h new file mode 100644 index 00000000000..fdf875a3a53 --- /dev/null +++ b/src/gui/theme/fonttheme_impl.h @@ -0,0 +1,60 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEME_FONTTHEME_P_H +#define QBT_THEME_FONTTHEME_P_H + +#include "fontprovider_p.h" +#include "fonttheme.h" +#include "serializabletheme.h" +#include "themeinfo.h" + +namespace Theme +{ + class SerializableFontTheme : public FontTheme, + protected SerializableTheme + { + public: + using BaseSerializableTheme = SerializableTheme; + SerializableFontTheme(const QString &themeName); + + using BaseSerializableTheme::save; + + const QFont &font(FontTheme::Element e) const override + { + return element(e); + } + + const Theme::ThemeInfo &info() const override; + + private: + static BaseSerializableTheme::NamesMap elementNames(); + }; +} + +#endif // QBT_THEME_FONTTHEME_P_H diff --git a/src/gui/theme/provider_p.cpp b/src/gui/theme/provider_p.cpp new file mode 100644 index 00000000000..966d302f1bd --- /dev/null +++ b/src/gui/theme/provider_p.cpp @@ -0,0 +1,30 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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 "provider_p.h" + diff --git a/src/gui/theme/provider_p.h b/src/gui/theme/provider_p.h new file mode 100644 index 00000000000..8ffd5499e3a --- /dev/null +++ b/src/gui/theme/provider_p.h @@ -0,0 +1,168 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEME_PROVIDER_P_H +#define QBT_THEME_PROVIDER_P_H + +#include +#include +#include +#include + +#include + +#include "themecommon.h" +#include "themeexceptions.h" + +namespace Theme +{ + namespace Serialization + { + /* + * Serialization: each serializable object shall be able to serialize itself into a QString value. + * To freely mix objects from different sources within a theme, they are serialized as ID:value pairs. + * For example: + * + * Background=RGB:128,128,125 + * Stalled=QPalette:Inactive,Midlight + * + * The value format is determined by the object, while : part is written and read + * by the managing code. + */ + + template + class Entity + { + public: + using ValueType = Value; + + virtual ~Entity() = default; + virtual Value value() const = 0; + virtual QString serializedValue() const = 0; + virtual QString explicitSerializedValue() const = 0; + virtual QString serializationKey() const = 0; + }; + + template + class Provider + { + public: + using EntityType = Entity; + using EntityUPtr = std::unique_ptr; + virtual ~Provider() = default; + QString name() const; + virtual EntityUPtr load(const QString &serialized) const = 0; + + protected: + Provider(const QString &name); + + private: + QString m_name; + }; + + template + class ProviderRegistry + { + public: + using ProviderType = Provider; + using ProviderUPtr = std::unique_ptr; + using EntityUPtr = typename ProviderType::EntityUPtr; + + void registerProvider(ProviderUPtr creator); + const ProviderType *provider(const QString &name) const; + EntityUPtr load(const QString &serializedWithKey) const; + + void applicationPaletteChanged() const; + + protected: + ~ProviderRegistry() = default; + + using ProvidersMap = std::map; + const ProvidersMap &providers() const; + + private: + ProvidersMap m_providers; + }; + + // ------------------------- implementation ------------------------------ + template + inline Provider::Provider(const QString &name) + : m_name(name) + { + } + + template + inline QString Provider::name() const + { + return m_name; + } + + template + inline void ProviderRegistry::registerProvider(ProviderUPtr creator) + { + const QString id = creator->name(); + Q_ASSERT_X(m_providers.count(id) == 0, Q_FUNC_INFO, + "Attempted to register the same theme color type twice"); + m_providers[id] = std::move(creator); + } + + template + inline const typename ProviderRegistry::ProviderType * + ProviderRegistry::provider(const QString &name) const + { + const auto i = m_providers.find(name); + if (i != m_providers.end()) + return i->second.get(); + return nullptr; + } + + template + inline typename ProviderRegistry::EntityUPtr + ProviderRegistry::load(const QString &serializedWithKey) const + { + const int keyValueSeparatorPos = serializedWithKey.indexOf(QLatin1Char(':'), 0); + if (keyValueSeparatorPos != -1) { + const QString id = serializedWithKey.left(keyValueSeparatorPos); + const auto *provider = this->provider(id); + if (provider) + return provider->load(serializedWithKey.right(serializedWithKey.size() - keyValueSeparatorPos - 1)); + throw UnknownProvider(serializedWithKey); + } + qCInfo(theme, "Failed to parse serialized value '%s'", qPrintable(serializedWithKey)); + throw ParsingError(serializedWithKey); + } + + template + const typename ProviderRegistry::ProvidersMap &ProviderRegistry::providers() const + { + return m_providers; + } + } +} + +#endif // QBT_THEME_PROVIDER_P_H diff --git a/src/gui/theme/qBittorrent.fonttheme b/src/gui/theme/qBittorrent.fonttheme new file mode 100644 index 00000000000..4a920d119ad --- /dev/null +++ b/src/gui/theme/qBittorrent.fonttheme @@ -0,0 +1,8 @@ +[Info] +Name=Default +Description=Default qBittorrent font theme. + +[Fonts] +TransferList=QFont: +TorrentProperties=QFont: +ExecutionLog=QFont: diff --git a/src/gui/theme/qBittorrentDark.colortheme b/src/gui/theme/qBittorrentDark.colortheme new file mode 100644 index 00000000000..95c866f5209 --- /dev/null +++ b/src/gui/theme/qBittorrentDark.colortheme @@ -0,0 +1,27 @@ +[Info] +Name=Default (Dark) +Description=Default qBittorrent color theme. Dark flavour. +Inherits=Default (Light) + +[TorrentState] +Uploading=RGB:#63b8ff +PausedUploading=RGB:#4f94cd +QueuedUploading=RGB:#00cdcd +StalledUploading=RGB:#cccccc +CheckingUploading=RGB:#00cdcd +ForcedUploading=RGB:#63b8ff +Allocating=RGB:#cccccc +Downloading=RGB:#32cd32 +DownloadingMetadata=RGB:#32cd32 +PausedDownloading=RGB:#fa8072 +QueuedDownloading=RGB:#00cdcd +StalledDownloading=RGB:#cccccc +CheckingDownloading=RGB:#00cdcd +ForcedDownloading=RGB:#32cd32 +QueuedForChecking=RGB:#00cdcd +CheckingResumeData=RGB:#00cdcd + +[LogMessageType] +INFO=RGB:lightblue +WARNING=RGB:lightsalmon +CRITICAL=RGB:red diff --git a/src/gui/theme/qBittorrentLight.colortheme b/src/gui/theme/qBittorrentLight.colortheme new file mode 100644 index 00000000000..c2590e1a1bd --- /dev/null +++ b/src/gui/theme/qBittorrentLight.colortheme @@ -0,0 +1,37 @@ +[Info] +Name=Default (Light) +Description=Default qBittorrent color theme. Light flavour. + +[TorrentState] +Unknown=RGB:#ff0000 +Error=RGB:#ff0000 +MissingFiles=RGB:#ff0000 +Uploading=RGB:#4169e1 +PausedUploading=RGB:#000081 +QueuedUploading=RGB:#008080 +StalledUploading=RGB:#000000 +CheckingUploading=RGB:#008080 +ForcedUploading=RGB:#4169e1 +Allocating=RGB:#000000 +Downloading=RGB:#228b22 +DownloadingMetadata=RGB:#228b22 +PausedDownloading=RGB:#fa8072 +QueuedDownloading=RGB:#008080 +StalledDownloading=RGB:#000000 +CheckingDownloading=RGB:#008080 +ForcedDownloading=RGB:#228b22 +QueuedForChecking=RGB:#008080 +CheckingResumeData=RGB:#008080 + +[LogMessageType] +ALL=QPalette:Active:WindowText +NORMAL=QPalette:Active:WindowText +INFO=RGB:blue +WARNING=RGB:orange +CRITICAL=RGB:red + +[DownloadProgressBar] +Background=RGB:white +Border=QPalette:Active:Dark +Complete=RGB:blue +Incomplete=RGB:#00d000 diff --git a/src/gui/theme/serializabletheme.cpp b/src/gui/theme/serializabletheme.cpp new file mode 100644 index 00000000000..ef1311e16ac --- /dev/null +++ b/src/gui/theme/serializabletheme.cpp @@ -0,0 +1,136 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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 "serializabletheme.h" + +#include + +#include + +#include "themeinfo.h" +#include "themeprovider.h" + +namespace +{ + void appendAbsentElements(Theme::ElementsMap &res, QSettings &settings) + { + // we ignore all keys in [Info] group + const QString infoPrefix = Theme::ThemeInfo::sectionName + QLatin1Char('/'); + const auto keys = settings.allKeys(); + for (const auto &key : keys) { + if (!key.startsWith(infoPrefix) && // ignore all keys in [Info] group + ( res.find(key) == res.end()) ) { + QVariant v = settings.value(key); + // if there were commas in the value, QVariant contains string list + // and we have to join it back + res[key] = v.type() == QVariant::StringList + ? v.toStringList().join(QLatin1Char(',')) + : v.toString(); + } + } + } + + struct LoadedTheme + { + Theme::ThemeInfo info; + Theme::ElementsMap elements; + }; + + /** + * @brief Loads serialized themes + * + * This function handles themes inheritance and takes care of appending default theme if needed + */ + LoadedTheme collectThemeElements(Theme::Kind kind, const QString &name, const QString &defaultInherited) + { + // we are going to load themes from inheritance hierarchy from descendant + // to ancestor, appending ancestor element to the dictionary if corresponding keys + // are not already present in it. + bool firstThemeWasLoaded = false; // we need info object from the first theme only + LoadedTheme res; + + QString themeName = name; + std::set loadedThemeNames; // this will be used to protect us against infinite + // inheritance loops + + Q_ASSERT(!themeName.isEmpty()); + do { + const QString themeFileName = Theme::ThemeProvider::instance().locateTheme(kind, themeName); + QSettings settings(themeFileName, QSettings::IniFormat); + Theme::ThemeInfo info = Theme::ThemeInfo(settings); + if (!firstThemeWasLoaded) { + res.info = info; + firstThemeWasLoaded = true; + } + appendAbsentElements(res.elements, settings); + + loadedThemeNames.insert(info.name()); + + themeName = info.inheritedTheme(); + if (themeName.isEmpty()) + themeName = defaultInherited; + } while (loadedThemeNames.count(themeName) == 0); + + return res; + } +} + +Theme::ThemeSerializer::ThemeSerializer(Kind kind, const QString &themeName, + const NamesMap &names, ElementDeserializer deserializer) + : m_names {names} + , m_info {} + , m_kind {kind} +{ + const QString defaultThemeName = ThemeProvider::instance().defaultThemeName(m_kind); + QString nameToUse = themeName.isEmpty() ? defaultThemeName : themeName; + const LoadedTheme loaded = collectThemeElements(m_kind, nameToUse, defaultThemeName); + m_info = std::move(loaded.info); + for (const auto &p : loaded.elements) { + const auto it = m_names.find(p.first.toLatin1()); + if (it == m_names.end()) { + qCWarning(theme) << "Unexpected theme element '" << p.first << '\''; + continue; + } + deserializer(it->second, p.second); + } +} + +void Theme::ThemeSerializer::save(const QString &themeFileName, + bool explicitValues, ElementSerializer serializer) const +{ + QSettings settings(themeFileName, QSettings::IniFormat); + + m_info.save(settings); + for (const auto &p : m_names) + settings.setValue(p.first, serializer(p.second, explicitValues)); +} + +const Theme::ThemeInfo &Theme::ThemeSerializer::info() const +{ + return m_info; +} diff --git a/src/gui/theme/serializabletheme.h b/src/gui/theme/serializabletheme.h new file mode 100644 index 00000000000..2c5ad07840c --- /dev/null +++ b/src/gui/theme/serializabletheme.h @@ -0,0 +1,155 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEME_SERIALIZABLE_THEME_H +#define QBT_THEME_SERIALIZABLE_THEME_H + +#include +#include +#include + +#include +#include + +#include "themecommon.h" +#include "themeinfo.h" + +namespace Theme +{ + using ElementsMap = std::map; + + class ThemeSerializer + { + public: + using NamesMap = std::map; + using ElementDeserializer = std::function; + using ElementSerializer = std::function; + + ThemeSerializer(Kind kind, const QString &themeName, + const NamesMap &names, ElementDeserializer deserializer); + + void save(const QString &themeFileName, bool explicitValues, ElementSerializer serializer) const; + + const ThemeInfo &info() const; + + private: + + NamesMap m_names; + ThemeInfo m_info; + Kind m_kind; + }; + + template + class SerializableTheme + { + static_assert(std::is_same::type>::value, + "Element underlying type has to be int"); + + public: + using EntityProviderType = typename ProviderRegistry::ProviderType; + using EntityType = typename EntityProviderType::EntityType; + using ThemeObjectType = typename EntityType::ValueType; + using ElementType = Element; + using NamesMap = typename ThemeSerializer::NamesMap; + + using ThisType = SerializableTheme; + + const ThemeObjectType &element(Element e) const; + const ThemeInfo &info() const; + + protected: + SerializableTheme(const QString &themeName, const NamesMap &names); + void updateCache() const; + + void save(const QString &themeFileName, bool explicitValues) const; + + private: + QString serialize(int elementId, bool explicitValue) const; + void deserialize(int elementId, const QString &value); + + using ElementsMap = std::map; + using ObjectsMap = std::map; + + ElementsMap m_elements; + ThemeSerializer m_serializer; + mutable ObjectsMap m_cache; + }; +} + +template +const typename Theme::SerializableTheme::ThemeObjectType +&Theme::SerializableTheme::element(Element e) const +{ + return m_cache[e]; +} + +template +const Theme::ThemeInfo &Theme::SerializableTheme::info() const +{ + return m_serializer.info(); +} + +template +Theme::SerializableTheme::SerializableTheme( + const QString &themeName, const NamesMap &names) + : m_elements{} + , m_serializer(ProviderRegistry::kind, themeName, names, + std::bind(&SerializableTheme::deserialize, this, std::placeholders::_1, std::placeholders::_2)) +{ + updateCache(); +} + +template +void Theme::SerializableTheme::save(const QString &themeFileName, bool explicitValues) const +{ + m_serializer.save(themeFileName, explicitValues, + std::bind(&SerializableTheme::serialize, this, std::placeholders::_1, std::placeholders::_2)); +} + +template +QString Theme::SerializableTheme::serialize(int elementId, bool explicitValue) const +{ + const auto &element = m_elements.at(static_cast(elementId)); + return element->serializationKey() + QLatin1Char(':') + + (explicitValue ? element->explicitSerializedValue() : element->serializedValue()); +} + +template +void Theme::SerializableTheme::deserialize(int elementId, const QString &value) +{ + m_elements[static_cast(elementId)] = ProviderRegistry::instance().load(value); +} + +template +void Theme::SerializableTheme::updateCache() const +{ + for (const auto &p : m_elements) + m_cache[p.first] = p.second->value(); +} + +#endif // QBT_THEME_SERIALIZABLE_THEME_H diff --git a/src/gui/theme/themecommon.cpp b/src/gui/theme/themecommon.cpp new file mode 100644 index 00000000000..08c6a4085e9 --- /dev/null +++ b/src/gui/theme/themecommon.cpp @@ -0,0 +1,40 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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 "themecommon.h" + +#include + +bool Theme::isDarkTheme() +{ + return (QPalette().color(QPalette::Active, QPalette::Base).lightness() < 127); +} + +Theme::VisualTheme::~VisualTheme() = default; + +Q_LOGGING_CATEGORY(theme, "qbt.theme") diff --git a/src/gui/theme/themecommon.h b/src/gui/theme/themecommon.h new file mode 100644 index 00000000000..e124d953297 --- /dev/null +++ b/src/gui/theme/themecommon.h @@ -0,0 +1,56 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEME_THEMECOMMON_H +#define QBT_THEME_THEMECOMMON_H + +#include + +Q_DECLARE_LOGGING_CATEGORY(theme) + +namespace Theme +{ + class ThemeInfo; + + enum class Kind + { + Color, + Font + }; + + class VisualTheme + { + public: + virtual const ThemeInfo &info() const = 0; + virtual ~VisualTheme(); + }; + + bool isDarkTheme(); +} + +#endif // QBT_THEME_THEMECOMMON_H diff --git a/src/gui/theme/themeexceptions.cpp b/src/gui/theme/themeexceptions.cpp new file mode 100644 index 00000000000..c992094caf6 --- /dev/null +++ b/src/gui/theme/themeexceptions.cpp @@ -0,0 +1,86 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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 "themeexceptions.h" + +Theme::ThemeNotFound::ThemeNotFound(const QString &themeName) + : ThemeError("Could not find theme named '" + themeName.toStdString() + "'") +{ +} + +Theme::Serialization::ThemeElementMissing::ThemeElementMissing(const QString &elementName) + : DeserializationError("Theme misses element '" + elementName.toStdString() + '\'') + , m_elementName(elementName) +{ +} + +const QString &Theme::Serialization::ThemeElementMissing::elementName() const +{ + return m_elementName; +} + +Theme::Serialization::ElementDeserializationError::ElementDeserializationError( + const std::string &message, const QString &serializedValue) + : DeserializationError("Could not deserialize theme object '" + serializedValue.toStdString() + "': " + message) + , m_serializedValue(serializedValue) +{ +} + +const QString &Theme::Serialization::ElementDeserializationError::serializedValue() const +{ + return m_serializedValue; +} + +Theme::Serialization::ParsingError::ParsingError(const QString &serializedValue) + : ElementDeserializationError("Could not find scheme part", serializedValue) +{ +} + +// we know that serializedValue follows scheme:value pattern, otherwise this exception is not applicable +Theme::Serialization::UnknownProvider::UnknownProvider(const QString &serializedValue) + : ElementDeserializationError("Could not find provider for scheme '" + + serializedValue.section(QLatin1Char(':'), 0, 1).toStdString() + '\'', serializedValue) +{ +} + +QString Theme::Serialization::UnknownProvider::profiderName() const +{ + // we know that serializedValue follows scheme:value pattern, otherwise this exception is not applicable + return this->serializedValue().section(QLatin1Char(':'), 0, 1); +} + +Theme::Serialization::ValueParsingError::ValueParsingError(const std::string &message, const QString &serializedValue) + : ElementDeserializationError("Could not parse element value: " + message, serializedValue) +{ +} + +QString Theme::Serialization::ValueParsingError::valueString() const +{ + // we know that serializedValue follows scheme:value pattern, otherwise this exception is not applicable + return this->serializedValue().section(QLatin1Char(':'), 1); +} diff --git a/src/gui/theme/themeexceptions.h b/src/gui/theme/themeexceptions.h new file mode 100644 index 00000000000..2dad9f29cae --- /dev/null +++ b/src/gui/theme/themeexceptions.h @@ -0,0 +1,102 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEME_THEMEEXCEPTIONS_H +#define QBT_THEME_THEMEEXCEPTIONS_H + +#include + +#include + +namespace Theme +{ + + class ThemeError : public std::runtime_error + { + public: + using std::runtime_error::runtime_error; + }; + + class ThemeNotFound : public ThemeError + { + public: + ThemeNotFound(const QString &themeName); + }; + + namespace Serialization + { + class DeserializationError : public ThemeError + { + public: + using ThemeError::ThemeError; + }; + + class ThemeElementMissing : public DeserializationError + { + public: + ThemeElementMissing(const QString &elementName); + + const QString &elementName() const; + + private: + QString m_elementName; + }; + + class ElementDeserializationError : public DeserializationError + { + public: + ElementDeserializationError(const std::string &message, const QString &serializedValue); + const QString &serializedValue() const; + + private: + QString m_serializedValue; + }; + + class ParsingError : public ElementDeserializationError + { + public: + ParsingError(const QString &serializedValue); + }; + + class UnknownProvider : public ElementDeserializationError + { + public: + UnknownProvider(const QString &serializedValue); + QString profiderName() const; + }; + + class ValueParsingError : public ElementDeserializationError + { + public: + ValueParsingError(const std::string &message, const QString &serializedValue); + QString valueString() const; + }; + } +} + +#endif // QBT_THEME_THEMEEXCEPTIONS_H diff --git a/src/gui/theme/themeinfo.cpp b/src/gui/theme/themeinfo.cpp new file mode 100644 index 00000000000..c7fb799c3a9 --- /dev/null +++ b/src/gui/theme/themeinfo.cpp @@ -0,0 +1,151 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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 "themeinfo.h" + +#include +#include +#include + +namespace +{ +#define GROUP_NAME "Info" +#define FULL_SETTING_KEY(name) GROUP_NAME "/" name + const QLatin1String nameFullKey(FULL_SETTING_KEY("Name")); + const QLatin1String descriptionFullKey(FULL_SETTING_KEY("Description")); + const QLatin1String inheritsFullKey(FULL_SETTING_KEY("Inherits")); + + const QLatin1String nameKey = QLatin1String("Name"); + const QLatin1String descriptionKey = QLatin1String("Description"); +} + +const QString Theme::ThemeInfo::sectionName = QLatin1String(GROUP_NAME); + +Theme::ThemeInfo::ThemeInfo() = default; + +Theme::ThemeInfo::ThemeInfo(QSettings &settings) + : m_name {settings.value(nameFullKey, QLatin1Literal("Unnamed")).toString()} + , m_description {settings.value(descriptionFullKey, QString()).toString()} + , m_inheritedTheme {settings.value(inheritsFullKey, QString()).toString()} +{ + settings.beginGroup(QLatin1String(GROUP_NAME)); + m_localizedNames = readAffixedKeys(settings, QString(nameKey) + QLatin1Char('_')); + m_localizedDescriptions = readAffixedKeys(settings, QString(descriptionKey) + QLatin1Char('_')); + settings.endGroup(); +} + +void Theme::ThemeInfo::save(QSettings &settings) const +{ + settings.setValue(nameFullKey, m_name); + settings.setValue(descriptionFullKey, m_description); + settings.setValue(inheritsFullKey, m_inheritedTheme); + + settings.beginGroup(QLatin1String(GROUP_NAME)); + writeAffixedKeys(m_localizedNames, settings, QString(nameKey) + QLatin1Char('_')); + writeAffixedKeys(m_localizedDescriptions, settings, QString(descriptionKey) + QLatin1Char('_')); + settings.endGroup(); +} + +Theme::ThemeInfo::LocalizedStrings +Theme::ThemeInfo::readAffixedKeys(QSettings &settings, const QString &name) +{ + LocalizedStrings res; + const QStringList keys = settings.allKeys(); + for (const QString &key : keys) + if (key.startsWith(name)) + res[key.mid(name.size())] = settings.value(key).toString(); + return res; +} + +void Theme::ThemeInfo::writeAffixedKeys(const LocalizedStrings &values, QSettings &settings, const QString &name) +{ + for (const auto &pair : values) + settings.setValue(name + pair.first, pair.second); +} + +QString Theme::ThemeInfo::name() const +{ + return m_name; +} + +QString Theme::ThemeInfo::description() const +{ + return m_description; +} + +QString Theme::ThemeInfo::inheritedTheme() const +{ + return m_inheritedTheme; +} + +void Theme::ThemeInfo::setName(const QString &name) +{ + m_name = name; +} + +void Theme::ThemeInfo::setDescription(const QString &description) +{ + m_description = description; +} + +void Theme::ThemeInfo::setInheritedTheme(const QString &name) +{ + m_inheritedTheme = name; +} + +QString Theme::ThemeInfo::findLocalizedString(const LocalizedStrings &strings) +{ + static QLocale loc; + auto i = strings.find(loc.name()); + if (i != strings.end()) + return i->second; + + i = strings.find(QLocale::languageToString(loc.language())); + if (i != strings.end()) + return i->second; + + return {}; +} + +QString Theme::ThemeInfo::localizedName() const +{ + const QString localized = findLocalizedString(m_localizedNames); + return localized.isEmpty() ? m_name : localized; +} + +QString Theme::ThemeInfo::localizedDescription() const +{ + const QString localized = findLocalizedString(m_localizedDescriptions); + return localized.isEmpty() ? m_description : localized; +} + +QDebug Theme::operator<<(QDebug debug, const Theme::ThemeInfo &info) +{ + debug << info.name() << " [" << info.description() << ']'; + return debug; +} diff --git a/src/gui/theme/themeinfo.h b/src/gui/theme/themeinfo.h new file mode 100644 index 00000000000..997e36f94e6 --- /dev/null +++ b/src/gui/theme/themeinfo.h @@ -0,0 +1,112 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEMES_THEMEINFO_H +#define QBT_THEMES_THEMEINFO_H + +#include + +#include + +class QDebug; +class QSettings; + +namespace Theme +{ + /** + * @brief Utility class to encapsulate general theme attributes + * + * Contains name and description of the theme + * + * These data reside in section [Info] of the theme file: + * + * [Info] + * Name=Theme name + * Description=Description of the theme + * Inherits=Other theme name + * Name_de=Name in German + * Name_gr=Name in Greek + * Description_fr=Description in French + * Description_uk=Description in Ukrainian + * + */ + class ThemeInfo + { + public: + ThemeInfo(); + ThemeInfo(QSettings &settings); + + void save(QSettings &settings) const; + + QString name() const; + QString localizedName() const; + QString description() const; + QString localizedDescription() const; + QString inheritedTheme() const; + + void setName(const QString &name); + void setDescription(const QString &description); + void setInheritedTheme(const QString &name); + + static const QString sectionName; + + private: + QString m_name; + QString m_description; + QString m_inheritedTheme; + + using LocalizedStrings = std::map; + /** + * @brief Function reads key values whose names start with "${name}" + * @returns dictionary of affix -> value + */ + static LocalizedStrings readAffixedKeys(QSettings &settings, const QString &name); + static void writeAffixedKeys(const LocalizedStrings &values, QSettings &settings, const QString &name); + + static QString findLocalizedString(const LocalizedStrings &strings); + + LocalizedStrings m_localizedNames; + LocalizedStrings m_localizedDescriptions; + }; + + QDebug operator<<(QDebug debug, const ThemeInfo &info); + + // comparison operators: there may not be two themes with equal names of the same kind in the app + + inline bool operator<(const ThemeInfo &left, const ThemeInfo &right) + { + return left.name() < right.name(); + } + + inline bool operator==(const ThemeInfo &left, const ThemeInfo &right) + { + return left.name() == right.name(); + } +} + +#endif diff --git a/src/gui/theme/themeprovider.cpp b/src/gui/theme/themeprovider.cpp new file mode 100644 index 00000000000..5ab1c6a995c --- /dev/null +++ b/src/gui/theme/themeprovider.cpp @@ -0,0 +1,264 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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 "themeprovider.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "base/logger.h" +#include "base/profile.h" +#include "base/settingsstorage.h" +#include "colorprovider_p.h" +#include "colortheme_impl.h" +#include "fonttheme_impl.h" +#include "themecommon.h" +#include "themeexceptions.h" +#include "themeinfo.h" + +const QLatin1String Theme::ThemeProvider::colorThemeFileExtension(".colortheme"); +const QLatin1String Theme::ThemeProvider::fontThemeFileExtension(".fonttheme"); + +namespace +{ + class ThemeProviderInstantiable : public Theme::ThemeProvider + { + public: + using ThemeProvider::ThemeProvider; + }; + +#define SETTINGS_KEY(name) "Appearance/" name + const QLatin1String selectedColorThemeKey (SETTINGS_KEY("ColorTheme")); + const QLatin1String selectedFontThemeKey (SETTINGS_KEY("FontTheme")); + + const QLatin1String defaultLightColorThemeName ("Default (Light)"); + const QLatin1String defaultDarkColorThemeName ("Default (Dark)"); + const QLatin1String defaultFontThemeName("Default"); +} + +Q_GLOBAL_STATIC(ThemeProviderInstantiable, themeProvider) + +Theme::ThemeProvider &Theme::ThemeProvider::instance(){ + return *themeProvider(); +} + +Theme::ThemeProvider::ThemeProvider() +{ + connect(qGuiApp, &QGuiApplication::paletteChanged, this, &ThemeProvider::applicationPaletteChanged); +} + +Theme::ThemeProvider::~ThemeProvider() = default; + +void Theme::ThemeProvider::loadConfiguration() +{ + const QString colorThemeName = SettingsStorage::instance()->loadValue(selectedColorThemeKey, QString()).toString(); + try { + setCurrentTheme(Kind::Color, colorThemeName); + } catch (Serialization::DeserializationError &ex) { + qCWarning(theme, "Color theme '%s' loading failed (%s), loading default theme instead", + qPrintable(colorThemeName), ex.what()); + Logger::instance()->addMessage(tr("Could not load color theme '%1'. Loading default theme.") + .arg(colorThemeName)); + setCurrentTheme(Kind::Color, QString()); + } + + const QString fontThemeName = SettingsStorage::instance()->loadValue(selectedFontThemeKey, QString()).toString(); + try { + setCurrentTheme(Kind::Font, fontThemeName); + } catch (Serialization::DeserializationError &ex) { + qCWarning(theme, "Font theme '%s' loading failed (%s), loading default theme instead", + qPrintable(fontThemeName), ex.what()); + Logger::instance()->addMessage(tr("Could not load font theme '%1'. Loading default theme.") + .arg(fontThemeName)); + setCurrentTheme(Kind::Font, QString()); + } +} + +std::map Theme::ThemeProvider::availableThemes(Kind kind) const +{ + QDir themeDir; + switch (kind) { + case Kind::Color: + themeDir.setNameFilters({QStringLiteral("*") + colorThemeFileExtension}); + break; + case Kind::Font: + themeDir.setNameFilters({QStringLiteral("*") + fontThemeFileExtension}); + break; + } + QStringList themeSearchPaths = this->themeSearchPaths(); + std::map res; + std::set loadedThemeNames; + for (const QString &path : themeSearchPaths) { + themeDir.cd(path); + QStringList themeFiles = themeDir.entryList(); + for (const QString &themeFile : themeFiles) { + const QString absThemePath = themeDir.absoluteFilePath(themeFile); + QSettings theme(absThemePath, QSettings::IniFormat); + ThemeInfo info(theme); + if (!loadedThemeNames.count(info.name())) { + res[info] = absThemePath; + loadedThemeNames.insert(info.name()); + } + } + } + + return res; +} + +QString Theme::ThemeProvider::defaultThemeName(Theme::Kind kind) const +{ + switch (kind) { + case Kind::Color: + return isDarkTheme() ? defaultDarkColorThemeName : defaultLightColorThemeName; + case Kind::Font: + return defaultFontThemeName; + default: + throw std::logic_error("Unexpected theme kind"); + } +} + +QString Theme::ThemeProvider::locateTheme(Kind kind, const QString &themeName) const +{ + const auto themes = availableThemes(kind); + ThemeInfo fake; + fake.setName(themeName); + auto i = themes.find(fake); + if (i != themes.end()) + return i->second; + + throw ThemeNotFound(themeName); +} + +void Theme::ThemeProvider::exportTheme(Theme::Kind kind, const QString &themeName, + const QString &fileName, ExportOptios options) +{ + switch (kind) { + case Kind::Color: + SerializableColorTheme(themeName).save(fileName, options.testFlag(ExportOption::WriteExplicitValues)); + break; + case Kind::Font: + SerializableFontTheme(themeName).save(fileName, options.testFlag(ExportOption::WriteExplicitValues)); + break; + } +} + +QStringList Theme::ThemeProvider::themeSearchPaths() const +{ + const QString themeDirName = QStringLiteral("theme"); + QStringList res; + // always support resources, they are first in the list for two reasons: + // 1) themes from this dir serve as fallback + // 2) user may not override them because of 1) + res << QLatin1String(":/") + themeDirName; + res << QDir(Profile::instance().location(SpecialFolder::Data)).absoluteFilePath(themeDirName); + // WARNING QStandardPaths::locateAll() returns a list with entries from home directory at the + // beginning, but this is not documented and might change. In that case this method will be incorrect. + res << QStandardPaths::locateAll(QStandardPaths::AppLocalDataLocation, themeDirName, QStandardPaths::LocateDirectory); +#if defined Q_OS_UNIX && !defined Q_OS_MACOS + QDir appDir = QCoreApplication::applicationDirPath(); + appDir.cdUp(); + const QString installPrefix = appDir.canonicalPath(); + const QString resourceDirInInstallPrefix = installPrefix + QLatin1String("/share/") + + QCoreApplication::applicationName() + QDir::separator() + themeDirName; + if (!res.contains(resourceDirInInstallPrefix)) + res << resourceDirInInstallPrefix; +#endif + return res; +} + +const Theme::ColorTheme &Theme::ThemeProvider::colorTheme() const +{ + return *m_currentColorTheme; +} + +const Theme::FontTheme &Theme::ThemeProvider::fontTheme() const +{ + return *m_currentFontTheme; +} + +void Theme::ThemeProvider::setCurrentTheme(Kind kind, const QString &themeName) +{ + switch (kind) { + case Kind::Color: + qCInfo(theme, "Loading color theme %s", qPrintable(themeName)); + try { + decltype(m_currentColorTheme)newTheme(new SerializableColorTheme(themeName)); + std::swap(m_currentColorTheme, newTheme); + emit colorThemeChanged(); + SettingsStorage::instance()->storeValue(selectedColorThemeKey, themeName); + } + catch (Serialization::DeserializationError &er) { + qCInfo(theme) << "Could not load color theme '" << themeName << "': " << er.what(); + throw; + } + break; + case Kind::Font: + qCInfo(theme, "Loading font theme %s", qPrintable(themeName)); + try { + decltype(m_currentFontTheme)newTheme(new SerializableFontTheme(themeName)); + std::swap(m_currentFontTheme, newTheme); + emit fontThemeChanged(); + SettingsStorage::instance()->storeValue(selectedFontThemeKey, themeName); + } + catch (Serialization::DeserializationError &er) { + qCInfo(theme) << "Could not load font theme '" << themeName << "': " << er.what(); + throw; + } + break; + } +} + +Theme::ThemeInfo Theme::ThemeProvider::currentTheme(Theme::Kind kind) const +{ + switch (kind) { + case Kind::Color: + return colorTheme().info(); + case Kind::Font: + return fontTheme().info(); + } + throw std::logic_error("Unexpected theme kind"); +} + +void Theme::ThemeProvider::applicationPaletteChanged(const QPalette &) +{ + Serialization::ColorsProviderRegistry::instance().applicationPaletteChanged(); + m_currentColorTheme->applicationPaletteChanged(); + emit colorThemeChanged(); +} + +void Theme::ThemeProvider::initInstance() +{ + instance().loadConfiguration(); +} diff --git a/src/gui/theme/themeprovider.h b/src/gui/theme/themeprovider.h new file mode 100644 index 00000000000..7ff588bb03d --- /dev/null +++ b/src/gui/theme/themeprovider.h @@ -0,0 +1,123 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Eugene Shalygin + * + * 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. + */ + +#ifndef QBT_THEMEPROVIDER_H +#define QBT_THEMEPROVIDER_H + +#include +#include + +#include + +#include "themecommon.h" + +class QPalette; +class QStringList; +class Application; + +namespace Theme +{ + class ColorTheme; + class FontTheme; + class SerializableColorTheme; + class SerializableFontTheme; + class ThemeInfo; + + class ThemeProvider : public QObject + { + Q_OBJECT + Q_DISABLE_COPY(ThemeProvider) + + public: + static ThemeProvider &instance(); + + /** + * @brief Collection of available colour themes + * + * @returns map them info object -> file path + */ + std::map availableThemes(Kind kind) const; + + QString defaultThemeName(Kind kind) const; + + const ColorTheme &colorTheme() const; + const FontTheme &fontTheme() const; + + void setCurrentTheme(Kind kind, const QString &themeName); + ThemeInfo currentTheme(Kind kind) const; + + /** + * @brief Locate file with given named theme + * + * @param themeName Name of the theme to search for + * @return File path + */ + QString locateTheme(Kind kind, const QString &themeName) const; + + static const QLatin1String colorThemeFileExtension; + static const QLatin1String fontThemeFileExtension; + + enum class ExportOption + { + NoOptions = 0x0, + WriteExplicitValues = 0x1 //!< write explicit values, i.e. RGB instead of palette members + }; + + Q_DECLARE_FLAGS(ExportOptios, ExportOption) + + void exportTheme(Kind kind, const QString &themeName, const QString &fileName, ExportOptios options = ExportOption::NoOptions); + + signals: + void colorThemeChanged(); + void fontThemeChanged(); + + protected: + ThemeProvider(); + ~ThemeProvider(); + + private slots: + void applicationPaletteChanged(const QPalette &); + + private: + friend class ::Application; + static void initInstance(); + void loadConfiguration(); + /** + * @brief Theme search paths arranged by priority + * + * @return QStringList with paths. The firs one is of highest priority. + */ + QStringList themeSearchPaths() const; + + + std::unique_ptr m_currentColorTheme; + std::unique_ptr m_currentFontTheme; + }; +} + +#endif // QBT_THEMEPROVIDER_H diff --git a/src/gui/transferlistmodel.cpp b/src/gui/transferlistmodel.cpp index adfd8773d60..f4ec6035046 100644 --- a/src/gui/transferlistmodel.cpp +++ b/src/gui/transferlistmodel.cpp @@ -38,9 +38,9 @@ #include "base/bittorrent/torrenthandle.h" #include "base/torrentfilter.h" #include "base/utils/fs.h" +#include "theme/colortheme.h" static QIcon getIconByState(BitTorrent::TorrentState state); -static QColor getColorByState(BitTorrent::TorrentState state); static QIcon getPausedIcon(); static QIcon getQueuedIcon(); @@ -52,8 +52,6 @@ static QIcon getCompletedIcon(); static QIcon getCheckingIcon(); static QIcon getErrorIcon(); -static bool isDarkTheme(); - // TransferListModel TransferListModel::TransferListModel(QObject *parent) @@ -169,7 +167,7 @@ QVariant TransferListModel::data(const QModelIndex &index, int role) const return getIconByState(torrent->state()); if (role == Qt::ForegroundRole) - return getColorByState(torrent->state()); + return Theme::ColorTheme::current().torrentStateColor(torrent->state()); if ((role != Qt::DisplayRole) && (role != Qt::UserRole)) return QVariant(); @@ -360,63 +358,6 @@ QIcon getIconByState(BitTorrent::TorrentState state) } } -QColor getColorByState(BitTorrent::TorrentState state) -{ - // Color names taken from http://cloford.com/resources/colours/500col.htm - bool dark = isDarkTheme(); - - switch (state) { - case BitTorrent::TorrentState::Downloading: - case BitTorrent::TorrentState::ForcedDownloading: - case BitTorrent::TorrentState::DownloadingMetadata: - if (!dark) - return QColor(34, 139, 34); // Forest Green - else - return QColor(50, 205, 50); // Lime Green - case BitTorrent::TorrentState::Allocating: - case BitTorrent::TorrentState::StalledDownloading: - case BitTorrent::TorrentState::StalledUploading: - if (!dark) - return QColor(0, 0, 0); // Black - else - return QColor(204, 204, 204); // Gray 80 - case BitTorrent::TorrentState::Uploading: - case BitTorrent::TorrentState::ForcedUploading: - if (!dark) - return QColor(65, 105, 225); // Royal Blue - else - return QColor(99, 184, 255); // Steel Blue 1 - case BitTorrent::TorrentState::PausedDownloading: - return QColor(250, 128, 114); // Salmon - case BitTorrent::TorrentState::PausedUploading: - if (!dark) - return QColor(0, 0, 139); // Dark Blue - else - return QColor(79, 148, 205); // Steel Blue 3 - case BitTorrent::TorrentState::Error: - case BitTorrent::TorrentState::MissingFiles: - return QColor(255, 0, 0); // red - case BitTorrent::TorrentState::QueuedDownloading: - case BitTorrent::TorrentState::QueuedUploading: - case BitTorrent::TorrentState::CheckingDownloading: - case BitTorrent::TorrentState::CheckingUploading: -#if LIBTORRENT_VERSION_NUM < 10100 - case BitTorrent::TorrentState::QueuedForChecking: -#endif - case BitTorrent::TorrentState::CheckingResumeData: - case BitTorrent::TorrentState::Moving: - if (!dark) - return QColor(0, 128, 128); // Teal - else - return QColor(0, 205, 205); // Cyan 3 - case BitTorrent::TorrentState::Unknown: - return QColor(255, 0, 0); // red - default: - Q_ASSERT(false); - return QColor(255, 0, 0); // red - } -} - QIcon getPausedIcon() { static QIcon cached = QIcon(":/icons/skin/paused.png"); @@ -471,10 +412,3 @@ QIcon getErrorIcon() return cached; } -bool isDarkTheme() -{ - QPalette pal = QApplication::palette(); - // QPalette::Base is used for the background of the Treeview - QColor color = pal.color(QPalette::Active, QPalette::Base); - return (color.lightness() < 127); -} diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index a4f10d5158a..918094764e5 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -55,6 +55,8 @@ #include "optionsdialog.h" #include "previewselectdialog.h" #include "speedlimitdialog.h" +#include "theme/fonttheme.h" +#include "theme/themeprovider.h" #include "torrentcategorydialog.h" #include "transferlistdelegate.h" #include "transferlistmodel.h" @@ -304,6 +306,10 @@ TransferListWidget::TransferListWidget(QWidget *parent, MainWindow *mainWindow) unused.setVerticalHeader(header()); header()->setParent(this); unused.setVerticalHeader(new QHeaderView(Qt::Horizontal)); + + applyFontTheme(); + connect(&Theme::ThemeProvider::instance(), &Theme::ThemeProvider::fontThemeChanged, + this, &TransferListWidget::applyFontTheme); } TransferListWidget::~TransferListWidget() @@ -1224,3 +1230,13 @@ void TransferListWidget::wheelEvent(QWheelEvent *event) QTreeView::wheelEvent(event); // event delegated to base class } + +void TransferListWidget::applyFontTheme() +{ + QFont font = Theme::FontTheme::current().font(Theme::FontTheme::Element::TransferList); + setFont(font); + header()->setFont(font); + foreach (QWidget *widget, header()->findChildren()) { + widget->setFont(font); + } +} diff --git a/src/gui/transferlistwidget.h b/src/gui/transferlistwidget.h index 987db2f8d78..4f7275fd9b8 100644 --- a/src/gui/transferlistwidget.h +++ b/src/gui/transferlistwidget.h @@ -117,6 +117,9 @@ protected slots: signals: void currentTorrentChanged(BitTorrent::TorrentHandle *const torrent); +private slots: + void applyFontTheme(); + private: void wheelEvent(QWheelEvent *event) override; void askAddTagsForSelection();