diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index fea759dee..7c37b24ea 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -1,903 +1,903 @@ OptionsDialog 0 0 560 440 Options true 0 &Main Automatically start %1 after logging in to the system. &Start %1 on system login Qt::Horizontal 40 5 Disables some advanced features but all blocks will still be fully validated. Reverting this setting requires re-downloading the entire blockchain. Actual disk usage may be somewhat higher. Prune &block storage to GB Qt::PlainText Qt::Horizontal 40 20 Reverting this setting requires re-downloading the entire blockchain. Qt::PlainText Size of &database cache Qt::PlainText databaseCache MiB Qt::PlainText Qt::Horizontal 40 20 Number of script &verification threads Qt::PlainText threadsScriptVerif (0 = auto, <0 = leave that many cores free) Qt::Horizontal 40 20 Qt::Vertical 20 40 W&allet Expert Whether to show coin control features or not. Enable coin &control features If you disable the spending of unconfirmed change, the change from a transaction cannot be used until that transaction has at least one confirmation. This also affects how your balance is computed. &Spend unconfirmed change Qt::Vertical 20 40 &Network Automatically open the Bitcoin client port on the router. This only works when your router supports UPnP and it is enabled. Map port using &UPnP Accept connections from outside. Allow incomin&g connections Connect to the Bitcoin network through a SOCKS5 proxy. &Connect through SOCKS5 proxy (default proxy): Proxy &IP: Qt::PlainText proxyIp 140 0 140 16777215 IP address of the proxy (e.g. IPv4: 127.0.0.1 / IPv6: ::1) &Port: Qt::PlainText proxyPort 55 0 55 16777215 Port of the proxy (e.g. 9050) Qt::Horizontal 40 20 Used for reaching peers via: Qt::PlainText false Shows if the supplied default SOCKS5 proxy is used to reach peers via this network type. IPv4 Qt::PlainText false Shows if the supplied default SOCKS5 proxy is used to reach peers via this network type. IPv6 Qt::PlainText false Shows if the supplied default SOCKS5 proxy is used to reach peers via this network type. Tor Qt::PlainText Qt::Horizontal 40 20 Connect to the Bitcoin network through a separate SOCKS5 proxy for Tor hidden services. Use separate SOCKS&5 proxy to reach peers via Tor hidden services: Proxy &IP: Qt::PlainText proxyIpTor 140 0 140 16777215 IP address of the proxy (e.g. IPv4: 127.0.0.1 / IPv6: ::1) &Port: Qt::PlainText proxyPortTor 55 0 55 16777215 Port of the proxy (e.g. 9050) Qt::Horizontal 40 20 Qt::Vertical 20 40 &Window Hide the icon from the system tray. &Hide tray icon Show only a tray icon after minimizing the window. &Minimize to the tray instead of the taskbar Minimize instead of exit the application when the window is closed. When this option is enabled, the application will be closed only after selecting Exit in the menu. M&inimize on close Qt::Vertical 20 40 &Display User Interface &language: Qt::PlainText lang The user interface language can be set here. This setting will take effect after restarting %1. &Unit to show amounts in: Qt::PlainText unit Choose the default subdivision unit to show in the interface and when sending coins. Third party URLs (e.g. a block explorer) that appear in the transactions tab as context menu items. %s in the URL is replaced by transaction hash. Multiple URLs are separated by vertical bar |. &Third party transaction URLs thirdPartyTxUrls - + Third party URLs (e.g. a block explorer) that appear in the transactions tab as context menu items. %s in the URL is replaced by transaction hash. Multiple URLs are separated by vertical bar |. https://example.com/tx/%s Qt::Vertical 20 40 Options set in this dialog are overridden by the command line or in the configuration file: Qt::PlainText Qt::Horizontal 40 20 Qt::PlainText true Open the %1 configuration file from the working directory. Open Configuration File false Reset all client options to default. &Reset Options false Qt::Horizontal 40 48 200 0 75 true Qt::PlainText true Qt::Horizontal 40 48 Qt::Vertical 20 40 &OK false true &Cancel false QValidatedLineEdit QLineEdit
qt/qvalidatedlineedit.h
QValueComboBox QComboBox
qt/qvaluecombobox.h
diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 0418caeff..7e7dc28df 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -1,426 +1,461 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include #include #include // for -dbcache defaults #include // for DEFAULT_SCRIPTCHECK_THREADS and MAX_SCRIPTCHECK_THREADS #include #include #include #include #include #include #include OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : QDialog(parent), ui(new Ui::OptionsDialog), model(nullptr), mapper(nullptr) { ui->setupUi(this); /* Main elements init */ ui->databaseCache->setMinimum(MIN_DB_CACHE_MB); ui->databaseCache->setMaximum(MAX_DB_CACHE_MB); ui->threadsScriptVerif->setMinimum(-GetNumCores()); ui->threadsScriptVerif->setMaximum(MAX_SCRIPTCHECK_THREADS); ui->pruneWarning->setVisible(false); ui->pruneWarning->setStyleSheet("QLabel { color: red; }"); ui->pruneSize->setEnabled(false); connect(ui->prune, &QPushButton::toggled, ui->pruneSize, &QWidget::setEnabled); /* Network elements init */ #ifndef USE_UPNP ui->mapPortUpnp->setEnabled(false); #endif ui->proxyIp->setEnabled(false); ui->proxyPort->setEnabled(false); ui->proxyPort->setValidator(new QIntValidator(1, 65535, this)); ui->proxyIpTor->setEnabled(false); ui->proxyPortTor->setEnabled(false); ui->proxyPortTor->setValidator(new QIntValidator(1, 65535, this)); connect(ui->connectSocks, &QPushButton::toggled, ui->proxyIp, &QWidget::setEnabled); connect(ui->connectSocks, &QPushButton::toggled, ui->proxyPort, &QWidget::setEnabled); connect(ui->connectSocks, &QPushButton::toggled, this, &OptionsDialog::updateProxyValidationState); connect(ui->connectSocksTor, &QPushButton::toggled, ui->proxyIpTor, &QWidget::setEnabled); connect(ui->connectSocksTor, &QPushButton::toggled, ui->proxyPortTor, &QWidget::setEnabled); connect(ui->connectSocksTor, &QPushButton::toggled, this, &OptionsDialog::updateProxyValidationState); /* Window elements init */ #ifdef Q_OS_MAC /* remove Window tab on Mac */ ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWindow)); /* hide launch at startup option on macOS */ ui->bitcoinAtStartup->setVisible(false); ui->verticalLayout_Main->removeWidget(ui->bitcoinAtStartup); ui->verticalLayout_Main->removeItem(ui->horizontalSpacer_0_Main); #endif /* remove Wallet tab and 3rd party-URL textbox in case of -disablewallet */ if (!enableWallet) { ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWallet)); ui->thirdPartyTxUrlsLabel->setVisible(false); ui->thirdPartyTxUrls->setVisible(false); } /* Display elements init */ QDir translations(":translations"); ui->bitcoinAtStartup->setToolTip( ui->bitcoinAtStartup->toolTip().arg(PACKAGE_NAME)); ui->bitcoinAtStartup->setText( ui->bitcoinAtStartup->text().arg(PACKAGE_NAME)); ui->openBitcoinConfButton->setToolTip( ui->openBitcoinConfButton->toolTip().arg(PACKAGE_NAME)); ui->lang->setToolTip(ui->lang->toolTip().arg(PACKAGE_NAME)); ui->lang->addItem(QString("(") + tr("default") + QString(")"), QVariant("")); for (const QString &langStr : translations.entryList()) { QLocale locale(langStr); /** check if the locale name consists of 2 parts (language_country) */ if (langStr.contains("_")) { /** display language strings as "native language - native country * (locale name)", e.g. "Deutsch - Deutschland (de)" */ ui->lang->addItem(locale.nativeLanguageName() + QString(" - ") + locale.nativeCountryName() + QString(" (") + langStr + QString(")"), QVariant(langStr)); } else { /** display language strings as "native language (locale name)", * e.g. "Deutsch (de)" */ ui->lang->addItem(locale.nativeLanguageName() + QString(" (") + langStr + QString(")"), QVariant(langStr)); } } ui->unit->setModel(new BitcoinUnits(this)); /* Widget-to-option mapper */ mapper = new QDataWidgetMapper(this); mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit); mapper->setOrientation(Qt::Vertical); GUIUtil::ItemDelegate *delegate = new GUIUtil::ItemDelegate(mapper); connect(delegate, &GUIUtil::ItemDelegate::keyEscapePressed, this, &OptionsDialog::reject); mapper->setItemDelegate(delegate); /* setup/change UI elements when proxy IPs are invalid/valid */ ui->proxyIp->setCheckValidator(new ProxyAddressValidator(parent)); ui->proxyIpTor->setCheckValidator(new ProxyAddressValidator(parent)); connect(ui->proxyIp, &QValidatedLineEdit::validationDidChange, this, &OptionsDialog::updateProxyValidationState); connect(ui->proxyIpTor, &QValidatedLineEdit::validationDidChange, this, &OptionsDialog::updateProxyValidationState); connect(ui->proxyPort, &QLineEdit::textChanged, this, &OptionsDialog::updateProxyValidationState); connect(ui->proxyPortTor, &QLineEdit::textChanged, this, &OptionsDialog::updateProxyValidationState); + /* setup/change UI elements when third party tx URLs are invalid/valid */ + ui->thirdPartyTxUrls->setCheckValidator( + new ThirdPartyTxUrlsValidator(parent)); + connect(ui->thirdPartyTxUrls, &QValidatedLineEdit::validationDidChange, + this, &OptionsDialog::updateThirdPartyTxUrlsState); + if (!QSystemTrayIcon::isSystemTrayAvailable()) { ui->hideTrayIcon->setChecked(true); ui->hideTrayIcon->setEnabled(false); ui->minimizeToTray->setChecked(false); ui->minimizeToTray->setEnabled(false); } GUIUtil::handleCloseWindowShortcut(this); } OptionsDialog::~OptionsDialog() { delete ui; } void OptionsDialog::setModel(OptionsModel *_model) { this->model = _model; if (_model) { /* check if client restart is needed and show persistent message */ if (_model->isRestartRequired()) { showRestartWarning(true); } // Prune values are in GB to be consistent with intro.cpp static constexpr uint64_t nMinDiskSpace = (MIN_DISK_SPACE_FOR_BLOCK_FILES / GB_BYTES) + (MIN_DISK_SPACE_FOR_BLOCK_FILES % GB_BYTES) ? 1 : 0; ui->pruneSize->setRange(nMinDiskSpace, std::numeric_limits::max()); QString strLabel = _model->getOverriddenByCommandLine(); if (strLabel.isEmpty()) { strLabel = tr("none"); } ui->overriddenByCommandLineLabel->setText(strLabel); mapper->setModel(_model); setMapper(); mapper->toFirst(); updateDefaultProxyNets(); } /* warn when one of the following settings changes by user action (placed * here so init via mapper doesn't trigger them) */ /* Main */ connect(ui->prune, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); connect(ui->prune, &QCheckBox::clicked, this, &OptionsDialog::togglePruneWarning); connect(ui->pruneSize, static_cast(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); connect(ui->databaseCache, static_cast(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); connect(ui->threadsScriptVerif, static_cast(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); /* Wallet */ connect(ui->spendZeroConfChange, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); /* Network */ connect(ui->allowIncoming, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); connect(ui->connectSocks, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); connect(ui->connectSocksTor, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); /* Display */ connect( ui->lang, static_cast(&QValueComboBox::valueChanged), [this] { showRestartWarning(); }); connect(ui->thirdPartyTxUrls, &QLineEdit::textChanged, [this] { showRestartWarning(); }); } void OptionsDialog::setCurrentTab(OptionsDialog::Tab tab) { QWidget *tab_widget = nullptr; if (tab == OptionsDialog::Tab::TAB_NETWORK) { tab_widget = ui->tabNetwork; } if (tab == OptionsDialog::Tab::TAB_MAIN) { tab_widget = ui->tabMain; } if (tab_widget && ui->tabWidget->currentWidget() != tab_widget) { ui->tabWidget->setCurrentWidget(tab_widget); } } void OptionsDialog::setMapper() { /* Main */ mapper->addMapping(ui->bitcoinAtStartup, OptionsModel::StartAtStartup); mapper->addMapping(ui->threadsScriptVerif, OptionsModel::ThreadsScriptVerif); mapper->addMapping(ui->databaseCache, OptionsModel::DatabaseCache); mapper->addMapping(ui->prune, OptionsModel::Prune); mapper->addMapping(ui->pruneSize, OptionsModel::PruneSize); /* Wallet */ mapper->addMapping(ui->spendZeroConfChange, OptionsModel::SpendZeroConfChange); mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures); /* Network */ mapper->addMapping(ui->mapPortUpnp, OptionsModel::MapPortUPnP); mapper->addMapping(ui->allowIncoming, OptionsModel::Listen); mapper->addMapping(ui->connectSocks, OptionsModel::ProxyUse); mapper->addMapping(ui->proxyIp, OptionsModel::ProxyIP); mapper->addMapping(ui->proxyPort, OptionsModel::ProxyPort); mapper->addMapping(ui->connectSocksTor, OptionsModel::ProxyUseTor); mapper->addMapping(ui->proxyIpTor, OptionsModel::ProxyIPTor); mapper->addMapping(ui->proxyPortTor, OptionsModel::ProxyPortTor); /* Window */ #ifndef Q_OS_MAC if (QSystemTrayIcon::isSystemTrayAvailable()) { mapper->addMapping(ui->hideTrayIcon, OptionsModel::HideTrayIcon); mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray); } mapper->addMapping(ui->minimizeOnClose, OptionsModel::MinimizeOnClose); #endif /* Display */ mapper->addMapping(ui->lang, OptionsModel::Language); mapper->addMapping(ui->unit, OptionsModel::DisplayUnit); mapper->addMapping(ui->thirdPartyTxUrls, OptionsModel::ThirdPartyTxUrls); } void OptionsDialog::setOkButtonState(bool fState) { ui->okButton->setEnabled(fState); } void OptionsDialog::on_resetButton_clicked() { if (model) { // confirmation dialog QMessageBox::StandardButton btnRetVal = QMessageBox::question( this, tr("Confirm options reset"), tr("Client restart required to activate changes.") + "

