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