diff --git a/src/qt/bitcoinaddressvalidator.cpp b/src/qt/bitcoinaddressvalidator.cpp index a2adca6eac7..ec6acb19986 100644 --- a/src/qt/bitcoinaddressvalidator.cpp +++ b/src/qt/bitcoinaddressvalidator.cpp @@ -6,6 +6,8 @@ #include +#include + /* Base58 characters are: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" @@ -20,10 +22,8 @@ BitcoinAddressEntryValidator::BitcoinAddressEntryValidator(QObject *parent) : { } -QValidator::State BitcoinAddressEntryValidator::validate(QString &input, int &pos) const +QValidator::State BitcoinAddressEntryValidator::validate(QString &input, std::vector&error_locations) const { - Q_UNUSED(pos); - // Empty address is "intermediate" input if (input.isEmpty()) return QValidator::Intermediate; @@ -73,6 +73,7 @@ QValidator::State BitcoinAddressEntryValidator::validate(QString &input, int &po } else { + error_locations.push_back(idx); state = QValidator::Invalid; } } @@ -80,16 +81,25 @@ QValidator::State BitcoinAddressEntryValidator::validate(QString &input, int &po return state; } +QValidator::State BitcoinAddressEntryValidator::validate(QString &input, int &pos) const +{ + std::vector error_locations; + const auto ret = validate(input, error_locations); + if (!error_locations.empty()) pos = error_locations.at(0); + return ret; +} + BitcoinAddressCheckValidator::BitcoinAddressCheckValidator(QObject *parent) : - QValidator(parent) + BitcoinAddressEntryValidator(parent) { } -QValidator::State BitcoinAddressCheckValidator::validate(QString &input, int &pos) const +QValidator::State BitcoinAddressCheckValidator::validate(QString &input, std::vector&error_locations) const { - Q_UNUSED(pos); // Validate the passed Bitcoin address - if (IsValidDestinationString(input.toStdString())) { + std::string error_msg; + CTxDestination dest = DecodeDestination(input.toStdString(), error_msg, &error_locations); + if (IsValidDestination(dest)) { return QValidator::Acceptable; } diff --git a/src/qt/bitcoinaddressvalidator.h b/src/qt/bitcoinaddressvalidator.h index ae698c72a6b..2b40019a792 100644 --- a/src/qt/bitcoinaddressvalidator.h +++ b/src/qt/bitcoinaddressvalidator.h @@ -7,6 +7,8 @@ #include +#include + /** Base58 entry widget validator, checks for valid characters and * removes some whitespace. */ @@ -17,19 +19,20 @@ class BitcoinAddressEntryValidator : public QValidator public: explicit BitcoinAddressEntryValidator(QObject *parent); - State validate(QString &input, int &pos) const override; + virtual State validate(QString &input, std::vector&error_locations) const; + virtual State validate(QString &input, int &pos) const override; }; /** Bitcoin address widget validator, checks for a valid bitcoin address. */ -class BitcoinAddressCheckValidator : public QValidator +class BitcoinAddressCheckValidator : public BitcoinAddressEntryValidator { Q_OBJECT public: explicit BitcoinAddressCheckValidator(QObject *parent); - State validate(QString &input, int &pos) const override; + State validate(QString &input, std::vector&error_locations) const override; }; #endif // BITCOIN_QT_BITCOINADDRESSVALIDATOR_H diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index fcdf6056c95..329601ed974 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -26,6 +26,8 @@ static const bool DEFAULT_SPLASHSCREEN = true; /* Invalid field background style */ #define STYLE_INVALID "background:#FF8080" +/* "Warning" field background style */ +#define STYLE_INCORRECT "background:#FFFF80" /* Transaction list -- unconfirmed transaction */ #define COLOR_UNCONFIRMED QColor(128, 128, 128) diff --git a/src/qt/qvalidatedlineedit.cpp b/src/qt/qvalidatedlineedit.cpp index bd4df75d23f..8e6fe00d2cf 100644 --- a/src/qt/qvalidatedlineedit.cpp +++ b/src/qt/qvalidatedlineedit.cpp @@ -7,6 +7,13 @@ #include #include +#include +#include +#include +#include +#include +#include + QValidatedLineEdit::QValidatedLineEdit(QWidget *parent) : QLineEdit(parent), valid(true), @@ -15,27 +22,56 @@ QValidatedLineEdit::QValidatedLineEdit(QWidget *parent) : connect(this, &QValidatedLineEdit::textChanged, this, &QValidatedLineEdit::markValid); } +QValidatedLineEdit::~QValidatedLineEdit() +{ + delete m_warning_validator; +} + void QValidatedLineEdit::setText(const QString& text) { QLineEdit::setText(text); checkValidity(); } -void QValidatedLineEdit::setValid(bool _valid) +void QValidatedLineEdit::setValid(bool _valid, bool with_warning, const std::vector&error_locations) { - if(_valid == this->valid) + if(_valid && this->valid) { - return; + if (with_warning == m_has_warning) { + return; + } } + QList attributes; + if(_valid) { - setStyleSheet(""); + m_has_warning = with_warning; + if (with_warning) { + setStyleSheet("QValidatedLineEdit { " STYLE_INCORRECT "}"); + } else { + setStyleSheet(""); + } } else { setStyleSheet("QValidatedLineEdit { " STYLE_INVALID "}"); + if (!error_locations.empty()) { + QTextCharFormat format; + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); + format.setUnderlineColor(Qt::yellow); + format.setForeground(Qt::yellow); + format.setFontWeight(QFont::Bold); + for (auto error_pos : error_locations) { + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, error_pos - cursorPosition(), /*length=*/ 1, format)); + } + } } + + QInputMethodEvent event(QString(), attributes); + QCoreApplication::sendEvent(this, &event); + this->valid = _valid; } @@ -84,23 +120,34 @@ void QValidatedLineEdit::setEnabled(bool enabled) void QValidatedLineEdit::checkValidity() { + const bool has_warning = checkWarning(); if (text().isEmpty()) { setValid(true); } else if (hasAcceptableInput()) { - setValid(true); + setValid(/* valid= */ true, has_warning); // Check contents on focus out if (checkValidator) { QString address = text(); - int pos = 0; - if (checkValidator->validate(address, pos) == QValidator::Acceptable) - setValid(true); - else - setValid(false); + QValidator::State validation_result; + std::vector error_locations; + const BitcoinAddressEntryValidator * const address_validator = dynamic_cast(checkValidator); + if (address_validator) { + validation_result = address_validator->validate(address, error_locations); + } else { + int pos = 0; + validation_result = checkValidator->validate(address, pos); + error_locations.push_back(pos); + } + if (validation_result == QValidator::Acceptable) { + setValid(/* valid= */ true, has_warning); + } else { + setValid(/* valid= */ false, /* with_warning= */ false, error_locations); + } } } else @@ -128,3 +175,28 @@ bool QValidatedLineEdit::isValid() return valid; } + +void QValidatedLineEdit::setWarningValidator(const QValidator *v) +{ + delete m_warning_validator; + m_warning_validator = v; + checkValidity(); +} + +bool QValidatedLineEdit::checkWarning() const +{ + if (m_warning_validator && !text().isEmpty()) { + QString address = text(); + int pos = 0; + if (m_warning_validator->validate(address, pos) != QValidator::Acceptable) { + return true; + } + } + + return false; +} + +bool QValidatedLineEdit::hasWarning() const +{ + return m_has_warning; +} diff --git a/src/qt/qvalidatedlineedit.h b/src/qt/qvalidatedlineedit.h index 12d35aa2645..055fd1a65e5 100644 --- a/src/qt/qvalidatedlineedit.h +++ b/src/qt/qvalidatedlineedit.h @@ -16,9 +16,12 @@ class QValidatedLineEdit : public QLineEdit public: explicit QValidatedLineEdit(QWidget *parent); + ~QValidatedLineEdit(); void clear(); void setCheckValidator(const QValidator *v); bool isValid(); + void setWarningValidator(const QValidator *); + bool hasWarning() const; protected: void focusInEvent(QFocusEvent *evt) override; @@ -27,10 +30,12 @@ class QValidatedLineEdit : public QLineEdit private: bool valid; const QValidator *checkValidator; + bool m_has_warning{false}; + const QValidator *m_warning_validator{nullptr}; public Q_SLOTS: void setText(const QString&); - void setValid(bool valid); + void setValid(bool valid, bool with_warning=false, const std::vector&error_locations=std::vector()); void setEnabled(bool enabled); Q_SIGNALS: @@ -39,6 +44,7 @@ public Q_SLOTS: private Q_SLOTS: void markValid(); void checkValidity(); + bool checkWarning() const; }; #endif // BITCOIN_QT_QVALIDATEDLINEEDIT_H