" + tr("Client will be shut down. Do you want to proceed?"), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); if (btnRetVal == QMessageBox::Cancel) { return; } /* reset all options and close GUI */ model->Reset(); QApplication::quit(); } } void OptionsDialog::on_openBitcoinConfButton_clicked() { /* explain the purpose of the config file */ QMessageBox::information( this, tr("Configuration options"), tr("The configuration file is used to specify advanced user options " "which override GUI settings. Additionally, any command-line " "options will override this configuration file.")); /* show an error if there was some problem opening the file */ if (!GUIUtil::openBitcoinConf()) { QMessageBox::critical( this, tr("Error"), tr("The configuration file could not be opened.")); } } void OptionsDialog::on_okButton_clicked() { mapper->submit(); accept(); updateDefaultProxyNets(); } void OptionsDialog::on_cancelButton_clicked() { reject(); } void OptionsDialog::on_hideTrayIcon_stateChanged(int fState) { if (fState) { ui->minimizeToTray->setChecked(false); ui->minimizeToTray->setEnabled(false); } else { ui->minimizeToTray->setEnabled(true); } } void OptionsDialog::togglePruneWarning(bool enabled) { ui->pruneWarning->setVisible(!ui->pruneWarning->isVisible()); } void OptionsDialog::showRestartWarning(bool fPersistent) { ui->statusLabel->setStyleSheet("QLabel { color: red; }"); if (fPersistent) { ui->statusLabel->setText( tr("Client restart required to activate changes.")); } else { ui->statusLabel->setText( tr("This change would require a client restart.")); // clear non-persistent status label after 10 seconds // TODO: should perhaps be a class attribute, if we extend the use of // statusLabel QTimer::singleShot(10000, this, &OptionsDialog::clearStatusLabel); } } void OptionsDialog::clearStatusLabel() { ui->statusLabel->clear(); if (model && model->isRestartRequired()) { showRestartWarning(true); } } void OptionsDialog::updateProxyValidationState() { QValidatedLineEdit *pUiProxyIp = ui->proxyIp; QValidatedLineEdit *otherProxyWidget = (pUiProxyIp == ui->proxyIpTor) ? ui->proxyIp : ui->proxyIpTor; if (pUiProxyIp->isValid() && (!ui->proxyPort->isEnabled() || ui->proxyPort->text().toInt() > 0) && (!ui->proxyPortTor->isEnabled() || ui->proxyPortTor->text().toInt() > 0)) { // Only enable ok button if both proxys are valid setOkButtonState(otherProxyWidget->isValid()); clearStatusLabel(); } else { setOkButtonState(false); ui->statusLabel->setStyleSheet("QLabel { color: red; }"); ui->statusLabel->setText(tr("The supplied proxy address is invalid.")); } } void OptionsDialog::updateDefaultProxyNets() { proxyType proxy; std::string strProxy; QString strDefaultProxyGUI; model->node().getProxy(NET_IPV4, proxy); strProxy = proxy.proxy.ToStringIP() + ":" + proxy.proxy.ToStringPort(); strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text(); (strProxy == strDefaultProxyGUI.toStdString()) ? ui->proxyReachIPv4->setChecked(true) : ui->proxyReachIPv4->setChecked(false); model->node().getProxy(NET_IPV6, proxy); strProxy = proxy.proxy.ToStringIP() + ":" + proxy.proxy.ToStringPort(); strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text(); (strProxy == strDefaultProxyGUI.toStdString()) ? ui->proxyReachIPv6->setChecked(true) : ui->proxyReachIPv6->setChecked(false); model->node().getProxy(NET_ONION, proxy); strProxy = proxy.proxy.ToStringIP() + ":" + proxy.proxy.ToStringPort(); strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text(); (strProxy == strDefaultProxyGUI.toStdString()) ? ui->proxyReachTor->setChecked(true) : ui->proxyReachTor->setChecked(false); } ProxyAddressValidator::ProxyAddressValidator(QObject *parent) : QValidator(parent) {} QValidator::State ProxyAddressValidator::validate(QString &input, int &pos) const { Q_UNUSED(pos); // Validate the proxy CService serv(LookupNumeric(input.toStdString(), DEFAULT_GUI_PROXY_PORT)); proxyType addrProxy = proxyType(serv, true); if (addrProxy.IsValid()) { return QValidator::Acceptable; } return QValidator::Invalid; } + +void OptionsDialog::updateThirdPartyTxUrlsState() { + QValidatedLineEdit *thirdPartyTxUrls = ui->thirdPartyTxUrls; + if (thirdPartyTxUrls->isValid()) { + // Only enable OK button if the third party tx URLS pattern is valid + setOkButtonState(true); + clearStatusLabel(); + } else { + setOkButtonState(false); + ui->statusLabel->setStyleSheet("QLabel { color: red; }"); + ui->statusLabel->setText( + tr("The third party transaction URLs should start with https://.")); + } +} + +ThirdPartyTxUrlsValidator::ThirdPartyTxUrlsValidator(QObject *parent) + : QValidator(parent) {} + +QValidator::State ThirdPartyTxUrlsValidator::validate(QString &input, + int &pos) const { + Q_UNUSED(pos); + // Check the URL starts with https. All other schemes are rejected for + // security reasons. + if (input.isEmpty() || input.startsWith("https://")) { + return QValidator::Acceptable; + } + + return QValidator::Invalid; +} diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h index 55c163836..86868ef29 100644 --- a/src/qt/optionsdialog.h +++ b/src/qt/optionsdialog.h @@ -1,75 +1,86 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_OPTIONSDIALOG_H #define BITCOIN_QT_OPTIONSDIALOG_H #include #include class OptionsModel; class QValidatedLineEdit; QT_BEGIN_NAMESPACE class QDataWidgetMapper; QT_END_NAMESPACE namespace Ui { class OptionsDialog; } /** Proxy address widget validator, checks for a valid proxy address. */ class ProxyAddressValidator : public QValidator { Q_OBJECT public: explicit ProxyAddressValidator(QObject *parent); State validate(QString &input, int &pos) const override; }; +/** Third party tx URL validator, checks for an https link. */ +class ThirdPartyTxUrlsValidator : public QValidator { + Q_OBJECT + +public: + explicit ThirdPartyTxUrlsValidator(QObject *parent); + + State validate(QString &input, int &pos) const override; +}; + /** Preferences dialog. */ class OptionsDialog : public QDialog { Q_OBJECT public: explicit OptionsDialog(QWidget *parent, bool enableWallet); ~OptionsDialog(); enum Tab { TAB_MAIN, TAB_NETWORK, }; void setModel(OptionsModel *model); void setMapper(); void setCurrentTab(OptionsDialog::Tab tab); private Q_SLOTS: /* set OK button state (enabled / disabled) */ void setOkButtonState(bool fState); void on_resetButton_clicked(); void on_openBitcoinConfButton_clicked(); void on_okButton_clicked(); void on_cancelButton_clicked(); void on_hideTrayIcon_stateChanged(int fState); void togglePruneWarning(bool enabled); void showRestartWarning(bool fPersistent = false); void clearStatusLabel(); void updateProxyValidationState(); /* query the networks, for which the default proxy is used */ void updateDefaultProxyNets(); + void updateThirdPartyTxUrlsState(); Q_SIGNALS: void proxyIpChecks(QValidatedLineEdit *pUiProxyIp, int nProxyPort); private: Ui::OptionsDialog *ui; OptionsModel *model; QDataWidgetMapper *mapper; }; #endif // BITCOIN_QT_OPTIONSDIALOG_H