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