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