special_fields.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "ui/widgets/fields/special_fields.h"
  8. #include "lang/lang_keys.h"
  9. #include "countries/countries_instance.h" // Countries::ValidPhoneCode
  10. #include "styles/style_widgets.h"
  11. #include <QtCore/QRegularExpression>
  12. namespace Ui {
  13. namespace {
  14. constexpr auto kMaxUsernameLength = 32;
  15. // Rest of the phone number, without country code (seen 12 at least),
  16. // need more for service numbers.
  17. constexpr auto kMaxPhoneTailLength = 32;
  18. // Max length of country phone code.
  19. constexpr auto kMaxPhoneCodeLength = 4;
  20. } // namespace
  21. CountryCodeInput::CountryCodeInput(
  22. QWidget *parent,
  23. const style::InputField &st)
  24. : MaskedInputField(parent, st) {
  25. }
  26. void CountryCodeInput::startErasing(QKeyEvent *e) {
  27. setFocus();
  28. keyPressEvent(e);
  29. }
  30. void CountryCodeInput::codeSelected(const QString &code) {
  31. auto wasText = getLastText();
  32. auto wasCursor = cursorPosition();
  33. auto newText = '+' + code;
  34. auto newCursor = int(newText.size());
  35. setText(newText);
  36. _nosignal = true;
  37. correctValue(wasText, wasCursor, newText, newCursor);
  38. _nosignal = false;
  39. changed();
  40. }
  41. void CountryCodeInput::correctValue(
  42. const QString &was,
  43. int wasCursor,
  44. QString &now,
  45. int &nowCursor) {
  46. QString newText, addToNumber;
  47. int oldPos(nowCursor);
  48. int newPos(-1);
  49. int oldLen(now.length());
  50. int start = 0;
  51. int digits = 5;
  52. newText.reserve(oldLen + 1);
  53. if (oldLen && now[0] == '+') {
  54. if (start == oldPos) {
  55. newPos = newText.length();
  56. }
  57. ++start;
  58. }
  59. newText += '+';
  60. for (int i = start; i < oldLen; ++i) {
  61. if (i == oldPos) {
  62. newPos = newText.length();
  63. }
  64. auto ch = now[i];
  65. if (ch.isDigit()) {
  66. if (!digits || !--digits) {
  67. addToNumber += ch;
  68. } else {
  69. newText += ch;
  70. }
  71. }
  72. }
  73. if (!addToNumber.isEmpty()) {
  74. auto validCode = Countries::Instance().validPhoneCode(newText.mid(1));
  75. addToNumber = newText.mid(1 + validCode.length()) + addToNumber;
  76. newText = '+' + validCode;
  77. }
  78. setCorrectedText(now, nowCursor, newText, newPos);
  79. if (!_nosignal && was != newText) {
  80. _codeChanged.fire(newText.mid(1));
  81. }
  82. if (!addToNumber.isEmpty()) {
  83. _addedToNumber.fire_copy(addToNumber);
  84. }
  85. }
  86. PhonePartInput::PhonePartInput(
  87. QWidget *parent,
  88. const style::InputField &st,
  89. PhonePartInput::GroupsCallback groupsCallback)
  90. : MaskedInputField(parent, st/*, tr::lng_phone_ph(tr::now)*/)
  91. , _groupsCallback(std::move(groupsCallback)) {
  92. }
  93. void PhonePartInput::paintAdditionalPlaceholder(QPainter &p) {
  94. if (!_pattern.isEmpty()) {
  95. auto t = getDisplayedText();
  96. auto ph = _additionalPlaceholder.mid(t.size());
  97. if (!ph.isEmpty()) {
  98. p.setClipRect(rect());
  99. auto phRect = placeholderRect();
  100. int tw = phFont()->width(t);
  101. if (tw < phRect.width()) {
  102. phRect.setLeft(phRect.left() + tw);
  103. placeholderAdditionalPrepare(p);
  104. p.drawText(phRect, ph, style::al_topleft);
  105. }
  106. }
  107. }
  108. }
  109. void PhonePartInput::keyPressEvent(QKeyEvent *e) {
  110. if (e->key() == Qt::Key_Backspace && cursorPosition() == 0) {
  111. _frontBackspaceEvent.fire_copy(e);
  112. } else {
  113. MaskedInputField::keyPressEvent(e);
  114. }
  115. }
  116. void PhonePartInput::correctValue(
  117. const QString &was,
  118. int wasCursor,
  119. QString &now,
  120. int &nowCursor) {
  121. if (!now.isEmpty() && (_lastDigits != now)) {
  122. _lastDigits = now;
  123. _lastDigits.replace(TextUtilities::RegExpDigitsExclude(), QString());
  124. updatePattern(_groupsCallback(_code + _lastDigits));
  125. }
  126. QString newText;
  127. int oldPos(nowCursor), newPos(-1), oldLen(now.length()), digitCount = 0;
  128. for (int i = 0; i < oldLen; ++i) {
  129. if (now[i].isDigit()) {
  130. ++digitCount;
  131. }
  132. }
  133. if (digitCount > kMaxPhoneTailLength) {
  134. digitCount = kMaxPhoneTailLength;
  135. }
  136. bool inPart = !_pattern.isEmpty();
  137. int curPart = -1, leftInPart = 0;
  138. newText.reserve(oldLen);
  139. for (int i = 0; i < oldLen; ++i) {
  140. if (i == oldPos && newPos < 0) {
  141. newPos = newText.length();
  142. }
  143. auto ch = now[i];
  144. if (ch.isDigit()) {
  145. if (!digitCount--) {
  146. break;
  147. }
  148. if (inPart) {
  149. if (leftInPart) {
  150. --leftInPart;
  151. } else {
  152. ++curPart;
  153. inPart = curPart < _pattern.size();
  154. // Don't add an extra space to the end.
  155. if (inPart) {
  156. newText += ' ';
  157. }
  158. leftInPart = inPart ? (_pattern.at(curPart) - 1) : 0;
  159. ++oldPos;
  160. }
  161. }
  162. newText += ch;
  163. } else if (ch == ' ' || ch == '-' || ch == '(' || ch == ')') {
  164. if (inPart) {
  165. if (leftInPart) {
  166. } else {
  167. newText += ch;
  168. ++curPart;
  169. inPart = curPart < _pattern.size();
  170. leftInPart = inPart ? _pattern.at(curPart) : 0;
  171. }
  172. } else {
  173. newText += ch;
  174. }
  175. }
  176. }
  177. auto newlen = newText.size();
  178. while (newlen > 0 && newText.at(newlen - 1).isSpace()) {
  179. --newlen;
  180. }
  181. if (newlen < newText.size()) {
  182. newText = newText.mid(0, newlen);
  183. }
  184. setCorrectedText(now, nowCursor, newText, newPos);
  185. }
  186. void PhonePartInput::addedToNumber(const QString &added) {
  187. setFocus();
  188. auto wasText = getLastText();
  189. auto wasCursor = cursorPosition();
  190. auto newText = added + wasText;
  191. auto newCursor = int(newText.size());
  192. setText(newText);
  193. setCursorPosition(added.length());
  194. correctValue(wasText, wasCursor, newText, newCursor);
  195. startPlaceholderAnimation();
  196. }
  197. void PhonePartInput::chooseCode(const QString &code) {
  198. _code = code;
  199. updatePattern(_groupsCallback(_code));
  200. auto wasText = getLastText();
  201. auto wasCursor = cursorPosition();
  202. auto newText = getLastText();
  203. auto newCursor = int(newText.size());
  204. correctValue(wasText, wasCursor, newText, newCursor);
  205. startPlaceholderAnimation();
  206. update();
  207. }
  208. void PhonePartInput::updatePattern(QVector<int> &&pattern) {
  209. if (_pattern == pattern) {
  210. return;
  211. }
  212. _pattern = std::move(pattern);
  213. if (!_pattern.isEmpty() && _pattern.at(0) == _code.size()) {
  214. _pattern.pop_front();
  215. } else {
  216. _pattern.clear();
  217. }
  218. _additionalPlaceholder = QString();
  219. if (!_pattern.isEmpty()) {
  220. _additionalPlaceholder.reserve(20);
  221. for (const auto &part : _pattern) {
  222. _additionalPlaceholder.append(' ');
  223. _additionalPlaceholder.append(QString(part, QChar(0x2212)));
  224. }
  225. }
  226. setPlaceholderHidden(!_additionalPlaceholder.isEmpty());
  227. }
  228. UsernameInput::UsernameInput(
  229. QWidget *parent,
  230. const style::InputField &st,
  231. rpl::producer<QString> placeholder,
  232. const QString &val,
  233. const QString &linkPlaceholder)
  234. : MaskedInputField(parent, st, std::move(placeholder), val) {
  235. setLinkPlaceholder(linkPlaceholder);
  236. }
  237. void UsernameInput::setLinkPlaceholder(const QString &placeholder) {
  238. _linkPlaceholder = placeholder;
  239. if (!_linkPlaceholder.isEmpty()) {
  240. setTextMargins(style::margins(_st.textMargins.left() + _st.style.font->width(_linkPlaceholder), _st.textMargins.top(), _st.textMargins.right(), _st.textMargins.bottom()));
  241. setPlaceholderHidden(true);
  242. }
  243. }
  244. void UsernameInput::paintAdditionalPlaceholder(QPainter &p) {
  245. if (!_linkPlaceholder.isEmpty()) {
  246. p.setFont(_st.style.font);
  247. p.setPen(_st.placeholderFg);
  248. p.drawText(QRect(_st.textMargins.left(), _st.textMargins.top(), width(), height() - _st.textMargins.top() - _st.textMargins.bottom()), _linkPlaceholder, style::al_topleft);
  249. }
  250. }
  251. void UsernameInput::correctValue(
  252. const QString &was,
  253. int wasCursor,
  254. QString &now,
  255. int &nowCursor) {
  256. auto newPos = nowCursor;
  257. auto from = 0, len = int(now.size());
  258. for (; from < len; ++from) {
  259. if (!now.at(from).isSpace()) {
  260. break;
  261. }
  262. if (newPos > 0) --newPos;
  263. }
  264. len -= from;
  265. if (len > kMaxUsernameLength) {
  266. len = kMaxUsernameLength + (now.at(from) == '@' ? 1 : 0);
  267. }
  268. for (int32 to = from + len; to > from;) {
  269. --to;
  270. if (!now.at(to).isSpace()) {
  271. break;
  272. }
  273. --len;
  274. }
  275. setCorrectedText(now, nowCursor, now.mid(from, len), newPos);
  276. }
  277. PhoneInput::PhoneInput(
  278. QWidget *parent,
  279. const style::InputField &st,
  280. rpl::producer<QString> placeholder,
  281. const QString &defaultValue,
  282. QString value,
  283. PhoneInput::GroupsCallback groupsCallback)
  284. : MaskedInputField(parent, st, std::move(placeholder), value)
  285. , _defaultValue(defaultValue)
  286. , _groupsCallback(std::move(groupsCallback)) {
  287. if (value.isEmpty()) {
  288. clearText();
  289. } else {
  290. auto pos = int(value.size());
  291. correctValue(QString(), 0, value, pos);
  292. }
  293. }
  294. void PhoneInput::focusInEvent(QFocusEvent *e) {
  295. MaskedInputField::focusInEvent(e);
  296. setSelection(cursorPosition(), cursorPosition());
  297. }
  298. void PhoneInput::clearText() {
  299. auto value = _defaultValue;
  300. setText(value);
  301. auto pos = int(value.size());
  302. correctValue(QString(), 0, value, pos);
  303. }
  304. void PhoneInput::paintAdditionalPlaceholder(QPainter &p) {
  305. if (!_pattern.isEmpty()) {
  306. auto t = getDisplayedText();
  307. auto ph = _additionalPlaceholder.mid(t.size());
  308. if (!ph.isEmpty()) {
  309. p.setClipRect(rect());
  310. auto phRect = placeholderRect();
  311. int tw = phFont()->width(t);
  312. if (tw < phRect.width()) {
  313. phRect.setLeft(phRect.left() + tw);
  314. placeholderAdditionalPrepare(p);
  315. p.drawText(phRect, ph, style::al_topleft);
  316. }
  317. }
  318. }
  319. }
  320. void PhoneInput::correctValue(
  321. const QString &was,
  322. int wasCursor,
  323. QString &now,
  324. int &nowCursor) {
  325. auto digits = now;
  326. digits.replace(TextUtilities::RegExpDigitsExclude(), QString());
  327. _pattern = _groupsCallback(digits);
  328. QString newPlaceholder;
  329. if (_pattern.isEmpty()) {
  330. newPlaceholder = QString();
  331. } else if (_pattern.size() == 1 && _pattern.at(0) == digits.size()) {
  332. newPlaceholder = QString(_pattern.at(0) + 2, ' ') + tr::lng_contact_phone(tr::now);
  333. } else {
  334. newPlaceholder.reserve(20);
  335. for (int i = 0, l = _pattern.size(); i < l; ++i) {
  336. if (i) {
  337. newPlaceholder.append(' ');
  338. } else {
  339. newPlaceholder.append('+');
  340. }
  341. newPlaceholder.append(i ? QString(_pattern.at(i), QChar(0x2212)) : digits.mid(0, _pattern.at(i)));
  342. }
  343. }
  344. if (_additionalPlaceholder != newPlaceholder) {
  345. _additionalPlaceholder = newPlaceholder;
  346. setPlaceholderHidden(!_additionalPlaceholder.isEmpty());
  347. update();
  348. }
  349. QString newText;
  350. int oldPos(nowCursor), newPos(-1), oldLen(now.length()), digitCount = qMin(digits.size(), kMaxPhoneCodeLength + kMaxPhoneTailLength);
  351. bool inPart = !_pattern.isEmpty(), plusFound = false;
  352. int curPart = 0, leftInPart = inPart ? _pattern.at(curPart) : 0;
  353. newText.reserve(oldLen + 1);
  354. newText.append('+');
  355. for (int i = 0; i < oldLen; ++i) {
  356. if (i == oldPos && newPos < 0) {
  357. newPos = newText.length();
  358. }
  359. QChar ch(now[i]);
  360. if (ch.isDigit()) {
  361. if (!digitCount--) {
  362. break;
  363. }
  364. if (inPart) {
  365. if (leftInPart) {
  366. --leftInPart;
  367. } else {
  368. ++curPart;
  369. inPart = curPart < _pattern.size();
  370. // Don't add an extra space to the end.
  371. if (inPart) {
  372. newText += ' ';
  373. }
  374. leftInPart = inPart ? (_pattern.at(curPart) - 1) : 0;
  375. ++oldPos;
  376. }
  377. }
  378. newText += ch;
  379. } else if (ch == ' ' || ch == '-' || ch == '(' || ch == ')') {
  380. if (inPart) {
  381. if (leftInPart) {
  382. } else {
  383. newText += ch;
  384. ++curPart;
  385. inPart = curPart < _pattern.size();
  386. leftInPart = inPart ? _pattern.at(curPart) : 0;
  387. }
  388. } else {
  389. newText += ch;
  390. }
  391. } else if (ch == '+') {
  392. plusFound = true;
  393. }
  394. }
  395. if (!plusFound && newText == u"+"_q) {
  396. newText = QString();
  397. newPos = 0;
  398. }
  399. int32 newlen = newText.size();
  400. while (newlen > 0 && newText.at(newlen - 1).isSpace()) {
  401. --newlen;
  402. }
  403. if (newlen < newText.size()) {
  404. newText = newText.mid(0, newlen);
  405. }
  406. setCorrectedText(now, nowCursor, newText, newPos);
  407. }
  408. } // namespace Ui