input_field.h 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #pragma once
  8. #include "base/flat_set.h"
  9. #include "base/timer.h"
  10. #include "ui/emoji_config.h"
  11. #include "ui/rp_widget.h"
  12. #include "ui/effects/animations.h"
  13. #include "ui/text/text_entity.h"
  14. #include "ui/text/text_custom_emoji.h"
  15. #include <rpl/variable.h>
  16. #include <QtGui/QTextCursor>
  17. #include <any>
  18. class QMenu;
  19. class QShortcut;
  20. class QTextEdit;
  21. class QTouchEvent;
  22. class QContextMenuEvent;
  23. class Painter;
  24. namespace Ui::Text {
  25. struct QuotePaintCache;
  26. } // namespace Ui::Text
  27. namespace style {
  28. struct InputField;
  29. } // namespace style
  30. namespace Ui {
  31. const auto kClearFormatSequence = QKeySequence("ctrl+shift+n");
  32. const auto kStrikeOutSequence = QKeySequence("ctrl+shift+x");
  33. const auto kBlockquoteSequence = QKeySequence("ctrl+shift+.");
  34. const auto kMonospaceSequence = QKeySequence("ctrl+shift+m");
  35. const auto kEditLinkSequence = QKeySequence("ctrl+k");
  36. const auto kSpoilerSequence = QKeySequence("ctrl+shift+p");
  37. class PopupMenu;
  38. class InputField;
  39. void InsertEmojiAtCursor(QTextCursor cursor, EmojiPtr emoji);
  40. void InsertCustomEmojiAtCursor(
  41. not_null<InputField*> field,
  42. QTextCursor cursor,
  43. const QString &text,
  44. const QString &link);
  45. struct InstantReplaces {
  46. struct Node {
  47. QString text;
  48. std::map<QChar, Node> tail;
  49. };
  50. void add(const QString &what, const QString &with);
  51. static const InstantReplaces &Default();
  52. static const InstantReplaces &TextOnly();
  53. int maxLength = 0;
  54. Node reverseMap;
  55. };
  56. enum class InputSubmitSettings {
  57. Enter,
  58. CtrlEnter,
  59. Both,
  60. None,
  61. };
  62. class CustomFieldObject;
  63. struct MarkdownEnabled {
  64. base::flat_set<QString> tagsSubset;
  65. friend inline bool operator==(
  66. const MarkdownEnabled &,
  67. const MarkdownEnabled &) = default;
  68. };
  69. struct MarkdownDisabled {
  70. friend inline bool operator==(
  71. const MarkdownDisabled &,
  72. const MarkdownDisabled &) = default;
  73. };
  74. struct MarkdownEnabledState {
  75. std::variant<MarkdownDisabled, MarkdownEnabled> data;
  76. [[nodiscard]] bool disabled() const;
  77. [[nodiscard]] bool enabledForTag(QStringView tag) const;
  78. friend inline bool operator==(
  79. const MarkdownEnabledState &,
  80. const MarkdownEnabledState &) = default;
  81. };
  82. struct InputFieldTextRange {
  83. int from = 0;
  84. int till = 0;
  85. friend inline bool operator==(
  86. InputFieldTextRange,
  87. InputFieldTextRange) = default;
  88. [[nodiscard]] bool empty() const {
  89. return (till <= from);
  90. }
  91. };
  92. struct InputFieldSpoilerRect {
  93. QRect geometry;
  94. bool blockquote = false;
  95. };
  96. class InputField : public RpWidget {
  97. public:
  98. enum class Mode {
  99. SingleLine,
  100. NoNewlines,
  101. MultiLine,
  102. };
  103. using TagList = TextWithTags::Tags;
  104. struct MarkdownTag {
  105. // With each emoji being QChar::ObjectReplacementCharacter.
  106. int internalStart = 0;
  107. int internalLength = 0;
  108. // Adjusted by emoji to match _lastTextWithTags.
  109. int adjustedStart = 0;
  110. int adjustedLength = 0;
  111. bool closed = false;
  112. QString tag;
  113. };
  114. static const QString kTagBold;
  115. static const QString kTagItalic;
  116. static const QString kTagUnderline;
  117. static const QString kTagStrikeOut;
  118. static const QString kTagCode;
  119. static const QString kTagPre;
  120. static const QString kTagSpoiler;
  121. static const QString kTagBlockquote;
  122. static const QString kTagBlockquoteCollapsed;
  123. static const QString kCustomEmojiTagStart;
  124. static const int kCollapsedQuoteFormat; // QTextFormat::ObjectTypes
  125. static const int kCustomEmojiFormat; // QTextFormat::ObjectTypes
  126. static const int kCustomEmojiId; // QTextFormat::Property
  127. static const int kCustomEmojiLink; // QTextFormat::Property
  128. static const int kQuoteId; // QTextFormat::Property
  129. InputField(
  130. QWidget *parent,
  131. const style::InputField &st,
  132. rpl::producer<QString> placeholder,
  133. const QString &value = QString());
  134. InputField(
  135. QWidget *parent,
  136. const style::InputField &st,
  137. Mode mode,
  138. rpl::producer<QString> placeholder,
  139. const QString &value);
  140. InputField(
  141. QWidget *parent,
  142. const style::InputField &st,
  143. Mode mode = Mode::SingleLine,
  144. rpl::producer<QString> placeholder = nullptr,
  145. const TextWithTags &value = TextWithTags());
  146. [[nodiscard]] const style::InputField &st() const {
  147. return _st;
  148. }
  149. void showError();
  150. void showErrorNoFocus();
  151. void hideError();
  152. void setMaxLength(int maxLength);
  153. void setMinHeight(int minHeight);
  154. void setMaxHeight(int maxHeight);
  155. [[nodiscard]] const TextWithTags &getTextWithTags() const {
  156. return _lastTextWithTags;
  157. }
  158. [[nodiscard]] const std::vector<MarkdownTag> &getMarkdownTags() const {
  159. return _lastMarkdownTags;
  160. }
  161. [[nodiscard]] TextWithTags getTextWithTagsPart(
  162. int start,
  163. int end = -1) const;
  164. [[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const;
  165. void insertTag(const QString &text, QString tagId = QString());
  166. [[nodiscard]] bool empty() const {
  167. return _lastTextWithTags.text.isEmpty();
  168. }
  169. enum class HistoryAction {
  170. NewEntry,
  171. MergeEntry,
  172. Clear,
  173. };
  174. void setTextWithTags(
  175. const TextWithTags &textWithTags,
  176. HistoryAction historyAction = HistoryAction::NewEntry);
  177. // If you need to make some preparations of tags before putting them to QMimeData
  178. // (and then to clipboard or to drag-n-drop object), here is a strategy for that.
  179. void setTagMimeProcessor(Fn<QString(QStringView)> processor);
  180. void setCustomTextContext(
  181. Text::MarkedContext context,
  182. Fn<bool()> pausedEmoji = nullptr,
  183. Fn<bool()> pausedSpoiler = nullptr);
  184. struct EditLinkSelection {
  185. int from = 0;
  186. int till = 0;
  187. };
  188. enum class EditLinkAction {
  189. Check,
  190. Edit,
  191. };
  192. void setEditLinkCallback(
  193. Fn<bool(
  194. EditLinkSelection selection,
  195. TextWithTags text,
  196. QString link,
  197. EditLinkAction action)> callback);
  198. void setEditLanguageCallback(
  199. Fn<void(QString now, Fn<void(QString)> save)> callback);
  200. struct ExtendedContextMenu {
  201. QMenu *menu = nullptr;
  202. std::shared_ptr<QContextMenuEvent> event;
  203. };
  204. void setDocumentMargin(float64 margin);
  205. void setAdditionalMargin(int margin);
  206. void setAdditionalMargins(QMargins margins);
  207. void setInstantReplaces(const InstantReplaces &replaces);
  208. void setInstantReplacesEnabled(rpl::producer<bool> enabled);
  209. void setMarkdownReplacesEnabled(bool enabled);
  210. void setMarkdownReplacesEnabled(rpl::producer<MarkdownEnabledState> enabled);
  211. void setExtendedContextMenu(rpl::producer<ExtendedContextMenu> value);
  212. void commitInstantReplacement(
  213. int from,
  214. int till,
  215. const QString &with,
  216. const QString &customEmojiData);
  217. void commitMarkdownLinkEdit(
  218. EditLinkSelection selection,
  219. const TextWithTags &textWithTags,
  220. const QString &link);
  221. [[nodiscard]] static bool IsValidMarkdownLink(QStringView link);
  222. [[nodiscard]] static bool IsCustomEmojiLink(QStringView link);
  223. [[nodiscard]] static QString CustomEmojiLink(QStringView entityData);
  224. [[nodiscard]] static QString CustomEmojiEntityData(QStringView link);
  225. [[nodiscard]] const QString &getLastText() const {
  226. return _lastTextWithTags.text;
  227. }
  228. void setPlaceholder(
  229. rpl::producer<QString> placeholder,
  230. int afterSymbols = 0);
  231. void setPlaceholderHidden(bool forcePlaceholderHidden);
  232. void setDisplayFocused(bool focused);
  233. void finishAnimating();
  234. void setFocusFast() {
  235. setDisplayFocused(true);
  236. setFocus();
  237. }
  238. QSize sizeHint() const override;
  239. QSize minimumSizeHint() const override;
  240. bool hasText() const;
  241. void selectAll();
  242. bool isUndoAvailable() const;
  243. bool isRedoAvailable() const;
  244. [[nodiscard]] MarkdownEnabledState markdownEnabledState() const {
  245. return _markdownEnabledState;
  246. }
  247. using SubmitSettings = InputSubmitSettings;
  248. void setSubmitSettings(SubmitSettings settings);
  249. static bool ShouldSubmit(
  250. SubmitSettings settings,
  251. Qt::KeyboardModifiers modifiers);
  252. void customUpDown(bool isCustom);
  253. void customTab(bool isCustom);
  254. int borderAnimationStart() const;
  255. not_null<QTextDocument*> document();
  256. not_null<const QTextDocument*> document() const;
  257. void setTextCursor(const QTextCursor &cursor);
  258. void setCursorPosition(int position);
  259. QTextCursor textCursor() const;
  260. void setText(const QString &text);
  261. void clear();
  262. bool hasFocus() const;
  263. void setFocus();
  264. void clearFocus();
  265. void ensureCursorVisible();
  266. not_null<QTextEdit*> rawTextEdit();
  267. not_null<const QTextEdit*> rawTextEdit() const;
  268. enum class MimeAction {
  269. Check,
  270. Insert,
  271. };
  272. using MimeDataHook = Fn<bool(
  273. not_null<const QMimeData*> data,
  274. MimeAction action)>;
  275. void setMimeDataHook(MimeDataHook hook) {
  276. _mimeDataHook = std::move(hook);
  277. }
  278. const rpl::variable<int> &scrollTop() const;
  279. int scrollTopMax() const;
  280. void scrollTo(int top);
  281. struct DocumentChangeInfo {
  282. int position = 0;
  283. int added = 0;
  284. int removed = 0;
  285. };
  286. auto documentContentsChanges() {
  287. return _documentContentsChanges.events();
  288. }
  289. auto markdownTagApplies() {
  290. return _markdownTagApplies.events();
  291. }
  292. void setPreCache(Fn<not_null<Ui::Text::QuotePaintCache*>()> make);
  293. void setBlockquoteCache(Fn<not_null<Ui::Text::QuotePaintCache*>()> make);
  294. [[nodiscard]] bool menuShown() const;
  295. [[nodiscard]] rpl::producer<bool> menuShownValue() const;
  296. [[nodiscard]] rpl::producer<> heightChanges() const;
  297. [[nodiscard]] rpl::producer<bool> focusedChanges() const;
  298. [[nodiscard]] rpl::producer<> tabbed() const;
  299. [[nodiscard]] rpl::producer<> cancelled() const;
  300. [[nodiscard]] rpl::producer<> changes() const;
  301. [[nodiscard]] rpl::producer<Qt::KeyboardModifiers> submits() const;
  302. void forceProcessContentsChanges();
  303. ~InputField();
  304. protected:
  305. void startPlaceholderAnimation();
  306. void startBorderAnimation();
  307. void paintEvent(QPaintEvent *e) override;
  308. void focusInEvent(QFocusEvent *e) override;
  309. void mousePressEvent(QMouseEvent *e) override;
  310. void contextMenuEvent(QContextMenuEvent *e) override;
  311. void resizeEvent(QResizeEvent *e) override;
  312. private:
  313. class Inner;
  314. friend class Inner;
  315. friend class CustomFieldObject;
  316. friend class FieldSpoilerOverlay;
  317. using TextRange = InputFieldTextRange;
  318. using SpoilerRect = InputFieldSpoilerRect;
  319. enum class MarkdownActionType {
  320. ToggleTag,
  321. EditLink,
  322. };
  323. struct MarkdownAction {
  324. QKeySequence sequence;
  325. QString tag;
  326. MarkdownActionType type = MarkdownActionType::ToggleTag;
  327. };
  328. void handleContentsChanged();
  329. void updateRootFrameFormat();
  330. bool viewportEventInner(QEvent *e);
  331. void handleTouchEvent(QTouchEvent *e);
  332. void updatePalette();
  333. void refreshPlaceholder(const QString &text);
  334. int placeholderSkipWidth() const;
  335. [[nodiscard]] static std::vector<MarkdownAction> MarkdownActions();
  336. void setupMarkdownShortcuts();
  337. bool executeMarkdownAction(MarkdownAction action);
  338. bool heightAutoupdated();
  339. void checkContentHeight();
  340. void setErrorShown(bool error);
  341. void focusInEventInner(QFocusEvent *e);
  342. void focusOutEventInner(QFocusEvent *e);
  343. void setFocused(bool focused);
  344. void keyPressEventInner(QKeyEvent *e);
  345. void contextMenuEventInner(QContextMenuEvent *e, QMenu *m = nullptr);
  346. void dropEventInner(QDropEvent *e);
  347. void inputMethodEventInner(QInputMethodEvent *e);
  348. void paintEventInner(QPaintEvent *e);
  349. void paintQuotes(QPaintEvent *e);
  350. void mousePressEventInner(QMouseEvent *e);
  351. void mouseReleaseEventInner(QMouseEvent *e);
  352. void mouseMoveEventInner(QMouseEvent *e);
  353. void leaveEventInner(QEvent *e);
  354. [[nodiscard]] int lookupActionQuoteId(QPoint point) const;
  355. void updateCursorShape();
  356. QMimeData *createMimeDataFromSelectionInner() const;
  357. bool canInsertFromMimeDataInner(const QMimeData *source) const;
  358. void insertFromMimeDataInner(const QMimeData *source);
  359. TextWithTags getTextWithTagsSelected() const;
  360. void documentContentsChanged(
  361. int position,
  362. int charsRemoved,
  363. int charsAdded);
  364. void focusInner();
  365. // "start" and "end" are in coordinates of text where emoji are replaced
  366. // by ObjectReplacementCharacter. If "end" = -1 means get text till the end.
  367. [[nodiscard]] QString getTextPart(
  368. int start,
  369. int end,
  370. TagList &outTagsList,
  371. bool &outTagsChanged,
  372. std::vector<MarkdownTag> *outMarkdownTags = nullptr) const;
  373. // After any characters added we must postprocess them. This includes:
  374. // 1. Replacing font family to semibold for ~ characters, if we used Open Sans 13px.
  375. // 2. Replacing font family from semibold for all non-~ characters, if we used ...
  376. // 3. Replacing emoji code sequences by ObjectReplacementCharacters with emoji pics.
  377. // 4. Interrupting tags in which the text was inserted by any char except a letter.
  378. // 5. Applying tags from "_insertedTags" in case we pasted text with tags, not just text.
  379. // Rule 4 applies only if we inserted chars not in the middle of a tag (but at the end).
  380. void processFormatting(int changedPosition, int changedEnd);
  381. void chopByMaxLength(int insertPosition, int insertLength);
  382. bool processMarkdownReplaces(const QString &appended);
  383. //bool processMarkdownReplace(const QString &tag);
  384. void addMarkdownActions(not_null<QMenu*> menu, QContextMenuEvent *e);
  385. void addMarkdownMenuAction(
  386. not_null<QMenu*> menu,
  387. not_null<QAction*> action);
  388. bool handleMarkdownKey(QKeyEvent *e);
  389. // We don't want accidentally detach InstantReplaces map.
  390. // So we access it only by const reference from this method.
  391. const InstantReplaces &instantReplaces() const;
  392. void processInstantReplaces(const QString &appended);
  393. void applyInstantReplace(const QString &what, const QString &with);
  394. struct EditLinkData {
  395. int from = 0;
  396. int till = 0;
  397. QString link;
  398. };
  399. EditLinkData selectionEditLinkData(EditLinkSelection selection) const;
  400. EditLinkSelection editLinkSelection(QContextMenuEvent *e) const;
  401. void editMarkdownLink(EditLinkSelection selection);
  402. void commitInstantReplacement(
  403. int from,
  404. int till,
  405. const QString &with,
  406. const QString &customEmojiData,
  407. std::optional<QString> checkOriginal,
  408. bool checkIfInMonospace);
  409. #if 0
  410. bool commitMarkdownReplacement(
  411. int from,
  412. int till,
  413. const QString &tag,
  414. const QString &edge = QString());
  415. #endif
  416. TextRange insertWithTags(TextRange range, TextWithTags text);
  417. TextRange addMarkdownTag(TextRange range, const QString &tag);
  418. void removeMarkdownTag(TextRange range, const QString &tag);
  419. void finishMarkdownTagChange(
  420. TextRange range,
  421. const TextWithTags &textWithTags);
  422. void toggleSelectionMarkdown(const QString &tag);
  423. void clearSelectionMarkdown();
  424. bool revertFormatReplace();
  425. bool jumpOutOfBlockByBackspace();
  426. void paintSurrounding(
  427. QPainter &p,
  428. QRect clip,
  429. float64 errorDegree,
  430. float64 focusedDegree);
  431. void paintRoundSurrounding(
  432. QPainter &p,
  433. QRect clip,
  434. float64 errorDegree,
  435. float64 focusedDegree);
  436. void paintFlatSurrounding(
  437. QPainter &p,
  438. QRect clip,
  439. float64 errorDegree,
  440. float64 focusedDegree);
  441. void customEmojiRepaint();
  442. void highlightMarkdown();
  443. bool exitQuoteWithNewBlock(int key);
  444. void blockActionClicked(int quoteId);
  445. void editPreLanguage(int quoteId, QStringView tag);
  446. void toggleBlockquoteCollapsed(
  447. int quoteId,
  448. QStringView tag,
  449. TextRange range);
  450. void trippleEnterExitBlock(QTextCursor &cursor);
  451. void touchUpdate(QPoint globalPosition);
  452. void touchFinish();
  453. const style::InputField &_st;
  454. Fn<not_null<Ui::Text::QuotePaintCache*>()> _preCache;
  455. Fn<not_null<Ui::Text::QuotePaintCache*>()> _blockquoteCache;
  456. Mode _mode = Mode::SingleLine;
  457. int _maxLength = -1;
  458. int _minHeight = -1;
  459. int _maxHeight = -1;
  460. const std::unique_ptr<Inner> _inner;
  461. Fn<bool(
  462. EditLinkSelection selection,
  463. TextWithTags text,
  464. QString link,
  465. EditLinkAction action)> _editLinkCallback;
  466. Fn<void(QString now, Fn<void(QString)> save)> _editLanguageCallback;
  467. TextWithTags _lastTextWithTags;
  468. std::vector<MarkdownTag> _lastMarkdownTags;
  469. QString _lastPreEditText;
  470. std::optional<QString> _inputMethodCommit;
  471. mutable std::vector<TextRange> _spoilerRangesText;
  472. mutable std::vector<TextRange> _spoilerRangesEmoji;
  473. mutable std::vector<SpoilerRect> _spoilerRects;
  474. mutable QColor _blockquoteBg;
  475. std::unique_ptr<RpWidget> _spoilerOverlay;
  476. QMargins _additionalMargins;
  477. QMargins _customFontMargins;
  478. int _placeholderCustomFontSkip = 0;
  479. int _requestedDocumentTopMargin = 0;
  480. bool _forcePlaceholderHidden = false;
  481. bool _reverseMarkdownReplacement = false;
  482. bool _customEmojiRepaintScheduled = false;
  483. bool _settingDocumentMargin = false;
  484. // Tags list which we should apply while setText() call or insert from mime data.
  485. TagList _insertedTags;
  486. bool _insertedTagsAreFromMime = false;
  487. bool _insertedTagsReplace = false;
  488. // Override insert position and charsAdded from complex text editing
  489. // (like drag-n-drop in the same text edit field).
  490. int _realInsertPosition = -1;
  491. int _realCharsAdded = 0;
  492. // Calculate the amount of emoji extra chars
  493. // before _documentContentsChanges fire.
  494. int _emojiSurrogateAmount = 0;
  495. Fn<QString(QStringView)> _tagMimeProcessor;
  496. std::unique_ptr<CustomFieldObject> _customObject;
  497. std::optional<QTextCursor> _formattingCursorUpdate;
  498. SubmitSettings _submitSettings = SubmitSettings::Enter;
  499. MarkdownEnabledState _markdownEnabledState;
  500. bool _undoAvailable = false;
  501. bool _redoAvailable = false;
  502. bool _insertedTagsDelayClear = false;
  503. bool _inHeightCheck = false;
  504. bool _customUpDown = false;
  505. bool _customTab = false;
  506. rpl::variable<QString> _placeholderFull;
  507. QString _placeholder;
  508. int _placeholderAfterSymbols = 0;
  509. Animations::Simple _a_placeholderShifted;
  510. bool _placeholderShifted = false;
  511. QPainterPath _placeholderPath;
  512. Animations::Simple _a_borderShown;
  513. int _borderAnimationStart = 0;
  514. Animations::Simple _a_borderOpacity;
  515. bool _borderVisible = false;
  516. Animations::Simple _a_focused;
  517. Animations::Simple _a_error;
  518. bool _focused = false;
  519. bool _error = false;
  520. base::Timer _touchTimer;
  521. bool _touchPress = false;
  522. bool _touchRightButton = false;
  523. bool _touchMove = false;
  524. bool _mousePressedInTouch = false;
  525. QPoint _touchStart;
  526. bool _correcting = false;
  527. MimeDataHook _mimeDataHook;
  528. rpl::event_stream<bool> _menuShownChanges;
  529. base::unique_qptr<PopupMenu> _contextMenu;
  530. QTextCharFormat _defaultCharFormat;
  531. int _selectedActionQuoteId = 0;
  532. int _pressedActionQuoteId = -1;
  533. rpl::variable<int> _scrollTop;
  534. InstantReplaces _mutableInstantReplaces;
  535. bool _instantReplacesEnabled = true;
  536. rpl::event_stream<DocumentChangeInfo> _documentContentsChanges;
  537. rpl::event_stream<MarkdownTag> _markdownTagApplies;
  538. std::vector<std::unique_ptr<QShortcut>> _markdownShortcuts;
  539. rpl::event_stream<bool> _focusedChanges;
  540. rpl::event_stream<> _heightChanges;
  541. rpl::event_stream<> _tabbed;
  542. rpl::event_stream<> _cancelled;
  543. rpl::event_stream<> _changes;
  544. rpl::event_stream<Qt::KeyboardModifiers> _submits;
  545. };
  546. void PrepareFormattingOptimization(not_null<QTextDocument*> document);
  547. [[nodiscard]] int ComputeRealUnicodeCharactersCount(const QString &text);
  548. [[nodiscard]] int ComputeFieldCharacterCount(not_null<InputField*> field);
  549. void AddLengthLimitLabel(
  550. not_null<InputField*> field,
  551. int limit,
  552. std::optional<uint> customThreshold = std::nullopt,
  553. int limitLabelTop = 0);
  554. } // namespace Ui