optionsmodel.cpp
1 // Copyright (c) 2011-present The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #include <bitcoin-build-config.h> // IWYU pragma: keep 6 7 #include <qt/optionsmodel.h> 8 9 #include <qt/bitcoinunits.h> 10 #include <qt/guiconstants.h> 11 #include <qt/guiutil.h> 12 13 #include <common/args.h> 14 #include <interfaces/node.h> 15 #include <mapport.h> 16 #include <net.h> 17 #include <netbase.h> 18 #include <node/caches.h> 19 #include <node/chainstatemanager_args.h> 20 #include <univalue.h> 21 #include <util/string.h> 22 #include <validation.h> 23 #include <wallet/wallet.h> 24 25 #include <QDebug> 26 #include <QLatin1Char> 27 #include <QSettings> 28 #include <QStringList> 29 #include <QVariant> 30 31 const char *DEFAULT_GUI_PROXY_HOST = "127.0.0.1"; 32 33 static QString GetDefaultProxyAddress(); 34 35 /** Map GUI option ID to node setting name. */ 36 static const char* SettingName(OptionsModel::OptionID option) 37 { 38 switch (option) { 39 case OptionsModel::DatabaseCache: return "dbcache"; 40 case OptionsModel::ThreadsScriptVerif: return "par"; 41 case OptionsModel::SpendZeroConfChange: return "spendzeroconfchange"; 42 case OptionsModel::ExternalSignerPath: return "signer"; 43 case OptionsModel::MapPortNatpmp: return "natpmp"; 44 case OptionsModel::Listen: return "listen"; 45 case OptionsModel::Server: return "server"; 46 case OptionsModel::PruneSize: return "prune"; 47 case OptionsModel::Prune: return "prune"; 48 case OptionsModel::ProxyIP: return "proxy"; 49 case OptionsModel::ProxyPort: return "proxy"; 50 case OptionsModel::ProxyUse: return "proxy"; 51 case OptionsModel::ProxyIPTor: return "onion"; 52 case OptionsModel::ProxyPortTor: return "onion"; 53 case OptionsModel::ProxyUseTor: return "onion"; 54 case OptionsModel::Language: return "lang"; 55 default: throw std::logic_error(strprintf("GUI option %i has no corresponding node setting.", option)); 56 } 57 } 58 59 /** Call node.updateRwSetting() with Bitcoin 22.x workaround. */ 60 static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID option, const std::string& suffix, const common::SettingsValue& value) 61 { 62 if (value.isNum() && 63 (option == OptionsModel::DatabaseCache || 64 option == OptionsModel::ThreadsScriptVerif || 65 option == OptionsModel::Prune || 66 option == OptionsModel::PruneSize)) { 67 // Write certain old settings as strings, even though they are numbers, 68 // because Bitcoin 22.x releases try to read these specific settings as 69 // strings in addOverriddenOption() calls at startup, triggering 70 // uncaught exceptions in UniValue::get_str(). These errors were fixed 71 // in later releases by https://github.com/bitcoin/bitcoin/pull/24498. 72 // If new numeric settings are added, they can be written as numbers 73 // instead of strings, because bitcoin 22.x will not try to read these. 74 node.updateRwSetting(SettingName(option) + suffix, value.getValStr()); 75 } else { 76 node.updateRwSetting(SettingName(option) + suffix, value); 77 } 78 } 79 80 //! Convert enabled/size values to bitcoin -prune setting. 81 static common::SettingsValue PruneSetting(bool prune_enabled, int prune_size_gb) 82 { 83 assert(!prune_enabled || prune_size_gb >= 1); // PruneSizeGB and ParsePruneSizeGB never return less 84 return prune_enabled ? PruneGBtoMiB(prune_size_gb) : 0; 85 } 86 87 //! Get pruning enabled value to show in GUI from bitcoin -prune setting. 88 static bool PruneEnabled(const common::SettingsValue& prune_setting) 89 { 90 // -prune=1 setting is manual pruning mode, so disabled for purposes of the gui 91 return SettingTo<int64_t>(prune_setting, 0) > 1; 92 } 93 94 //! Get pruning size value to show in GUI from bitcoin -prune setting. If 95 //! pruning is not enabled, just show default recommended pruning size (2GB). 96 static int PruneSizeGB(const common::SettingsValue& prune_setting) 97 { 98 int value = SettingTo<int64_t>(prune_setting, 0); 99 return value > 1 ? PruneMiBtoGB(value) : DEFAULT_PRUNE_TARGET_GB; 100 } 101 102 //! Parse pruning size value provided by user in GUI or loaded from QSettings 103 //! (windows registry key or qt .conf file). Smallest value that the GUI can 104 //! display is 1 GB, so round up if anything less is parsed. 105 static int ParsePruneSizeGB(const QVariant& prune_size) 106 { 107 return std::max(1, prune_size.toInt()); 108 } 109 110 struct ProxySetting { 111 bool is_set; 112 QString ip; 113 QString port; 114 }; 115 static ProxySetting ParseProxyString(const std::string& proxy); 116 static std::string ProxyString(bool is_set, QString ip, QString port); 117 118 static const QLatin1String fontchoice_str_embedded{"embedded"}; 119 static const QLatin1String fontchoice_str_best_system{"best_system"}; 120 static const QString fontchoice_str_custom_prefix{QStringLiteral("custom, ")}; 121 122 QString OptionsModel::FontChoiceToString(const OptionsModel::FontChoice& f) 123 { 124 if (std::holds_alternative<FontChoiceAbstract>(f)) { 125 if (f == UseBestSystemFont) { 126 return fontchoice_str_best_system; 127 } else { 128 return fontchoice_str_embedded; 129 } 130 } 131 return fontchoice_str_custom_prefix + std::get<QFont>(f).toString(); 132 } 133 134 OptionsModel::FontChoice OptionsModel::FontChoiceFromString(const QString& s) 135 { 136 if (s == fontchoice_str_best_system) { 137 return FontChoiceAbstract::BestSystemFont; 138 } else if (s == fontchoice_str_embedded) { 139 return FontChoiceAbstract::EmbeddedFont; 140 } else if (s.startsWith(fontchoice_str_custom_prefix)) { 141 QFont f; 142 f.fromString(s.mid(fontchoice_str_custom_prefix.size())); 143 return f; 144 } else { 145 return FontChoiceAbstract::EmbeddedFont; // default 146 } 147 } 148 149 OptionsModel::OptionsModel(interfaces::Node& node, QObject *parent) : 150 QAbstractListModel(parent), m_node{node} 151 { 152 } 153 154 void OptionsModel::addOverriddenOption(const std::string &option) 155 { 156 strOverriddenByCommandLine += QString::fromStdString(option) + "=" + QString::fromStdString(gArgs.GetArg(option, "")) + " "; 157 } 158 159 // Writes all missing QSettings with their default values 160 bool OptionsModel::Init(bilingual_str& error) 161 { 162 // Initialize display settings from stored settings. 163 language = QString::fromStdString(SettingToString(node().getPersistentSetting("lang"), "")); 164 165 checkAndMigrate(); 166 167 QSettings settings; 168 169 // Ensure restart flag is unset on client startup 170 setRestartRequired(false); 171 172 // These are Qt-only settings: 173 174 // Window 175 if (!settings.contains("fHideTrayIcon")) { 176 settings.setValue("fHideTrayIcon", false); 177 } 178 m_show_tray_icon = !settings.value("fHideTrayIcon").toBool(); 179 Q_EMIT showTrayIconChanged(m_show_tray_icon); 180 181 if (!settings.contains("fMinimizeToTray")) 182 settings.setValue("fMinimizeToTray", false); 183 fMinimizeToTray = settings.value("fMinimizeToTray").toBool() && m_show_tray_icon; 184 185 if (!settings.contains("fMinimizeOnClose")) 186 settings.setValue("fMinimizeOnClose", false); 187 fMinimizeOnClose = settings.value("fMinimizeOnClose").toBool(); 188 189 // Display 190 if (!settings.contains("DisplayBitcoinUnit")) { 191 settings.setValue("DisplayBitcoinUnit", QVariant::fromValue(BitcoinUnit::BTC)); 192 } 193 QVariant unit = settings.value("DisplayBitcoinUnit"); 194 if (unit.canConvert<BitcoinUnit>()) { 195 m_display_bitcoin_unit = unit.value<BitcoinUnit>(); 196 } else { 197 m_display_bitcoin_unit = BitcoinUnit::BTC; 198 settings.setValue("DisplayBitcoinUnit", QVariant::fromValue(m_display_bitcoin_unit)); 199 } 200 201 if (!settings.contains("strThirdPartyTxUrls")) 202 settings.setValue("strThirdPartyTxUrls", ""); 203 strThirdPartyTxUrls = settings.value("strThirdPartyTxUrls", "").toString(); 204 205 if (!settings.contains("fCoinControlFeatures")) 206 settings.setValue("fCoinControlFeatures", false); 207 fCoinControlFeatures = settings.value("fCoinControlFeatures", false).toBool(); 208 209 if (!settings.contains("enable_psbt_controls")) { 210 settings.setValue("enable_psbt_controls", false); 211 } 212 m_enable_psbt_controls = settings.value("enable_psbt_controls", false).toBool(); 213 214 // These are shared with the core or have a command-line parameter 215 // and we want command-line parameters to overwrite the GUI settings. 216 for (OptionID option : {DatabaseCache, ThreadsScriptVerif, SpendZeroConfChange, ExternalSignerPath, 217 MapPortNatpmp, Listen, Server, Prune, ProxyUse, ProxyUseTor, Language}) { 218 std::string setting = SettingName(option); 219 if (node().isSettingIgnored(setting)) addOverriddenOption("-" + setting); 220 try { 221 getOption(option); 222 } catch (const std::exception& e) { 223 // This handles exceptions thrown by univalue that can happen if 224 // settings in settings.json don't have the expected types. 225 error.original = strprintf("Could not read setting \"%s\", %s.", setting, e.what()); 226 error.translated = tr("Could not read setting \"%1\", %2.").arg(QString::fromStdString(setting), e.what()).toStdString(); 227 return false; 228 } 229 } 230 231 // If setting doesn't exist create it with defaults. 232 233 // Main 234 if (!settings.contains("strDataDir")) 235 settings.setValue("strDataDir", GUIUtil::getDefaultDataDirectory()); 236 237 // Wallet 238 #ifdef ENABLE_WALLET 239 if (!settings.contains("SubFeeFromAmount")) { 240 settings.setValue("SubFeeFromAmount", false); 241 } 242 m_sub_fee_from_amount = settings.value("SubFeeFromAmount", false).toBool(); 243 #endif 244 245 // Display 246 if (settings.contains("FontForMoney")) { 247 m_font_money = FontChoiceFromString(settings.value("FontForMoney").toString()); 248 } else if (settings.contains("UseEmbeddedMonospacedFont")) { 249 if (settings.value("UseEmbeddedMonospacedFont").toBool()) { 250 m_font_money = FontChoiceAbstract::EmbeddedFont; 251 } else { 252 m_font_money = FontChoiceAbstract::BestSystemFont; 253 } 254 } 255 Q_EMIT fontForMoneyChanged(getFontForMoney()); 256 257 m_mask_values = settings.value("mask_values", false).toBool(); 258 259 return true; 260 } 261 262 /** Helper function to copy contents from one QSettings to another. 263 * By using allKeys this also covers nested settings in a hierarchy. 264 */ 265 static void CopySettings(QSettings& dst, const QSettings& src) 266 { 267 for (const QString& key : src.allKeys()) { 268 dst.setValue(key, src.value(key)); 269 } 270 } 271 272 /** Back up a QSettings to an ini-formatted file. */ 273 static void BackupSettings(const fs::path& filename, const QSettings& src) 274 { 275 qInfo() << "Backing up GUI settings to" << GUIUtil::PathToQString(filename); 276 QSettings dst(GUIUtil::PathToQString(filename), QSettings::IniFormat); 277 dst.clear(); 278 CopySettings(dst, src); 279 } 280 281 void OptionsModel::Reset() 282 { 283 // Backup and reset settings.json 284 node().resetSettings(); 285 286 QSettings settings; 287 288 // Backup old settings to chain-specific datadir for troubleshooting 289 BackupSettings(gArgs.GetDataDirNet() / "guisettings.ini.bak", settings); 290 291 // Save the strDataDir setting 292 QString dataDir = GUIUtil::getDefaultDataDirectory(); 293 dataDir = settings.value("strDataDir", dataDir).toString(); 294 295 // Remove all entries from our QSettings object 296 settings.clear(); 297 298 // Set strDataDir 299 settings.setValue("strDataDir", dataDir); 300 301 // Set that this was reset 302 settings.setValue("fReset", true); 303 304 // default setting for OptionsModel::StartAtStartup - disabled 305 if (GUIUtil::GetStartOnSystemStartup()) 306 GUIUtil::SetStartOnSystemStartup(false); 307 } 308 309 int OptionsModel::rowCount(const QModelIndex & parent) const 310 { 311 return OptionIDRowCount; 312 } 313 314 static ProxySetting ParseProxyString(const QString& proxy) 315 { 316 static const ProxySetting default_val = {false, DEFAULT_GUI_PROXY_HOST, QString("%1").arg(DEFAULT_GUI_PROXY_PORT)}; 317 // Handle the case that the setting is not set at all 318 if (proxy.isEmpty()) { 319 return default_val; 320 } 321 uint16_t port{0}; 322 std::string hostname; 323 if (SplitHostPort(proxy.toStdString(), port, hostname) && port != 0) { 324 // Valid and port within the valid range 325 // Check if the hostname contains a colon, indicating an IPv6 address 326 if (hostname.find(':') != std::string::npos) { 327 hostname = "[" + hostname + "]"; // Wrap IPv6 address in brackets 328 } 329 return {true, QString::fromStdString(hostname), QString::number(port)}; 330 } else { // Invalid: return default 331 return default_val; 332 } 333 } 334 335 static ProxySetting ParseProxyString(const std::string& proxy) 336 { 337 return ParseProxyString(QString::fromStdString(proxy)); 338 } 339 340 static std::string ProxyString(bool is_set, QString ip, QString port) 341 { 342 return is_set ? QString(ip + ":" + port).toStdString() : ""; 343 } 344 345 static QString GetDefaultProxyAddress() 346 { 347 return QString("%1:%2").arg(DEFAULT_GUI_PROXY_HOST).arg(DEFAULT_GUI_PROXY_PORT); 348 } 349 350 void OptionsModel::SetPruneTargetGB(int prune_target_gb) 351 { 352 const common::SettingsValue cur_value = node().getPersistentSetting("prune"); 353 const common::SettingsValue new_value = PruneSetting(prune_target_gb > 0, prune_target_gb); 354 355 // Force setting to take effect. It is still safe to change the value at 356 // this point because this function is only called after the intro screen is 357 // shown, before the node starts. 358 node().forceSetting("prune", new_value); 359 360 // Update settings.json if value configured in intro screen is different 361 // from saved value. Avoid writing settings.json if bitcoin.conf value 362 // doesn't need to be overridden. 363 if (PruneEnabled(cur_value) != PruneEnabled(new_value) || 364 PruneSizeGB(cur_value) != PruneSizeGB(new_value)) { 365 // Call UpdateRwSetting() instead of setOption() to avoid setting 366 // RestartRequired flag 367 UpdateRwSetting(node(), Prune, "", new_value); 368 } 369 370 // Keep previous pruning size, if pruning was disabled. 371 if (PruneEnabled(cur_value)) { 372 UpdateRwSetting(node(), Prune, "-prev", PruneEnabled(new_value) ? common::SettingsValue{} : cur_value); 373 } 374 } 375 376 // read QSettings values and return them 377 QVariant OptionsModel::data(const QModelIndex & index, int role) const 378 { 379 if(role == Qt::EditRole) 380 { 381 return getOption(OptionID(index.row())); 382 } 383 return QVariant(); 384 } 385 386 // write QSettings values 387 bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, int role) 388 { 389 bool successful = true; /* set to false on parse error */ 390 if(role == Qt::EditRole) 391 { 392 successful = setOption(OptionID(index.row()), value); 393 } 394 395 Q_EMIT dataChanged(index, index); 396 397 return successful; 398 } 399 400 // NOLINTNEXTLINE(misc-no-recursion) 401 QVariant OptionsModel::getOption(OptionID option, const std::string& suffix) const 402 { 403 auto setting = [&]{ return node().getPersistentSetting(SettingName(option) + suffix); }; 404 405 QSettings settings; 406 switch (option) { 407 case StartAtStartup: 408 return GUIUtil::GetStartOnSystemStartup(); 409 case ShowTrayIcon: 410 return m_show_tray_icon; 411 case MinimizeToTray: 412 return fMinimizeToTray; 413 case MapPortNatpmp: 414 return SettingToBool(setting(), DEFAULT_NATPMP); 415 case MinimizeOnClose: 416 return fMinimizeOnClose; 417 418 // default proxy 419 case ProxyUse: 420 case ProxyUseTor: 421 return ParseProxyString(SettingToString(setting(), "")).is_set; 422 case ProxyIP: 423 case ProxyIPTor: { 424 ProxySetting proxy = ParseProxyString(SettingToString(setting(), "")); 425 if (proxy.is_set) { 426 return proxy.ip; 427 } else if (suffix.empty()) { 428 return getOption(option, "-prev"); 429 } else { 430 return ParseProxyString(GetDefaultProxyAddress().toStdString()).ip; 431 } 432 } 433 case ProxyPort: 434 case ProxyPortTor: { 435 ProxySetting proxy = ParseProxyString(SettingToString(setting(), "")); 436 if (proxy.is_set) { 437 return proxy.port; 438 } else if (suffix.empty()) { 439 return getOption(option, "-prev"); 440 } else { 441 return ParseProxyString(GetDefaultProxyAddress().toStdString()).port; 442 } 443 } 444 445 #ifdef ENABLE_WALLET 446 case SpendZeroConfChange: 447 return SettingToBool(setting(), wallet::DEFAULT_SPEND_ZEROCONF_CHANGE); 448 case ExternalSignerPath: 449 return QString::fromStdString(SettingToString(setting(), "")); 450 case SubFeeFromAmount: 451 return m_sub_fee_from_amount; 452 #endif 453 case DisplayUnit: 454 return QVariant::fromValue(m_display_bitcoin_unit); 455 case ThirdPartyTxUrls: 456 return strThirdPartyTxUrls; 457 case Language: 458 return QString::fromStdString(SettingToString(setting(), "")); 459 case FontForMoney: 460 return QVariant::fromValue(m_font_money); 461 case CoinControlFeatures: 462 return fCoinControlFeatures; 463 case EnablePSBTControls: 464 return settings.value("enable_psbt_controls"); 465 case Prune: 466 return PruneEnabled(setting()); 467 case PruneSize: 468 return PruneEnabled(setting()) ? PruneSizeGB(setting()) : 469 suffix.empty() ? getOption(option, "-prev") : 470 DEFAULT_PRUNE_TARGET_GB; 471 case DatabaseCache: 472 return qlonglong(SettingTo<int64_t>(setting(), node::GetDefaultDBCache() >> 20)); 473 case ThreadsScriptVerif: 474 return qlonglong(SettingTo<int64_t>(setting(), DEFAULT_SCRIPTCHECK_THREADS)); 475 case Listen: 476 return SettingToBool(setting(), DEFAULT_LISTEN); 477 case Server: 478 return SettingToBool(setting(), false); 479 case MaskValues: 480 return m_mask_values; 481 default: 482 return QVariant(); 483 } 484 } 485 486 QFont OptionsModel::getFontForChoice(const FontChoice& fc) 487 { 488 QFont f; 489 if (std::holds_alternative<FontChoiceAbstract>(fc)) { 490 f = GUIUtil::fixedPitchFont(fc != UseBestSystemFont); 491 f.setWeight(QFont::Bold); 492 } else { 493 f = std::get<QFont>(fc); 494 } 495 return f; 496 } 497 498 QFont OptionsModel::getFontForMoney() const 499 { 500 return getFontForChoice(m_font_money); 501 } 502 503 // NOLINTNEXTLINE(misc-no-recursion) 504 bool OptionsModel::setOption(OptionID option, const QVariant& value, const std::string& suffix) 505 { 506 auto changed = [&] { return value.isValid() && value != getOption(option, suffix); }; 507 auto update = [&](const common::SettingsValue& value) { return UpdateRwSetting(node(), option, suffix, value); }; 508 509 bool successful = true; /* set to false on parse error */ 510 QSettings settings; 511 512 switch (option) { 513 case StartAtStartup: 514 successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); 515 break; 516 case ShowTrayIcon: 517 m_show_tray_icon = value.toBool(); 518 settings.setValue("fHideTrayIcon", !m_show_tray_icon); 519 Q_EMIT showTrayIconChanged(m_show_tray_icon); 520 break; 521 case MinimizeToTray: 522 fMinimizeToTray = value.toBool(); 523 settings.setValue("fMinimizeToTray", fMinimizeToTray); 524 break; 525 case MapPortNatpmp: // core option - can be changed on-the-fly 526 if (changed()) { 527 update(value.toBool()); 528 node().mapPort(value.toBool()); 529 } 530 break; 531 case MinimizeOnClose: 532 fMinimizeOnClose = value.toBool(); 533 settings.setValue("fMinimizeOnClose", fMinimizeOnClose); 534 break; 535 536 // default proxy 537 case ProxyUse: 538 if (changed()) { 539 if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); 540 update(ProxyString(value.toBool(), getOption(ProxyIP).toString(), getOption(ProxyPort).toString())); 541 if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); 542 if (suffix.empty()) setRestartRequired(true); 543 } 544 break; 545 case ProxyIP: 546 if (changed()) { 547 if (suffix.empty() && !getOption(ProxyUse).toBool()) { 548 setOption(option, value, "-prev"); 549 } else { 550 update(ProxyString(true, value.toString(), getOption(ProxyPort).toString())); 551 } 552 if (suffix.empty() && getOption(ProxyUse).toBool()) setRestartRequired(true); 553 } 554 break; 555 case ProxyPort: 556 if (changed()) { 557 if (suffix.empty() && !getOption(ProxyUse).toBool()) { 558 setOption(option, value, "-prev"); 559 } else { 560 update(ProxyString(true, getOption(ProxyIP).toString(), value.toString())); 561 } 562 if (suffix.empty() && getOption(ProxyUse).toBool()) setRestartRequired(true); 563 } 564 break; 565 566 // separate Tor proxy 567 case ProxyUseTor: 568 if (changed()) { 569 if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); 570 update(ProxyString(value.toBool(), getOption(ProxyIPTor).toString(), getOption(ProxyPortTor).toString())); 571 if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); 572 if (suffix.empty()) setRestartRequired(true); 573 } 574 break; 575 case ProxyIPTor: 576 if (changed()) { 577 if (suffix.empty() && !getOption(ProxyUseTor).toBool()) { 578 setOption(option, value, "-prev"); 579 } else { 580 update(ProxyString(true, value.toString(), getOption(ProxyPortTor).toString())); 581 } 582 if (suffix.empty() && getOption(ProxyUseTor).toBool()) setRestartRequired(true); 583 } 584 break; 585 case ProxyPortTor: 586 if (changed()) { 587 if (suffix.empty() && !getOption(ProxyUseTor).toBool()) { 588 setOption(option, value, "-prev"); 589 } else { 590 update(ProxyString(true, getOption(ProxyIPTor).toString(), value.toString())); 591 } 592 if (suffix.empty() && getOption(ProxyUseTor).toBool()) setRestartRequired(true); 593 } 594 break; 595 596 #ifdef ENABLE_WALLET 597 case SpendZeroConfChange: 598 if (changed()) { 599 update(value.toBool()); 600 setRestartRequired(true); 601 } 602 break; 603 case ExternalSignerPath: 604 if (changed()) { 605 update(value.toString().toStdString()); 606 setRestartRequired(true); 607 } 608 break; 609 case SubFeeFromAmount: 610 m_sub_fee_from_amount = value.toBool(); 611 settings.setValue("SubFeeFromAmount", m_sub_fee_from_amount); 612 break; 613 #endif 614 case DisplayUnit: 615 setDisplayUnit(value); 616 break; 617 case ThirdPartyTxUrls: 618 if (strThirdPartyTxUrls != value.toString()) { 619 strThirdPartyTxUrls = value.toString(); 620 settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls); 621 setRestartRequired(true); 622 } 623 break; 624 case Language: 625 if (changed()) { 626 update(value.toString().toStdString()); 627 setRestartRequired(true); 628 } 629 break; 630 case FontForMoney: 631 { 632 const auto& new_font = value.value<FontChoice>(); 633 if (m_font_money == new_font) break; 634 settings.setValue("FontForMoney", FontChoiceToString(new_font)); 635 m_font_money = new_font; 636 Q_EMIT fontForMoneyChanged(getFontForMoney()); 637 break; 638 } 639 case CoinControlFeatures: 640 fCoinControlFeatures = value.toBool(); 641 settings.setValue("fCoinControlFeatures", fCoinControlFeatures); 642 Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); 643 break; 644 case EnablePSBTControls: 645 m_enable_psbt_controls = value.toBool(); 646 settings.setValue("enable_psbt_controls", m_enable_psbt_controls); 647 break; 648 case Prune: 649 if (changed()) { 650 if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); 651 update(PruneSetting(value.toBool(), getOption(PruneSize).toInt())); 652 if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); 653 if (suffix.empty()) setRestartRequired(true); 654 } 655 break; 656 case PruneSize: 657 if (changed()) { 658 if (suffix.empty() && !getOption(Prune).toBool()) { 659 setOption(option, value, "-prev"); 660 } else { 661 update(PruneSetting(true, ParsePruneSizeGB(value))); 662 } 663 if (suffix.empty() && getOption(Prune).toBool()) setRestartRequired(true); 664 } 665 break; 666 case DatabaseCache: 667 if (changed()) { 668 update(static_cast<int64_t>(value.toLongLong())); 669 setRestartRequired(true); 670 } 671 break; 672 case ThreadsScriptVerif: 673 if (changed()) { 674 update(static_cast<int64_t>(value.toLongLong())); 675 setRestartRequired(true); 676 } 677 break; 678 case Listen: 679 case Server: 680 if (changed()) { 681 update(value.toBool()); 682 setRestartRequired(true); 683 } 684 break; 685 case MaskValues: 686 m_mask_values = value.toBool(); 687 settings.setValue("mask_values", m_mask_values); 688 break; 689 default: 690 break; 691 } 692 693 return successful; 694 } 695 696 void OptionsModel::setDisplayUnit(const QVariant& new_unit) 697 { 698 if (new_unit.isNull() || new_unit.value<BitcoinUnit>() == m_display_bitcoin_unit) return; 699 m_display_bitcoin_unit = new_unit.value<BitcoinUnit>(); 700 QSettings settings; 701 settings.setValue("DisplayBitcoinUnit", QVariant::fromValue(m_display_bitcoin_unit)); 702 Q_EMIT displayUnitChanged(m_display_bitcoin_unit); 703 } 704 705 void OptionsModel::setRestartRequired(bool fRequired) 706 { 707 QSettings settings; 708 return settings.setValue("fRestartRequired", fRequired); 709 } 710 711 bool OptionsModel::isRestartRequired() const 712 { 713 QSettings settings; 714 return settings.value("fRestartRequired", false).toBool(); 715 } 716 717 bool OptionsModel::hasSigner() 718 { 719 return gArgs.GetArg("-signer", "") != ""; 720 } 721 722 void OptionsModel::checkAndMigrate() 723 { 724 // Migration of default values 725 // Check if the QSettings container was already loaded with this client version 726 QSettings settings; 727 static const char strSettingsVersionKey[] = "nSettingsVersion"; 728 int settingsVersion = settings.contains(strSettingsVersionKey) ? settings.value(strSettingsVersionKey).toInt() : 0; 729 if (settingsVersion < CLIENT_VERSION) 730 { 731 // -dbcache was bumped from 100 to 300 in 0.13 732 // see https://github.com/bitcoin/bitcoin/pull/8273 733 // force people to upgrade to the new value if they are using 100MB 734 if (settingsVersion < 130000 && settings.contains("nDatabaseCache") && settings.value("nDatabaseCache").toLongLong() == 100) 735 settings.setValue("nDatabaseCache", (qint64)(DEFAULT_DB_CACHE >> 20)); 736 737 settings.setValue(strSettingsVersionKey, CLIENT_VERSION); 738 } 739 740 // Overwrite the 'addrProxy' setting in case it has been set to an illegal 741 // default value (see issue #12623; PR #12650). 742 if (settings.contains("addrProxy") && settings.value("addrProxy").toString().endsWith("%2")) { 743 settings.setValue("addrProxy", GetDefaultProxyAddress()); 744 } 745 746 // Overwrite the 'addrSeparateProxyTor' setting in case it has been set to an illegal 747 // default value (see issue #12623; PR #12650). 748 if (settings.contains("addrSeparateProxyTor") && settings.value("addrSeparateProxyTor").toString().endsWith("%2")) { 749 settings.setValue("addrSeparateProxyTor", GetDefaultProxyAddress()); 750 } 751 752 // Migrate and delete legacy GUI settings that have now moved to <datadir>/settings.json. 753 auto migrate_setting = [&](OptionID option, const QString& qt_name) { 754 if (!settings.contains(qt_name)) return; 755 QVariant value = settings.value(qt_name); 756 if (node().getPersistentSetting(SettingName(option)).isNull()) { 757 if (option == ProxyIP) { 758 ProxySetting parsed = ParseProxyString(value.toString()); 759 setOption(ProxyIP, parsed.ip); 760 setOption(ProxyPort, parsed.port); 761 } else if (option == ProxyIPTor) { 762 ProxySetting parsed = ParseProxyString(value.toString()); 763 setOption(ProxyIPTor, parsed.ip); 764 setOption(ProxyPortTor, parsed.port); 765 } else { 766 setOption(option, value); 767 } 768 } 769 settings.remove(qt_name); 770 }; 771 772 migrate_setting(DatabaseCache, "nDatabaseCache"); 773 migrate_setting(ThreadsScriptVerif, "nThreadsScriptVerif"); 774 #ifdef ENABLE_WALLET 775 migrate_setting(SpendZeroConfChange, "bSpendZeroConfChange"); 776 migrate_setting(ExternalSignerPath, "external_signer_path"); 777 #endif 778 migrate_setting(MapPortNatpmp, "fUseNatpmp"); 779 migrate_setting(Listen, "fListen"); 780 migrate_setting(Server, "server"); 781 migrate_setting(PruneSize, "nPruneSize"); 782 migrate_setting(Prune, "bPrune"); 783 migrate_setting(ProxyIP, "addrProxy"); 784 migrate_setting(ProxyUse, "fUseProxy"); 785 migrate_setting(ProxyIPTor, "addrSeparateProxyTor"); 786 migrate_setting(ProxyUseTor, "fUseSeparateProxyTor"); 787 migrate_setting(Language, "language"); 788 789 // In case migrating QSettings caused any settings value to change, rerun 790 // parameter interaction code to update other settings. This is particularly 791 // important for the -listen setting, which should cause -listenonion 792 // and other settings to default to false if it was set to false. 793 // (https://github.com/bitcoin-core/gui/issues/567). 794 node().initParameterInteraction(); 795 }