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