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