Skip to content

Commit 4a7bb86

Browse files
authored
qt: Introduce runtime theme changes (#3559)
* qt: Set the default theme properly * qt: Keep track of disabled rects for macOS This allows enabling them again on theme changes * qt: Introduce runtime theme changes Runtime theme changes means no more client restart required if the theme gets changed in the options dialog. In the RPCConsole's StyleChange event make sure following things are still correct after a runtime theme change: - Hide prompt icon for dash themes in rpc console if dash theme gets activated. - Clear rpc console on theme changes to make sure fonts/sizes/colors are correct.
1 parent f870600 commit 4a7bb86

File tree

10 files changed

+103
-33
lines changed

10 files changed

+103
-33
lines changed

src/qt/bitcoingui.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,6 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *_platformStyle, const NetworkStyle *
125125
spinnerFrame(0),
126126
platformStyle(_platformStyle)
127127
{
128-
GUIUtil::loadStyleSheet(this);
129-
130128
QSettings settings;
131129
if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) {
132130
// Restore failed (perhaps missing setting), center the window
@@ -844,6 +842,7 @@ void BitcoinGUI::optionsClicked()
844842

845843
OptionsDialog dlg(this, enableWallet);
846844
dlg.setModel(clientModel->getOptionsModel());
845+
connect(&dlg, &OptionsDialog::themeChanged, [=]() { GUIUtil::loadTheme(); });
847846
dlg.exec();
848847
}
849848

src/qt/dash.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,8 @@ void BitcoinApplication::createWindow(const NetworkStyle *networkStyle)
395395
{
396396
window = new BitcoinGUI(platformStyle, networkStyle, 0);
397397

398+
GUIUtil::loadTheme(window);
399+
398400
pollShutdownTimer = new QTimer(window);
399401
connect(pollShutdownTimer, SIGNAL(timeout()), window, SLOT(detectShutdown()));
400402
}

src/qt/guiutil.cpp

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ static std::set<QWidget*> setFixedPitchFontUpdates;
132132
// Contains all widgets where a non-default fontsize has been seet with GUIUtil::setFont
133133
static std::map<QWidget*, int> mapFontSizeUpdates;
134134

135+
#ifdef Q_OS_MAC
136+
// Contains all widgets where the macOS focus rect has been disabled.
137+
static std::set<QWidget*> setRectsDisabled;
138+
#endif
139+
135140
static const std::map<ThemedColor, QColor> themedColors = {
136141
{ ThemedColor::DEFAULT, QColor(85, 85, 85) },
137142
{ ThemedColor::UNCONFIRMED, QColor(128, 128, 128) },
@@ -1025,7 +1030,12 @@ const std::vector<QString> listThemes()
10251030
return vecThemes;
10261031
}
10271032

1028-
void loadStyleSheet(QWidget* widget, bool fDebugWidget)
1033+
const QString getDefaultTheme()
1034+
{
1035+
return defaultTheme;
1036+
}
1037+
1038+
void loadStyleSheet(QWidget* widget, bool fForceUpdate)
10291039
{
10301040
AssertLockNotHeld(cs_css);
10311041
LOCK(cs_css);
@@ -1036,7 +1046,7 @@ void loadStyleSheet(QWidget* widget, bool fDebugWidget)
10361046
bool fDebugCustomStyleSheets = gArgs.GetBoolArg("-debug-ui", false) && isStyleSheetDirectoryCustom();
10371047
bool fStyleSheetChanged = false;
10381048

1039-
if (stylesheet == nullptr || fDebugCustomStyleSheets) {
1049+
if (stylesheet == nullptr || fForceUpdate || fDebugCustomStyleSheets) {
10401050
auto hasModified = [](const std::vector<QString>& vecFiles) -> bool {
10411051
static std::map<const QString, QDateTime> mapLastModified;
10421052

@@ -1053,7 +1063,7 @@ void loadStyleSheet(QWidget* widget, bool fDebugWidget)
10531063
};
10541064

10551065
auto loadFiles = [&](const std::vector<QString>& vecFiles) -> bool {
1056-
if (fDebugCustomStyleSheets && !hasModified(vecFiles)) {
1066+
if (!fForceUpdate && fDebugCustomStyleSheets && !hasModified(vecFiles)) {
10571067
return false;
10581068
}
10591069

@@ -1086,29 +1096,27 @@ void loadStyleSheet(QWidget* widget, bool fDebugWidget)
10861096
fStyleSheetChanged = loadFiles(vecFiles);
10871097
}
10881098

1089-
bool fUpdateStyleSheet = fDebugCustomStyleSheets && fStyleSheetChanged;
1099+
bool fUpdateStyleSheet = fForceUpdate || (fDebugCustomStyleSheets && fStyleSheetChanged);
10901100

1091-
if (fDebugWidget) {
1101+
if (widget) {
10921102
setWidgets.insert(widget);
1093-
QWidgetList allWidgets = QApplication::allWidgets();
1094-
auto it = setWidgets.begin();
1095-
while (it != setWidgets.end()) {
1096-
if (!allWidgets.contains(*it)) {
1097-
it = setWidgets.erase(it);
1098-
continue;
1099-
}
1100-
if (fUpdateStyleSheet && *it != widget) {
1101-
(*it)->setStyleSheet(*stylesheet);
1102-
}
1103-
++it;
1104-
}
1103+
widget->setStyleSheet(*stylesheet);
11051104
}
11061105

1107-
if (widget) {
1108-
widget->setStyleSheet(*stylesheet);
1106+
QWidgetList allWidgets = QApplication::allWidgets();
1107+
auto it = setWidgets.begin();
1108+
while (it != setWidgets.end()) {
1109+
if (!allWidgets.contains(*it)) {
1110+
it = setWidgets.erase(it);
1111+
continue;
1112+
}
1113+
if (fUpdateStyleSheet && *it != widget) {
1114+
(*it)->setStyleSheet(*stylesheet);
1115+
}
1116+
++it;
11091117
}
11101118

1111-
if (!ShutdownRequested() && fDebugCustomStyleSheets) {
1119+
if (!ShutdownRequested() && fDebugCustomStyleSheets && !fForceUpdate) {
11121120
QTimer::singleShot(200, [] { loadStyleSheet(); });
11131121
}
11141122
}
@@ -1517,12 +1525,36 @@ bool dashThemeActive()
15171525
return theme != traditionalTheme;
15181526
}
15191527

1528+
void loadTheme(QWidget* widget, bool fForce)
1529+
{
1530+
loadStyleSheet(widget, fForce);
1531+
updateFonts();
1532+
updateMacFocusRects();
1533+
}
1534+
15201535
void disableMacFocusRect(const QWidget* w)
15211536
{
15221537
#ifdef Q_OS_MAC
15231538
for (const auto& c : w->findChildren<QWidget*>()) {
15241539
if (c->testAttribute(Qt::WA_MacShowFocusRect)) {
15251540
c->setAttribute(Qt::WA_MacShowFocusRect, !dashThemeActive());
1541+
setRectsDisabled.emplace(c);
1542+
}
1543+
}
1544+
#endif
1545+
}
1546+
1547+
void updateMacFocusRects()
1548+
{
1549+
#ifdef Q_OS_MAC
1550+
QWidgetList allWidgets = QApplication::allWidgets();
1551+
auto it = setRectsDisabled.begin();
1552+
while (it != setRectsDisabled.end()) {
1553+
if (allWidgets.contains(*it)) {
1554+
(*it)->setAttribute(Qt::WA_MacShowFocusRect, !dashThemeActive());
1555+
++it;
1556+
} else {
1557+
it = setRectsDisabled.erase(it);
15261558
}
15271559
}
15281560
#endif

src/qt/guiutil.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,13 @@ namespace GUIUtil
253253
/** Return a list of all theme css files */
254254
const std::vector<QString> listThemes();
255255

256-
/** Updates the widgets stylesheet and adds it to the list of ui debug elements
257-
if fDebugWidget is true. Beeing on that list means the stylesheet of the
258-
widget gets updated if the related css files has been changed if -debug-ui mode is active. */
259-
void loadStyleSheet(QWidget* widget = nullptr, bool fDebugWidget = true);
256+
/** Return the name of the default theme `*/
257+
const QString getDefaultTheme();
258+
259+
/** Updates the widgets stylesheet and adds it to the list of ui debug elements.
260+
Beeing on that list means the stylesheet of the widget gets updated if the
261+
related css files has been changed if -debug-ui mode is active. */
262+
void loadStyleSheet(QWidget* widget = nullptr, bool fForceUpdate = false);
260263

261264
enum class FontFamily {
262265
SystemDefault,
@@ -335,10 +338,16 @@ namespace GUIUtil
335338
/** Check if a dash specific theme is activated (light/dark).*/
336339
bool dashThemeActive();
337340

341+
/** Load the theme and update all UI elements according to the appearance settings. */
342+
void loadTheme(QWidget* widget = nullptr, bool fForce = true);
343+
338344
/** Disable the OS default focus rect for macOS because we have custom focus rects
339345
* set in the css files */
340346
void disableMacFocusRect(const QWidget* w);
341347

348+
/** Enable/Disable the macOS focus rects depending on the current theme. */
349+
void updateMacFocusRects();
350+
342351
/* Convert QString to OS specific boost path through UTF-8 */
343352
fs::path qstringToBoostPath(const QString &path);
344353

src/qt/optionsdialog.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <QIntValidator>
2727
#include <QLocale>
2828
#include <QMessageBox>
29+
#include <QSettings>
2930
#include <QTimer>
3031

3132
OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) :
@@ -36,6 +37,8 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) :
3637
{
3738
ui->setupUi(this);
3839

40+
previousTheme = GUIUtil::getActiveTheme();
41+
3942
GUIUtil::setFont({ui->statusLabel}, GUIUtil::FontWeight::Bold, 16);
4043

4144
GUIUtil::updateFonts();
@@ -104,6 +107,7 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) :
104107
for (const QString& entry : GUIUtil::listThemes()) {
105108
ui->theme->addItem(entry, QVariant(entry));
106109
}
110+
connect(ui->theme, SIGNAL(valueChanged()), this, SLOT(updateTheme()));
107111

108112
/* Language selector */
109113
QDir translations(":translations");
@@ -188,7 +192,6 @@ void OptionsDialog::setModel(OptionsModel *_model)
188192
connect(ui->connectSocksTor, SIGNAL(clicked(bool)), this, SLOT(showRestartWarning()));
189193
/* Display */
190194
connect(ui->digits, SIGNAL(valueChanged()), this, SLOT(showRestartWarning()));
191-
connect(ui->theme, SIGNAL(valueChanged()), this, SLOT(showRestartWarning()));
192195
connect(ui->lang, SIGNAL(valueChanged()), this, SLOT(showRestartWarning()));
193196
connect(ui->thirdPartyTxUrls, SIGNAL(textChanged(const QString &)), this, SLOT(showRestartWarning()));
194197
}
@@ -293,6 +296,9 @@ void OptionsDialog::on_okButton_clicked()
293296

294297
void OptionsDialog::on_cancelButton_clicked()
295298
{
299+
if (previousTheme != GUIUtil::getActiveTheme()) {
300+
updateTheme(previousTheme);
301+
}
296302
reject();
297303
}
298304

@@ -373,6 +379,16 @@ void OptionsDialog::updateDefaultProxyNets()
373379
(strProxy == strDefaultProxyGUI.toStdString()) ? ui->proxyReachTor->setChecked(true) : ui->proxyReachTor->setChecked(false);
374380
}
375381

382+
void OptionsDialog::updateTheme(const QString& theme)
383+
{
384+
QString newValue = theme.isEmpty() ? ui->theme->value().toString() : theme;
385+
if (GUIUtil::getActiveTheme() != newValue) {
386+
QSettings().setValue("theme", newValue);
387+
QSettings().sync();
388+
Q_EMIT themeChanged();
389+
}
390+
}
391+
376392
ProxyAddressValidator::ProxyAddressValidator(QObject *parent) :
377393
QValidator(parent)
378394
{

src/qt/optionsdialog.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,18 @@ private Q_SLOTS:
6060
void updateProxyValidationState();
6161
/* query the networks, for which the default proxy is used */
6262
void updateDefaultProxyNets();
63+
void updateTheme(const QString& toTheme = QString());
6364

6465
Q_SIGNALS:
6566
void proxyIpChecks(QValidatedLineEdit *pUiProxyIp, int nProxyPort);
67+
void themeChanged();
6668

6769
private:
6870
Ui::OptionsDialog *ui;
6971
OptionsModel *model;
7072
QDataWidgetMapper *mapper;
7173
QButtonGroup pageButtons;
74+
QString previousTheme;
7275
};
7376

7477
#endif // BITCOIN_QT_OPTIONSDIALOG_H

src/qt/optionsmodel.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ void OptionsModel::Init(bool resetSettings)
8585
strThirdPartyTxUrls = settings.value("strThirdPartyTxUrls", "").toString();
8686

8787
if (!settings.contains("theme"))
88-
settings.setValue("theme", "");
88+
settings.setValue("theme", GUIUtil::getDefaultTheme());
8989

9090
#ifdef ENABLE_WALLET
9191
if (!settings.contains("fCoinControlFeatures"))
@@ -510,10 +510,8 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in
510510
break;
511511
#endif // ENABLE_WALLET
512512
case Theme:
513-
if (settings.value("theme") != value) {
514-
settings.setValue("theme", value);
515-
setRestartRequired(true);
516-
}
513+
// Set in OptionsDialog::updateTheme slot now
514+
// to allow instant theme changes.
517515
break;
518516
case Language:
519517
if (settings.value("language") != value) {

src/qt/rpcconsole.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,16 @@ void RPCConsole::hideEvent(QHideEvent *event)
12631263
clientModel->getPeerTableModel()->stopAutoRefresh();
12641264
}
12651265

1266+
void RPCConsole::changeEvent(QEvent* e)
1267+
{
1268+
if (e->type() == QEvent::StyleChange) {
1269+
clear();
1270+
ui->promptIcon->setHidden(GUIUtil::dashThemeActive());
1271+
}
1272+
1273+
QWidget::changeEvent(e);
1274+
}
1275+
12661276
void RPCConsole::showPeersTableContextMenu(const QPoint& point)
12671277
{
12681278
QModelIndex index = ui->peerWidget->indexAt(point);

src/qt/rpcconsole.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ private Q_SLOTS:
7979
void resizeEvent(QResizeEvent *event);
8080
void showEvent(QShowEvent *event);
8181
void hideEvent(QHideEvent *event);
82+
void changeEvent(QEvent* e);
8283
/** Show custom context menu on Peers tab */
8384
void showPeersTableContextMenu(const QPoint& point);
8485
/** Show custom context menu on Bans tab */

src/qt/utilitydialog.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ ShutdownWindow::ShutdownWindow(QWidget *parent, Qt::WindowFlags f):
211211
{
212212
setObjectName("ShutdownWindow");
213213

214-
GUIUtil::loadStyleSheet(this, false);
214+
GUIUtil::loadStyleSheet(this);
215215

216216
QVBoxLayout *layout = new QVBoxLayout();
217217
layout->addWidget(new QLabel(

0 commit comments

Comments
 (0)