From 0856ab981aff6eabd80eda3a5fbb4b7f09c87f92 Mon Sep 17 00:00:00 2001 From: pasta Date: Sat, 14 Mar 2026 13:26:26 -0500 Subject: [PATCH 1/4] feat(qt): add global zoom shortcuts Add Ctrl++/Ctrl+=, Ctrl+-, and Ctrl+0 shortcuts for zooming in, out, and resetting font scale. Clamp range and slider bounds use [-100, 100] matching the CLI-accepted range. Shortcuts are disabled when -font-scale is CLI-overridden, consistent with the Appearance slider behavior. Scope existing RPCConsole font-size shortcuts to WidgetWithChildrenShortcut to avoid ambiguity with the new window-level zoom actions when the console is embedded as the central widget (e.g. -disablewallet mode). --- CLAUDE.md | 1 + src/qt/bitcoingui.cpp | 49 ++++++++++++++++++++++++++++++++ src/qt/bitcoingui.h | 6 ++++ src/qt/forms/appearancewidget.ui | 4 +-- src/qt/guiutil.cpp | 6 ++-- src/qt/guiutil.h | 2 +- src/qt/rpcconsole.cpp | 12 ++++---- 7 files changed, 69 insertions(+), 11 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b43b9c667a3f..fcbe041c3326 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,6 +63,7 @@ make -C depends -j"$(( $(nproc) - 1 ))" | tail 5 --enable-werror # Build with parallel jobs (leaving one core free) +# NOTE: Individual object files cannot be built separately; always do a full build make -j"$(( $(nproc) - 1 ))" ``` diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 8f6800b482ec..e7664ef12c9d 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -71,6 +71,7 @@ #include #include +#include #include namespace { @@ -85,6 +86,10 @@ constexpr int GOV_CYCLE_FRAME_MS{STATUSBAR_ICON_CYCLE_MS / (GOV_CYCLE_FRAME_COUN // Per-frame interval for the spinner animation constexpr int SPINNER_FRAME_MS{STATUSBAR_ICON_CYCLE_MS / SPINNER_FRAMES}; + +constexpr int FONT_SCALE_MIN{-100}; +constexpr int FONT_SCALE_MAX{100}; +constexpr int FONT_SCALE_SHORTCUT_STEP{3}; } // anonymous namespace const std::string BitcoinGUI::DEFAULT_UIPLATFORM = @@ -517,12 +522,27 @@ void BitcoinGUI::createActions() m_mask_values_action->setStatusTip(tr("Mask the values in the Overview tab")); m_mask_values_action->setCheckable(true); + m_zoom_in_action = new QAction(tr("Zoom &In"), this); + m_zoom_in_action->setShortcuts({QKeySequence(QKeySequence::ZoomIn), QKeySequence(tr("Ctrl+="))}); + m_zoom_in_action->setStatusTip(tr("Increase the font size")); + + m_zoom_out_action = new QAction(tr("Zoom &Out"), this); + m_zoom_out_action->setShortcuts({QKeySequence(QKeySequence::ZoomOut), QKeySequence(tr("Ctrl+_"))}); + m_zoom_out_action->setStatusTip(tr("Decrease the font size")); + + m_zoom_reset_action = new QAction(tr("Reset &Zoom"), this); + m_zoom_reset_action->setShortcut(QKeySequence(tr("Ctrl+0"))); + m_zoom_reset_action->setStatusTip(tr("Reset the font size to default")); + connect(quitAction, &QAction::triggered, this, &BitcoinGUI::quitRequested); connect(aboutAction, &QAction::triggered, this, &BitcoinGUI::aboutClicked); connect(aboutQtAction, &QAction::triggered, qApp, QApplication::aboutQt); connect(optionsAction, &QAction::triggered, this, &BitcoinGUI::optionsClicked); connect(showHelpMessageAction, &QAction::triggered, this, &BitcoinGUI::showHelpMessageClicked); connect(showCoinJoinHelpAction, &QAction::triggered, this, &BitcoinGUI::showCoinJoinHelpClicked); + connect(m_zoom_in_action, &QAction::triggered, this, [this] { adjustFontScale(FONT_SCALE_SHORTCUT_STEP); }); + connect(m_zoom_out_action, &QAction::triggered, this, [this] { adjustFontScale(-FONT_SCALE_SHORTCUT_STEP); }); + connect(m_zoom_reset_action, &QAction::triggered, this, [this] { adjustFontScale(0); }); // Jump directly to tabs in RPC-console connect(openInfoAction, &QAction::triggered, this, &BitcoinGUI::showInfo); @@ -677,6 +697,11 @@ void BitcoinGUI::createMenuBar() } settings->addAction(optionsAction); + QMenu* view = appMenuBar->addMenu(tr("&View")); + view->addAction(m_zoom_in_action); + view->addAction(m_zoom_out_action); + view->addAction(m_zoom_reset_action); + QMenu* window_menu = appMenuBar->addMenu(tr("&Window")); QAction* minimize_action = window_menu->addAction(tr("&Minimize")); @@ -735,6 +760,30 @@ void BitcoinGUI::createMenuBar() help->addAction(aboutQtAction); } +void BitcoinGUI::adjustFontScale(int delta) +{ + if (clientModel && clientModel->getOptionsModel() && + clientModel->getOptionsModel()->isOptionOverridden("-font-scale")) { + return; + } + + const int current_scale{GUIUtil::g_font_registry.GetFontScale()}; + const int new_scale{delta == 0 + ? GUIUtil::FontRegistry::DEFAULT_FONT_SCALE + : std::clamp(current_scale + delta, FONT_SCALE_MIN, FONT_SCALE_MAX)}; + + if (new_scale == current_scale) { + return; + } + + GUIUtil::g_font_registry.SetFontScale(new_scale); + GUIUtil::updateFonts(); + + if (clientModel && clientModel->getOptionsModel()) { + clientModel->getOptionsModel()->setOption(OptionsModel::FontScale, new_scale); + } +} + void BitcoinGUI::createToolBars() { #ifdef ENABLE_WALLET diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 670ce48f084b..999789f4e24b 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -187,6 +187,9 @@ class BitcoinGUI : public QMainWindow QAction* m_close_all_wallets_action{nullptr}; QAction* m_wallet_selector_action = nullptr; QAction* m_mask_values_action{nullptr}; + QAction* m_zoom_in_action{nullptr}; + QAction* m_zoom_out_action{nullptr}; + QAction* m_zoom_reset_action{nullptr}; QComboBox* m_wallet_selector = nullptr; @@ -262,6 +265,9 @@ class BitcoinGUI : public QMainWindow /** Update UI with latest network info from model. */ void updateNetworkState(); + /** Apply a global font scale delta or reset when delta is 0. */ + void adjustFontScale(int delta); + /** Regenerate all pre-cached governance clock pixmaps (e.g. after a theme change). */ void refreshGovernanceCycleIcons(); diff --git a/src/qt/forms/appearancewidget.ui b/src/qt/forms/appearancewidget.ui index 955e94d21969..c2368a4fdb03 100644 --- a/src/qt/forms/appearancewidget.ui +++ b/src/qt/forms/appearancewidget.ui @@ -183,10 +183,10 @@ - -30 + -100 - 30 + 100 10 diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 2b02ac80e1a4..340c44f187e6 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -302,9 +302,11 @@ void setupAppearance(QWidget* parent, OptionsModel* model) } } -void AddButtonShortcut(QAbstractButton* button, const QKeySequence& shortcut) +void AddButtonShortcut(QAbstractButton* button, const QKeySequence& shortcut, Qt::ShortcutContext context) { - QObject::connect(new QShortcut(shortcut, button), &QShortcut::activated, [button]() { button->animateClick(); }); + auto* sc = new QShortcut(shortcut, button); + sc->setContext(context); + QObject::connect(sc, &QShortcut::activated, [button]() { button->animateClick(); }); } bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 3a9b9240400a..3d0c1fcf084a 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -140,7 +140,7 @@ namespace GUIUtil * @param[in] button QAbstractButton to assign shortcut to * @param[in] shortcut QKeySequence to use as shortcut */ - void AddButtonShortcut(QAbstractButton* button, const QKeySequence& shortcut); + void AddButtonShortcut(QAbstractButton* button, const QKeySequence& shortcut, Qt::ShortcutContext context = Qt::WindowShortcut); // Parse "dash:" URI into recipient object, return true on successful parsing bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out); diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 1c5596baafb7..6a7b8dd9fa1c 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -1031,8 +1031,8 @@ void RPCConsole::clear(bool keep_prompt) " without fully understanding the ramifications of a command.%8") .arg(PACKAGE_NAME, "" + ui->clearButton->shortcut().toString(QKeySequence::NativeText) + "", - "" + ui->fontBiggerButton->shortcut().toString(QKeySequence::NativeText) + "", - "" + ui->fontSmallerButton->shortcut().toString(QKeySequence::NativeText) + "", + "" + QKeySequence(tr("Ctrl++")).toString(QKeySequence::NativeText) + "", + "" + QKeySequence(tr("Ctrl+-")).toString(QKeySequence::NativeText) + "", "help", "help-console", "", @@ -1348,15 +1348,15 @@ void RPCConsole::setButtonIcons() GUIUtil::setIcon(ui->fontBiggerButton, "fontbigger", GUIUtil::ThemedColor::BLUE, consoleButtonsSize); //: Main shortcut to increase the RPC console font size. - ui->fontBiggerButton->setShortcut(tr("Ctrl++")); + GUIUtil::AddButtonShortcut(ui->fontBiggerButton, tr("Ctrl++"), Qt::WidgetWithChildrenShortcut); //: Secondary shortcut to increase the RPC console font size. - GUIUtil::AddButtonShortcut(ui->fontBiggerButton, tr("Ctrl+=")); + GUIUtil::AddButtonShortcut(ui->fontBiggerButton, tr("Ctrl+="), Qt::WidgetWithChildrenShortcut); GUIUtil::setIcon(ui->fontSmallerButton, "fontsmaller", GUIUtil::ThemedColor::BLUE, consoleButtonsSize); //: Main shortcut to decrease the RPC console font size. - ui->fontSmallerButton->setShortcut(tr("Ctrl+-")); + GUIUtil::AddButtonShortcut(ui->fontSmallerButton, tr("Ctrl+-"), Qt::WidgetWithChildrenShortcut); //: Secondary shortcut to decrease the RPC console font size. - GUIUtil::AddButtonShortcut(ui->fontSmallerButton, tr("Ctrl+_")); + GUIUtil::AddButtonShortcut(ui->fontSmallerButton, tr("Ctrl+_"), Qt::WidgetWithChildrenShortcut); } void RPCConsole::reloadThemedWidgets() From 62ceb0025aa992c3e63de00542d4c11117d42f67 Mon Sep 17 00:00:00 2001 From: pasta Date: Sun, 15 Mar 2026 14:59:49 -0500 Subject: [PATCH 2/4] fix(qt): prevent duplicate shortcuts and update width on zoom Move RPC console font-size shortcut registration out of setButtonIcons() into the constructor so theme reloads don't create duplicate QShortcut objects. Also call updateWidth() after adjustFontScale() to recompute toolbar minimum width when the font scale changes. --- src/qt/bitcoingui.cpp | 1 + src/qt/rpcconsole.cpp | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index e7664ef12c9d..0dd40de8808a 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -778,6 +778,7 @@ void BitcoinGUI::adjustFontScale(int delta) GUIUtil::g_font_registry.SetFontScale(new_scale); GUIUtil::updateFonts(); + updateWidth(); if (clientModel && clientModel->getOptionsModel()) { clientModel->getOptionsModel()->setOption(OptionsModel::FontScale, new_scale); diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 6a7b8dd9fa1c..5a3ddf50781f 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -558,6 +558,17 @@ RPCConsole::RPCConsole(interfaces::Node& node, QWidget* parent, Qt::WindowFlags setButtonIcons(); + // Register console font-size shortcuts once (not in setButtonIcons which + // re-runs on theme changes and would create duplicate QShortcut objects). + //: Main shortcut to increase the RPC console font size. + GUIUtil::AddButtonShortcut(ui->fontBiggerButton, tr("Ctrl++"), Qt::WidgetWithChildrenShortcut); + //: Secondary shortcut to increase the RPC console font size. + GUIUtil::AddButtonShortcut(ui->fontBiggerButton, tr("Ctrl+="), Qt::WidgetWithChildrenShortcut); + //: Main shortcut to decrease the RPC console font size. + GUIUtil::AddButtonShortcut(ui->fontSmallerButton, tr("Ctrl+-"), Qt::WidgetWithChildrenShortcut); + //: Secondary shortcut to decrease the RPC console font size. + GUIUtil::AddButtonShortcut(ui->fontSmallerButton, tr("Ctrl+_"), Qt::WidgetWithChildrenShortcut); + // Install event filter for up and down arrow ui->lineEdit->installEventFilter(this); ui->lineEdit->setMaxLength(16 * 1024 * 1024); @@ -1347,16 +1358,7 @@ void RPCConsole::setButtonIcons() GUIUtil::setIcon(ui->clearButton, "remove", GUIUtil::ThemedColor::RED, consoleButtonsSize); GUIUtil::setIcon(ui->fontBiggerButton, "fontbigger", GUIUtil::ThemedColor::BLUE, consoleButtonsSize); - //: Main shortcut to increase the RPC console font size. - GUIUtil::AddButtonShortcut(ui->fontBiggerButton, tr("Ctrl++"), Qt::WidgetWithChildrenShortcut); - //: Secondary shortcut to increase the RPC console font size. - GUIUtil::AddButtonShortcut(ui->fontBiggerButton, tr("Ctrl+="), Qt::WidgetWithChildrenShortcut); - GUIUtil::setIcon(ui->fontSmallerButton, "fontsmaller", GUIUtil::ThemedColor::BLUE, consoleButtonsSize); - //: Main shortcut to decrease the RPC console font size. - GUIUtil::AddButtonShortcut(ui->fontSmallerButton, tr("Ctrl+-"), Qt::WidgetWithChildrenShortcut); - //: Secondary shortcut to decrease the RPC console font size. - GUIUtil::AddButtonShortcut(ui->fontSmallerButton, tr("Ctrl+_"), Qt::WidgetWithChildrenShortcut); } void RPCConsole::reloadThemedWidgets() From bf517dfbc0b1ee07259386bac2c94469dc4ef7c0 Mon Sep 17 00:00:00 2001 From: pasta Date: Thu, 2 Apr 2026 10:45:27 -0500 Subject: [PATCH 3/4] fix(qt): clamp font scale minimum to -50 to prevent zero-sized fonts At scale -100, the formula size*(1+scale*0.01) yields zero, making all text invisible. Change minimum from -100 to -50 (half-size text) in both the constant and the appearance slider. --- src/qt/bitcoingui.cpp | 2 +- src/qt/forms/appearancewidget.ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 0dd40de8808a..60f7a4a10ccf 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -87,7 +87,7 @@ constexpr int GOV_CYCLE_FRAME_MS{STATUSBAR_ICON_CYCLE_MS / (GOV_CYCLE_FRAME_COUN // Per-frame interval for the spinner animation constexpr int SPINNER_FRAME_MS{STATUSBAR_ICON_CYCLE_MS / SPINNER_FRAMES}; -constexpr int FONT_SCALE_MIN{-100}; +constexpr int FONT_SCALE_MIN{-50}; constexpr int FONT_SCALE_MAX{100}; constexpr int FONT_SCALE_SHORTCUT_STEP{3}; } // anonymous namespace diff --git a/src/qt/forms/appearancewidget.ui b/src/qt/forms/appearancewidget.ui index c2368a4fdb03..208d42259a21 100644 --- a/src/qt/forms/appearancewidget.ui +++ b/src/qt/forms/appearancewidget.ui @@ -183,7 +183,7 @@ - -100 + -50 100 From 12c0ebdda4d7c1d9039082cfdc1851108c30df34 Mon Sep 17 00:00:00 2001 From: pasta Date: Sun, 5 Apr 2026 22:21:54 -0500 Subject: [PATCH 4/4] fix(qt): clamp CLI -font-scale minimum to -50 to match GUI bounds The GUI slider and keyboard shortcuts already enforce -50..100, but the CLI validation and help text still allowed -100..100. A scale of -100 produces zero-sized fonts (size * 0.0 = 0). Align CLI bounds with GUI for consistency. --- src/qt/bitcoin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 08132c0a3848..9a89ce889b46 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -501,7 +501,7 @@ static void SetupUIArgs(ArgsManager& argsman) argsman.AddArg("-choosedatadir", strprintf(QObject::tr("Choose data directory on startup (default: %u)").toStdString(), DEFAULT_CHOOSE_DATADIR), ArgsManager::ALLOW_ANY, OptionsCategory::GUI); argsman.AddArg("-custom-css-dir", "Set a directory which contains custom css files. Those will be used as stylesheets for the UI.", ArgsManager::ALLOW_ANY, OptionsCategory::GUI); argsman.AddArg("-font-family", QObject::tr("Set the font family. Possible values: %1. (default: %2)").arg(Join(GUIUtil::getFonts(/*selectable_only=*/true), ", ")).arg(GUIUtil::FontRegistry::DEFAULT_FONT).toStdString(), ArgsManager::ALLOW_ANY, OptionsCategory::GUI); - argsman.AddArg("-font-scale", QObject::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::FontRegistry::DEFAULT_FONT_SCALE).toStdString(), ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + argsman.AddArg("-font-scale", QObject::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(-50).arg(100).arg(GUIUtil::FontRegistry::DEFAULT_FONT_SCALE).toStdString(), ArgsManager::ALLOW_ANY, OptionsCategory::GUI); argsman.AddArg("-font-weight-bold", QObject::tr("Set the font weight for bold texts. Possible range %1 to %2 (default: %3)").arg(0).arg(8).arg(GUIUtil::weightToArg(GUIUtil::FontRegistry::TARGET_WEIGHT_BOLD)).toStdString(), ArgsManager::ALLOW_ANY, OptionsCategory::GUI); argsman.AddArg("-font-weight-normal", QObject::tr("Set the font weight for normal texts. Possible range %1 to %2 (default: %3)").arg(0).arg(8).arg(GUIUtil::weightToArg(GUIUtil::FontRegistry::TARGET_WEIGHT_NORMAL)).toStdString(), ArgsManager::ALLOW_ANY, OptionsCategory::GUI); argsman.AddArg("-lang=", QObject::tr("Set language, for example \"de_DE\" (default: system locale)").toStdString(), ArgsManager::ALLOW_ANY, OptionsCategory::GUI); @@ -747,7 +747,7 @@ int GuiMain(int argc, char* argv[]) } // Validate/set font scale if (gArgs.IsArgSet("-font-scale")) { - const int nScaleMin = -100, nScaleMax = 100; + const int nScaleMin = -50, nScaleMax = 100; int nScale = gArgs.GetIntArg("-font-scale", GUIUtil::g_font_registry.GetFontScale()); if (nScale < nScaleMin || nScale > nScaleMax) { QMessageBox::critical(nullptr, PACKAGE_NAME,