/* 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 "base/timer.h" #include "ui/rp_widget.h" #include "ui/effects/animations.h" #include "ui/dragging_scroll_manager.h" #include "ui/widgets/tooltip.h" #include "mtproto/sender.h" #include "data/data_messages.h" #include "history/view/history_view_element.h" #include "history/history_view_highlight_manager.h" #include "history/history_view_top_toast.h" struct ClickHandlerContext; namespace Main { class Session; } // namespace Main namespace Ui { class PopupMenu; class ChatTheme; struct ChatPaintContext; enum class TouchScrollState; struct PeerUserpicView; } // namespace Ui namespace Window { class SessionController; } // namespace Window namespace Data { struct Group; struct Reaction; struct AllowedReactions; } // namespace Data namespace HistoryView::Reactions { class Manager; struct ChosenReaction; struct ButtonParameters; } // namespace HistoryView::Reactions namespace Window { struct SectionShow; } // namespace Window namespace HistoryView { struct TextState; struct StateRequest; class EmojiInteractions; class TranslateTracker; enum class CursorState : char; enum class PointState : char; enum class Context : char; enum class CopyRestrictionType : char { None, Group, Channel, }; struct SelectedItem { explicit SelectedItem(FullMsgId msgId) : msgId(msgId) { } FullMsgId msgId; bool canDelete = false; bool canForward = false; bool canSendNow = false; }; struct MessagesBar { Element *element = nullptr; bool hidden = false; bool focus = false; }; struct MessagesBarData { MessagesBar bar; rpl::producer text; }; using SelectedItems = std::vector; class ListDelegate { public: virtual Context listContext() = 0; virtual bool listScrollTo(int top, bool syntetic = true) = 0; virtual void listCancelRequest() = 0; virtual void listDeleteRequest() = 0; virtual void listTryProcessKeyInput(not_null e) = 0; virtual rpl::producer listSource( Data::MessagePosition aroundId, int limitBefore, int limitAfter) = 0; virtual bool listAllowsMultiSelect() = 0; virtual bool listIsItemGoodForSelection(not_null item) = 0; virtual bool listIsLessInOrder( not_null first, not_null second) = 0; virtual void listSelectionChanged(SelectedItems &&items) = 0; virtual void listMarkReadTill(not_null item) = 0; virtual void listMarkContentsRead( const base::flat_set> &items) = 0; virtual MessagesBarData listMessagesBar( const std::vector> &elements) = 0; virtual void listContentRefreshed() = 0; virtual void listUpdateDateLink( ClickHandlerPtr &link, not_null view) = 0; virtual bool listElementHideReply(not_null view) = 0; virtual bool listElementShownUnread(not_null view) = 0; virtual bool listIsGoodForAroundPosition( not_null view) = 0; virtual void listSendBotCommand( const QString &command, const FullMsgId &context) = 0; virtual void listHandleViaClick(not_null bot) = 0; virtual not_null listChatTheme() = 0; virtual CopyRestrictionType listCopyRestrictionType( HistoryItem *item) = 0; CopyRestrictionType listCopyRestrictionType() { return listCopyRestrictionType(nullptr); } virtual CopyRestrictionType listCopyMediaRestrictionType( not_null item) = 0; virtual CopyRestrictionType listSelectRestrictionType() = 0; virtual auto listAllowedReactionsValue() -> rpl::producer = 0; virtual void listShowPremiumToast(not_null document) = 0; virtual void listOpenPhoto( not_null photo, FullMsgId context) = 0; virtual void listOpenDocument( not_null document, FullMsgId context, bool showInMediaView) = 0; virtual void listPaintEmpty( Painter &p, const Ui::ChatPaintContext &context) = 0; virtual QString listElementAuthorRank(not_null view) = 0; virtual History *listTranslateHistory() = 0; virtual void listAddTranslatedItems( not_null tracker) = 0; }; struct SelectionData { bool canDelete = false; bool canForward = false; bool canSendNow = false; }; using SelectedMap = base::flat_map< FullMsgId, SelectionData, std::less<>>; class ListMemento { public: struct ScrollTopState { Data::MessagePosition item; int shift = 0; }; explicit ListMemento( Data::MessagePosition position = Data::UnreadMessagePosition) : _aroundPosition(position) { } void setAroundPosition(Data::MessagePosition position) { _aroundPosition = position; } Data::MessagePosition aroundPosition() const { return _aroundPosition; } void setIdsLimit(int limit) { _idsLimit = limit; } int idsLimit() const { return _idsLimit; } void setScrollTopState(ScrollTopState state) { _scrollTopState = state; } ScrollTopState scrollTopState() const { return _scrollTopState; } private: Data::MessagePosition _aroundPosition; ScrollTopState _scrollTopState; int _idsLimit = 0; }; class ListWidget final : public Ui::RpWidget , public ElementDelegate , public Ui::AbstractTooltipShower { public: ListWidget( QWidget *parent, not_null controller, not_null delegate); static const crl::time kItemRevealDuration; [[nodiscard]] Main::Session &session() const; [[nodiscard]] not_null controller() const; [[nodiscard]] not_null delegate() const; // Set the correct scroll position after being resized. void restoreScrollPosition(); void resizeToWidth(int newWidth, int minHeight); void saveState(not_null memento); void restoreState(not_null memento); std::optional scrollTopForPosition( Data::MessagePosition position) const; Element *viewByPosition(Data::MessagePosition position) const; std::optional scrollTopForView(not_null view) const; [[nodiscard]] bool animatedScrolling() const; bool isAbovePosition(Data::MessagePosition position) const; bool isBelowPosition(Data::MessagePosition position) const; void highlightMessage( FullMsgId itemId, const TextWithEntities &highlightPart); void showAtPosition( Data::MessagePosition position, const Window::SectionShow ¶ms, Fn done = nullptr); void refreshViewer(); [[nodiscard]] TextForMimeData getSelectedText() const; [[nodiscard]] MessageIdsList getSelectedIds() const; [[nodiscard]] SelectedItems getSelectedItems() const; void cancelSelection(); void selectItem(not_null item); void selectItemAsGroup(not_null item); void touchScrollUpdated(const QPoint &screenPos); [[nodiscard]] bool loadedAtTopKnown() const; [[nodiscard]] bool loadedAtTop() const; [[nodiscard]] bool loadedAtBottomKnown() const; [[nodiscard]] bool loadedAtBottom() const; [[nodiscard]] bool isEmpty() const; [[nodiscard]] bool markingContentsRead() const; [[nodiscard]] bool markingMessagesRead() const; void showFinished(); void checkActivation(); [[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const; [[nodiscard]] bool hasCopyMediaRestriction( not_null item) const; [[nodiscard]] bool showCopyRestriction(HistoryItem *item = nullptr); [[nodiscard]] bool showCopyMediaRestriction(not_null item); [[nodiscard]] bool hasCopyRestrictionForSelected() const; [[nodiscard]] bool showCopyRestrictionForSelected(); [[nodiscard]] bool hasSelectRestriction() const; [[nodiscard]] std::pair findViewForPinnedTracking( int top) const; [[nodiscard]] ClickHandlerContext prepareClickHandlerContext( FullMsgId id); // AbstractTooltipShower interface QString tooltipText() const override; QPoint tooltipPos() const override; bool tooltipWindowActive() const override; [[nodiscard]] rpl::producer editMessageRequested() const; void editMessageRequestNotify(FullMsgId item) const; [[nodiscard]] bool lastMessageEditRequestNotify() const; [[nodiscard]] rpl::producer replyToMessageRequested() const; void replyToMessageRequestNotify(FullReplyTo id); [[nodiscard]] rpl::producer readMessageRequested() const; [[nodiscard]] rpl::producer showMessageRequested() const; void replyNextMessage(FullMsgId fullId, bool next = true); [[nodiscard]] Reactions::ButtonParameters reactionButtonParameters( not_null view, QPoint position, const TextState &reactionState) const; void toggleFavoriteReaction(not_null view) const; // ElementDelegate interface. Context elementContext() override; bool elementUnderCursor(not_null view) override; bool elementInSelectionMode() override; bool elementIntersectsRange( not_null view, int from, int till) override; void elementStartStickerLoop(not_null view) override; void elementShowPollResults( not_null poll, FullMsgId context) override; void elementOpenPhoto( not_null photo, FullMsgId context) override; void elementOpenDocument( not_null document, FullMsgId context, bool showInMediaView = false) override; void elementCancelUpload(const FullMsgId &context) override; void elementShowTooltip( const TextWithEntities &text, Fn hiddenCallback) override; bool elementAnimationsPaused() override; bool elementHideReply(not_null view) override; bool elementShownUnread(not_null view) override; void elementSendBotCommand( const QString &command, const FullMsgId &context) override; void elementHandleViaClick(not_null bot) override; bool elementIsChatWide() override; not_null elementPathShiftGradient() override; void elementReplyTo(const FullReplyTo &to) override; void elementStartInteraction(not_null view) override; void elementStartPremium( not_null view, Element *replacing) override; void elementCancelPremium(not_null view) override; QString elementAuthorRank(not_null view) override; void setEmptyInfoWidget(base::unique_qptr &&w); ~ListWidget(); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; bool eventHook(QEvent *e) override; // calls touchEvent when necessary void touchEvent(QTouchEvent *e); void paintEvent(QPaintEvent *e) override; void keyPressEvent(QKeyEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *e) override; void enterEventHook(QEnterEvent *e) override; void leaveEventHook(QEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; // Resize content and count natural widget height for the desired width. int resizeGetHeight(int newWidth) override; private: using ScrollTopState = ListMemento::ScrollTopState; using PointState = HistoryView::PointState; using CursorState = HistoryView::CursorState; using ChosenReaction = HistoryView::Reactions::ChosenReaction; using ViewsMap = base::flat_map< not_null, std::unique_ptr>; struct MouseState { MouseState(); MouseState( FullMsgId itemId, int height, QPoint point, PointState pointState); FullMsgId itemId; int height = 0; QPoint point; PointState pointState; inline bool operator==(const MouseState &other) const { return (itemId == other.itemId) && (point == other.point); } inline bool operator!=(const MouseState &other) const { return !(*this == other); } }; struct ItemRevealAnimation { Ui::Animations::Simple animation; int startHeight = 0; }; enum class Direction { Up, Down, }; enum class MouseAction { None, PrepareDrag, Dragging, PrepareSelect, Selecting, }; enum class SelectAction { Select, Deselect, Invert, }; enum class EnumItemsDirection { TopToBottom, BottomToTop, }; enum class DragSelectAction { None, Selecting, Deselecting, }; void onTouchSelect(); void onTouchScrollTimer(); void updateAroundPositionFromNearest(int nearestIndex); void refreshRows(const Data::MessagesSlice &old); ScrollTopState countScrollState() const; void saveScrollState(); void restoreScrollState(); [[nodiscard]] bool jumpToBottomInsteadOfUnread() const; void showAroundPosition( Data::MessagePosition position, Fn overrideInitialScroll); bool showAtPositionNow( Data::MessagePosition position, const Window::SectionShow ¶ms, Fn done); Ui::ChatPaintContext preparePaintContext(const QRect &clip) const; Element *viewForItem(FullMsgId itemId) const; Element *viewForItem(const HistoryItem *item) const; not_null enforceViewForItem( not_null item, ViewsMap &old); void mouseActionStart( const QPoint &globalPosition, Qt::MouseButton button); void mouseActionUpdate(const QPoint &globalPosition); void mouseActionUpdate(); void mouseActionFinish( const QPoint &globalPosition, Qt::MouseButton button); void mouseActionCancel(); std::unique_ptr prepareDrag(); void performDrag(); style::cursor computeMouseCursor() const; int itemTop(not_null view) const; void repaintItem(FullMsgId itemId); void repaintItem(const Element *view); void resizeItem(not_null view); void refreshItem(not_null view); void itemRemoved(not_null item); QPoint mapPointToItem(QPoint point, const Element *view) const; void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); void reactionChosen(ChosenReaction reaction); void touchResetSpeed(); void touchUpdateSpeed(); void touchDeaccelerate(int32 elapsed); [[nodiscard]] int findItemIndexByY(int y) const; [[nodiscard]] not_null findItemByY(int y) const; [[nodiscard]] Element *strictFindItemByY(int y) const; [[nodiscard]] int findNearestItem(Data::MessagePosition position) const; void viewReplaced(not_null was, Element *now); [[nodiscard]] HistoryItemsList collectVisibleItems() const; void checkMoveToOtherViewer(); void updateVisibleTopItem(); void updateItemsGeometry(); void updateSize(); void refreshAttachmentsFromTill(int from, int till); void refreshAttachmentsAtIndex(int index); void toggleScrollDateShown(); void repaintScrollDateCallback(); bool displayScrollDate() const; void scrollDateHide(); void scrollDateCheck(); void scrollDateHideByTimer(); void keepScrollDateForNow(); void computeScrollTo( int to, Data::MessagePosition position, anim::type animated); enum class AnimatedScroll { Full, Part, None, }; void scrollTo( int scrollTop, Data::MessagePosition attachPosition, int delta, AnimatedScroll type); void trySwitchToWordSelection(); void switchToWordSelection(); void validateTrippleClickStartTime(); SelectedItems collectSelectedItems() const; MessageIdsList collectSelectedIds() const; void pushSelectedItems(); void removeItemSelection( const SelectedMap::const_iterator &i); bool hasSelectedText() const; bool hasSelectedItems() const; bool inSelectionMode() const; bool overSelectedItems() const; void clearTextSelection(); void clearSelected(); void setTextSelection( not_null view, TextSelection selection); int itemMinimalHeight() const; bool isGoodForSelection( SelectedMap &applyTo, not_null item, int &totalCount) const; bool addToSelection( SelectedMap &applyTo, not_null item) const; bool removeFromSelection( SelectedMap &applyTo, FullMsgId itemId) const; void changeSelection( SelectedMap &applyTo, not_null item, SelectAction action) const; bool isSelectedGroup( const SelectedMap &applyTo, not_null group) const; bool isSelectedAsGroup( const SelectedMap &applyTo, not_null item) const; void changeSelectionAsGroup( SelectedMap &applyTo, not_null item, SelectAction action) const; SelectedMap::iterator itemUnderPressSelection(); SelectedMap::const_iterator itemUnderPressSelection() const; bool isItemUnderPressSelected() const; bool isInsideSelection( not_null view, not_null exactItem, const MouseState &state) const; bool requiredToStartDragging(not_null view) const; bool isPressInSelectedText(TextState state) const; void updateDragSelection(); void updateDragSelection( const Element *fromView, const MouseState &fromState, const Element *tillView, const MouseState &tillState); void updateDragSelection( std::vector>::const_iterator from, std::vector>::const_iterator till); void ensureDragSelectAction( std::vector>::const_iterator from, std::vector>::const_iterator till); void clearDragSelection(); void applyDragSelection(); void applyDragSelection(SelectedMap &applyTo) const; TextSelection itemRenderSelection( not_null view) const; TextSelection computeRenderSelection( not_null selected, not_null view) const; void checkUnreadBarCreation(); void applyUpdatedScrollState(); void scrollToAnimationCallback(FullMsgId attachToId, int relativeTo); void startItemRevealAnimations(); void revealItemsCallback(); void maybeMarkReactionsRead(not_null item); void startMessageSendingAnimation(not_null item); void showPremiumStickerTooltip( not_null view); // This function finds all history items that are displayed and calls template method // for each found message (in given direction) in the passed history with passed top offset. // // Method has "bool (*Method)(not_null view, int itemtop, int itembottom)" signature // if it returns false the enumeration stops immediately. template void enumerateItems(Method method); // This function finds all userpics on the left that are displayed and calls template method // for each found userpic (from the top to the bottom) using enumerateItems() method. // // Method has "bool (*Method)(not_null view, int userpicTop)" signature // if it returns false the enumeration stops immediately. template void enumerateUserpics(Method method); // This function finds all date elements that are displayed and calls template method // for each found date element (from the bottom to the top) using enumerateItems() method. // // Method has "bool (*Method)(not_null item, int itemtop, int dateTop)" signature // if it returns false the enumeration stops immediately. template void enumerateDates(Method method); void setGeometryCrashAnnotations(not_null view); static constexpr auto kMinimalIdsLimit = 24; const not_null _delegate; const not_null _controller; const std::unique_ptr _emojiInteractions; Data::MessagePosition _aroundPosition; Data::MessagePosition _shownAtPosition; Data::MessagePosition _initialAroundPosition; Context _context; int _aroundIndex = -1; int _idsLimit = kMinimalIdsLimit; Data::MessagesSlice _slice; std::vector> _items; ViewsMap _views, _viewsCapacity; int _itemsTop = 0; int _itemsWidth = 0; int _itemsHeight = 0; int _itemAverageHeight = 0; base::flat_set> _itemRevealPending; base::flat_map< not_null, ItemRevealAnimation> _itemRevealAnimations; int _itemsRevealHeight = 0; base::flat_set _animatedStickersPlayed; base::flat_map, Ui::PeerUserpicView> _userpics; base::flat_map, Ui::PeerUserpicView> _userpicsCache; base::flat_map _hiddenSenderUserpics; const std::unique_ptr _pathGradient; QPainterPath _highlightPathCache; base::unique_qptr _emptyInfo = nullptr; std::unique_ptr _reactionsManager; rpl::variable _reactionsItem; bool _useCornerReaction = false; std::unique_ptr _translateTracker; int _minHeight = 0; int _visibleTop = 0; int _visibleBottom = 0; Element *_visibleTopItem = nullptr; int _visibleTopFromItem = 0; ScrollTopState _scrollTopState; Ui::Animations::Simple _scrollToAnimation; Fn _overrideInitialScroll; bool _scrollInited = false; bool _scrollDateShown = false; Ui::Animations::Simple _scrollDateOpacity; SingleQueuedInvokation _scrollDateCheck; base::Timer _scrollDateHideTimer; Element *_scrollDateLastItem = nullptr; int _scrollDateLastItemTop = 0; ClickHandlerPtr _scrollDateLink; SingleQueuedInvokation _applyUpdatedScrollState; MessagesBar _bar; rpl::variable _barText; MouseAction _mouseAction = MouseAction::None; TextSelectType _mouseSelectType = TextSelectType::Letters; QPoint _mousePosition; MouseState _overState; MouseState _pressState; Element *_overElement = nullptr; HistoryItem *_overItemExact = nullptr; HistoryItem *_pressItemExact = nullptr; CursorState _mouseCursorState = CursorState(); uint16 _mouseTextSymbol = 0; bool _pressWasInactive = false; bool _selectEnabled = false; HistoryItem *_selectedTextItem = nullptr; TextSelection _selectedTextRange; TextForMimeData _selectedText; SelectedMap _selected; base::flat_set _dragSelected; DragSelectAction _dragSelectAction = DragSelectAction::None; bool _dragSelectDirectionUp = false; // Was some text selected in current drag action. bool _wasSelectedText = false; Qt::CursorShape _cursor = style::cur_default; bool _isChatWide = false; bool _refreshingViewer = false; bool _showFinished = false; bool _resizePending = false; // _menu must be destroyed before _whoReactedMenuLifetime. rpl::lifetime _whoReactedMenuLifetime; base::unique_qptr _menu; QPoint _trippleClickPoint; crl::time _trippleClickStartTime = 0; ElementHighlighter _highlighter; // scroll by touch support (at least Windows Surface tablets) bool _touchScroll = false; bool _touchSelect = false; bool _touchInProgress = false; QPoint _touchStart, _touchPrevPos, _touchPos; base::Timer _touchSelectTimer; Ui::DraggingScrollManager _selectScroll; InfoTooltip _topToast; Ui::TouchScrollState _touchScrollState = Ui::TouchScrollState(); bool _touchPrevPosValid = false; bool _touchWaitingAcceleration = false; QPoint _touchSpeed; crl::time _touchSpeedTime = 0; crl::time _touchAccelerationTime = 0; crl::time _touchTime = 0; base::Timer _touchScrollTimer; rpl::event_stream _requestedToEditMessage; rpl::event_stream _requestedToReplyToMessage; rpl::event_stream _requestedToReadMessage; rpl::event_stream _requestedToShowMessage; rpl::lifetime _viewerLifetime; }; void ConfirmDeleteSelectedItems(not_null widget); void ConfirmForwardSelectedItems(not_null widget); void ConfirmSendNowSelectedItems(not_null widget); [[nodiscard]] CopyRestrictionType CopyRestrictionTypeFor( not_null peer, HistoryItem *item = nullptr); [[nodiscard]] CopyRestrictionType CopyMediaRestrictionTypeFor( not_null peer, not_null item); [[nodiscard]] CopyRestrictionType SelectRestrictionTypeFor( not_null peer); } // namespace HistoryView