tdesktop/Telegram/SourceFiles/history.h
John Preston 1ef944ed7b Not inline bot keyboard now supports editing as well.
Styles improved for not inline bot keyboard.
Full crash string adding to crash report.
Preparing to leave source code without #include "stdafx.h"
2016-04-01 19:32:26 +04:00

3196 lines
90 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"
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);
void step_typings(uint64 ms, bool timer);
History *find(const PeerId &peerId);
History *findOrInsert(const PeerId &peerId, int32 unreadCount, int32 maxInboxRead);
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);
}
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;
struct DialogRow {
DialogRow(History *history = 0, DialogRow *prev = 0, DialogRow *next = 0, int32 pos = 0) : prev(prev), next(next), history(history), pos(pos), attached(0) {
}
void paint(Painter &p, int32 w, bool act, bool sel, bool onlyBackground) const;
DialogRow *prev, *next;
History *history;
int32 pos;
void *attached; // for any attached data, for example View in contacts list
};
struct FakeDialogRow {
FakeDialogRow(HistoryItem *item) : _item(item), _cacheFor(0), _cache(st::dlgRichMinWidth) {
}
void paint(Painter &p, int32 w, bool act, bool sel, bool onlyBackground) const;
HistoryItem *_item;
mutable const HistoryItem *_cacheFor;
mutable Text _cache;
};
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,
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();
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;
};
struct HistoryDraft {
HistoryDraft() : msgId(0), previewCancelled(false) {
}
HistoryDraft(const QString &text, MsgId msgId, const MessageCursor &cursor, bool previewCancelled)
: text(text)
, msgId(msgId)
, cursor(cursor)
, previewCancelled(previewCancelled) {
}
HistoryDraft(const FlatTextarea &field, MsgId msgId, bool previewCancelled)
: text(field.getLastText())
, msgId(msgId)
, cursor(field)
, previewCancelled(previewCancelled) {
}
QString text;
MsgId msgId; // replyToId for message draft, editMsgId for edit draft
MessageCursor cursor;
bool previewCancelled;
};
struct HistoryEditDraft : public HistoryDraft {
HistoryEditDraft()
: HistoryDraft()
, saveRequest(0) {
}
HistoryEditDraft(const QString &text, MsgId msgId, const MessageCursor &cursor, bool previewCancelled, mtpRequestId saveRequest = 0)
: HistoryDraft(text, msgId, cursor, previewCancelled)
, saveRequest(saveRequest) {
}
HistoryEditDraft(const FlatTextarea &field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequest = 0)
: HistoryDraft(field, msgId, previewCancelled)
, saveRequest(saveRequest) {
}
mtpRequestId saveRequest;
};
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
};
struct DialogsIndexed;
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();
}
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);
HistoryItem *addNewPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption);
void addOlderSlice(const QVector<MTPMessage> &slice, const QVector<MTPMessageGroup> *collapsed);
void addNewerSlice(const QVector<MTPMessage> &slice, const QVector<MTPMessageGroup> *collapsed);
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;
void setUnreadCount(int newUnreadCount, bool psUpdate = true);
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, MsgId &fixInScrollMsgId, int32 &fixInScrollMsgTop); // has messages for showing history at msgId
void getReadyFor(MsgId msgId, MsgId &fixInScrollMsgId, int32 &fixInScrollMsgTop);
void setLastMessage(HistoryItem *msg);
void fixLastMessage(bool wasAtBottom);
typedef QMap<QChar, DialogRow*> ChatListLinksMap;
void setChatsListDate(const QDateTime &date);
QPair<int32, int32> adjustByPosInChatsList(DialogsIndexed &indexed);
uint64 sortKeyInChatList() const {
return _sortKeyInChatList;
}
bool inChatList() const {
return !_chatListLinks.isEmpty();
}
int32 posInChatList() const {
return mainChatListLink()->pos;
}
DialogRow *addToChatList(DialogsIndexed &indexed);
void removeFromChatList(DialogsIndexed &indexed);
void removeChatListEntryByLetter(QChar letter);
void addChatListEntryByLetter(QChar letter, DialogRow *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;
int32 width, height, msgCount, unreadCount;
int32 inboxReadBefore, outboxReadBefore;
HistoryItem *showFrom;
HistoryItem *unreadBar;
PeerData *peer;
bool oldLoaded, newLoaded;
HistoryItem *lastMsg;
QDateTime lastMsgDate;
typedef QList<HistoryItem*> NotifyQueue;
NotifyQueue notifies;
HistoryDraft *msgDraft;
HistoryEditDraft *editDraft;
HistoryDraft *draft() {
return editDraft ? editDraft : msgDraft;
}
void setMsgDraft(HistoryDraft *draft) {
if (msgDraft) delete msgDraft;
msgDraft = draft;
}
void setEditDraft(HistoryEditDraft *draft) {
if (editDraft) delete editDraft;
editDraft = draft;
}
// 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;
// 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;
int scrollTopOffset;
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 mute;
bool lastKeyboardInited, lastKeyboardUsed;
MsgId lastKeyboardId, lastKeyboardHiddenId;
PeerId lastKeyboardFrom;
mtpRequestId sendRequestId;
mutable const HistoryItem *textCachedFor; // 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);
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);
HistoryItem *createItemPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption);
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.
// If the previous item is a message group the new group is
// not created but is just united with the previous one.
// create(HistoryItem *previous) should return a new HistoryGroup*
// unite(HistoryGroup *existing) should unite a new group with an existing
template <typename CreateGroup, typename UniteGroup>
void addMessageGroup(CreateGroup create, UniteGroup unite);
void addMessageGroup(const MTPDmessageGroup &group);
// 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.isNull();
}
private:
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;
ChatListLinksMap _chatListLinks;
DialogRow *mainChatListLink() const {
auto it = _chatListLinks.constFind(0);
t_assert(it != _chatListLinks.cend());
return it.value();
}
uint64 _sortKeyInChatList; // 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;
};
UniquePointer<BuildingBlock> _buildingFrontBlock;
// Creates if necessary a new block for adding item.
// Depending on isBuildingFrontBlock() gets front or back block.
HistoryBlock *prepareBlockForAddingItem();
};
class HistoryGroup;
class HistoryCollapse;
class HistoryJoined;
class ChannelHistory : public History {
public:
ChannelHistory(const PeerId &peer);
void messageDetached(HistoryItem *msg);
void messageDeleted(HistoryItem *msg);
void messageWithIdDeleted(MsgId msgId);
bool isSwitchReadyFor(MsgId switchId, MsgId &fixInScrollMsgId, int32 &fixInScrollMsgTop); // has messages for showing history after switching mode at switchId
void getSwitchReadyFor(MsgId switchId, MsgId &fixInScrollMsgId, int32 &fixInScrollMsgTop);
void insertCollapseItem(MsgId wasMinId);
void getRangeDifference();
void getRangeDifferenceNext(int32 pts);
void addNewGroup(const MTPMessageGroup &group);
int32 unreadCountAll;
bool onlyImportant() const {
return _onlyImportant;
}
HistoryCollapse *collapse() const {
return _collapseMessage;
}
void clearOther() {
_otherNewLoaded = true;
_otherOldLoaded = false;
_otherList.clear();
}
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 addNewToOther(HistoryItem *item, NewMessageType type);
void checkMaxReadMessageDate();
HistoryGroup *findGroup(MsgId msgId) const;
HistoryBlock *findGroupBlock(MsgId msgId) const;
HistoryGroup *findGroupInOther(MsgId msgId) const;
HistoryItem *findPrevItem(HistoryItem *item) const;
void switchMode();
void cleared();
bool _onlyImportant;
QDateTime _maxReadMessageDate;
typedef QList<HistoryItem*> OtherList;
OtherList _otherList;
bool _otherOldLoaded, _otherNewLoaded;
HistoryCollapse *_collapseMessage;
HistoryJoined *_joinedMessage;
MsgId _rangeDifferenceFromId, _rangeDifferenceToId;
int32 _rangeDifferencePts;
mtpRequestId _rangeDifferenceRequestId;
};
enum DialogsSortMode {
DialogsSortByDate,
DialogsSortByName,
DialogsSortByAdd
};
struct DialogsList {
DialogsList(DialogsSortMode sortMode) : begin(&last), end(&last), sortMode(sortMode), count(0), current(&last) {
}
void adjustCurrent(int32 y, int32 h) const {
int32 pos = (y > 0) ? (y / h) : 0;
while (current->pos > pos && current != begin) {
current = current->prev;
}
while (current->pos + 1 <= pos && current->next != end) {
current = current->next;
}
}
void paint(Painter &p, int32 w, int32 hFrom, int32 hTo, PeerData *act, PeerData *sel, bool onlyBackground) const {
adjustCurrent(hFrom, st::dlgHeight);
DialogRow *drawFrom = current;
p.translate(0, drawFrom->pos * st::dlgHeight);
while (drawFrom != end && drawFrom->pos * st::dlgHeight < hTo) {
bool active = (drawFrom->history->peer == act) || (drawFrom->history->peer->migrateTo() && drawFrom->history->peer->migrateTo() == act);
bool selected = (drawFrom->history->peer == sel);
drawFrom->paint(p, w, active, selected, onlyBackground);
drawFrom = drawFrom->next;
p.translate(0, st::dlgHeight);
}
}
DialogRow *rowAtY(int32 y, int32 h) const {
if (!count) return 0;
int32 pos = (y > 0) ? (y / h) : 0;
adjustCurrent(y, h);
return (pos == current->pos) ? current : 0;
}
DialogRow *addToEnd(History *history) {
DialogRow *result = new DialogRow(history, end->prev, end, end->pos);
end->pos++;
if (begin == end) {
begin = current = result;
} else {
end->prev->next = result;
}
rowByPeer.insert(history->peer->id, result);
++count;
end->prev = result;
if (sortMode == DialogsSortByDate) {
adjustByPos(result);
}
return result;
}
bool insertBefore(DialogRow *row, DialogRow *before) {
if (row == before) return false;
if (current == row) current = row->prev;
DialogRow *updateTill = row->prev;
remove(row);
// insert row
row->next = before; // update row
row->prev = before->prev;
row->next->prev = row; // update row->next
if (row->prev) { // update row->prev
row->prev->next = row;
} else {
begin = row;
}
// update y
for (DialogRow *n = row; n != updateTill; n = n->next) {
n->next->pos++;
row->pos--;
}
return true;
}
bool insertAfter(DialogRow *row, DialogRow *after) {
if (row == after) return false;
if (current == row) current = row->next;
DialogRow *updateFrom = row->next;
remove(row);
// insert row
row->prev = after; // update row
row->next = after->next;
row->prev->next = row; // update row->prev
row->next->prev = row; // update row->next
// update y
for (DialogRow *n = updateFrom; n != row; n = n->next) {
n->pos--;
row->pos++;
}
return true;
}
DialogRow *adjustByName(const PeerData *peer) {
if (sortMode != DialogsSortByName) return 0;
RowByPeer::iterator i = rowByPeer.find(peer->id);
if (i == rowByPeer.cend()) return 0;
DialogRow *row = i.value(), *change = row;
while (change->prev && change->prev->history->peer->name > peer->name) {
change = change->prev;
}
if (!insertBefore(row, change)) {
while (change->next != end && change->next->history->peer->name < peer->name) {
change = change->next;
}
insertAfter(row, change);
}
return row;
}
DialogRow *addByName(History *history) {
if (sortMode != DialogsSortByName) return 0;
DialogRow *row = addToEnd(history), *change = row;
const QString &peerName(history->peer->name);
while (change->prev && change->prev->history->peer->name.compare(peerName, Qt::CaseInsensitive) > 0) {
change = change->prev;
}
if (!insertBefore(row, change)) {
while (change->next != end && change->next->history->peer->name.compare(peerName, Qt::CaseInsensitive) < 0) {
change = change->next;
}
insertAfter(row, change);
}
return row;
}
void adjustByPos(DialogRow *row) {
if (sortMode != DialogsSortByDate) return;
DialogRow *change = row;
if (change != begin && begin->history->sortKeyInChatList() < row->history->sortKeyInChatList()) {
change = begin;
} else while (change->prev && change->prev->history->sortKeyInChatList() < row->history->sortKeyInChatList()) {
change = change->prev;
}
if (!insertBefore(row, change)) {
if (change->next != end && end->prev->history->sortKeyInChatList() > row->history->sortKeyInChatList()) {
change = end->prev;
} else while (change->next != end && change->next->history->sortKeyInChatList() > row->history->sortKeyInChatList()) {
change = change->next;
}
insertAfter(row, change);
}
}
bool del(const PeerId &peerId, DialogRow *replacedBy = 0);
void remove(DialogRow *row) {
row->next->prev = row->prev; // update row->next
if (row->prev) { // update row->prev
row->prev->next = row->next;
} else {
begin = row->next;
}
}
void clear() {
while (begin != end) {
current = begin;
begin = begin->next;
delete current;
}
current = begin;
rowByPeer.clear();
count = 0;
}
~DialogsList() {
clear();
}
DialogRow last;
DialogRow *begin, *end;
DialogsSortMode sortMode;
int32 count;
typedef QHash<PeerId, DialogRow*> RowByPeer;
RowByPeer rowByPeer;
mutable DialogRow *current; // cache
};
struct DialogsIndexed {
DialogsIndexed(DialogsSortMode sortMode) : sortMode(sortMode), list(sortMode) {
}
History::ChatListLinksMap addToEnd(History *history) {
History::ChatListLinksMap result;
DialogsList::RowByPeer::const_iterator i = list.rowByPeer.find(history->peer->id);
if (i == list.rowByPeer.cend()) {
result.insert(0, list.addToEnd(history));
for (PeerData::NameFirstChars::const_iterator i = history->peer->chars.cbegin(), e = history->peer->chars.cend(); i != e; ++i) {
DialogsIndex::iterator j = index.find(*i);
if (j == index.cend()) {
j = index.insert(*i, new DialogsList(sortMode));
}
result.insert(*i, j.value()->addToEnd(history));
}
}
return result;
}
DialogRow *addByName(History *history) {
DialogsList::RowByPeer::const_iterator i = list.rowByPeer.constFind(history->peer->id);
if (i != list.rowByPeer.cend()) {
return i.value();
}
DialogRow *res = list.addByName(history);
for (PeerData::NameFirstChars::const_iterator i = history->peer->chars.cbegin(), e = history->peer->chars.cend(); i != e; ++i) {
DialogsIndex::iterator j = index.find(*i);
if (j == index.cend()) {
j = index.insert(*i, new DialogsList(sortMode));
}
j.value()->addByName(history);
}
return res;
}
void adjustByPos(const History::ChatListLinksMap &links) {
for (History::ChatListLinksMap::const_iterator i = links.cbegin(), e = links.cend(); i != e; ++i) {
if (i.key() == QChar(0)) {
list.adjustByPos(i.value());
} else {
DialogsIndex::iterator j = index.find(i.key());
if (j != index.cend()) {
j.value()->adjustByPos(i.value());
}
}
}
}
void peerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars);
void del(const PeerData *peer, DialogRow *replacedBy = 0) {
if (list.del(peer->id, replacedBy)) {
for (PeerData::NameFirstChars::const_iterator i = peer->chars.cbegin(), e = peer->chars.cend(); i != e; ++i) {
DialogsIndex::iterator j = index.find(*i);
if (j != index.cend()) {
j.value()->del(peer->id, replacedBy);
}
}
}
}
~DialogsIndexed() {
clear();
}
void clear();
DialogsSortMode sortMode;
DialogsList list;
typedef QMap<QChar, DialogsList*> DialogsIndex;
DialogsIndex index;
};
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 *previous() const {
t_assert(_indexInHistory >= 0);
return (_indexInHistory > 0) ? 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; // dynamic_cast optimize
enum HistoryCursorState {
HistoryDefaultCursorState,
HistoryInTextCursorState,
HistoryInDateCursorState,
HistoryInForwardedCursorState,
};
enum InfoDisplayType {
InfoDisplayDefault,
InfoDisplayOverImage,
InfoDisplayOverBackground,
};
inline bool isImportantChannelMessage(MsgId id, MTPDmessage::Flags flags) { // client-side important msgs always has_views or has_from_id
return (flags & MTPDmessage::Flag::f_out) || (flags & MTPDmessage::Flag::f_mentioned) || (flags & MTPDmessage::Flag::f_post);
}
enum HistoryItemType {
HistoryItemMsg = 0,
HistoryItemGroup,
HistoryItemCollapse,
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 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.data() == nullptr);
}
bool updateData(HistoryMessage *holder, bool force = false);
void clearData(HistoryMessage *holder); // must be called before destructor
void checkNameUpdate() 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;
UniquePointer<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,
};
Type type;
QString text;
QByteArray data;
};
using ButtonRow = QVector<Button>;
using ButtonRows = QVector<ButtonRow>;
ButtonRows rows;
MTPDreplyKeyboardMarkup::Flags flags = 0;
UniquePointer<ReplyKeyboard> inlineKeyboard;
};
class ReplyMarkupClickHandler : public LeftButtonClickHandler {
public:
ReplyMarkupClickHandler(const FullMsgId &msgId, int row, int col) : _msgId(msgId), _row(row), _col(col) {
}
QString tooltip() const override {
return _fullDisplayed ? QString() : text();
}
void setFullDisplayed(bool full) {
_fullDisplayed = full;
}
protected:
void onClickImpl() const override;
private:
FullMsgId _msgId;
int _row, _col;
bool _fullDisplayed = true;
// Finds the corresponding item and button in the items markup struct.
// If the item or the button is not found it returns false.
// Any of the two output arguments can be nullptr if its value is not needed.
bool getItemAndButton(
const HistoryItem **outItem,
const HistoryMessageReplyMarkup::Button **outButtonPointer) const;
// Returns the full text of the corresponding button.
QString text() const {
const HistoryMessageReplyMarkup::Button *button = nullptr;
if (getItemAndButton(nullptr, &button)) {
return button->text;
}
return QString();
}
};
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;
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 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 UniquePointer<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;
void getState(ClickHandlerPtr &lnk, int x, int y) const;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active);
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed);
void clearSelection();
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(HistoryItem *host, HistoryMedia *p = nullptr);
bool isNull() const {
return data() == nullptr;
}
void clear(HistoryItem *host) {
reset(host);
}
HistoryMedia *operator->() const {
return data();
}
HistoryMedia &operator*() const {
t_assert(!isNull());
return *data();
}
explicit operator bool() const {
return !isNull();
}
~HistoryMediaPtr() {
t_assert(isNull());
}
private:
HistoryMedia *_p = nullptr;
};
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, uint32 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;
}
virtual 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 {
if (out() && id > 0 && id < _history->outboxReadBefore) return false;
if (!out() && id > 0) {
if (id < _history->inboxReadBefore) return false;
if (channelId() != NoChannel) return true; // no unread flag for incoming messages in channels
}
if (history()->peer->isSelf()) return false; // messages from myself are always read
if (out() && history()->peer->migrateTo()) return false; // outgoing messages in converted chats are always read
return (_flags & MTPDmessage::Flag::f_unread);
}
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::Flag::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 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 isImportant() const {
return _history->isChannel() && isImportantChannelMessage(id, _flags);
}
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(int32 x, int32 y) const {
return false;
}
virtual void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const {
lnk.clear();
state = HistoryDefaultCursorState;
}
virtual void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const { // from text
upon = hasPoint(x, y);
symbol = upon ? 0xFFFF : 0;
after = false;
}
virtual uint32 adjustSelection(uint16 from, uint16 to, TextSelectType type) const {
return (from << 16) | to;
}
// 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 updateMedia(const MTPMessageMedia *media) {
}
virtual int32 addToOverview(AddToOverviewMethod method) {
return 0;
}
virtual bool hasBubble() const {
return false;
}
virtual void previousItemChanged();
virtual QString selectedText(uint32 selection) const {
return qsl("[-]");
}
virtual QString inDialogsText() const {
return qsl("-");
}
virtual QString inReplyText() const {
return inDialogsText();
}
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);
virtual void drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const = 0;
virtual QString notificationHeader() const {
return QString();
}
virtual QString notificationText() const = 0;
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);
}
virtual HistoryMedia *getMedia() const {
return nullptr;
}
virtual void setText(const QString &text, const EntitiesInText &links) {
}
virtual QString originalText() const {
return QString();
}
virtual EntitiesInText originalEntities() const {
return EntitiesInText();
}
virtual bool textHasLinks() {
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, int32 x, int32 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;
}
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;
PeerData *_from;
History *_history;
HistoryBlock *_block = nullptr;
int _indexInBlock = -1;
MTPDmessage::Flags _flags;
mutable int32 _authorNameVersion;
HistoryItem *previous() const {
if (_block && _indexInBlock >= 0) {
if (_indexInBlock > 0) {
return _block->items.at(_indexInBlock - 1);
}
if (HistoryBlock *previousBlock = _block->previous()) {
t_assert(!previousBlock->items.isEmpty());
return previousBlock->items.back();
}
}
return nullptr;
}
// this should be used only in previousItemChanged()
// to add required bits to the Composer mask
// after that always use Has<HistoryMessageDate>()
bool displayDate() const {
if (HistoryItem *prev = previous()) {
return prev->date.date().day() != date.date().day();
}
return true;
}
// 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::Flag::f_inline) {
return markup;
}
}
return nullptr;
}
const ReplyKeyboard *inlineReplyKeyboard() const {
if (auto *markup = inlineReplyMarkup()) {
return markup->inlineKeyboard.data();
}
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());
}
Text _text = { int(st::msgMinWidth) };
int32 _textWidth, _textHeight;
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(AnimationCreator creator);
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() : _width(0) {
}
HistoryMedia(const HistoryMedia &other) : _width(0) {
}
virtual HistoryMediaType type() const = 0;
virtual const QString inDialogsText() const = 0;
virtual const QString inHistoryText() const = 0;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent) const {
return (x >= 0 && y >= 0 && x < _width && y < _height);
}
virtual bool isDisplayed() const {
return true;
}
virtual void initDimensions(const HistoryItem *parent) = 0;
virtual int32 resize(int32 width, const HistoryItem *parent) { // return new height
_width = qMin(width, _maxw);
return _height;
}
virtual void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const = 0;
virtual void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) 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;
}
// if we press and drag this link should we drag the item
virtual bool dragItemByHandler(const ClickHandlerPtr &p) const = 0;
virtual void clickHandlerActiveChanged(HistoryItem *parent, const ClickHandlerPtr &p, bool active) {
}
virtual void clickHandlerPressedChanged(HistoryItem *parent, const ClickHandlerPtr &p, bool pressed) {
}
virtual bool uploading() const {
return false;
}
virtual HistoryMedia *clone() const = 0;
virtual DocumentData *getDocument() {
return 0;
}
virtual ClipReader *getClipReader() {
return 0;
}
bool playInline(HistoryItem *item/*, bool autoplay = false*/) {
return playInline(item, false);
}
virtual bool playInline(HistoryItem *item, bool autoplay) {
return false;
}
virtual void stopInline(HistoryItem *item) {
}
virtual void attachToItem(HistoryItem *item) {
}
virtual void detachFromItem(HistoryItem *item) {
}
virtual void updateFrom(const MTPMessageMedia &media, HistoryItem *parent) {
}
virtual bool isImageLink() const {
return false;
}
virtual bool animating() const {
return false;
}
virtual bool hasReplyPreview() const {
return false;
}
virtual ImagePtr replyPreview() {
return ImagePtr();
}
virtual QString getCaption() const {
return QString();
}
virtual bool needsBubble(const HistoryItem *parent) 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;
}
int32 currentWidth() const {
return _width;
}
protected:
int32 _width;
};
inline MediaOverviewType mediaToOverviewType(HistoryMedia *media) {
switch (media->type()) {
case MediaTypePhoto: return OverviewPhotos;
case MediaTypeVideo: return OverviewVideos;
case MediaTypeFile: return OverviewFiles;
case MediaTypeMusicFile: return media->getDocument()->isMusic() ? OverviewMusicFiles : OverviewFiles;
case MediaTypeVoiceFile: return OverviewVoiceFiles;
case MediaTypeGif: return media->getDocument()->isGifv() ? OverviewCount : OverviewFiles;
// case MediaTypeSticker: return OverviewFiles;
}
return OverviewCount;
}
class HistoryFileMedia : public HistoryMedia {
public:
HistoryFileMedia();
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(HistoryItem *parent, const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(HistoryItem *parent, 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(const HistoryItem *parent, float64 ms, bool timer);
void step_radial(const HistoryItem *parent, uint64 ms, bool timer);
void ensureAnimation(const HistoryItem *parent) 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(AnimationCreator thumbOverCallbacks, AnimationCreator radialCallbacks) : a_thumbOver(0, 0)
, _a_thumbOver(thumbOverCallbacks)
, radial(radialCallbacks) {
}
anim::fvalue a_thumbOver;
Animation _a_thumbOver;
RadialAnimation radial;
};
mutable AnimationData *_animation;
private:
HistoryFileMedia(const HistoryFileMedia &other);
};
class HistoryPhoto : public HistoryFileMedia {
public:
HistoryPhoto(PhotoData *photo, const QString &caption, const HistoryItem *parent);
HistoryPhoto(PeerData *chat, const MTPDphoto &photo, int32 width = 0);
HistoryPhoto(const HistoryPhoto &other);
void init();
HistoryMediaType type() const override {
return MediaTypePhoto;
}
HistoryMedia *clone() const override {
return new HistoryPhoto(*this);
}
void initDimensions(const HistoryItem *parent) override;
int32 resize(int32 width, const HistoryItem *parent) override;
void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const override;
void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const override;
const QString inDialogsText() const override;
const QString inHistoryText() const override;
PhotoData *photo() const {
return _data;
}
void updateFrom(const MTPMessageMedia &media, HistoryItem *parent) override;
void attachToItem(HistoryItem *item) override;
void detachFromItem(HistoryItem *item) override;
bool hasReplyPreview() const override {
return !_data->thumb->isNull();
}
ImagePtr replyPreview() override;
QString getCaption() const override {
return _caption.original();
}
bool needsBubble(const HistoryItem *parent) const override {
return !_caption.isEmpty() || parent->Has<HistoryMessageForwarded>() || parent->Has<HistoryMessageReply>() || parent->viaBot();
}
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(DocumentData *document, const QString &caption, const HistoryItem *parent);
HistoryVideo(const HistoryVideo &other);
HistoryMediaType type() const override {
return MediaTypeVideo;
}
HistoryMedia *clone() const override {
return new HistoryVideo(*this);
}
void initDimensions(const HistoryItem *parent) override;
int32 resize(int32 width, const HistoryItem *parent) override;
void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const override;
void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const override;
const QString inDialogsText() const override;
const QString inHistoryText() const override;
DocumentData *getDocument() override {
return _data;
}
bool uploading() const override {
return _data->uploading();
}
void attachToItem(HistoryItem *item) override;
void detachFromItem(HistoryItem *item) override;
bool hasReplyPreview() const override {
return !_data->thumb->isNull();
}
ImagePtr replyPreview() override;
QString getCaption() const override {
return _caption.original();
}
bool needsBubble(const HistoryItem *parent) const override {
return !_caption.isEmpty() || parent->Has<HistoryMessageForwarded>() || parent->Has<HistoryMessageReply>() || parent->viaBot();
}
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 HistoryItem *parent) 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 = { 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(DocumentData *document, const QString &caption, const HistoryItem *parent);
HistoryDocument(const HistoryDocument &other);
HistoryMediaType type() const override {
return _data->voice() ? MediaTypeVoiceFile : (_data->song() ? MediaTypeMusicFile : MediaTypeFile);
}
HistoryMedia *clone() const override {
return new HistoryDocument(*this);
}
void initDimensions(const HistoryItem *parent) override;
int32 resize(int32 width, const HistoryItem *parent) override;
void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const override;
void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const override;
const QString inDialogsText() const override;
const QString inHistoryText() const override;
bool uploading() const override {
return _data->uploading();
}
DocumentData *getDocument() override {
return _data;
}
void attachToItem(HistoryItem *item) override;
void detachFromItem(HistoryItem *item) override;
void updateFrom(const MTPMessageMedia &media, HistoryItem *parent) override;
bool hasReplyPreview() const override {
return !_data->thumb->isNull();
}
ImagePtr replyPreview() override;
QString getCaption() const override {
if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.original();
}
return QString();
}
bool needsBubble(const HistoryItem *parent) 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);
const HistoryItem *_parent;
DocumentData *_data;
void setStatusSize(int32 newSize, qint64 realDuration = 0) const;
bool updateStatusText(const HistoryItem *parent) const; // returns showPause
};
class HistoryGif : public HistoryFileMedia {
public:
HistoryGif(DocumentData *document, const QString &caption, const HistoryItem *parent);
HistoryGif(const HistoryGif &other);
HistoryMediaType type() const override {
return MediaTypeGif;
}
HistoryMedia *clone() const override {
return new HistoryGif(*this);
}
void initDimensions(const HistoryItem *parent) override;
int32 resize(int32 width, const HistoryItem *parent) override;
void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const override;
void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const override;
const QString inDialogsText() const override;
const QString inHistoryText() const override;
bool uploading() const override {
return _data->uploading();
}
DocumentData *getDocument() override {
return _data;
}
ClipReader *getClipReader() override {
return gif();
}
bool playInline(HistoryItem *item, bool autoplay) override;
void stopInline(HistoryItem *item) override;
void attachToItem(HistoryItem *item) override;
void detachFromItem(HistoryItem *item) override;
void updateFrom(const MTPMessageMedia &media, HistoryItem *parent) override;
bool hasReplyPreview() const override {
return !_data->thumb->isNull();
}
ImagePtr replyPreview() override;
QString getCaption() const override {
return _caption.original();
}
bool needsBubble(const HistoryItem *parent) const override {
return !_caption.isEmpty() || parent->Has<HistoryMessageForwarded>() || parent->Has<HistoryMessageReply>() || parent->viaBot();
}
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:
const HistoryItem *_parent;
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 HistoryItem *parent) const;
};
class HistorySticker : public HistoryMedia {
public:
HistorySticker(DocumentData *document);
HistoryMediaType type() const override {
return MediaTypeSticker;
}
HistoryMedia *clone() const override {
return new HistorySticker(*this);
}
void initDimensions(const HistoryItem *parent) override;
int32 resize(int32 width, const HistoryItem *parent) override;
void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const override;
void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) 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;
}
const QString inDialogsText() const override;
const QString inHistoryText() const override;
DocumentData *getDocument() override {
return _data;
}
void attachToItem(HistoryItem *item) override;
void detachFromItem(HistoryItem *item) override;
void updateFrom(const MTPMessageMedia &media, HistoryItem *parent) override;
bool needsBubble(const HistoryItem *parent) const override {
return false;
}
bool customInfoLayout() const override {
return true;
}
private:
int16 _pixw, _pixh;
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(int32 userId, const QString &first, const QString &last, const QString &phone);
HistoryMediaType type() const override {
return MediaTypeContact;
}
HistoryMedia *clone() const override {
return new HistoryContact(_userId, _fname, _lname, _phone);
}
void initDimensions(const HistoryItem *parent) override;
void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const override;
void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const override;
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
return true;
}
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
return true;
}
const QString inDialogsText() const override;
const QString inHistoryText() const override;
void attachToItem(HistoryItem *item) override;
void detachFromItem(HistoryItem *item) override;
void updateFrom(const MTPMessageMedia &media, HistoryItem *parent) override;
bool needsBubble(const HistoryItem *parent) 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(WebPageData *data);
HistoryWebPage(const HistoryWebPage &other);
HistoryMediaType type() const override {
return MediaTypeWebPage;
}
HistoryMedia *clone() const override {
return new HistoryWebPage(*this);
}
void initDimensions(const HistoryItem *parent) override;
int32 resize(int32 width, const HistoryItem *parent) override;
void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const override;
void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const override;
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
return _attach && _attach->toggleSelectionByHandlerClick(p);
}
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
return _attach && _attach->dragItemByHandler(p);
}
const QString inDialogsText() const override;
const QString inHistoryText() const override;
void clickHandlerActiveChanged(HistoryItem *parent, const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(HistoryItem *parent, 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(HistoryItem *item, bool autoplay) override {
return _attach ? _attach->playInline(item, autoplay) : false;
}
void stopInline(HistoryItem *item) override {
if (_attach) _attach->stopInline(item);
}
void attachToItem(HistoryItem *item) override;
void detachFromItem(HistoryItem *item) override;
bool hasReplyPreview() const override {
return (_data->photo && !_data->photo->thumb->isNull()) || (_data->doc && !_data->doc->thumb->isNull());
}
ImagePtr replyPreview() override;
WebPageData *webpage() {
return _data;
}
bool needsBubble(const HistoryItem *parent) const override {
return true;
}
bool customInfoLayout() const override {
return false;
}
HistoryMedia *attach() const {
return _attach;
}
~HistoryWebPage();
private:
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 LocationData {
LocationData(const LocationCoords &coords) : coords(coords), loading(false) {
}
LocationCoords coords;
ImagePtr thumb;
bool loading;
void load();
};
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(const LocationCoords &coords, const QString &title = QString(), const QString &description = QString());
HistoryMediaType type() const {
return MediaTypeLocation;
}
HistoryMedia *clone() const {
return new HistoryLocation(*this);
}
void initDimensions(const HistoryItem *parent);
int32 resize(int32 width, const HistoryItem *parent);
void draw(Painter &p, const HistoryItem *parent, const QRect &r, bool selected, uint64 ms) const;
void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y, const HistoryItem *parent) const;
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
return p == _link;
}
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
return p == _link;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool isImageLink() const {
return true;
}
bool needsBubble(const HistoryItem *parent) const {
return !_title.isEmpty() || !_description.isEmpty() || parent->Has<HistoryMessageForwarded>() || parent->Has<HistoryMessageReply>() || parent->viaBot();
}
bool customInfoLayout() const {
return true;
}
private:
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 QString &msg, const EntitiesInText &entities) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, msg, entities);
}
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, doc, caption);
}
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, photo, caption);
}
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 emptyText() const {
return _text.isEmpty();
}
bool drawBubble() const {
return _media ? (!emptyText() || _media->needsBubble(this)) : true;
}
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 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, uint32 selection, uint64 ms) const override;
void dependencyItemRemoved(HistoryItem *dependency) override;
void destroy() override;
bool hasPoint(int32 x, int32 y) const override;
bool pointInTime(int32 right, int32 bottom, int32 x, int32 y, InfoDisplayType type) const override;
void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const override;
void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const override;
uint32 adjustSelection(uint16 from, uint16 to, TextSelectType type) const override {
return _text.adjustSelection(from, to, type);
}
// ClickHandlerHost interface
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override {
if (_media) _media->clickHandlerActiveChanged(this, p, active);
HistoryItem::clickHandlerActiveChanged(p, active);
}
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override {
if (_media) _media->clickHandlerActiveChanged(this, p, pressed);
HistoryItem::clickHandlerPressedChanged(p, pressed);
}
void drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const override;
QString notificationHeader() const override;
QString notificationText() const override;
void applyEdition(const MTPDmessage &message) override;
void updateMedia(const MTPMessageMedia *media) override {
if (media && _media && _media->type() != MediaTypeWebPage) {
_media->updateFrom(*media, this);
} else {
setMedia(media);
}
setPendingInitDimensions();
}
int32 addToOverview(AddToOverviewMethod method) override;
void eraseFromOverview();
QString selectedText(uint32 selection) const override;
QString inDialogsText() const override;
HistoryMedia *getMedia() const override;
void setText(const QString &text, const EntitiesInText &entities) override;
QString originalText() const override;
EntitiesInText originalEntities() const override;
bool textHasLinks() 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 QString &msg, const EntitiesInText &entities); // local message
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption); // local document
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption); // local photo
friend class HistoryItemInstantiated<HistoryMessage>;
void initDimensions() override;
int resizeGetHeight_(int width) override;
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;
const MTPReplyMarkup *markup = nullptr;
};
void createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId);
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;
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;
}
inline MTPDmessage::Flags newForwardedFlags(PeerData *p, int32 from, HistoryMessage *fwd) {
MTPDmessage::Flags result = newMessageFlags(p);
if (from) {
result |= MTPDmessage::Flag::f_from_id;
}
if (fwd->Has<HistoryMessageVia>()) {
result |= MTPDmessage::Flag::f_via_bot_id;
}
if (!p->isChannel()) {
if (HistoryMedia *media = fwd->getMedia()) {
if (media->type() == MediaTypeVoiceFile) {
result |= MTPDmessage::Flag::f_media_unread;
// } else if (media->type() == MediaTypeVideo) {
// result |= MTPDmessage::flag_media_unread;
}
}
}
return result;
}
struct HistoryServicePinned : public BaseComponent<HistoryServicePinned> {
MsgId msgId = 0;
HistoryItem *msg = nullptr;
ClickHandlerPtr lnk;
};
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, uint32 selection, uint64 ms) const override;
bool hasPoint(int32 x, int32 y) const override;
void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const override;
void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const override;
uint32 adjustSelection(uint16 from, uint16 to, TextSelectType type) const override {
return _text.adjustSelection(from, to, type);
}
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override {
if (_media) _media->clickHandlerActiveChanged(this, p, active);
HistoryItem::clickHandlerActiveChanged(p, active);
}
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override {
if (_media) _media->clickHandlerPressedChanged(this, p, pressed);
HistoryItem::clickHandlerPressedChanged(p, pressed);
}
void drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const override;
QString notificationText() const override;
bool needCheck() const override {
return false;
}
bool serviceMsg() const override {
return true;
}
QString selectedText(uint32 selection) const override;
QString inDialogsText() const override;
QString inReplyText() const override;
HistoryMedia *getMedia() const override;
void setServiceText(const QString &text);
~HistoryService();
protected:
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 setMessageByAction(const MTPmessageAction &action);
bool updatePinned(bool force = false);
bool updatePinnedText(const QString *pfrom = nullptr, QString *ptext = nullptr);
};
class HistoryGroup : public HistoryService, private HistoryItemInstantiated<HistoryGroup> {
public:
static HistoryGroup *create(History *history, const MTPDmessageGroup &group, const QDateTime &date) {
return _create(history, group, date);
}
static HistoryGroup *create(History *history, HistoryItem *newItem, const QDateTime &date) {
return _create(history, newItem, date);
}
void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const;
void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const {
symbol = 0xFFFF;
after = false;
upon = false;
}
QString selectedText(uint32 selection) const {
return QString();
}
HistoryItemType type() const {
return HistoryItemGroup;
}
void uniteWith(MsgId minId, MsgId maxId, int32 count);
void uniteWith(HistoryItem *item) {
uniteWith(item->id - 1, item->id + 1, 1);
}
void uniteWith(HistoryGroup *other) {
uniteWith(other->_minId, other->_maxId, other->_count);
}
bool decrementCount(); // returns true if result count > 0
MsgId minId() const {
return _minId;
}
MsgId maxId() const {
return _maxId;
}
protected:
HistoryGroup(History *history, const MTPDmessageGroup &group, const QDateTime &date);
HistoryGroup(History *history, HistoryItem *newItem, const QDateTime &date);
using HistoryItemInstantiated<HistoryGroup>::_create;
friend class HistoryItemInstantiated<HistoryGroup>;
private:
MsgId _minId, _maxId;
int32 _count;
ClickHandlerPtr _lnk;
void updateText();
};
class HistoryCollapse : public HistoryService, private HistoryItemInstantiated<HistoryCollapse> {
public:
static HistoryCollapse *create(History *history, MsgId wasMinId, const QDateTime &date) {
return _create(history, wasMinId, date);
}
void draw(Painter &p, const QRect &r, uint32 selection, uint64 ms) const;
void getState(ClickHandlerPtr &lnk, HistoryCursorState &state, int32 x, int32 y) const;
void getSymbol(uint16 &symbol, bool &after, bool &upon, int32 x, int32 y) const {
symbol = 0xFFFF;
after = false;
upon = false;
}
QString selectedText(uint32 selection) const {
return QString();
}
HistoryItemType type() const {
return HistoryItemCollapse;
}
MsgId wasMinId() const {
return _wasMinId;
}
protected:
HistoryCollapse(History *history, MsgId wasMinId, const QDateTime &date);
using HistoryItemInstantiated<HistoryCollapse>::_create;
friend class HistoryItemInstantiated<HistoryCollapse>;
private:
MsgId _wasMinId;
};
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>;
};