From 2314eb0be4c28591d6b30dbc4de9314b125ea7e3 Mon Sep 17 00:00:00 2001 From: shiny <23282055+bigshiny90@users.noreply.github.com> Date: Thu, 11 Sep 2025 14:17:42 -0400 Subject: [PATCH 1/3] GUI/QValidatedLineEdit: add validation control features and fix error highlighting - Add setAllowEmptyInput() to control whether empty input is valid - Add setAllowValidationWhileEditing() for real-time validation - Fix focusInEvent to handle validation-while-editing fields correctly - Fix setEnabled to clear focus before disabling to ensure proper cleanup of invalid visual state - Add changeEvent() handler to update validation colors on palette/theme changes --- src/qt/qvalidatedlineedit.cpp | 36 +++++++++++++++++++++++++++++++---- src/qt/qvalidatedlineedit.h | 11 +++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/qt/qvalidatedlineedit.cpp b/src/qt/qvalidatedlineedit.cpp index 6e1b684289403..e9e4c83d8d235 100644 --- a/src/qt/qvalidatedlineedit.cpp +++ b/src/qt/qvalidatedlineedit.cpp @@ -10,10 +10,13 @@ #include #include +#include +#include #include #include #include #include +#include QValidatedLineEdit::QValidatedLineEdit(QWidget* parent) : QLineEdit(parent) @@ -92,8 +95,13 @@ void QValidatedLineEdit::setValid(bool _valid, bool with_warning, const std::vec void QValidatedLineEdit::focusInEvent(QFocusEvent *evt) { - // Clear invalid flag on focus - setValid(true); + if (!m_allow_validation_while_editing) { + // Clear invalid flag on focus for normal fields + setValid(true); + } else { + // For validation-while-editing fields, recheck validity + checkValidity(); + } QLineEdit::focusInEvent(evt); } @@ -105,10 +113,27 @@ void QValidatedLineEdit::focusOutEvent(QFocusEvent *evt) QLineEdit::focusOutEvent(evt); } +void QValidatedLineEdit::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::PaletteChange) { + if (!valid) { + // Refresh styling using setValid function + setValid(valid); + } + } + + QLineEdit::changeEvent(e); +} + void QValidatedLineEdit::markValid() { // As long as a user is typing ensure we display state as valid - setValid(true); + // unless we're validating while editing + if (m_allow_validation_while_editing) { + checkValidity(); + } else { + setValid(true); + } } void QValidatedLineEdit::clear() @@ -122,6 +147,9 @@ void QValidatedLineEdit::setEnabled(bool enabled) if (!enabled) { // A disabled QValidatedLineEdit should be marked valid + // Clear focus first to trigger focusOutEvent + // required to properly clear invalid visual state + if (hasFocus()) { clearFocus(); } setValid(true); } else @@ -138,7 +166,7 @@ void QValidatedLineEdit::checkValidity() const bool has_warning = checkWarning(); if (text().isEmpty()) { - setValid(true); + setValid(m_allow_empty_input); } else if (hasAcceptableInput()) { diff --git a/src/qt/qvalidatedlineedit.h b/src/qt/qvalidatedlineedit.h index 63fc15de505f3..64777fdaac933 100644 --- a/src/qt/qvalidatedlineedit.h +++ b/src/qt/qvalidatedlineedit.h @@ -7,6 +7,12 @@ #include +QT_BEGIN_NAMESPACE +class QEvent; +class QFocusEvent; +class QValidator; +QT_END_NAMESPACE + /** Line edit that can be marked as "invalid" to show input validation feedback. When marked as invalid, it will get a red background until it is focused. */ @@ -22,16 +28,21 @@ class QValidatedLineEdit : public QLineEdit bool isValid(); void setWarningValidator(const QValidator *); bool hasWarning() const; + void setAllowEmptyInput(bool allow) { m_allow_empty_input = allow; } + void setAllowValidationWhileEditing(bool allow) { m_allow_validation_while_editing = allow; } protected: void focusInEvent(QFocusEvent *evt) override; void focusOutEvent(QFocusEvent *evt) override; + void changeEvent(QEvent *e) override; private: bool valid{true}; const QValidator* checkValidator{nullptr}; bool m_has_warning{false}; const QValidator *m_warning_validator{nullptr}; + bool m_allow_empty_input{true}; + bool m_allow_validation_while_editing{false}; public Q_SLOTS: void setText(const QString&); From 132e486cf50d70839678a8a4fbebd126fcd2f666 Mon Sep 17 00:00:00 2001 From: shiny <23282055+bigshiny90@users.noreply.github.com> Date: Thu, 11 Sep 2025 14:21:15 -0400 Subject: [PATCH 2/3] GUI/OptionsDialog: fix proxy field validation and checkbox handling - Enable validation while typing for proxy fields (proxyIp, proxyPort, proxyIpTor, proxyPortTor) - Fix checkbox signal handling for Qt 6.7+ compatibility - Ensure proxy fields are properly enabled/disabled based on checkbox state --- src/qt/forms/optionsdialog.ui | 4 +- src/qt/optionsdialog.cpp | 111 +++++++++++++++++++++++++++++----- 2 files changed, 97 insertions(+), 18 deletions(-) diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index f992a304c31cc..22776f7bf897d 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -473,7 +473,7 @@ - + 55 @@ -660,7 +660,7 @@ - + 55 diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index a469dc1ce9632..750d39081ac51 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -278,13 +278,33 @@ OptionsDialog::OptionsDialog(QWidget* parent, bool enableWallet) 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); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->connectSocks, &QCheckBox::checkStateChanged, [this](const Qt::CheckState state){ + const bool enabled = (state == Qt::Checked); + ui->proxyIp->setEnabled(enabled); + ui->proxyPort->setEnabled(enabled); + updateProxyValidationState(); + }); + connect(ui->connectSocksTor, &QCheckBox::checkStateChanged, [this](const Qt::CheckState state){ + const bool enabled = (state == Qt::Checked); + ui->proxyIpTor->setEnabled(enabled); + ui->proxyPortTor->setEnabled(enabled); + updateProxyValidationState(); + }); +#else + connect(ui->connectSocks, &QCheckBox::stateChanged, [this](int state){ + const bool enabled = (state == Qt::Checked); + ui->proxyIp->setEnabled(enabled); + ui->proxyPort->setEnabled(enabled); + updateProxyValidationState(); + }); + connect(ui->connectSocksTor, &QCheckBox::stateChanged, [this](int state){ + const bool enabled = (state == Qt::Checked); + ui->proxyIpTor->setEnabled(enabled); + ui->proxyPortTor->setEnabled(enabled); + updateProxyValidationState(); + }); +#endif ui->maxuploadtarget->setMinimum(144 /* MiB/day */); ui->maxuploadtarget->setMaximum(std::numeric_limits::max()); @@ -720,10 +740,23 @@ OptionsDialog::OptionsDialog(QWidget* parent, bool enableWallet) /* setup/change UI elements when proxy IPs are invalid/valid */ ui->proxyIp->setCheckValidator(new ProxyAddressValidator(parent)); ui->proxyIpTor->setCheckValidator(new ProxyAddressValidator(parent)); + + // do not allow empty input for validation for all proxy fields + ui->proxyIp->setAllowEmptyInput(false); + ui->proxyIpTor->setAllowEmptyInput(false); + ui->proxyPort->setAllowEmptyInput(false); + ui->proxyPortTor->setAllowEmptyInput(false); + + // Enable validation while typing for all proxy fields + ui->proxyIp->setAllowValidationWhileEditing(true); + ui->proxyPort->setAllowValidationWhileEditing(true); + ui->proxyIpTor->setAllowValidationWhileEditing(true); + ui->proxyPortTor->setAllowValidationWhileEditing(true); + 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); + connect(ui->proxyPort, &QValidatedLineEdit::validationDidChange, this, &OptionsDialog::updateProxyValidationState); + connect(ui->proxyPortTor, &QValidatedLineEdit::validationDidChange, this, &OptionsDialog::updateProxyValidationState); if (!QSystemTrayIcon::isSystemTrayAvailable()) { ui->showTrayIcon->setChecked(false); @@ -1152,6 +1185,16 @@ void OptionsDialog::on_okButton_clicked() model->setData(model->index(OptionsModel::dustdynamic, 0), "off"); } + // Before mapper->submit() + if (!ui->connectSocks->isChecked()) { + ui->proxyIp->clear(); + ui->proxyPort->clear(); + } + if (!ui->connectSocksTor->isChecked()) { + ui->proxyIpTor->clear(); + ui->proxyPortTor->clear(); + } + mapper->submit(); accept(); updateDefaultProxyNets(); @@ -1174,11 +1217,13 @@ void OptionsDialog::on_showTrayIcon_stateChanged(int state) void OptionsDialog::changeEvent(QEvent* e) { + // First let the base class update all child widget palettes + // required for qvalidatedlineedit invalid colors to update properly + QWidget::changeEvent(e); if (e->type() == QEvent::PaletteChange) { + // Then update theme colors with the new palette updateThemeColors(); } - - QWidget::changeEvent(e); } void OptionsDialog::togglePruneWarning(bool enabled) @@ -1211,17 +1256,51 @@ void OptionsDialog::clearStatusLabel() 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)) + bool socksProxyEnabled = ui->connectSocks->isChecked(); + bool torProxyEnabled = ui->connectSocksTor->isChecked(); + + bool proxyIpValid = ui->proxyIp->isValid(); + bool proxyPortValid = ui->proxyPort->isValid(); + bool proxyIpTorValid = ui->proxyIpTor->isValid(); + bool proxyPortTorValid = ui->proxyPortTor->isValid(); + + // proxy is OK if: disabled OR (enabled AND valid ip and valid port) + bool socksProxyOk = !socksProxyEnabled || (proxyIpValid && proxyPortValid); + bool torProxyOk = !torProxyEnabled || (proxyIpTorValid && proxyPortTorValid); + + // Both must be OK for the form to be valid + if (socksProxyOk && torProxyOk) { - setOkButtonState(otherProxyWidget->isValid()); //only enable ok button if both proxies are valid + setOkButtonState(true); clearStatusLabel(); } else { setOkButtonState(false); - ui->statusLabel->setText(tr("The supplied proxy address is invalid.")); + QStringList socksErrors; + + if (socksProxyEnabled) { + if (!proxyIpValid && !proxyPortValid) { + socksErrors.append(tr("The supplied proxy address and port are invalid.")); + } else if (!proxyIpValid) { + socksErrors.append(tr("The supplied proxy address is invalid.")); + } else if (!proxyPortValid) { + socksErrors.append(tr("The supplied proxy port is invalid.")); + } + } + if (torProxyEnabled) { + if (!proxyIpTorValid && !proxyPortTorValid) { + socksErrors.append(tr("The supplied Tor proxy address and port are invalid.")); + } else if (!proxyIpTorValid) { + socksErrors.append(tr("The supplied Tor proxy address is invalid.")); + } else if (!proxyPortTorValid) { + socksErrors.append(tr("The supplied Tor proxy port is invalid.")); + } + } + + if (socksErrors.size() > 0) { + ui->statusLabel->setText(socksErrors.join(" ")); + } } } From bb701223ab55092383c5d5d0ddb4588d288e195f Mon Sep 17 00:00:00 2001 From: shiny <23282055+bigshiny90@users.noreply.github.com> Date: Mon, 15 Sep 2025 08:52:39 -0400 Subject: [PATCH 3/3] GUI/SignVerifyMessageDialog: remove unnecessary QValidatedLineEdit revalidation on theme change --- src/qt/signverifymessagedialog.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 014a58107c6c0..0050a888c1e59 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -327,19 +327,6 @@ void SignVerifyMessageDialog::updateThemeColors() // Update status labels updateStatusLabelColor(ui->statusLabel_SM); updateStatusLabelColor(ui->statusLabel_VM); - - // Re-trigger validation on all input fields to update their styling - // including background and text color - // Use setText to trigger validation - if (ui->addressIn_SM) { - ui->addressIn_SM->setText(ui->addressIn_SM->text()); - } - if (ui->addressIn_VM) { - ui->addressIn_VM->setText(ui->addressIn_VM->text()); - } - if (ui->signatureIn_VM) { - ui->signatureIn_VM->setText(ui->signatureIn_VM->text()); - } } void SignVerifyMessageDialog::updateStatusLabelColor(QLabel* label)