diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 8561587c1fc1..b975e8161342 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -353,7 +353,7 @@ RES_CSS = \ qt/res/css/general.css \ qt/res/css/light.css \ qt/res/css/scrollbars.css \ - qt/res/css/trad.css + qt/res/css/traditional.css RES_FONTS = \ qt/res/fonts/Montserrat/Montserrat-Black.otf \ diff --git a/src/qt/dash.cpp b/src/qt/dash.cpp index fd0e31715b09..dfcd3b8cde75 100644 --- a/src/qt/dash.cpp +++ b/src/qt/dash.cpp @@ -740,6 +740,41 @@ int main(int argc, char *argv[]) } GUIUtil::setFontScale(nScale); } + // Validate/set custom css directory + if (gArgs.IsArgSet("-custom-css-dir")) { + fs::path customDir = fs::path(gArgs.GetArg("-custom-css-dir", "")); + QString strCustomDir = QString::fromStdString(customDir.string()); + std::vector vecRequiredFiles = GUIUtil::listStyleSheets(); + QString strFile; + + if (!fs::is_directory(customDir)) { + QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), + QObject::tr("Error: Invalid -custom-css-dir path.") + "\n\n" + strCustomDir); + return EXIT_FAILURE; + } + + for (auto itCustomDir = fs::directory_iterator(customDir); itCustomDir != fs::directory_iterator(); ++itCustomDir) { + if (fs::is_regular_file(*itCustomDir) && itCustomDir->path().extension() == ".css") { + strFile = QString::fromStdString(itCustomDir->path().filename().string()); + auto itFile = std::find(vecRequiredFiles.begin(), vecRequiredFiles.end(), strFile); + if (itFile != vecRequiredFiles.end()) { + vecRequiredFiles.erase(itFile); + } + } + } + + if (vecRequiredFiles.size()) { + QString strMissingFiles; + for (const auto& strMissingFile : vecRequiredFiles) { + strMissingFiles += strMissingFile + "\n"; + } + QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), + QObject::tr("Error: %1 CSS file(s) missing in -custom-css-dir path.").arg(vecRequiredFiles.size()) + "\n\n" + strMissingFiles); + return EXIT_FAILURE; + } + + GUIUtil::setStyleSheetDirectory(strCustomDir); + } // Subscribe to global signals from core uiInterface.InitMessage.connect(InitMessage); diff --git a/src/qt/dash.qrc b/src/qt/dash.qrc index 2d4ba4a8c4aa..392e0ce6cf8c 100644 --- a/src/qt/dash.qrc +++ b/src/qt/dash.qrc @@ -57,6 +57,9 @@ res/css/general.css res/css/scrollbars.css + res/css/dark.css + res/css/light.css + res/css/traditional.css res/fonts/Montserrat/Montserrat-Black.otf @@ -78,11 +81,6 @@ res/fonts/Montserrat/Montserrat-Thin.otf res/fonts/Montserrat/Montserrat-ThinItalic.otf - - res/css/dark.css - res/css/light.css - res/css/trad.css - res/images/arrow_down_normal.png res/images/arrow_down_hover.png diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 5ce10d864db0..5f7f2073791b 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -80,12 +80,24 @@ void ForceActivation(); namespace GUIUtil { +// The default stylesheet directory +static const QString defaultStylesheetDirectory = ":css"; +// The actual stylesheet directory +static QString stylesheetDirectory = defaultStylesheetDirectory; // The name of the traditional theme static const QString traditionalTheme = "Traditional"; // The theme to set by default if settings are missing or incorrect static const QString defaultTheme = "Light"; // The prefix a theme name should have if we want to apply dark colors and styles to it static const QString darkThemePrefix = "Dark"; +// Mapping css file => theme. +static const std::map mapStyleToTheme{ + {"general.css", ""}, + {"dark.css", "Dark"}, + {"light.css", "Light"}, + {"traditional.css", "Traditional"}, + {"scrollbars.css", ""} +}; /** Font related default values. */ static const FontFamily defaultFontFamily = FontFamily::SystemDefault; @@ -971,48 +983,73 @@ void migrateQtSettings() } } +void setStyleSheetDirectory(const QString& path) +{ + stylesheetDirectory = path; +} + +bool isStyleSheetDirectoryCustom() +{ + return stylesheetDirectory != defaultStylesheetDirectory; +} + +const std::vector listStyleSheets() +{ + std::vector vecStylesheets; + for (const auto& it : mapStyleToTheme) { + vecStylesheets.push_back(it.first); + } + return vecStylesheets; +} + +const std::vector listThemes() +{ + std::vector vecThemes; + for (const auto& it : mapStyleToTheme) { + if (!it.second.isEmpty()) { + vecThemes.push_back(it.second); + } + } + return vecThemes; +} + // Open CSS when configured QString loadStyleSheet() { static std::unique_ptr stylesheet; - if (stylesheet.get() == nullptr) { - + if (stylesheet == nullptr) { stylesheet = std::make_unique(); QSettings settings; - QDir themes(":themes"); + QDir themes(":css"); QString theme = settings.value("theme", "").toString(); // Make sure settings are pointing to an existent theme - if (theme.isEmpty() || !themes.exists(theme)) { + if (!isStyleSheetDirectoryCustom() && (theme.isEmpty() || !themes.exists(theme))) { theme = defaultTheme; settings.setValue("theme", theme); } - // If light/dark theme is used load general styles first - if (dashThemeActive()) { - QFile qFileGeneral(":css/general"); - if (qFileGeneral.open(QFile::ReadOnly)) { - stylesheet.get()->append(QLatin1String(qFileGeneral.readAll())); + auto loadFile = [&](const QString& name) { + QFile qFile(stylesheetDirectory + "/" + name + (isStyleSheetDirectoryCustom() ? ".css" : "")); + if (qFile.open(QFile::ReadOnly)) { + stylesheet->append(QLatin1String(qFile.readAll())); } + }; + // If light/dark theme is used load general styles first + if (dashThemeActive()) { + loadFile("general"); #ifndef Q_OS_MAC - // Apply some styling to scrollbars - QFile qFileScrollbars(QString(":/css/scrollbars")); - if (qFileScrollbars.open(QFile::ReadOnly)) { - stylesheet.get()->append(QLatin1String(qFileScrollbars.readAll())); - } + loadFile("scrollbars"); #endif } - QFile qFileTheme(":themes/" + theme); - if (qFileTheme.open(QFile::ReadOnly)) { - stylesheet.get()->append(QLatin1String(qFileTheme.readAll())); - } + loadFile(theme); } - return *stylesheet.get(); + return *stylesheet; } FontFamily fontFamilyFromString(const QString& strFamily) diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 59fd9dde1a12..ce6e55f4fe50 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -230,6 +230,19 @@ namespace GUIUtil /** Modify Qt network specific settings on migration */ void migrateQtSettings(); + /** Change the stylesheet directory. This is used by + the parameter -custom-css-dir.*/ + void setStyleSheetDirectory(const QString& path); + + /** Check if a custom css directory has been set with -custom-css-dir */ + bool isStyleSheetDirectoryCustom(); + + /** Return a list of all required css files */ + const std::vector listStyleSheets(); + + /** Return a list of all theme css files */ + const std::vector listThemes(); + /** Load global CSS theme */ QString loadStyleSheet(); diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 01a5fa3b5698..0fd78378d49c 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -90,8 +90,7 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : } /* Theme selector */ - QDir themes(":themes"); - for (const QString &entry : themes.entryList()) { + for (const QString& entry : GUIUtil::listThemes()) { ui->theme->addItem(entry, QVariant(entry)); } diff --git a/src/qt/res/css/general.css b/src/qt/res/css/general.css index 7ce269b14aa9..0c7c6dc73a8f 100644 --- a/src/qt/res/css/general.css +++ b/src/qt/res/css/general.css @@ -10,7 +10,7 @@ loaded and combined in `GUIUtil::loadStyleSheet()` in guitil.cpp. Hierarchy: -* general.css - base layout: Loaded first if selected theme is not "Traditional" (trad.css) +* general.css - base layout: Loaded first if selected theme is not "Traditional" (traditional.css) * scrollbars.css - custom scrollbars: Loaded second only for windows/linux if general.css is loaded * - theme css file: Always loaded and loaded last. @@ -18,7 +18,7 @@ To replace there are currently the following themes available: * Dark (dark.css) * Light (light.css) -* Traditional (trad.css) +* Traditional (traditional.css) NOTE: This file is only the base layout which is getting shared between all full themes (e.g. Dark or Light). It may contain diff --git a/src/qt/res/css/trad.css b/src/qt/res/css/traditional.css similarity index 97% rename from src/qt/res/css/trad.css rename to src/qt/res/css/traditional.css index 23d618ff5013..57aea5e1599a 100644 --- a/src/qt/res/css/trad.css +++ b/src/qt/res/css/traditional.css @@ -17,7 +17,7 @@ Loaded in GUIUtil::loadStyleSheet() in guitil.cpp. /* do not modify! section updated by update-css-files.py -# Used colors in trad.css for commit 3bebd1a5c +# Used colors in traditional.css for commit 3bebd1a5c #fff #ccfafafa diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index 54b27280c56f..5a6bbe5e7f24 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -88,6 +88,7 @@ HelpMessageDialog::HelpMessageDialog(QWidget *parent, HelpMode helpMode) : strUsage += HelpMessageOpt("-allowselfsignedrootcertificates", strprintf("Allow self signed root certificates (default: %u)", DEFAULT_SELFSIGNED_ROOTCERTS)); } strUsage += HelpMessageOpt("-choosedatadir", strprintf(tr("Choose data directory on startup (default: %u)").toStdString(), DEFAULT_CHOOSE_DATADIR)); + strUsage += HelpMessageOpt("-custom-css-dir", "Set a directory which contains custom css files. Those will be used as stylesheets for the UI."); strUsage += HelpMessageOpt("-font-family", tr("Set the font family. Possible values: %1. (default: %2)").arg("SystemDefault, Montserrat").arg(GUIUtil::fontFamilyToString(GUIUtil::getFontFamilyDefault())).toStdString()); strUsage += HelpMessageOpt("-font-scale", tr("Set a scale factor which gets applied to the base font size. Possible range %1 (smallest fonts) to %2 (largest fonts). (default: %3)").arg(-100).arg(100).arg(GUIUtil::getFontScaleDefault()).toStdString()); strUsage += HelpMessageOpt("-font-weight-bold", tr("Set the font weight for bold texts. Possible range %1 to %2 (default: %3)").arg(0).arg(8).arg(GUIUtil::weightToArg(GUIUtil::getFontWeightBoldDefault())).toStdString());