From 6c9f1ae69bb05a5d4bb07405c38364246725f30f Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 27 Dec 2025 03:10:07 +0530 Subject: [PATCH 01/20] merge bitcoin-core/gui#600: Add OptionsModel getOption/setOption methods Review with `git log -p -n1 --ignore-space-change --color-moved=dimmed_zebra` --- src/qt/optionsmodel.cpp | 799 ++++++++++++++++++++-------------------- src/qt/optionsmodel.h | 2 + 2 files changed, 407 insertions(+), 394 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index ba84e8e75558..e5f96071ed05 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -554,439 +554,450 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const { if(role == Qt::EditRole) { - QSettings settings; - switch(index.row()) - { - case StartAtStartup: - return GUIUtil::GetStartOnSystemStartup(); - case ShowTrayIcon: - return m_show_tray_icon; - case MinimizeToTray: - return fMinimizeToTray; - case MapPortUPnP: + return getOption(OptionID(index.row())); + } + return QVariant(); +} + +// write QSettings values +bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role) +{ + bool successful = true; /* set to false on parse error */ + if(role == Qt::EditRole) + { + successful = setOption(OptionID(index.row()), value); + } + + Q_EMIT dataChanged(index, index); + + return successful; +} + +QVariant OptionsModel::getOption(OptionID option) const +{ + QSettings settings; + switch (option) { + case StartAtStartup: + return GUIUtil::GetStartOnSystemStartup(); + case ShowTrayIcon: + return m_show_tray_icon; + case MinimizeToTray: + return fMinimizeToTray; + case MapPortUPnP: #ifdef USE_UPNP - return settings.value("fUseUPnP"); + return settings.value("fUseUPnP"); #else - return false; + return false; #endif // USE_UPNP - case MapPortNatpmp: + case MapPortNatpmp: #ifdef USE_NATPMP - return settings.value("fUseNatpmp"); + return settings.value("fUseNatpmp"); #else - return false; + return false; #endif // USE_NATPMP - case MinimizeOnClose: - return fMinimizeOnClose; - - // default proxy - case ProxyUse: - return settings.value("fUseProxy", false); - case ProxyIP: - return GetProxySetting(settings, "addrProxy").ip; - case ProxyPort: - return GetProxySetting(settings, "addrProxy").port; - - // separate Tor proxy - case ProxyUseTor: - return settings.value("fUseSeparateProxyTor", false); - case ProxyIPTor: - return GetProxySetting(settings, "addrSeparateProxyTor").ip; - case ProxyPortTor: - return GetProxySetting(settings, "addrSeparateProxyTor").port; + case MinimizeOnClose: + return fMinimizeOnClose; + + // default proxy + case ProxyUse: + return settings.value("fUseProxy", false); + case ProxyIP: + return GetProxySetting(settings, "addrProxy").ip; + case ProxyPort: + return GetProxySetting(settings, "addrProxy").port; + + // separate Tor proxy + case ProxyUseTor: + return settings.value("fUseSeparateProxyTor", false); + case ProxyIPTor: + return GetProxySetting(settings, "addrSeparateProxyTor").ip; + case ProxyPortTor: + return GetProxySetting(settings, "addrSeparateProxyTor").port; #ifdef ENABLE_WALLET - case SpendZeroConfChange: - return settings.value("bSpendZeroConfChange"); - case ExternalSignerPath: - return settings.value("external_signer_path"); - case SubFeeFromAmount: - return m_sub_fee_from_amount; - case ShowMasternodesTab: - return settings.value("fShowMasternodesTab"); - case ShowGovernanceTab: - return settings.value("fShowGovernanceTab"); - case CoinJoinEnabled: - return settings.value("fCoinJoinEnabled"); - case ShowAdvancedCJUI: - return fShowAdvancedCJUI; - case ShowCoinJoinPopups: - return settings.value("fShowCoinJoinPopups"); - case LowKeysWarning: - return settings.value("fLowKeysWarning"); - case CoinJoinSessions: - return settings.value("nCoinJoinSessions"); - case CoinJoinRounds: - return settings.value("nCoinJoinRounds"); - case CoinJoinAmount: - return settings.value("nCoinJoinAmount"); - case CoinJoinDenomsGoal: - return settings.value("nCoinJoinDenomsGoal"); - case CoinJoinDenomsHardCap: - return settings.value("nCoinJoinDenomsHardCap"); - case CoinJoinMultiSession: - return settings.value("fCoinJoinMultiSession"); + case SpendZeroConfChange: + return settings.value("bSpendZeroConfChange"); + case ExternalSignerPath: + return settings.value("external_signer_path"); + case SubFeeFromAmount: + return m_sub_fee_from_amount; + case ShowMasternodesTab: + return settings.value("fShowMasternodesTab"); + case ShowGovernanceTab: + return settings.value("fShowGovernanceTab"); + case CoinJoinEnabled: + return settings.value("fCoinJoinEnabled"); + case ShowAdvancedCJUI: + return fShowAdvancedCJUI; + case ShowCoinJoinPopups: + return settings.value("fShowCoinJoinPopups"); + case LowKeysWarning: + return settings.value("fLowKeysWarning"); + case CoinJoinSessions: + return settings.value("nCoinJoinSessions"); + case CoinJoinRounds: + return settings.value("nCoinJoinRounds"); + case CoinJoinAmount: + return settings.value("nCoinJoinAmount"); + case CoinJoinDenomsGoal: + return settings.value("nCoinJoinDenomsGoal"); + case CoinJoinDenomsHardCap: + return settings.value("nCoinJoinDenomsHardCap"); + case CoinJoinMultiSession: + return settings.value("fCoinJoinMultiSession"); #endif - case DisplayUnit: - return QVariant::fromValue(m_display_bitcoin_unit); - case ThirdPartyTxUrls: - return strThirdPartyTxUrls; + case DisplayUnit: + return QVariant::fromValue(m_display_bitcoin_unit); + case ThirdPartyTxUrls: + return strThirdPartyTxUrls; #ifdef ENABLE_WALLET - case Digits: - return settings.value("digits"); + case Digits: + return settings.value("digits"); #endif // ENABLE_WALLET - case Theme: - return settings.value("theme"); - case FontFamily: - return settings.value("fontFamily"); - case FontScale: - return settings.value("fontScale"); - case FontWeightNormal: { - int nIndex = [&]() -> int { - if (QFont::Weight weight; GUIUtil::weightFromArg(settings.value("fontWeightNormal").toInt(), weight) && GUIUtil::g_font_registry.IsValidWeight(weight)) { - return GUIUtil::g_font_registry.WeightToIdx(weight); - } - return GUIUtil::g_font_registry.WeightToIdx(GUIUtil::g_font_registry.GetWeightNormalDefault()); - }(); - assert(nIndex != -1); - return nIndex; - } - case FontWeightBold: { - int nIndex = [&]() -> int { - if (QFont::Weight weight; GUIUtil::weightFromArg(settings.value("fontWeightBold").toInt(), weight) && GUIUtil::g_font_registry.IsValidWeight(weight)) { - return GUIUtil::g_font_registry.WeightToIdx(weight); - } - return GUIUtil::g_font_registry.WeightToIdx(GUIUtil::g_font_registry.GetWeightBoldDefault()); - }(); - assert(nIndex != -1); - return nIndex; - } - case Language: - return settings.value("language"); - case FontForMoney: - return QVariant::fromValue(m_font_money); + case Theme: + return settings.value("theme"); + case FontFamily: + return settings.value("fontFamily"); + case FontScale: + return settings.value("fontScale"); + case FontWeightNormal: { + int nIndex = [&]() -> int { + if (QFont::Weight weight; GUIUtil::weightFromArg(settings.value("fontWeightNormal").toInt(), weight) && GUIUtil::g_font_registry.IsValidWeight(weight)) { + return GUIUtil::g_font_registry.WeightToIdx(weight); + } + return GUIUtil::g_font_registry.WeightToIdx(GUIUtil::g_font_registry.GetWeightNormalDefault()); + }(); + assert(nIndex != -1); + return nIndex; + } + case FontWeightBold: { + int nIndex = [&]() -> int { + if (QFont::Weight weight; GUIUtil::weightFromArg(settings.value("fontWeightBold").toInt(), weight) && GUIUtil::g_font_registry.IsValidWeight(weight)) { + return GUIUtil::g_font_registry.WeightToIdx(weight); + } + return GUIUtil::g_font_registry.WeightToIdx(GUIUtil::g_font_registry.GetWeightBoldDefault()); + }(); + assert(nIndex != -1); + return nIndex; + } + case Language: + return settings.value("language"); + case FontForMoney: + return QVariant::fromValue(m_font_money); #ifdef ENABLE_WALLET - case CoinControlFeatures: - return fCoinControlFeatures; - case EnablePSBTControls: - return settings.value("enable_psbt_controls"); - case KeepChangeAddress: - return fKeepChangeAddress; + case CoinControlFeatures: + return fCoinControlFeatures; + case EnablePSBTControls: + return settings.value("enable_psbt_controls"); + case KeepChangeAddress: + return fKeepChangeAddress; #endif // ENABLE_WALLET - case Prune: - return settings.value("bPrune"); - case PruneSize: - return settings.value("nPruneSize"); - case DatabaseCache: - return settings.value("nDatabaseCache"); - case ThreadsScriptVerif: - return settings.value("nThreadsScriptVerif"); - case Listen: - return settings.value("fListen"); - case Server: - return settings.value("server"); - default: - return QVariant(); - } + case Prune: + return settings.value("bPrune"); + case PruneSize: + return settings.value("nPruneSize"); + case DatabaseCache: + return settings.value("nDatabaseCache"); + case ThreadsScriptVerif: + return settings.value("nThreadsScriptVerif"); + case Listen: + return settings.value("fListen"); + case Server: + return settings.value("server"); + default: + return QVariant(); } - return QVariant(); } -// write QSettings values -bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role) +bool OptionsModel::setOption(OptionID option, const QVariant& value) { bool successful = true; /* set to false on parse error */ - if(role == Qt::EditRole) - { - QSettings settings; - switch(index.row()) - { - case StartAtStartup: - successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); - break; - case ShowTrayIcon: - m_show_tray_icon = value.toBool(); - settings.setValue("fHideTrayIcon", !m_show_tray_icon); - Q_EMIT showTrayIconChanged(m_show_tray_icon); - break; - case MinimizeToTray: - fMinimizeToTray = value.toBool(); - settings.setValue("fMinimizeToTray", fMinimizeToTray); - break; - case MapPortUPnP: // core option - can be changed on-the-fly - settings.setValue("fUseUPnP", value.toBool()); - break; - case MapPortNatpmp: // core option - can be changed on-the-fly - settings.setValue("fUseNatpmp", value.toBool()); - break; - case MinimizeOnClose: - fMinimizeOnClose = value.toBool(); - settings.setValue("fMinimizeOnClose", fMinimizeOnClose); - break; + QSettings settings; + switch (option) { + case StartAtStartup: + successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); + break; + case ShowTrayIcon: + m_show_tray_icon = value.toBool(); + settings.setValue("fHideTrayIcon", !m_show_tray_icon); + Q_EMIT showTrayIconChanged(m_show_tray_icon); + break; + case MinimizeToTray: + fMinimizeToTray = value.toBool(); + settings.setValue("fMinimizeToTray", fMinimizeToTray); + break; + case MapPortUPnP: // core option - can be changed on-the-fly + settings.setValue("fUseUPnP", value.toBool()); + break; + case MapPortNatpmp: // core option - can be changed on-the-fly + settings.setValue("fUseNatpmp", value.toBool()); + break; + case MinimizeOnClose: + fMinimizeOnClose = value.toBool(); + settings.setValue("fMinimizeOnClose", fMinimizeOnClose); + break; - // default proxy - case ProxyUse: - if (settings.value("fUseProxy") != value) { - settings.setValue("fUseProxy", value.toBool()); - setRestartRequired(true); - } - break; - case ProxyIP: { - auto ip_port = GetProxySetting(settings, "addrProxy"); - if (!ip_port.is_set || ip_port.ip != value.toString()) { - ip_port.ip = value.toString(); - SetProxySetting(settings, "addrProxy", ip_port); - setRestartRequired(true); - } + // default proxy + case ProxyUse: + if (settings.value("fUseProxy") != value) { + settings.setValue("fUseProxy", value.toBool()); + setRestartRequired(true); } break; - case ProxyPort: { - auto ip_port = GetProxySetting(settings, "addrProxy"); - if (!ip_port.is_set || ip_port.port != value.toString()) { - ip_port.port = value.toString(); - SetProxySetting(settings, "addrProxy", ip_port); - setRestartRequired(true); - } + case ProxyIP: { + auto ip_port = GetProxySetting(settings, "addrProxy"); + if (!ip_port.is_set || ip_port.ip != value.toString()) { + ip_port.ip = value.toString(); + SetProxySetting(settings, "addrProxy", ip_port); + setRestartRequired(true); } - break; + } + break; + case ProxyPort: { + auto ip_port = GetProxySetting(settings, "addrProxy"); + if (!ip_port.is_set || ip_port.port != value.toString()) { + ip_port.port = value.toString(); + SetProxySetting(settings, "addrProxy", ip_port); + setRestartRequired(true); + } + } + break; - // separate Tor proxy - case ProxyUseTor: - if (settings.value("fUseSeparateProxyTor") != value) { - settings.setValue("fUseSeparateProxyTor", value.toBool()); - setRestartRequired(true); - } - break; - case ProxyIPTor: { - auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); - if (!ip_port.is_set || ip_port.ip != value.toString()) { - ip_port.ip = value.toString(); - SetProxySetting(settings, "addrSeparateProxyTor", ip_port); - setRestartRequired(true); - } + // separate Tor proxy + case ProxyUseTor: + if (settings.value("fUseSeparateProxyTor") != value) { + settings.setValue("fUseSeparateProxyTor", value.toBool()); + setRestartRequired(true); } break; - case ProxyPortTor: { - auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); - if (!ip_port.is_set || ip_port.port != value.toString()) { - ip_port.port = value.toString(); - SetProxySetting(settings, "addrSeparateProxyTor", ip_port); - setRestartRequired(true); - } + case ProxyIPTor: { + auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); + if (!ip_port.is_set || ip_port.ip != value.toString()) { + ip_port.ip = value.toString(); + SetProxySetting(settings, "addrSeparateProxyTor", ip_port); + setRestartRequired(true); } - break; + } + break; + case ProxyPortTor: { + auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); + if (!ip_port.is_set || ip_port.port != value.toString()) { + ip_port.port = value.toString(); + SetProxySetting(settings, "addrSeparateProxyTor", ip_port); + setRestartRequired(true); + } + } + break; #ifdef ENABLE_WALLET - case SpendZeroConfChange: - if (settings.value("bSpendZeroConfChange") != value) { - settings.setValue("bSpendZeroConfChange", value); - setRestartRequired(true); - } - break; - case ExternalSignerPath: - if (settings.value("external_signer_path") != value.toString()) { - settings.setValue("external_signer_path", value.toString()); - setRestartRequired(true); - } - break; - case ShowMasternodesTab: - if (settings.value("fShowMasternodesTab") != value) { - settings.setValue("fShowMasternodesTab", value); - setRestartRequired(true); - } - break; - case SubFeeFromAmount: - m_sub_fee_from_amount = value.toBool(); - settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount); - break; - case ShowGovernanceTab: - if (settings.value("fShowGovernanceTab") != value) { - settings.setValue("fShowGovernanceTab", value); - setRestartRequired(true); - } - break; - case CoinJoinEnabled: - if (settings.value("fCoinJoinEnabled") != value) { - settings.setValue("fCoinJoinEnabled", value.toBool()); - Q_EMIT coinJoinEnabledChanged(); - } - break; - case ShowAdvancedCJUI: - if (settings.value("fShowAdvancedCJUI") != value) { - fShowAdvancedCJUI = value.toBool(); - settings.setValue("fShowAdvancedCJUI", fShowAdvancedCJUI); - Q_EMIT AdvancedCJUIChanged(fShowAdvancedCJUI); - } - break; - case ShowCoinJoinPopups: - settings.setValue("fShowCoinJoinPopups", value); - break; - case LowKeysWarning: - settings.setValue("fLowKeysWarning", value); - break; - case CoinJoinSessions: - if (settings.value("nCoinJoinSessions") != value) { - node().coinJoinOptions().setSessions(value.toInt()); - settings.setValue("nCoinJoinSessions", node().coinJoinOptions().getSessions()); - Q_EMIT coinJoinRoundsChanged(); - } - break; - case CoinJoinRounds: - if (settings.value("nCoinJoinRounds") != value) - { - node().coinJoinOptions().setRounds(value.toInt()); - settings.setValue("nCoinJoinRounds", node().coinJoinOptions().getRounds()); - Q_EMIT coinJoinRoundsChanged(); - } - break; - case CoinJoinAmount: - if (settings.value("nCoinJoinAmount") != value) - { - node().coinJoinOptions().setAmount(value.toInt()); - settings.setValue("nCoinJoinAmount", node().coinJoinOptions().getAmount()); - Q_EMIT coinJoinAmountChanged(); - } - break; - case CoinJoinDenomsGoal: - if (settings.value("nCoinJoinDenomsGoal") != value) { - node().coinJoinOptions().setDenomsGoal(value.toInt()); - settings.setValue("nCoinJoinDenomsGoal", node().coinJoinOptions().getDenomsGoal()); - } - break; - case CoinJoinDenomsHardCap: - if (settings.value("nCoinJoinDenomsHardCap") != value) { - node().coinJoinOptions().setDenomsHardCap(value.toInt()); - settings.setValue("nCoinJoinDenomsHardCap", node().coinJoinOptions().getDenomsHardCap()); - } - break; - case CoinJoinMultiSession: - if (settings.value("fCoinJoinMultiSession") != value) - { - node().coinJoinOptions().setMultiSessionEnabled(value.toBool()); - settings.setValue("fCoinJoinMultiSession", node().coinJoinOptions().isMultiSessionEnabled()); - } - break; + case SpendZeroConfChange: + if (settings.value("bSpendZeroConfChange") != value) { + settings.setValue("bSpendZeroConfChange", value); + setRestartRequired(true); + } + break; + case ExternalSignerPath: + if (settings.value("external_signer_path") != value.toString()) { + settings.setValue("external_signer_path", value.toString()); + setRestartRequired(true); + } + break; + case ShowMasternodesTab: + if (settings.value("fShowMasternodesTab") != value) { + settings.setValue("fShowMasternodesTab", value); + setRestartRequired(true); + } + break; + case SubFeeFromAmount: + m_sub_fee_from_amount = value.toBool(); + settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount); + break; + case ShowGovernanceTab: + if (settings.value("fShowGovernanceTab") != value) { + settings.setValue("fShowGovernanceTab", value); + setRestartRequired(true); + } + break; + case CoinJoinEnabled: + if (settings.value("fCoinJoinEnabled") != value) { + settings.setValue("fCoinJoinEnabled", value.toBool()); + Q_EMIT coinJoinEnabledChanged(); + } + break; + case ShowAdvancedCJUI: + if (settings.value("fShowAdvancedCJUI") != value) { + fShowAdvancedCJUI = value.toBool(); + settings.setValue("fShowAdvancedCJUI", fShowAdvancedCJUI); + Q_EMIT AdvancedCJUIChanged(fShowAdvancedCJUI); + } + break; + case ShowCoinJoinPopups: + settings.setValue("fShowCoinJoinPopups", value); + break; + case LowKeysWarning: + settings.setValue("fLowKeysWarning", value); + break; + case CoinJoinSessions: + if (settings.value("nCoinJoinSessions") != value) { + node().coinJoinOptions().setSessions(value.toInt()); + settings.setValue("nCoinJoinSessions", node().coinJoinOptions().getSessions()); + Q_EMIT coinJoinRoundsChanged(); + } + break; + case CoinJoinRounds: + if (settings.value("nCoinJoinRounds") != value) + { + node().coinJoinOptions().setRounds(value.toInt()); + settings.setValue("nCoinJoinRounds", node().coinJoinOptions().getRounds()); + Q_EMIT coinJoinRoundsChanged(); + } + break; + case CoinJoinAmount: + if (settings.value("nCoinJoinAmount") != value) + { + node().coinJoinOptions().setAmount(value.toInt()); + settings.setValue("nCoinJoinAmount", node().coinJoinOptions().getAmount()); + Q_EMIT coinJoinAmountChanged(); + } + break; + case CoinJoinDenomsGoal: + if (settings.value("nCoinJoinDenomsGoal") != value) { + node().coinJoinOptions().setDenomsGoal(value.toInt()); + settings.setValue("nCoinJoinDenomsGoal", node().coinJoinOptions().getDenomsGoal()); + } + break; + case CoinJoinDenomsHardCap: + if (settings.value("nCoinJoinDenomsHardCap") != value) { + node().coinJoinOptions().setDenomsHardCap(value.toInt()); + settings.setValue("nCoinJoinDenomsHardCap", node().coinJoinOptions().getDenomsHardCap()); + } + break; + case CoinJoinMultiSession: + if (settings.value("fCoinJoinMultiSession") != value) + { + node().coinJoinOptions().setMultiSessionEnabled(value.toBool()); + settings.setValue("fCoinJoinMultiSession", node().coinJoinOptions().isMultiSessionEnabled()); + } + break; #endif - case DisplayUnit: - setDisplayUnit(value); - break; - case ThirdPartyTxUrls: - if (strThirdPartyTxUrls != value.toString()) { - strThirdPartyTxUrls = value.toString(); - settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls); - setRestartRequired(true); - } - break; + case DisplayUnit: + setDisplayUnit(value); + break; + case ThirdPartyTxUrls: + if (strThirdPartyTxUrls != value.toString()) { + strThirdPartyTxUrls = value.toString(); + settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls); + setRestartRequired(true); + } + break; #ifdef ENABLE_WALLET - case Digits: - if (settings.value("digits") != value) { - settings.setValue("digits", value); - setRestartRequired(true); - } - break; + case Digits: + if (settings.value("digits") != value) { + settings.setValue("digits", value); + setRestartRequired(true); + } + break; #endif // ENABLE_WALLET - case Theme: - // Set in AppearanceWidget::updateTheme slot now - // to allow instant theme changes. - break; - case FontFamily: - if (settings.value("fontFamily") != value) { - settings.setValue("fontFamily", value); - } - break; - case FontScale: - if (settings.value("fontScale") != value) { - settings.setValue("fontScale", value); - } - break; - case FontWeightNormal: { - int nWeight = GUIUtil::weightToArg(GUIUtil::g_font_registry.IdxToWeight(value.toInt())); - if (settings.value("fontWeightNormal") != nWeight) { - settings.setValue("fontWeightNormal", nWeight); - } - break; + case Theme: + // Set in AppearanceWidget::updateTheme slot now + // to allow instant theme changes. + break; + case FontFamily: + if (settings.value("fontFamily") != value) { + settings.setValue("fontFamily", value); } - case FontWeightBold: { - int nWeight = GUIUtil::weightToArg(GUIUtil::g_font_registry.IdxToWeight(value.toInt())); - if (settings.value("fontWeightBold") != nWeight) { - settings.setValue("fontWeightBold", nWeight); - } - break; + break; + case FontScale: + if (settings.value("fontScale") != value) { + settings.setValue("fontScale", value); } - case Language: - if (settings.value("language") != value) { - settings.setValue("language", value); - setRestartRequired(true); - } - break; - case FontForMoney: - { - const auto& new_font = value.value(); - if (m_font_money != new_font) { - settings.setValue("FontForMoney", FontChoiceToString(new_font)); - m_font_money = new_font; - } - Q_EMIT fontForMoneyChanged(getFontForMoney()); - break; + break; + case FontWeightNormal: { + int nWeight = GUIUtil::weightToArg(GUIUtil::g_font_registry.IdxToWeight(value.toInt())); + if (settings.value("fontWeightNormal") != nWeight) { + settings.setValue("fontWeightNormal", nWeight); + } + break; + } + case FontWeightBold: { + int nWeight = GUIUtil::weightToArg(GUIUtil::g_font_registry.IdxToWeight(value.toInt())); + if (settings.value("fontWeightBold") != nWeight) { + settings.setValue("fontWeightBold", nWeight); + } + break; + } + case Language: + if (settings.value("language") != value) { + settings.setValue("language", value); + setRestartRequired(true); + } + break; + case FontForMoney: + { + const auto& new_font = value.value(); + if (m_font_money != new_font) { + settings.setValue("FontForMoney", FontChoiceToString(new_font)); + m_font_money = new_font; } + Q_EMIT fontForMoneyChanged(getFontForMoney()); + break; + } #ifdef ENABLE_WALLET - case CoinControlFeatures: - fCoinControlFeatures = value.toBool(); - settings.setValue("fCoinControlFeatures", fCoinControlFeatures); - Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); - break; - case EnablePSBTControls: - m_enable_psbt_controls = value.toBool(); - settings.setValue("enable_psbt_controls", m_enable_psbt_controls); - break; - case KeepChangeAddress: - fKeepChangeAddress = value.toBool(); - settings.setValue("fKeepChangeAddress", fKeepChangeAddress); - Q_EMIT keepChangeAddressChanged(fKeepChangeAddress); - break; - case Prune: - if (settings.value("bPrune") != value) { - settings.setValue("bPrune", value); - setRestartRequired(true); - } - break; - case PruneSize: - if (settings.value("nPruneSize") != value) { - settings.setValue("nPruneSize", value); - setRestartRequired(true); - } - break; + case CoinControlFeatures: + fCoinControlFeatures = value.toBool(); + settings.setValue("fCoinControlFeatures", fCoinControlFeatures); + Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); + break; + case EnablePSBTControls: + m_enable_psbt_controls = value.toBool(); + settings.setValue("enable_psbt_controls", m_enable_psbt_controls); + break; + case KeepChangeAddress: + fKeepChangeAddress = value.toBool(); + settings.setValue("fKeepChangeAddress", fKeepChangeAddress); + Q_EMIT keepChangeAddressChanged(fKeepChangeAddress); + break; + case Prune: + if (settings.value("bPrune") != value) { + settings.setValue("bPrune", value); + setRestartRequired(true); + } + break; + case PruneSize: + if (settings.value("nPruneSize") != value) { + settings.setValue("nPruneSize", value); + setRestartRequired(true); + } + break; #endif // ENABLE_WALLET - case DatabaseCache: - if (settings.value("nDatabaseCache") != value) { - settings.setValue("nDatabaseCache", value); - setRestartRequired(true); - } - break; - case ThreadsScriptVerif: - if (settings.value("nThreadsScriptVerif") != value) { - settings.setValue("nThreadsScriptVerif", value); - setRestartRequired(true); - } - break; - case Listen: - if (settings.value("fListen") != value) { - settings.setValue("fListen", value); - setRestartRequired(true); - } - break; - case Server: - if (settings.value("server") != value) { - settings.setValue("server", value); - setRestartRequired(true); - } - break; - default: - break; + case DatabaseCache: + if (settings.value("nDatabaseCache") != value) { + settings.setValue("nDatabaseCache", value); + setRestartRequired(true); + } + break; + case ThreadsScriptVerif: + if (settings.value("nThreadsScriptVerif") != value) { + settings.setValue("nThreadsScriptVerif", value); + setRestartRequired(true); + } + break; + case Listen: + if (settings.value("fListen") != value) { + settings.setValue("fListen", value); + setRestartRequired(true); } + break; + case Server: + if (settings.value("server") != value) { + settings.setValue("server", value); + setRestartRequired(true); + } + break; + default: + break; } - Q_EMIT dataChanged(index, index); - return successful; } diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 1220c45ca192..6aa80e329bf9 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -111,6 +111,8 @@ class OptionsModel : public QAbstractListModel int rowCount(const QModelIndex & parent = QModelIndex()) const override; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override; + QVariant getOption(OptionID option) const; + bool setOption(OptionID option, const QVariant& value); /** Updates current unit in memory, settings and emits displayUnitChanged(new_unit) signal */ void setDisplayUnit(const QVariant& new_unit); From 4fc25af146b0cbbcc4c3d8ae673cab2bd3462728 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 29 Apr 2019 15:29:00 -0400 Subject: [PATCH 02/20] merge bitcoin-core/gui#601: Pass interfaces::Node references to OptionsModel constructor --- src/qt/bitcoin.cpp | 14 +++++++------- src/qt/optionsmodel.cpp | 4 ++-- src/qt/optionsmodel.h | 7 +++---- src/qt/test/addressbooktests.cpp | 2 +- src/qt/test/optiontests.cpp | 7 +++---- src/qt/test/optiontests.h | 2 +- src/qt/test/wallettests.cpp | 2 +- 7 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 6f468642f74f..8362b2aa2f00 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -267,7 +267,7 @@ void BitcoinApplication::createPaymentServer() void BitcoinApplication::createOptionsModel(bool resetSettings) { - optionsModel = new OptionsModel(this, resetSettings); + optionsModel = new OptionsModel(node(), this, resetSettings); } void BitcoinApplication::createWindow(const NetworkStyle *networkStyle) @@ -293,7 +293,6 @@ void BitcoinApplication::createNode(interfaces::Init& init) { assert(!m_node); m_node = init.makeNode(); - if (optionsModel) optionsModel->setNode(*m_node); if (m_splash) m_splash->setNode(*m_node); } @@ -668,6 +667,12 @@ int GuiMain(int argc, char* argv[]) QMessageBox::critical(nullptr, PACKAGE_NAME, QObject::tr("Error: Failed to load application fonts.")); return EXIT_FAILURE; } + + if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) + app.createSplashScreen(networkStyle.data()); + + app.createNode(*init); + // Load GUI settings from QSettings app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false)); // Validate/set font family @@ -758,11 +763,6 @@ int GuiMain(int argc, char* argv[]) app.InitPruneSetting(prune_MiB); } - if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false)) - app.createSplashScreen(networkStyle.data()); - - app.createNode(*init); - int rv = EXIT_SUCCESS; try { diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index e5f96071ed05..708cbf6c9d54 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -74,8 +74,8 @@ OptionsModel::FontChoice OptionsModel::FontChoiceFromString(const QString& s) } } -OptionsModel::OptionsModel(QObject *parent, bool resetSettings) : - QAbstractListModel(parent) +OptionsModel::OptionsModel(interfaces::Node& node, QObject *parent, bool resetSettings) : + QAbstractListModel(parent), m_node{node} { Init(resetSettings); } diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 6aa80e329bf9..4e8227bea1aa 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -44,7 +44,7 @@ class OptionsModel : public QAbstractListModel Q_OBJECT public: - explicit OptionsModel(QObject *parent = nullptr, bool resetSettings = false); + explicit OptionsModel(interfaces::Node& node, QObject *parent = nullptr, bool resetSettings = false); enum OptionID { StartAtStartup, // bool @@ -141,11 +141,10 @@ class OptionsModel : public QAbstractListModel bool isRestartRequired() const; bool resetSettingsOnShutdown{false}; - interfaces::Node& node() const { assert(m_node); return *m_node; } - void setNode(interfaces::Node& node) { assert(!m_node); m_node = &node; } + interfaces::Node& node() const { return m_node; } private: - interfaces::Node* m_node = nullptr; + interfaces::Node& m_node; /* Qt-only settings */ bool m_show_tray_icon; bool fMinimizeToTray; diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 3aa570ce793f..0db094962b7c 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -122,7 +122,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node) check_addbook_size(2); // Initialize relevant QT models. - OptionsModel optionsModel; + OptionsModel optionsModel(node); ClientModel clientModel(node, &optionsModel); WalletContext& context = *node.walletLoader().context(); AddWallet(context, wallet); diff --git a/src/qt/test/optiontests.cpp b/src/qt/test/optiontests.cpp index 343755103cf2..c3cd78553e99 100644 --- a/src/qt/test/optiontests.cpp +++ b/src/qt/test/optiontests.cpp @@ -14,8 +14,7 @@ #include -//! Entry point for BitcoinApplication tests. -void OptionTests::optionTests() +void OptionTests::integerGetArgBug() { // Test regression https://github.com/bitcoin/bitcoin/issues/24457. Ensure // that setting integer prune value doesn't cause an exception to be thrown @@ -25,7 +24,7 @@ void OptionTests::optionTests() settings.rw_settings["prune"] = 3814; }); gArgs.WriteSettingsFile(); - OptionsModel{}; + OptionsModel{m_node}; gArgs.LockSettings([&](util::Settings& settings) { settings.rw_settings.erase("prune"); }); @@ -50,7 +49,7 @@ void OptionTests::parametersInteraction() QSettings settings; settings.setValue("fListen", false); - OptionsModel{}; + OptionsModel{m_node}; const bool expected{false}; diff --git a/src/qt/test/optiontests.h b/src/qt/test/optiontests.h index d5a3bfd6b4a0..78061d8fe870 100644 --- a/src/qt/test/optiontests.h +++ b/src/qt/test/optiontests.h @@ -16,7 +16,7 @@ class OptionTests : public QObject explicit OptionTests(interfaces::Node& node) : m_node(node) {} private Q_SLOTS: - void optionTests(); + void integerGetArgBug(); void parametersInteraction(); void extractFilter(); diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 9cd8d1ceb885..3650e3a8a2c5 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -153,7 +153,7 @@ void TestGUI(interfaces::Node& node) // Create widgets for sending coins and listing transactions. SendCoinsDialog sendCoinsDialog; TransactionView transactionView; - OptionsModel optionsModel; + OptionsModel optionsModel(node); ClientModel clientModel(node, &optionsModel); WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel); sendCoinsDialog.setModel(&walletModel); From bb2efec34e0bd7f48c9d26328e1a471d3638fbfe Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 3 Jan 2026 23:23:39 +0530 Subject: [PATCH 03/20] qt: remove PrivateSend -> CoinJoin migration logic, remove old values The migration to CoinJoin was done in dash#4038, included in v0.17 and there's more than a few major releases since then, we should be able to safely assume that upgrading users are using more recent clients. --- src/qt/optionsmodel.cpp | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 708cbf6c9d54..d2e088417783 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -1052,6 +1052,15 @@ void OptionsModel::checkAndMigrate() settings.remove("bUsePrivateSend"); } + if (settingsVersion < 230100) { + settings.remove("fPrivateSendEnabled"); + settings.remove("fPrivateSendMultiSession"); + settings.remove("fShowAdvancedPSUI"); + settings.remove("fShowPrivateSendPopups"); + settings.remove("nPrivateSendAmount"); + settings.remove("nPrivateSendRounds"); + } + settings.setValue(strSettingsVersionKey, CLIENT_VERSION); } @@ -1072,31 +1081,4 @@ void OptionsModel::checkAndMigrate() if (!GUIUtil::isValidTheme(strActiveTheme)) { settings.setValue("theme", GUIUtil::getDefaultTheme()); } - - // begin PrivateSend -> CoinJoin migration - if (settings.contains("nPrivateSendRounds") && !settings.contains("nCoinJoinRounds")) { - settings.setValue("nCoinJoinRounds", settings.value("nPrivateSendRounds").toInt()); - settings.remove("nPrivateSendRounds"); - } - if (settings.contains("nPrivateSendAmount") && !settings.contains("nCoinJoinAmount")) { - settings.setValue("nCoinJoinAmount", settings.value("nPrivateSendAmount").toInt()); - settings.remove("nPrivateSendAmount"); - } - if (settings.contains("fPrivateSendEnabled") && !settings.contains("fCoinJoinEnabled")) { - settings.setValue("fCoinJoinEnabled", settings.value("fPrivateSendEnabled").toBool()); - settings.remove("fPrivateSendEnabled"); - } - if (settings.contains("fPrivateSendMultiSession") && !settings.contains("fCoinJoinMultiSession")) { - settings.setValue("fCoinJoinMultiSession", settings.value("fPrivateSendMultiSession").toBool()); - settings.remove("fPrivateSendMultiSession"); - } - if (settings.contains("fShowAdvancedPSUI") && !settings.contains("fShowAdvancedCJUI")) { - settings.setValue("fShowAdvancedCJUI", settings.value("fShowAdvancedPSUI").toBool()); - settings.remove("fShowAdvancedPSUI"); - } - if (settings.contains("fShowPrivateSendPopups") && !settings.contains("fShowCoinJoinPopups")) { - settings.setValue("fShowCoinJoinPopups", settings.value("fShowPrivateSendPopups").toBool()); - settings.remove("fShowPrivateSendPopups"); - } - // end PrivateSend -> CoinJoin migration } From d7cc771e99513540f834e8b11b70ff8fb87550f0 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 30 Aug 2025 12:00:45 +0000 Subject: [PATCH 04/20] qt: move Prune{,Size} handling outside `ENABLE_WALLET` gate --- src/qt/optionsmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index d2e088417783..d8c830999ec6 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -957,6 +957,7 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) settings.setValue("fKeepChangeAddress", fKeepChangeAddress); Q_EMIT keepChangeAddressChanged(fKeepChangeAddress); break; +#endif // ENABLE_WALLET case Prune: if (settings.value("bPrune") != value) { settings.setValue("bPrune", value); @@ -969,7 +970,6 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) setRestartRequired(true); } break; -#endif // ENABLE_WALLET case DatabaseCache: if (settings.value("nDatabaseCache") != value) { settings.setValue("nDatabaseCache", value); From dd2199284d0a0ec5894119ecc93e3149a8e34e57 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 2 Jan 2026 02:35:30 +0530 Subject: [PATCH 05/20] merge bitcoin-core/gui#602: Unify bitcoin-qt and bitcoind persistent settings Changes related to external signers are not included as we don't have support for them. Additionally, pruning affects governance and txindex. --- doc/release-notes-6833.md | 15 + src/qt/bitcoin.cpp | 28 +- src/qt/bitcoin.h | 2 +- src/qt/forms/optionsdialog.ui | 2 +- src/qt/optionsdialog.cpp | 5 - src/qt/optionsmodel.cpp | 506 +++++++++++++++++-------------- src/qt/optionsmodel.h | 19 +- src/qt/test/addressbooktests.cpp | 2 + src/qt/test/apptests.cpp | 7 +- src/qt/test/optiontests.cpp | 65 +++- src/qt/test/optiontests.h | 7 +- src/qt/test/test_main.cpp | 5 +- src/qt/test/wallettests.cpp | 2 + 13 files changed, 413 insertions(+), 252 deletions(-) create mode 100644 doc/release-notes-6833.md diff --git a/doc/release-notes-6833.md b/doc/release-notes-6833.md new file mode 100644 index 000000000000..23e50378905b --- /dev/null +++ b/doc/release-notes-6833.md @@ -0,0 +1,15 @@ +GUI changes +----------- + +Configuration changes made in the Dash GUI (such as the pruning setting, +proxy settings, UPNP preferences) are now saved to `/settings.json` +file rather than to the Qt settings backend (windows registry or unix desktop +config files), so these settings will now apply to dashd, instead of being +ignored. + +Also, the interaction between GUI settings and `dash.conf` settings is +simplified. Settings from `dash.conf` are now displayed normally in the GUI +settings dialog, instead of in a separate warning message ("Options set in this +dialog are overridden by the configuration file: -setting=value"). And these +settings can now be edited because `settings.json` values take precedence over +`dash.conf` values. diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 8362b2aa2f00..aa895e627754 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -265,9 +265,26 @@ void BitcoinApplication::createPaymentServer() } #endif -void BitcoinApplication::createOptionsModel(bool resetSettings) +bool BitcoinApplication::createOptionsModel(bool resetSettings) { - optionsModel = new OptionsModel(node(), this, resetSettings); + optionsModel = new OptionsModel(node(), this); + if (resetSettings) { + optionsModel->Reset(); + } + bilingual_str error; + if (!optionsModel->Init(error)) { + fs::path settings_path; + if (gArgs.GetSettingsPath(&settings_path)) { + error += Untranslated("\n"); + std::string quoted_path = strprintf("%s", fs::quoted(fs::PathToString(settings_path))); + error.original += strprintf("Settings file %s might be corrupt or invalid.", quoted_path); + error.translated += tr("Settings file %1 might be corrupt or invalid.").arg(QString::fromStdString(quoted_path)).toStdString(); + } + InitError(error); + QMessageBox::critical(nullptr, PACKAGE_NAME, QString::fromStdString(error.translated)); + return false; + } + return true; } void BitcoinApplication::createWindow(const NetworkStyle *networkStyle) @@ -329,7 +346,7 @@ void BitcoinApplication::parameterSetup() void BitcoinApplication::InitPruneSetting(int64_t prune_MiB) { - optionsModel->SetPruneTargetGB(PruneMiBtoGB(prune_MiB), true); + optionsModel->SetPruneTargetGB(PruneMiBtoGB(prune_MiB)); } void BitcoinApplication::requestInitialize() @@ -674,7 +691,10 @@ int GuiMain(int argc, char* argv[]) app.createNode(*init); // Load GUI settings from QSettings - app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false)); + if (!app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false))) { + return EXIT_FAILURE; + } + // Validate/set font family if (gArgs.IsArgSet("-font-family")) { QString family = gArgs.GetArg("-font-family", GUIUtil::FontRegistry::DEFAULT_FONT.toUtf8().toStdString()).c_str(); diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h index 7aa12efbc2fb..0cf8ee03bcfc 100644 --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -46,7 +46,7 @@ class BitcoinApplication: public QApplication /// parameter interaction/setup based on rules void parameterSetup(); /// Create options model - void createOptionsModel(bool resetSettings); + [[nodiscard]] bool createOptionsModel(bool resetSettings); /// Initialize prune setting void InitPruneSetting(int64_t prune_MiB); /// Create main window diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index 49e06646e9c3..eda0ce5cdd69 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -1140,7 +1140,7 @@ https://explore.transifex.com/dash/dash/ - Options set in this dialog are overridden by the command line or in the configuration file: + Options set in this dialog are overridden by the command line: Qt::PlainText diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 9518d6f6ff38..034ae4f908ef 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -84,10 +83,6 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : #ifndef USE_NATPMP ui->mapPortNatpmp->setEnabled(false); #endif - connect(this, &QDialog::accepted, [this](){ - QSettings settings; - model->node().mapPort(settings.value("fUseUPnP").toBool(), settings.value("fUseNatpmp").toBool()); - }); ui->proxyIp->setEnabled(false); ui->proxyPort->setEnabled(false); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index d8c830999ec6..9aa2834641bd 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -24,6 +24,7 @@ #ifdef ENABLE_WALLET #include +#include // For DEFAULT_SPEND_ZEROCONF_CHANGE #endif #include @@ -32,10 +33,96 @@ #include #include +#include + const char *DEFAULT_GUI_PROXY_HOST = "127.0.0.1"; static QString GetDefaultProxyAddress(); +/** Map GUI option ID to node setting name. */ +static const char* SettingName(OptionsModel::OptionID option) +{ + switch (option) { + case OptionsModel::DatabaseCache: return "dbcache"; + case OptionsModel::ThreadsScriptVerif: return "par"; + case OptionsModel::SpendZeroConfChange: return "spendzeroconfchange"; + case OptionsModel::ExternalSignerPath: return "signer"; + case OptionsModel::MapPortUPnP: return "upnp"; + case OptionsModel::MapPortNatpmp: return "natpmp"; + case OptionsModel::Listen: return "listen"; + case OptionsModel::Server: return "server"; + case OptionsModel::PruneSize: return "prune"; + case OptionsModel::Prune: return "prune"; + case OptionsModel::ProxyIP: return "proxy"; + case OptionsModel::ProxyPort: return "proxy"; + case OptionsModel::ProxyUse: return "proxy"; + case OptionsModel::ProxyIPTor: return "onion"; + case OptionsModel::ProxyPortTor: return "onion"; + case OptionsModel::ProxyUseTor: return "onion"; + case OptionsModel::Language: return "lang"; + default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option)); + } +} + +/** Call node.updateRwSetting() with Bitcoin 22.x workaround. */ +static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID option, const util::SettingsValue& value) +{ + if (value.isNum() && + (option == OptionsModel::DatabaseCache || + option == OptionsModel::ThreadsScriptVerif || + option == OptionsModel::Prune || + option == OptionsModel::PruneSize)) { + // Write certain old settings as strings, even though they are numbers, + // because Bitcoin 22.x releases try to read these specific settings as + // strings in addOverriddenOption() calls at startup, triggering + // uncaught exceptions in UniValue::get_str(). These errors were fixed + // in later releases by https://github.com/bitcoin/bitcoin/pull/24498. + // If new numeric settings are added, they can be written as numbers + // instead of strings, because bitcoin 22.x will not try to read these. + node.updateRwSetting(SettingName(option), value.getValStr()); + } else { + node.updateRwSetting(SettingName(option), value); + } +} + +//! Convert enabled/size values to bitcoin -prune setting. +static util::SettingsValue PruneSetting(bool prune_enabled, int prune_size_gb) +{ + assert(!prune_enabled || prune_size_gb >= 1); // PruneSizeGB and ParsePruneSizeGB never return less + return prune_enabled ? PruneGBtoMiB(prune_size_gb) : 0; +} + +//! Get pruning enabled value to show in GUI from bitcoin -prune setting. +static bool PruneEnabled(const util::SettingsValue& prune_setting) +{ + // -prune=1 setting is manual pruning mode, so disabled for purposes of the gui + return SettingToInt(prune_setting, 0) > 1; +} + +//! Get pruning size value to show in GUI from bitcoin -prune setting. If +//! pruning is not enabled, just show default recommended pruning size (2GB). +static int PruneSizeGB(const util::SettingsValue& prune_setting) +{ + int value = SettingToInt(prune_setting, 0); + return value > 1 ? PruneMiBtoGB(value) : DEFAULT_PRUNE_TARGET_GB; +} + +//! Parse pruning size value provided by user in GUI or loaded from QSettings +//! (windows registry key or qt .conf file). Smallest value that the GUI can +//! display is 1 GB, so round up if anything less is parsed. +static int ParsePruneSizeGB(const QVariant& prune_size) +{ + return std::max(1, prune_size.toInt()); +} + +struct ProxySetting { + bool is_set; + QString ip; + QString port; +}; +static ProxySetting ParseProxyString(const std::string& proxy); +static std::string ProxyString(bool is_set, QString ip, QString port); + static const QLatin1String fontchoice_str_app_font{"application_font"}; static const QLatin1String fontchoice_str_embedded{"embedded"}; static const QLatin1String fontchoice_str_best_system{"best_system"}; @@ -74,10 +161,9 @@ OptionsModel::FontChoice OptionsModel::FontChoiceFromString(const QString& s) } } -OptionsModel::OptionsModel(interfaces::Node& node, QObject *parent, bool resetSettings) : +OptionsModel::OptionsModel(interfaces::Node& node, QObject *parent) : QAbstractListModel(parent), m_node{node} { - Init(resetSettings); } void OptionsModel::addOverriddenOption(const std::string &option) @@ -86,10 +172,17 @@ void OptionsModel::addOverriddenOption(const std::string &option) } // Writes all missing QSettings with their default values -void OptionsModel::Init(bool resetSettings) +bool OptionsModel::Init(bilingual_str& error) { - if (resetSettings) - Reset(); + // Initialize display settings from stored settings. + m_prune_size_gb = PruneSizeGB(node().getPersistentSetting("prune")); + ProxySetting proxy = ParseProxyString(SettingToString(node().getPersistentSetting("proxy"), GetDefaultProxyAddress().toStdString())); + m_proxy_ip = proxy.ip; + m_proxy_port = proxy.port; + ProxySetting onion = ParseProxyString(SettingToString(node().getPersistentSetting("onion"), GetDefaultProxyAddress().toStdString())); + m_onion_ip = onion.ip; + m_onion_port = onion.port; + language = QString::fromStdString(SettingToString(node().getPersistentSetting("lang"), "")); checkAndMigrate(); @@ -245,46 +338,29 @@ void OptionsModel::Init(bool resetSettings) // These are shared with the core or have a command-line parameter // and we want command-line parameters to overwrite the GUI settings. - // + for (OptionID option : {DatabaseCache, ThreadsScriptVerif, SpendZeroConfChange, ExternalSignerPath, MapPortUPnP, + MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language}) { + std::string setting = SettingName(option); + if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting); + try { + getOption(option); + } catch (const std::exception& e) { + // This handles exceptions thrown by univalue that can happen if + // settings in settings.json don't have the expected types. + error.original = strprintf("Could not read setting \"%s\", %s.", setting, e.what()); + error.translated = tr("Could not read setting \"%1\", %2.").arg(QString::fromStdString(setting), e.what()).toStdString(); + return false; + } + } + // If setting doesn't exist create it with defaults. - // - // If gArgs.SoftSetArg() or m_node.softSetBoolArg() return false we were overridden - // by command-line and show this in the UI. // Main - if (!settings.contains("bPrune")) - settings.setValue("bPrune", false); - if (!settings.contains("nPruneSize")) - settings.setValue("nPruneSize", DEFAULT_PRUNE_TARGET_GB); - SetPruneEnabled(settings.value("bPrune").toBool()); - - if (!settings.contains("nDatabaseCache")) - settings.setValue("nDatabaseCache", (qint64)nDefaultDbCache); - if (!gArgs.SoftSetArg("-dbcache", settings.value("nDatabaseCache").toString().toStdString())) - addOverriddenOption("-dbcache"); - - if (!settings.contains("nThreadsScriptVerif")) - settings.setValue("nThreadsScriptVerif", DEFAULT_SCRIPTCHECK_THREADS); - if (!gArgs.SoftSetArg("-par", settings.value("nThreadsScriptVerif").toString().toStdString())) - addOverriddenOption("-par"); - if (!settings.contains("strDataDir")) settings.setValue("strDataDir", GUIUtil::getDefaultDataDirectory()); // Wallet #ifdef ENABLE_WALLET - if (!settings.contains("bSpendZeroConfChange")) - settings.setValue("bSpendZeroConfChange", true); - if (!gArgs.SoftSetBoolArg("-spendzeroconfchange", settings.value("bSpendZeroConfChange").toBool())) - addOverriddenOption("-spendzeroconfchange"); - - if (!settings.contains("external_signer_path")) - settings.setValue("external_signer_path", ""); - - if (!gArgs.SoftSetArg("-signer", settings.value("external_signer_path").toString().toStdString())) { - addOverriddenOption("-signer"); - } - if (!settings.contains("SubFeeFromAmount")) { settings.setValue("SubFeeFromAmount", false); } @@ -322,79 +398,7 @@ void OptionsModel::Init(bool resetSettings) addOverriddenOption("-coinjoindenomshardcap"); #endif - // Network - if (!settings.contains("fUseUPnP")) - settings.setValue("fUseUPnP", DEFAULT_UPNP); - if (!gArgs.SoftSetBoolArg("-upnp", settings.value("fUseUPnP").toBool())) - addOverriddenOption("-upnp"); - - if (!settings.contains("fUseNatpmp")) { - settings.setValue("fUseNatpmp", DEFAULT_NATPMP); - } - if (!gArgs.SoftSetBoolArg("-natpmp", settings.value("fUseNatpmp").toBool())) { - addOverriddenOption("-natpmp"); - } - - if (!settings.contains("fListen")) - settings.setValue("fListen", DEFAULT_LISTEN); - const bool listen{settings.value("fListen").toBool()}; - if (!gArgs.SoftSetBoolArg("-listen", listen)) { - addOverriddenOption("-listen"); - } else if (!listen) { - // We successfully set -listen=0, thus mimic the logic from InitParameterInteraction(): - // "parameter interaction: -listen=0 -> setting -listenonion=0". - // - // Both -listen and -listenonion default to true. - // - // The call order is: - // - // InitParameterInteraction() - // would set -listenonion=0 if it sees -listen=0, but for bitcoin-qt with - // fListen=false -listen is 1 at this point - // - // OptionsModel::Init() - // (this method) can flip -listen from 1 to 0 if fListen=false - // - // AppInitParameterInteraction() - // raises an error if -listen=0 and -listenonion=1 - gArgs.SoftSetBoolArg("-listenonion", false); - } - - if (!settings.contains("server")) { - settings.setValue("server", false); - } - if (!gArgs.SoftSetBoolArg("-server", settings.value("server").toBool())) { - addOverriddenOption("-server"); - } - - if (!settings.contains("fUseProxy")) - settings.setValue("fUseProxy", false); - if (!settings.contains("addrProxy")) - settings.setValue("addrProxy", GetDefaultProxyAddress()); - // Only try to set -proxy, if user has enabled fUseProxy - if ((settings.value("fUseProxy").toBool() && !gArgs.SoftSetArg("-proxy", settings.value("addrProxy").toString().toStdString()))) - addOverriddenOption("-proxy"); - else if(!settings.value("fUseProxy").toBool() && !gArgs.GetArg("-proxy", "").empty()) - addOverriddenOption("-proxy"); - - if (!settings.contains("fUseSeparateProxyTor")) - settings.setValue("fUseSeparateProxyTor", false); - if (!settings.contains("addrSeparateProxyTor")) - settings.setValue("addrSeparateProxyTor", GetDefaultProxyAddress()); - // Only try to set -onion, if user has enabled fUseSeparateProxyTor - if ((settings.value("fUseSeparateProxyTor").toBool() && !gArgs.SoftSetArg("-onion", settings.value("addrSeparateProxyTor").toString().toStdString()))) - addOverriddenOption("-onion"); - else if(!settings.value("fUseSeparateProxyTor").toBool() && !gArgs.GetArg("-onion", "").empty()) - addOverriddenOption("-onion"); - // Display - if (!settings.contains("language")) - settings.setValue("language", ""); - if (!gArgs.SoftSetArg("-lang", settings.value("language").toString().toStdString())) - addOverriddenOption("-lang"); - - language = settings.value("language").toString(); - if (settings.contains("FontForMoney")) { m_font_money = FontChoiceFromString(settings.value("FontForMoney").toString()); } else if (settings.contains("UseEmbeddedMonospacedFont")) { @@ -405,6 +409,8 @@ void OptionsModel::Init(bool resetSettings) } } Q_EMIT fontForMoneyChanged(getFontForMoney()); + + return true; } /** Helper function to copy contents from one QSettings to another. @@ -428,6 +434,9 @@ static void BackupSettings(const fs::path& filename, const QSettings& src) void OptionsModel::Reset() { + // Backup and reset settings.json + node().resetSettings(); + QSettings settings; // Backup old settings to chain-specific datadir for troubleshooting @@ -456,21 +465,15 @@ int OptionsModel::rowCount(const QModelIndex & parent) const return OptionIDRowCount; } -struct ProxySetting { - bool is_set; - QString ip; - QString port; -}; - -static ProxySetting GetProxySetting(QSettings &settings, const QString &name) +static ProxySetting ParseProxyString(const QString& proxy) { static const ProxySetting default_val = {false, DEFAULT_GUI_PROXY_HOST, QString("%1").arg(DEFAULT_GUI_PROXY_PORT)}; // Handle the case that the setting is not set at all - if (!settings.contains(name)) { + if (proxy.isEmpty()) { return default_val; } // contains IP at index 0 and port at index 1 - QStringList ip_port = GUIUtil::SplitSkipEmptyParts(settings.value(name).toString(), ":"); + QStringList ip_port = GUIUtil::SplitSkipEmptyParts(proxy, ":"); if (ip_port.size() == 2) { return {true, ip_port.at(0), ip_port.at(1)}; } else { // Invalid: return default @@ -478,9 +481,14 @@ static ProxySetting GetProxySetting(QSettings &settings, const QString &name) } } -static void SetProxySetting(QSettings &settings, const QString &name, const ProxySetting &ip_port) +static ProxySetting ParseProxyString(const std::string& proxy) +{ + return ParseProxyString(QString::fromStdString(proxy)); +} + +static std::string ProxyString(bool is_set, QString ip, QString port) { - settings.setValue(name, QString{ip_port.ip + QLatin1Char(':') + ip_port.port}); + return is_set ? QString(ip + ":" + port).toStdString() : ""; } static QString GetDefaultProxyAddress() @@ -488,37 +496,33 @@ static QString GetDefaultProxyAddress() return QString("%1:%2").arg(DEFAULT_GUI_PROXY_HOST).arg(DEFAULT_GUI_PROXY_PORT); } -void OptionsModel::SetPruneEnabled(bool prune, bool force) +void OptionsModel::SetPruneTargetGB(int prune_target_gb) { - QSettings settings; - settings.setValue("bPrune", prune); - const int64_t prune_target_mib = PruneGBtoMiB(settings.value("nPruneSize").toInt()); - std::string prune_val = prune ? ToString(prune_target_mib) : "0"; - if (force) { - gArgs.ForceSetArg("-prune", prune_val); - if (prune) { - gArgs.ForceSetArg("-disablegovernance", "1"); - gArgs.ForceSetArg("-txindex", "0"); - } - return; - } - if (!gArgs.SoftSetArg("-prune", prune_val)) { - addOverriddenOption("-prune"); - } - if (gArgs.GetIntArg("-prune", 0) > 0) { - gArgs.SoftSetBoolArg("-disablegovernance", true); - gArgs.SoftSetBoolArg("-txindex", false); + const util::SettingsValue cur_value = node().getPersistentSetting("prune"); + const util::SettingsValue new_value = PruneSetting(prune_target_gb > 0, prune_target_gb); + + m_prune_size_gb = prune_target_gb; + + // Force setting to take effect. It is still safe to change the value at + // this point because this function is only called after the intro screen is + // shown, before the node starts. + node().forceSetting("prune", new_value); + + // When pruning is enabled, force disable governance and txindex + if (prune_target_gb > 0) { + node().forceSetting("disablegovernance", "1"); + node().forceSetting("txindex", "0"); } -} -void OptionsModel::SetPruneTargetGB(int prune_target_gb, bool force) -{ - const bool prune = prune_target_gb > 0; - if (prune) { - QSettings settings; - settings.setValue("nPruneSize", prune_target_gb); + // Update settings.json if value configured in intro screen is different + // from saved value. Avoid writing settings.json if bitcoin.conf value + // doesn't need to be overridden. + if (PruneEnabled(cur_value) != PruneEnabled(new_value) || + PruneSizeGB(cur_value) != PruneSizeGB(new_value)) { + // Call UpdateRwSetting() instead of setOption() to avoid setting + // RestartRequired flag + UpdateRwSetting(node(), Prune, new_value); } - SetPruneEnabled(prune, force); } QFont OptionsModel::getFontForChoice(const FontChoice& fc) @@ -575,6 +579,8 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in QVariant OptionsModel::getOption(OptionID option) const { + auto setting = [&]{ return node().getPersistentSetting(SettingName(option)); }; + QSettings settings; switch (option) { case StartAtStartup: @@ -585,13 +591,13 @@ QVariant OptionsModel::getOption(OptionID option) const return fMinimizeToTray; case MapPortUPnP: #ifdef USE_UPNP - return settings.value("fUseUPnP"); + return SettingToBool(setting(), DEFAULT_UPNP); #else return false; #endif // USE_UPNP case MapPortNatpmp: #ifdef USE_NATPMP - return settings.value("fUseNatpmp"); + return SettingToBool(setting(), DEFAULT_NATPMP); #else return false; #endif // USE_NATPMP @@ -600,25 +606,25 @@ QVariant OptionsModel::getOption(OptionID option) const // default proxy case ProxyUse: - return settings.value("fUseProxy", false); + return ParseProxyString(SettingToString(setting(), "")).is_set; case ProxyIP: - return GetProxySetting(settings, "addrProxy").ip; + return m_proxy_ip; case ProxyPort: - return GetProxySetting(settings, "addrProxy").port; + return m_proxy_port; // separate Tor proxy case ProxyUseTor: - return settings.value("fUseSeparateProxyTor", false); + return ParseProxyString(SettingToString(setting(), "")).is_set; case ProxyIPTor: - return GetProxySetting(settings, "addrSeparateProxyTor").ip; + return m_onion_ip; case ProxyPortTor: - return GetProxySetting(settings, "addrSeparateProxyTor").port; + return m_onion_port; #ifdef ENABLE_WALLET case SpendZeroConfChange: - return settings.value("bSpendZeroConfChange"); + return SettingToBool(setting(), wallet::DEFAULT_SPEND_ZEROCONF_CHANGE); case ExternalSignerPath: - return settings.value("external_signer_path"); + return QString::fromStdString(SettingToString(setting(), "")); case SubFeeFromAmount: return m_sub_fee_from_amount; case ShowMasternodesTab: @@ -681,7 +687,7 @@ QVariant OptionsModel::getOption(OptionID option) const return nIndex; } case Language: - return settings.value("language"); + return QString::fromStdString(SettingToString(setting(), "")); case FontForMoney: return QVariant::fromValue(m_font_money); #ifdef ENABLE_WALLET @@ -693,17 +699,17 @@ QVariant OptionsModel::getOption(OptionID option) const return fKeepChangeAddress; #endif // ENABLE_WALLET case Prune: - return settings.value("bPrune"); + return PruneEnabled(setting()); case PruneSize: - return settings.value("nPruneSize"); + return m_prune_size_gb; case DatabaseCache: - return settings.value("nDatabaseCache"); + return qlonglong(SettingToInt(setting(), nDefaultDbCache)); case ThreadsScriptVerif: - return settings.value("nThreadsScriptVerif"); + return qlonglong(SettingToInt(setting(), DEFAULT_SCRIPTCHECK_THREADS)); case Listen: - return settings.value("fListen"); + return SettingToBool(setting(), DEFAULT_LISTEN); case Server: - return settings.value("server"); + return SettingToBool(setting(), false); default: return QVariant(); } @@ -711,6 +717,9 @@ QVariant OptionsModel::getOption(OptionID option) const bool OptionsModel::setOption(OptionID option, const QVariant& value) { + auto changed = [&] { return value.isValid() && value != getOption(option); }; + auto update = [&](const util::SettingsValue& value) { return UpdateRwSetting(node(), option, value); }; + bool successful = true; /* set to false on parse error */ QSettings settings; switch (option) { @@ -727,10 +736,16 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) settings.setValue("fMinimizeToTray", fMinimizeToTray); break; case MapPortUPnP: // core option - can be changed on-the-fly - settings.setValue("fUseUPnP", value.toBool()); + if (changed()) { + update(value.toBool()); + node().mapPort(value.toBool(), getOption(MapPortNatpmp).toBool()); + } break; case MapPortNatpmp: // core option - can be changed on-the-fly - settings.setValue("fUseNatpmp", value.toBool()); + if (changed()) { + update(value.toBool()); + node().mapPort(getOption(MapPortUPnP).toBool(), value.toBool()); + } break; case MinimizeOnClose: fMinimizeOnClose = value.toBool(); @@ -739,66 +754,66 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) // default proxy case ProxyUse: - if (settings.value("fUseProxy") != value) { - settings.setValue("fUseProxy", value.toBool()); + if (changed()) { + update(ProxyString(value.toBool(), m_proxy_ip, m_proxy_port)); setRestartRequired(true); } break; - case ProxyIP: { - auto ip_port = GetProxySetting(settings, "addrProxy"); - if (!ip_port.is_set || ip_port.ip != value.toString()) { - ip_port.ip = value.toString(); - SetProxySetting(settings, "addrProxy", ip_port); - setRestartRequired(true); + case ProxyIP: + if (changed()) { + m_proxy_ip = value.toString(); + if (getOption(ProxyUse).toBool()) { + update(ProxyString(true, m_proxy_ip, m_proxy_port)); + setRestartRequired(true); + } } - } - break; - case ProxyPort: { - auto ip_port = GetProxySetting(settings, "addrProxy"); - if (!ip_port.is_set || ip_port.port != value.toString()) { - ip_port.port = value.toString(); - SetProxySetting(settings, "addrProxy", ip_port); - setRestartRequired(true); + break; + case ProxyPort: + if (changed()) { + m_proxy_port = value.toString(); + if (getOption(ProxyUse).toBool()) { + update(ProxyString(true, m_proxy_ip, m_proxy_port)); + setRestartRequired(true); + } } - } - break; + break; // separate Tor proxy case ProxyUseTor: - if (settings.value("fUseSeparateProxyTor") != value) { - settings.setValue("fUseSeparateProxyTor", value.toBool()); + if (changed()) { + update(ProxyString(value.toBool(), m_onion_ip, m_onion_port)); setRestartRequired(true); } break; - case ProxyIPTor: { - auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); - if (!ip_port.is_set || ip_port.ip != value.toString()) { - ip_port.ip = value.toString(); - SetProxySetting(settings, "addrSeparateProxyTor", ip_port); - setRestartRequired(true); + case ProxyIPTor: + if (changed()) { + m_onion_ip = value.toString(); + if (getOption(ProxyUseTor).toBool()) { + update(ProxyString(true, m_onion_ip, m_onion_port)); + setRestartRequired(true); + } } - } - break; - case ProxyPortTor: { - auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); - if (!ip_port.is_set || ip_port.port != value.toString()) { - ip_port.port = value.toString(); - SetProxySetting(settings, "addrSeparateProxyTor", ip_port); - setRestartRequired(true); + break; + case ProxyPortTor: + if (changed()) { + m_onion_port = value.toString(); + if (getOption(ProxyUseTor).toBool()) { + update(ProxyString(true, m_onion_ip, m_onion_port)); + setRestartRequired(true); + } } - } - break; + break; #ifdef ENABLE_WALLET case SpendZeroConfChange: - if (settings.value("bSpendZeroConfChange") != value) { - settings.setValue("bSpendZeroConfChange", value); + if (changed()) { + update(value.toBool()); setRestartRequired(true); } break; case ExternalSignerPath: - if (settings.value("external_signer_path") != value.toString()) { - settings.setValue("external_signer_path", value.toString()); + if (changed()) { + update(value.toString().toStdString()); setRestartRequired(true); } break; @@ -927,8 +942,8 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) break; } case Language: - if (settings.value("language") != value) { - settings.setValue("language", value); + if (changed()) { + update(value.toString().toStdString()); setRestartRequired(true); } break; @@ -959,38 +974,36 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) break; #endif // ENABLE_WALLET case Prune: - if (settings.value("bPrune") != value) { - settings.setValue("bPrune", value); + if (changed()) { + update(PruneSetting(value.toBool(), m_prune_size_gb)); setRestartRequired(true); } break; case PruneSize: - if (settings.value("nPruneSize") != value) { - settings.setValue("nPruneSize", value); - setRestartRequired(true); + if (changed()) { + m_prune_size_gb = ParsePruneSizeGB(value); + if (getOption(Prune).toBool()) { + update(PruneSetting(true, m_prune_size_gb)); + setRestartRequired(true); + } } break; case DatabaseCache: - if (settings.value("nDatabaseCache") != value) { - settings.setValue("nDatabaseCache", value); + if (changed()) { + update(static_cast(value.toLongLong())); setRestartRequired(true); } break; case ThreadsScriptVerif: - if (settings.value("nThreadsScriptVerif") != value) { - settings.setValue("nThreadsScriptVerif", value); + if (changed()) { + update(static_cast(value.toLongLong())); setRestartRequired(true); } break; case Listen: - if (settings.value("fListen") != value) { - settings.setValue("fListen", value); - setRestartRequired(true); - } - break; case Server: - if (settings.value("server") != value) { - settings.setValue("server", value); + if (changed()) { + update(value.toBool()); setRestartRequired(true); } break; @@ -1081,4 +1094,49 @@ void OptionsModel::checkAndMigrate() if (!GUIUtil::isValidTheme(strActiveTheme)) { settings.setValue("theme", GUIUtil::getDefaultTheme()); } + + // Migrate and delete legacy GUI settings that have now moved to /settings.json. + auto migrate_setting = [&](OptionID option, const QString& qt_name) { + if (!settings.contains(qt_name)) return; + QVariant value = settings.value(qt_name); + if (node().getPersistentSetting(SettingName(option)).isNull()) { + if (option == ProxyIP) { + ProxySetting parsed = ParseProxyString(value.toString()); + setOption(ProxyIP, parsed.ip); + setOption(ProxyPort, parsed.port); + } else if (option == ProxyIPTor) { + ProxySetting parsed = ParseProxyString(value.toString()); + setOption(ProxyIPTor, parsed.ip); + setOption(ProxyPortTor, parsed.port); + } else { + setOption(option, value); + } + } + settings.remove(qt_name); + }; + + migrate_setting(DatabaseCache, "nDatabaseCache"); + migrate_setting(ThreadsScriptVerif, "nThreadsScriptVerif"); +#ifdef ENABLE_WALLET + migrate_setting(SpendZeroConfChange, "bSpendZeroConfChange"); + migrate_setting(ExternalSignerPath, "external_signer_path"); +#endif + migrate_setting(MapPortUPnP, "fUseUPnP"); + migrate_setting(MapPortNatpmp, "fUseNatpmp"); + migrate_setting(Listen, "fListen"); + migrate_setting(Server, "server"); + migrate_setting(PruneSize, "nPruneSize"); + migrate_setting(Prune, "bPrune"); + migrate_setting(ProxyIP, "addrProxy"); + migrate_setting(ProxyUse, "fUseProxy"); + migrate_setting(ProxyIPTor, "addrSeparateProxyTor"); + migrate_setting(ProxyUseTor, "fUseSeparateProxyTor"); + migrate_setting(Language, "language"); + + // In case migrating QSettings caused any settings value to change, rerun + // parameter interaction code to update other settings. This is particularly + // important for the -listen setting, which should cause -listenonion, -upnp, + // and other settings to default to false if it was set to false. + // (https://github.com/bitcoin-core/gui/issues/567). + node().initParameterInteraction(); } diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 4e8227bea1aa..a12f2a1827b9 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -16,6 +16,7 @@ #include #include +struct bilingual_str; namespace interfaces { class Node; } @@ -44,7 +45,7 @@ class OptionsModel : public QAbstractListModel Q_OBJECT public: - explicit OptionsModel(interfaces::Node& node, QObject *parent = nullptr, bool resetSettings = false); + explicit OptionsModel(interfaces::Node& node, QObject *parent = nullptr); enum OptionID { StartAtStartup, // bool @@ -105,7 +106,7 @@ class OptionsModel : public QAbstractListModel static inline const FontChoice UseBestSystemFont{FontChoiceAbstract::BestSystemFont}; static QFont getFontForChoice(const FontChoice& fc); - void Init(bool resetSettings = false); + bool Init(bilingual_str& error); void Reset(); int rowCount(const QModelIndex & parent = QModelIndex()) const override; @@ -133,8 +134,7 @@ class OptionsModel : public QAbstractListModel void emitCoinJoinEnabledChanged(); /* Explicit setters */ - void SetPruneEnabled(bool prune, bool force = false); - void SetPruneTargetGB(int prune_target_gb, bool force = false); + void SetPruneTargetGB(int prune_target_gb); /* Restart flag helper */ void setRestartRequired(bool fRequired); @@ -158,6 +158,16 @@ class OptionsModel : public QAbstractListModel bool m_enable_psbt_controls; bool fKeepChangeAddress; bool fShowAdvancedCJUI; + + //! In-memory settings for display. These are stored persistently by the + //! bitcoin node but it's also nice to store them in memory to prevent them + //! getting cleared when enable/disable toggles are used in the GUI. + int m_prune_size_gb; + QString m_proxy_ip; + QString m_proxy_port; + QString m_onion_ip; + QString m_onion_port; + /* settings that were overridden by command-line */ QString strOverriddenByCommandLine; @@ -169,6 +179,7 @@ class OptionsModel : public QAbstractListModel // Check settings version and upgrade default values if required void checkAndMigrate(); + Q_SIGNALS: void displayUnitChanged(BitcoinUnit unit); void coinJoinEnabledChanged(); diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 0db094962b7c..e8216d5531b6 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -123,6 +123,8 @@ void TestAddAddressesToSendBook(interfaces::Node& node) // Initialize relevant QT models. OptionsModel optionsModel(node); + bilingual_str error; + QVERIFY(optionsModel.Init(error)); ClientModel clientModel(node, &optionsModel); WalletContext& context = *node.walletLoader().context(); AddWallet(context, wallet); diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index 1104192ed8bd..9b97b2bad89e 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -72,16 +72,11 @@ void AppTests::appTests() } #endif - fs::create_directories([] { - BasicTestingSetup test{CBaseChainParams::REGTEST}; // Create a temp data directory to backup the gui settings to - return gArgs.GetDataDirNet() / "blocks"; - }()); - qRegisterMetaType("interfaces::BlockAndHeaderTipInfo"); m_app.parameterSetup(); GUIUtil::loadFonts(); GUIUtil::setApplicationFont(); - m_app.createOptionsModel(true /* reset settings */); + QVERIFY(m_app.createOptionsModel(true /* reset settings */)); QScopedPointer style( NetworkStyle::instantiate(Params().NetworkIDString())); m_app.createWindow(style.data()); diff --git a/src/qt/test/optiontests.cpp b/src/qt/test/optiontests.cpp index c3cd78553e99..17ffeb220b69 100644 --- a/src/qt/test/optiontests.cpp +++ b/src/qt/test/optiontests.cpp @@ -14,6 +14,63 @@ #include +#include + +OptionTests::OptionTests(interfaces::Node& node) : m_node(node) +{ + gArgs.LockSettings([&](util::Settings& s) { m_previous_settings = s; }); +} + +void OptionTests::init() +{ + // reset args + gArgs.LockSettings([&](util::Settings& s) { s = m_previous_settings; }); + gArgs.ClearPathCache(); +} + +void OptionTests::migrateSettings() +{ + // Set legacy QSettings and verify that they get cleared and migrated to + // settings.json + QSettings settings; + settings.setValue("nDatabaseCache", 600); + settings.setValue("nThreadsScriptVerif", 12); + settings.setValue("fUseUPnP", false); + settings.setValue("fListen", false); + settings.setValue("bPrune", true); + settings.setValue("nPruneSize", 3); + settings.setValue("fUseProxy", true); + settings.setValue("addrProxy", "proxy:123"); + settings.setValue("fUseSeparateProxyTor", true); + settings.setValue("addrSeparateProxyTor", "onion:234"); + + settings.sync(); + + OptionsModel options{m_node}; + bilingual_str error; + QVERIFY(options.Init(error)); + QVERIFY(!settings.contains("nDatabaseCache")); + QVERIFY(!settings.contains("nThreadsScriptVerif")); + QVERIFY(!settings.contains("fUseUPnP")); + QVERIFY(!settings.contains("fListen")); + QVERIFY(!settings.contains("bPrune")); + QVERIFY(!settings.contains("nPruneSize")); + QVERIFY(!settings.contains("fUseProxy")); + QVERIFY(!settings.contains("addrProxy")); + QVERIFY(!settings.contains("fUseSeparateProxyTor")); + QVERIFY(!settings.contains("addrSeparateProxyTor")); + + std::ifstream file(gArgs.GetDataDirNet() / "settings.json"); + QCOMPARE(std::string(std::istreambuf_iterator(file), std::istreambuf_iterator()).c_str(), "{\n" + " \"dbcache\": \"600\",\n" + " \"listen\": false,\n" + " \"onion\": \"onion:234\",\n" + " \"par\": \"12\",\n" + " \"proxy\": \"proxy:123\",\n" + " \"prune\": \"2861\"\n" + "}\n"); +} + void OptionTests::integerGetArgBug() { // Test regression https://github.com/bitcoin/bitcoin/issues/24457. Ensure @@ -24,7 +81,8 @@ void OptionTests::integerGetArgBug() settings.rw_settings["prune"] = 3814; }); gArgs.WriteSettingsFile(); - OptionsModel{m_node}; + bilingual_str error; + QVERIFY(OptionsModel{m_node}.Init(error)); gArgs.LockSettings([&](util::Settings& settings) { settings.rw_settings.erase("prune"); }); @@ -37,8 +95,6 @@ void OptionTests::parametersInteraction() // It was fixed via https://github.com/bitcoin-core/gui/pull/568. // With fListen=false in ~/.config/Bitcoin/Bitcoin-Qt.conf and all else left as default, // bitcoin-qt should set both -listen and -listenonion to false and start successfully. - gArgs.ClearPathCache(); - gArgs.LockSettings([&](util::Settings& s) { s.forced_settings.erase("listen"); s.forced_settings.erase("listenonion"); @@ -49,7 +105,8 @@ void OptionTests::parametersInteraction() QSettings settings; settings.setValue("fListen", false); - OptionsModel{m_node}; + bilingual_str error; + QVERIFY(OptionsModel{m_node}.Init(error)); const bool expected{false}; diff --git a/src/qt/test/optiontests.h b/src/qt/test/optiontests.h index 78061d8fe870..57ec8bd0f2ab 100644 --- a/src/qt/test/optiontests.h +++ b/src/qt/test/optiontests.h @@ -6,6 +6,8 @@ #define BITCOIN_QT_TEST_OPTIONTESTS_H #include +#include +#include #include @@ -13,15 +15,18 @@ class OptionTests : public QObject { Q_OBJECT public: - explicit OptionTests(interfaces::Node& node) : m_node(node) {} + explicit OptionTests(interfaces::Node& node); private Q_SLOTS: + void init(); // called before each test function execution. + void migrateSettings(); void integerGetArgBug(); void parametersInteraction(); void extractFilter(); private: interfaces::Node& m_node; + util::Settings m_previous_settings; }; #endif // BITCOIN_QT_TEST_OPTIONTESTS_H diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index a043b24ffe9c..08018e007c4f 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -58,9 +58,10 @@ int main(int argc, char* argv[]) // regtest params. // // All tests must use their own testing setup (if needed). - { + fs::create_directories([] { BasicTestingSetup dummy{CBaseChainParams::REGTEST}; - } + return gArgs.GetDataDirNet() / "blocks"; + }()); std::unique_ptr init = interfaces::MakeGuiInit(argc, argv); gArgs.ForceSetArg("-listen", "0"); diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 3650e3a8a2c5..327d7eacc07b 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -154,6 +154,8 @@ void TestGUI(interfaces::Node& node) SendCoinsDialog sendCoinsDialog; TransactionView transactionView; OptionsModel optionsModel(node); + bilingual_str error; + QVERIFY(optionsModel.Init(error)); ClientModel clientModel(node, &optionsModel); WalletModel walletModel(interfaces::MakeWallet(context, wallet), clientModel); sendCoinsDialog.setModel(&walletModel); From eed631a72a0a81eef270575596e228a5bd7ed37b Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 25 Aug 2025 21:35:27 +0000 Subject: [PATCH 06/20] refactor: spin-off list of option IDs that require string workaround We have a lot more configurable options and the current approach will get unwieldy as it grows --- src/qt/optionsmodel.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 9aa2834641bd..51d2639b2f3f 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -64,14 +64,23 @@ static const char* SettingName(OptionsModel::OptionID option) } } +static bool RequiresNumWorkaround(OptionsModel::OptionID option) +{ + switch (option) { + case OptionsModel::DatabaseCache: + case OptionsModel::Prune: + case OptionsModel::PruneSize: + case OptionsModel::ThreadsScriptVerif: + return true; + default: + return false; + } +} + /** Call node.updateRwSetting() with Bitcoin 22.x workaround. */ static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID option, const util::SettingsValue& value) { - if (value.isNum() && - (option == OptionsModel::DatabaseCache || - option == OptionsModel::ThreadsScriptVerif || - option == OptionsModel::Prune || - option == OptionsModel::PruneSize)) { + if (value.isNum() && RequiresNumWorkaround(option)) { // Write certain old settings as strings, even though they are numbers, // because Bitcoin 22.x releases try to read these specific settings as // strings in addOverriddenOption() calls at startup, triggering From 44833d9568f8355d29d8e2d21f88797daf8dfd8d Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 26 Aug 2025 08:11:25 +0000 Subject: [PATCH 07/20] qt: migrate `-coinjoinsessions` setting from QSettings to settings.json --- src/qt/optionsmodel.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 51d2639b2f3f..124faefc3ade 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -60,6 +60,8 @@ static const char* SettingName(OptionsModel::OptionID option) case OptionsModel::ProxyPortTor: return "onion"; case OptionsModel::ProxyUseTor: return "onion"; case OptionsModel::Language: return "lang"; + //! Dash + case OptionsModel::CoinJoinSessions: return "coinjoinsessions"; default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option)); } } @@ -67,6 +69,7 @@ static const char* SettingName(OptionsModel::OptionID option) static bool RequiresNumWorkaround(OptionsModel::OptionID option) { switch (option) { + case OptionsModel::CoinJoinSessions: case OptionsModel::DatabaseCache: case OptionsModel::Prune: case OptionsModel::PruneSize: @@ -348,7 +351,7 @@ bool OptionsModel::Init(bilingual_str& error) // These are shared with the core or have a command-line parameter // and we want command-line parameters to overwrite the GUI settings. for (OptionID option : {DatabaseCache, ThreadsScriptVerif, SpendZeroConfChange, ExternalSignerPath, MapPortUPnP, - MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language}) { + MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language, CoinJoinSessions}) { std::string setting = SettingName(option); if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting); try { @@ -376,11 +379,6 @@ bool OptionsModel::Init(bilingual_str& error) m_sub_fee_from_amount = settings.value("SubFeeFromAmount", false).toBool(); // CoinJoin - if (!settings.contains("nCoinJoinSessions")) - settings.setValue("nCoinJoinSessions", DEFAULT_COINJOIN_SESSIONS); - if (!gArgs.SoftSetArg("-coinjoinsessions", settings.value("nCoinJoinSessions").toString().toStdString())) - addOverriddenOption("-coinjoinsessions"); - if (!settings.contains("nCoinJoinRounds")) settings.setValue("nCoinJoinRounds", DEFAULT_COINJOIN_ROUNDS); if (!gArgs.SoftSetArg("-coinjoinrounds", settings.value("nCoinJoinRounds").toString().toStdString())) @@ -649,7 +647,7 @@ QVariant OptionsModel::getOption(OptionID option) const case LowKeysWarning: return settings.value("fLowKeysWarning"); case CoinJoinSessions: - return settings.value("nCoinJoinSessions"); + return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_SESSIONS)); case CoinJoinRounds: return settings.value("nCoinJoinRounds"); case CoinJoinAmount: @@ -862,9 +860,9 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) settings.setValue("fLowKeysWarning", value); break; case CoinJoinSessions: - if (settings.value("nCoinJoinSessions") != value) { + if (changed()) { node().coinJoinOptions().setSessions(value.toInt()); - settings.setValue("nCoinJoinSessions", node().coinJoinOptions().getSessions()); + update(value.toInt()); Q_EMIT coinJoinRoundsChanged(); } break; @@ -1142,6 +1140,11 @@ void OptionsModel::checkAndMigrate() migrate_setting(ProxyUseTor, "fUseSeparateProxyTor"); migrate_setting(Language, "language"); + //! Dash +#ifdef ENABLE_WALLET + migrate_setting(CoinJoinSessions, "nCoinJoinSessions"); +#endif + // In case migrating QSettings caused any settings value to change, rerun // parameter interaction code to update other settings. This is particularly // important for the -listen setting, which should cause -listenonion, -upnp, From 2174fc67419082654d6f2781a841661e024a5bcb Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 26 Aug 2025 08:11:45 +0000 Subject: [PATCH 08/20] qt: migrate `-coinjoinrounds` setting from QSettings to settings.json --- src/qt/optionsmodel.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 124faefc3ade..687e35352e52 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -61,6 +61,7 @@ static const char* SettingName(OptionsModel::OptionID option) case OptionsModel::ProxyUseTor: return "onion"; case OptionsModel::Language: return "lang"; //! Dash + case OptionsModel::CoinJoinRounds: return "coinjoinrounds"; case OptionsModel::CoinJoinSessions: return "coinjoinsessions"; default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option)); } @@ -69,6 +70,7 @@ static const char* SettingName(OptionsModel::OptionID option) static bool RequiresNumWorkaround(OptionsModel::OptionID option) { switch (option) { + case OptionsModel::CoinJoinRounds: case OptionsModel::CoinJoinSessions: case OptionsModel::DatabaseCache: case OptionsModel::Prune: @@ -351,7 +353,8 @@ bool OptionsModel::Init(bilingual_str& error) // These are shared with the core or have a command-line parameter // and we want command-line parameters to overwrite the GUI settings. for (OptionID option : {DatabaseCache, ThreadsScriptVerif, SpendZeroConfChange, ExternalSignerPath, MapPortUPnP, - MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language, CoinJoinSessions}) { + MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language, CoinJoinRounds, + CoinJoinSessions}) { std::string setting = SettingName(option); if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting); try { @@ -379,11 +382,6 @@ bool OptionsModel::Init(bilingual_str& error) m_sub_fee_from_amount = settings.value("SubFeeFromAmount", false).toBool(); // CoinJoin - if (!settings.contains("nCoinJoinRounds")) - settings.setValue("nCoinJoinRounds", DEFAULT_COINJOIN_ROUNDS); - if (!gArgs.SoftSetArg("-coinjoinrounds", settings.value("nCoinJoinRounds").toString().toStdString())) - addOverriddenOption("-coinjoinrounds"); - if (!settings.contains("nCoinJoinAmount")) settings.setValue("nCoinJoinAmount", DEFAULT_COINJOIN_AMOUNT); if (!gArgs.SoftSetArg("-coinjoinamount", settings.value("nCoinJoinAmount").toString().toStdString())) @@ -649,7 +647,7 @@ QVariant OptionsModel::getOption(OptionID option) const case CoinJoinSessions: return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_SESSIONS)); case CoinJoinRounds: - return settings.value("nCoinJoinRounds"); + return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_ROUNDS)); case CoinJoinAmount: return settings.value("nCoinJoinAmount"); case CoinJoinDenomsGoal: @@ -867,10 +865,9 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) } break; case CoinJoinRounds: - if (settings.value("nCoinJoinRounds") != value) - { + if (changed()) { node().coinJoinOptions().setRounds(value.toInt()); - settings.setValue("nCoinJoinRounds", node().coinJoinOptions().getRounds()); + update(value.toInt()); Q_EMIT coinJoinRoundsChanged(); } break; @@ -1142,6 +1139,7 @@ void OptionsModel::checkAndMigrate() //! Dash #ifdef ENABLE_WALLET + migrate_setting(CoinJoinRounds, "nCoinJoinRounds"); migrate_setting(CoinJoinSessions, "nCoinJoinSessions"); #endif From fb97375d87ce98ee41b1e50b1185ae91c82621e1 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 26 Aug 2025 08:12:00 +0000 Subject: [PATCH 09/20] qt: migrate `-coinjoinamount` setting from QSettings to settings.json --- src/qt/optionsmodel.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 687e35352e52..d6dea6ad75e7 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -61,6 +61,7 @@ static const char* SettingName(OptionsModel::OptionID option) case OptionsModel::ProxyUseTor: return "onion"; case OptionsModel::Language: return "lang"; //! Dash + case OptionsModel::CoinJoinAmount: return "coinjoinamount"; case OptionsModel::CoinJoinRounds: return "coinjoinrounds"; case OptionsModel::CoinJoinSessions: return "coinjoinsessions"; default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option)); @@ -70,6 +71,7 @@ static const char* SettingName(OptionsModel::OptionID option) static bool RequiresNumWorkaround(OptionsModel::OptionID option) { switch (option) { + case OptionsModel::CoinJoinAmount: case OptionsModel::CoinJoinRounds: case OptionsModel::CoinJoinSessions: case OptionsModel::DatabaseCache: @@ -353,8 +355,8 @@ bool OptionsModel::Init(bilingual_str& error) // These are shared with the core or have a command-line parameter // and we want command-line parameters to overwrite the GUI settings. for (OptionID option : {DatabaseCache, ThreadsScriptVerif, SpendZeroConfChange, ExternalSignerPath, MapPortUPnP, - MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language, CoinJoinRounds, - CoinJoinSessions}) { + MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language, CoinJoinAmount, + CoinJoinRounds, CoinJoinSessions}) { std::string setting = SettingName(option); if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting); try { @@ -382,11 +384,6 @@ bool OptionsModel::Init(bilingual_str& error) m_sub_fee_from_amount = settings.value("SubFeeFromAmount", false).toBool(); // CoinJoin - if (!settings.contains("nCoinJoinAmount")) - settings.setValue("nCoinJoinAmount", DEFAULT_COINJOIN_AMOUNT); - if (!gArgs.SoftSetArg("-coinjoinamount", settings.value("nCoinJoinAmount").toString().toStdString())) - addOverriddenOption("-coinjoinamount"); - if (!settings.contains("fCoinJoinMultiSession")) settings.setValue("fCoinJoinMultiSession", DEFAULT_COINJOIN_MULTISESSION); if (!gArgs.SoftSetBoolArg("-coinjoinmultisession", settings.value("fCoinJoinMultiSession").toBool())) @@ -649,7 +646,7 @@ QVariant OptionsModel::getOption(OptionID option) const case CoinJoinRounds: return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_ROUNDS)); case CoinJoinAmount: - return settings.value("nCoinJoinAmount"); + return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_AMOUNT)); case CoinJoinDenomsGoal: return settings.value("nCoinJoinDenomsGoal"); case CoinJoinDenomsHardCap: @@ -872,10 +869,9 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) } break; case CoinJoinAmount: - if (settings.value("nCoinJoinAmount") != value) - { + if (changed()) { node().coinJoinOptions().setAmount(value.toInt()); - settings.setValue("nCoinJoinAmount", node().coinJoinOptions().getAmount()); + update(value.toInt()); Q_EMIT coinJoinAmountChanged(); } break; @@ -1139,6 +1135,7 @@ void OptionsModel::checkAndMigrate() //! Dash #ifdef ENABLE_WALLET + migrate_setting(CoinJoinAmount, "nCoinJoinAmount"); migrate_setting(CoinJoinRounds, "nCoinJoinRounds"); migrate_setting(CoinJoinSessions, "nCoinJoinSessions"); #endif From ea60d7998e242c1eb93b14bbd265ba1f89d6c734 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 25 Aug 2025 21:49:23 +0000 Subject: [PATCH 10/20] qt: migrate `-coinjoinmultisession` setting from QSettings to settings.json --- src/qt/optionsmodel.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index d6dea6ad75e7..2e1be87b5bd3 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -62,6 +62,7 @@ static const char* SettingName(OptionsModel::OptionID option) case OptionsModel::Language: return "lang"; //! Dash case OptionsModel::CoinJoinAmount: return "coinjoinamount"; + case OptionsModel::CoinJoinMultiSession: return "coinjoinmultisession"; case OptionsModel::CoinJoinRounds: return "coinjoinrounds"; case OptionsModel::CoinJoinSessions: return "coinjoinsessions"; default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option)); @@ -356,7 +357,7 @@ bool OptionsModel::Init(bilingual_str& error) // and we want command-line parameters to overwrite the GUI settings. for (OptionID option : {DatabaseCache, ThreadsScriptVerif, SpendZeroConfChange, ExternalSignerPath, MapPortUPnP, MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language, CoinJoinAmount, - CoinJoinRounds, CoinJoinSessions}) { + CoinJoinMultiSession, CoinJoinRounds, CoinJoinSessions}) { std::string setting = SettingName(option); if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting); try { @@ -384,11 +385,6 @@ bool OptionsModel::Init(bilingual_str& error) m_sub_fee_from_amount = settings.value("SubFeeFromAmount", false).toBool(); // CoinJoin - if (!settings.contains("fCoinJoinMultiSession")) - settings.setValue("fCoinJoinMultiSession", DEFAULT_COINJOIN_MULTISESSION); - if (!gArgs.SoftSetBoolArg("-coinjoinmultisession", settings.value("fCoinJoinMultiSession").toBool())) - addOverriddenOption("-coinjoinmultisession"); - if (!settings.contains("nCoinJoinDenomsGoal")) settings.setValue("nCoinJoinDenomsGoal", DEFAULT_COINJOIN_DENOMS_GOAL); if (!gArgs.SoftSetArg("-coinjoindenomsgoal", settings.value("nCoinJoinDenomsGoal").toString().toStdString())) @@ -652,7 +648,7 @@ QVariant OptionsModel::getOption(OptionID option) const case CoinJoinDenomsHardCap: return settings.value("nCoinJoinDenomsHardCap"); case CoinJoinMultiSession: - return settings.value("fCoinJoinMultiSession"); + return SettingToBool(setting(), DEFAULT_COINJOIN_MULTISESSION); #endif case DisplayUnit: return QVariant::fromValue(m_display_bitcoin_unit); @@ -888,10 +884,9 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) } break; case CoinJoinMultiSession: - if (settings.value("fCoinJoinMultiSession") != value) - { + if (changed()) { node().coinJoinOptions().setMultiSessionEnabled(value.toBool()); - settings.setValue("fCoinJoinMultiSession", node().coinJoinOptions().isMultiSessionEnabled()); + update(value.toBool()); } break; #endif @@ -1136,6 +1131,7 @@ void OptionsModel::checkAndMigrate() //! Dash #ifdef ENABLE_WALLET migrate_setting(CoinJoinAmount, "nCoinJoinAmount"); + migrate_setting(CoinJoinMultiSession, "fCoinJoinMultiSession"); migrate_setting(CoinJoinRounds, "nCoinJoinRounds"); migrate_setting(CoinJoinSessions, "nCoinJoinSessions"); #endif From 4f744c24bded9905da4856407e01d988486e6126 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 26 Aug 2025 08:12:26 +0000 Subject: [PATCH 11/20] qt: migrate `-coinjoindenomsgoal` setting from QSettings to settings.json --- src/qt/optionsmodel.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 2e1be87b5bd3..c281016106b7 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -62,6 +62,7 @@ static const char* SettingName(OptionsModel::OptionID option) case OptionsModel::Language: return "lang"; //! Dash case OptionsModel::CoinJoinAmount: return "coinjoinamount"; + case OptionsModel::CoinJoinDenomsGoal: return "coinjoindenomsgoal"; case OptionsModel::CoinJoinMultiSession: return "coinjoinmultisession"; case OptionsModel::CoinJoinRounds: return "coinjoinrounds"; case OptionsModel::CoinJoinSessions: return "coinjoinsessions"; @@ -73,6 +74,7 @@ static bool RequiresNumWorkaround(OptionsModel::OptionID option) { switch (option) { case OptionsModel::CoinJoinAmount: + case OptionsModel::CoinJoinDenomsGoal: case OptionsModel::CoinJoinRounds: case OptionsModel::CoinJoinSessions: case OptionsModel::DatabaseCache: @@ -357,7 +359,7 @@ bool OptionsModel::Init(bilingual_str& error) // and we want command-line parameters to overwrite the GUI settings. for (OptionID option : {DatabaseCache, ThreadsScriptVerif, SpendZeroConfChange, ExternalSignerPath, MapPortUPnP, MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language, CoinJoinAmount, - CoinJoinMultiSession, CoinJoinRounds, CoinJoinSessions}) { + CoinJoinDenomsGoal, CoinJoinMultiSession, CoinJoinRounds, CoinJoinSessions}) { std::string setting = SettingName(option); if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting); try { @@ -385,11 +387,6 @@ bool OptionsModel::Init(bilingual_str& error) m_sub_fee_from_amount = settings.value("SubFeeFromAmount", false).toBool(); // CoinJoin - if (!settings.contains("nCoinJoinDenomsGoal")) - settings.setValue("nCoinJoinDenomsGoal", DEFAULT_COINJOIN_DENOMS_GOAL); - if (!gArgs.SoftSetArg("-coinjoindenomsgoal", settings.value("nCoinJoinDenomsGoal").toString().toStdString())) - addOverriddenOption("-coinjoindenomsgoal"); - if (!settings.contains("nCoinJoinDenomsHardCap")) settings.setValue("nCoinJoinDenomsHardCap", DEFAULT_COINJOIN_DENOMS_HARDCAP); if (!gArgs.SoftSetArg("-coinjoindenomshardcap", settings.value("nCoinJoinDenomsHardCap").toString().toStdString())) @@ -644,7 +641,7 @@ QVariant OptionsModel::getOption(OptionID option) const case CoinJoinAmount: return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_AMOUNT)); case CoinJoinDenomsGoal: - return settings.value("nCoinJoinDenomsGoal"); + return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_DENOMS_GOAL)); case CoinJoinDenomsHardCap: return settings.value("nCoinJoinDenomsHardCap"); case CoinJoinMultiSession: @@ -872,9 +869,9 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) } break; case CoinJoinDenomsGoal: - if (settings.value("nCoinJoinDenomsGoal") != value) { + if (changed()) { node().coinJoinOptions().setDenomsGoal(value.toInt()); - settings.setValue("nCoinJoinDenomsGoal", node().coinJoinOptions().getDenomsGoal()); + update(value.toInt()); } break; case CoinJoinDenomsHardCap: @@ -1131,6 +1128,7 @@ void OptionsModel::checkAndMigrate() //! Dash #ifdef ENABLE_WALLET migrate_setting(CoinJoinAmount, "nCoinJoinAmount"); + migrate_setting(CoinJoinDenomsGoal, "nCoinJoinDenomsGoal"); migrate_setting(CoinJoinMultiSession, "fCoinJoinMultiSession"); migrate_setting(CoinJoinRounds, "nCoinJoinRounds"); migrate_setting(CoinJoinSessions, "nCoinJoinSessions"); From da15040d5de3ffae54ae49686e65d38252a45f66 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 26 Aug 2025 08:12:53 +0000 Subject: [PATCH 12/20] qt: migrate `-coinjoindenomshardcap` setting from QSettings to settings.json --- src/qt/optionsmodel.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index c281016106b7..a6bb298b0d47 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -63,6 +63,7 @@ static const char* SettingName(OptionsModel::OptionID option) //! Dash case OptionsModel::CoinJoinAmount: return "coinjoinamount"; case OptionsModel::CoinJoinDenomsGoal: return "coinjoindenomsgoal"; + case OptionsModel::CoinJoinDenomsHardCap: return "coinjoindenomshardcap"; case OptionsModel::CoinJoinMultiSession: return "coinjoinmultisession"; case OptionsModel::CoinJoinRounds: return "coinjoinrounds"; case OptionsModel::CoinJoinSessions: return "coinjoinsessions"; @@ -75,6 +76,7 @@ static bool RequiresNumWorkaround(OptionsModel::OptionID option) switch (option) { case OptionsModel::CoinJoinAmount: case OptionsModel::CoinJoinDenomsGoal: + case OptionsModel::CoinJoinDenomsHardCap: case OptionsModel::CoinJoinRounds: case OptionsModel::CoinJoinSessions: case OptionsModel::DatabaseCache: @@ -359,7 +361,8 @@ bool OptionsModel::Init(bilingual_str& error) // and we want command-line parameters to overwrite the GUI settings. for (OptionID option : {DatabaseCache, ThreadsScriptVerif, SpendZeroConfChange, ExternalSignerPath, MapPortUPnP, MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language, CoinJoinAmount, - CoinJoinDenomsGoal, CoinJoinMultiSession, CoinJoinRounds, CoinJoinSessions}) { + CoinJoinDenomsGoal, CoinJoinDenomsHardCap, CoinJoinMultiSession, CoinJoinRounds, + CoinJoinSessions}) { std::string setting = SettingName(option); if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting); try { @@ -385,12 +388,6 @@ bool OptionsModel::Init(bilingual_str& error) settings.setValue("SubFeeFromAmount", false); } m_sub_fee_from_amount = settings.value("SubFeeFromAmount", false).toBool(); - - // CoinJoin - if (!settings.contains("nCoinJoinDenomsHardCap")) - settings.setValue("nCoinJoinDenomsHardCap", DEFAULT_COINJOIN_DENOMS_HARDCAP); - if (!gArgs.SoftSetArg("-coinjoindenomshardcap", settings.value("nCoinJoinDenomsHardCap").toString().toStdString())) - addOverriddenOption("-coinjoindenomshardcap"); #endif // Display @@ -643,7 +640,7 @@ QVariant OptionsModel::getOption(OptionID option) const case CoinJoinDenomsGoal: return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_DENOMS_GOAL)); case CoinJoinDenomsHardCap: - return settings.value("nCoinJoinDenomsHardCap"); + return qlonglong(SettingToInt(setting(), DEFAULT_COINJOIN_DENOMS_HARDCAP)); case CoinJoinMultiSession: return SettingToBool(setting(), DEFAULT_COINJOIN_MULTISESSION); #endif @@ -875,9 +872,9 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) } break; case CoinJoinDenomsHardCap: - if (settings.value("nCoinJoinDenomsHardCap") != value) { + if (changed()) { node().coinJoinOptions().setDenomsHardCap(value.toInt()); - settings.setValue("nCoinJoinDenomsHardCap", node().coinJoinOptions().getDenomsHardCap()); + update(value.toInt()); } break; case CoinJoinMultiSession: @@ -1129,6 +1126,7 @@ void OptionsModel::checkAndMigrate() #ifdef ENABLE_WALLET migrate_setting(CoinJoinAmount, "nCoinJoinAmount"); migrate_setting(CoinJoinDenomsGoal, "nCoinJoinDenomsGoal"); + migrate_setting(CoinJoinDenomsHardCap, "nCoinJoinDenomsHardCap"); migrate_setting(CoinJoinMultiSession, "fCoinJoinMultiSession"); migrate_setting(CoinJoinRounds, "nCoinJoinRounds"); migrate_setting(CoinJoinSessions, "nCoinJoinSessions"); From 2bb8106b547213bf2a5d9a8502e804f6f4b2bca8 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 29 Aug 2025 04:07:20 +0000 Subject: [PATCH 13/20] qt: migrate `-enablecoinjoin` setting from QSettings to settings.json --- src/qt/optionsmodel.cpp | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index a6bb298b0d47..8785dca17aa9 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -64,6 +64,7 @@ static const char* SettingName(OptionsModel::OptionID option) case OptionsModel::CoinJoinAmount: return "coinjoinamount"; case OptionsModel::CoinJoinDenomsGoal: return "coinjoindenomsgoal"; case OptionsModel::CoinJoinDenomsHardCap: return "coinjoindenomshardcap"; + case OptionsModel::CoinJoinEnabled: return "enablecoinjoin"; case OptionsModel::CoinJoinMultiSession: return "coinjoinmultisession"; case OptionsModel::CoinJoinRounds: return "coinjoinrounds"; case OptionsModel::CoinJoinSessions: return "coinjoinsessions"; @@ -339,13 +340,6 @@ bool OptionsModel::Init(bilingual_str& error) settings.setValue("digits", "2"); // CoinJoin - if (!settings.contains("fCoinJoinEnabled")) { - settings.setValue("fCoinJoinEnabled", true); - } - if (!gArgs.SoftSetBoolArg("-enablecoinjoin", settings.value("fCoinJoinEnabled").toBool())) { - addOverriddenOption("-enablecoinjoin"); - } - if (!settings.contains("fShowAdvancedCJUI")) settings.setValue("fShowAdvancedCJUI", false); fShowAdvancedCJUI = settings.value("fShowAdvancedCJUI", false).toBool(); @@ -361,8 +355,8 @@ bool OptionsModel::Init(bilingual_str& error) // and we want command-line parameters to overwrite the GUI settings. for (OptionID option : {DatabaseCache, ThreadsScriptVerif, SpendZeroConfChange, ExternalSignerPath, MapPortUPnP, MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language, CoinJoinAmount, - CoinJoinDenomsGoal, CoinJoinDenomsHardCap, CoinJoinMultiSession, CoinJoinRounds, - CoinJoinSessions}) { + CoinJoinDenomsGoal, CoinJoinDenomsHardCap, CoinJoinEnabled, CoinJoinMultiSession, + CoinJoinRounds, CoinJoinSessions}) { std::string setting = SettingName(option); if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting); try { @@ -624,7 +618,7 @@ QVariant OptionsModel::getOption(OptionID option) const case ShowGovernanceTab: return settings.value("fShowGovernanceTab"); case CoinJoinEnabled: - return settings.value("fCoinJoinEnabled"); + return SettingToBool(setting(), /*fDefault=*/true); case ShowAdvancedCJUI: return fShowAdvancedCJUI; case ShowCoinJoinPopups: @@ -826,8 +820,8 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) } break; case CoinJoinEnabled: - if (settings.value("fCoinJoinEnabled") != value) { - settings.setValue("fCoinJoinEnabled", value.toBool()); + if (changed()) { + update(value.toBool()); Q_EMIT coinJoinEnabledChanged(); } break; @@ -1127,6 +1121,7 @@ void OptionsModel::checkAndMigrate() migrate_setting(CoinJoinAmount, "nCoinJoinAmount"); migrate_setting(CoinJoinDenomsGoal, "nCoinJoinDenomsGoal"); migrate_setting(CoinJoinDenomsHardCap, "nCoinJoinDenomsHardCap"); + migrate_setting(CoinJoinEnabled, "fCoinJoinEnabled"); migrate_setting(CoinJoinMultiSession, "fCoinJoinMultiSession"); migrate_setting(CoinJoinRounds, "nCoinJoinRounds"); migrate_setting(CoinJoinSessions, "nCoinJoinSessions"); From a5b5edeb3f5c1bee0c2b8421100ad9f1db1ab8dc Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 26 Aug 2025 09:16:00 +0000 Subject: [PATCH 14/20] qt: migrate `-font-family` setting from QSettings to settings.json --- src/qt/optionsmodel.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 8785dca17aa9..177ba14d0ddf 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -68,6 +68,7 @@ static const char* SettingName(OptionsModel::OptionID option) case OptionsModel::CoinJoinMultiSession: return "coinjoinmultisession"; case OptionsModel::CoinJoinRounds: return "coinjoinrounds"; case OptionsModel::CoinJoinSessions: return "coinjoinsessions"; + case OptionsModel::FontFamily: return "font-family"; default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option)); } } @@ -251,14 +252,11 @@ bool OptionsModel::Init(bilingual_str& error) settings.setValue("theme", GUIUtil::getDefaultTheme()); // Font Family - if (!settings.contains("fontFamily")) { - settings.setValue("fontFamily", GUIUtil::FontRegistry::DEFAULT_FONT.toUtf8()); - } - if (!gArgs.SoftSetArg("-font-family", settings.value("fontFamily").toString().toStdString())) { + if (node().isSettingIgnored("font-family")) { addOverriddenOption("-font-family"); } if (GUIUtil::fontsLoaded()) { - if (auto font_name = QString::fromStdString(gArgs.GetArg("-font-family", settings.value("fontFamily").toString().toStdString())); + if (auto font_name = QString::fromStdString(SettingToString(node().getPersistentSetting("font-family"), GUIUtil::FontRegistry::DEFAULT_FONT.toUtf8().toStdString())); GUIUtil::g_font_registry.RegisterFont(font_name, /*selectable=*/true) && GUIUtil::g_font_registry.SetFont(font_name)) { GUIUtil::setApplicationFont(); } @@ -649,7 +647,7 @@ QVariant OptionsModel::getOption(OptionID option) const case Theme: return settings.value("theme"); case FontFamily: - return settings.value("fontFamily"); + return QString::fromStdString(SettingToString(setting(), GUIUtil::FontRegistry::DEFAULT_FONT.toUtf8().toStdString())); case FontScale: return settings.value("fontScale"); case FontWeightNormal: { @@ -901,8 +899,8 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) // to allow instant theme changes. break; case FontFamily: - if (settings.value("fontFamily") != value) { - settings.setValue("fontFamily", value); + if (changed()) { + update(value.toString().toStdString()); } break; case FontScale: @@ -1117,6 +1115,9 @@ void OptionsModel::checkAndMigrate() migrate_setting(Language, "language"); //! Dash + if (GUIUtil::fontsLoaded()) { + migrate_setting(FontFamily, "fontFamily"); + } #ifdef ENABLE_WALLET migrate_setting(CoinJoinAmount, "nCoinJoinAmount"); migrate_setting(CoinJoinDenomsGoal, "nCoinJoinDenomsGoal"); From c3a5ba112f738855ae0ed5e690522c8784e2a566 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 27 Dec 2025 02:33:24 +0530 Subject: [PATCH 15/20] qt: migrate `-font-scale` setting from QSettings to settings.json --- src/qt/optionsmodel.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 177ba14d0ddf..78ab8a45a6a1 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -69,6 +69,7 @@ static const char* SettingName(OptionsModel::OptionID option) case OptionsModel::CoinJoinRounds: return "coinjoinrounds"; case OptionsModel::CoinJoinSessions: return "coinjoinsessions"; case OptionsModel::FontFamily: return "font-family"; + case OptionsModel::FontScale: return "font-scale"; default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option)); } } @@ -82,6 +83,7 @@ static bool RequiresNumWorkaround(OptionsModel::OptionID option) case OptionsModel::CoinJoinRounds: case OptionsModel::CoinJoinSessions: case OptionsModel::DatabaseCache: + case OptionsModel::FontScale: case OptionsModel::Prune: case OptionsModel::PruneSize: case OptionsModel::ThreadsScriptVerif: @@ -263,14 +265,11 @@ bool OptionsModel::Init(bilingual_str& error) } // Font Scale - if (!settings.contains("fontScale")) { - settings.setValue("fontScale", GUIUtil::FontRegistry::DEFAULT_FONT_SCALE); - } - if (!gArgs.SoftSetArg("-font-scale", settings.value("fontScale").toString().toStdString())) { + if (node().isSettingIgnored("font-scale")) { addOverriddenOption("-font-scale"); } if (GUIUtil::fontsLoaded()) { - GUIUtil::g_font_registry.SetFontScale(gArgs.GetIntArg("-font-scale", settings.value("fontScale").toString().toInt())); + GUIUtil::g_font_registry.SetFontScale(SettingToInt(node().getPersistentSetting("font-scale"), GUIUtil::FontRegistry::DEFAULT_FONT_SCALE)); } // Font Weight (Normal) @@ -649,7 +648,7 @@ QVariant OptionsModel::getOption(OptionID option) const case FontFamily: return QString::fromStdString(SettingToString(setting(), GUIUtil::FontRegistry::DEFAULT_FONT.toUtf8().toStdString())); case FontScale: - return settings.value("fontScale"); + return qlonglong(SettingToInt(setting(), GUIUtil::FontRegistry::DEFAULT_FONT_SCALE)); case FontWeightNormal: { int nIndex = [&]() -> int { if (QFont::Weight weight; GUIUtil::weightFromArg(settings.value("fontWeightNormal").toInt(), weight) && GUIUtil::g_font_registry.IsValidWeight(weight)) { @@ -904,8 +903,8 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) } break; case FontScale: - if (settings.value("fontScale") != value) { - settings.setValue("fontScale", value); + if (changed()) { + update(value.toInt()); } break; case FontWeightNormal: { @@ -1117,6 +1116,7 @@ void OptionsModel::checkAndMigrate() //! Dash if (GUIUtil::fontsLoaded()) { migrate_setting(FontFamily, "fontFamily"); + migrate_setting(FontScale, "fontScale"); } #ifdef ENABLE_WALLET migrate_setting(CoinJoinAmount, "nCoinJoinAmount"); From 70ff8ef0187a02cdf2880fdfcd9874446cc2cba1 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:54:09 +0530 Subject: [PATCH 16/20] qt: migrate `-font-weight-normal` from QSettings to settings.json --- src/qt/optionsmodel.cpp | 51 ++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 78ab8a45a6a1..3c932a3e566d 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -70,6 +70,7 @@ static const char* SettingName(OptionsModel::OptionID option) case OptionsModel::CoinJoinSessions: return "coinjoinsessions"; case OptionsModel::FontFamily: return "font-family"; case OptionsModel::FontScale: return "font-scale"; + case OptionsModel::FontWeightNormal: return "font-weight-normal"; default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option)); } } @@ -84,6 +85,7 @@ static bool RequiresNumWorkaround(OptionsModel::OptionID option) case OptionsModel::CoinJoinSessions: case OptionsModel::DatabaseCache: case OptionsModel::FontScale: + case OptionsModel::FontWeightNormal: case OptionsModel::Prune: case OptionsModel::PruneSize: case OptionsModel::ThreadsScriptVerif: @@ -140,6 +142,22 @@ static int ParsePruneSizeGB(const QVariant& prune_size) return std::max(1, prune_size.toInt()); } +static int GetFallbackWeightIndex() +{ + // If the currently selected weight is not supported fallback to the lightest weight for normal font. + return 0; +} + +static int WeightArgToIdx(int weight_arg) +{ + if (QFont::Weight weight; GUIUtil::weightFromArg(weight_arg, weight)) { + if (int index = GUIUtil::g_font_registry.WeightToIdx(weight); index != -1) { + return index; + } + } + return GetFallbackWeightIndex(); +} + struct ProxySetting { bool is_set; QString ip; @@ -273,10 +291,7 @@ bool OptionsModel::Init(bilingual_str& error) } // Font Weight (Normal) - if (!settings.contains("fontWeightNormal")) { - settings.setValue("fontWeightNormal", GUIUtil::weightToArg(GUIUtil::FontRegistry::TARGET_WEIGHT_NORMAL)); - } - if (gArgs.IsArgSet("-font-weight-normal") && !gArgs.SoftSetArg("-font-weight-normal", settings.value("fontWeightNormal").toString().toStdString())) { + if (node().isSettingIgnored("font-weight-normal")) { addOverriddenOption("-font-weight-normal"); } @@ -285,10 +300,10 @@ bool OptionsModel::Init(bilingual_str& error) // If font was overridden by CLI but weight wasn't, use the font's default weight QFont::Weight weight{GUIUtil::g_font_registry.GetWeightNormalDefault()}; if (!override_family || isOptionOverridden("-font-weight-normal")) { - GUIUtil::weightFromArg(gArgs.GetIntArg("-font-weight-normal", settings.value("fontWeightNormal").toInt()), weight); - if (!GUIUtil::g_font_registry.IsValidWeight(weight)) { - // If the currently selected weight is not supported fallback to the lightest weight for normal font. - weight = GUIUtil::g_font_registry.GetSupportedWeights().front(); + const auto raw_weight{SettingToInt(node().getPersistentSetting("font-weight-normal"), GUIUtil::weightToArg(GUIUtil::FontRegistry::TARGET_WEIGHT_NORMAL))}; + if (!GUIUtil::weightFromArg(raw_weight, weight) || !GUIUtil::g_font_registry.IsValidWeight(weight)) { + weight = GUIUtil::g_font_registry.IdxToWeight(GetFallbackWeightIndex()); + node().forceSetting("font-weight-normal", GUIUtil::weightToArg(weight)); } } GUIUtil::g_font_registry.SetWeightNormal(weight); @@ -649,16 +664,8 @@ QVariant OptionsModel::getOption(OptionID option) const return QString::fromStdString(SettingToString(setting(), GUIUtil::FontRegistry::DEFAULT_FONT.toUtf8().toStdString())); case FontScale: return qlonglong(SettingToInt(setting(), GUIUtil::FontRegistry::DEFAULT_FONT_SCALE)); - case FontWeightNormal: { - int nIndex = [&]() -> int { - if (QFont::Weight weight; GUIUtil::weightFromArg(settings.value("fontWeightNormal").toInt(), weight) && GUIUtil::g_font_registry.IsValidWeight(weight)) { - return GUIUtil::g_font_registry.WeightToIdx(weight); - } - return GUIUtil::g_font_registry.WeightToIdx(GUIUtil::g_font_registry.GetWeightNormalDefault()); - }(); - assert(nIndex != -1); - return nIndex; - } + case FontWeightNormal: + return WeightArgToIdx(SettingToInt(setting(), GUIUtil::g_font_registry.GetWeightNormalDefault())); case FontWeightBold: { int nIndex = [&]() -> int { if (QFont::Weight weight; GUIUtil::weightFromArg(settings.value("fontWeightBold").toInt(), weight) && GUIUtil::g_font_registry.IsValidWeight(weight)) { @@ -908,9 +915,8 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) } break; case FontWeightNormal: { - int nWeight = GUIUtil::weightToArg(GUIUtil::g_font_registry.IdxToWeight(value.toInt())); - if (settings.value("fontWeightNormal") != nWeight) { - settings.setValue("fontWeightNormal", nWeight); + if (changed()) { + update(GUIUtil::weightToArg(GUIUtil::g_font_registry.IdxToWeight(value.toInt()))); } break; } @@ -1088,6 +1094,8 @@ void OptionsModel::checkAndMigrate() ProxySetting parsed = ParseProxyString(value.toString()); setOption(ProxyIPTor, parsed.ip); setOption(ProxyPortTor, parsed.port); + } else if (option == FontWeightNormal) { + setOption(option, WeightArgToIdx(value.toInt())); } else { setOption(option, value); } @@ -1117,6 +1125,7 @@ void OptionsModel::checkAndMigrate() if (GUIUtil::fontsLoaded()) { migrate_setting(FontFamily, "fontFamily"); migrate_setting(FontScale, "fontScale"); + migrate_setting(FontWeightNormal, "fontWeightNormal"); } #ifdef ENABLE_WALLET migrate_setting(CoinJoinAmount, "nCoinJoinAmount"); From e4294376312426c734843b27665efe0970215cdb Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 30 Aug 2025 10:19:46 +0000 Subject: [PATCH 17/20] qt: migrate `-font-weight-bold` from QSettings to settings.json --- src/qt/optionsmodel.cpp | 59 +++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 3c932a3e566d..9c73e1193ccd 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -70,6 +70,7 @@ static const char* SettingName(OptionsModel::OptionID option) case OptionsModel::CoinJoinSessions: return "coinjoinsessions"; case OptionsModel::FontFamily: return "font-family"; case OptionsModel::FontScale: return "font-scale"; + case OptionsModel::FontWeightBold: return "font-weight-bold"; case OptionsModel::FontWeightNormal: return "font-weight-normal"; default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option)); } @@ -85,6 +86,7 @@ static bool RequiresNumWorkaround(OptionsModel::OptionID option) case OptionsModel::CoinJoinSessions: case OptionsModel::DatabaseCache: case OptionsModel::FontScale: + case OptionsModel::FontWeightBold: case OptionsModel::FontWeightNormal: case OptionsModel::Prune: case OptionsModel::PruneSize: @@ -142,20 +144,26 @@ static int ParsePruneSizeGB(const QVariant& prune_size) return std::max(1, prune_size.toInt()); } -static int GetFallbackWeightIndex() +static int GetFallbackWeightIndex(bool is_bold) { - // If the currently selected weight is not supported fallback to the lightest weight for normal font. - return 0; + if (is_bold) { + // If the currently selected weight is not supported fallback to the second lightest weight for bold font + // or the lightest if there is only one. + return GUIUtil::g_font_registry.GetSupportedWeights().size() > 1 ? 1 : 0; + } else { + // If the currently selected weight is not supported fallback to the lightest weight for normal font. + return 0; + } } -static int WeightArgToIdx(int weight_arg) +static int WeightArgToIdx(bool is_bold, int weight_arg) { if (QFont::Weight weight; GUIUtil::weightFromArg(weight_arg, weight)) { if (int index = GUIUtil::g_font_registry.WeightToIdx(weight); index != -1) { return index; } } - return GetFallbackWeightIndex(); + return GetFallbackWeightIndex(is_bold); } struct ProxySetting { @@ -302,7 +310,7 @@ bool OptionsModel::Init(bilingual_str& error) if (!override_family || isOptionOverridden("-font-weight-normal")) { const auto raw_weight{SettingToInt(node().getPersistentSetting("font-weight-normal"), GUIUtil::weightToArg(GUIUtil::FontRegistry::TARGET_WEIGHT_NORMAL))}; if (!GUIUtil::weightFromArg(raw_weight, weight) || !GUIUtil::g_font_registry.IsValidWeight(weight)) { - weight = GUIUtil::g_font_registry.IdxToWeight(GetFallbackWeightIndex()); + weight = GUIUtil::g_font_registry.IdxToWeight(GetFallbackWeightIndex(/*is_bold=*/false)); node().forceSetting("font-weight-normal", GUIUtil::weightToArg(weight)); } } @@ -310,22 +318,17 @@ bool OptionsModel::Init(bilingual_str& error) } // Font Weight (Bold) - if (!settings.contains("fontWeightBold")) { - settings.setValue("fontWeightBold", GUIUtil::weightToArg(GUIUtil::FontRegistry::TARGET_WEIGHT_BOLD)); - } - if (gArgs.IsArgSet("-font-weight-bold") && !gArgs.SoftSetArg("-font-weight-bold", settings.value("fontWeightBold").toString().toStdString())) { + if (node().isSettingIgnored("font-weight-bold")) { addOverriddenOption("-font-weight-bold"); } if (GUIUtil::fontsLoaded()) { // If font was overridden by CLI but weight wasn't, use the font's default weight QFont::Weight weight{GUIUtil::g_font_registry.GetWeightBoldDefault()}; if (!override_family || isOptionOverridden("-font-weight-bold")) { - GUIUtil::weightFromArg(gArgs.GetIntArg("-font-weight-bold", settings.value("fontWeightBold").toInt()), weight); - if (!GUIUtil::g_font_registry.IsValidWeight(weight)) { - // If the currently selected weight is not supported fallback to the second lightest weight for bold font - // or the lightest if there is only one. - auto vecSupported = GUIUtil::g_font_registry.GetSupportedWeights(); - weight = vecSupported[vecSupported.size() > 1 ? 1 : 0]; + const auto raw_weight{SettingToInt(node().getPersistentSetting("font-weight-bold"), GUIUtil::weightToArg(GUIUtil::FontRegistry::TARGET_WEIGHT_BOLD))}; + if (!GUIUtil::weightFromArg(raw_weight, weight) || !GUIUtil::g_font_registry.IsValidWeight(weight)) { + weight = GUIUtil::g_font_registry.IdxToWeight(GetFallbackWeightIndex(/*is_bold=*/true)); + node().forceSetting("font-weight-bold", GUIUtil::weightToArg(weight)); } } GUIUtil::g_font_registry.SetWeightBold(weight); @@ -665,17 +668,9 @@ QVariant OptionsModel::getOption(OptionID option) const case FontScale: return qlonglong(SettingToInt(setting(), GUIUtil::FontRegistry::DEFAULT_FONT_SCALE)); case FontWeightNormal: - return WeightArgToIdx(SettingToInt(setting(), GUIUtil::g_font_registry.GetWeightNormalDefault())); - case FontWeightBold: { - int nIndex = [&]() -> int { - if (QFont::Weight weight; GUIUtil::weightFromArg(settings.value("fontWeightBold").toInt(), weight) && GUIUtil::g_font_registry.IsValidWeight(weight)) { - return GUIUtil::g_font_registry.WeightToIdx(weight); - } - return GUIUtil::g_font_registry.WeightToIdx(GUIUtil::g_font_registry.GetWeightBoldDefault()); - }(); - assert(nIndex != -1); - return nIndex; - } + return WeightArgToIdx(/*is_bold=*/false, SettingToInt(setting(), GUIUtil::g_font_registry.GetWeightNormalDefault())); + case FontWeightBold: + return WeightArgToIdx(/*is_bold=*/true, SettingToInt(setting(), GUIUtil::g_font_registry.GetWeightBoldDefault())); case Language: return QString::fromStdString(SettingToString(setting(), "")); case FontForMoney: @@ -921,9 +916,8 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) break; } case FontWeightBold: { - int nWeight = GUIUtil::weightToArg(GUIUtil::g_font_registry.IdxToWeight(value.toInt())); - if (settings.value("fontWeightBold") != nWeight) { - settings.setValue("fontWeightBold", nWeight); + if (changed()) { + update(GUIUtil::weightToArg(GUIUtil::g_font_registry.IdxToWeight(value.toInt()))); } break; } @@ -1094,8 +1088,8 @@ void OptionsModel::checkAndMigrate() ProxySetting parsed = ParseProxyString(value.toString()); setOption(ProxyIPTor, parsed.ip); setOption(ProxyPortTor, parsed.port); - } else if (option == FontWeightNormal) { - setOption(option, WeightArgToIdx(value.toInt())); + } else if (option == FontWeightNormal || option == FontWeightBold) { + setOption(option, WeightArgToIdx(/*is_bold=*/option == FontWeightBold, value.toInt())); } else { setOption(option, value); } @@ -1125,6 +1119,7 @@ void OptionsModel::checkAndMigrate() if (GUIUtil::fontsLoaded()) { migrate_setting(FontFamily, "fontFamily"); migrate_setting(FontScale, "fontScale"); + migrate_setting(FontWeightBold, "fontWeightBold"); migrate_setting(FontWeightNormal, "fontWeightNormal"); } #ifdef ENABLE_WALLET From 45976c7b259443d56c80cd3646c4377bcd20c829 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 23 Jan 2023 19:12:04 -0500 Subject: [PATCH 18/20] merge bitcoin-core/gui#701: Persist Mask Values option --- src/qt/bitcoingui.cpp | 2 ++ src/qt/optionsmodel.cpp | 8 ++++++++ src/qt/optionsmodel.h | 2 ++ src/qt/overviewpage.cpp | 1 + 4 files changed, 13 insertions(+) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 779d1054e0fb..db33a094ea82 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -889,6 +889,8 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndH connect(optionsModel, &OptionsModel::coinJoinEnabledChanged, this, &BitcoinGUI::updateCoinJoinVisibility); } + + m_mask_values_action->setChecked(_clientModel->getOptionsModel()->getOption(OptionsModel::OptionID::MaskValues).toBool()); } else { if(trayIconMenu) { diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 9c73e1193ccd..2e3a778b61a5 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -411,6 +411,8 @@ bool OptionsModel::Init(bilingual_str& error) } Q_EMIT fontForMoneyChanged(getFontForMoney()); + m_mask_values = settings.value("mask_values", false).toBool(); + return true; } @@ -695,6 +697,8 @@ QVariant OptionsModel::getOption(OptionID option) const return SettingToBool(setting(), DEFAULT_LISTEN); case Server: return SettingToBool(setting(), false); + case MaskValues: + return m_mask_values; default: return QVariant(); } @@ -987,6 +991,10 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) setRestartRequired(true); } break; + case MaskValues: + m_mask_values = value.toBool(); + settings.setValue("mask_values", m_mask_values); + break; default: break; } diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index a12f2a1827b9..74ddc03284a3 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -94,6 +94,7 @@ class OptionsModel : public QAbstractListModel Listen, // bool Server, // bool EnablePSBTControls, // bool + MaskValues, // bool OptionIDRowCount, }; @@ -156,6 +157,7 @@ class OptionsModel : public QAbstractListModel bool fCoinControlFeatures; bool m_sub_fee_from_amount; bool m_enable_psbt_controls; + bool m_mask_values; bool fKeepChangeAddress; bool fShowAdvancedCJUI; diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 6c9f06b956ad..8ed12617a19f 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -204,6 +204,7 @@ void OverviewPage::handleTransactionClicked(const QModelIndex &index) void OverviewPage::setPrivacy(bool privacy) { m_privacy = privacy; + clientModel->getOptionsModel()->setOption(OptionsModel::OptionID::MaskValues, privacy); if (m_balances.balance != -1) { setBalance(m_balances); coinJoinStatus(true); From 0c3b224d133f6f079bb4b9cb6b61b0635f3355f5 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 22 Apr 2022 19:15:17 -0400 Subject: [PATCH 19/20] merge bitcoin-core/gui#603: Add settings.json prune-prev, proxy-prev, onion-prev settings --- src/qt/optionsmodel.cpp | 130 +++++++++++++++++++++++----------------- src/qt/optionsmodel.h | 13 +--- 2 files changed, 77 insertions(+), 66 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 2e3a778b61a5..54dae577268c 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -98,7 +98,7 @@ static bool RequiresNumWorkaround(OptionsModel::OptionID option) } /** Call node.updateRwSetting() with Bitcoin 22.x workaround. */ -static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID option, const util::SettingsValue& value) +static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID option, const std::string& suffix, const util::SettingsValue& value) { if (value.isNum() && RequiresNumWorkaround(option)) { // Write certain old settings as strings, even though they are numbers, @@ -108,9 +108,9 @@ static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID optio // in later releases by https://github.com/bitcoin/bitcoin/pull/24498. // If new numeric settings are added, they can be written as numbers // instead of strings, because bitcoin 22.x will not try to read these. - node.updateRwSetting(SettingName(option), value.getValStr()); + node.updateRwSetting(SettingName(option) + suffix, value.getValStr()); } else { - node.updateRwSetting(SettingName(option), value); + node.updateRwSetting(SettingName(option) + suffix, value); } } @@ -226,13 +226,6 @@ void OptionsModel::addOverriddenOption(const std::string &option) bool OptionsModel::Init(bilingual_str& error) { // Initialize display settings from stored settings. - m_prune_size_gb = PruneSizeGB(node().getPersistentSetting("prune")); - ProxySetting proxy = ParseProxyString(SettingToString(node().getPersistentSetting("proxy"), GetDefaultProxyAddress().toStdString())); - m_proxy_ip = proxy.ip; - m_proxy_port = proxy.port; - ProxySetting onion = ParseProxyString(SettingToString(node().getPersistentSetting("onion"), GetDefaultProxyAddress().toStdString())); - m_onion_ip = onion.ip; - m_onion_port = onion.port; language = QString::fromStdString(SettingToString(node().getPersistentSetting("lang"), "")); checkAndMigrate(); @@ -504,8 +497,6 @@ void OptionsModel::SetPruneTargetGB(int prune_target_gb) const util::SettingsValue cur_value = node().getPersistentSetting("prune"); const util::SettingsValue new_value = PruneSetting(prune_target_gb > 0, prune_target_gb); - m_prune_size_gb = prune_target_gb; - // Force setting to take effect. It is still safe to change the value at // this point because this function is only called after the intro screen is // shown, before the node starts. @@ -524,7 +515,12 @@ void OptionsModel::SetPruneTargetGB(int prune_target_gb) PruneSizeGB(cur_value) != PruneSizeGB(new_value)) { // Call UpdateRwSetting() instead of setOption() to avoid setting // RestartRequired flag - UpdateRwSetting(node(), Prune, new_value); + UpdateRwSetting(node(), Prune, "", new_value); + } + + // Keep previous pruning size, if pruning was disabled. + if (PruneEnabled(cur_value)) { + UpdateRwSetting(node(), Prune, "-prev", PruneEnabled(new_value) ? util::SettingsValue{} : cur_value); } } @@ -580,9 +576,9 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in return successful; } -QVariant OptionsModel::getOption(OptionID option) const +QVariant OptionsModel::getOption(OptionID option, const std::string& suffix) const { - auto setting = [&]{ return node().getPersistentSetting(SettingName(option)); }; + auto setting = [&]{ return node().getPersistentSetting(SettingName(option) + suffix); }; QSettings settings; switch (option) { @@ -609,19 +605,30 @@ QVariant OptionsModel::getOption(OptionID option) const // default proxy case ProxyUse: + case ProxyUseTor: return ParseProxyString(SettingToString(setting(), "")).is_set; case ProxyIP: - return m_proxy_ip; + case ProxyIPTor: { + ProxySetting proxy = ParseProxyString(SettingToString(setting(), "")); + if (proxy.is_set) { + return proxy.ip; + } else if (suffix.empty()) { + return getOption(option, "-prev"); + } else { + return ParseProxyString(GetDefaultProxyAddress().toStdString()).ip; + } + } case ProxyPort: - return m_proxy_port; - - // separate Tor proxy - case ProxyUseTor: - return ParseProxyString(SettingToString(setting(), "")).is_set; - case ProxyIPTor: - return m_onion_ip; - case ProxyPortTor: - return m_onion_port; + case ProxyPortTor: { + ProxySetting proxy = ParseProxyString(SettingToString(setting(), "")); + if (proxy.is_set) { + return proxy.port; + } else if (suffix.empty()) { + return getOption(option, "-prev"); + } else { + return ParseProxyString(GetDefaultProxyAddress().toStdString()).port; + } + } #ifdef ENABLE_WALLET case SpendZeroConfChange: @@ -688,7 +695,9 @@ QVariant OptionsModel::getOption(OptionID option) const case Prune: return PruneEnabled(setting()); case PruneSize: - return m_prune_size_gb; + return PruneEnabled(setting()) ? PruneSizeGB(setting()) : + suffix.empty() ? getOption(option, "-prev") : + DEFAULT_PRUNE_TARGET_GB; case DatabaseCache: return qlonglong(SettingToInt(setting(), nDefaultDbCache)); case ThreadsScriptVerif: @@ -704,10 +713,10 @@ QVariant OptionsModel::getOption(OptionID option) const } } -bool OptionsModel::setOption(OptionID option, const QVariant& value) +bool OptionsModel::setOption(OptionID option, const QVariant& value, const std::string& suffix) { - auto changed = [&] { return value.isValid() && value != getOption(option); }; - auto update = [&](const util::SettingsValue& value) { return UpdateRwSetting(node(), option, value); }; + auto changed = [&] { return value.isValid() && value != getOption(option, suffix); }; + auto update = [&](const util::SettingsValue& value) { return UpdateRwSetting(node(), option, suffix, value); }; bool successful = true; /* set to false on parse error */ QSettings settings; @@ -744,52 +753,60 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) // default proxy case ProxyUse: if (changed()) { - update(ProxyString(value.toBool(), m_proxy_ip, m_proxy_port)); - setRestartRequired(true); + if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); + update(ProxyString(value.toBool(), getOption(ProxyIP).toString(), getOption(ProxyPort).toString())); + if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); + if (suffix.empty()) setRestartRequired(true); } break; case ProxyIP: if (changed()) { - m_proxy_ip = value.toString(); - if (getOption(ProxyUse).toBool()) { - update(ProxyString(true, m_proxy_ip, m_proxy_port)); - setRestartRequired(true); + if (suffix.empty() && !getOption(ProxyUse).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, value.toString(), getOption(ProxyPort).toString())); } + if (suffix.empty() && getOption(ProxyUse).toBool()) setRestartRequired(true); } break; case ProxyPort: if (changed()) { - m_proxy_port = value.toString(); - if (getOption(ProxyUse).toBool()) { - update(ProxyString(true, m_proxy_ip, m_proxy_port)); - setRestartRequired(true); + if (suffix.empty() && !getOption(ProxyUse).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, getOption(ProxyIP).toString(), value.toString())); } + if (suffix.empty() && getOption(ProxyUse).toBool()) setRestartRequired(true); } break; // separate Tor proxy case ProxyUseTor: if (changed()) { - update(ProxyString(value.toBool(), m_onion_ip, m_onion_port)); - setRestartRequired(true); + if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); + update(ProxyString(value.toBool(), getOption(ProxyIPTor).toString(), getOption(ProxyPortTor).toString())); + if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); + if (suffix.empty()) setRestartRequired(true); } break; case ProxyIPTor: if (changed()) { - m_onion_ip = value.toString(); - if (getOption(ProxyUseTor).toBool()) { - update(ProxyString(true, m_onion_ip, m_onion_port)); - setRestartRequired(true); + if (suffix.empty() && !getOption(ProxyUseTor).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, value.toString(), getOption(ProxyPortTor).toString())); } + if (suffix.empty() && getOption(ProxyUseTor).toBool()) setRestartRequired(true); } break; case ProxyPortTor: if (changed()) { - m_onion_port = value.toString(); - if (getOption(ProxyUseTor).toBool()) { - update(ProxyString(true, m_onion_ip, m_onion_port)); - setRestartRequired(true); + if (suffix.empty() && !getOption(ProxyUseTor).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, getOption(ProxyIPTor).toString(), value.toString())); } + if (suffix.empty() && getOption(ProxyUseTor).toBool()) setRestartRequired(true); } break; @@ -959,17 +976,20 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) #endif // ENABLE_WALLET case Prune: if (changed()) { - update(PruneSetting(value.toBool(), m_prune_size_gb)); - setRestartRequired(true); + if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); + update(PruneSetting(value.toBool(), getOption(PruneSize).toInt())); + if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); + if (suffix.empty()) setRestartRequired(true); } break; case PruneSize: if (changed()) { - m_prune_size_gb = ParsePruneSizeGB(value); - if (getOption(Prune).toBool()) { - update(PruneSetting(true, m_prune_size_gb)); - setRestartRequired(true); + if (suffix.empty() && !getOption(Prune).toBool()) { + setOption(option, value, "-prev"); + } else { + update(PruneSetting(true, ParsePruneSizeGB(value))); } + if (suffix.empty() && getOption(Prune).toBool()) setRestartRequired(true); } break; case DatabaseCache: diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 74ddc03284a3..1bf20c7b0efc 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -113,8 +113,8 @@ class OptionsModel : public QAbstractListModel int rowCount(const QModelIndex & parent = QModelIndex()) const override; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override; - QVariant getOption(OptionID option) const; - bool setOption(OptionID option, const QVariant& value); + QVariant getOption(OptionID option, const std::string& suffix="") const; + bool setOption(OptionID option, const QVariant& value, const std::string& suffix=""); /** Updates current unit in memory, settings and emits displayUnitChanged(new_unit) signal */ void setDisplayUnit(const QVariant& new_unit); @@ -161,15 +161,6 @@ class OptionsModel : public QAbstractListModel bool fKeepChangeAddress; bool fShowAdvancedCJUI; - //! In-memory settings for display. These are stored persistently by the - //! bitcoin node but it's also nice to store them in memory to prevent them - //! getting cleared when enable/disable toggles are used in the GUI. - int m_prune_size_gb; - QString m_proxy_ip; - QString m_proxy_port; - QString m_onion_ip; - QString m_onion_port; - /* settings that were overridden by command-line */ QString strOverriddenByCommandLine; From 076ce3d41a25e08020960999d7c5c3d0761c2e65 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 5 Jan 2026 02:36:41 +0530 Subject: [PATCH 20/20] fix: only reset GUI-managed settings in -resetguisettings Co-authored-by: UdjinM6 Co-Authored-By: Claude Sonnet 4.5 --- src/qt/optionsmodel.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 54dae577268c..ce4579ca5814 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -430,8 +430,22 @@ static void BackupSettings(const fs::path& filename, const QSettings& src) void OptionsModel::Reset() { - // Backup and reset settings.json - node().resetSettings(); + // Backup settings.json + gArgs.WriteSettingsFile(/*errors=*/nullptr, /*backup=*/true); + + // Clear GUI-managed settings from settings.json as node().resetSettings() + // clears ALL settings including non-GUI settings + for (int i{0}; i < OptionIDRowCount; i++) { + const char* setting_name = nullptr; + try { + setting_name = SettingName(static_cast(i)); + } catch (const std::logic_error&) { + // Option doesn't have a corresponding node setting, skip it + continue; + } + node().updateRwSetting(setting_name, {}); + node().updateRwSetting(strprintf("%s-prev", setting_name), {}); + } QSettings settings;