/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once #include "ui/widgets/tooltip.h" #include "mainwidget.h" #include "chat_helpers/field_autocomplete.h" #include "window/section_widget.h" #include "core/single_timer.h" #include "ui/widgets/input_fields.h" #include "ui/rp_widget.h" #include "base/flags.h" #include "base/timer.h" struct FileLoadResult; struct FileMediaInformation; struct SendingAlbum; enum class SendMediaType; enum class CompressConfirm; class MessageLinksParser; namespace InlineBots { namespace Layout { class ItemBase; class Widget; } // namespace Layout class Result; } // namespace InlineBots namespace Data { struct Draft; } // namespace Data namespace Support { class Autocomplete; struct Contact; } // namespace Support namespace Ui { class AbstractButton; class InnerDropdown; class DropdownMenu; class PlainShadow; class PopupMenu; class IconButton; class HistoryDownButton; class EmojiButton; class SendButton; class SilentToggle; class FlatButton; class LinkButton; class RoundButton; } // namespace Ui namespace Window { class Controller; } // namespace Window namespace ChatHelpers { class TabbedPanel; class TabbedSection; class TabbedSelector; } // namespace ChatHelpers namespace Storage { enum class MimeDataState; struct PreparedList; struct UploadedPhoto; struct UploadedDocument; struct UploadedThumbDocument; } // namespace Storage namespace HistoryView { class TopBarWidget; } // namespace HistoryView class DragArea; class SendFilesBox; class BotKeyboard; class MessageField; class HistoryInner; struct HistoryMessageMarkupButton; class ReportSpamPanel : public TWidget { Q_OBJECT public: ReportSpamPanel(QWidget *parent); void setReported(bool reported, PeerData *onPeer); signals: void hideClicked(); void reportClicked(); void clearClicked(); protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; private: object_ptr _report; object_ptr _hide; object_ptr _clear; }; class HistoryWidget final : public Window::AbstractSectionWidget, public RPCSender { Q_OBJECT public: using FieldHistoryAction = Ui::InputField::HistoryAction; HistoryWidget(QWidget *parent, not_null controller); void start(); void messagesReceived(PeerData *peer, const MTPmessages_Messages &messages, mtpRequestId requestId); void historyLoaded(); void windowShown(); bool doWeReadServerHistory() const; bool doWeReadMentions() const; bool skipItemRepaint(); void leaveToChildEvent(QEvent *e, QWidget *child) override; void dragEnterEvent(QDragEnterEvent *e) override; void dragLeaveEvent(QDragLeaveEvent *e) override; void dropEvent(QDropEvent *e) override; bool isItemCompletelyHidden(HistoryItem *item) const; void updateTopBarSelection(); void loadMessages(); void loadMessagesDown(); void firstLoadMessages(); void delayedShowAt(MsgId showAtMsgId); void newUnreadMsg( not_null history, not_null item); void historyToDown(History *history); QRect historyRect() const; void pushTabbedSelectorToThirdSection( const Window::SectionShow ¶ms); void updateRecentStickers(); void destroyData(); void updateFieldPlaceholder(); void updateStickersByEmoji(); bool confirmSendingFiles(const QStringList &files); bool confirmSendingFiles(not_null data); void sendFileConfirmed(const std::shared_ptr &file); void updateControlsVisibility(); void updateControlsGeometry(); History *history() const; PeerData *peer() const; void setMsgId(MsgId showAtMsgId); MsgId msgId() const; bool hasTopBarShadow() const { return peer() != nullptr; } void showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms); void finishAnimating(); void doneShow(); QPoint clampMousePosition(QPoint point); void checkSelectingScroll(QPoint point); void noSelectingScroll(); bool touchScroll(const QPoint &delta); void enqueueMessageHighlight(not_null view); TimeMs highlightStartTime(not_null item) const; bool inSelectionMode() const; MessageIdsList getSelectedItems() const; void itemEdited(HistoryItem *item); void updateScrollColors(); void replyToMessage(FullMsgId itemId); void replyToMessage(not_null item); void editMessage(FullMsgId itemId); void editMessage(not_null item); void pinMessage(FullMsgId itemId); void unpinMessage(FullMsgId itemId); void copyPostLink(FullMsgId itemId); MsgId replyToId() const; void messageDataReceived(ChannelData *channel, MsgId msgId); bool lastForceReplyReplied(const FullMsgId &replyTo) const; bool lastForceReplyReplied() const; bool cancelReply(bool lastKeyboardUsed = false); void cancelEdit(); void updateForwarding(); void updateForwardingTexts(); void clearReplyReturns(); void pushReplyReturn(not_null item); QList replyReturns(); void setReplyReturns(PeerId peer, const QList &replyReturns); void calcNextReplyReturn(); void updatePreview(); void previewCancel(); void step_recording(float64 ms, bool timer); void stopRecording(bool send); void onListEscapePressed(); void sendBotCommand(PeerData *peer, UserData *bot, const QString &cmd, MsgId replyTo); void hideSingleUseKeyboard(PeerData *peer, MsgId replyTo); bool insertBotCommand(const QString &cmd); bool eventFilter(QObject *obj, QEvent *e) override; // With force=true the markup is updated even if it is // already shown for the passed history item. void updateBotKeyboard(History *h = nullptr, bool force = false); void fastShowAtEnd(not_null history); void applyDraft( FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false); void clearDelayedShowAt(); void clearAllLoadRequests(); void saveFieldToHistoryLocalDraft(); void applyCloudDraft(History *history); void updateHistoryDownPosition(); void updateHistoryDownVisibility(); void updateUnreadMentionsPosition(); void updateUnreadMentionsVisibility(); void updateFieldSubmitSettings(); void setInnerFocus(); void updateNotifyControls(); bool contentOverlapped(const QRect &globalRect); QPixmap grabForShowAnimation(const Window::SectionSlideParams ¶ms); void forwardSelected(); void confirmDeleteSelected(); void clearSelected(); bool sendExistingDocument( not_null document, TextWithEntities caption = TextWithEntities()); bool sendExistingPhoto( not_null photo, TextWithEntities caption = TextWithEntities()); // Float player interface. bool wheelEventFromFloatPlayer(QEvent *e) override; QRect rectForFloatPlayer() const override; void app_sendBotCallback( not_null button, not_null msg, int row, int column); PeerData *ui_getPeerForMouseAction(); void notify_botCommandsChanged(UserData *user); void notify_inlineBotRequesting(bool requesting); void notify_replyMarkupUpdated(const HistoryItem *item); void notify_inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop); bool notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo); void notify_userIsBotChanged(UserData *user); void notify_migrateUpdated(PeerData *peer); ~HistoryWidget(); protected: void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void paintEvent(QPaintEvent *e) override; void leaveEventHook(QEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; signals: void cancelled(); public slots: void onCancel(); void onPinnedHide(); void onFieldBarCancel(); void onReportSpamClicked(); void onReportSpamHide(); void onReportSpamClear(); void onScroll(); void onUnblock(); void onBotStart(); void onJoinChannel(); void onMuteUnmute(); void onBroadcastSilentChange(); void onKbToggle(bool manual = true); void onCmdStart(); void activate(); void onTextChange(); void onFieldTabbed(); void onWindowVisibleChanged(); void onFieldFocused(); void onFieldResize(); void onCheckFieldAutocomplete(); void onScrollTimer(); void onDraftSaveDelayed(); void onDraftSave(bool delayed = false); void onCloudDraftSave(); void onRecordError(); void onRecordDone(QByteArray result, VoiceWaveform waveform, qint32 samples); void onRecordUpdate(quint16 level, qint32 samples); void onUpdateHistoryItems(); // checks if we are too close to the top or to the bottom // in the scroll area and preloads history if needed void preloadHistoryIfNeeded(); private slots: void onHashtagOrBotCommandInsert(QString str, FieldAutocomplete::ChooseMethod method); void onMentionInsert(UserData *user); void onInlineBotCancel(); void onMembersDropdownShow(); void onModerateKeyActivate(int index, bool *outHandled); void updateField(); private: using TabbedPanel = ChatHelpers::TabbedPanel; using TabbedSelector = ChatHelpers::TabbedSelector; using DragState = Storage::MimeDataState; void initTabbedSelector(); void send(Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers()); void handlePendingHistoryUpdate(); void fullPeerUpdated(PeerData *peer); void toggleTabbedSelectorMode(); void returnTabbedSelector(object_ptr selector); void recountChatWidth(); void setReportSpamStatus(DBIPeerReportSpamStatus status); void historyDownClicked(); void showNextUnreadMention(); void handlePeerUpdate(); void setMembersShowAreaActive(bool active); void handleHistoryChange(not_null history); void refreshAboutProxyPromotion(); void unreadCountUpdated(); void supportInitAutocomplete(); void supportInsertText(const QString &text); void supportShareContact(Support::Contact contact); void highlightMessage(MsgId universalMessageId); void adjustHighlightedMessageToMigrated(); void checkNextHighlight(); void updateHighlightedMessage(); void clearHighlightMessages(); void stopMessageHighlight(); void updateSendAction( not_null history, SendAction::Type type, int32 progress = 0); void cancelSendAction( not_null history, SendAction::Type type); void cancelTypingAction(); void sendActionDone(const MTPBool &result, mtpRequestId req); void animationCallback(); void updateOverStates(QPoint pos); void recordStartCallback(); void recordStopCallback(bool active); void recordUpdateCallback(QPoint globalPos); void chooseAttach(); void historyDownAnimationFinish(); void unreadMentionsAnimationFinish(); void sendButtonClicked(); bool canSendFiles(not_null data) const; bool confirmSendingFiles( const QStringList &files, CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( QImage &&image, QByteArray &&content, CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( not_null data, CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool confirmSendingFiles( Storage::PreparedList &&list, CompressConfirm compressed, const QString &insertTextOnCancel = QString()); bool showSendingFilesError(const Storage::PreparedList &list) const; void uploadFiles(Storage::PreparedList &&list, SendMediaType type); void uploadFile(const QByteArray &fileContent, SendMediaType type); void uploadFilesAfterConfirmation( Storage::PreparedList &&list, SendMediaType type, TextWithTags &&caption, MsgId replyTo, std::shared_ptr album = nullptr); void subscribeToUploader(); void photoUploaded( const FullMsgId &msgId, bool silent, const MTPInputFile &file); void photoProgress(const FullMsgId &msgId); void photoFailed(const FullMsgId &msgId); void documentUploaded( const FullMsgId &msgId, bool silent, const MTPInputFile &file); void thumbDocumentUploaded( const FullMsgId &msgId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb); void documentProgress(const FullMsgId &msgId); void documentFailed(const FullMsgId &msgId); void itemRemoved(not_null item); // Updates position of controls around the message field, // like send button, emoji button and others. void moveFieldControls(); void updateFieldSize(); void updateTabbedSelectorToggleTooltipGeometry(); void checkTabbedSelectorToggleTooltip(); bool canWriteMessage() const; bool isRestrictedWrite() const; void orderWidgets(); void clearInlineBot(); void inlineBotChanged(); // Look in the _field for the inline bot and query string. void updateInlineBotQuery(); // Request to show results in the emoji panel. void applyInlineBotQuery(UserData *bot, const QString &query); void cancelReplyAfterMediaSend(bool lastKeyboardUsed); void replyToPreviousMessage(); void replyToNextMessage(); void hideSelectorControlsAnimated(); int countMembersDropdownHeightMax() const; void updateReplyToName(); void checkForwardingInfo(); bool editingMessage() const { return _editMsgId != 0; } bool jumpToDialogRow(const Dialogs::RowDescriptor &to); void setupShortcuts(); bool showNextChat(); bool showPreviousChat(); MsgId _replyToId = 0; Text _replyToName; int _replyToNameVersion = 0; HistoryItemsList _toForward; Text _toForwardFrom, _toForwardText; int _toForwardNameVersion = 0; MsgId _editMsgId = 0; HistoryItem *_replyEditMsg = nullptr; Text _replyEditMsgText; mutable SingleTimer _updateEditTimeLeftDisplay; object_ptr _fieldBarCancel; void updateReplyEditTexts(bool force = false); void updateReplyEditText(not_null item); struct PinnedBar { PinnedBar(MsgId msgId, HistoryWidget *parent); ~PinnedBar(); MsgId msgId = 0; HistoryItem *msg = nullptr; Text text; object_ptr cancel; object_ptr shadow; }; std::unique_ptr _pinnedBar; void updatePinnedBar(bool force = false); bool pinnedMsgVisibilityUpdated(); void destroyPinnedBar(); void unpinDone(const MTPUpdates &updates); void sendInlineResult( not_null result, not_null bot); void drawField(Painter &p, const QRect &rect); void paintEditHeader(Painter &p, const QRect &rect, int left, int top) const; void drawRecording(Painter &p, float64 recordActive); void drawPinnedBar(Painter &p); void drawRestrictedWrite(Painter &p); bool paintShowAnimationFrame(TimeMs ms); void updateMouseTracking(); // destroys _history and _migrated unread bars void destroyUnreadBar(); mtpRequestId _saveEditMsgRequestId = 0; void saveEditMsg(); void saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req); bool saveEditMsgFail(History *history, const RPCError &error, mtpRequestId req); void updateReportSpamStatus(); void requestReportSpamSetting(); void reportSpamSettingDone(const MTPPeerSettings &result, mtpRequestId req); bool reportSpamSettingFail(const RPCError &error, mtpRequestId req); void checkPreview(); void requestPreview(); void gotPreview(QString links, const MTPMessageMedia &media, mtpRequestId req); static const mtpRequestId ReportSpamRequestNeeded = -1; DBIPeerReportSpamStatus _reportSpamStatus = dbiprsUnknown; mtpRequestId _reportSpamSettingRequestId = ReportSpamRequestNeeded; QStringList _parsedLinks; QString _previewLinks; WebPageData *_previewData = nullptr; typedef QMap PreviewCache; PreviewCache _previewCache; mtpRequestId _previewRequest = 0; Text _previewTitle; Text _previewDescription; base::Timer _previewTimer; bool _previewCancelled = false; bool _replyForwardPressed = false; HistoryItem *_replyReturn = nullptr; QList _replyReturns; bool messagesFailed(const RPCError &error, mtpRequestId requestId); void addMessagesToFront(PeerData *peer, const QVector &messages); void addMessagesToBack(PeerData *peer, const QVector &messages); struct BotCallbackInfo { UserData *bot; FullMsgId msgId; int row, col; bool game; }; void botCallbackDone(BotCallbackInfo info, const MTPmessages_BotCallbackAnswer &answer, mtpRequestId req); bool botCallbackFail(BotCallbackInfo info, const RPCError &error, mtpRequestId req); enum ScrollChangeType { ScrollChangeNone, // When we toggle a pinned message. ScrollChangeAdd, // When loading a history part while scrolling down. ScrollChangeNoJumpToBottom, }; struct ScrollChange { ScrollChangeType type; int value; }; void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 }); void updateListSize(); // Does any of the shown histories has this flag set. bool hasPendingResizedItems() const; // Counts scrollTop for placing the scroll right at the unread // messages bar, choosing from _history and _migrated unreadBar. std::optional unreadBarTop() const; int itemTopForHighlight(not_null view) const; void scrollToCurrentVoiceMessage(FullMsgId fromId, FullMsgId toId); HistoryView::Element *firstUnreadMessage() const; // Scroll to current y without updating the _lastUserScrolled time. // Used to distinguish between user scrolls and syntetic scrolls. // This one is syntetic. void synteticScrollToY(int y); void reportSpamDone(PeerData *peer, const MTPBool &result, mtpRequestId request); bool reportSpamFail(const RPCError &error, mtpRequestId request); void countHistoryShowFrom(); enum class TextUpdateEvent { SaveDraft = (1 << 0), SendTyping = (1 << 1), }; using TextUpdateEvents = base::flags; friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; }; void writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft); void writeDrafts(History *history); void setFieldText( const TextWithTags &textWithTags, TextUpdateEvents events = 0, FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); void clearFieldText( TextUpdateEvents events = 0, FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); HistoryItem *getItemFromHistoryOrMigrated(MsgId genericMsgId) const; void animatedScrollToItem(MsgId msgId); void animatedScrollToY(int scrollTo, HistoryItem *attachTo = nullptr); void updateDragAreas(); // when scroll position or scroll area size changed this method // updates the boundings of the visible area in HistoryInner void visibleAreaUpdated(); int countInitialScrollTop(); int countAutomaticScrollTop(); void preloadHistoryByScroll(); void checkReplyReturns(); void scrollToAnimationCallback(FullMsgId attachToId); bool readyToForward() const; bool hasSilentToggle() const; void handleSupportSwitch(not_null updated); PeerData *_peer = nullptr; ChannelId _channel = NoChannel; bool _canSendMessages = false; MsgId _showAtMsgId = ShowAtUnreadMsgId; mtpRequestId _firstLoadRequest = 0; mtpRequestId _preloadRequest = 0; mtpRequestId _preloadDownRequest = 0; MsgId _delayedShowAtMsgId = -1; mtpRequestId _delayedShowAtRequest = 0; object_ptr _topBar; object_ptr _scroll; QPointer _list; History *_migrated = nullptr; History *_history = nullptr; // Initial updateHistoryGeometry() was called. bool _historyInited = false; // If updateListSize() was called without updateHistoryGeometry(). bool _updateHistoryGeometryRequired = false; int _addToScroll = 0; int _lastScrollTop = 0; // gifs optimization TimeMs _lastScrolled = 0; QTimer _updateHistoryItems; TimeMs _lastUserScrolled = 0; bool _synteticScrollEvent = false; Animation _scrollToAnimation; Animation _historyDownShown; bool _historyDownIsShown = false; object_ptr _historyDown; Animation _unreadMentionsShown; bool _unreadMentionsIsShown = false; object_ptr _unreadMentions; object_ptr _fieldAutocomplete; object_ptr _supportAutocomplete; std::unique_ptr _fieldLinksParser; UserData *_inlineBot = nullptr; QString _inlineBotUsername; bool _inlineLookingUpBot = false; mtpRequestId _inlineBotResolveRequestId = 0; bool _isInlineBot = false; void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result); bool inlineBotResolveFail(QString name, const RPCError &error); bool isBotStart() const; bool isBlocked() const; bool isJoinChannel() const; bool isMuteUnmute() const; bool updateCmdStartShown(); void updateSendButtonType(); bool showRecordButton() const; bool showInlineBotCancel() const; void refreshSilentToggle(); object_ptr _reportSpamPanel = { nullptr }; object_ptr _send; object_ptr _unblock; object_ptr _botStart; object_ptr _joinChannel; object_ptr _muteUnmute; object_ptr _aboutProxyPromotion = { nullptr }; mtpRequestId _reportSpamRequest = 0; object_ptr _attachToggle; object_ptr _tabbedSelectorToggle; object_ptr _tabbedSelectorToggleTooltip = { nullptr }; bool _tabbedSelectorToggleTooltipShown = false; object_ptr _botKeyboardShow; object_ptr _botKeyboardHide; object_ptr _botCommandStart; object_ptr _silent = { nullptr }; bool _cmdStartShown = false; object_ptr _field; bool _recording = false; bool _inField = false; bool _inReplyEditForward = false; bool _inPinnedMsg = false; bool _inClickable = false; int _recordingSamples = 0; int _recordCancelWidth; rpl::lifetime _uploaderSubscriptions; // This can animate for a very long time (like in music playing), // so it should be a BasicAnimation, not an Animation. BasicAnimation _a_recording; anim::value a_recordingLevel; bool kbWasHidden() const; bool _kbShown = false; HistoryItem *_kbReplyTo = nullptr; object_ptr _kbScroll; QPointer _keyboard; object_ptr _membersDropdown = { nullptr }; QTimer _membersDropdownShowTimer; object_ptr _inlineResults = { nullptr }; object_ptr _tabbedPanel; QPointer _tabbedSelector; DragState _attachDragState; object_ptr _attachDragDocument, _attachDragPhoto; Fn _raiseEmojiSuggestions; bool _nonEmptySelection = false; TextUpdateEvents _textUpdateEvents = (TextUpdateEvents() | TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping); QString _confirmSource; Animation _a_show; Window::SlideDirection _showDirection; QPixmap _cacheUnder, _cacheOver; QTimer _scrollTimer; int32 _scrollDelta = 0; MsgId _highlightedMessageId = 0; std::deque _highlightQueue; base::Timer _highlightTimer; TimeMs _highlightStart = 0; QMap, SendAction::Type>, mtpRequestId> _sendActionRequests; base::Timer _sendActionStopTimer; TimeMs _saveDraftStart = 0; bool _saveDraftText = false; QTimer _saveDraftTimer, _saveCloudDraftTimer; object_ptr _topShadow; bool _inGrab = false; };