1 /* 2 This file is part of Telegram Desktop, 3 the official desktop application for the Telegram messaging service. 4 5 For license and copyright information please follow this link: 6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL 7 */ 8 #pragma once 9 10 #include "ui/rp_widget.h" 11 #include "ui/effects/animations.h" 12 #include "ui/chat/select_scroll_manager.h" // Has base/timer.h. 13 #include "ui/widgets/tooltip.h" 14 #include "mtproto/sender.h" 15 #include "data/data_messages.h" 16 #include "history/view/history_view_element.h" 17 18 namespace Main { 19 class Session; 20 } // namespace Main 21 22 namespace Ui { 23 class PopupMenu; 24 class ChatTheme; 25 } // namespace Ui 26 27 namespace Window { 28 class SessionController; 29 } // namespace Window 30 31 namespace Data { 32 struct Group; 33 class CloudImageView; 34 } // namespace Data 35 36 namespace HistoryView { 37 38 struct TextState; 39 struct StateRequest; 40 enum class CursorState : char; 41 enum class PointState : char; 42 enum class Context : char; 43 44 struct SelectedItem { SelectedItemSelectedItem45 explicit SelectedItem(FullMsgId msgId) : msgId(msgId) { 46 } 47 48 FullMsgId msgId; 49 bool canDelete = false; 50 bool canForward = false; 51 bool canSendNow = false; 52 }; 53 54 struct MessagesBar { 55 Element *element = nullptr; 56 bool hidden = false; 57 bool focus = false; 58 }; 59 60 struct MessagesBarData { 61 MessagesBar bar; 62 rpl::producer<QString> text; 63 }; 64 65 using SelectedItems = std::vector<SelectedItem>; 66 67 class ListDelegate { 68 public: 69 virtual Context listContext() = 0; 70 virtual void listScrollTo(int top) = 0; 71 virtual void listCancelRequest() = 0; 72 virtual void listDeleteRequest() = 0; 73 virtual rpl::producer<Data::MessagesSlice> listSource( 74 Data::MessagePosition aroundId, 75 int limitBefore, 76 int limitAfter) = 0; 77 virtual bool listAllowsMultiSelect() = 0; 78 virtual bool listIsItemGoodForSelection(not_null<HistoryItem*> item) = 0; 79 virtual bool listIsLessInOrder( 80 not_null<HistoryItem*> first, 81 not_null<HistoryItem*> second) = 0; 82 virtual void listSelectionChanged(SelectedItems &&items) = 0; 83 virtual void listVisibleItemsChanged(HistoryItemsList &&items) = 0; 84 virtual MessagesBarData listMessagesBar( 85 const std::vector<not_null<Element*>> &elements) = 0; 86 virtual void listContentRefreshed() = 0; 87 virtual ClickHandlerPtr listDateLink(not_null<Element*> view) = 0; 88 virtual bool listElementHideReply(not_null<const Element*> view) = 0; 89 virtual bool listElementShownUnread(not_null<const Element*> view) = 0; 90 virtual bool listIsGoodForAroundPosition( 91 not_null<const Element*> view) = 0; 92 virtual void listSendBotCommand( 93 const QString &command, 94 const FullMsgId &context) = 0; 95 virtual void listHandleViaClick(not_null<UserData*> bot) = 0; 96 virtual not_null<Ui::ChatTheme*> listChatTheme() = 0; 97 98 }; 99 100 struct SelectionData { 101 bool canDelete = false; 102 bool canForward = false; 103 bool canSendNow = false; 104 105 }; 106 107 using SelectedMap = base::flat_map< 108 FullMsgId, 109 SelectionData, 110 std::less<>>; 111 112 class ListMemento { 113 public: 114 struct ScrollTopState { 115 Data::MessagePosition item; 116 int shift = 0; 117 }; 118 119 explicit ListMemento( 120 Data::MessagePosition position = Data::UnreadMessagePosition) _aroundPosition(position)121 : _aroundPosition(position) { 122 } setAroundPosition(Data::MessagePosition position)123 void setAroundPosition(Data::MessagePosition position) { 124 _aroundPosition = position; 125 } aroundPosition()126 Data::MessagePosition aroundPosition() const { 127 return _aroundPosition; 128 } setIdsLimit(int limit)129 void setIdsLimit(int limit) { 130 _idsLimit = limit; 131 } idsLimit()132 int idsLimit() const { 133 return _idsLimit; 134 } setScrollTopState(ScrollTopState state)135 void setScrollTopState(ScrollTopState state) { 136 _scrollTopState = state; 137 } scrollTopState()138 ScrollTopState scrollTopState() const { 139 return _scrollTopState; 140 } 141 142 private: 143 Data::MessagePosition _aroundPosition; 144 ScrollTopState _scrollTopState; 145 int _idsLimit = 0; 146 147 }; 148 149 class ListWidget final 150 : public Ui::RpWidget 151 , public ElementDelegate 152 , public Ui::AbstractTooltipShower 153 , private base::Subscriber { 154 public: 155 ListWidget( 156 QWidget *parent, 157 not_null<Window::SessionController*> controller, 158 not_null<ListDelegate*> delegate); 159 160 static const crl::time kItemRevealDuration; 161 162 [[nodiscard]] Main::Session &session() const; 163 [[nodiscard]] not_null<Window::SessionController*> controller() const; 164 [[nodiscard]] not_null<ListDelegate*> delegate() const; 165 166 // Set the correct scroll position after being resized. 167 void restoreScrollPosition(); 168 169 void resizeToWidth(int newWidth, int minHeight); 170 171 void saveState(not_null<ListMemento*> memento); 172 void restoreState(not_null<ListMemento*> memento); 173 std::optional<int> scrollTopForPosition( 174 Data::MessagePosition position) const; 175 Element *viewByPosition(Data::MessagePosition position) const; 176 std::optional<int> scrollTopForView(not_null<Element*> view) const; 177 enum class AnimatedScroll { 178 Full, 179 Part, 180 None, 181 }; 182 void scrollTo( 183 int scrollTop, 184 Data::MessagePosition attachPosition, 185 int delta, 186 AnimatedScroll type); 187 [[nodiscard]] bool animatedScrolling() const; 188 bool isAbovePosition(Data::MessagePosition position) const; 189 bool isBelowPosition(Data::MessagePosition position) const; 190 void highlightMessage(FullMsgId itemId); 191 void showAroundPosition( 192 Data::MessagePosition position, 193 Fn<bool()> overrideInitialScroll); 194 195 [[nodiscard]] TextForMimeData getSelectedText() const; 196 [[nodiscard]] MessageIdsList getSelectedIds() const; 197 [[nodiscard]] SelectedItems getSelectedItems() const; 198 void cancelSelection(); 199 void selectItem(not_null<HistoryItem*> item); 200 void selectItemAsGroup(not_null<HistoryItem*> item); 201 202 bool loadedAtTopKnown() const; 203 bool loadedAtTop() const; 204 bool loadedAtBottomKnown() const; 205 bool loadedAtBottom() const; 206 bool isEmpty() const; 207 208 // AbstractTooltipShower interface 209 QString tooltipText() const override; 210 QPoint tooltipPos() const override; 211 bool tooltipWindowActive() const override; 212 213 [[nodiscard]] rpl::producer<FullMsgId> editMessageRequested() const; 214 void editMessageRequestNotify(FullMsgId item) const; 215 [[nodiscard]] bool lastMessageEditRequestNotify() const; 216 [[nodiscard]] rpl::producer<FullMsgId> replyToMessageRequested() const; 217 void replyToMessageRequestNotify(FullMsgId item); 218 [[nodiscard]] rpl::producer<FullMsgId> readMessageRequested() const; 219 [[nodiscard]] rpl::producer<FullMsgId> showMessageRequested() const; 220 void replyNextMessage(FullMsgId fullId, bool next = true); 221 222 // ElementDelegate interface. 223 Context elementContext() override; 224 std::unique_ptr<Element> elementCreate( 225 not_null<HistoryMessage*> message, 226 Element *replacing = nullptr) override; 227 std::unique_ptr<Element> elementCreate( 228 not_null<HistoryService*> message, 229 Element *replacing = nullptr) override; 230 bool elementUnderCursor(not_null<const Element*> view) override; 231 crl::time elementHighlightTime( 232 not_null<const HistoryItem*> item) override; 233 bool elementInSelectionMode() override; 234 bool elementIntersectsRange( 235 not_null<const Element*> view, 236 int from, 237 int till) override; 238 void elementStartStickerLoop(not_null<const Element*> view) override; 239 void elementShowPollResults( 240 not_null<PollData*> poll, 241 FullMsgId context) override; 242 void elementOpenPhoto( 243 not_null<PhotoData*> photo, 244 FullMsgId context) override; 245 void elementOpenDocument( 246 not_null<DocumentData*> document, 247 FullMsgId context, 248 bool showInMediaView = false) override; 249 void elementCancelUpload(const FullMsgId &context) override; 250 void elementShowTooltip( 251 const TextWithEntities &text, 252 Fn<void()> hiddenCallback) override; 253 bool elementIsGifPaused() override; 254 bool elementHideReply(not_null<const Element*> view) override; 255 bool elementShownUnread(not_null<const Element*> view) override; 256 void elementSendBotCommand( 257 const QString &command, 258 const FullMsgId &context) override; 259 void elementHandleViaClick(not_null<UserData*> bot) override; 260 bool elementIsChatWide() override; 261 not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override; 262 void elementReplyTo(const FullMsgId &to) override; 263 void elementStartInteraction(not_null<const Element*> view) override; 264 265 void setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w); 266 267 ~ListWidget(); 268 269 protected: 270 void visibleTopBottomUpdated( 271 int visibleTop, 272 int visibleBottom) override; 273 274 void paintEvent(QPaintEvent *e) override; 275 void keyPressEvent(QKeyEvent *e) override; 276 void mousePressEvent(QMouseEvent *e) override; 277 void mouseMoveEvent(QMouseEvent *e) override; 278 void mouseReleaseEvent(QMouseEvent *e) override; 279 void mouseDoubleClickEvent(QMouseEvent *e) override; 280 void enterEventHook(QEnterEvent *e) override; 281 void leaveEventHook(QEvent *e) override; 282 void contextMenuEvent(QContextMenuEvent *e) override; 283 284 // Resize content and count natural widget height for the desired width. 285 int resizeGetHeight(int newWidth) override; 286 287 private: 288 struct MouseState { 289 MouseState(); 290 MouseState( 291 FullMsgId itemId, 292 int height, 293 QPoint point, 294 PointState pointState); 295 296 FullMsgId itemId; 297 int height = 0; 298 QPoint point; 299 PointState pointState; 300 301 inline bool operator==(const MouseState &other) const { 302 return (itemId == other.itemId) 303 && (point == other.point); 304 } 305 inline bool operator!=(const MouseState &other) const { 306 return !(*this == other); 307 } 308 }; 309 struct ItemRevealAnimation { 310 Ui::Animations::Simple animation; 311 int startHeight = 0; 312 }; 313 enum class Direction { 314 Up, 315 Down, 316 }; 317 enum class MouseAction { 318 None, 319 PrepareDrag, 320 Dragging, 321 PrepareSelect, 322 Selecting, 323 }; 324 enum class SelectAction { 325 Select, 326 Deselect, 327 Invert, 328 }; 329 enum class EnumItemsDirection { 330 TopToBottom, 331 BottomToTop, 332 }; 333 enum class DragSelectAction { 334 None, 335 Selecting, 336 Deselecting, 337 }; 338 using ScrollTopState = ListMemento::ScrollTopState; 339 using PointState = HistoryView::PointState; 340 using CursorState = HistoryView::CursorState; 341 342 void refreshViewer(); 343 void updateAroundPositionFromNearest(int nearestIndex); 344 void refreshRows(const Data::MessagesSlice &old); 345 ScrollTopState countScrollState() const; 346 void saveScrollState(); 347 void restoreScrollState(); 348 349 Element *viewForItem(FullMsgId itemId) const; 350 Element *viewForItem(const HistoryItem *item) const; 351 not_null<Element*> enforceViewForItem(not_null<HistoryItem*> item); 352 353 void mouseActionStart( 354 const QPoint &globalPosition, 355 Qt::MouseButton button); 356 void mouseActionUpdate(const QPoint &globalPosition); 357 void mouseActionUpdate(); 358 void mouseActionFinish( 359 const QPoint &globalPosition, 360 Qt::MouseButton button); 361 void mouseActionCancel(); 362 std::unique_ptr<QMimeData> prepareDrag(); 363 void performDrag(); 364 style::cursor computeMouseCursor() const; 365 int itemTop(not_null<const Element*> view) const; 366 void repaintItem(FullMsgId itemId); 367 void repaintItem(const Element *view); 368 void repaintHighlightedItem(not_null<const Element*> view); 369 void resizeItem(not_null<Element*> view); 370 void refreshItem(not_null<const Element*> view); 371 void itemRemoved(not_null<const HistoryItem*> item); 372 QPoint mapPointToItem(QPoint point, const Element *view) const; 373 374 void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); 375 376 [[nodiscard]] int findItemIndexByY(int y) const; 377 [[nodiscard]] not_null<Element*> findItemByY(int y) const; 378 [[nodiscard]] Element *strictFindItemByY(int y) const; 379 [[nodiscard]] int findNearestItem(Data::MessagePosition position) const; 380 void viewReplaced(not_null<const Element*> was, Element *now); 381 [[nodiscard]] HistoryItemsList collectVisibleItems() const; 382 383 void checkMoveToOtherViewer(); 384 void updateVisibleTopItem(); 385 void updateItemsGeometry(); 386 void updateSize(); 387 void refreshAttachmentsFromTill(int from, int till); 388 void refreshAttachmentsAtIndex(int index); 389 390 void toggleScrollDateShown(); 391 void repaintScrollDateCallback(); 392 bool displayScrollDate() const; 393 void scrollDateHide(); 394 void scrollDateCheck(); 395 void scrollDateHideByTimer(); 396 void keepScrollDateForNow(); 397 398 void trySwitchToWordSelection(); 399 void switchToWordSelection(); 400 void validateTrippleClickStartTime(); 401 SelectedItems collectSelectedItems() const; 402 MessageIdsList collectSelectedIds() const; 403 void pushSelectedItems(); 404 void removeItemSelection( 405 const SelectedMap::const_iterator &i); 406 bool hasSelectedText() const; 407 bool hasSelectedItems() const; 408 bool overSelectedItems() const; 409 void clearTextSelection(); 410 void clearSelected(); 411 void setTextSelection( 412 not_null<Element*> view, 413 TextSelection selection); 414 int itemMinimalHeight() const; 415 416 bool isGoodForSelection( 417 SelectedMap &applyTo, 418 not_null<HistoryItem*> item, 419 int &totalCount) const; 420 bool addToSelection( 421 SelectedMap &applyTo, 422 not_null<HistoryItem*> item) const; 423 bool removeFromSelection( 424 SelectedMap &applyTo, 425 FullMsgId itemId) const; 426 void changeSelection( 427 SelectedMap &applyTo, 428 not_null<HistoryItem*> item, 429 SelectAction action) const; 430 bool isSelectedGroup( 431 const SelectedMap &applyTo, 432 not_null<const Data::Group*> group) const; 433 bool isSelectedAsGroup( 434 const SelectedMap &applyTo, 435 not_null<HistoryItem*> item) const; 436 void changeSelectionAsGroup( 437 SelectedMap &applyTo, 438 not_null<HistoryItem*> item, 439 SelectAction action) const; 440 441 SelectedMap::iterator itemUnderPressSelection(); 442 SelectedMap::const_iterator itemUnderPressSelection() const; 443 bool isItemUnderPressSelected() const; 444 bool isInsideSelection( 445 not_null<const Element*> view, 446 not_null<HistoryItem*> exactItem, 447 const MouseState &state) const; 448 bool requiredToStartDragging(not_null<Element*> view) const; 449 bool isPressInSelectedText(TextState state) const; 450 void updateDragSelection(); 451 void updateDragSelection( 452 const Element *fromView, 453 const MouseState &fromState, 454 const Element *tillView, 455 const MouseState &tillState); 456 void updateDragSelection( 457 std::vector<not_null<Element*>>::const_iterator from, 458 std::vector<not_null<Element*>>::const_iterator till); 459 void ensureDragSelectAction( 460 std::vector<not_null<Element*>>::const_iterator from, 461 std::vector<not_null<Element*>>::const_iterator till); 462 void clearDragSelection(); 463 void applyDragSelection(); 464 void applyDragSelection(SelectedMap &applyTo) const; 465 TextSelection itemRenderSelection( 466 not_null<const Element*> view) const; 467 TextSelection computeRenderSelection( 468 not_null<const SelectedMap*> selected, 469 not_null<const Element*> view) const; 470 void checkUnreadBarCreation(); 471 void applyUpdatedScrollState(); 472 void scrollToAnimationCallback(FullMsgId attachToId, int relativeTo); 473 void startItemRevealAnimations(); 474 void revealItemsCallback(); 475 476 void updateHighlightedMessage(); 477 void clearHighlightedMessage(); 478 479 // This function finds all history items that are displayed and calls template method 480 // for each found message (in given direction) in the passed history with passed top offset. 481 // 482 // Method has "bool (*Method)(not_null<Element*> view, int itemtop, int itembottom)" signature 483 // if it returns false the enumeration stops immediately. 484 template <EnumItemsDirection direction, typename Method> 485 void enumerateItems(Method method); 486 487 // This function finds all userpics on the left that are displayed and calls template method 488 // for each found userpic (from the top to the bottom) using enumerateItems() method. 489 // 490 // Method has "bool (*Method)(not_null<Element*> view, int userpicTop)" signature 491 // if it returns false the enumeration stops immediately. 492 template <typename Method> 493 void enumerateUserpics(Method method); 494 495 // This function finds all date elements that are displayed and calls template method 496 // for each found date element (from the bottom to the top) using enumerateItems() method. 497 // 498 // Method has "bool (*Method)(not_null<HistoryItem*> item, int itemtop, int dateTop)" signature 499 // if it returns false the enumeration stops immediately. 500 template <typename Method> 501 void enumerateDates(Method method); 502 503 static constexpr auto kMinimalIdsLimit = 24; 504 505 const not_null<ListDelegate*> _delegate; 506 const not_null<Window::SessionController*> _controller; 507 Data::MessagePosition _aroundPosition; 508 Data::MessagePosition _shownAtPosition; 509 Context _context; 510 int _aroundIndex = -1; 511 int _idsLimit = kMinimalIdsLimit; 512 Data::MessagesSlice _slice; 513 std::vector<not_null<Element*>> _items; 514 std::map< 515 not_null<HistoryItem*>, 516 std::unique_ptr<Element>, 517 std::less<>> _views; 518 int _itemsTop = 0; 519 int _itemsWidth = 0; 520 int _itemsHeight = 0; 521 int _itemAverageHeight = 0; 522 base::flat_set<not_null<Element*>> _itemRevealPending; 523 base::flat_map< 524 not_null<Element*>, 525 ItemRevealAnimation> _itemRevealAnimations; 526 int _itemsRevealHeight = 0; 527 base::flat_set<FullMsgId> _animatedStickersPlayed; 528 base::flat_map< 529 not_null<PeerData*>, 530 std::shared_ptr<Data::CloudImageView>> _userpics, _userpicsCache; 531 532 const std::unique_ptr<Ui::PathShiftGradient> _pathGradient; 533 534 base::unique_qptr<Ui::RpWidget> _emptyInfo = nullptr; 535 536 int _minHeight = 0; 537 int _visibleTop = 0; 538 int _visibleBottom = 0; 539 Element *_visibleTopItem = nullptr; 540 int _visibleTopFromItem = 0; 541 ScrollTopState _scrollTopState; 542 Ui::Animations::Simple _scrollToAnimation; 543 Fn<bool()> _overrideInitialScroll; 544 545 bool _scrollInited = false; 546 bool _scrollDateShown = false; 547 Ui::Animations::Simple _scrollDateOpacity; 548 SingleQueuedInvokation _scrollDateCheck; 549 base::Timer _scrollDateHideTimer; 550 Element *_scrollDateLastItem = nullptr; 551 int _scrollDateLastItemTop = 0; 552 ClickHandlerPtr _scrollDateLink; 553 SingleQueuedInvokation _applyUpdatedScrollState; 554 555 MessagesBar _bar; 556 rpl::variable<QString> _barText; 557 558 MouseAction _mouseAction = MouseAction::None; 559 TextSelectType _mouseSelectType = TextSelectType::Letters; 560 QPoint _mousePosition; 561 MouseState _overState; 562 MouseState _pressState; 563 Element *_overElement = nullptr; 564 HistoryItem *_overItemExact = nullptr; 565 HistoryItem *_pressItemExact = nullptr; 566 CursorState _mouseCursorState = CursorState(); 567 uint16 _mouseTextSymbol = 0; 568 bool _pressWasInactive = false; 569 570 bool _selectEnabled = false; 571 HistoryItem *_selectedTextItem = nullptr; 572 TextSelection _selectedTextRange; 573 TextForMimeData _selectedText; 574 SelectedMap _selected; 575 base::flat_set<FullMsgId> _dragSelected; 576 DragSelectAction _dragSelectAction = DragSelectAction::None; 577 bool _dragSelectDirectionUp = false; 578 // Was some text selected in current drag action. 579 bool _wasSelectedText = false; 580 Qt::CursorShape _cursor = style::cur_default; 581 582 bool _isChatWide = false; 583 584 base::unique_qptr<Ui::PopupMenu> _menu; 585 586 QPoint _trippleClickPoint; 587 crl::time _trippleClickStartTime = 0; 588 589 crl::time _highlightStart = 0; 590 FullMsgId _highlightedMessageId; 591 base::Timer _highlightTimer; 592 593 Ui::SelectScrollManager _selectScroll; 594 595 rpl::event_stream<FullMsgId> _requestedToEditMessage; 596 rpl::event_stream<FullMsgId> _requestedToReplyToMessage; 597 rpl::event_stream<FullMsgId> _requestedToReadMessage; 598 rpl::event_stream<FullMsgId> _requestedToShowMessage; 599 600 rpl::lifetime _viewerLifetime; 601 602 }; 603 604 void ConfirmDeleteSelectedItems(not_null<ListWidget*> widget); 605 void ConfirmForwardSelectedItems(not_null<ListWidget*> widget); 606 void ConfirmSendNowSelectedItems(not_null<ListWidget*> widget); 607 608 } // namespace HistoryView 609