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