/* This file is part of Telegram Desktop, the official desktop version of Telegram messaging app, see https://telegram.org Telegram Desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once void historyInit(); class HistoryItem; typedef QMap<int32, HistoryItem*> SelectedItemSet; #include "structs.h" #include "dialogs/dialogs_common.h" enum NewMessageType { NewMessageUnread, NewMessageLast, NewMessageExisting, }; class History; class Histories { public: typedef QHash<PeerId, History*> Map; Map map; Histories() : _a_typings(animation(this, &Histories::step_typings)), _unreadFull(0), _unreadMuted(0) { } void regSendAction(History *history, UserData *user, const MTPSendMessageAction &action, TimeId when); void step_typings(uint64 ms, bool timer); History *find(const PeerId &peerId); History *findOrInsert(const PeerId &peerId, int32 unreadCount, int32 maxInboxRead, int32 maxOutboxRead); void clear(); void remove(const PeerId &peer); ~Histories() { _unreadFull = _unreadMuted = 0; } HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type); typedef QMap<History*, uint64> TypingHistories; // when typing in this history started TypingHistories typing; Animation _a_typings; int32 unreadBadge() const { return _unreadFull - (cIncludeMuted() ? 0 : _unreadMuted); } int32 unreadMutedCount() const { return _unreadMuted; } bool unreadOnlyMuted() const { return cIncludeMuted() ? (_unreadMuted >= _unreadFull) : false; } void unreadIncrement(int32 count, bool muted) { _unreadFull += count; if (muted) { _unreadMuted += count; } } void unreadMuteChanged(int32 count, bool muted) { if (muted) { _unreadMuted += count; } else { _unreadMuted -= count; } } private: int32 _unreadFull, _unreadMuted; }; class HistoryBlock; enum HistoryMediaType { MediaTypePhoto, MediaTypeVideo, MediaTypeContact, MediaTypeFile, MediaTypeGif, MediaTypeSticker, MediaTypeLocation, MediaTypeWebPage, MediaTypeMusicFile, MediaTypeVoiceFile, MediaTypeCount }; enum MediaOverviewType { OverviewPhotos = 0, OverviewVideos = 1, OverviewMusicFiles = 2, OverviewFiles = 3, OverviewVoiceFiles = 4, OverviewLinks = 5, OverviewChatPhotos = 6, OverviewCount }; inline MTPMessagesFilter typeToMediaFilter(MediaOverviewType &type) { switch (type) { case OverviewPhotos: return MTP_inputMessagesFilterPhotos(); case OverviewVideos: return MTP_inputMessagesFilterVideo(); case OverviewMusicFiles: return MTP_inputMessagesFilterMusic(); case OverviewFiles: return MTP_inputMessagesFilterDocument(); case OverviewVoiceFiles: return MTP_inputMessagesFilterVoice(); case OverviewLinks: return MTP_inputMessagesFilterUrl(); case OverviewChatPhotos: return MTP_inputMessagesFilterChatPhotos(); case OverviewCount: break; default: type = OverviewCount; break; } return MTPMessagesFilter(); } enum SendActionType { SendActionTyping, SendActionRecordVideo, SendActionUploadVideo, SendActionRecordVoice, SendActionUploadVoice, SendActionUploadPhoto, SendActionUploadFile, SendActionChooseLocation, SendActionChooseContact, }; struct SendAction { SendAction(SendActionType type, uint64 until, int32 progress = 0) : type(type), until(until), progress(progress) { } SendActionType type; uint64 until; int32 progress; }; using TextWithTags = FlatTextarea::TextWithTags; namespace Data { struct Draft; } // namespace Data class HistoryMedia; class HistoryMessage; enum AddToOverviewMethod { AddToOverviewNew, // when new message is added to history AddToOverviewFront, // when old messages slice was received AddToOverviewBack, // when new messages slice was received and it is the last one, we index all media }; namespace Dialogs { class Row; class IndexedList; } // namespace Dialogs class ChannelHistory; class History { public: History(const PeerId &peerId); History(const History &) = delete; History &operator=(const History &) = delete; ChannelId channelId() const { return peerToChannel(peer->id); } bool isChannel() const { return peerIsChannel(peer->id); } bool isMegagroup() const { return peer->isMegagroup(); } ChannelHistory *asChannelHistory(); const ChannelHistory *asChannelHistory() const; bool isEmpty() const { return blocks.isEmpty(); } bool isDisplayedEmpty() const; void clear(bool leaveItems = false); virtual ~History(); HistoryItem *addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true); HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type); HistoryItem *addToHistory(const MTPMessage &msg); HistoryItem *addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *item); HistoryItem *addNewDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); HistoryItem *addNewPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); void addOlderSlice(const QVector<MTPMessage> &slice); void addNewerSlice(const QVector<MTPMessage> &slice); bool addToOverview(MediaOverviewType type, MsgId msgId, AddToOverviewMethod method); void eraseFromOverview(MediaOverviewType type, MsgId msgId); void newItemAdded(HistoryItem *item); void unregTyping(UserData *from); int countUnread(MsgId upTo); void updateShowFrom(); MsgId inboxRead(MsgId upTo); MsgId inboxRead(HistoryItem *wasRead); MsgId outboxRead(MsgId upTo); MsgId outboxRead(HistoryItem *wasRead); HistoryItem *lastImportantMessage() const; int unreadCount() const { return _unreadCount; } void setUnreadCount(int newUnreadCount); bool mute() const { return _mute; } void setMute(bool newMute); void getNextShowFrom(HistoryBlock *block, int i); void addUnreadBar(); void destroyUnreadBar(); void clearNotifications(); bool loadedAtBottom() const; // last message is in the list void setNotLoadedAtBottom(); bool loadedAtTop() const; // nothing was added after loading history back bool isReadyFor(MsgId msgId); // has messages for showing history at msgId void getReadyFor(MsgId msgId); void setLastMessage(HistoryItem *msg); void fixLastMessage(bool wasAtBottom); bool needUpdateInChatList() const; void updateChatListSortPosition(); void setChatsListDate(const QDateTime &date); uint64 sortKeyInChatList() const { return _sortKeyInChatList; } struct PositionInChatListChange { int movedFrom; int movedTo; }; PositionInChatListChange adjustByPosInChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed); bool inChatList(Dialogs::Mode list) const { return !chatListLinks(list).isEmpty(); } int posInChatList(Dialogs::Mode list) const; Dialogs::Row *addToChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed); void removeFromChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed); void removeChatListEntryByLetter(Dialogs::Mode list, QChar letter); void addChatListEntryByLetter(Dialogs::Mode list, QChar letter, Dialogs::Row *row); void updateChatListEntry() const; MsgId minMsgId() const; MsgId maxMsgId() const; MsgId msgIdForRead() const; int resizeGetHeight(int newWidth); void removeNotification(HistoryItem *item) { if (!notifies.isEmpty()) { for (auto i = notifies.begin(), e = notifies.end(); i != e; ++i) { if ((*i) == item) { notifies.erase(i); break; } } } } HistoryItem *currentNotification() { return notifies.isEmpty() ? 0 : notifies.front(); } bool hasNotification() const { return !notifies.isEmpty(); } void skipNotification() { if (!notifies.isEmpty()) { notifies.pop_front(); } } void popNotification(HistoryItem *item) { if (!notifies.isEmpty() && notifies.back() == item) notifies.pop_back(); } bool hasPendingResizedItems() const { return _flags & Flag::f_has_pending_resized_items; } void setHasPendingResizedItems(); void setPendingResize() { _flags |= Flag::f_pending_resize; setHasPendingResizedItems(); } void paintDialog(Painter &p, int32 w, bool sel) const; bool updateTyping(uint64 ms, bool force = false); void clearLastKeyboard(); // optimization for userpics displayed on the left // if this returns false there is no need to even try to handle them bool canHaveFromPhotos() const; typedef QList<HistoryBlock*> Blocks; Blocks blocks; int width = 0; int height = 0; int32 msgCount = 0; MsgId inboxReadBefore = 1; MsgId outboxReadBefore = 1; HistoryItem *showFrom = nullptr; HistoryItem *unreadBar = nullptr; PeerData *peer; bool oldLoaded = false; bool newLoaded = true; HistoryItem *lastMsg = nullptr; HistoryItem *lastSentMsg = nullptr; QDateTime lastMsgDate; typedef QList<HistoryItem*> NotifyQueue; NotifyQueue notifies; Data::Draft *localDraft() { return _localDraft.get(); } Data::Draft *cloudDraft() { return _cloudDraft.get(); } Data::Draft *editDraft() { return _editDraft.get(); } void setLocalDraft(std_::unique_ptr<Data::Draft> &&draft); void takeLocalDraft(History *from); void createLocalDraftFromCloud(); void setCloudDraft(std_::unique_ptr<Data::Draft> &&draft); Data::Draft *createCloudDraft(Data::Draft *fromDraft); void setEditDraft(std_::unique_ptr<Data::Draft> &&draft); void clearLocalDraft(); void clearCloudDraft(); void clearEditDraft(); void draftSavedToCloud(); Data::Draft *draft() { return _editDraft ? editDraft() : localDraft(); } // some fields below are a property of a currently displayed instance of this // conversation history not a property of the conversation history itself public: // we save the last showAtMsgId to restore the state when switching // between different conversation histories MsgId showAtMsgId = ShowAtUnreadMsgId; // we save a pointer of the history item at the top of the displayed window // together with an offset from the window top to the top of this message // resulting scrollTop = top(scrollTopItem) + scrollTopOffset HistoryItem *scrollTopItem = nullptr; int scrollTopOffset = 0; void forgetScrollState() { scrollTopItem = nullptr; } // find the correct scrollTopItem and scrollTopOffset using given top // of the displayed window relative to the history start coord void countScrollState(int top); protected: // when this item is destroyed scrollTopItem just points to the next one // and scrollTopOffset remains the same // if we are at the bottom of the window scrollTopItem == nullptr and // scrollTopOffset is undefined void getNextScrollTopItem(HistoryBlock *block, int32 i); // helper method for countScrollState(int top) void countScrollTopItem(int top); public: bool lastKeyboardInited = false; bool lastKeyboardUsed = false; MsgId lastKeyboardId = 0; MsgId lastKeyboardHiddenId = 0; PeerId lastKeyboardFrom = 0; mtpRequestId sendRequestId = 0; mutable const HistoryItem *textCachedFor = nullptr; // cache mutable Text lastItemTextCache; typedef QMap<UserData*, uint64> TypingUsers; TypingUsers typing; typedef QMap<UserData*, SendAction> SendActionUsers; SendActionUsers sendActions; QString typingStr; Text typingText; uint32 typingDots; QMap<SendActionType, uint64> mySendActions; typedef QList<MsgId> MediaOverview; MediaOverview overview[OverviewCount]; bool overviewCountLoaded(int32 overviewIndex) const { return overviewCountData[overviewIndex] >= 0; } bool overviewLoaded(int32 overviewIndex) const { return overviewCount(overviewIndex) == overview[overviewIndex].size(); } int32 overviewCount(int32 overviewIndex, int32 defaultValue = -1) const { int32 result = overviewCountData[overviewIndex], loaded = overview[overviewIndex].size(); if (result < 0) return defaultValue; if (result < loaded) { if (result > 0) { const_cast<History*>(this)->overviewCountData[overviewIndex] = 0; } return loaded; } return result; } MsgId overviewMinId(int32 overviewIndex) const { for (MediaOverviewIds::const_iterator i = overviewIds[overviewIndex].cbegin(), e = overviewIds[overviewIndex].cend(); i != e; ++i) { if (i.key() > 0) { return i.key(); } } return 0; } void overviewSliceDone(int32 overviewIndex, const MTPmessages_Messages &result, bool onlyCounts = false); bool overviewHasMsgId(int32 overviewIndex, MsgId msgId) const { return overviewIds[overviewIndex].constFind(msgId) != overviewIds[overviewIndex].cend(); } void changeMsgId(MsgId oldId, MsgId newId); Text cloudDraftTextCache; protected: void clearOnDestroy(); HistoryItem *addNewToLastBlock(const MTPMessage &msg, NewMessageType type); friend class HistoryBlock; // this method just removes a block from the blocks list // when the last item from this block was detached and // calls the required previousItemChanged() void removeBlock(HistoryBlock *block); void clearBlocks(bool leaveItems); HistoryItem *createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem); HistoryItem *createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *msg); HistoryItem *createItemDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); HistoryItem *createItemPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); HistoryItem *addNewItem(HistoryItem *adding, bool newMsg); HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex); // All this methods add a new item to the first or last block // depending on if we are in isBuildingFronBlock() state. // The last block is created on the go if it is needed. // Adds the item to the back or front block, depending on // isBuildingFrontBlock(), creating the block if necessary. void addItemToBlock(HistoryItem *item); // Usually all new items are added to the last block. // Only when we scroll up and add a new slice to the // front we want to create a new front block. void startBuildingFrontBlock(int expectedItemsCount = 1); HistoryBlock *finishBuildingFrontBlock(); // Returns the built block or nullptr if nothing was added. bool isBuildingFrontBlock() const { return _buildingFrontBlock != nullptr; } private: // After adding a new history slice check the lastMsg and newLoaded. void checkLastMsg(); enum class Flag { f_has_pending_resized_items = (1 << 0), f_pending_resize = (1 << 1), }; Q_DECLARE_FLAGS(Flags, Flag); Q_DECL_CONSTEXPR friend inline QFlags<Flags::enum_type> operator|(Flags::enum_type f1, Flags::enum_type f2) noexcept { return QFlags<Flags::enum_type>(f1) | f2; } Q_DECL_CONSTEXPR friend inline QFlags<Flags::enum_type> operator|(Flags::enum_type f1, QFlags<Flags::enum_type> f2) noexcept { return f2 | f1; } Q_DECL_CONSTEXPR friend inline QFlags<Flags::enum_type> operator~(Flags::enum_type f) noexcept { return ~QFlags<Flags::enum_type>(f); } Flags _flags; bool _mute; int32 _unreadCount = 0; Dialogs::RowsByLetter _chatListLinks[2]; Dialogs::RowsByLetter &chatListLinks(Dialogs::Mode list) { return _chatListLinks[static_cast<int>(list)]; } const Dialogs::RowsByLetter &chatListLinks(Dialogs::Mode list) const { return _chatListLinks[static_cast<int>(list)]; } Dialogs::Row *mainChatListLink(Dialogs::Mode list) const { auto it = chatListLinks(list).constFind(0); t_assert(it != chatListLinks(list).cend()); return it.value(); } uint64 _sortKeyInChatList = 0; // like ((unixtime) << 32) | (incremented counter) typedef QMap<MsgId, NullType> MediaOverviewIds; MediaOverviewIds overviewIds[OverviewCount]; int32 overviewCountData[OverviewCount]; // -1 - not loaded, 0 - all loaded, > 0 - count, but not all loaded // A pointer to the block that is currently being built. // We hold this pointer so we can destroy it while building // and then create a new one if it is necessary. struct BuildingBlock { int expectedItemsCount = 0; // optimization for block->items.reserve() call HistoryBlock *block = nullptr; }; std_::unique_ptr<BuildingBlock> _buildingFrontBlock; // Creates if necessary a new block for adding item. // Depending on isBuildingFrontBlock() gets front or back block. HistoryBlock *prepareBlockForAddingItem(); std_::unique_ptr<Data::Draft> _localDraft, _cloudDraft; std_::unique_ptr<Data::Draft> _editDraft; }; class HistoryJoined; class ChannelHistory : public History { public: ChannelHistory(const PeerId &peer); void messageDetached(HistoryItem *msg); void getRangeDifference(); void getRangeDifferenceNext(int32 pts); HistoryJoined *insertJoinedMessage(bool unread); void checkJoinedMessage(bool createUnread = false); const QDateTime &maxReadMessageDate(); ~ChannelHistory(); private: friend class History; HistoryItem* addNewChannelMessage(const MTPMessage &msg, NewMessageType type); HistoryItem *addNewToBlocks(const MTPMessage &msg, NewMessageType type); void checkMaxReadMessageDate(); HistoryItem *findPrevItem(HistoryItem *item) const; void cleared(bool leaveItems); QDateTime _maxReadMessageDate; HistoryJoined *_joinedMessage; MsgId _rangeDifferenceFromId, _rangeDifferenceToId; int32 _rangeDifferencePts; mtpRequestId _rangeDifferenceRequestId; }; class HistoryBlock { public: HistoryBlock(History *hist) : y(0), height(0), history(hist), _indexInHistory(-1) { } HistoryBlock(const HistoryBlock &) = delete; HistoryBlock &operator=(const HistoryBlock &) = delete; typedef QVector<HistoryItem*> Items; Items items; void clear(bool leaveItems = false); ~HistoryBlock() { clear(); } void removeItem(HistoryItem *item); int resizeGetHeight(int newWidth, bool resizeAllItems); int32 y, height; History *history; HistoryBlock *previousBlock() const { t_assert(_indexInHistory >= 0); return (_indexInHistory > 0) ? history->blocks.at(_indexInHistory - 1) : nullptr; } HistoryBlock *nextBlock() const { t_assert(_indexInHistory >= 0); return (_indexInHistory + 1 < history->blocks.size()) ? history->blocks.at(_indexInHistory + 1) : nullptr; } void setIndexInHistory(int index) { _indexInHistory = index; } int indexInHistory() const { t_assert(_indexInHistory >= 0); t_assert(history->blocks.at(_indexInHistory) == this); return _indexInHistory; } protected: int _indexInHistory; }; class HistoryElem { public: HistoryElem() : _maxw(0), _minh(0), _height(0) { } int32 maxWidth() const { return _maxw; } int32 minHeight() const { return _minh; } int32 height() const { return _height; } virtual ~HistoryElem() { } protected: mutable int32 _maxw, _minh, _height; HistoryElem &operator=(const HistoryElem &); }; class HistoryMessage; enum HistoryCursorState { HistoryDefaultCursorState, HistoryInTextCursorState, HistoryInDateCursorState, HistoryInForwardedCursorState, }; struct HistoryTextState { HistoryTextState() = default; HistoryTextState(const Text::StateResult &state) : cursor(state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState) , link(state.link) , afterSymbol(state.afterSymbol) , symbol(state.symbol) { } HistoryTextState &operator=(const Text::StateResult &state) { cursor = state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState; link = state.link; afterSymbol = state.afterSymbol; symbol = state.symbol; return *this; } HistoryCursorState cursor = HistoryDefaultCursorState; ClickHandlerPtr link; bool afterSymbol = false; uint16 symbol = 0; }; struct HistoryStateRequest { Text::StateRequest::Flags flags = Text::StateRequest::Flag::LookupLink; Text::StateRequest forText() const { Text::StateRequest result; result.flags = flags; return result; } }; enum InfoDisplayType { InfoDisplayDefault, InfoDisplayOverImage, InfoDisplayOverBackground, }; enum HistoryItemType { HistoryItemMsg = 0, HistoryItemJoined }; struct HistoryMessageVia : public BaseComponent<HistoryMessageVia> { void create(int32 userId); void resize(int32 availw) const; UserData *_bot = nullptr; mutable QString _text; mutable int _width = 0; mutable int _maxWidth = 0; ClickHandlerPtr _lnk; }; struct HistoryMessageViews : public BaseComponent<HistoryMessageViews> { QString _viewsText; int _views = 0; int _viewsWidth = 0; }; struct HistoryMessageSigned : public BaseComponent<HistoryMessageSigned> { void create(UserData *from, const QDateTime &date); int maxWidth() const; Text _signature; }; struct HistoryMessageEdited : public BaseComponent<HistoryMessageEdited> { void create(const QDateTime &editDate, const QDateTime &date); int maxWidth() const; QDateTime _editDate; Text _edited; }; struct HistoryMessageForwarded : public BaseComponent<HistoryMessageForwarded> { void create(const HistoryMessageVia *via) const; PeerData *_authorOriginal = nullptr; PeerData *_fromOriginal = nullptr; MsgId _originalId = 0; mutable Text _text = { 1 }; }; struct HistoryMessageReply : public BaseComponent<HistoryMessageReply> { HistoryMessageReply &operator=(HistoryMessageReply &&other) { replyToMsgId = other.replyToMsgId; std::swap(replyToMsg, other.replyToMsg); replyToLnk = std_::move(other.replyToLnk); replyToName = std_::move(other.replyToName); replyToText = std_::move(other.replyToText); replyToVersion = other.replyToVersion; _maxReplyWidth = other._maxReplyWidth; _replyToVia = std_::move(other._replyToVia); return *this; } ~HistoryMessageReply() { // clearData() should be called by holder t_assert(replyToMsg == nullptr); t_assert(_replyToVia == nullptr); } bool updateData(HistoryMessage *holder, bool force = false); void clearData(HistoryMessage *holder); // must be called before destructor bool isNameUpdated() const; void updateName() const; void resize(int width) const; void itemRemoved(HistoryMessage *holder, HistoryItem *removed); enum PaintFlag { PaintInBubble = 0x01, PaintSelected = 0x02, }; Q_DECLARE_FLAGS(PaintFlags, PaintFlag); void paint(Painter &p, const HistoryItem *holder, int x, int y, int w, PaintFlags flags) const; MsgId replyToId() const { return replyToMsgId; } int replyToWidth() const { return _maxReplyWidth; } ClickHandlerPtr replyToLink() const { return replyToLnk; } MsgId replyToMsgId = 0; HistoryItem *replyToMsg = nullptr; ClickHandlerPtr replyToLnk; mutable Text replyToName, replyToText; mutable int replyToVersion = 0; mutable int _maxReplyWidth = 0; std_::unique_ptr<HistoryMessageVia> _replyToVia; int toWidth = 0; }; Q_DECLARE_OPERATORS_FOR_FLAGS(HistoryMessageReply::PaintFlags); class ReplyKeyboard; struct HistoryMessageReplyMarkup : public BaseComponent<HistoryMessageReplyMarkup> { HistoryMessageReplyMarkup() = default; HistoryMessageReplyMarkup(MTPDreplyKeyboardMarkup::Flags f) : flags(f) { } void create(const MTPReplyMarkup &markup); struct Button { enum Type { Default, Url, Callback, RequestPhone, RequestLocation, SwitchInline, }; Type type; QString text; QByteArray data; mutable mtpRequestId requestId; }; using ButtonRow = QVector<Button>; using ButtonRows = QVector<ButtonRow>; ButtonRows rows; MTPDreplyKeyboardMarkup::Flags flags = 0; std_::unique_ptr<ReplyKeyboard> inlineKeyboard; // If >= 0 it holds the y coord of the inlineKeyboard before the last edition. int oldTop = -1; private: void createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v); }; class ReplyMarkupClickHandler; class ReplyKeyboard { private: struct Button; public: class Style { public: Style(const style::botKeyboardButton &st) : _st(&st) { } virtual void startPaint(Painter &p) const = 0; virtual style::font textFont() const = 0; int buttonSkip() const { return _st->margin; } int buttonPadding() const { return _st->padding; } int buttonHeight() const { return _st->height; } virtual void repaint(const HistoryItem *item) const = 0; virtual ~Style() { } protected: virtual void paintButtonBg(Painter &p, const QRect &rect, bool pressed, float64 howMuchOver) const = 0; virtual void paintButtonIcon(Painter &p, const QRect &rect, HistoryMessageReplyMarkup::Button::Type type) const = 0; virtual void paintButtonLoading(Painter &p, const QRect &rect) const = 0; virtual int minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const = 0; private: const style::botKeyboardButton *_st; void paintButton(Painter &p, const ReplyKeyboard::Button &button) const; friend class ReplyKeyboard; }; typedef std_::unique_ptr<Style> StylePtr; ReplyKeyboard(const HistoryItem *item, StylePtr &&s); ReplyKeyboard(const ReplyKeyboard &other) = delete; ReplyKeyboard &operator=(const ReplyKeyboard &other) = delete; bool isEnoughSpace(int width, const style::botKeyboardButton &st) const; void setStyle(StylePtr &&s); void resize(int width, int height); // what width and height will best fit this keyboard int naturalWidth() const; int naturalHeight() const; void paint(Painter &p, const QRect &clip) const; ClickHandlerPtr getState(int x, int y) const; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active); void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed); void clearSelection(); void updateMessageId(); private: const HistoryItem *_item; int _width = 0; friend class Style; using ReplyMarkupClickHandlerPtr = QSharedPointer<ReplyMarkupClickHandler>; struct Button { Text text = { 1 }; QRect rect; int characters = 0; float64 howMuchOver = 0.; HistoryMessageReplyMarkup::Button::Type type; ReplyMarkupClickHandlerPtr link; }; using ButtonRow = QVector<Button>; using ButtonRows = QVector<ButtonRow>; ButtonRows _rows; using Animations = QMap<int, uint64>; Animations _animations; Animation _a_selected; void step_selected(uint64 ms, bool timer); StylePtr _st; }; class HistoryDependentItemCallback : public SharedCallback<void, ChannelData*, MsgId> { public: HistoryDependentItemCallback(FullMsgId dependent) : _dependent(dependent) { } void call(ChannelData *channel, MsgId msgId) const override; private: FullMsgId _dependent; }; // any HistoryItem can have this Interface for // displaying the day mark above the message struct HistoryMessageDate : public BaseComponent<HistoryMessageDate> { void init(const QDateTime &date); int height() const; void paint(Painter &p, int y, int w) const; QString _text; int _width = 0; }; // any HistoryItem can have this Interface for // displaying the unread messages bar above the message struct HistoryMessageUnreadBar : public BaseComponent<HistoryMessageUnreadBar> { void init(int count); static int height(); static int marginTop(); void paint(Painter &p, int y, int w) const; QString _text; int _width = 0; // if unread bar is freezed the new messages do not // increment the counter displayed by this bar // // it happens when we've opened the conversation and // we've seen the bar and new messages are marked as read // as soon as they are added to the chat history bool _freezed = false; }; // HistoryMedia has a special owning smart pointer // which regs/unregs this media to the holding HistoryItem class HistoryMedia; class HistoryMediaPtr { public: HistoryMediaPtr() = default; HistoryMediaPtr(const HistoryMediaPtr &other) = delete; HistoryMediaPtr &operator=(const HistoryMediaPtr &other) = delete; HistoryMedia *data() const { return _p; } void reset(HistoryMedia *p = nullptr); void clear() { reset(); } bool isNull() const { return data() == nullptr; } HistoryMedia *operator->() const { return data(); } HistoryMedia &operator*() const { t_assert(!isNull()); return *data(); } explicit operator bool() const { return !isNull(); } ~HistoryMediaPtr() { clear(); } private: HistoryMedia *_p = nullptr; }; namespace internal { TextSelection unshiftSelection(TextSelection selection, const Text &byText); TextSelection shiftSelection(TextSelection selection, const Text &byText); } // namespace internal class HistoryItem : public HistoryElem, public Composer, public ClickHandlerHost { public: HistoryItem(const HistoryItem &) = delete; HistoryItem &operator=(const HistoryItem &) = delete; int resizeGetHeight(int width) { if (_flags & MTPDmessage_ClientFlag::f_pending_init_dimensions) { _flags &= ~MTPDmessage_ClientFlag::f_pending_init_dimensions; initDimensions(); } if (_flags & MTPDmessage_ClientFlag::f_pending_resize) { _flags &= ~MTPDmessage_ClientFlag::f_pending_resize; } return resizeGetHeight_(width); } virtual void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const = 0; virtual void dependencyItemRemoved(HistoryItem *dependency) { } virtual bool updateDependencyItem() { return true; } virtual MsgId dependencyMsgId() const { return 0; } virtual bool notificationReady() const { return true; } UserData *viaBot() const { if (const HistoryMessageVia *via = Get<HistoryMessageVia>()) { return via->_bot; } return nullptr; } History *history() const { return _history; } PeerData *from() const { return _from; } HistoryBlock *block() { return _block; } const HistoryBlock *block() const { return _block; } void destroy(); void detach(); void detachFast(); bool detached() const { return !_block; } void attachToBlock(HistoryBlock *block, int index) { t_assert(_block == nullptr); t_assert(_indexInBlock < 0); t_assert(block != nullptr); t_assert(index >= 0); _block = block; _indexInBlock = index; if (pendingResize()) { _history->setHasPendingResizedItems(); } } void setIndexInBlock(int index) { t_assert(_block != nullptr); t_assert(index >= 0); _indexInBlock = index; } int indexInBlock() const { if (_indexInBlock >= 0) { t_assert(_block != nullptr); t_assert(_block->items.at(_indexInBlock) == this); } else if (_block != nullptr) { t_assert(_indexInBlock >= 0); t_assert(_block->items.at(_indexInBlock) == this); } return _indexInBlock; } bool out() const { return _flags & MTPDmessage::Flag::f_out; } bool unread() const; bool mentionsMe() const { return _flags & MTPDmessage::Flag::f_mentioned; } bool isMediaUnread() const { return (_flags & MTPDmessage::Flag::f_media_unread) && (channelId() == NoChannel); } void markMediaRead() { _flags &= ~MTPDmessage::Flag::f_media_unread; } bool definesReplyKeyboard() const { if (auto markup = Get<HistoryMessageReplyMarkup>()) { if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) { return false; } return true; } // optimization: don't create markup component for the case // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag return (_flags & MTPDmessage::Flag::f_reply_markup); } MTPDreplyKeyboardMarkup::Flags replyKeyboardFlags() const { t_assert(definesReplyKeyboard()); if (auto markup = Get<HistoryMessageReplyMarkup>()) { return markup->flags; } // optimization: don't create markup component for the case // MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag return qFlags(MTPDreplyKeyboardMarkup_ClientFlag::f_zero); } bool hasSwitchInlineButton() const { return _flags & MTPDmessage_ClientFlag::f_has_switch_inline_button; } bool hasTextLinks() const { return _flags & MTPDmessage_ClientFlag::f_has_text_links; } bool isGroupMigrate() const { return _flags & MTPDmessage_ClientFlag::f_is_group_migrate; } bool hasViews() const { return _flags & MTPDmessage::Flag::f_views; } bool isPost() const { return _flags & MTPDmessage::Flag::f_post; } bool indexInOverview() const { return (id > 0) && (!history()->isChannel() || history()->isMegagroup() || isPost()); } bool isSilent() const { return _flags & MTPDmessage::Flag::f_silent; } bool hasOutLayout() const { return out() && !isPost(); } virtual int32 viewsCount() const { return hasViews() ? 1 : -1; } virtual bool needCheck() const { return out() || (id < 0 && history()->peer->isSelf()); } virtual bool hasPoint(int x, int y) const { return false; } virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0; virtual TextSelection adjustSelection(TextSelection selection, TextSelectType type) const { return selection; } // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; virtual HistoryItemType type() const { return HistoryItemMsg; } virtual bool serviceMsg() const { return false; } virtual void applyEdition(const MTPDmessage &message) { } virtual void applyEditionToEmpty() { } virtual void updateMedia(const MTPMessageMedia *media) { } virtual int32 addToOverview(AddToOverviewMethod method) { return 0; } virtual void eraseFromOverview() { } virtual bool hasBubble() const { return false; } virtual void previousItemChanged(); virtual TextWithEntities selectedText(TextSelection selection) const { return { qsl("[-]"), EntitiesInText() }; } virtual QString notificationHeader() const { return QString(); } virtual QString notificationText() const; // Returns text with link-start and link-end commands for service-color highlighting. // Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text" virtual QString inDialogsText() const; virtual QString inReplyText() const { return notificationText(); } virtual TextWithEntities originalText() const { return { QString(), EntitiesInText() }; } virtual void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const { } virtual void setViewsCount(int32 count) { } virtual void setId(MsgId newId); void drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const; bool emptyText() const { return _text.isEmpty(); } bool canDelete() const { ChannelData *channel = _history->peer->asChannel(); if (!channel) return !(_flags & MTPDmessage_ClientFlag::f_is_group_migrate); if (id == 1) return false; if (channel->amCreator()) return true; if (isPost()) { if (channel->amEditor() && out()) return true; return false; } return (channel->amEditor() || channel->amModerator() || out()); } bool canPin() const { return id > 0 && _history->peer->isMegagroup() && (_history->peer->asChannel()->amEditor() || _history->peer->asChannel()->amCreator()) && toHistoryMessage(); } bool canEdit(const QDateTime &cur) const; bool suggestBanReportDeleteAll() const { ChannelData *channel = history()->peer->asChannel(); if (!channel || (!channel->amEditor() && !channel->amCreator())) return false; return !isPost() && !out() && from()->isUser() && toHistoryMessage(); } bool hasDirectLink() const { return id > 0 && _history->peer->isChannel() && _history->peer->asChannel()->isPublic() && !_history->peer->isMegagroup(); } QString directLink() const { return hasDirectLink() ? qsl("https://telegram.me/") + _history->peer->asChannel()->username + '/' + QString::number(id) : QString(); } int32 y; MsgId id; QDateTime date; ChannelId channelId() const { return _history->channelId(); } FullMsgId fullId() const { return FullMsgId(channelId(), id); } HistoryMedia *getMedia() const { return _media.data(); } virtual void setText(const TextWithEntities &textWithEntities) { } virtual bool textHasLinks() const { return false; } virtual int infoWidth() const { return 0; } virtual int timeLeft() const { return 0; } virtual int timeWidth() const { return 0; } virtual bool pointInTime(int32 right, int32 bottom, int x, int y, InfoDisplayType type) const { return false; } int32 skipBlockWidth() const { return st::msgDateSpace + infoWidth() - st::msgDateDelta.x(); } int32 skipBlockHeight() const { return st::msgDateFont->height - st::msgDateDelta.y(); } QString skipBlock() const { return textcmdSkipBlock(skipBlockWidth(), skipBlockHeight()); } virtual HistoryMessage *toHistoryMessage() { // dynamic_cast optimize return nullptr; } virtual const HistoryMessage *toHistoryMessage() const { // dynamic_cast optimize return nullptr; } MsgId replyToId() const { if (auto reply = Get<HistoryMessageReply>()) { return reply->replyToId(); } return 0; } bool hasFromName() const { return (!out() || isPost()) && !history()->peer->isUser(); } PeerData *author() const { return isPost() ? history()->peer : _from; } PeerData *fromOriginal() const { if (const HistoryMessageForwarded *fwd = Get<HistoryMessageForwarded>()) { return fwd->_fromOriginal; } return from(); } PeerData *authorOriginal() const { if (const HistoryMessageForwarded *fwd = Get<HistoryMessageForwarded>()) { return fwd->_authorOriginal; } return author(); } // count > 0 - creates the unread bar if necessary and // sets unread messages count if bar is not freezed yet // count <= 0 - destroys the unread bar void setUnreadBarCount(int count); void destroyUnreadBar(); // marks the unread bar as freezed so that unread // messages count will not change for this bar // when the new messages arrive in this chat history void setUnreadBarFreezed(); bool pendingResize() const { return _flags & MTPDmessage_ClientFlag::f_pending_resize; } void setPendingResize() { _flags |= MTPDmessage_ClientFlag::f_pending_resize; if (!detached()) { _history->setHasPendingResizedItems(); } } bool pendingInitDimensions() const { return _flags & MTPDmessage_ClientFlag::f_pending_init_dimensions; } void setPendingInitDimensions() { _flags |= MTPDmessage_ClientFlag::f_pending_init_dimensions; setPendingResize(); } int displayedDateHeight() const { if (auto date = Get<HistoryMessageDate>()) { return date->height(); } return 0; } int marginTop() const { int result = 0; if (isAttachedToPrevious()) { result += st::msgMarginTopAttached; } else { result += st::msgMargin.top(); } result += displayedDateHeight(); if (auto unreadbar = Get<HistoryMessageUnreadBar>()) { result += unreadbar->height(); } return result; } int marginBottom() const { return st::msgMargin.bottom(); } bool isAttachedToPrevious() const { return _flags & MTPDmessage_ClientFlag::f_attach_to_previous; } bool displayDate() const { return Has<HistoryMessageDate>(); } bool isInOneDayWithPrevious() const { return !isEmpty() && !displayDate(); } bool isEmpty() const { return _text.isEmpty() && !_media; } void clipCallback(Media::Clip::Notification notification); virtual ~HistoryItem(); protected: HistoryItem(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime msgDate, int32 from); // to completely create history item we need to call // a virtual method, it can not be done from constructor virtual void finishCreate(); // called from resizeGetHeight() when MTPDmessage_ClientFlag::f_pending_init_dimensions is set virtual void initDimensions() = 0; virtual int resizeGetHeight_(int width) = 0; void finishEdition(int oldKeyboardTop); void finishEditionToEmpty(); PeerData *_from; History *_history; HistoryBlock *_block = nullptr; int _indexInBlock = -1; MTPDmessage::Flags _flags; mutable int32 _authorNameVersion; HistoryItem *previousItem() const { if (_block && _indexInBlock >= 0) { if (_indexInBlock > 0) { return _block->items.at(_indexInBlock - 1); } if (auto previous = _block->previousBlock()) { t_assert(!previous->items.isEmpty()); return previous->items.back(); } } return nullptr; } HistoryItem *nextItem() const { if (_block && _indexInBlock >= 0) { if (_indexInBlock + 1 < _block->items.size()) { return _block->items.at(_indexInBlock + 1); } if (auto next = _block->nextBlock()) { t_assert(!next->items.isEmpty()); return next->items.front(); } } return nullptr; } // this should be used only in previousItemChanged() // to add required bits to the Composer mask // after that always use Has<HistoryMessageDate>() void recountDisplayDate(); // this should be used only in previousItemChanged() or when // HistoryMessageDate or HistoryMessageUnreadBar bit is changed in the Composer mask // then the result should be cached in a client side flag MTPDmessage_ClientFlag::f_attach_to_previous void recountAttachToPrevious(); const HistoryMessageReplyMarkup *inlineReplyMarkup() const { if (auto markup = Get<HistoryMessageReplyMarkup>()) { if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) { return markup; } } return nullptr; } const ReplyKeyboard *inlineReplyKeyboard() const { if (auto markup = inlineReplyMarkup()) { return markup->inlineKeyboard.get(); } return nullptr; } HistoryMessageReplyMarkup *inlineReplyMarkup() { return const_cast<HistoryMessageReplyMarkup*>(static_cast<const HistoryItem*>(this)->inlineReplyMarkup()); } ReplyKeyboard *inlineReplyKeyboard() { return const_cast<ReplyKeyboard*>(static_cast<const HistoryItem*>(this)->inlineReplyKeyboard()); } TextSelection toMediaSelection(TextSelection selection) const { return internal::unshiftSelection(selection, _text); } TextSelection fromMediaSelection(TextSelection selection) const { return internal::shiftSelection(selection, _text); } Text _text = { int(st::msgMinWidth) }; int _textWidth = -1; int _textHeight = 0; HistoryMediaPtr _media; }; // make all the constructors in HistoryItem children protected // and wrapped with a static create() call with the same args // so that history item can not be created directly, without // calling a virtual finishCreate() method template <typename T> class HistoryItemInstantiated { public: template <typename ... Args> static T *_create(Args ... args) { T *result = new T(args ...); result->finishCreate(); return result; } }; class MessageClickHandler : public LeftButtonClickHandler { public: MessageClickHandler(PeerId peer, MsgId msgid) : _peer(peer), _msgid(msgid) { } MessageClickHandler(HistoryItem *item) : _peer(item->history()->peer->id), _msgid(item->id) { } PeerId peer() const { return _peer; } MsgId msgid() const { return _msgid; } private: PeerId _peer; MsgId _msgid; }; class GoToMessageClickHandler : public MessageClickHandler { public: using MessageClickHandler::MessageClickHandler; protected: void onClickImpl() const override; }; class CommentsClickHandler : public MessageClickHandler { public: using MessageClickHandler::MessageClickHandler; protected: void onClickImpl() const override; }; class RadialAnimation { public: RadialAnimation(AnimationCallbacks &&callbacks); float64 opacity() const { return _opacity; } bool animating() const { return _animation.animating(); } void start(float64 prg); void update(float64 prg, bool finished, uint64 ms); void stop(); void step(uint64 ms); void step() { step(getms()); } void draw(Painter &p, const QRect &inner, int32 thickness, const style::color &color); private: uint64 _firstStart, _lastStart, _lastTime; float64 _opacity; anim::ivalue a_arcEnd, a_arcStart; Animation _animation; }; class HistoryMedia : public HistoryElem { public: HistoryMedia(HistoryItem *parent) : _parent(parent) { } HistoryMedia(const HistoryMedia &other) = delete; HistoryMedia &operator=(const HistoryMedia &other) = delete; virtual HistoryMediaType type() const = 0; virtual QString notificationText() const { return QString(); } // Returns text with link-start and link-end commands for service-color highlighting. // Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text" virtual QString inDialogsText() const; virtual TextWithEntities selectedText(TextSelection selection) const = 0; bool hasPoint(int x, int y) const { return (x >= 0 && y >= 0 && x < _width && y < _height); } virtual bool isDisplayed() const { return true; } virtual bool hasTextForCopy() const { return false; } virtual void initDimensions() = 0; virtual int resizeGetHeight(int width) { _width = qMin(width, _maxw); return _height; } virtual void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const = 0; virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0; // if we are in selecting items mode perhaps we want to // toggle selection instead of activating the pressed link virtual bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const = 0; // if we press and drag on this media should we drag the item virtual bool dragItem() const { return false; } virtual TextSelection adjustSelection(TextSelection selection, TextSelectType type) const { return selection; } // if we press and drag this link should we drag the item virtual bool dragItemByHandler(const ClickHandlerPtr &p) const = 0; virtual void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) { } virtual void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) { } virtual bool uploading() const { return false; } virtual HistoryMedia *clone(HistoryItem *newParent) const = 0; virtual DocumentData *getDocument() { return nullptr; } virtual Media::Clip::Reader *getClipReader() { return nullptr; } bool playInline(/*bool autoplay = false*/) { return playInline(false); } virtual bool playInline(bool autoplay) { return false; } virtual void stopInline() { } virtual void attachToParent() { } virtual void detachFromParent() { } virtual void updateSentMedia(const MTPMessageMedia &media) { } // After sending an inline result we may want to completely recreate // the media (all media that was generated on client side, for example) virtual bool needReSetInlineResultMedia(const MTPMessageMedia &media) { return true; } virtual bool animating() const { return false; } virtual bool hasReplyPreview() const { return false; } virtual ImagePtr replyPreview() { return ImagePtr(); } virtual TextWithEntities getCaption() const { return TextWithEntities(); } virtual bool needsBubble() const = 0; virtual bool customInfoLayout() const = 0; virtual QMargins bubbleMargins() const { return QMargins(); } virtual bool hideFromName() const { return false; } virtual bool hideForwardedFrom() const { return false; } int currentWidth() const { return _width; } protected: HistoryItem *_parent; int _width = 0; }; class HistoryFileMedia : public HistoryMedia { public: using HistoryMedia::HistoryMedia; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return p == _openl || p == _savel || p == _cancell; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return p == _openl || p == _savel || p == _cancell; } void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; ~HistoryFileMedia(); protected: ClickHandlerPtr _openl, _savel, _cancell; void setLinks(ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell); void setDocumentLinks(DocumentData *document, bool inlinegif = false) { ClickHandlerPtr open, save; if (inlinegif) { open.reset(new GifOpenClickHandler(document)); } else { open.reset(new DocumentOpenClickHandler(document)); } if (inlinegif) { save.reset(new GifOpenClickHandler(document)); } else if (document->voice()) { save.reset(new DocumentOpenClickHandler(document)); } else { save.reset(new DocumentSaveClickHandler(document)); } setLinks(std_::move(open), std_::move(save), MakeShared<DocumentCancelClickHandler>(document)); } // >= 0 will contain download / upload string, _statusSize = loaded bytes // < 0 will contain played string, _statusSize = -(seconds + 1) played // 0x7FFFFFF0 will contain status for not yet downloaded file // 0x7FFFFFF1 will contain status for already downloaded file // 0x7FFFFFF2 will contain status for failed to download / upload file mutable int32 _statusSize; mutable QString _statusText; // duration = -1 - no duration, duration = -2 - "GIF" duration void setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 realDuration) const; void step_thumbOver(float64 ms, bool timer); void step_radial(uint64 ms, bool timer); void ensureAnimation() const; void checkAnimationFinished(); bool isRadialAnimation(uint64 ms) const { if (!_animation || !_animation->radial.animating()) return false; _animation->radial.step(ms); return _animation && _animation->radial.animating(); } bool isThumbAnimation(uint64 ms) const { if (!_animation || !_animation->_a_thumbOver.animating()) return false; _animation->_a_thumbOver.step(ms); return _animation && _animation->_a_thumbOver.animating(); } virtual float64 dataProgress() const = 0; virtual bool dataFinished() const = 0; virtual bool dataLoaded() const = 0; struct AnimationData { AnimationData(AnimationCallbacks &&thumbOverCallbacks, AnimationCallbacks &&radialCallbacks) : a_thumbOver(0, 0) , _a_thumbOver(std_::move(thumbOverCallbacks)) , radial(std_::move(radialCallbacks)) { } anim::fvalue a_thumbOver; Animation _a_thumbOver; RadialAnimation radial; }; mutable AnimationData *_animation = nullptr; }; class HistoryPhoto : public HistoryFileMedia { public: HistoryPhoto(HistoryItem *parent, PhotoData *photo, const QString &caption); HistoryPhoto(HistoryItem *parent, PeerData *chat, const MTPDphoto &photo, int width); HistoryPhoto(HistoryItem *parent, const HistoryPhoto &other); void init(); HistoryMediaType type() const override { return MediaTypePhoto; } HistoryPhoto *clone(HistoryItem *newParent) const override { return new HistoryPhoto(newParent, *this); } void initDimensions() override; int resizeGetHeight(int width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { return _caption.adjustSelection(selection, type); } bool hasTextForCopy() const override { return !_caption.isEmpty(); } QString notificationText() const override; QString inDialogsText() const override; TextWithEntities selectedText(TextSelection selection) const override; PhotoData *photo() const { return _data; } void updateSentMedia(const MTPMessageMedia &media) override; bool needReSetInlineResultMedia(const MTPMessageMedia &media) override; void attachToParent() override; void detachFromParent() override; bool hasReplyPreview() const override { return !_data->thumb->isNull(); } ImagePtr replyPreview() override; TextWithEntities getCaption() const override { return _caption.originalTextWithEntities(); } bool needsBubble() const override { if (!_caption.isEmpty()) { return true; } if (_parent->viaBot()) { return true; } return (_parent->Has<HistoryMessageForwarded>() || _parent->Has<HistoryMessageReply>()); } bool customInfoLayout() const override { return _caption.isEmpty(); } bool hideFromName() const override { return true; } protected: float64 dataProgress() const override { return _data->progress(); } bool dataFinished() const override { return !_data->loading() && !_data->uploading(); } bool dataLoaded() const override { return _data->loaded(); } private: PhotoData *_data; int16 _pixw = 1; int16 _pixh = 1; Text _caption; }; class HistoryVideo : public HistoryFileMedia { public: HistoryVideo(HistoryItem *parent, DocumentData *document, const QString &caption); HistoryVideo(HistoryItem *parent, const HistoryVideo &other); HistoryMediaType type() const override { return MediaTypeVideo; } HistoryVideo *clone(HistoryItem *newParent) const override { return new HistoryVideo(newParent, *this); } void initDimensions() override; int resizeGetHeight(int width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { return _caption.adjustSelection(selection, type); } bool hasTextForCopy() const override { return !_caption.isEmpty(); } QString notificationText() const override; QString inDialogsText() const override; TextWithEntities selectedText(TextSelection selection) const override; DocumentData *getDocument() override { return _data; } bool uploading() const override { return _data->uploading(); } void attachToParent() override; void detachFromParent() override; bool needReSetInlineResultMedia(const MTPMessageMedia &media) override; bool hasReplyPreview() const override { return !_data->thumb->isNull(); } ImagePtr replyPreview() override; TextWithEntities getCaption() const override { return _caption.originalTextWithEntities(); } bool needsBubble() const override { if (!_caption.isEmpty()) { return true; } if (_parent->viaBot()) { return true; } return (_parent->Has<HistoryMessageForwarded>() || _parent->Has<HistoryMessageReply>()); } bool customInfoLayout() const override { return _caption.isEmpty(); } bool hideFromName() const override { return true; } protected: float64 dataProgress() const override { return _data->progress(); } bool dataFinished() const override { return !_data->loading() && !_data->uploading(); } bool dataLoaded() const override { return _data->loaded(); } private: DocumentData *_data; int32 _thumbw; Text _caption; void setStatusSize(int32 newSize) const; void updateStatusText() const; }; struct HistoryDocumentThumbed : public BaseComponent<HistoryDocumentThumbed> { ClickHandlerPtr _linksavel, _linkcancell; int _thumbw = 0; mutable int _linkw = 0; mutable QString _link; }; struct HistoryDocumentCaptioned : public BaseComponent<HistoryDocumentCaptioned> { Text _caption = { int(st::msgFileMinWidth) - st::msgPadding.left() - st::msgPadding.right() }; }; struct HistoryDocumentNamed : public BaseComponent<HistoryDocumentNamed> { QString _name; int _namew = 0; }; class HistoryDocument; struct HistoryDocumentVoicePlayback { HistoryDocumentVoicePlayback(const HistoryDocument *that); int32 _position; anim::fvalue a_progress; Animation _a_progress; }; struct HistoryDocumentVoice : public BaseComponent<HistoryDocumentVoice> { HistoryDocumentVoice &operator=(HistoryDocumentVoice &&other) { std::swap(_playback, other._playback); return *this; } ~HistoryDocumentVoice() { deleteAndMark(_playback); } void ensurePlayback(const HistoryDocument *interfaces) const; void checkPlaybackFinished() const; mutable HistoryDocumentVoicePlayback *_playback = nullptr; }; class HistoryDocument : public HistoryFileMedia, public Composer { public: HistoryDocument(HistoryItem *parent, DocumentData *document, const QString &caption); HistoryDocument(HistoryItem *parent, const HistoryDocument &other); HistoryMediaType type() const override { return _data->voice() ? MediaTypeVoiceFile : (_data->song() ? MediaTypeMusicFile : MediaTypeFile); } HistoryDocument *clone(HistoryItem *newParent) const override { return new HistoryDocument(newParent, *this); } void initDimensions() override; int resizeGetHeight(int width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { if (auto captioned = Get<HistoryDocumentCaptioned>()) { return captioned->_caption.adjustSelection(selection, type); } return selection; } bool hasTextForCopy() const override { return Has<HistoryDocumentCaptioned>(); } QString notificationText() const override; QString inDialogsText() const override; TextWithEntities selectedText(TextSelection selection) const override; bool uploading() const override { return _data->uploading(); } DocumentData *getDocument() override { return _data; } void attachToParent() override; void detachFromParent() override; void updateSentMedia(const MTPMessageMedia &media) override; bool needReSetInlineResultMedia(const MTPMessageMedia &media) override; bool hasReplyPreview() const override { return !_data->thumb->isNull(); } ImagePtr replyPreview() override; TextWithEntities getCaption() const override { if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) { return captioned->_caption.originalTextWithEntities(); } return TextWithEntities(); } bool needsBubble() const override { return true; } bool customInfoLayout() const override { return false; } QMargins bubbleMargins() const override { return Get<HistoryDocumentThumbed>() ? QMargins(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbPadding.left(), st::msgFileThumbPadding.bottom()) : st::msgPadding; } bool hideForwardedFrom() const override { return _data->song(); } void step_voiceProgress(float64 ms, bool timer); protected: float64 dataProgress() const override { return _data->progress(); } bool dataFinished() const override { return !_data->loading() && !_data->uploading(); } bool dataLoaded() const override { return _data->loaded(); } private: void createComponents(bool caption); void setStatusSize(int32 newSize, qint64 realDuration = 0) const; bool updateStatusText() const; // returns showPause // Callback is a void(const QString &, const QString &, const Text &) functor. // It will be called as callback(attachType, attachFileName, attachCaption). template <typename Callback> void buildStringRepresentation(Callback callback) const; DocumentData *_data; }; class HistoryGif : public HistoryFileMedia { public: HistoryGif(HistoryItem *parent, DocumentData *document, const QString &caption); HistoryGif(HistoryItem *parent, const HistoryGif &other); HistoryMediaType type() const override { return MediaTypeGif; } HistoryGif *clone(HistoryItem *newParent) const override { return new HistoryGif(newParent, *this); } void initDimensions() override; int resizeGetHeight(int width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { return _caption.adjustSelection(selection, type); } bool hasTextForCopy() const override { return !_caption.isEmpty(); } QString notificationText() const override; QString inDialogsText() const override; TextWithEntities selectedText(TextSelection selection) const override; bool uploading() const override { return _data->uploading(); } DocumentData *getDocument() override { return _data; } Media::Clip::Reader *getClipReader() override { return gif(); } bool playInline(bool autoplay) override; void stopInline() override; void attachToParent() override; void detachFromParent() override; void updateSentMedia(const MTPMessageMedia &media) override; bool needReSetInlineResultMedia(const MTPMessageMedia &media) override; bool hasReplyPreview() const override { return !_data->thumb->isNull(); } ImagePtr replyPreview() override; TextWithEntities getCaption() const override { return _caption.originalTextWithEntities(); } bool needsBubble() const override { if (!_caption.isEmpty()) { return true; } if (_parent->viaBot()) { return true; } return (_parent->Has<HistoryMessageForwarded>() || _parent->Has<HistoryMessageReply>()); } bool customInfoLayout() const override { return _caption.isEmpty(); } bool hideFromName() const override { return true; } ~HistoryGif(); protected: float64 dataProgress() const override; bool dataFinished() const override; bool dataLoaded() const override; private: DocumentData *_data; int32 _thumbw, _thumbh; Text _caption; Media::Clip::Reader *_gif; Media::Clip::Reader *gif() { return (_gif == Media::Clip::BadReader) ? nullptr : _gif; } const Media::Clip::Reader *gif() const { return (_gif == Media::Clip::BadReader) ? nullptr : _gif; } void setStatusSize(int32 newSize) const; void updateStatusText() const; }; class HistorySticker : public HistoryMedia { public: HistorySticker(HistoryItem *parent, DocumentData *document); HistoryMediaType type() const override { return MediaTypeSticker; } HistorySticker *clone(HistoryItem *newParent) const override { return new HistorySticker(newParent, _data); } void initDimensions() override; int resizeGetHeight(int width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return true; } bool dragItem() const override { return true; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return true; } QString notificationText() const override; TextWithEntities selectedText(TextSelection selection) const override; DocumentData *getDocument() override { return _data; } void attachToParent() override; void detachFromParent() override; void updateSentMedia(const MTPMessageMedia &media) override; bool needReSetInlineResultMedia(const MTPMessageMedia &media) override; bool needsBubble() const override { return false; } bool customInfoLayout() const override { return true; } private: int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const; int additionalWidth() const { return additionalWidth(_parent->Get<HistoryMessageVia>(), _parent->Get<HistoryMessageReply>()); } QString toString() const; int16 _pixw, _pixh; ClickHandlerPtr _packLink; DocumentData *_data; QString _emoji; }; class SendMessageClickHandler : public PeerClickHandler { public: using PeerClickHandler::PeerClickHandler; protected: void onClickImpl() const override; }; class AddContactClickHandler : public MessageClickHandler { public: using MessageClickHandler::MessageClickHandler; protected: void onClickImpl() const override; }; class HistoryContact : public HistoryMedia { public: HistoryContact(HistoryItem *parent, int32 userId, const QString &first, const QString &last, const QString &phone); HistoryMediaType type() const override { return MediaTypeContact; } HistoryContact *clone(HistoryItem *newParent) const override { return new HistoryContact(newParent, _userId, _fname, _lname, _phone); } void initDimensions() override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return true; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return true; } QString notificationText() const override; TextWithEntities selectedText(TextSelection selection) const override; void attachToParent() override; void detachFromParent() override; void updateSentMedia(const MTPMessageMedia &media) override; bool needsBubble() const override { return true; } bool customInfoLayout() const override { return false; } const QString &fname() const { return _fname; } const QString &lname() const { return _lname; } const QString &phone() const { return _phone; } private: int32 _userId; UserData *_contact; int32 _phonew; QString _fname, _lname, _phone; Text _name; ClickHandlerPtr _linkl; int32 _linkw; QString _link; }; class HistoryWebPage : public HistoryMedia { public: HistoryWebPage(HistoryItem *parent, WebPageData *data); HistoryWebPage(HistoryItem *parent, const HistoryWebPage &other); HistoryMediaType type() const override { return MediaTypeWebPage; } HistoryWebPage *clone(HistoryItem *newParent) const override { return new HistoryWebPage(newParent, *this); } void initDimensions() override; int resizeGetHeight(int width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; bool hasTextForCopy() const override { return false; // we do not add _title and _description in FullSelection text copy. } bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return _attach && _attach->toggleSelectionByHandlerClick(p); } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return _attach && _attach->dragItemByHandler(p); } TextWithEntities selectedText(TextSelection selection) const override; void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; bool isDisplayed() const override { return !_data->pendingTill; } DocumentData *getDocument() override { return _attach ? _attach->getDocument() : 0; } Media::Clip::Reader *getClipReader() override { return _attach ? _attach->getClipReader() : 0; } bool playInline(bool autoplay) override { return _attach ? _attach->playInline(autoplay) : false; } void stopInline() override { if (_attach) _attach->stopInline(); } void attachToParent() override; void detachFromParent() override; bool hasReplyPreview() const override { return (_data->photo && !_data->photo->thumb->isNull()) || (_data->document && !_data->document->thumb->isNull()); } ImagePtr replyPreview() override; WebPageData *webpage() { return _data; } bool needsBubble() const override { return true; } bool customInfoLayout() const override { return false; } HistoryMedia *attach() const { return _attach; } ~HistoryWebPage(); private: TextSelection toDescriptionSelection(TextSelection selection) const { return internal::unshiftSelection(selection, _title); } TextSelection fromDescriptionSelection(TextSelection selection) const { return internal::shiftSelection(selection, _title); } WebPageData *_data; ClickHandlerPtr _openl; HistoryMedia *_attach; bool _asArticle; int32 _titleLines, _descriptionLines; Text _title, _description; int32 _siteNameWidth; QString _duration; int32 _durationWidth; int16 _pixw, _pixh; }; void initImageLinkManager(); void reinitImageLinkManager(); void deinitImageLinkManager(); struct LocationCoords; struct LocationData; class LocationManager : public QObject { Q_OBJECT public: LocationManager() : manager(0), black(0) { } void init(); void reinit(); void deinit(); void getData(LocationData *data); ~LocationManager() { deinit(); } public slots: void onFinished(QNetworkReply *reply); void onFailed(QNetworkReply *reply); private: void failed(LocationData *data); QNetworkAccessManager *manager; QMap<QNetworkReply*, LocationData*> dataLoadings, imageLoadings; QMap<LocationData*, int32> serverRedirects; ImagePtr *black; }; class HistoryLocation : public HistoryMedia { public: HistoryLocation(HistoryItem *parent, const LocationCoords &coords, const QString &title = QString(), const QString &description = QString()); HistoryLocation(HistoryItem *parent, const HistoryLocation &other); HistoryMediaType type() const override { return MediaTypeLocation; } HistoryLocation *clone(HistoryItem *newParent) const override { return new HistoryLocation(newParent, *this); } void initDimensions() override; int resizeGetHeight(int32 width) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; bool hasTextForCopy() const override { return !_title.isEmpty() || !_description.isEmpty(); } bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override { return p == _link; } bool dragItemByHandler(const ClickHandlerPtr &p) const override { return p == _link; } QString notificationText() const override; QString inDialogsText() const override; TextWithEntities selectedText(TextSelection selection) const override; bool needsBubble() const override { if (!_title.isEmpty() || !_description.isEmpty()) { return true; } if (_parent->viaBot()) { return true; } return (_parent->Has<HistoryMessageForwarded>() || _parent->Has<HistoryMessageReply>()); } bool customInfoLayout() const override { return true; } private: TextSelection toDescriptionSelection(TextSelection selection) const { return internal::unshiftSelection(selection, _title); } TextSelection fromDescriptionSelection(TextSelection selection) const { return internal::shiftSelection(selection, _title); } LocationData *_data; Text _title, _description; ClickHandlerPtr _link; int32 fullWidth() const; int32 fullHeight() const; }; class ViaInlineBotClickHandler : public LeftButtonClickHandler { public: ViaInlineBotClickHandler(UserData *bot) : _bot(bot) { } protected: void onClickImpl() const override; private: UserData *_bot; }; class HistoryMessage : public HistoryItem, private HistoryItemInstantiated<HistoryMessage> { public: static HistoryMessage *create(History *history, const MTPDmessage &msg) { return _create(history, msg); } static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd) { return _create(history, msgId, flags, date, from, fwd); } static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities) { return _create(history, msgId, flags, replyTo, viaBotId, date, from, textWithEntities); } static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) { return _create(history, msgId, flags, replyTo, viaBotId, date, from, doc, caption, markup); } static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) { return _create(history, msgId, flags, replyTo, viaBotId, date, from, photo, caption, markup); } void initTime(); void initMedia(const MTPMessageMedia *media, QString ¤tText); void initMediaFromDocument(DocumentData *doc, const QString &caption); void fromNameUpdated(int32 width) const; int32 plainMaxWidth() const; void countPositionAndSize(int32 &left, int32 &width) const; bool drawBubble() const { return _media ? (!emptyText() || _media->needsBubble()) : !isEmpty(); } bool hasBubble() const override { return drawBubble(); } bool displayFromName() const { if (!hasFromName()) return false; if (isAttachedToPrevious()) return false; return (!emptyText() || !_media || !_media->isDisplayed() || Has<HistoryMessageReply>() || Has<HistoryMessageForwarded>() || viaBot() || !_media->hideFromName()); } bool displayEditedBadge(bool hasViaBot) const; bool uploading() const { return _media && _media->uploading(); } void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const override; void setViewsCount(int32 count) override; void setId(MsgId newId) override; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; void dependencyItemRemoved(HistoryItem *dependency) override; bool hasPoint(int x, int y) const override; bool pointInTime(int32 right, int32 bottom, int x, int y, InfoDisplayType type) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override { if (_media) _media->clickHandlerActiveChanged(p, active); HistoryItem::clickHandlerActiveChanged(p, active); } void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override { if (_media) _media->clickHandlerPressedChanged(p, pressed); HistoryItem::clickHandlerPressedChanged(p, pressed); } QString notificationHeader() const override; void applyEdition(const MTPDmessage &message) override; void applyEditionToEmpty() override; void updateMedia(const MTPMessageMedia *media) override; int32 addToOverview(AddToOverviewMethod method) override; void eraseFromOverview() override; TextWithEntities selectedText(TextSelection selection) const override; void setText(const TextWithEntities &textWithEntities) override; TextWithEntities originalText() const override; bool textHasLinks() const override; int32 infoWidth() const override { int32 result = _timeWidth; if (const HistoryMessageViews *views = Get<HistoryMessageViews>()) { result += st::msgDateViewsSpace + views->_viewsWidth + st::msgDateCheckSpace + st::msgViewsImg.pxWidth(); } else if (id < 0 && history()->peer->isSelf()) { result += st::msgDateCheckSpace + st::msgCheckImg.pxWidth(); } if (out() && !isPost()) { result += st::msgDateCheckSpace + st::msgCheckImg.pxWidth(); } return result; } int32 timeLeft() const override { int32 result = 0; if (const HistoryMessageViews *views = Get<HistoryMessageViews>()) { result += st::msgDateViewsSpace + views->_viewsWidth + st::msgDateCheckSpace + st::msgViewsImg.pxWidth(); } else if (id < 0 && history()->peer->isSelf()) { result += st::msgDateCheckSpace + st::msgCheckImg.pxWidth(); } return result; } int32 timeWidth() const override { return _timeWidth; } int32 viewsCount() const override { if (const HistoryMessageViews *views = Get<HistoryMessageViews>()) { return views->_views; } return HistoryItem::viewsCount(); } bool updateDependencyItem() override { if (auto reply = Get<HistoryMessageReply>()) { return reply->updateData(this, true); } return true; } MsgId dependencyMsgId() const override { return replyToId(); } HistoryMessage *toHistoryMessage() override { // dynamic_cast optimize return this; } const HistoryMessage *toHistoryMessage() const override { // dynamic_cast optimize return this; } // hasFromPhoto() returns true even if we don't display the photo // but we need to skip a place at the left side for this photo bool displayFromPhoto() const; bool hasFromPhoto() const; ~HistoryMessage(); private: HistoryMessage(History *history, const MTPDmessage &msg); HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd); // local forwarded HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities); // local message HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); // local document HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); // local photo friend class HistoryItemInstantiated<HistoryMessage>; void setEmptyText(); void initDimensions() override; int resizeGetHeight_(int width) override; int performResizeGetHeight(int width); bool displayForwardedFrom() const { if (const HistoryMessageForwarded *fwd = Get<HistoryMessageForwarded>()) { return Has<HistoryMessageVia>() || !_media || !_media->isDisplayed() || fwd->_authorOriginal->isChannel() || !_media->hideForwardedFrom(); } return false; } void paintForwardedInfo(Painter &p, QRect &trect, bool selected) const; void paintReplyInfo(Painter &p, QRect &trect, bool selected) const; // this method draws "via @bot" if it is not painted in forwarded info or in from name void paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const; void setMedia(const MTPMessageMedia *media); void setReplyMarkup(const MTPReplyMarkup *markup); QString _timeText; int _timeWidth = 0; struct CreateConfig { MsgId replyTo = 0; UserId viaBotId = 0; int viewsCount = -1; PeerId authorIdOriginal = 0; PeerId fromIdOriginal = 0; MsgId originalId = 0; QDateTime editDate; const MTPReplyMarkup *markup = nullptr; }; void createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, const MTPReplyMarkup &markup); void createComponents(const CreateConfig &config); class KeyboardStyle : public ReplyKeyboard::Style { public: using ReplyKeyboard::Style::Style; void startPaint(Painter &p) const override; style::font textFont() const override; void repaint(const HistoryItem *item) const override; protected: void paintButtonBg(Painter &p, const QRect &rect, bool down, float64 howMuchOver) const override; void paintButtonIcon(Painter &p, const QRect &rect, HistoryMessageReplyMarkup::Button::Type type) const override; void paintButtonLoading(Painter &p, const QRect &rect) const override; int minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const override; }; }; inline MTPDmessage::Flags newMessageFlags(PeerData *p) { MTPDmessage::Flags result = 0; if (!p->isSelf()) { result |= MTPDmessage::Flag::f_out; //if (p->isChat() || (p->isUser() && !p->asUser()->botInfo)) { // result |= MTPDmessage::Flag::f_unread; //} } return result; } struct HistoryServicePinned : public BaseComponent<HistoryServicePinned> { MsgId msgId = 0; HistoryItem *msg = nullptr; ClickHandlerPtr lnk; }; namespace HistoryLayout { class ServiceMessagePainter; } // namespace HistoryLayout class HistoryService : public HistoryItem, private HistoryItemInstantiated<HistoryService> { public: static HistoryService *create(History *history, const MTPDmessageService &msg) { return _create(history, msg); } static HistoryService *create(History *history, MsgId msgId, QDateTime date, const QString &msg, MTPDmessage::Flags flags = 0, int32 from = 0) { return _create(history, msgId, date, msg, flags, from); } bool updateDependencyItem() override { return updatePinned(true); } MsgId dependencyMsgId() const override { if (const HistoryServicePinned *pinned = Get<HistoryServicePinned>()) { return pinned->msgId; } return 0; } bool notificationReady() const override { if (const HistoryServicePinned *pinned = Get<HistoryServicePinned>()) { return (pinned->msg || !pinned->msgId); } return true; } void countPositionAndSize(int32 &left, int32 &width) const; void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override; bool hasPoint(int x, int y) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { return _text.adjustSelection(selection, type); } void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override { if (_media) _media->clickHandlerActiveChanged(p, active); HistoryItem::clickHandlerActiveChanged(p, active); } void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override { if (_media) _media->clickHandlerPressedChanged(p, pressed); HistoryItem::clickHandlerPressedChanged(p, pressed); } void applyEditionToEmpty() override; int32 addToOverview(AddToOverviewMethod method) override; void eraseFromOverview() override; bool needCheck() const override { return false; } bool serviceMsg() const override { return true; } TextWithEntities selectedText(TextSelection selection) const override; QString inDialogsText() const override; QString inReplyText() const override; void setServiceText(const QString &text); ~HistoryService(); protected: friend class HistoryLayout::ServiceMessagePainter; HistoryService(History *history, const MTPDmessageService &msg); HistoryService(History *history, MsgId msgId, QDateTime date, const QString &msg, MTPDmessage::Flags flags = 0, int32 from = 0); friend class HistoryItemInstantiated<HistoryService>; void initDimensions() override; int resizeGetHeight_(int width) override; void removeMedia(); void setMessageByAction(const MTPmessageAction &action); bool updatePinned(bool force = false); bool updatePinnedText(const QString *pfrom = nullptr, QString *ptext = nullptr); }; class HistoryJoined : public HistoryService, private HistoryItemInstantiated<HistoryJoined> { public: static HistoryJoined *create(History *history, const QDateTime &date, UserData *from, MTPDmessage::Flags flags) { return _create(history, date, from, flags); } HistoryItemType type() const { return HistoryItemJoined; } protected: HistoryJoined(History *history, const QDateTime &date, UserData *from, MTPDmessage::Flags flags); using HistoryItemInstantiated<HistoryJoined>::_create; friend class HistoryItemInstantiated<HistoryJoined>; };