| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658 |
- // This file is part of Desktop App Toolkit,
- // a set of libraries for developing nice desktop applications.
- //
- // For license and copyright information please follow this link:
- // https://github.com/desktop-app/legal/blob/master/LEGAL
- //
- #pragma once
- #include "base/flat_set.h"
- #include "base/timer.h"
- #include "ui/emoji_config.h"
- #include "ui/rp_widget.h"
- #include "ui/effects/animations.h"
- #include "ui/text/text_entity.h"
- #include "ui/text/text_custom_emoji.h"
- #include <rpl/variable.h>
- #include <QtGui/QTextCursor>
- #include <any>
- class QMenu;
- class QShortcut;
- class QTextEdit;
- class QTouchEvent;
- class QContextMenuEvent;
- class Painter;
- namespace Ui::Text {
- struct QuotePaintCache;
- } // namespace Ui::Text
- namespace style {
- struct InputField;
- } // namespace style
- namespace Ui {
- const auto kClearFormatSequence = QKeySequence("ctrl+shift+n");
- const auto kStrikeOutSequence = QKeySequence("ctrl+shift+x");
- const auto kBlockquoteSequence = QKeySequence("ctrl+shift+.");
- const auto kMonospaceSequence = QKeySequence("ctrl+shift+m");
- const auto kEditLinkSequence = QKeySequence("ctrl+k");
- const auto kSpoilerSequence = QKeySequence("ctrl+shift+p");
- class PopupMenu;
- class InputField;
- void InsertEmojiAtCursor(QTextCursor cursor, EmojiPtr emoji);
- void InsertCustomEmojiAtCursor(
- not_null<InputField*> field,
- QTextCursor cursor,
- const QString &text,
- const QString &link);
- struct InstantReplaces {
- struct Node {
- QString text;
- std::map<QChar, Node> tail;
- };
- void add(const QString &what, const QString &with);
- static const InstantReplaces &Default();
- static const InstantReplaces &TextOnly();
- int maxLength = 0;
- Node reverseMap;
- };
- enum class InputSubmitSettings {
- Enter,
- CtrlEnter,
- Both,
- None,
- };
- class CustomFieldObject;
- struct MarkdownEnabled {
- base::flat_set<QString> tagsSubset;
- friend inline bool operator==(
- const MarkdownEnabled &,
- const MarkdownEnabled &) = default;
- };
- struct MarkdownDisabled {
- friend inline bool operator==(
- const MarkdownDisabled &,
- const MarkdownDisabled &) = default;
- };
- struct MarkdownEnabledState {
- std::variant<MarkdownDisabled, MarkdownEnabled> data;
- [[nodiscard]] bool disabled() const;
- [[nodiscard]] bool enabledForTag(QStringView tag) const;
- friend inline bool operator==(
- const MarkdownEnabledState &,
- const MarkdownEnabledState &) = default;
- };
- struct InputFieldTextRange {
- int from = 0;
- int till = 0;
- friend inline bool operator==(
- InputFieldTextRange,
- InputFieldTextRange) = default;
- [[nodiscard]] bool empty() const {
- return (till <= from);
- }
- };
- struct InputFieldSpoilerRect {
- QRect geometry;
- bool blockquote = false;
- };
- class InputField : public RpWidget {
- public:
- enum class Mode {
- SingleLine,
- NoNewlines,
- MultiLine,
- };
- using TagList = TextWithTags::Tags;
- struct MarkdownTag {
- // With each emoji being QChar::ObjectReplacementCharacter.
- int internalStart = 0;
- int internalLength = 0;
- // Adjusted by emoji to match _lastTextWithTags.
- int adjustedStart = 0;
- int adjustedLength = 0;
- bool closed = false;
- QString tag;
- };
- static const QString kTagBold;
- static const QString kTagItalic;
- static const QString kTagUnderline;
- static const QString kTagStrikeOut;
- static const QString kTagCode;
- static const QString kTagPre;
- static const QString kTagSpoiler;
- static const QString kTagBlockquote;
- static const QString kTagBlockquoteCollapsed;
- static const QString kCustomEmojiTagStart;
- static const int kCollapsedQuoteFormat; // QTextFormat::ObjectTypes
- static const int kCustomEmojiFormat; // QTextFormat::ObjectTypes
- static const int kCustomEmojiId; // QTextFormat::Property
- static const int kCustomEmojiLink; // QTextFormat::Property
- static const int kQuoteId; // QTextFormat::Property
- InputField(
- QWidget *parent,
- const style::InputField &st,
- rpl::producer<QString> placeholder,
- const QString &value = QString());
- InputField(
- QWidget *parent,
- const style::InputField &st,
- Mode mode,
- rpl::producer<QString> placeholder,
- const QString &value);
- InputField(
- QWidget *parent,
- const style::InputField &st,
- Mode mode = Mode::SingleLine,
- rpl::producer<QString> placeholder = nullptr,
- const TextWithTags &value = TextWithTags());
- [[nodiscard]] const style::InputField &st() const {
- return _st;
- }
- void showError();
- void showErrorNoFocus();
- void hideError();
- void setMaxLength(int maxLength);
- void setMinHeight(int minHeight);
- void setMaxHeight(int maxHeight);
- [[nodiscard]] const TextWithTags &getTextWithTags() const {
- return _lastTextWithTags;
- }
- [[nodiscard]] const std::vector<MarkdownTag> &getMarkdownTags() const {
- return _lastMarkdownTags;
- }
- [[nodiscard]] TextWithTags getTextWithTagsPart(
- int start,
- int end = -1) const;
- [[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const;
- void insertTag(const QString &text, QString tagId = QString());
- [[nodiscard]] bool empty() const {
- return _lastTextWithTags.text.isEmpty();
- }
- enum class HistoryAction {
- NewEntry,
- MergeEntry,
- Clear,
- };
- void setTextWithTags(
- const TextWithTags &textWithTags,
- HistoryAction historyAction = HistoryAction::NewEntry);
- // If you need to make some preparations of tags before putting them to QMimeData
- // (and then to clipboard or to drag-n-drop object), here is a strategy for that.
- void setTagMimeProcessor(Fn<QString(QStringView)> processor);
- void setCustomTextContext(
- Text::MarkedContext context,
- Fn<bool()> pausedEmoji = nullptr,
- Fn<bool()> pausedSpoiler = nullptr);
- struct EditLinkSelection {
- int from = 0;
- int till = 0;
- };
- enum class EditLinkAction {
- Check,
- Edit,
- };
- void setEditLinkCallback(
- Fn<bool(
- EditLinkSelection selection,
- TextWithTags text,
- QString link,
- EditLinkAction action)> callback);
- void setEditLanguageCallback(
- Fn<void(QString now, Fn<void(QString)> save)> callback);
- struct ExtendedContextMenu {
- QMenu *menu = nullptr;
- std::shared_ptr<QContextMenuEvent> event;
- };
- void setDocumentMargin(float64 margin);
- void setAdditionalMargin(int margin);
- void setAdditionalMargins(QMargins margins);
- void setInstantReplaces(const InstantReplaces &replaces);
- void setInstantReplacesEnabled(rpl::producer<bool> enabled);
- void setMarkdownReplacesEnabled(bool enabled);
- void setMarkdownReplacesEnabled(rpl::producer<MarkdownEnabledState> enabled);
- void setExtendedContextMenu(rpl::producer<ExtendedContextMenu> value);
- void commitInstantReplacement(
- int from,
- int till,
- const QString &with,
- const QString &customEmojiData);
- void commitMarkdownLinkEdit(
- EditLinkSelection selection,
- const TextWithTags &textWithTags,
- const QString &link);
- [[nodiscard]] static bool IsValidMarkdownLink(QStringView link);
- [[nodiscard]] static bool IsCustomEmojiLink(QStringView link);
- [[nodiscard]] static QString CustomEmojiLink(QStringView entityData);
- [[nodiscard]] static QString CustomEmojiEntityData(QStringView link);
- [[nodiscard]] const QString &getLastText() const {
- return _lastTextWithTags.text;
- }
- void setPlaceholder(
- rpl::producer<QString> placeholder,
- int afterSymbols = 0);
- void setPlaceholderHidden(bool forcePlaceholderHidden);
- void setDisplayFocused(bool focused);
- void finishAnimating();
- void setFocusFast() {
- setDisplayFocused(true);
- setFocus();
- }
- QSize sizeHint() const override;
- QSize minimumSizeHint() const override;
- bool hasText() const;
- void selectAll();
- bool isUndoAvailable() const;
- bool isRedoAvailable() const;
- [[nodiscard]] MarkdownEnabledState markdownEnabledState() const {
- return _markdownEnabledState;
- }
- using SubmitSettings = InputSubmitSettings;
- void setSubmitSettings(SubmitSettings settings);
- static bool ShouldSubmit(
- SubmitSettings settings,
- Qt::KeyboardModifiers modifiers);
- void customUpDown(bool isCustom);
- void customTab(bool isCustom);
- int borderAnimationStart() const;
- not_null<QTextDocument*> document();
- not_null<const QTextDocument*> document() const;
- void setTextCursor(const QTextCursor &cursor);
- void setCursorPosition(int position);
- QTextCursor textCursor() const;
- void setText(const QString &text);
- void clear();
- bool hasFocus() const;
- void setFocus();
- void clearFocus();
- void ensureCursorVisible();
- not_null<QTextEdit*> rawTextEdit();
- not_null<const QTextEdit*> rawTextEdit() const;
- enum class MimeAction {
- Check,
- Insert,
- };
- using MimeDataHook = Fn<bool(
- not_null<const QMimeData*> data,
- MimeAction action)>;
- void setMimeDataHook(MimeDataHook hook) {
- _mimeDataHook = std::move(hook);
- }
- const rpl::variable<int> &scrollTop() const;
- int scrollTopMax() const;
- void scrollTo(int top);
- struct DocumentChangeInfo {
- int position = 0;
- int added = 0;
- int removed = 0;
- };
- auto documentContentsChanges() {
- return _documentContentsChanges.events();
- }
- auto markdownTagApplies() {
- return _markdownTagApplies.events();
- }
- void setPreCache(Fn<not_null<Ui::Text::QuotePaintCache*>()> make);
- void setBlockquoteCache(Fn<not_null<Ui::Text::QuotePaintCache*>()> make);
- [[nodiscard]] bool menuShown() const;
- [[nodiscard]] rpl::producer<bool> menuShownValue() const;
- [[nodiscard]] rpl::producer<> heightChanges() const;
- [[nodiscard]] rpl::producer<bool> focusedChanges() const;
- [[nodiscard]] rpl::producer<> tabbed() const;
- [[nodiscard]] rpl::producer<> cancelled() const;
- [[nodiscard]] rpl::producer<> changes() const;
- [[nodiscard]] rpl::producer<Qt::KeyboardModifiers> submits() const;
- void forceProcessContentsChanges();
- ~InputField();
- protected:
- void startPlaceholderAnimation();
- void startBorderAnimation();
- void paintEvent(QPaintEvent *e) override;
- void focusInEvent(QFocusEvent *e) override;
- void mousePressEvent(QMouseEvent *e) override;
- void contextMenuEvent(QContextMenuEvent *e) override;
- void resizeEvent(QResizeEvent *e) override;
- private:
- class Inner;
- friend class Inner;
- friend class CustomFieldObject;
- friend class FieldSpoilerOverlay;
- using TextRange = InputFieldTextRange;
- using SpoilerRect = InputFieldSpoilerRect;
- enum class MarkdownActionType {
- ToggleTag,
- EditLink,
- };
- struct MarkdownAction {
- QKeySequence sequence;
- QString tag;
- MarkdownActionType type = MarkdownActionType::ToggleTag;
- };
- void handleContentsChanged();
- void updateRootFrameFormat();
- bool viewportEventInner(QEvent *e);
- void handleTouchEvent(QTouchEvent *e);
- void updatePalette();
- void refreshPlaceholder(const QString &text);
- int placeholderSkipWidth() const;
- [[nodiscard]] static std::vector<MarkdownAction> MarkdownActions();
- void setupMarkdownShortcuts();
- bool executeMarkdownAction(MarkdownAction action);
- bool heightAutoupdated();
- void checkContentHeight();
- void setErrorShown(bool error);
- void focusInEventInner(QFocusEvent *e);
- void focusOutEventInner(QFocusEvent *e);
- void setFocused(bool focused);
- void keyPressEventInner(QKeyEvent *e);
- void contextMenuEventInner(QContextMenuEvent *e, QMenu *m = nullptr);
- void dropEventInner(QDropEvent *e);
- void inputMethodEventInner(QInputMethodEvent *e);
- void paintEventInner(QPaintEvent *e);
- void paintQuotes(QPaintEvent *e);
- void mousePressEventInner(QMouseEvent *e);
- void mouseReleaseEventInner(QMouseEvent *e);
- void mouseMoveEventInner(QMouseEvent *e);
- void leaveEventInner(QEvent *e);
- [[nodiscard]] int lookupActionQuoteId(QPoint point) const;
- void updateCursorShape();
- QMimeData *createMimeDataFromSelectionInner() const;
- bool canInsertFromMimeDataInner(const QMimeData *source) const;
- void insertFromMimeDataInner(const QMimeData *source);
- TextWithTags getTextWithTagsSelected() const;
- void documentContentsChanged(
- int position,
- int charsRemoved,
- int charsAdded);
- void focusInner();
- // "start" and "end" are in coordinates of text where emoji are replaced
- // by ObjectReplacementCharacter. If "end" = -1 means get text till the end.
- [[nodiscard]] QString getTextPart(
- int start,
- int end,
- TagList &outTagsList,
- bool &outTagsChanged,
- std::vector<MarkdownTag> *outMarkdownTags = nullptr) const;
- // After any characters added we must postprocess them. This includes:
- // 1. Replacing font family to semibold for ~ characters, if we used Open Sans 13px.
- // 2. Replacing font family from semibold for all non-~ characters, if we used ...
- // 3. Replacing emoji code sequences by ObjectReplacementCharacters with emoji pics.
- // 4. Interrupting tags in which the text was inserted by any char except a letter.
- // 5. Applying tags from "_insertedTags" in case we pasted text with tags, not just text.
- // Rule 4 applies only if we inserted chars not in the middle of a tag (but at the end).
- void processFormatting(int changedPosition, int changedEnd);
- void chopByMaxLength(int insertPosition, int insertLength);
- bool processMarkdownReplaces(const QString &appended);
- //bool processMarkdownReplace(const QString &tag);
- void addMarkdownActions(not_null<QMenu*> menu, QContextMenuEvent *e);
- void addMarkdownMenuAction(
- not_null<QMenu*> menu,
- not_null<QAction*> action);
- bool handleMarkdownKey(QKeyEvent *e);
- // We don't want accidentally detach InstantReplaces map.
- // So we access it only by const reference from this method.
- const InstantReplaces &instantReplaces() const;
- void processInstantReplaces(const QString &appended);
- void applyInstantReplace(const QString &what, const QString &with);
- struct EditLinkData {
- int from = 0;
- int till = 0;
- QString link;
- };
- EditLinkData selectionEditLinkData(EditLinkSelection selection) const;
- EditLinkSelection editLinkSelection(QContextMenuEvent *e) const;
- void editMarkdownLink(EditLinkSelection selection);
- void commitInstantReplacement(
- int from,
- int till,
- const QString &with,
- const QString &customEmojiData,
- std::optional<QString> checkOriginal,
- bool checkIfInMonospace);
- #if 0
- bool commitMarkdownReplacement(
- int from,
- int till,
- const QString &tag,
- const QString &edge = QString());
- #endif
- TextRange insertWithTags(TextRange range, TextWithTags text);
- TextRange addMarkdownTag(TextRange range, const QString &tag);
- void removeMarkdownTag(TextRange range, const QString &tag);
- void finishMarkdownTagChange(
- TextRange range,
- const TextWithTags &textWithTags);
- void toggleSelectionMarkdown(const QString &tag);
- void clearSelectionMarkdown();
- bool revertFormatReplace();
- bool jumpOutOfBlockByBackspace();
- void paintSurrounding(
- QPainter &p,
- QRect clip,
- float64 errorDegree,
- float64 focusedDegree);
- void paintRoundSurrounding(
- QPainter &p,
- QRect clip,
- float64 errorDegree,
- float64 focusedDegree);
- void paintFlatSurrounding(
- QPainter &p,
- QRect clip,
- float64 errorDegree,
- float64 focusedDegree);
- void customEmojiRepaint();
- void highlightMarkdown();
- bool exitQuoteWithNewBlock(int key);
- void blockActionClicked(int quoteId);
- void editPreLanguage(int quoteId, QStringView tag);
- void toggleBlockquoteCollapsed(
- int quoteId,
- QStringView tag,
- TextRange range);
- void trippleEnterExitBlock(QTextCursor &cursor);
- void touchUpdate(QPoint globalPosition);
- void touchFinish();
- const style::InputField &_st;
- Fn<not_null<Ui::Text::QuotePaintCache*>()> _preCache;
- Fn<not_null<Ui::Text::QuotePaintCache*>()> _blockquoteCache;
- Mode _mode = Mode::SingleLine;
- int _maxLength = -1;
- int _minHeight = -1;
- int _maxHeight = -1;
- const std::unique_ptr<Inner> _inner;
- Fn<bool(
- EditLinkSelection selection,
- TextWithTags text,
- QString link,
- EditLinkAction action)> _editLinkCallback;
- Fn<void(QString now, Fn<void(QString)> save)> _editLanguageCallback;
- TextWithTags _lastTextWithTags;
- std::vector<MarkdownTag> _lastMarkdownTags;
- QString _lastPreEditText;
- std::optional<QString> _inputMethodCommit;
- mutable std::vector<TextRange> _spoilerRangesText;
- mutable std::vector<TextRange> _spoilerRangesEmoji;
- mutable std::vector<SpoilerRect> _spoilerRects;
- mutable QColor _blockquoteBg;
- std::unique_ptr<RpWidget> _spoilerOverlay;
- QMargins _additionalMargins;
- QMargins _customFontMargins;
- int _placeholderCustomFontSkip = 0;
- int _requestedDocumentTopMargin = 0;
- bool _forcePlaceholderHidden = false;
- bool _reverseMarkdownReplacement = false;
- bool _customEmojiRepaintScheduled = false;
- bool _settingDocumentMargin = false;
- // Tags list which we should apply while setText() call or insert from mime data.
- TagList _insertedTags;
- bool _insertedTagsAreFromMime = false;
- bool _insertedTagsReplace = false;
- // Override insert position and charsAdded from complex text editing
- // (like drag-n-drop in the same text edit field).
- int _realInsertPosition = -1;
- int _realCharsAdded = 0;
- // Calculate the amount of emoji extra chars
- // before _documentContentsChanges fire.
- int _emojiSurrogateAmount = 0;
- Fn<QString(QStringView)> _tagMimeProcessor;
- std::unique_ptr<CustomFieldObject> _customObject;
- std::optional<QTextCursor> _formattingCursorUpdate;
- SubmitSettings _submitSettings = SubmitSettings::Enter;
- MarkdownEnabledState _markdownEnabledState;
- bool _undoAvailable = false;
- bool _redoAvailable = false;
- bool _insertedTagsDelayClear = false;
- bool _inHeightCheck = false;
- bool _customUpDown = false;
- bool _customTab = false;
- rpl::variable<QString> _placeholderFull;
- QString _placeholder;
- int _placeholderAfterSymbols = 0;
- Animations::Simple _a_placeholderShifted;
- bool _placeholderShifted = false;
- QPainterPath _placeholderPath;
- Animations::Simple _a_borderShown;
- int _borderAnimationStart = 0;
- Animations::Simple _a_borderOpacity;
- bool _borderVisible = false;
- Animations::Simple _a_focused;
- Animations::Simple _a_error;
- bool _focused = false;
- bool _error = false;
- base::Timer _touchTimer;
- bool _touchPress = false;
- bool _touchRightButton = false;
- bool _touchMove = false;
- bool _mousePressedInTouch = false;
- QPoint _touchStart;
- bool _correcting = false;
- MimeDataHook _mimeDataHook;
- rpl::event_stream<bool> _menuShownChanges;
- base::unique_qptr<PopupMenu> _contextMenu;
- QTextCharFormat _defaultCharFormat;
- int _selectedActionQuoteId = 0;
- int _pressedActionQuoteId = -1;
- rpl::variable<int> _scrollTop;
- InstantReplaces _mutableInstantReplaces;
- bool _instantReplacesEnabled = true;
- rpl::event_stream<DocumentChangeInfo> _documentContentsChanges;
- rpl::event_stream<MarkdownTag> _markdownTagApplies;
- std::vector<std::unique_ptr<QShortcut>> _markdownShortcuts;
- rpl::event_stream<bool> _focusedChanges;
- rpl::event_stream<> _heightChanges;
- rpl::event_stream<> _tabbed;
- rpl::event_stream<> _cancelled;
- rpl::event_stream<> _changes;
- rpl::event_stream<Qt::KeyboardModifiers> _submits;
- };
- void PrepareFormattingOptimization(not_null<QTextDocument*> document);
- [[nodiscard]] int ComputeRealUnicodeCharactersCount(const QString &text);
- [[nodiscard]] int ComputeFieldCharacterCount(not_null<InputField*> field);
- void AddLengthLimitLabel(
- not_null<InputField*> field,
- int limit,
- std::optional<uint> customThreshold = std::nullopt,
- int limitLabelTop = 0);
- } // namespace Ui
|