diff --git a/src/qt/bitcoinaddressvalidator.cpp b/src/qt/bitcoinaddressvalidator.cpp index a2adca6eac..ec6acb1998 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 ae698c72a6..b0a4215d22 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,21 @@ 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; + using BitcoinAddressEntryValidator::validate; + State validate(QString &input, std::vector&error_locations) const override; }; #endif // BITCOIN_QT_BITCOINADDRESSVALIDATOR_H diff --git a/src/qt/qvalidatedlineedit.cpp b/src/qt/qvalidatedlineedit.cpp index d90ff5526e..4b32b4583e 100644 --- a/src/qt/qvalidatedlineedit.cpp +++ b/src/qt/qvalidatedlineedit.cpp @@ -7,6 +7,15 @@ #include #include +#include + +#include +#include +#include +#include +#include +#include + QValidatedLineEdit::QValidatedLineEdit(QWidget* parent) : QLineEdit(parent) { @@ -24,15 +33,25 @@ void QValidatedLineEdit::setText(const QString& text) checkValidity(); } -void QValidatedLineEdit::setValid(bool _valid, bool with_warning) +double ColourLuminosity(QColor c) { - if(_valid == this->valid) + const auto Lr = std::pow(c.redF(), 2.2) * .2126; + const auto Lg = std::pow(c.greenF(), 2.2) * .7152; + const auto Lb = std::pow(c.blueF(), 2.2) * .0722; + return Lr + Lg + Lb; +} + +void QValidatedLineEdit::setValid(bool _valid, bool with_warning, const std::vector&error_locations) +{ + if(_valid && this->valid) { - if (with_warning == m_has_warning || !valid) { + if (with_warning == m_has_warning) { return; } } + QList attributes; + if(_valid) { m_has_warning = with_warning; @@ -45,7 +64,38 @@ void QValidatedLineEdit::setValid(bool _valid, bool with_warning) else { setStyleSheet("QValidatedLineEdit { " STYLE_INVALID "}"); + if (!error_locations.empty()) { + const QColor normal_text_colour = palette().color(foregroundRole()); + const QColor bg_colour = palette().color(backgroundRole()); + const bool dark_mode = ColourLuminosity(bg_colour) < .36; + QColor error_colour; + if (normal_text_colour.red() > normal_text_colour.green() && normal_text_colour.red() > normal_text_colour.blue()) { + // red is dominant, avoid fg red + if (bg_colour.red() > bg_colour.blue() && bg_colour.green() > bg_colour.blue()) { + // bg is yellowish, fallback to blues + error_colour = dark_mode ? Qt::cyan : Qt::blue; + } else { + error_colour = dark_mode ? Qt::yellow : Qt::darkYellow; + } + } else { + error_colour = dark_mode ? QColor(255, 159, 159) : Qt::red; + } + + QTextCharFormat format; + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); + format.setUnderlineColor(error_colour); + format.setForeground(error_colour); + 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; } @@ -107,11 +157,20 @@ void QValidatedLineEdit::checkValidity() if (checkValidator) { QString address = text(); - int pos = 0; - if (checkValidator->validate(address, pos) == QValidator::Acceptable) + 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(true, has_warning); else - setValid(false); + setValid(/* valid= */ false, /* with_warning= */ false, error_locations); } } else diff --git a/src/qt/qvalidatedlineedit.h b/src/qt/qvalidatedlineedit.h index a695435a0b..63fc15de50 100644 --- a/src/qt/qvalidatedlineedit.h +++ b/src/qt/qvalidatedlineedit.h @@ -35,7 +35,7 @@ private: public Q_SLOTS: void setText(const QString&); - void setValid(bool valid, bool with_warning=false); + void setValid(bool valid, bool with_warning=false, const std::vector&error_locations=std::vector()); void setEnabled(bool enabled); Q_SIGNALS: