optionsdialog.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/optionsdialog.h>
   8  #include <qt/forms/ui_optionsdialog.h>
   9  
  10  #include <qt/bitcoinunits.h>
  11  #include <qt/clientmodel.h>
  12  #include <qt/guiconstants.h>
  13  #include <qt/guiutil.h>
  14  #include <qt/optionsmodel.h>
  15  
  16  #include <common/system.h>
  17  #include <interfaces/node.h>
  18  #include <netbase.h>
  19  #include <node/caches.h>
  20  #include <node/chainstatemanager_args.h>
  21  #include <util/strencodings.h>
  22  
  23  #include <chrono>
  24  
  25  #include <QApplication>
  26  #include <QDataWidgetMapper>
  27  #include <QDir>
  28  #include <QFontDialog>
  29  #include <QIntValidator>
  30  #include <QLocale>
  31  #include <QMessageBox>
  32  #include <QSystemTrayIcon>
  33  #include <QTimer>
  34  
  35  int setFontChoice(QComboBox* cb, const OptionsModel::FontChoice& fc)
  36  {
  37      int i;
  38      for (i = cb->count(); --i >= 0; ) {
  39          QVariant item_data = cb->itemData(i);
  40          if (!item_data.canConvert<OptionsModel::FontChoice>()) continue;
  41          if (item_data.value<OptionsModel::FontChoice>() == fc) {
  42              break;
  43          }
  44      }
  45      if (i == -1) {
  46          // New item needed
  47          QFont chosen_font = OptionsModel::getFontForChoice(fc);
  48          QSignalBlocker block_currentindexchanged_signal(cb);  // avoid triggering QFontDialog
  49          cb->insertItem(0, QFontInfo(chosen_font).family(), QVariant::fromValue(fc));
  50          i = 0;
  51      }
  52  
  53      cb->setCurrentIndex(i);
  54      return i;
  55  }
  56  
  57  void setupFontOptions(QComboBox* cb, QLabel* preview)
  58  {
  59      QFont embedded_font{GUIUtil::fixedPitchFont(true)};
  60      QFont system_font{GUIUtil::fixedPitchFont(false)};
  61      cb->addItem(QObject::tr("Embedded \"%1\"").arg(QFontInfo(embedded_font).family()), QVariant::fromValue(OptionsModel::FontChoice{OptionsModel::FontChoiceAbstract::EmbeddedFont}));
  62      cb->addItem(QObject::tr("Default system font \"%1\"").arg(QFontInfo(system_font).family()), QVariant::fromValue(OptionsModel::FontChoice{OptionsModel::FontChoiceAbstract::BestSystemFont}));
  63      cb->addItem(QObject::tr("Custom…"));
  64  
  65      const auto& on_font_choice_changed = [cb, preview](int index) {
  66          static int previous_index = -1;
  67          QVariant item_data = cb->itemData(index);
  68          QFont f;
  69          if (item_data.canConvert<OptionsModel::FontChoice>()) {
  70              f = OptionsModel::getFontForChoice(item_data.value<OptionsModel::FontChoice>());
  71          } else {
  72              bool ok;
  73              f = QFontDialog::getFont(&ok, GUIUtil::fixedPitchFont(false), cb->parentWidget());
  74              if (!ok) {
  75                  cb->setCurrentIndex(previous_index);
  76                  return;
  77              }
  78              index = setFontChoice(cb, OptionsModel::FontChoice{f});
  79          }
  80          if (preview) {
  81              preview->setFont(f);
  82          }
  83          previous_index = index;
  84      };
  85      QObject::connect(cb, QOverload<int>::of(&QComboBox::currentIndexChanged), on_font_choice_changed);
  86      on_font_choice_changed(cb->currentIndex());
  87  }
  88  
  89  OptionsDialog::OptionsDialog(QWidget* parent, bool enableWallet)
  90      : QDialog(parent, GUIUtil::dialog_flags | Qt::WindowMaximizeButtonHint),
  91        ui(new Ui::OptionsDialog)
  92  {
  93      ui->setupUi(this);
  94  
  95      ui->verticalLayout->setStretchFactor(ui->tabWidget, 1);
  96  
  97      /* Main elements init */
  98      ui->databaseCache->setRange(MIN_DB_CACHE >> 20, std::numeric_limits<int>::max());
  99      ui->threadsScriptVerif->setMinimum(-GetNumCores());
 100      ui->threadsScriptVerif->setMaximum(MAX_SCRIPTCHECK_THREADS);
 101      ui->pruneWarning->setVisible(false);
 102      ui->pruneWarning->setStyleSheet("QLabel { color: red; }");
 103  
 104      ui->pruneSize->setEnabled(false);
 105      connect(ui->prune, &QPushButton::toggled, ui->pruneSize, &QWidget::setEnabled);
 106  
 107      /* Network elements init */
 108      ui->proxyIp->setEnabled(false);
 109      ui->proxyPort->setEnabled(false);
 110      ui->proxyPort->setValidator(new QIntValidator(1, 65535, this));
 111  
 112      ui->proxyIpTor->setEnabled(false);
 113      ui->proxyPortTor->setEnabled(false);
 114      ui->proxyPortTor->setValidator(new QIntValidator(1, 65535, this));
 115  
 116      connect(ui->connectSocks, &QPushButton::toggled, ui->proxyIp, &QWidget::setEnabled);
 117      connect(ui->connectSocks, &QPushButton::toggled, ui->proxyPort, &QWidget::setEnabled);
 118      connect(ui->connectSocks, &QPushButton::toggled, this, &OptionsDialog::updateProxyValidationState);
 119  
 120      connect(ui->connectSocksTor, &QPushButton::toggled, ui->proxyIpTor, &QWidget::setEnabled);
 121      connect(ui->connectSocksTor, &QPushButton::toggled, ui->proxyPortTor, &QWidget::setEnabled);
 122      connect(ui->connectSocksTor, &QPushButton::toggled, this, &OptionsDialog::updateProxyValidationState);
 123  
 124      /* Window elements init */
 125  #ifdef Q_OS_MACOS
 126      /* remove Window tab on Mac */
 127      ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWindow));
 128      /* hide launch at startup option on macOS */
 129      ui->bitcoinAtStartup->setVisible(false);
 130      ui->verticalLayout_Main->removeWidget(ui->bitcoinAtStartup);
 131      ui->verticalLayout_Main->removeItem(ui->horizontalSpacer_0_Main);
 132  #endif
 133  
 134      /* remove Wallet tab and 3rd party-URL textbox in case of -disablewallet */
 135      if (!enableWallet) {
 136          ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWallet));
 137          ui->thirdPartyTxUrlsLabel->setVisible(false);
 138          ui->thirdPartyTxUrls->setVisible(false);
 139      }
 140  
 141  #ifdef ENABLE_EXTERNAL_SIGNER
 142      ui->externalSignerPath->setToolTip(ui->externalSignerPath->toolTip().arg(CLIENT_NAME));
 143  #else
 144      //: "External signing" means using devices such as hardware wallets.
 145      ui->externalSignerPath->setToolTip(tr("Compiled without external signing support (required for external signing)"));
 146      ui->externalSignerPath->setEnabled(false);
 147  #endif
 148      /* Display elements init */
 149      QDir translations(":translations");
 150  
 151      ui->bitcoinAtStartup->setToolTip(ui->bitcoinAtStartup->toolTip().arg(CLIENT_NAME));
 152      ui->bitcoinAtStartup->setText(ui->bitcoinAtStartup->text().arg(CLIENT_NAME));
 153  
 154      ui->openBitcoinConfButton->setToolTip(ui->openBitcoinConfButton->toolTip().arg(CLIENT_NAME));
 155  
 156      ui->lang->setToolTip(ui->lang->toolTip().arg(CLIENT_NAME));
 157      ui->lang->addItem(QString("(") + tr("default") + QString(")"), QVariant(""));
 158      for (const QString &langStr : translations.entryList())
 159      {
 160          QLocale locale(langStr);
 161  
 162          /** check if the locale name consists of 2 parts (language_country) */
 163          if(langStr.contains("_"))
 164          {
 165              /** display language strings as "native language - native country/territory (locale name)", e.g. "Deutsch - Deutschland (de)" */
 166              ui->lang->addItem(locale.nativeLanguageName() + QString(" - ") +
 167                                locale.nativeTerritoryName() +
 168                                QString(" (") + langStr + QString(")"), QVariant(langStr));
 169  
 170          }
 171          else
 172          {
 173              /** display language strings as "native language (locale name)", e.g. "Deutsch (de)" */
 174              ui->lang->addItem(locale.nativeLanguageName() + QString(" (") + langStr + QString(")"), QVariant(langStr));
 175          }
 176      }
 177      ui->unit->setModel(new BitcoinUnits(this));
 178  
 179      /* Widget-to-option mapper */
 180      mapper = new QDataWidgetMapper(this);
 181      mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
 182      mapper->setOrientation(Qt::Vertical);
 183  
 184      GUIUtil::ItemDelegate* delegate = new GUIUtil::ItemDelegate(mapper);
 185      connect(delegate, &GUIUtil::ItemDelegate::keyEscapePressed, this, &OptionsDialog::reject);
 186      mapper->setItemDelegate(delegate);
 187  
 188      /* setup/change UI elements when proxy IPs are invalid/valid */
 189      ui->proxyIp->setCheckValidator(new ProxyAddressValidator(parent));
 190      ui->proxyIpTor->setCheckValidator(new ProxyAddressValidator(parent));
 191      connect(ui->proxyIp, &QValidatedLineEdit::validationDidChange, this, &OptionsDialog::updateProxyValidationState);
 192      connect(ui->proxyIpTor, &QValidatedLineEdit::validationDidChange, this, &OptionsDialog::updateProxyValidationState);
 193      connect(ui->proxyPort, &QLineEdit::textChanged, this, &OptionsDialog::updateProxyValidationState);
 194      connect(ui->proxyPortTor, &QLineEdit::textChanged, this, &OptionsDialog::updateProxyValidationState);
 195  
 196      if (!QSystemTrayIcon::isSystemTrayAvailable()) {
 197          ui->showTrayIcon->setChecked(false);
 198          ui->showTrayIcon->setEnabled(false);
 199          ui->minimizeToTray->setChecked(false);
 200          ui->minimizeToTray->setEnabled(false);
 201      }
 202  
 203      setupFontOptions(ui->moneyFont, ui->moneyFont_preview);
 204  
 205      GUIUtil::handleCloseWindowShortcut(this);
 206  }
 207  
 208  OptionsDialog::~OptionsDialog()
 209  {
 210      delete ui;
 211  }
 212  
 213  void OptionsDialog::setClientModel(ClientModel* client_model)
 214  {
 215      m_client_model = client_model;
 216  }
 217  
 218  void OptionsDialog::setModel(OptionsModel *_model)
 219  {
 220      this->model = _model;
 221  
 222      if(_model)
 223      {
 224          /* check if client restart is needed and show persistent message */
 225          if (_model->isRestartRequired())
 226              showRestartWarning(true);
 227  
 228          // Prune values are in GB to be consistent with intro.cpp
 229          static constexpr uint64_t nMinDiskSpace = (MIN_DISK_SPACE_FOR_BLOCK_FILES / GB_BYTES) + (MIN_DISK_SPACE_FOR_BLOCK_FILES % GB_BYTES) ? 1 : 0;
 230          ui->pruneSize->setRange(nMinDiskSpace, std::numeric_limits<int>::max());
 231  
 232          QString strLabel = _model->getOverriddenByCommandLine();
 233          if (strLabel.isEmpty())
 234              strLabel = tr("none");
 235          ui->overriddenByCommandLineLabel->setText(strLabel);
 236  
 237          mapper->setModel(_model);
 238          setMapper();
 239          mapper->toFirst();
 240  
 241          const auto& font_for_money = _model->data(_model->index(OptionsModel::FontForMoney, 0), Qt::EditRole).value<OptionsModel::FontChoice>();
 242          setFontChoice(ui->moneyFont, font_for_money);
 243  
 244          updateDefaultProxyNets();
 245      }
 246  
 247      /* warn when one of the following settings changes by user action (placed here so init via mapper doesn't trigger them) */
 248  
 249      /* Main */
 250      connect(ui->prune, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
 251      connect(ui->prune, &QCheckBox::clicked, this, &OptionsDialog::togglePruneWarning);
 252      connect(ui->pruneSize, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
 253      connect(ui->databaseCache, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
 254      connect(ui->externalSignerPath, &QLineEdit::textChanged, [this]{ showRestartWarning(); });
 255      connect(ui->threadsScriptVerif, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
 256      /* Wallet */
 257      connect(ui->spendZeroConfChange, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
 258      /* Network */
 259      connect(ui->allowIncoming, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
 260      connect(ui->enableServer, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
 261      connect(ui->connectSocks, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
 262      connect(ui->connectSocksTor, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
 263      /* Display */
 264      connect(ui->lang, qOverload<>(&QValueComboBox::valueChanged), [this]{ showRestartWarning(); });
 265      connect(ui->thirdPartyTxUrls, &QLineEdit::textChanged, [this]{ showRestartWarning(); });
 266  }
 267  
 268  void OptionsDialog::setCurrentTab(OptionsDialog::Tab tab)
 269  {
 270      QWidget *tab_widget = nullptr;
 271      if (tab == OptionsDialog::Tab::TAB_NETWORK) tab_widget = ui->tabNetwork;
 272      if (tab == OptionsDialog::Tab::TAB_MAIN) tab_widget = ui->tabMain;
 273      if (tab_widget && ui->tabWidget->currentWidget() != tab_widget) {
 274          ui->tabWidget->setCurrentWidget(tab_widget);
 275      }
 276  }
 277  
 278  void OptionsDialog::setMapper()
 279  {
 280      /* Main */
 281      mapper->addMapping(ui->bitcoinAtStartup, OptionsModel::StartAtStartup);
 282      mapper->addMapping(ui->threadsScriptVerif, OptionsModel::ThreadsScriptVerif);
 283      mapper->addMapping(ui->databaseCache, OptionsModel::DatabaseCache);
 284      mapper->addMapping(ui->prune, OptionsModel::Prune);
 285      mapper->addMapping(ui->pruneSize, OptionsModel::PruneSize);
 286  
 287      /* Wallet */
 288      mapper->addMapping(ui->spendZeroConfChange, OptionsModel::SpendZeroConfChange);
 289      mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures);
 290      mapper->addMapping(ui->subFeeFromAmount, OptionsModel::SubFeeFromAmount);
 291      mapper->addMapping(ui->externalSignerPath, OptionsModel::ExternalSignerPath);
 292      mapper->addMapping(ui->m_enable_psbt_controls, OptionsModel::EnablePSBTControls);
 293  
 294      /* Network */
 295      mapper->addMapping(ui->mapPortNatpmp, OptionsModel::MapPortNatpmp);
 296      mapper->addMapping(ui->allowIncoming, OptionsModel::Listen);
 297      mapper->addMapping(ui->enableServer, OptionsModel::Server);
 298  
 299      mapper->addMapping(ui->connectSocks, OptionsModel::ProxyUse);
 300      mapper->addMapping(ui->proxyIp, OptionsModel::ProxyIP);
 301      mapper->addMapping(ui->proxyPort, OptionsModel::ProxyPort);
 302  
 303      mapper->addMapping(ui->connectSocksTor, OptionsModel::ProxyUseTor);
 304      mapper->addMapping(ui->proxyIpTor, OptionsModel::ProxyIPTor);
 305      mapper->addMapping(ui->proxyPortTor, OptionsModel::ProxyPortTor);
 306  
 307      /* Window */
 308  #ifndef Q_OS_MACOS
 309      if (QSystemTrayIcon::isSystemTrayAvailable()) {
 310          mapper->addMapping(ui->showTrayIcon, OptionsModel::ShowTrayIcon);
 311          mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray);
 312      }
 313      mapper->addMapping(ui->minimizeOnClose, OptionsModel::MinimizeOnClose);
 314  #endif
 315  
 316      /* Display */
 317      mapper->addMapping(ui->lang, OptionsModel::Language);
 318      mapper->addMapping(ui->unit, OptionsModel::DisplayUnit);
 319      mapper->addMapping(ui->thirdPartyTxUrls, OptionsModel::ThirdPartyTxUrls);
 320  }
 321  
 322  void OptionsDialog::setOkButtonState(bool fState)
 323  {
 324      ui->okButton->setEnabled(fState);
 325  }
 326  
 327  void OptionsDialog::on_resetButton_clicked()
 328  {
 329      if (model) {
 330          // confirmation dialog
 331          /*: Text explaining that the settings changed will not come into effect
 332              until the client is restarted. */
 333          QString reset_dialog_text = tr("Client restart required to activate changes.") + "<br><br>";
 334          /*: Text explaining to the user that the client's current settings
 335              will be backed up at a specific location. %1 is a stand-in
 336              argument for the backup location's path. */
 337          reset_dialog_text.append(tr("Current settings will be backed up at \"%1\".").arg(m_client_model->dataDir()) + "<br><br>");
 338          /*: Text asking the user to confirm if they would like to proceed
 339              with a client shutdown. */
 340          reset_dialog_text.append(tr("Client will be shut down. Do you want to proceed?"));
 341          //: Window title text of pop-up window shown when the user has chosen to reset options.
 342          QMessageBox::StandardButton btnRetVal = QMessageBox::question(this, tr("Confirm options reset"),
 343              reset_dialog_text, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
 344  
 345          if (btnRetVal == QMessageBox::Cancel)
 346              return;
 347  
 348          /* reset all options and close GUI */
 349          model->Reset();
 350          close();
 351          Q_EMIT quitOnReset();
 352      }
 353  }
 354  
 355  void OptionsDialog::on_openBitcoinConfButton_clicked()
 356  {
 357      QMessageBox config_msgbox(this);
 358      config_msgbox.setIcon(QMessageBox::Information);
 359      //: Window title text of pop-up box that allows opening up of configuration file.
 360      config_msgbox.setWindowTitle(tr("Configuration options"));
 361      /*: Explanatory text about the priority order of instructions considered by client.
 362          The order from high to low being: command-line, configuration file, GUI settings. */
 363      config_msgbox.setText(tr("The configuration file is used to specify advanced user options which override GUI settings. "
 364                               "Additionally, any command-line options will override this configuration file."));
 365  
 366      QPushButton* open_button = config_msgbox.addButton(tr("Continue"), QMessageBox::ActionRole);
 367      config_msgbox.addButton(tr("Cancel"), QMessageBox::RejectRole);
 368      open_button->setDefault(true);
 369  
 370      config_msgbox.exec();
 371  
 372      if (config_msgbox.clickedButton() != open_button) return;
 373  
 374      /* show an error if there was some problem opening the file */
 375      if (!GUIUtil::openBitcoinConf())
 376          QMessageBox::critical(this, tr("Error"), tr("The configuration file could not be opened."));
 377  }
 378  
 379  void OptionsDialog::on_okButton_clicked()
 380  {
 381      model->setData(model->index(OptionsModel::FontForMoney, 0), ui->moneyFont->itemData(ui->moneyFont->currentIndex()));
 382  
 383      mapper->submit();
 384      accept();
 385      updateDefaultProxyNets();
 386  }
 387  
 388  void OptionsDialog::on_cancelButton_clicked()
 389  {
 390      reject();
 391  }
 392  
 393  void OptionsDialog::on_showTrayIcon_stateChanged(int state)
 394  {
 395      if (state == Qt::Checked) {
 396          ui->minimizeToTray->setEnabled(true);
 397      } else {
 398          ui->minimizeToTray->setChecked(false);
 399          ui->minimizeToTray->setEnabled(false);
 400      }
 401  }
 402  
 403  void OptionsDialog::togglePruneWarning(bool enabled)
 404  {
 405      ui->pruneWarning->setVisible(!ui->pruneWarning->isVisible());
 406  }
 407  
 408  void OptionsDialog::showRestartWarning(bool fPersistent)
 409  {
 410      ui->statusLabel->setStyleSheet("QLabel { color: red; }");
 411  
 412      if(fPersistent)
 413      {
 414          ui->statusLabel->setText(tr("Client restart required to activate changes."));
 415      }
 416      else
 417      {
 418          ui->statusLabel->setText(tr("This change would require a client restart."));
 419          // clear non-persistent status label after 10 seconds
 420          // Todo: should perhaps be a class attribute, if we extend the use of statusLabel
 421          QTimer::singleShot(10s, this, &OptionsDialog::clearStatusLabel);
 422      }
 423  }
 424  
 425  void OptionsDialog::clearStatusLabel()
 426  {
 427      ui->statusLabel->clear();
 428      if (model && model->isRestartRequired()) {
 429          showRestartWarning(true);
 430      }
 431  }
 432  
 433  void OptionsDialog::updateProxyValidationState()
 434  {
 435      QValidatedLineEdit *pUiProxyIp = ui->proxyIp;
 436      QValidatedLineEdit *otherProxyWidget = (pUiProxyIp == ui->proxyIpTor) ? ui->proxyIp : ui->proxyIpTor;
 437      if (pUiProxyIp->isValid() && (!ui->proxyPort->isEnabled() || ui->proxyPort->text().toInt() > 0) && (!ui->proxyPortTor->isEnabled() || ui->proxyPortTor->text().toInt() > 0))
 438      {
 439          setOkButtonState(otherProxyWidget->isValid()); //only enable ok button if both proxies are valid
 440          clearStatusLabel();
 441      }
 442      else
 443      {
 444          setOkButtonState(false);
 445          ui->statusLabel->setStyleSheet("QLabel { color: red; }");
 446          ui->statusLabel->setText(tr("The supplied proxy address is invalid."));
 447      }
 448  }
 449  
 450  void OptionsDialog::updateDefaultProxyNets()
 451  {
 452      std::string proxyIpText{ui->proxyIp->text().toStdString()};
 453      if (!IsUnixSocketPath(proxyIpText)) {
 454          const std::optional<CNetAddr> ui_proxy_netaddr{LookupHost(proxyIpText, /*fAllowLookup=*/false)};
 455          const CService ui_proxy{ui_proxy_netaddr.value_or(CNetAddr{}), ui->proxyPort->text().toUShort()};
 456          proxyIpText = ui_proxy.ToStringAddrPort();
 457      }
 458  
 459      const auto proxy_ipv4 = model->node().getProxy(NET_IPV4);
 460      ui->proxyReachIPv4->setChecked(proxy_ipv4 && proxy_ipv4->ToString() == proxyIpText);
 461  
 462      const auto proxy_ipv6 = model->node().getProxy(NET_IPV6);
 463      ui->proxyReachIPv6->setChecked(proxy_ipv6 && proxy_ipv6->ToString() == proxyIpText);
 464  
 465      const auto proxy_onion = model->node().getProxy(NET_ONION);
 466      ui->proxyReachTor->setChecked(proxy_onion && proxy_onion->ToString() == proxyIpText);
 467  }
 468  
 469  ProxyAddressValidator::ProxyAddressValidator(QObject *parent) :
 470  QValidator(parent)
 471  {
 472  }
 473  
 474  QValidator::State ProxyAddressValidator::validate(QString &input, int &pos) const
 475  {
 476      Q_UNUSED(pos);
 477      uint16_t port{0};
 478      std::string hostname;
 479      if (!SplitHostPort(input.toStdString(), port, hostname) || port != 0) return QValidator::Invalid;
 480  
 481      CService serv(LookupNumeric(input.toStdString(), DEFAULT_GUI_PROXY_PORT));
 482      Proxy addrProxy = Proxy(serv, /*tor_stream_isolation=*/true);
 483      if (addrProxy.IsValid())
 484          return QValidator::Acceptable;
 485  
 486      return QValidator::Invalid;
 487  }
 488