/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/widgets/fields/special_fields.h" #include "lang/lang_keys.h" #include "countries/countries_instance.h" // Countries::ValidPhoneCode #include namespace Ui { namespace { constexpr auto kMaxUsernameLength = 32; // Rest of the phone number, without country code (seen 12 at least), // need more for service numbers. constexpr auto kMaxPhoneTailLength = 32; // Max length of country phone code. constexpr auto kMaxPhoneCodeLength = 4; } // namespace CountryCodeInput::CountryCodeInput( QWidget *parent, const style::InputField &st) : MaskedInputField(parent, st) { } void CountryCodeInput::startErasing(QKeyEvent *e) { setFocus(); keyPressEvent(e); } void CountryCodeInput::codeSelected(const QString &code) { auto wasText = getLastText(); auto wasCursor = cursorPosition(); auto newText = '+' + code; auto newCursor = int(newText.size()); setText(newText); _nosignal = true; correctValue(wasText, wasCursor, newText, newCursor); _nosignal = false; changed(); } void CountryCodeInput::correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) { QString newText, addToNumber; int oldPos(nowCursor); int newPos(-1); int oldLen(now.length()); int start = 0; int digits = 5; newText.reserve(oldLen + 1); if (oldLen && now[0] == '+') { if (start == oldPos) { newPos = newText.length(); } ++start; } newText += '+'; for (int i = start; i < oldLen; ++i) { if (i == oldPos) { newPos = newText.length(); } auto ch = now[i]; if (ch.isDigit()) { if (!digits || !--digits) { addToNumber += ch; } else { newText += ch; } } } if (!addToNumber.isEmpty()) { auto validCode = Countries::Instance().validPhoneCode(newText.mid(1)); addToNumber = newText.mid(1 + validCode.length()) + addToNumber; newText = '+' + validCode; } setCorrectedText(now, nowCursor, newText, newPos); if (!_nosignal && was != newText) { _codeChanged.fire(newText.mid(1)); } if (!addToNumber.isEmpty()) { _addedToNumber.fire_copy(addToNumber); } } PhonePartInput::PhonePartInput( QWidget *parent, const style::InputField &st, PhonePartInput::GroupsCallback groupsCallback) : MaskedInputField(parent, st/*, tr::lng_phone_ph(tr::now)*/) , _groupsCallback(std::move(groupsCallback)) { } void PhonePartInput::paintAdditionalPlaceholder(QPainter &p) { if (!_pattern.isEmpty()) { auto t = getDisplayedText(); auto ph = _additionalPlaceholder.mid(t.size()); if (!ph.isEmpty()) { p.setClipRect(rect()); auto phRect = placeholderRect(); int tw = phFont()->width(t); if (tw < phRect.width()) { phRect.setLeft(phRect.left() + tw); placeholderAdditionalPrepare(p); p.drawText(phRect, ph, style::al_topleft); } } } } void PhonePartInput::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Backspace && cursorPosition() == 0) { _frontBackspaceEvent.fire_copy(e); } else { MaskedInputField::keyPressEvent(e); } } void PhonePartInput::correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) { if (!now.isEmpty() && (_lastDigits != now)) { _lastDigits = now; _lastDigits.replace(TextUtilities::RegExpDigitsExclude(), QString()); updatePattern(_groupsCallback(_code + _lastDigits)); } QString newText; int oldPos(nowCursor), newPos(-1), oldLen(now.length()), digitCount = 0; for (int i = 0; i < oldLen; ++i) { if (now[i].isDigit()) { ++digitCount; } } if (digitCount > kMaxPhoneTailLength) { digitCount = kMaxPhoneTailLength; } bool inPart = !_pattern.isEmpty(); int curPart = -1, leftInPart = 0; newText.reserve(oldLen); for (int i = 0; i < oldLen; ++i) { if (i == oldPos && newPos < 0) { newPos = newText.length(); } auto ch = now[i]; if (ch.isDigit()) { if (!digitCount--) { break; } if (inPart) { if (leftInPart) { --leftInPart; } else { ++curPart; inPart = curPart < _pattern.size(); // Don't add an extra space to the end. if (inPart) { newText += ' '; } leftInPart = inPart ? (_pattern.at(curPart) - 1) : 0; ++oldPos; } } newText += ch; } else if (ch == ' ' || ch == '-' || ch == '(' || ch == ')') { if (inPart) { if (leftInPart) { } else { newText += ch; ++curPart; inPart = curPart < _pattern.size(); leftInPart = inPart ? _pattern.at(curPart) : 0; } } else { newText += ch; } } } auto newlen = newText.size(); while (newlen > 0 && newText.at(newlen - 1).isSpace()) { --newlen; } if (newlen < newText.size()) { newText = newText.mid(0, newlen); } setCorrectedText(now, nowCursor, newText, newPos); } void PhonePartInput::addedToNumber(const QString &added) { setFocus(); auto wasText = getLastText(); auto wasCursor = cursorPosition(); auto newText = added + wasText; auto newCursor = int(newText.size()); setText(newText); setCursorPosition(added.length()); correctValue(wasText, wasCursor, newText, newCursor); startPlaceholderAnimation(); } void PhonePartInput::chooseCode(const QString &code) { _code = code; updatePattern(_groupsCallback(_code)); auto wasText = getLastText(); auto wasCursor = cursorPosition(); auto newText = getLastText(); auto newCursor = int(newText.size()); correctValue(wasText, wasCursor, newText, newCursor); startPlaceholderAnimation(); update(); } void PhonePartInput::updatePattern(QVector &&pattern) { if (_pattern == pattern) { return; } _pattern = std::move(pattern); if (!_pattern.isEmpty() && _pattern.at(0) == _code.size()) { _pattern.pop_front(); } else { _pattern.clear(); } _additionalPlaceholder = QString(); if (!_pattern.isEmpty()) { _additionalPlaceholder.reserve(20); for (const auto &part : _pattern) { _additionalPlaceholder.append(' '); _additionalPlaceholder.append(QString(part, QChar(0x2212))); } } setPlaceholderHidden(!_additionalPlaceholder.isEmpty()); } UsernameInput::UsernameInput( QWidget *parent, const style::InputField &st, rpl::producer placeholder, const QString &val, const QString &linkPlaceholder) : MaskedInputField(parent, st, std::move(placeholder), val) { setLinkPlaceholder(linkPlaceholder); } void UsernameInput::setLinkPlaceholder(const QString &placeholder) { _linkPlaceholder = placeholder; if (!_linkPlaceholder.isEmpty()) { setTextMargins(style::margins(_st.textMargins.left() + _st.font->width(_linkPlaceholder), _st.textMargins.top(), _st.textMargins.right(), _st.textMargins.bottom())); setPlaceholderHidden(true); } } void UsernameInput::paintAdditionalPlaceholder(QPainter &p) { if (!_linkPlaceholder.isEmpty()) { p.setFont(_st.font); p.setPen(_st.placeholderFg); p.drawText(QRect(_st.textMargins.left(), _st.textMargins.top(), width(), height() - _st.textMargins.top() - _st.textMargins.bottom()), _linkPlaceholder, style::al_topleft); } } void UsernameInput::correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) { auto newPos = nowCursor; auto from = 0, len = int(now.size()); for (; from < len; ++from) { if (!now.at(from).isSpace()) { break; } if (newPos > 0) --newPos; } len -= from; if (len > kMaxUsernameLength) { len = kMaxUsernameLength + (now.at(from) == '@' ? 1 : 0); } for (int32 to = from + len; to > from;) { --to; if (!now.at(to).isSpace()) { break; } --len; } setCorrectedText(now, nowCursor, now.mid(from, len), newPos); } PhoneInput::PhoneInput( QWidget *parent, const style::InputField &st, rpl::producer placeholder, const QString &defaultValue, QString value, PhoneInput::GroupsCallback groupsCallback) : MaskedInputField(parent, st, std::move(placeholder), value) , _defaultValue(defaultValue) , _groupsCallback(std::move(groupsCallback)) { if (value.isEmpty()) { clearText(); } else { auto pos = int(value.size()); correctValue(QString(), 0, value, pos); } } void PhoneInput::focusInEvent(QFocusEvent *e) { MaskedInputField::focusInEvent(e); setSelection(cursorPosition(), cursorPosition()); } void PhoneInput::clearText() { auto value = _defaultValue; setText(value); auto pos = int(value.size()); correctValue(QString(), 0, value, pos); } void PhoneInput::paintAdditionalPlaceholder(QPainter &p) { if (!_pattern.isEmpty()) { auto t = getDisplayedText(); auto ph = _additionalPlaceholder.mid(t.size()); if (!ph.isEmpty()) { p.setClipRect(rect()); auto phRect = placeholderRect(); int tw = phFont()->width(t); if (tw < phRect.width()) { phRect.setLeft(phRect.left() + tw); placeholderAdditionalPrepare(p); p.drawText(phRect, ph, style::al_topleft); } } } } void PhoneInput::correctValue( const QString &was, int wasCursor, QString &now, int &nowCursor) { auto digits = now; digits.replace(TextUtilities::RegExpDigitsExclude(), QString()); _pattern = _groupsCallback(digits); QString newPlaceholder; if (_pattern.isEmpty()) { newPlaceholder = QString(); } else if (_pattern.size() == 1 && _pattern.at(0) == digits.size()) { newPlaceholder = QString(_pattern.at(0) + 2, ' ') + tr::lng_contact_phone(tr::now); } else { newPlaceholder.reserve(20); for (int i = 0, l = _pattern.size(); i < l; ++i) { if (i) { newPlaceholder.append(' '); } else { newPlaceholder.append('+'); } newPlaceholder.append(i ? QString(_pattern.at(i), QChar(0x2212)) : digits.mid(0, _pattern.at(i))); } } if (_additionalPlaceholder != newPlaceholder) { _additionalPlaceholder = newPlaceholder; setPlaceholderHidden(!_additionalPlaceholder.isEmpty()); update(); } QString newText; int oldPos(nowCursor), newPos(-1), oldLen(now.length()), digitCount = qMin(digits.size(), kMaxPhoneCodeLength + kMaxPhoneTailLength); bool inPart = !_pattern.isEmpty(), plusFound = false; int curPart = 0, leftInPart = inPart ? _pattern.at(curPart) : 0; newText.reserve(oldLen + 1); newText.append('+'); for (int i = 0; i < oldLen; ++i) { if (i == oldPos && newPos < 0) { newPos = newText.length(); } QChar ch(now[i]); if (ch.isDigit()) { if (!digitCount--) { break; } if (inPart) { if (leftInPart) { --leftInPart; } else { ++curPart; inPart = curPart < _pattern.size(); // Don't add an extra space to the end. if (inPart) { newText += ' '; } leftInPart = inPart ? (_pattern.at(curPart) - 1) : 0; ++oldPos; } } newText += ch; } else if (ch == ' ' || ch == '-' || ch == '(' || ch == ')') { if (inPart) { if (leftInPart) { } else { newText += ch; ++curPart; inPart = curPart < _pattern.size(); leftInPart = inPart ? _pattern.at(curPart) : 0; } } else { newText += ch; } } else if (ch == '+') { plusFound = true; } } if (!plusFound && newText == u"+"_q) { newText = QString(); newPos = 0; } int32 newlen = newText.size(); while (newlen > 0 && newText.at(newlen - 1).isSpace()) { --newlen; } if (newlen < newText.size()) { newText = newText.mid(0, newlen); } setCorrectedText(now, nowCursor, newText, newPos); } } // namespace Ui