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 
9 #include "ui/emoji_config.h"
10 #include "ui/rp_widget.h"
11 #include "ui/effects/animations.h"
12 #include "ui/text/text_entity.h"
13 #include "styles/style_widgets.h"
14 
15 #include <QContextMenuEvent>
16 #include <QtWidgets/QLineEdit>
17 #include <QtWidgets/QTextEdit>
18 #include <QtCore/QTimer>
19 
20 class QTouchEvent;
21 class Painter;
22 
23 namespace Ui {
24 
25 const auto kClearFormatSequence = QKeySequence("ctrl+shift+n");
26 const auto kStrikeOutSequence = QKeySequence("ctrl+shift+x");
27 const auto kMonospaceSequence = QKeySequence("ctrl+shift+m");
28 const auto kEditLinkSequence = QKeySequence("ctrl+k");
29 
30 class PopupMenu;
31 
32 void InsertEmojiAtCursor(QTextCursor cursor, EmojiPtr emoji);
33 
34 struct InstantReplaces {
35 	struct Node {
36 		QString text;
37 		std::map<QChar, Node> tail;
38 	};
39 
40 	void add(const QString &what, const QString &with);
41 
42 	static const InstantReplaces &Default();
43 	static const InstantReplaces &TextOnly();
44 
45 	int maxLength = 0;
46 	Node reverseMap;
47 
48 };
49 
50 enum class InputSubmitSettings {
51 	Enter,
52 	CtrlEnter,
53 	Both,
54 	None,
55 };
56 
57 class FlatInput : public RpWidgetBase<QLineEdit> {
58 	// The Q_OBJECT meta info is used for qobject_cast!
59 	Q_OBJECT
60 
61 	using Parent = RpWidgetBase<QLineEdit>;
62 public:
63 	FlatInput(
64 		QWidget *parent,
65 		const style::FlatInput &st,
66 		rpl::producer<QString> placeholder = nullptr,
67 		const QString &val = QString());
68 
69 	void updatePlaceholder();
70 	void setPlaceholder(rpl::producer<QString> placeholder);
71 	QRect placeholderRect() const;
72 
73 	void finishAnimations();
74 
75 	void setTextMrg(const QMargins &textMrg);
76 	QRect getTextRect() const;
77 
78 	QSize sizeHint() const override;
79 	QSize minimumSizeHint() const override;
80 
81 	void customUpDown(bool isCustom);
getLastText()82 	const QString &getLastText() const {
83 		return _oldtext;
84 	}
85 
86 public Q_SLOTS:
87 	void onTextChange(const QString &text);
88 	void onTextEdited();
89 
90 	void onTouchTimer();
91 
92 Q_SIGNALS:
93 	void changed();
94 	void cancelled();
95 	void submitted(Qt::KeyboardModifiers);
96 	void focused();
97 	void blurred();
98 
99 protected:
100 	bool eventHook(QEvent *e) override;
101 	void touchEvent(QTouchEvent *e);
102 	void paintEvent(QPaintEvent *e) override;
103 	void focusInEvent(QFocusEvent *e) override;
104 	void focusOutEvent(QFocusEvent *e) override;
105 	void keyPressEvent(QKeyEvent *e) override;
106 	void resizeEvent(QResizeEvent *e) override;
107 	void contextMenuEvent(QContextMenuEvent *e) override;
108 	void inputMethodEvent(QInputMethodEvent *e) override;
109 
110 	virtual void correctValue(const QString &was, QString &now);
111 
phFont()112 	style::font phFont() {
113 		return _st.font;
114 	}
115 
116 	void phPrepare(QPainter &p, float64 placeholderFocused);
117 
118 private:
119 	void updatePalette();
120 	void refreshPlaceholder(const QString &text);
121 
122 	QString _oldtext;
123 	rpl::variable<QString> _placeholderFull;
124 	QString _placeholder;
125 
126 	bool _customUpDown = false;
127 
128 	bool _focused = false;
129 	bool _placeholderVisible = true;
130 	Animations::Simple _placeholderFocusedAnimation;
131 	Animations::Simple _placeholderVisibleAnimation;
132 	bool _lastPreEditTextNotEmpty = false;
133 
134 	const style::FlatInput &_st;
135 	QMargins _textMrg;
136 
137 	QTimer _touchTimer;
138 	bool _touchPress, _touchRightButton, _touchMove;
139 	QPoint _touchStart;
140 };
141 
142 class InputField : public RpWidget {
143 	Q_OBJECT
144 
145 public:
146 	enum class Mode {
147 		SingleLine,
148 		NoNewlines,
149 		MultiLine,
150 	};
151 	using TagList = TextWithTags::Tags;
152 
153 	struct MarkdownTag {
154 		// With each emoji being QChar::ObjectReplacementCharacter.
155 		int internalStart = 0;
156 		int internalLength = 0;
157 
158 		// Adjusted by emoji to match _lastTextWithTags.
159 		int adjustedStart = 0;
160 		int adjustedLength = 0;
161 
162 		bool closed = false;
163 		QString tag;
164 	};
165 	static const QString kTagBold;
166 	static const QString kTagItalic;
167 	static const QString kTagUnderline;
168 	static const QString kTagStrikeOut;
169 	static const QString kTagCode;
170 	static const QString kTagPre;
171 
172 	InputField(
173 		QWidget *parent,
174 		const style::InputField &st,
175 		rpl::producer<QString> placeholder,
176 		const QString &value = QString());
177 	InputField(
178 		QWidget *parent,
179 		const style::InputField &st,
180 		Mode mode,
181 		rpl::producer<QString> placeholder,
182 		const QString &value);
183 	InputField(
184 		QWidget *parent,
185 		const style::InputField &st,
186 		Mode mode = Mode::SingleLine,
187 		rpl::producer<QString> placeholder = nullptr,
188 		const TextWithTags &value = TextWithTags());
189 
190 	void showError();
191 	void showErrorNoFocus();
192 	void hideError();
193 
194 	void setMaxLength(int maxLength);
195 	void setMinHeight(int minHeight);
196 	void setMaxHeight(int maxHeight);
197 
getTextWithTags()198 	const TextWithTags &getTextWithTags() const {
199 		return _lastTextWithTags;
200 	}
getMarkdownTags()201 	const std::vector<MarkdownTag> &getMarkdownTags() const {
202 		return _lastMarkdownTags;
203 	}
204 	TextWithTags getTextWithTagsPart(int start, int end = -1) const;
205 	TextWithTags getTextWithAppliedMarkdown() const;
206 	void insertTag(const QString &text, QString tagId = QString());
empty()207 	bool empty() const {
208 		return _lastTextWithTags.text.isEmpty();
209 	}
210 	enum class HistoryAction {
211 		NewEntry,
212 		MergeEntry,
213 		Clear,
214 	};
215 	void setTextWithTags(
216 		const TextWithTags &textWithTags,
217 		HistoryAction historyAction = HistoryAction::NewEntry);
218 
219 	// If you need to make some preparations of tags before putting them to QMimeData
220 	// (and then to clipboard or to drag-n-drop object), here is a strategy for that.
221 	class TagMimeProcessor {
222 	public:
223 		virtual QString tagFromMimeTag(const QString &mimeTag) = 0;
224 		virtual ~TagMimeProcessor() = default;
225 	};
226 	void setTagMimeProcessor(std::unique_ptr<TagMimeProcessor> &&processor);
227 
228 	struct EditLinkSelection {
229 		int from = 0;
230 		int till = 0;
231 	};
232 	enum class EditLinkAction {
233 		Check,
234 		Edit,
235 	};
236 	void setEditLinkCallback(
237 		Fn<bool(
238 			EditLinkSelection selection,
239 			QString text,
240 			QString link,
241 			EditLinkAction action)> callback);
242 
243 	struct ExtendedContextMenu {
244 		QMenu *menu = nullptr;
245 		std::shared_ptr<QContextMenuEvent> event;
246 	};
247 
248 	void setAdditionalMargin(int margin);
249 
250 	void setInstantReplaces(const InstantReplaces &replaces);
251 	void setInstantReplacesEnabled(rpl::producer<bool> enabled);
252 	void setMarkdownReplacesEnabled(rpl::producer<bool> enabled);
253 	void setExtendedContextMenu(rpl::producer<ExtendedContextMenu> value);
254 	void commitInstantReplacement(int from, int till, const QString &with);
255 	void commitMarkdownLinkEdit(
256 		EditLinkSelection selection,
257 		const QString &text,
258 		const QString &link);
259 	static bool IsValidMarkdownLink(QStringView link);
260 
getLastText()261 	const QString &getLastText() const {
262 		return _lastTextWithTags.text;
263 	}
264 	void setPlaceholder(
265 		rpl::producer<QString> placeholder,
266 		int afterSymbols = 0);
267 	void setPlaceholderHidden(bool forcePlaceholderHidden);
268 	void setDisplayFocused(bool focused);
269 	void finishAnimating();
setFocusFast()270 	void setFocusFast() {
271 		setDisplayFocused(true);
272 		setFocus();
273 	}
274 
275 	QSize sizeHint() const override;
276 	QSize minimumSizeHint() const override;
277 
278 	bool hasText() const;
279 	void selectAll();
280 
281 	bool isUndoAvailable() const;
282 	bool isRedoAvailable() const;
283 
isMarkdownEnabled()284 	bool isMarkdownEnabled() const {
285 		return _markdownEnabled;
286 	}
287 
288 	using SubmitSettings = InputSubmitSettings;
289 	void setSubmitSettings(SubmitSettings settings);
290 	static bool ShouldSubmit(
291 		SubmitSettings settings,
292 		Qt::KeyboardModifiers modifiers);
293 	void customUpDown(bool isCustom);
294 	void customTab(bool isCustom);
295 	int borderAnimationStart() const;
296 
297 	not_null<QTextDocument*> document();
298 	not_null<const QTextDocument*> document() const;
299 	void setTextCursor(const QTextCursor &cursor);
300 	void setCursorPosition(int position);
301 	QTextCursor textCursor() const;
302 	void setText(const QString &text);
303 	void clear();
304 	bool hasFocus() const;
305 	void setFocus();
306 	void clearFocus();
307 	void ensureCursorVisible();
308 	not_null<QTextEdit*> rawTextEdit();
309 	not_null<const QTextEdit*> rawTextEdit() const;
310 
311 	enum class MimeAction {
312 		Check,
313 		Insert,
314 	};
315 	using MimeDataHook = Fn<bool(
316 		not_null<const QMimeData*> data,
317 		MimeAction action)>;
setMimeDataHook(MimeDataHook hook)318 	void setMimeDataHook(MimeDataHook hook) {
319 		_mimeDataHook = std::move(hook);
320 	}
321 
322 	const rpl::variable<int> &scrollTop() const;
323 	int scrollTopMax() const;
324 	void scrollTo(int top);
325 
326 	struct DocumentChangeInfo {
327 		int position = 0;
328 		int added = 0;
329 		int removed = 0;
330 	};
documentContentsChanges()331 	auto documentContentsChanges() {
332 		return _documentContentsChanges.events();
333 	}
markdownTagApplies()334 	auto markdownTagApplies() {
335 		return _markdownTagApplies.events();
336 	}
337 
338 	~InputField();
339 
340 private Q_SLOTS:
341 	void onTouchTimer();
342 
343 	void onDocumentContentsChange(int position, int charsRemoved, int charsAdded);
344 	void onCursorPositionChanged();
345 
346 	void onUndoAvailable(bool avail);
347 	void onRedoAvailable(bool avail);
348 
349 	void onFocusInner();
350 
351 Q_SIGNALS:
352 	void changed();
353 	void submitted(Qt::KeyboardModifiers);
354 	void cancelled();
355 	void tabbed();
356 	void focused();
357 	void blurred();
358 	void resized();
359 
360 protected:
361 	void startPlaceholderAnimation();
362 	void startBorderAnimation();
363 
364 	void paintEvent(QPaintEvent *e) override;
365 	void focusInEvent(QFocusEvent *e) override;
366 	void mousePressEvent(QMouseEvent *e) override;
367 	void contextMenuEvent(QContextMenuEvent *e) override;
368 	void resizeEvent(QResizeEvent *e) override;
369 
370 private:
371 	class Inner;
372 	friend class Inner;
373 
374 	void handleContentsChanged();
375 	bool viewportEventInner(QEvent *e);
376 	void handleTouchEvent(QTouchEvent *e);
377 
378 	void updatePalette();
379 	void refreshPlaceholder(const QString &text);
380 	int placeholderSkipWidth() const;
381 
382 	bool heightAutoupdated();
383 	void checkContentHeight();
384 	void setErrorShown(bool error);
385 
386 	void focusInEventInner(QFocusEvent *e);
387 	void focusOutEventInner(QFocusEvent *e);
388 	void setFocused(bool focused);
389 	void keyPressEventInner(QKeyEvent *e);
390 	void contextMenuEventInner(QContextMenuEvent *e, QMenu *m = nullptr);
391 	void dropEventInner(QDropEvent *e);
392 	void inputMethodEventInner(QInputMethodEvent *e);
393 
394 	QMimeData *createMimeDataFromSelectionInner() const;
395 	bool canInsertFromMimeDataInner(const QMimeData *source) const;
396 	void insertFromMimeDataInner(const QMimeData *source);
397 	TextWithTags getTextWithTagsSelected() const;
398 
399 	// "start" and "end" are in coordinates of text where emoji are replaced
400 	// by ObjectReplacementCharacter. If "end" = -1 means get text till the end.
401 	QString getTextPart(
402 		int start,
403 		int end,
404 		TagList &outTagsList,
405 		bool &outTagsChanged,
406 		std::vector<MarkdownTag> *outMarkdownTags = nullptr) const;
407 
408 	// After any characters added we must postprocess them. This includes:
409 	// 1. Replacing font family to semibold for ~ characters, if we used Open Sans 13px.
410 	// 2. Replacing font family from semibold for all non-~ characters, if we used ...
411 	// 3. Replacing emoji code sequences by ObjectReplacementCharacters with emoji pics.
412 	// 4. Interrupting tags in which the text was inserted by any char except a letter.
413 	// 5. Applying tags from "_insertedTags" in case we pasted text with tags, not just text.
414 	// Rule 4 applies only if we inserted chars not in the middle of a tag (but at the end).
415 	void processFormatting(int changedPosition, int changedEnd);
416 
417 	void chopByMaxLength(int insertPosition, int insertLength);
418 
419 	bool processMarkdownReplaces(const QString &appended);
420 	//bool processMarkdownReplace(const QString &tag);
421 	void addMarkdownActions(not_null<QMenu*> menu, QContextMenuEvent *e);
422 	void addMarkdownMenuAction(
423 		not_null<QMenu*> menu,
424 		not_null<QAction*> action);
425 	bool handleMarkdownKey(QKeyEvent *e);
426 
427 	// We don't want accidentally detach InstantReplaces map.
428 	// So we access it only by const reference from this method.
429 	const InstantReplaces &instantReplaces() const;
430 	void processInstantReplaces(const QString &appended);
431 	void applyInstantReplace(const QString &what, const QString &with);
432 
433 	struct EditLinkData {
434 		int from = 0;
435 		int till = 0;
436 		QString link;
437 	};
438 	EditLinkData selectionEditLinkData(EditLinkSelection selection) const;
439 	EditLinkSelection editLinkSelection(QContextMenuEvent *e) const;
440 	void editMarkdownLink(EditLinkSelection selection);
441 
442 	void commitInstantReplacement(
443 		int from,
444 		int till,
445 		const QString &with,
446 		std::optional<QString> checkOriginal,
447 		bool checkIfInMonospace);
448 	bool commitMarkdownReplacement(
449 		int from,
450 		int till,
451 		const QString &tag,
452 		const QString &edge = QString());
453 	void addMarkdownTag(int from, int till, const QString &tag);
454 	void removeMarkdownTag(int from, int till, const QString &tag);
455 	void finishMarkdownTagChange(
456 		int from,
457 		int till,
458 		const TextWithTags &textWithTags);
459 	void toggleSelectionMarkdown(const QString &tag);
460 	void clearSelectionMarkdown();
461 
462 	bool revertFormatReplace();
463 
464 	void highlightMarkdown();
465 
466 	const style::InputField &_st;
467 
468 	Mode _mode = Mode::SingleLine;
469 	int _maxLength = -1;
470 	int _minHeight = -1;
471 	int _maxHeight = -1;
472 
473 	const std::unique_ptr<Inner> _inner;
474 
475 	Fn<bool(
476 		EditLinkSelection selection,
477 		QString text,
478 		QString link,
479 		EditLinkAction action)> _editLinkCallback;
480 	TextWithTags _lastTextWithTags;
481 	std::vector<MarkdownTag> _lastMarkdownTags;
482 	QString _lastPreEditText;
483 	std::optional<QString> _inputMethodCommit;
484 
485 	bool _forcePlaceholderHidden = false;
486 	bool _reverseMarkdownReplacement = false;
487 
488 	// Tags list which we should apply while setText() call or insert from mime data.
489 	TagList _insertedTags;
490 	bool _insertedTagsAreFromMime;
491 
492 	// Override insert position and charsAdded from complex text editing
493 	// (like drag-n-drop in the same text edit field).
494 	int _realInsertPosition = -1;
495 	int _realCharsAdded = 0;
496 
497 	// Calculate the amount of emoji extra chars
498 	// before _documentContentsChanges fire.
499 	int _emojiSurrogateAmount = 0;
500 
501 	std::unique_ptr<TagMimeProcessor> _tagMimeProcessor;
502 
503 	SubmitSettings _submitSettings = SubmitSettings::Enter;
504 	bool _markdownEnabled = false;
505 	bool _undoAvailable = false;
506 	bool _redoAvailable = false;
507 	bool _inDrop = false;
508 	bool _inHeightCheck = false;
509 	int _additionalMargin = 0;
510 
511 	bool _customUpDown = false;
512 	bool _customTab = false;
513 
514 	rpl::variable<QString> _placeholderFull;
515 	QString _placeholder;
516 	int _placeholderAfterSymbols = 0;
517 	Animations::Simple _a_placeholderShifted;
518 	bool _placeholderShifted = false;
519 	QPainterPath _placeholderPath;
520 
521 	Animations::Simple _a_borderShown;
522 	int _borderAnimationStart = 0;
523 	Animations::Simple _a_borderOpacity;
524 	bool _borderVisible = false;
525 
526 	Animations::Simple _a_focused;
527 	Animations::Simple _a_error;
528 
529 	bool _focused = false;
530 	bool _error = false;
531 
532 	QTimer _touchTimer;
533 	bool _touchPress = false;
534 	bool _touchRightButton = false;
535 	bool _touchMove = false;
536 	QPoint _touchStart;
537 
538 	bool _correcting = false;
539 	MimeDataHook _mimeDataHook;
540 	base::unique_qptr<PopupMenu> _contextMenu;
541 
542 	QTextCharFormat _defaultCharFormat;
543 
544 	rpl::variable<int> _scrollTop;
545 
546 	InstantReplaces _mutableInstantReplaces;
547 	bool _instantReplacesEnabled = true;
548 
549 	rpl::event_stream<DocumentChangeInfo> _documentContentsChanges;
550 	rpl::event_stream<MarkdownTag> _markdownTagApplies;
551 
552 };
553 
554 class MaskedInputField : public RpWidgetBase<QLineEdit> {
555 	// The Q_OBJECT meta info is used for qobject_cast!
556 	Q_OBJECT
557 
558 	using Parent = RpWidgetBase<QLineEdit>;
559 public:
560 	MaskedInputField(
561 		QWidget *parent,
562 		const style::InputField &st,
563 		rpl::producer<QString> placeholder = nullptr,
564 		const QString &val = QString());
565 
566 	void showError();
567 	void showErrorNoFocus();
568 	void hideError();
569 
570 	QRect getTextRect() const;
571 
572 	QSize sizeHint() const override;
573 	QSize minimumSizeHint() const override;
574 
575 	void customUpDown(bool isCustom);
576 	int borderAnimationStart() const;
577 
getLastText()578 	const QString &getLastText() const {
579 		return _oldtext;
580 	}
581 	void setPlaceholder(rpl::producer<QString> placeholder);
582 	void setPlaceholderHidden(bool forcePlaceholderHidden);
583 	void setDisplayFocused(bool focused);
584 	void finishAnimating();
setFocusFast()585 	void setFocusFast() {
586 		setDisplayFocused(true);
587 		setFocus();
588 	}
589 
setText(const QString & text)590 	void setText(const QString &text) {
591 		QLineEdit::setText(text);
592 		startPlaceholderAnimation();
593 	}
clear()594 	void clear() {
595 		QLineEdit::clear();
596 		startPlaceholderAnimation();
597 	}
598 
599 public Q_SLOTS:
600 	void onTextChange(const QString &text);
601 	void onCursorPositionChanged(int oldPosition, int position);
602 
603 	void onTextEdited();
604 
605 	void onTouchTimer();
606 
607 Q_SIGNALS:
608 	void changed();
609 	void cancelled();
610 	void submitted(Qt::KeyboardModifiers);
611 	void focused();
612 	void blurred();
613 
614 protected:
getDisplayedText()615 	QString getDisplayedText() const {
616 		auto result = getLastText();
617 		if (!_lastPreEditText.isEmpty()) {
618 			result = result.mid(0, _oldcursor) + _lastPreEditText + result.mid(_oldcursor);
619 		}
620 		return result;
621 	}
622 	void startBorderAnimation();
623 	void startPlaceholderAnimation();
624 
625 	bool eventHook(QEvent *e) override;
626 	void touchEvent(QTouchEvent *e);
627 	void paintEvent(QPaintEvent *e) override;
628 	void focusInEvent(QFocusEvent *e) override;
629 	void focusOutEvent(QFocusEvent *e) override;
630 	void keyPressEvent(QKeyEvent *e) override;
631 	void resizeEvent(QResizeEvent *e) override;
632 	void contextMenuEvent(QContextMenuEvent *e) override;
633 	void inputMethodEvent(QInputMethodEvent *e) override;
634 
correctValue(const QString & was,int wasCursor,QString & now,int & nowCursor)635 	virtual void correctValue(
636 		const QString &was,
637 		int wasCursor,
638 		QString &now,
639 		int &nowCursor) {
640 	}
641 	void setCorrectedText(QString &now, int &nowCursor, const QString &newText, int newPos);
642 
paintAdditionalPlaceholder(Painter & p)643 	virtual void paintAdditionalPlaceholder(Painter &p) {
644 	}
645 
phFont()646 	style::font phFont() {
647 		return _st.font;
648 	}
649 
650 	void placeholderAdditionalPrepare(Painter &p);
651 	QRect placeholderRect() const;
652 
653 	void setTextMargins(const QMargins &mrg);
654 	const style::InputField &_st;
655 
656 private:
657 	void updatePalette();
658 	void refreshPlaceholder(const QString &text);
659 	void setErrorShown(bool error);
660 
661 	void setFocused(bool focused);
662 
663 	int _maxLength = -1;
664 	bool _forcePlaceholderHidden = false;
665 
666 	QString _oldtext;
667 	int _oldcursor = 0;
668 	QString _lastPreEditText;
669 
670 	bool _undoAvailable = false;
671 	bool _redoAvailable = false;
672 
673 	bool _customUpDown = false;
674 
675 	rpl::variable<QString> _placeholderFull;
676 	QString _placeholder;
677 	Animations::Simple _a_placeholderShifted;
678 	bool _placeholderShifted = false;
679 	QPainterPath _placeholderPath;
680 
681 	Animations::Simple _a_borderShown;
682 	int _borderAnimationStart = 0;
683 	Animations::Simple _a_borderOpacity;
684 	bool _borderVisible = false;
685 
686 	Animations::Simple _a_focused;
687 	Animations::Simple _a_error;
688 
689 	bool _focused = false;
690 	bool _error = false;
691 
692 	style::margins _textMargins;
693 
694 	QTimer _touchTimer;
695 	bool _touchPress = false;
696 	bool _touchRightButton = false;
697 	bool _touchMove = false;
698 	QPoint _touchStart;
699 };
700 
701 class PasswordInput : public MaskedInputField {
702 public:
703 	PasswordInput(QWidget *parent, const style::InputField &st, rpl::producer<QString> placeholder = nullptr, const QString &val = QString());
704 
705 };
706 
707 class NumberInput : public MaskedInputField {
708 public:
709 	NumberInput(
710 		QWidget *parent,
711 		const style::InputField &st,
712 		rpl::producer<QString> placeholder,
713 		const QString &value,
714 		int limit);
715 
716 protected:
717 	void correctValue(
718 		const QString &was,
719 		int wasCursor,
720 		QString &now,
721 		int &nowCursor) override;
722 
723 private:
724 	int _limit = 0;
725 
726 };
727 
728 class HexInput : public MaskedInputField {
729 public:
730 	HexInput(QWidget *parent, const style::InputField &st, rpl::producer<QString> placeholder, const QString &val);
731 
732 protected:
733 	void correctValue(
734 		const QString &was,
735 		int wasCursor,
736 		QString &now,
737 		int &nowCursor) override;
738 
739 };
740 
741 } // namespace Ui
742