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 "info/media/info_media_widget.h"
12 #include "data/data_shared_media.h"
13 #include "overview/overview_layout_delegate.h"
14 
15 class DeleteMessagesBox;
16 
17 namespace Main {
18 class Session;
19 } // namespace Main
20 
21 namespace HistoryView {
22 struct TextState;
23 struct StateRequest;
24 enum class CursorState : char;
25 enum class PointState : char;
26 } // namespace HistoryView
27 
28 namespace Ui {
29 class PopupMenu;
30 } // namespace Ui
31 
32 namespace Overview {
33 namespace Layout {
34 class ItemBase;
35 } // namespace Layout
36 } // namespace Overview
37 
38 namespace Window {
39 class SessionController;
40 } // namespace Window
41 
42 namespace Info {
43 
44 class AbstractController;
45 
46 namespace Media {
47 
48 using BaseLayout = Overview::Layout::ItemBase;
49 using UniversalMsgId = MsgId;
50 
51 class ListWidget final
52 	: public Ui::RpWidget
53 	, public Overview::Layout::Delegate {
54 public:
55 	ListWidget(
56 		QWidget *parent,
57 		not_null<AbstractController*> controller);
58 	~ListWidget();
59 
60 	Main::Session &session() const;
61 
62 	void restart();
63 
64 	rpl::producer<int> scrollToRequests() const;
65 	rpl::producer<SelectedItems> selectedListValue() const;
cancelSelection()66 	void cancelSelection() {
67 		clearSelected();
68 	}
69 
70 	QRect getCurrentSongGeometry();
checkForHide()71 	rpl::producer<> checkForHide() const {
72 		return _checkForHide.events();
73 	}
74 	bool preventAutoHide() const;
75 
76 	void saveState(not_null<Memento*> memento);
77 	void restoreState(not_null<Memento*> memento);
78 
79 	// Overview::Layout::Delegate
80 	void registerHeavyItem(not_null<const BaseLayout*> item) override;
81 	void unregisterHeavyItem(not_null<const BaseLayout*> item) override;
82 	void repaintItem(not_null<const BaseLayout*> item) override;
83 	bool itemVisible(not_null<const BaseLayout*> item) override;
84 
85 	void openPhoto(not_null<PhotoData*> photo, FullMsgId id) override;
86 	void openDocument(
87 		not_null<DocumentData*> document,
88 		FullMsgId id,
89 		bool showInMediaView = false) override;
90 
91 private:
92 	struct Context;
93 	struct DateBadge;
94 	class Section;
95 	using CursorState = HistoryView::CursorState;
96 	using TextState = HistoryView::TextState;
97 	using StateRequest = HistoryView::StateRequest;
98 	enum class MouseAction {
99 		None,
100 		PrepareDrag,
101 		Dragging,
102 		PrepareSelect,
103 		Selecting,
104 	};
105 	struct CachedItem {
106 		CachedItem(std::unique_ptr<BaseLayout> item);
107 		CachedItem(CachedItem &&other);
108 		CachedItem &operator=(CachedItem &&other);
109 		~CachedItem();
110 
111 		std::unique_ptr<BaseLayout> item;
112 		bool stale = false;
113 	};
114 	struct FoundItem {
115 		not_null<BaseLayout*> layout;
116 		QRect geometry;
117 		bool exact = false;
118 	};
119 	struct SelectionData {
SelectionDataSelectionData120 		explicit SelectionData(TextSelection text) : text(text) {
121 		}
122 
123 		TextSelection text;
124 		bool canDelete = false;
125 		bool canForward = false;
126 	};
127 	using SelectedMap = base::flat_map<
128 		UniversalMsgId,
129 		SelectionData,
130 		std::less<>>;
131 	enum class DragSelectAction {
132 		None,
133 		Selecting,
134 		Deselecting,
135 	};
136 	struct MouseState {
137 		UniversalMsgId itemId = 0;
138 		QSize size;
139 		QPoint cursor;
140 		bool inside = false;
141 
142 		inline bool operator==(const MouseState &other) const {
143 			return (itemId == other.itemId)
144 				&& (cursor == other.cursor);
145 		}
146 		inline bool operator!=(const MouseState &other) const {
147 			return !(*this == other);
148 		}
149 
150 	};
151 	enum class ContextMenuSource {
152 		Mouse,
153 		Touch,
154 		Other,
155 	};
156 	struct ScrollTopState {
157 		UniversalMsgId item = 0;
158 		int shift = 0;
159 	};
160 
161 	int resizeGetHeight(int newWidth) override;
162 	void visibleTopBottomUpdated(
163 		int visibleTop,
164 		int visibleBottom) override;
165 
166 	void paintEvent(QPaintEvent *e) override;
167 	void mouseMoveEvent(QMouseEvent *e) override;
168 	void mousePressEvent(QMouseEvent *e) override;
169 	void mouseReleaseEvent(QMouseEvent *e) override;
170 	void mouseDoubleClickEvent(QMouseEvent *e) override;
171 	void contextMenuEvent(QContextMenuEvent *e) override;
172 	void enterEventHook(QEnterEvent *e) override;
173 	void leaveEventHook(QEvent *e) override;
174 
175 	void start();
176 	int recountHeight();
177 	void refreshHeight();
178 
179 	QMargins padding() const;
180 	bool isMyItem(not_null<const HistoryItem*> item) const;
181 	bool isItemLayout(
182 		not_null<const HistoryItem*> item,
183 		BaseLayout *layout) const;
184 	bool isPossiblyMyId(FullMsgId fullId) const;
185 	void repaintItem(const HistoryItem *item);
186 	void repaintItem(UniversalMsgId msgId);
187 	void repaintItem(const BaseLayout *item);
188 	void repaintItem(QRect itemGeometry);
189 	void itemRemoved(not_null<const HistoryItem*> item);
190 	void itemLayoutChanged(not_null<const HistoryItem*> item);
191 
192 	void refreshViewer();
193 	void invalidatePaletteCache();
194 	void refreshRows();
195 	SparseIdsMergedSlice::Key sliceKey(
196 		UniversalMsgId universalId) const;
197 	BaseLayout *getLayout(UniversalMsgId universalId);
198 	BaseLayout *getExistingLayout(UniversalMsgId universalId) const;
199 	std::unique_ptr<BaseLayout> createLayout(
200 		UniversalMsgId universalId,
201 		Type type);
202 
203 	SelectedItems collectSelectedItems() const;
204 	MessageIdsList collectSelectedIds() const;
205 	void pushSelectedItems();
206 	FullMsgId computeFullId(UniversalMsgId universalId) const;
207 	bool hasSelected() const;
208 	bool isSelectedItem(
209 		const SelectedMap::const_iterator &i) const;
210 	void removeItemSelection(
211 		const SelectedMap::const_iterator &i);
212 	bool hasSelectedText() const;
213 	bool hasSelectedItems() const;
214 	void clearSelected();
215 	void forwardSelected();
216 	void forwardItem(UniversalMsgId universalId);
217 	void forwardItems(MessageIdsList &&items);
218 	void deleteSelected();
219 	void deleteItem(UniversalMsgId universalId);
220 	DeleteMessagesBox *deleteItems(MessageIdsList &&items);
221 	void applyItemSelection(
222 		UniversalMsgId universalId,
223 		TextSelection selection);
224 	void toggleItemSelection(
225 		UniversalMsgId universalId);
226 	SelectedMap::iterator itemUnderPressSelection();
227 	SelectedMap::const_iterator itemUnderPressSelection() const;
228 	bool isItemUnderPressSelected() const;
229 	bool requiredToStartDragging(not_null<BaseLayout*> layout) const;
230 	bool isPressInSelectedText(TextState state) const;
231 	void applyDragSelection();
232 	void applyDragSelection(SelectedMap &applyTo) const;
233 	bool changeItemSelection(
234 		SelectedMap &selected,
235 		UniversalMsgId universalId,
236 		TextSelection selection) const;
237 
238 	static bool IsAfter(
239 		const MouseState &a,
240 		const MouseState &b);
241 	static bool SkipSelectFromItem(const MouseState &state);
242 	static bool SkipSelectTillItem(const MouseState &state);
243 
244 	void markLayoutsStale();
245 	void clearStaleLayouts();
246 	std::vector<Section>::iterator findSectionByItem(
247 		UniversalMsgId universalId);
248 	std::vector<Section>::iterator findSectionAfterTop(int top);
249 	std::vector<Section>::const_iterator findSectionAfterTop(
250 		int top) const;
251 	std::vector<Section>::const_iterator findSectionAfterBottom(
252 		std::vector<Section>::const_iterator from,
253 		int bottom) const;
254 	FoundItem findItemByPoint(QPoint point) const;
255 	std::optional<FoundItem> findItemById(UniversalMsgId universalId);
256 	FoundItem findItemDetails(not_null<BaseLayout*> item);
257 	FoundItem foundItemInSection(
258 		const FoundItem &item,
259 		const Section &section) const;
260 
261 	ScrollTopState countScrollState() const;
262 	void saveScrollState();
263 	void restoreScrollState();
264 
265 	QPoint clampMousePosition(QPoint position) const;
266 	void mouseActionStart(
267 		const QPoint &globalPosition,
268 		Qt::MouseButton button);
269 	void mouseActionUpdate(const QPoint &globalPosition);
270 	void mouseActionUpdate();
271 	void mouseActionFinish(
272 		const QPoint &globalPosition,
273 		Qt::MouseButton button);
274 	void mouseActionCancel();
275 	void performDrag();
276 	style::cursor computeMouseCursor() const;
277 	void showContextMenu(
278 		QContextMenuEvent *e,
279 		ContextMenuSource source);
280 
281 	void updateDragSelection();
282 	void clearDragSelection();
283 
284 	void updateDateBadgeFor(int top);
285 	void scrollDateCheck();
286 	void scrollDateHide();
287 	void toggleScrollDateShown();
288 
289 	void trySwitchToWordSelection();
290 	void switchToWordSelection();
291 	void validateTrippleClickStartTime();
292 	void checkMoveToOtherViewer();
293 	void clearHeavyItems();
294 
295 	void setActionBoxWeak(QPointer<Ui::RpWidget> box);
296 
297 	const not_null<AbstractController*> _controller;
298 	const not_null<PeerData*> _peer;
299 	PeerData * const _migrated = nullptr;
300 	const Type _type = Type::Photo;
301 
302 	static constexpr auto kMinimalIdsLimit = 16;
303 	static constexpr auto kDefaultAroundId = (ServerMaxMsgId - 1);
304 	UniversalMsgId _universalAroundId = kDefaultAroundId;
305 	int _idsLimit = kMinimalIdsLimit;
306 	SparseIdsMergedSlice _slice;
307 
308 	std::unordered_map<UniversalMsgId, CachedItem> _layouts;
309 	base::flat_set<not_null<const BaseLayout*>> _heavyLayouts;
310 	bool _heavyLayoutsInvalidated = false;
311 	std::vector<Section> _sections;
312 
313 	int _visibleTop = 0;
314 	int _visibleBottom = 0;
315 	ScrollTopState _scrollTopState;
316 	rpl::event_stream<int> _scrollToRequests;
317 
318 	MouseAction _mouseAction = MouseAction::None;
319 	TextSelectType _mouseSelectType = TextSelectType::Letters;
320 	QPoint _mousePosition;
321 	MouseState _overState;
322 	MouseState _pressState;
323 	BaseLayout *_overLayout = nullptr;
324 	UniversalMsgId _contextUniversalId = 0;
325 	CursorState _mouseCursorState = CursorState();
326 	uint16 _mouseTextSymbol = 0;
327 	bool _pressWasInactive = false;
328 	SelectedMap _selected;
329 	SelectedMap _dragSelected;
330 	rpl::event_stream<SelectedItems> _selectedListStream;
331 	style::cursor _cursor = style::cur_default;
332 	DragSelectAction _dragSelectAction = DragSelectAction::None;
333 	bool _wasSelectedText = false; // was some text selected in current drag action
334 
335 	const std::unique_ptr<DateBadge> _dateBadge;
336 
337 	base::unique_qptr<Ui::PopupMenu> _contextMenu;
338 	rpl::event_stream<> _checkForHide;
339 	QPointer<Ui::RpWidget> _actionBoxWeak;
340 	rpl::lifetime _actionBoxWeakLifetime;
341 
342 	QPoint _trippleClickPoint;
343 	crl::time _trippleClickStartTime = 0;
344 
345 	rpl::lifetime _viewerLifetime;
346 
347 };
348 
349 } // namespace Media
350 } // namespace Info
351