/ src / qt / optionsmodel.cpp
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  }