optionsmodel.cpp raw
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 }
796