payments_field.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719
  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 "payments/ui/payments_field.h"
  8. #include "ui/widgets/fields/input_field.h"
  9. #include "ui/boxes/country_select_box.h"
  10. #include "ui/text/format_values.h"
  11. #include "ui/ui_utility.h"
  12. #include "ui/widgets/fields/special_fields.h"
  13. #include "countries/countries_instance.h"
  14. #include "base/platform/base_platform_info.h"
  15. #include "base/event_filter.h"
  16. #include "base/qt/qt_common_adapters.h"
  17. #include "styles/style_payments.h"
  18. #include <QtCore/QRegularExpression>
  19. #include <QtWidgets/QTextEdit>
  20. namespace Payments::Ui {
  21. namespace {
  22. struct SimpleFieldState {
  23. QString value;
  24. int position = 0;
  25. };
  26. [[nodiscard]] char FieldThousandsSeparator(const CurrencyRule &rule) {
  27. return (rule.thousands == '.' || rule.thousands == ',')
  28. ? ' '
  29. : rule.thousands;
  30. }
  31. [[nodiscard]] QString RemoveNonNumbers(QString value) {
  32. static const auto RegExp = QRegularExpression("[^0-9]");
  33. return value.replace(RegExp, QString());
  34. }
  35. [[nodiscard]] SimpleFieldState CleanMoneyState(
  36. const CurrencyRule &rule,
  37. SimpleFieldState state) {
  38. const auto withDecimal = state.value.replace(
  39. QChar('.'),
  40. rule.decimal
  41. ).replace(
  42. QChar(','),
  43. rule.decimal
  44. );
  45. const auto digitsLimit = 16 - rule.exponent;
  46. const auto beforePosition = state.value.mid(0, state.position);
  47. auto decimalPosition = int(withDecimal.lastIndexOf(rule.decimal));
  48. if (decimalPosition < 0) {
  49. state = {
  50. .value = RemoveNonNumbers(state.value),
  51. .position = int(RemoveNonNumbers(beforePosition).size()),
  52. };
  53. } else {
  54. const auto onlyNumbersBeforeDecimal = RemoveNonNumbers(
  55. state.value.mid(0, decimalPosition));
  56. state = {
  57. .value = (onlyNumbersBeforeDecimal
  58. + QChar(rule.decimal)
  59. + RemoveNonNumbers(state.value.mid(decimalPosition + 1))),
  60. .position = int(RemoveNonNumbers(beforePosition).size()
  61. + (state.position > decimalPosition ? 1 : 0)),
  62. };
  63. decimalPosition = onlyNumbersBeforeDecimal.size();
  64. const auto maxLength = decimalPosition + 1 + rule.exponent;
  65. if (state.value.size() > maxLength) {
  66. state = {
  67. .value = state.value.mid(0, maxLength),
  68. .position = std::min(state.position, maxLength),
  69. };
  70. }
  71. }
  72. if (!state.value.isEmpty() && state.value[0] == QChar(rule.decimal)) {
  73. state = {
  74. .value = QChar('0') + state.value,
  75. .position = state.position + 1,
  76. };
  77. if (decimalPosition >= 0) {
  78. ++decimalPosition;
  79. }
  80. }
  81. auto skip = 0;
  82. while (state.value.size() > skip + 1
  83. && state.value[skip] == QChar('0')
  84. && state.value[skip + 1] != QChar(rule.decimal)) {
  85. ++skip;
  86. }
  87. state = {
  88. .value = state.value.mid(skip),
  89. .position = std::max(state.position - skip, 0),
  90. };
  91. if (decimalPosition >= 0) {
  92. Assert(decimalPosition >= skip);
  93. decimalPosition -= skip;
  94. if (decimalPosition > digitsLimit) {
  95. state = {
  96. .value = (state.value.mid(0, digitsLimit)
  97. + state.value.mid(decimalPosition)),
  98. .position = (state.position > digitsLimit
  99. ? std::max(
  100. state.position - (decimalPosition - digitsLimit),
  101. digitsLimit)
  102. : state.position),
  103. };
  104. }
  105. } else if (state.value.size() > digitsLimit) {
  106. state = {
  107. .value = state.value.mid(0, digitsLimit),
  108. .position = std::min(state.position, digitsLimit),
  109. };
  110. }
  111. return state;
  112. }
  113. [[nodiscard]] SimpleFieldState PostprocessMoneyResult(
  114. const CurrencyRule &rule,
  115. SimpleFieldState result) {
  116. const auto position = result.value.indexOf(rule.decimal);
  117. const auto from = (position >= 0) ? position : result.value.size();
  118. for (auto insertAt = from - 3; insertAt > 0; insertAt -= 3) {
  119. result.value.insert(insertAt, QChar(FieldThousandsSeparator(rule)));
  120. if (result.position >= insertAt) {
  121. ++result.position;
  122. }
  123. }
  124. return result;
  125. }
  126. [[nodiscard]] bool IsBackspace(const FieldValidateRequest &request) {
  127. return (request.wasAnchor == request.wasPosition)
  128. && (request.wasPosition == request.nowPosition + 1)
  129. && (base::StringViewMid(request.wasValue, 0, request.wasPosition - 1)
  130. == base::StringViewMid(request.nowValue, 0, request.nowPosition))
  131. && (base::StringViewMid(request.wasValue, request.wasPosition)
  132. == base::StringViewMid(request.nowValue, request.nowPosition));
  133. }
  134. [[nodiscard]] bool IsDelete(const FieldValidateRequest &request) {
  135. return (request.wasAnchor == request.wasPosition)
  136. && (request.wasPosition == request.nowPosition)
  137. && (base::StringViewMid(request.wasValue, 0, request.wasPosition)
  138. == base::StringViewMid(request.nowValue, 0, request.nowPosition))
  139. && (base::StringViewMid(request.wasValue, request.wasPosition + 1)
  140. == base::StringViewMid(request.nowValue, request.nowPosition));
  141. }
  142. [[nodiscard]] auto MoneyValidator(const CurrencyRule &rule) {
  143. return [=](FieldValidateRequest request) {
  144. const auto realNowState = [&] {
  145. const auto backspaced = IsBackspace(request);
  146. const auto deleted = IsDelete(request);
  147. if (!backspaced && !deleted) {
  148. return CleanMoneyState(rule, {
  149. .value = request.nowValue,
  150. .position = request.nowPosition,
  151. });
  152. }
  153. const auto realWasState = CleanMoneyState(rule, {
  154. .value = request.wasValue,
  155. .position = request.wasPosition,
  156. });
  157. const auto changedValue = deleted
  158. ? (realWasState.value.mid(0, realWasState.position)
  159. + realWasState.value.mid(realWasState.position + 1))
  160. : (realWasState.position > 1)
  161. ? (realWasState.value.mid(0, realWasState.position - 1)
  162. + realWasState.value.mid(realWasState.position))
  163. : realWasState.value.mid(realWasState.position);
  164. return SimpleFieldState{
  165. .value = changedValue,
  166. .position = (deleted
  167. ? realWasState.position
  168. : std::max(realWasState.position - 1, 0))
  169. };
  170. }();
  171. const auto postprocessed = PostprocessMoneyResult(
  172. rule,
  173. realNowState);
  174. return FieldValidateResult{
  175. .value = postprocessed.value,
  176. .position = postprocessed.position,
  177. };
  178. };
  179. }
  180. [[nodiscard]] QString Parse(const FieldConfig &config) {
  181. if (config.type == FieldType::Country) {
  182. return Countries::Instance().countryNameByISO2(config.value);
  183. } else if (config.type == FieldType::Money) {
  184. const auto amount = config.value.toLongLong();
  185. if (!amount) {
  186. return QString();
  187. }
  188. const auto rule = LookupCurrencyRule(config.currency);
  189. const auto value = std::abs(amount) / std::pow(10., rule.exponent);
  190. const auto precision = (!rule.stripDotZero
  191. || std::floor(value) != value)
  192. ? rule.exponent
  193. : 0;
  194. return FormatWithSeparators(
  195. value,
  196. precision,
  197. rule.decimal,
  198. FieldThousandsSeparator(rule));
  199. }
  200. return config.value;
  201. }
  202. [[nodiscard]] QString Format(
  203. const FieldConfig &config,
  204. const QString &parsed,
  205. const QString &countryIso2) {
  206. if (config.type == FieldType::Country) {
  207. return countryIso2;
  208. } else if (config.type == FieldType::Money) {
  209. static const auto RegExp = QRegularExpression("[^0-9\\.]");
  210. const auto rule = LookupCurrencyRule(config.currency);
  211. const auto real = QString(parsed).replace(
  212. QChar(rule.decimal),
  213. QChar('.')
  214. ).replace(
  215. QChar(','),
  216. QChar('.')
  217. ).replace(
  218. RegExp,
  219. QString()
  220. ).toDouble();
  221. return QString::number(
  222. int64(base::SafeRound(real * std::pow(10., rule.exponent))));
  223. } else if (config.type == FieldType::CardNumber
  224. || config.type == FieldType::CardCVC) {
  225. static const auto RegExp = QRegularExpression("[^0-9]");
  226. return QString(parsed).replace(RegExp, QString());
  227. }
  228. return parsed;
  229. }
  230. [[nodiscard]] bool UseMaskedField(FieldType type) {
  231. switch (type) {
  232. case FieldType::Text:
  233. case FieldType::Email:
  234. return false;
  235. case FieldType::CardNumber:
  236. case FieldType::CardExpireDate:
  237. case FieldType::CardCVC:
  238. case FieldType::Country:
  239. case FieldType::Phone:
  240. case FieldType::Money:
  241. return true;
  242. }
  243. Unexpected("FieldType in Payments::Ui::UseMaskedField.");
  244. }
  245. [[nodiscard]] base::unique_qptr<RpWidget> CreateWrap(
  246. QWidget *parent,
  247. FieldConfig &config) {
  248. switch (config.type) {
  249. case FieldType::Text:
  250. case FieldType::Email:
  251. return base::make_unique_q<InputField>(
  252. parent,
  253. st::paymentsField,
  254. std::move(config.placeholder),
  255. Parse(config));
  256. case FieldType::CardNumber:
  257. case FieldType::CardExpireDate:
  258. case FieldType::CardCVC:
  259. case FieldType::Country:
  260. case FieldType::Phone:
  261. case FieldType::Money:
  262. return base::make_unique_q<RpWidget>(parent);
  263. }
  264. Unexpected("FieldType in Payments::Ui::CreateWrap.");
  265. }
  266. [[nodiscard]] InputField *LookupInputField(
  267. not_null<RpWidget*> wrap,
  268. FieldConfig &config) {
  269. return UseMaskedField(config.type)
  270. ? nullptr
  271. : static_cast<InputField*>(wrap.get());
  272. }
  273. [[nodiscard]] MaskedInputField *CreateMoneyField(
  274. not_null<RpWidget*> wrap,
  275. FieldConfig &config,
  276. rpl::producer<> textPossiblyChanged) {
  277. struct State {
  278. CurrencyRule rule;
  279. style::InputField st;
  280. QString currencyText;
  281. int currencySkip = 0;
  282. FlatLabel *left = nullptr;
  283. FlatLabel *right = nullptr;
  284. };
  285. const auto state = wrap->lifetime().make_state<State>(State{
  286. .rule = LookupCurrencyRule(config.currency),
  287. .st = st::paymentsMoneyField,
  288. });
  289. const auto &rule = state->rule;
  290. state->currencySkip = rule.space ? state->st.style.font->spacew : 0;
  291. state->currencyText = ((!rule.left && rule.space)
  292. ? QString(QChar(' '))
  293. : QString()) + (*rule.international
  294. ? QString(rule.international)
  295. : config.currency) + ((rule.left && rule.space)
  296. ? QString(QChar(' '))
  297. : QString());
  298. if (rule.left) {
  299. state->left = CreateChild<FlatLabel>(
  300. wrap.get(),
  301. state->currencyText,
  302. st::paymentsFieldAdditional);
  303. }
  304. state->right = CreateChild<FlatLabel>(
  305. wrap.get(),
  306. QString(),
  307. st::paymentsFieldAdditional);
  308. const auto leftSkip = state->left
  309. ? (state->left->textMaxWidth() + state->currencySkip)
  310. : 0;
  311. const auto rightSkip = st::paymentsFieldAdditional.style.font->width(
  312. QString(QChar(rule.decimal))
  313. + QString(QChar('0')).repeated(rule.exponent)
  314. + (rule.left ? QString() : state->currencyText));
  315. state->st.textMargins += QMargins(leftSkip, 0, rightSkip, 0);
  316. state->st.placeholderMargins -= QMargins(leftSkip, 0, rightSkip, 0);
  317. const auto result = CreateChild<MaskedInputField>(
  318. wrap.get(),
  319. state->st,
  320. std::move(config.placeholder),
  321. Parse(config));
  322. result->setPlaceholderHidden(true);
  323. if (state->left) {
  324. state->left->move(0, state->st.textMargins.top());
  325. }
  326. const auto updateRight = [=] {
  327. const auto text = result->getLastText();
  328. const auto width = state->st.style.font->width(text);
  329. const auto &rule = state->rule;
  330. const auto symbol = QChar(rule.decimal);
  331. const auto decimal = text.indexOf(symbol);
  332. const auto zeros = (decimal >= 0)
  333. ? std::max(rule.exponent - int(text.size() - decimal - 1), 0)
  334. : rule.stripDotZero
  335. ? 0
  336. : rule.exponent;
  337. const auto valueDecimalSeparator = (decimal >= 0 || !zeros)
  338. ? QString()
  339. : QString(symbol);
  340. const auto zeroString = QString(QChar('0'));
  341. const auto valueRightPart = (text.isEmpty() ? zeroString : QString())
  342. + valueDecimalSeparator
  343. + zeroString.repeated(zeros);
  344. const auto right = valueRightPart
  345. + (rule.left ? QString() : state->currencyText);
  346. state->right->setText(right);
  347. state->right->setTextColorOverride(valueRightPart.isEmpty()
  348. ? std::nullopt
  349. : std::make_optional(st::windowSubTextFg->c));
  350. state->right->move(
  351. (state->st.textMargins.left()
  352. + width
  353. + ((rule.left || !valueRightPart.isEmpty())
  354. ? 0
  355. : state->currencySkip)),
  356. state->st.textMargins.top());
  357. };
  358. std::move(
  359. textPossiblyChanged
  360. ) | rpl::start_with_next(updateRight, result->lifetime());
  361. if (state->left) {
  362. state->left->raise();
  363. }
  364. state->right->raise();
  365. return result;
  366. }
  367. [[nodiscard]] MaskedInputField *LookupMaskedField(
  368. not_null<RpWidget*> wrap,
  369. FieldConfig &config,
  370. rpl::producer<> textPossiblyChanged) {
  371. if (!UseMaskedField(config.type)) {
  372. return nullptr;
  373. }
  374. switch (config.type) {
  375. case FieldType::Text:
  376. case FieldType::Email:
  377. return nullptr;
  378. case FieldType::CardNumber:
  379. case FieldType::CardExpireDate:
  380. case FieldType::CardCVC:
  381. case FieldType::Country:
  382. return CreateChild<MaskedInputField>(
  383. wrap.get(),
  384. st::paymentsField,
  385. std::move(config.placeholder),
  386. Parse(config));
  387. case FieldType::Phone:
  388. return CreateChild<PhoneInput>(
  389. wrap.get(),
  390. st::paymentsField,
  391. std::move(config.placeholder),
  392. Countries::ExtractPhoneCode(config.defaultPhone),
  393. Parse(config),
  394. [](const QString &s) { return Countries::Groups(s); });
  395. case FieldType::Money:
  396. return CreateMoneyField(
  397. wrap,
  398. config,
  399. std::move(textPossiblyChanged));
  400. }
  401. Unexpected("FieldType in Payments::Ui::LookupMaskedField.");
  402. }
  403. } // namespace
  404. Field::Field(QWidget *parent, FieldConfig &&config)
  405. : _config(config)
  406. , _wrap(CreateWrap(parent, config))
  407. , _input(LookupInputField(_wrap.get(), config))
  408. , _masked(LookupMaskedField(
  409. _wrap.get(),
  410. config,
  411. _textPossiblyChanged.events_starting_with({})))
  412. , _countryIso2(config.value) {
  413. if (_masked) {
  414. setupMaskedGeometry();
  415. }
  416. if (_config.type == FieldType::Country) {
  417. setupCountry();
  418. }
  419. if (const auto &validator = config.validator) {
  420. setupValidator(validator);
  421. } else if (config.type == FieldType::Money) {
  422. setupValidator(MoneyValidator(LookupCurrencyRule(config.currency)));
  423. }
  424. setupFrontBackspace();
  425. setupSubmit();
  426. }
  427. RpWidget *Field::widget() const {
  428. return _wrap.get();
  429. }
  430. object_ptr<RpWidget> Field::ownedWidget() const {
  431. return object_ptr<RpWidget>::fromRaw(_wrap.get());
  432. }
  433. QString Field::value() const {
  434. return Format(
  435. _config,
  436. _input ? _input->getLastText() : _masked->getLastText(),
  437. _countryIso2);
  438. }
  439. rpl::producer<> Field::frontBackspace() const {
  440. return _frontBackspace.events();
  441. }
  442. rpl::producer<> Field::finished() const {
  443. return _finished.events();
  444. }
  445. rpl::producer<> Field::submitted() const {
  446. return _submitted.events();
  447. }
  448. void Field::setupMaskedGeometry() {
  449. Expects(_masked != nullptr);
  450. _wrap->resize(_masked->size());
  451. _wrap->widthValue(
  452. ) | rpl::start_with_next([=](int width) {
  453. _masked->resize(width, _masked->height());
  454. }, _masked->lifetime());
  455. _masked->heightValue(
  456. ) | rpl::start_with_next([=](int height) {
  457. _wrap->resize(_wrap->width(), height);
  458. }, _masked->lifetime());
  459. }
  460. void Field::setupCountry() {
  461. Expects(_config.type == FieldType::Country);
  462. Expects(_masked != nullptr);
  463. QObject::connect(_masked, &MaskedInputField::focused, [=] {
  464. setFocus();
  465. const auto name = Countries::Instance().countryNameByISO2(
  466. _countryIso2);
  467. const auto country = !name.isEmpty()
  468. ? _countryIso2
  469. : !_config.defaultCountry.isEmpty()
  470. ? _config.defaultCountry
  471. : Platform::SystemCountry();
  472. auto box = Box<CountrySelectBox>(
  473. country,
  474. CountrySelectBox::Type::Countries);
  475. const auto raw = box.data();
  476. raw->countryChosen(
  477. ) | rpl::start_with_next([=](QString iso2) {
  478. _countryIso2 = iso2;
  479. _masked->setText(Countries::Instance().countryNameByISO2(iso2));
  480. _masked->hideError();
  481. raw->closeBox();
  482. if (!iso2.isEmpty()) {
  483. if (_nextField) {
  484. _nextField->activate();
  485. } else {
  486. _submitted.fire({});
  487. }
  488. }
  489. }, _masked->lifetime());
  490. raw->boxClosing() | rpl::start_with_next([=] {
  491. setFocus();
  492. }, _masked->lifetime());
  493. _config.showBox(std::move(box));
  494. });
  495. }
  496. void Field::setupValidator(Fn<ValidateResult(ValidateRequest)> validator) {
  497. Expects(validator != nullptr);
  498. const auto state = [=]() -> State {
  499. if (_masked) {
  500. const auto position = _masked->cursorPosition();
  501. const auto selectionStart = _masked->selectionStart();
  502. const auto selectionEnd = _masked->selectionEnd();
  503. return {
  504. .value = _masked->getLastText(),
  505. .position = position,
  506. .anchor = (selectionStart == selectionEnd
  507. ? position
  508. : (selectionStart == position)
  509. ? selectionEnd
  510. : selectionStart),
  511. };
  512. }
  513. const auto cursor = _input->textCursor();
  514. return {
  515. .value = _input->getLastText(),
  516. .position = cursor.position(),
  517. .anchor = cursor.anchor(),
  518. };
  519. };
  520. const auto save = [=] {
  521. _was = state();
  522. };
  523. const auto setText = [=](const QString &text) {
  524. if (_masked) {
  525. _masked->setText(text);
  526. } else {
  527. _input->setText(text);
  528. }
  529. };
  530. const auto setPosition = [=](int position) {
  531. if (_masked) {
  532. _masked->setCursorPosition(position);
  533. } else {
  534. auto cursor = _input->textCursor();
  535. cursor.setPosition(position);
  536. _input->setTextCursor(cursor);
  537. }
  538. };
  539. const auto validate = [=] {
  540. if (_validating) {
  541. return;
  542. }
  543. _validating = true;
  544. const auto guard = gsl::finally([&] {
  545. _validating = false;
  546. save();
  547. _textPossiblyChanged.fire({});
  548. });
  549. const auto now = state();
  550. const auto result = validator(ValidateRequest{
  551. .wasValue = _was.value,
  552. .wasPosition = _was.position,
  553. .wasAnchor = _was.anchor,
  554. .nowValue = now.value,
  555. .nowPosition = now.position,
  556. });
  557. _valid = result.finished || !result.invalid;
  558. const auto changed = (result.value != now.value);
  559. if (changed) {
  560. setText(result.value);
  561. }
  562. if (changed || result.position != now.position) {
  563. setPosition(result.position);
  564. }
  565. if (result.finished) {
  566. _finished.fire({});
  567. } else if (result.invalid) {
  568. Ui::PostponeCall(
  569. _masked ? (QWidget*)_masked : _input,
  570. [=] { showErrorNoFocus(); });
  571. }
  572. };
  573. if (_masked) {
  574. QObject::connect(_masked, &QLineEdit::cursorPositionChanged, save);
  575. QObject::connect(_masked, &MaskedInputField::changed, validate);
  576. } else {
  577. const auto raw = _input->rawTextEdit();
  578. QObject::connect(raw, &QTextEdit::cursorPositionChanged, save);
  579. _input->changes(
  580. ) | rpl::start_with_next(validate, _input->lifetime());
  581. }
  582. }
  583. void Field::setupFrontBackspace() {
  584. const auto filter = [=](not_null<QEvent*> e) {
  585. const auto frontBackspace = (e->type() == QEvent::KeyPress)
  586. && (static_cast<QKeyEvent*>(e.get())->key() == Qt::Key_Backspace)
  587. && (_masked
  588. ? (_masked->cursorPosition() == 0
  589. && _masked->selectionLength() == 0)
  590. : (_input->textCursor().position() == 0
  591. && _input->textCursor().anchor() == 0));
  592. if (frontBackspace) {
  593. _frontBackspace.fire({});
  594. }
  595. return base::EventFilterResult::Continue;
  596. };
  597. if (_masked) {
  598. base::install_event_filter(_masked, filter);
  599. } else {
  600. base::install_event_filter(_input->rawTextEdit(), filter);
  601. }
  602. }
  603. void Field::setupSubmit() {
  604. const auto submitted = [=] {
  605. if (!_valid) {
  606. showError();
  607. } else if (_nextField) {
  608. _nextField->activate();
  609. } else {
  610. _submitted.fire({});
  611. }
  612. };
  613. if (_masked) {
  614. QObject::connect(_masked, &MaskedInputField::submitted, submitted);
  615. } else {
  616. _input->submits(
  617. ) | rpl::start_with_next(submitted, _input->lifetime());
  618. }
  619. }
  620. void Field::setNextField(not_null<Field*> field) {
  621. _nextField = field;
  622. finished() | rpl::start_with_next([=] {
  623. field->setFocus();
  624. }, _masked ? _masked->lifetime() : _input->lifetime());
  625. }
  626. void Field::setPreviousField(not_null<Field*> field) {
  627. frontBackspace(
  628. ) | rpl::start_with_next([=] {
  629. field->setFocus();
  630. }, _masked ? _masked->lifetime() : _input->lifetime());
  631. }
  632. void Field::activate() {
  633. if (_input) {
  634. _input->setFocus();
  635. } else {
  636. _masked->setFocus();
  637. }
  638. }
  639. void Field::setFocus() {
  640. if (_config.type == FieldType::Country) {
  641. _wrap->setFocus();
  642. } else {
  643. activate();
  644. }
  645. }
  646. void Field::setFocusFast() {
  647. if (_config.type == FieldType::Country) {
  648. setFocus();
  649. } else if (_input) {
  650. _input->setFocusFast();
  651. } else {
  652. _masked->setFocusFast();
  653. }
  654. }
  655. void Field::showError() {
  656. if (_config.type == FieldType::Country) {
  657. setFocus();
  658. _masked->showErrorNoFocus();
  659. } else if (_input) {
  660. _input->showError();
  661. } else {
  662. _masked->showError();
  663. }
  664. }
  665. void Field::showErrorNoFocus() {
  666. if (_input) {
  667. _input->showErrorNoFocus();
  668. } else {
  669. _masked->showErrorNoFocus();
  670. }
  671. }
  672. } // namespace Payments::Ui