tdesktop/Telegram/SourceFiles/history.h
John Preston cbde6e1ee9 Using square rounded photos in native notifications (Win, OS X).
When updating local online status from user action events we use
action timestamp, and ignoring events from getDifference, if no
timestamp is available for the specific action.
Couple of hidden actions added in Settings. Alpha version 0.9.54.
2016-06-22 21:47:43 +03:00

2870 lines
81 KiB
C++

/*
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(ClipReaderNotification 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 ClipReader *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;
}
ClipReader *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;
ClipReader *_gif;
ClipReader *gif() {
return (_gif == BadClipReader) ? nullptr : _gif;
}
const ClipReader *gif() const {
return (_gif == BadClipReader) ? 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;
}
ClipReader *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 &currentText);
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>;
};