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 "history/history_item.h"
11 #include "ui/empty_userpic.h"
12 #include "ui/effects/animations.h"
13 
14 struct WebPageData;
15 class VoiceSeekClickHandler;
16 
17 namespace Ui {
18 struct ChatPaintContext;
19 class ChatStyle;
20 } // namespace Ui
21 
22 namespace Data {
23 class Session;
24 } // namespace Data
25 
26 namespace HistoryView {
27 class Element;
28 class Document;
29 } // namespace HistoryView
30 
31 struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia, HistoryItem> {
32 	void create(not_null<Data::Session*> owner, UserId userId);
33 	void resize(int32 availw) const;
34 
35 	UserData *bot = nullptr;
36 	mutable QString text;
37 	mutable int width = 0;
38 	mutable int maxWidth = 0;
39 	ClickHandlerPtr link;
40 };
41 
42 struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, HistoryItem> {
43 	static constexpr auto kMaxRecentRepliers = 3;
44 
45 	struct Part {
46 		QString text;
47 		int textWidth = 0;
48 		int count = -1;
49 	};
50 	std::vector<PeerId> recentRepliers;
51 	Part views;
52 	Part replies;
53 	Part repliesSmall;
54 	MsgId repliesInboxReadTillId = 0;
55 	MsgId repliesOutboxReadTillId = 0;
56 	MsgId repliesMaxId = 0;
57 	int repliesUnreadCount = -1; // unknown
58 	ChannelId commentsMegagroupId = 0;
59 	MsgId commentsRootId = 0;
60 };
61 
62 struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned, HistoryItem> {
63 	void refresh(const QString &date);
64 	int maxWidth() const;
65 
66 	QString author;
67 	Ui::Text::String signature;
68 	bool isElided = false;
69 	bool isAnonymousRank = false;
70 };
71 
72 struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited, HistoryItem> {
73 	void refresh(const QString &date, bool displayed);
74 	int maxWidth() const;
75 
76 	TimeId date = 0;
77 	Ui::Text::String text;
78 };
79 
80 struct HistoryMessageSponsored : public RuntimeComponent<
81 		HistoryMessageSponsored,
82 		HistoryItem> {
83 	HistoryMessageSponsored();
84 	int maxWidth() const;
85 
86 	Ui::Text::String text;
87 };
88 
89 struct HiddenSenderInfo {
90 	HiddenSenderInfo(const QString &name, bool external);
91 
92 	QString name;
93 	QString firstName;
94 	QString lastName;
95 	PeerId colorPeerId = 0;
96 	Ui::EmptyUserpic userpic;
97 	Ui::Text::String nameText;
98 
99 	inline bool operator==(const HiddenSenderInfo &other) const {
100 		return name == other.name;
101 	}
102 	inline bool operator!=(const HiddenSenderInfo &other) const {
103 		return !(*this == other);
104 	}
105 };
106 
107 struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded, HistoryItem> {
108 	void create(const HistoryMessageVia *via) const;
109 
110 	TimeId originalDate = 0;
111 	PeerData *originalSender = nullptr;
112 	std::unique_ptr<HiddenSenderInfo> hiddenSenderInfo;
113 	QString originalAuthor;
114 	QString psaType;
115 	MsgId originalId = 0;
116 	mutable Ui::Text::String text = { 1 };
117 
118 	PeerData *savedFromPeer = nullptr;
119 	MsgId savedFromMsgId = 0;
120 	bool imported = false;
121 };
122 
123 struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply, HistoryItem> {
124 	HistoryMessageReply() = default;
125 	HistoryMessageReply(const HistoryMessageReply &other) = delete;
126 	HistoryMessageReply(HistoryMessageReply &&other) = delete;
127 	HistoryMessageReply &operator=(const HistoryMessageReply &other) = delete;
128 	HistoryMessageReply &operator=(HistoryMessageReply &&other) {
129 		replyToPeerId = other.replyToPeerId;
130 		replyToMsgId = other.replyToMsgId;
131 		replyToMsgTop = other.replyToMsgTop;
132 		replyToDocumentId = other.replyToDocumentId;
133 		std::swap(replyToMsg, other.replyToMsg);
134 		replyToLnk = std::move(other.replyToLnk);
135 		replyToName = std::move(other.replyToName);
136 		replyToText = std::move(other.replyToText);
137 		replyToVersion = other.replyToVersion;
138 		maxReplyWidth = other.maxReplyWidth;
139 		replyToVia = std::move(other.replyToVia);
140 		return *this;
141 	}
~HistoryMessageReplyHistoryMessageReply142 	~HistoryMessageReply() {
143 		// clearData() should be called by holder.
144 		Expects(replyToMsg == nullptr);
145 		Expects(replyToVia == nullptr);
146 	}
147 
148 	bool updateData(not_null<HistoryMessage*> holder, bool force = false);
149 
150 	// Must be called before destructor.
151 	void clearData(not_null<HistoryMessage*> holder);
152 
153 	bool isNameUpdated() const;
154 	void updateName() const;
155 	void resize(int width) const;
156 	void itemRemoved(HistoryMessage *holder, HistoryItem *removed);
157 
158 	void paint(
159 		Painter &p,
160 		not_null<const HistoryView::Element*> holder,
161 		const Ui::ChatPaintContext &context,
162 		int x,
163 		int y,
164 		int w,
165 		bool inBubble) const;
166 
replyToPeerHistoryMessageReply167 	[[nodiscard]] PeerId replyToPeer() const {
168 		return replyToPeerId;
169 	}
replyToIdHistoryMessageReply170 	[[nodiscard]] MsgId replyToId() const {
171 		return replyToMsgId;
172 	}
replyToTopHistoryMessageReply173 	[[nodiscard]] MsgId replyToTop() const {
174 		return replyToMsgTop;
175 	}
replyToWidthHistoryMessageReply176 	[[nodiscard]] int replyToWidth() const {
177 		return maxReplyWidth;
178 	}
replyToLinkHistoryMessageReply179 	[[nodiscard]] ClickHandlerPtr replyToLink() const {
180 		return replyToLnk;
181 	}
182 	void setReplyToLinkFrom(
183 		not_null<HistoryMessage*> holder);
184 
185 	void refreshReplyToDocument();
186 
187 	PeerId replyToPeerId = 0;
188 	MsgId replyToMsgId = 0;
189 	MsgId replyToMsgTop = 0;
190 	HistoryItem *replyToMsg = nullptr;
191 	DocumentId replyToDocumentId = 0;
192 	ClickHandlerPtr replyToLnk;
193 	mutable Ui::Text::String replyToName, replyToText;
194 	mutable int replyToVersion = 0;
195 	mutable int maxReplyWidth = 0;
196 	std::unique_ptr<HistoryMessageVia> replyToVia;
197 	int toWidth = 0;
198 
199 };
200 
201 struct HistoryMessageReplyMarkup
202 	: public RuntimeComponent<HistoryMessageReplyMarkup, HistoryItem> {
203 	using Button = HistoryMessageMarkupButton;
204 
205 	void createForwarded(const HistoryMessageReplyMarkup &original);
206 	void updateData(HistoryMessageMarkupData &&markup);
207 
208 	HistoryMessageMarkupData data;
209 	std::unique_ptr<ReplyKeyboard> inlineKeyboard;
210 
211 };
212 
213 class ReplyMarkupClickHandler : public ClickHandler {
214 public:
215 	ReplyMarkupClickHandler(
216 		not_null<Data::Session*> owner,
217 		int row,
218 		int column,
219 		FullMsgId context);
220 
221 	QString tooltip() const override;
222 
setFullDisplayed(bool full)223 	void setFullDisplayed(bool full) {
224 		_fullDisplayed = full;
225 	}
226 
227 	// Copy to clipboard support.
228 	QString copyToClipboardText() const override;
229 	QString copyToClipboardContextItemText() const override;
230 
231 	// Finds the corresponding button in the items markup struct.
232 	// If the button is not found it returns nullptr.
233 	// Note: it is possible that we will point to the different button
234 	// than the one was used when constructing the handler, but not a big deal.
235 	const HistoryMessageMarkupButton *getButton() const;
236 
237 	const HistoryMessageMarkupButton *getUrlButton() const;
238 
239 	// We hold only FullMsgId, not HistoryItem*, because all click handlers
240 	// are activated async and the item may be already destroyed.
setMessageId(const FullMsgId & msgId)241 	void setMessageId(const FullMsgId &msgId) {
242 		_itemId = msgId;
243 	}
244 
245 	void onClick(ClickContext context) const override;
246 
247 private:
248 	const not_null<Data::Session*> _owner;
249 	FullMsgId _itemId;
250 	int _row = 0;
251 	int _column = 0;
252 	bool _fullDisplayed = true;
253 
254 	// Returns the full text of the corresponding button.
255 	QString buttonText() const;
256 
257 };
258 
259 class ReplyKeyboard {
260 private:
261 	struct Button;
262 
263 public:
264 	class Style {
265 	public:
Style(const style::BotKeyboardButton & st)266 		Style(const style::BotKeyboardButton &st) : _st(&st) {
267 		}
268 
269 		virtual void startPaint(
270 			Painter &p,
271 			const Ui::ChatStyle *st) const = 0;
272 		virtual const style::TextStyle &textStyle() const = 0;
273 
274 		int buttonSkip() const;
275 		int buttonPadding() const;
276 		int buttonHeight() const;
277 		virtual int buttonRadius() const = 0;
278 
279 		virtual void repaint(not_null<const HistoryItem*> item) const = 0;
~Style()280 		virtual ~Style() {
281 		}
282 
283 	protected:
284 		virtual void paintButtonBg(
285 			Painter &p,
286 			const Ui::ChatStyle *st,
287 			const QRect &rect,
288 			float64 howMuchOver) const = 0;
289 		virtual void paintButtonIcon(
290 			Painter &p,
291 			const Ui::ChatStyle *st,
292 			const QRect &rect,
293 			int outerWidth,
294 			HistoryMessageMarkupButton::Type type) const = 0;
295 		virtual void paintButtonLoading(
296 			Painter &p,
297 			const Ui::ChatStyle *st,
298 			const QRect &rect) const = 0;
299 		virtual int minButtonWidth(
300 			HistoryMessageMarkupButton::Type type) const = 0;
301 
302 	private:
303 		const style::BotKeyboardButton *_st;
304 
305 		void paintButton(
306 			Painter &p,
307 			const Ui::ChatStyle *st,
308 			int outerWidth,
309 			const ReplyKeyboard::Button &button) const;
310 		friend class ReplyKeyboard;
311 
312 	};
313 
314 	ReplyKeyboard(
315 		not_null<const HistoryItem*> item,
316 		std::unique_ptr<Style> &&s);
317 	ReplyKeyboard(const ReplyKeyboard &other) = delete;
318 	ReplyKeyboard &operator=(const ReplyKeyboard &other) = delete;
319 
320 	bool isEnoughSpace(int width, const style::BotKeyboardButton &st) const;
321 	void setStyle(std::unique_ptr<Style> &&s);
322 	void resize(int width, int height);
323 
324 	// what width and height will best fit this keyboard
325 	int naturalWidth() const;
326 	int naturalHeight() const;
327 
328 	void paint(
329 		Painter &p,
330 		const Ui::ChatStyle *st,
331 		int outerWidth,
332 		const QRect &clip) const;
333 	ClickHandlerPtr getLink(QPoint point) const;
334 
335 	void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active);
336 	void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed);
337 
338 	void clearSelection();
339 	void updateMessageId();
340 
341 private:
342 	friend class Style;
343 	struct Button {
344 		Button();
345 		Button(Button &&other);
346 		Button &operator=(Button &&other);
347 		~Button();
348 
349 		Ui::Text::String text = { 1 };
350 		QRect rect;
351 		int characters = 0;
352 		float64 howMuchOver = 0.;
353 		HistoryMessageMarkupButton::Type type;
354 		std::shared_ptr<ReplyMarkupClickHandler> link;
355 		mutable std::unique_ptr<Ui::RippleAnimation> ripple;
356 	};
357 	struct ButtonCoords {
358 		int i, j;
359 	};
360 
361 	void startAnimation(int i, int j, int direction);
362 
363 	ButtonCoords findButtonCoordsByClickHandler(const ClickHandlerPtr &p);
364 
365 	bool selectedAnimationCallback(crl::time now);
366 
367 	const not_null<const HistoryItem*> _item;
368 	int _width = 0;
369 
370 	std::vector<std::vector<Button>> _rows;
371 
372 	base::flat_map<int, crl::time> _animations;
373 	Ui::Animations::Basic _selectedAnimation;
374 	std::unique_ptr<Style> _st;
375 
376 	ClickHandlerPtr _savedPressed;
377 	ClickHandlerPtr _savedActive;
378 	mutable QPoint _savedCoords;
379 
380 };
381 
382 // Special type of Component for the channel actions log.
383 struct HistoryMessageLogEntryOriginal
384 	: public RuntimeComponent<HistoryMessageLogEntryOriginal, HistoryItem> {
385 	HistoryMessageLogEntryOriginal();
386 	HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other);
387 	HistoryMessageLogEntryOriginal &operator=(HistoryMessageLogEntryOriginal &&other);
388 	~HistoryMessageLogEntryOriginal();
389 
390 	WebPageData *page = nullptr;
391 
392 };
393 
394 class FileClickHandler;
395 struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed, HistoryView::Document> {
396 	std::shared_ptr<FileClickHandler> _linksavel;
397 	std::shared_ptr<FileClickHandler> _linkopenwithl;
398 	std::shared_ptr<FileClickHandler> _linkcancell;
399 	int _thumbw = 0;
400 
401 	mutable int _linkw = 0;
402 	mutable QString _link;
403 };
404 
405 struct HistoryDocumentCaptioned : public RuntimeComponent<HistoryDocumentCaptioned, HistoryView::Document> {
406 	HistoryDocumentCaptioned();
407 
408 	Ui::Text::String _caption;
409 };
410 
411 struct HistoryDocumentNamed : public RuntimeComponent<HistoryDocumentNamed, HistoryView::Document> {
412 	QString _name;
413 	int _namew = 0;
414 };
415 
416 struct HistoryDocumentVoicePlayback {
417 	HistoryDocumentVoicePlayback(const HistoryView::Document *that);
418 
419 	int32 position = 0;
420 	anim::value progress;
421 	Ui::Animations::Basic progressAnimation;
422 };
423 
424 class HistoryDocumentVoice : public RuntimeComponent<HistoryDocumentVoice, HistoryView::Document> {
425 	// We don't use float64 because components should align to pointer even on 32bit systems.
426 	static constexpr float64 kFloatToIntMultiplier = 65536.;
427 
428 public:
429 	void ensurePlayback(const HistoryView::Document *interfaces) const;
430 	void checkPlaybackFinished() const;
431 
432 	mutable std::unique_ptr<HistoryDocumentVoicePlayback> _playback;
433 	std::shared_ptr<VoiceSeekClickHandler> _seekl;
434 	mutable int _lastDurationMs = 0;
435 
seeking()436 	bool seeking() const {
437 		return _seeking;
438 	}
439 	void startSeeking();
440 	void stopSeeking();
seekingStart()441 	float64 seekingStart() const {
442 		return _seekingStart / kFloatToIntMultiplier;
443 	}
setSeekingStart(float64 seekingStart)444 	void setSeekingStart(float64 seekingStart) const {
445 		_seekingStart = qRound(seekingStart * kFloatToIntMultiplier);
446 	}
seekingCurrent()447 	float64 seekingCurrent() const {
448 		return _seekingCurrent / kFloatToIntMultiplier;
449 	}
setSeekingCurrent(float64 seekingCurrent)450 	void setSeekingCurrent(float64 seekingCurrent) {
451 		_seekingCurrent = qRound(seekingCurrent * kFloatToIntMultiplier);
452 	}
453 
454 private:
455 	bool _seeking = false;
456 
457 	mutable int _seekingStart = 0;
458 	mutable int _seekingCurrent = 0;
459 
460 };
461