diff --git a/doc/release-notes-7216.md b/doc/release-notes-7216.md new file mode 100644 index 000000000000..16ce6bd6d2f7 --- /dev/null +++ b/doc/release-notes-7216.md @@ -0,0 +1,19 @@ +GUI changes +----------- + +- The Window menu has new Zoom In, Zoom Out and Reset Zoom entries that change + the font scale of the whole application, bound to the platform zoom + shortcuts (Ctrl++ / Ctrl+= and Ctrl+- / Ctrl+_; Cmd on macOS) and Ctrl+0. + The new scale is saved like the Appearance tab's font scale setting. Like + that slider, the entries are disabled when `-font-scale` is given on the + command line. Inside the RPC console window the zoom-in/out keys still resize + only the console text, and when the console is the main window + (`-disablewallet` or builds without wallet support) the keys stay with the + console while the menu entries keep zooming the whole application. (#7216) + +- The font scale range is now -50 to 100, both for `-font-scale` and the + Appearance tab slider, which used to stop at -30 and 30. `-font-scale` values + below -50 on the command line or in the configuration file, which earlier + versions accepted down to -100 although they shrank text towards zero size, + are now rejected at startup. A font scale saved in `settings.json` + outside the range is clamped on load and the clamped value is saved. (#7216) diff --git a/src/qt/appearancewidget.cpp b/src/qt/appearancewidget.cpp index 7cba5922dccd..5e87a1dfef85 100644 --- a/src/qt/appearancewidget.cpp +++ b/src/qt/appearancewidget.cpp @@ -88,6 +88,7 @@ AppearanceWidget::AppearanceWidget(QWidget* parent) : prevWeightBoldArg{GUIUtil::currentWeightArg(GUIUtil::FontWeight::Bold)} { ui->setupUi(this); + ui->fontScaleSlider->setRange(GUIUtil::FONT_SCALE_MIN, GUIUtil::FONT_SCALE_MAX); for (const QString& entry : GUIUtil::listThemes()) { ui->theme->addItem(entry, QVariant(entry)); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 61c4e825ea43..940ff2006855 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -493,7 +493,7 @@ static void SetupUIArgs(ArgsManager& argsman) if (selectable) selectable_fonts.push_back(name); } argsman.AddArg("-font-family", QObject::tr("Set the font family. Possible values: %1. (default: %2)").arg(Join(selectable_fonts, ", ")).arg(GUIUtil::defaultFontFamily()).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::defaultFontScale()).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(GUIUtil::FONT_SCALE_MIN).arg(GUIUtil::FONT_SCALE_MAX).arg(GUIUtil::defaultFontScale()).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::defaultWeightArg(GUIUtil::FontWeight::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::defaultWeightArg(GUIUtil::FontWeight::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); @@ -720,15 +720,10 @@ int GuiMain(int argc, char* argv[]) } } // Validate/set font scale - if (gArgs.IsArgSet("-font-scale")) { - const int nScaleMin = -100, nScaleMax = 100; - int nScale = gArgs.GetIntArg("-font-scale", GUIUtil::fontScale()); - if (nScale < nScaleMin || nScale > nScaleMax) { - QMessageBox::critical(nullptr, PACKAGE_NAME, - QObject::tr("Error: Specified font-scale invalid. Valid range %1 to %2.").arg(nScaleMin).arg(nScaleMax)); - return EXIT_FAILURE; - } - GUIUtil::setFontScale(nScale); + if (!GUIUtil::setFontScaleFromArg()) { + QMessageBox::critical(nullptr, PACKAGE_NAME, + QObject::tr("Error: Specified font-scale invalid. Valid range %1 to %2.").arg(GUIUtil::FONT_SCALE_MIN).arg(GUIUtil::FONT_SCALE_MAX)); + return EXIT_FAILURE; } // Apply font changes GUIUtil::updateFonts(); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 0ad815bd8bcf..f8e83356170a 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -74,6 +74,7 @@ #include #include +#include #include namespace { @@ -88,6 +89,8 @@ 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_SHORTCUT_STEP{3}; } // anonymous namespace BitcoinGUI::BitcoinGUI(interfaces::Node& node, const NetworkStyle* networkStyle, QWidget* parent) : @@ -514,12 +517,53 @@ 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->setObjectName("zoomInAction"); + m_zoom_in_action->setStatusTip(tr("Increase the font size")); + + m_zoom_out_action = new QAction(tr("Zoom &Out"), this); + m_zoom_out_action->setObjectName("zoomOutAction"); + m_zoom_out_action->setStatusTip(tr("Decrease the font size")); + + // Without a wallet the RPC console is the central widget and already binds these keys to its own font size + if (walletFrame) { + m_zoom_in_action->setShortcuts({QKeySequence(QKeySequence::ZoomIn), QKeySequence(tr("Ctrl+="))}); + m_zoom_out_action->setShortcuts({QKeySequence(QKeySequence::ZoomOut), QKeySequence(tr("Ctrl+_"))}); + } + + m_zoom_reset_action = new QAction(tr("Reset &Zoom"), this); + m_zoom_reset_action->setObjectName("zoomResetAction"); + m_zoom_reset_action->setShortcut(QKeySequence(tr("Ctrl+0"))); + m_zoom_reset_action->setStatusTip(tr("Reset the font size to default")); + + // Enabled by setClientModel() once the font scale can be persisted + for (QAction* action : {m_zoom_in_action, m_zoom_out_action, m_zoom_reset_action}) { + action->setEnabled(false); + } + 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); + // The macOS menu bar keeps these shortcuts live while the separate RPC console window has focus, + // so let them resize the console text there like its own shortcuts do on other platforms + connect(m_zoom_in_action, &QAction::triggered, this, [this] { + if (rpcConsole->isWindow() && rpcConsole->isActiveWindow()) { + rpcConsole->fontBigger(); + } else { + adjustFontScale(FONT_SCALE_SHORTCUT_STEP); + } + }); + connect(m_zoom_out_action, &QAction::triggered, this, [this] { + if (rpcConsole->isWindow() && rpcConsole->isActiveWindow()) { + rpcConsole->fontSmaller(); + } else { + 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); @@ -706,6 +750,11 @@ void BitcoinGUI::createMenuBar() }); #endif + window_menu->addSeparator(); + window_menu->addAction(m_zoom_in_action); + window_menu->addAction(m_zoom_out_action); + window_menu->addAction(m_zoom_reset_action); + if (walletFrame) { #ifdef Q_OS_MACOS window_menu->addSeparator(); @@ -737,6 +786,27 @@ void BitcoinGUI::createMenuBar() help->addAction(aboutQtAction); } +void BitcoinGUI::adjustFontScale(int delta) +{ + const int current_scale{GUIUtil::fontScale()}; + const int new_scale{delta == 0 + ? GUIUtil::defaultFontScale() + : std::clamp(current_scale + delta, GUIUtil::FONT_SCALE_MIN, GUIUtil::FONT_SCALE_MAX)}; + + if (new_scale == current_scale) { + return; + } + + GUIUtil::setFontScale(new_scale); + GUIUtil::updateFonts(); + updateWidth(); + + // With -nosettings there is no settings file to write, so the zoom only lasts for this session + if (clientModel && clientModel->getOptionsModel() && gArgs.GetSettingsPath()) { + clientModel->getOptionsModel()->setOption(OptionsModel::FontScale, new_scale); + } +} + void BitcoinGUI::createToolBars() { #ifdef ENABLE_WALLET @@ -926,6 +996,11 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndH unitDisplayControl->setOptionsModel(optionsModel); m_mask_values_action->setChecked(optionsModel->getOption(OptionsModel::OptionID::MaskValues).toBool()); + const bool font_scale_overridden{optionsModel->isOptionOverridden("-font-scale")}; + for (QAction* action : {m_zoom_in_action, m_zoom_out_action, m_zoom_reset_action}) { + action->setEnabled(!font_scale_overridden); + } + connect(optionsModel, &OptionsModel::displayUnitChanged, this, [this]() { m_last_gov_cycle_height.reset(); updateGovernanceCycleIcon(); }); connect(optionsModel, &OptionsModel::showCoinJoinChanged, this, &BitcoinGUI::updateCoinJoinVisibility); connect(optionsModel, &OptionsModel::showGovernanceChanged, this, &BitcoinGUI::updateGovernanceVisibility); @@ -955,6 +1030,9 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndH } #endif // ENABLE_WALLET unitDisplayControl->setOptionsModel(nullptr); + for (QAction* action : {m_zoom_in_action, m_zoom_out_action, m_zoom_reset_action}) { + action->setEnabled(false); + } #ifdef Q_OS_MACOS if(dockIconMenu) diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 6243619b05d5..a4f03a65dd9c 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -187,6 +187,9 @@ class BitcoinGUI : public QMainWindow QAction* m_mask_values_action{nullptr}; QAction* m_migrate_wallet_action{nullptr}; QMenu* m_migrate_wallet_menu{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..208d42259a21 100644 --- a/src/qt/forms/appearancewidget.ui +++ b/src/qt/forms/appearancewidget.ui @@ -183,10 +183,10 @@ - -30 + -50 - 30 + 100 10 diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 4d62592b3b16..6d7f19423e1d 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -212,8 +212,14 @@ namespace GUIUtil /** Known fonts and their "selectable in UI" flag, in registration order. */ const std::vector>& knownFonts(); + /** Supported font scale range; much below the minimum text becomes unreadably small. */ + constexpr int FONT_SCALE_MIN{-50}; + constexpr int FONT_SCALE_MAX{100}; void setFontScale(int font_scale); int fontScale(); + /** Apply `-font-scale` from the command line, config file or settings.json. + * Returns false if it is out of range (no state change in that case). */ + bool setFontScaleFromArg(); /* Weight operations expressed as caller-friendly arg ints 0..8 -- the format used by * `-font-weight-*` CLI args and QSettings persistence. */ diff --git a/src/qt/guiutil_font.cpp b/src/qt/guiutil_font.cpp index db86c3b28cc0..9ca09aa699a3 100644 --- a/src/qt/guiutil_font.cpp +++ b/src/qt/guiutil_font.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -580,6 +581,15 @@ const std::vector>& knownFonts() { return g_fonts_known void setFontScale(int font_scale) { g_font_registry.SetFontScale(font_scale); } int fontScale() { return g_font_registry.GetFontScale(); } +bool setFontScaleFromArg() +{ + if (!gArgs.IsArgSet("-font-scale")) return true; + const int64_t font_scale{gArgs.GetIntArg("-font-scale", fontScale())}; + if (font_scale < FONT_SCALE_MIN || font_scale > FONT_SCALE_MAX) return false; + setFontScale(static_cast(font_scale)); + return true; +} + int currentWeightArg(FontWeight slot) { return weightToArg(slot == FontWeight::Bold ? g_font_registry.GetWeightBold() diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 12e077381cb7..fbb34d01c66c 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -34,6 +34,8 @@ #include +#include + const char *DEFAULT_GUI_PROXY_HOST = "127.0.0.1"; static QString GetDefaultProxyAddress(); @@ -115,6 +117,14 @@ static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID optio } } +//! Whether settings.json holds the setting, which then takes precedence over the config file. +static bool IsSettingsFileValue(const std::string& name) +{ + bool found{false}; + gArgs.LockSettings([&](const util::Settings& settings) { found = settings.rw_settings.count(name) > 0; }); + return found; +} + //! Convert enabled/size values to bitcoin -prune setting. static util::SettingsValue PruneSetting(bool prune_enabled, int prune_size_gb) { @@ -278,15 +288,24 @@ bool OptionsModel::Init(bilingual_str& error) } // Font Scale - if (node().isSettingIgnored("font-scale")) { + const bool font_scale_overridden{node().isSettingIgnored("font-scale")}; + if (font_scale_overridden) { addOverriddenOption("-font-scale"); } + int64_t font_scale; + try { + font_scale = SettingToInt(node().getPersistentSetting("font-scale"), GUIUtil::defaultFontScale()); + } catch (const std::exception& e) { + return setting_error("font-scale", e); + } + const int clamped_scale{static_cast(std::clamp(font_scale, GUIUtil::FONT_SCALE_MIN, GUIUtil::FONT_SCALE_MAX))}; + // Save a clamped settings.json value so the -font-scale validation at startup accepts it; + // an out-of-range config file value is left for that validation to reject. + if (clamped_scale != font_scale && !font_scale_overridden && IsSettingsFileValue("font-scale")) { + UpdateRwSetting(node(), FontScale, /*suffix=*/"", clamped_scale); + } if (GUIUtil::fontsLoaded()) { - try { - GUIUtil::setFontScale(SettingToInt(node().getPersistentSetting("font-scale"), GUIUtil::defaultFontScale())); - } catch (const std::exception& e) { - return setting_error("font-scale", e); - } + GUIUtil::setFontScale(clamped_scale); } // Font Weight (Normal) diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index aa19de5109a7..64656a1dae52 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -553,6 +553,16 @@ RPCConsole::RPCConsole(interfaces::Node& node, QWidget* parent, Qt::WindowFlags setButtonIcons(); + // Not in setButtonIcons(): it re-runs on theme changes and would stack duplicate QShortcuts. + //: Main shortcut to increase the RPC console font size. + ui->fontBiggerButton->setShortcut(tr("Ctrl++")); + //: Secondary shortcut to increase the RPC console font size. + GUIUtil::AddButtonShortcut(ui->fontBiggerButton, tr("Ctrl+=")); + //: Main shortcut to decrease the RPC console font size. + ui->fontSmallerButton->setShortcut(tr("Ctrl+-")); + //: Secondary shortcut to decrease the RPC console font size. + GUIUtil::AddButtonShortcut(ui->fontSmallerButton, tr("Ctrl+_")); + // Install event filter for up and down arrow ui->lineEdit->installEventFilter(this); ui->lineEdit->setMaxLength(16 * 1024 * 1024); @@ -1342,16 +1352,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. - ui->fontBiggerButton->setShortcut(tr("Ctrl++")); - //: Secondary shortcut to increase the RPC console font size. - GUIUtil::AddButtonShortcut(ui->fontBiggerButton, tr("Ctrl+=")); - GUIUtil::setIcon(ui->fontSmallerButton, "fontsmaller", GUIUtil::ThemedColor::BLUE, consoleButtonsSize); - //: Main shortcut to decrease the RPC console font size. - ui->fontSmallerButton->setShortcut(tr("Ctrl+-")); - //: Secondary shortcut to decrease the RPC console font size. - GUIUtil::AddButtonShortcut(ui->fontSmallerButton, tr("Ctrl+_")); } void RPCConsole::reloadThemedWidgets() diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index 2bdf82f7e7f6..38dc8107fae0 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include @@ -28,6 +30,8 @@ #include #include +#include + namespace { //! Regex find a string group inside of the console output QString FindInConsole(const QString& output, const QString& pattern) @@ -51,6 +55,84 @@ void TestRpcCommand(RPCConsole* console) const QString pattern = QStringLiteral("\"chain\": \"(\\w+)\""); QCOMPARE(FindInConsole(output, pattern), QString("regtest")); } + +//! Zoom the main window in, out and back to the default, checking the live and persisted font scale. +void TestZoomShortcuts(BitcoinGUI* window, interfaces::Node& node) +{ + QAction* zoom_in{window->findChild("zoomInAction")}; + QAction* zoom_out{window->findChild("zoomOutAction")}; + QAction* zoom_reset{window->findChild("zoomResetAction")}; + QVERIFY(zoom_in && zoom_out && zoom_reset); + QVERIFY(zoom_in->isEnabled()); + window->activateWindow(); + QVERIFY(QTest::qWaitForWindowActive(window)); + + // Without a wallet the RPC console is embedded and keeps the zoom in/out keys for its own font size. + const bool console_embedded{!window->findChild()->isWindow()}; + QCOMPARE(zoom_in->shortcuts().isEmpty(), console_embedded); + QCOMPARE(zoom_out->shortcuts().isEmpty(), console_embedded); + + const auto persisted_scale{[&] { return SettingToInt(node.getPersistentSetting("font-scale")).value_or(INT64_MIN); }}; + + const auto zoom{[&](QAction* action, QKeySequence::StandardKey key) { + if (console_embedded) { + action->trigger(); + } else { + QTest::keySequence(window, key); + } + }}; + + const int initial_scale{GUIUtil::fontScale()}; + zoom(zoom_in, QKeySequence::ZoomIn); + const int zoomed_scale{GUIUtil::fontScale()}; + QVERIFY(zoomed_scale > initial_scale); + QCOMPARE(persisted_scale(), int64_t{zoomed_scale}); + + zoom(zoom_in, QKeySequence::ZoomIn); + QVERIFY(GUIUtil::fontScale() > zoomed_scale); + zoom(zoom_out, QKeySequence::ZoomOut); + QCOMPARE(GUIUtil::fontScale(), zoomed_scale); + QCOMPARE(persisted_scale(), int64_t{zoomed_scale}); + + QTest::keySequence(window, QKeySequence{"Ctrl+0"}); + QCOMPARE(GUIUtil::fontScale(), GUIUtil::defaultFontScale()); + QCOMPARE(persisted_scale(), int64_t{GUIUtil::defaultFontScale()}); + + // With -nosettings there is no settings file, so the zoom applies for the session without being saved. + node.forceSetting("settings", false); + zoom(zoom_in, QKeySequence::ZoomIn); + QVERIFY(GUIUtil::fontScale() > GUIUtil::defaultFontScale()); + QCOMPARE(persisted_scale(), int64_t{GUIUtil::defaultFontScale()}); + QTest::keySequence(window, QKeySequence{"Ctrl+0"}); + QCOMPARE(GUIUtil::fontScale(), GUIUtil::defaultFontScale()); + node.forceSetting("settings", {}); + + // OptionTests compares the whole settings.json later on. + node.updateRwSetting("font-scale", {}); +} + +//! The zoom keys in the RPC console, separate or embedded, resize only the console text. +void TestConsoleZoomShortcuts(RPCConsole* console) +{ + console->activateWindow(); + QVERIFY(QTest::qWaitForWindowActive(console->window())); + QLineEdit* line_edit{console->findChild("lineEdit")}; + const auto console_font_size{[] { return QSettings().value("consoleFontSize").toInt(); }}; + + // Start from a fixed size: without platform fonts (the "minimal" plugin on Linux) the default size is 0, + // which the console rejects, and the keys cannot move it from there. + const int initial_size{12}; + console->setFontSize(initial_size); + QCOMPARE(console_font_size(), initial_size); + + const int app_scale{GUIUtil::fontScale()}; + // The font size buttons animate their click, so the size changes asynchronously. + QTest::keySequence(line_edit, QKeySequence{"Ctrl++"}); + QTRY_COMPARE(console_font_size(), initial_size + 1); + QTest::keySequence(line_edit, QKeySequence{"Ctrl+-"}); + QTRY_COMPARE(console_font_size(), initial_size); + QCOMPARE(GUIUtil::fontScale(), app_scale); +} } // namespace //! Entry point for BitcoinApplication tests. @@ -94,6 +176,7 @@ void AppTests::appTests() void AppTests::guiTests(BitcoinGUI* window) { HandleCallback callback{"guiTests", *this}; + TestZoomShortcuts(window, m_app.node()); connect(window, &BitcoinGUI::consoleShown, this, &AppTests::consoleTests); expectCallback("consoleTests"); QAction* action = window->findChild("openRPCConsoleAction"); @@ -105,6 +188,7 @@ void AppTests::consoleTests(RPCConsole* console) { HandleCallback callback{"consoleTests", *this}; TestRpcCommand(console); + TestConsoleZoomShortcuts(console); } //! Destructor to shut down after the last expected callback completes. diff --git a/src/qt/test/optiontests.cpp b/src/qt/test/optiontests.cpp index 626b5d1079d1..7f73ea4e2185 100644 --- a/src/qt/test/optiontests.cpp +++ b/src/qt/test/optiontests.cpp @@ -23,6 +23,7 @@ #include #include +#include OptionTests::OptionTests(interfaces::Node& node) : m_node(node) { @@ -263,3 +264,64 @@ void OptionTests::updateFontsWithPixelSizedWidget() GUIUtil::updateFonts(); QCOMPARE(label->font().pointSizeF(), scaled_size); } + +void OptionTests::fontScaleSources() +{ + const int previous_scale{GUIUtil::fontScale()}; + const auto read_settings_file{[] { + std::ifstream file{gArgs.GetDataDirNet() / "settings.json"}; + return std::string{std::istreambuf_iterator{file}, std::istreambuf_iterator{}}; + }}; + + const auto set_sources{[](const util::SettingsValue& settings_file, const util::SettingsValue& command_line, const util::SettingsValue& config_file) { + gArgs.LockSettings([&](util::Settings& settings) { + settings.rw_settings.erase("font-scale"); + settings.command_line_options.erase("font-scale"); + settings.ro_config[""].erase("font-scale"); + if (!settings_file.isNull()) settings.rw_settings["font-scale"] = settings_file; + if (!command_line.isNull()) settings.command_line_options["font-scale"] = {command_line}; + if (!config_file.isNull()) settings.ro_config[""]["font-scale"] = {config_file}; + }); + gArgs.WriteSettingsFile(); + }}; + + const UniValue none{}; + struct Case { + UniValue settings_file, command_line, config_file; + bool accepted; + int scale; + //! Expected settings.json value afterwards, nullptr if absent. + const char* saved; + }; + // OptionsModel writes the font scale as a string, older settings may hold a number. + for (const auto& [settings_file, command_line, config_file, accepted, scale, saved] : { + // An out-of-range settings.json value is clamped and saved back. + Case{"-80", none, none, true, GUIUtil::FONT_SCALE_MIN, "-50"}, + Case{GUIUtil::FONT_SCALE_MAX + 1, none, none, true, GUIUtil::FONT_SCALE_MAX, "100"}, + Case{"20", none, none, true, 20, "20"}, + // Command line and config file values are validated strictly, and never saved. + Case{none, "-80", none, false, 0, nullptr}, + Case{none, none, "-80", false, 0, nullptr}, + Case{none, none, "20", true, 20, nullptr}, + // settings.json is left alone while the command line overrides it. + Case{"-80", "20", none, true, 20, "-80"}, + }) { + set_sources(settings_file, command_line, config_file); + + // GuiMain() runs OptionsModel::Init() and then validates -font-scale. + bilingual_str error; + QVERIFY(OptionsModel{m_node}.Init(error)); + QCOMPARE(GUIUtil::setFontScaleFromArg(), accepted); + if (accepted) QCOMPARE(GUIUtil::fontScale(), scale); + + const std::string settings_json{read_settings_file()}; + if (saved) { + QVERIFY(settings_json.find(strprintf("\"font-scale\": \"%s\"", saved)) != std::string::npos); + } else { + QVERIFY(settings_json.find("font-scale") == std::string::npos); + } + } + + set_sources(none, none, none); + GUIUtil::setFontScale(previous_scale); +} diff --git a/src/qt/test/optiontests.h b/src/qt/test/optiontests.h index 2cee85ab5903..c6f58970ed6e 100644 --- a/src/qt/test/optiontests.h +++ b/src/qt/test/optiontests.h @@ -27,6 +27,7 @@ private Q_SLOTS: void extractFilter(); void effectivePointSize(); void updateFontsWithPixelSizedWidget(); + void fontScaleSources(); private: interfaces::Node& m_node;