qvalidatedlineedit.cpp raw

   1  // Copyright (c) 2011-2022 The Limenka 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 <qt/qvalidatedlineedit.h>
   6  
   7  #include <qt/limenkaaddressvalidator.h>
   8  #include <qt/guiconstants.h>
   9  #include <qt/guiutil.h>
  10  
  11  #include <QColor>
  12  #include <QCoreApplication>
  13  #include <QFont>
  14  #include <QInputMethodEvent>
  15  #include <QList>
  16  #include <QTextCharFormat>
  17  
  18  QValidatedLineEdit::QValidatedLineEdit(QWidget* parent)
  19      : QLineEdit(parent)
  20  {
  21      connect(this, &QValidatedLineEdit::textChanged, this, &QValidatedLineEdit::markValid);
  22  }
  23  
  24  QValidatedLineEdit::~QValidatedLineEdit()
  25  {
  26      delete m_warning_validator;
  27  }
  28  
  29  void QValidatedLineEdit::setText(const QString& text)
  30  {
  31      QLineEdit::setText(text);
  32      checkValidity();
  33  }
  34  
  35  void QValidatedLineEdit::setValid(bool _valid, bool with_warning, const std::vector<int>&error_locations)
  36  {
  37      if(_valid && this->valid)
  38      {
  39          if (with_warning == m_has_warning) {
  40              return;
  41          }
  42      }
  43  
  44      QList<QInputMethodEvent::Attribute> attributes;
  45  
  46      if(_valid)
  47      {
  48          m_has_warning = with_warning;
  49          if (with_warning) {
  50              setStyleSheet("QValidatedLineEdit { " STYLE_INCORRECT "}");
  51          } else {
  52              setStyleSheet("");
  53          }
  54      }
  55      else
  56      {
  57          // Use theme-aware red color for invalid state
  58          const QColor bg_colour = palette().color(backgroundRole());
  59          const bool dark_mode = GUIUtil::isDarkMode(bg_colour);
  60          QColor error_colour = dark_mode ? QColor("#FF8080") : QColor("#FF0000");
  61          setStyleSheet(QStringLiteral("QValidatedLineEdit { border: 3px solid %1; }").arg(error_colour.name()));
  62  
  63          if (!error_locations.empty()) {
  64              const QColor normal_text_colour = palette().color(foregroundRole());
  65              if (normal_text_colour.red() > normal_text_colour.green() && normal_text_colour.red() > normal_text_colour.blue()) {
  66                  // red is dominant, avoid fg red
  67                  if (bg_colour.red() > bg_colour.blue() && bg_colour.green() > bg_colour.blue()) {
  68                      // bg is yellowish, fallback to blues
  69                      error_colour = dark_mode ? Qt::cyan : Qt::blue;
  70                  } else {
  71                      error_colour = dark_mode ? Qt::yellow : Qt::darkYellow;
  72                  }
  73              }
  74  
  75              QTextCharFormat format;
  76              format.setFontUnderline(true);
  77              format.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
  78              format.setUnderlineColor(error_colour);
  79              format.setForeground(error_colour);
  80              format.setFontWeight(QFont::Bold);
  81              for (auto error_pos : error_locations) {
  82                  attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, error_pos - cursorPosition(), /*length=*/ 1, format));
  83              }
  84          }
  85      }
  86  
  87      QInputMethodEvent event(QString(), attributes);
  88      QCoreApplication::sendEvent(this, &event);
  89  
  90      this->valid = _valid;
  91  }
  92  
  93  void QValidatedLineEdit::focusInEvent(QFocusEvent *evt)
  94  {
  95      // Clear invalid flag on focus
  96      setValid(true);
  97  
  98      QLineEdit::focusInEvent(evt);
  99  }
 100  
 101  void QValidatedLineEdit::focusOutEvent(QFocusEvent *evt)
 102  {
 103      checkValidity();
 104  
 105      QLineEdit::focusOutEvent(evt);
 106  }
 107  
 108  void QValidatedLineEdit::markValid()
 109  {
 110      // As long as a user is typing ensure we display state as valid
 111      setValid(true);
 112  }
 113  
 114  void QValidatedLineEdit::clear()
 115  {
 116      setValid(true);
 117      QLineEdit::clear();
 118  }
 119  
 120  void QValidatedLineEdit::setEnabled(bool enabled)
 121  {
 122      if (!enabled)
 123      {
 124          // A disabled QValidatedLineEdit should be marked valid
 125          setValid(true);
 126      }
 127      else
 128      {
 129          // Recheck validity when QValidatedLineEdit gets enabled
 130          checkValidity();
 131      }
 132  
 133      QLineEdit::setEnabled(enabled);
 134  }
 135  
 136  void QValidatedLineEdit::checkValidity()
 137  {
 138      const bool has_warning = checkWarning();
 139      if (text().isEmpty())
 140      {
 141          setValid(true);
 142      }
 143      else if (hasAcceptableInput())
 144      {
 145          setValid(true, has_warning);
 146  
 147          // Check contents on focus out
 148          if (checkValidator)
 149          {
 150              QString address = text();
 151              QValidator::State validation_result;
 152              std::vector<int> error_locations;
 153              const LimenkaAddressEntryValidator * const address_validator = dynamic_cast<const LimenkaAddressEntryValidator*>(checkValidator);
 154              if (address_validator) {
 155                  validation_result = address_validator->validate(address, error_locations);
 156              } else {
 157                  int pos = 0;
 158                  validation_result = checkValidator->validate(address, pos);
 159                  error_locations.push_back(pos);
 160              }
 161              if (validation_result == QValidator::Acceptable)
 162                  setValid(true, has_warning);
 163              else
 164                  setValid(/* valid= */ false, /* with_warning= */ false, error_locations);
 165          }
 166      }
 167      else
 168          setValid(false);
 169  
 170      Q_EMIT validationDidChange(this);
 171  }
 172  
 173  void QValidatedLineEdit::setCheckValidator(const QValidator *v)
 174  {
 175      checkValidator = v;
 176      checkValidity();
 177  }
 178  
 179  bool QValidatedLineEdit::isValid()
 180  {
 181      // use checkValidator in case the QValidatedLineEdit is disabled
 182      if (checkValidator)
 183      {
 184          QString address = text();
 185          int pos = 0;
 186          if (checkValidator->validate(address, pos) == QValidator::Acceptable)
 187              return true;
 188      }
 189  
 190      return valid;
 191  }
 192  
 193  void QValidatedLineEdit::setWarningValidator(const QValidator *v)
 194  {
 195      delete m_warning_validator;
 196      m_warning_validator = v;
 197      checkValidity();
 198  }
 199  
 200  bool QValidatedLineEdit::checkWarning() const
 201  {
 202      if (m_warning_validator && !text().isEmpty()) {
 203          QString address = text();
 204          int pos = 0;
 205          if (m_warning_validator->validate(address, pos) != QValidator::Acceptable) {
 206              return true;
 207          }
 208      }
 209  
 210      return false;
 211  }
 212  
 213  bool QValidatedLineEdit::hasWarning() const
 214  {
 215      return m_has_warning;
 216  }
 217