tdesktop/Telegram/SourceFiles/history/history.h

599 lines
18 KiB
C
Raw Normal View History

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_types.h"
#include "data/data_peer.h"
#include "dialogs/dialogs_entry.h"
2016-12-01 19:20:33 +00:00
#include "ui/effects/send_action_animations.h"
2017-04-06 14:38:10 +00:00
#include "base/observer.h"
#include "base/timer.h"
#include "base/variant.h"
#include "base/flat_set.h"
2017-08-31 16:28:58 +00:00
#include "base/flags.h"
2016-12-01 19:20:33 +00:00
class History;
enum NewMessageType : char {
NewMessageUnread,
NewMessageLast,
NewMessageExisting,
};
namespace HistoryView {
class Element;
} // namespace HistoryView
2015-12-08 12:33:37 +00:00
class Histories {
2015-09-19 09:13:21 +00:00
public:
using Map = QHash<PeerId, History*>;
2015-09-19 09:13:21 +00:00
Map map;
Histories() : _a_typings(animation(this, &Histories::step_typings)) {
_selfDestructTimer.setCallback([this] { checkSelfDestructItems(); });
}
void registerSendAction(
not_null<History*> history,
not_null<UserData*> user,
const MTPSendMessageAction &action,
TimeId when);
2016-12-01 19:20:33 +00:00
void step_typings(TimeMs ms, bool timer);
2015-09-19 09:13:21 +00:00
History *find(const PeerId &peerId);
not_null<History*> findOrInsert(const PeerId &peerId);
not_null<History*> findOrInsert(
const PeerId &peerId,
int unreadCount,
MsgId maxInboxRead,
MsgId maxOutboxRead);
2015-09-19 09:13:21 +00:00
void clear();
void remove(const PeerId &peer);
2015-09-21 20:57:42 +00:00
HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type);
2016-12-01 19:20:33 +00:00
typedef QMap<History*, TimeMs> TypingHistories; // when typing in this history started
TypingHistories typing;
BasicAnimation _a_typings;
int unreadBadge() const;
int unreadMutedCount() const {
return _unreadMuted;
}
bool unreadOnlyMuted() const;
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;
}
}
struct SendActionAnimationUpdate {
History *history;
int width;
int height;
bool textUpdated;
};
base::Observable<SendActionAnimationUpdate> &sendActionAnimationUpdated() {
return _sendActionAnimationUpdated;
}
void selfDestructIn(not_null<HistoryItem*> item, TimeMs delay);
private:
void checkSelfDestructItems();
int _unreadFull = 0;
int _unreadMuted = 0;
base::Observable<SendActionAnimationUpdate> _sendActionAnimationUpdated;
base::Timer _selfDestructTimer;
std::vector<FullMsgId> _selfDestructItems;
};
2015-09-19 09:13:21 +00:00
class HistoryBlock;
namespace Data {
struct Draft;
} // namespace Data
2016-02-25 10:32:31 +00:00
class HistoryMedia;
class HistoryMessage;
2015-09-19 09:13:21 +00:00
enum class UnreadMentionType {
2017-12-08 18:27:28 +00:00
New, // when new message is added to history
Existing, // when some messages slice was received
};
namespace Dialogs {
class Row;
class IndexedList;
} // namespace Dialogs
2015-09-19 09:13:21 +00:00
class ChannelHistory;
class History : public Dialogs::Entry {
2015-09-19 09:13:21 +00:00
public:
History(const PeerId &peerId);
2016-03-22 09:51:20 +00:00
History(const History &) = delete;
History &operator=(const History &) = delete;
ChannelId channelId() const {
return peerToChannel(peer->id);
}
2015-09-19 09:13:21 +00:00
bool isChannel() const {
return peerIsChannel(peer->id);
}
2015-11-02 22:33:57 +00:00
bool isMegagroup() const {
return peer->isMegagroup();
}
2015-09-19 09:13:21 +00:00
ChannelHistory *asChannelHistory();
const ChannelHistory *asChannelHistory() const;
not_null<History*> migrateToOrMe() const;
History *migrateFrom() const;
2015-09-19 09:13:21 +00:00
bool isEmpty() const {
2017-09-27 18:45:26 +00:00
return blocks.empty();
2015-09-19 09:13:21 +00:00
}
bool isDisplayedEmpty() const;
bool hasOrphanMediaGroupPart() const;
bool removeOrphanMediaGroupPart();
void clear(bool leaveItems = false);
2017-11-20 19:54:05 +00:00
void clearUpTill(MsgId availableMinId);
void applyGroupAdminChanges(const base::flat_map<UserId, bool> &changes);
2015-09-21 20:57:42 +00:00
HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type);
HistoryItem *addToHistory(const MTPMessage &msg);
2017-12-13 18:10:48 +00:00
not_null<HistoryItem*> addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true);
not_null<HistoryItem*> addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item);
2018-01-18 13:59:22 +00:00
not_null<HistoryItem*> addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const TextWithEntities &caption, const MTPReplyMarkup &markup);
not_null<HistoryItem*> addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const TextWithEntities &caption, const MTPReplyMarkup &markup);
2017-12-13 18:10:48 +00:00
not_null<HistoryItem*> addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
// Used only internally and for channel admin log.
HistoryItem *createItem(
const MTPMessage &message,
bool detachExistingItem);
std::vector<not_null<HistoryItem*>> createItems(
const QVector<MTPMessage> &data);
void addOlderSlice(const QVector<MTPMessage> &slice);
void addNewerSlice(const QVector<MTPMessage> &slice);
void newItemAdded(not_null<HistoryItem*> item);
2016-03-22 15:23:34 +00:00
int countUnread(MsgId upTo);
2015-09-06 10:17:09 +00:00
MsgId inboxRead(MsgId upTo);
MsgId inboxRead(HistoryItem *wasRead);
MsgId outboxRead(MsgId upTo);
MsgId outboxRead(HistoryItem *wasRead);
int unreadCount() const {
return _unreadCount;
}
2016-04-14 19:24:42 +00:00
void setUnreadCount(int newUnreadCount);
bool mute() const {
return _mute;
}
2017-12-04 17:46:03 +00:00
bool changeMute(bool newMute);
void addUnreadBar();
void destroyUnreadBar();
bool hasNotFreezedUnreadBar() const;
HistoryView::Element *unreadBar() const;
void calculateFirstUnreadMessage();
void unsetFirstUnreadMessage();
HistoryView::Element *firstUnreadMessage() const;
void clearNotifications();
bool loadedAtBottom() const; // last message is in the list
2015-09-13 08:41:27 +00:00
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);
2015-09-21 20:57:42 +00:00
void setLastMessage(HistoryItem *msg);
void fixLastMessage(bool wasAtBottom);
MsgId minMsgId() const;
MsgId maxMsgId() const;
2015-09-06 10:17:09 +00:00
MsgId msgIdForRead() const;
void resizeToWidth(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();
2015-09-19 09:13:21 +00:00
void paintDialog(Painter &p, int32 w, bool sel) const;
2016-12-01 19:20:33 +00:00
bool mySendActionUpdated(SendAction::Type type, bool doing);
bool paintSendAction(Painter &p, int x, int y, int availableWidth, int outerWidth, style::color color, TimeMs ms);
2016-12-01 19:20:33 +00:00
// Interface for Histories
bool updateSendActionNeedsAnimating(TimeMs ms, bool force = false);
bool updateSendActionNeedsAnimating(
not_null<UserData*> user,
const MTPSendMessageAction &action);
2015-09-19 09:13:21 +00:00
void clearLastKeyboard();
int getUnreadMentionsLoadedCount() const {
return _unreadMentions.size();
}
MsgId getMinLoadedUnreadMention() const {
return _unreadMentions.empty() ? 0 : _unreadMentions.front();
}
MsgId getMaxLoadedUnreadMention() const {
return _unreadMentions.empty() ? 0 : _unreadMentions.back();
}
int getUnreadMentionsCount(int notLoadedValue = -1) const {
return _unreadMentionsCount ? *_unreadMentionsCount : notLoadedValue;
}
bool hasUnreadMentions() const {
return (getUnreadMentionsCount() > 0);
}
void setUnreadMentionsCount(int count);
bool addToUnreadMentions(MsgId msgId, UnreadMentionType type);
void eraseFromUnreadMentions(MsgId msgId);
void addUnreadMentionsSlice(const MTPmessages_Messages &result);
Data::Draft *localDraft() const {
return _localDraft.get();
}
Data::Draft *cloudDraft() const {
return _cloudDraft.get();
}
Data::Draft *editDraft() const {
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();
2016-02-25 10:32:31 +00:00
}
const MessageIdsList &forwardDraft() const {
return _forwardDraft;
}
HistoryItemsList validateForwardDraft();
void setForwardDraft(MessageIdsList &&items);
bool needUpdateInChatList() const override;
bool toImportant() const override {
return !mute();
}
int chatListUnreadCount() const override;
bool chatListMutedBadge() const override;
HistoryItem *chatsListItem() const override;
void loadUserpic() override;
void paintUserpic(
Painter &p,
int x,
int y,
int size) const override;
void forgetScrollState() {
scrollTopItem = nullptr;
}
// find the correct scrollTopItem and scrollTopOffset using given top
// of the displayed window relative to the history start coordinate
void countScrollState(int top);
virtual ~History();
// Still public data.
std::deque<std::unique_ptr<HistoryBlock>> blocks;
int height() const;
int32 msgCount = 0;
MsgId inboxReadBefore = 1;
MsgId outboxReadBefore = 1;
not_null<PeerData*> peer;
bool oldLoaded = false;
bool newLoaded = true;
HistoryItem *lastMsg = nullptr;
HistoryItem *lastSentMsg = nullptr;
typedef QList<HistoryItem*> NotifyQueue;
NotifyQueue notifies;
// 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
HistoryView::Element *scrollTopItem = nullptr;
int scrollTopOffset = 0;
bool lastKeyboardInited = false;
bool lastKeyboardUsed = false;
MsgId lastKeyboardId = 0;
MsgId lastKeyboardHiddenId = 0;
PeerId lastKeyboardFrom = 0;
mtpRequestId sendRequestId = 0;
Text cloudDraftTextCache;
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);
void clearOnDestroy();
HistoryItem *addNewToLastBlock(const MTPMessage &msg, NewMessageType type);
// this method just removes a block from the blocks list
// when the last item from this block was detached and
// calls the required previousItemChanged()
2017-12-13 18:10:48 +00:00
void removeBlock(not_null<HistoryBlock*> block);
void clearBlocks(bool leaveItems);
2017-12-13 18:10:48 +00:00
not_null<HistoryItem*> createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg);
2018-01-18 13:59:22 +00:00
not_null<HistoryItem*> createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const TextWithEntities &caption, const MTPReplyMarkup &markup);
not_null<HistoryItem*> createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const TextWithEntities &caption, const MTPReplyMarkup &markup);
2017-12-13 18:10:48 +00:00
not_null<HistoryItem*> createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
not_null<HistoryItem*> addNewItem(
not_null<HistoryItem*> item,
bool unread);
not_null<HistoryItem*> addNewInTheMiddle(
not_null<HistoryItem*> item,
int blockIndex,
int 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.
2017-12-13 18:10:48 +00:00
void addItemToBlock(not_null<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;
}
2015-09-19 09:13:21 +00:00
private:
friend class HistoryBlock;
enum class Flag {
f_has_pending_resized_items = (1 << 0),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) {
return true;
};
void mainViewRemoved(
not_null<HistoryBlock*> block,
not_null<HistoryView::Element*> view);
QDateTime adjustChatListDate() const override;
void removeDialog() override;
void changedInChatListHook(Dialogs::Mode list, bool added) override;
void changedChatListPinHook() override;
void applyMessageChanges(
not_null<HistoryItem*> item,
const MTPMessage &original);
void applyServiceChanges(
not_null<HistoryItem*> item,
const MTPDmessageService &data);
// After adding a new history slice check the lastMsg and newLoaded.
void checkLastMsg();
2017-12-08 18:27:28 +00:00
// Add all items to the unread mentions if we were not loaded at bottom and now are.
void checkAddAllToUnreadMentions();
template <int kSharedMediaTypeCount>
void addToSharedMedia(std::vector<MsgId> (&medias)[kSharedMediaTypeCount], bool force);
void addToSharedMedia(const std::vector<not_null<HistoryItem*>> &items);
void addEdgesToSharedMedia();
void addItemsToLists(const std::vector<not_null<HistoryItem*>> &items);
void clearSendAction(not_null<UserData*> from);
HistoryItem *lastAvailableMessage() const;
void getNextFirstUnreadMessage();
// Creates if necessary a new block for adding item.
// Depending on isBuildingFrontBlock() gets front or back block.
HistoryBlock *prepareBlockForAddingItem();
2017-08-31 16:28:58 +00:00
Flags _flags = 0;
bool _mute = false;
int _unreadCount = 0;
int _width = 0;
int _height = 0;
HistoryView::Element *_unreadBarView = nullptr;
HistoryView::Element *_firstUnreadView = nullptr;
base::optional<int> _unreadMentionsCount;
base::flat_set<MsgId> _unreadMentions;
// 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;
2015-09-19 09:13:21 +00:00
std::unique_ptr<Data::Draft> _localDraft, _cloudDraft;
std::unique_ptr<Data::Draft> _editDraft;
MessageIdsList _forwardDraft;
2016-12-01 19:20:33 +00:00
using TypingUsers = QMap<UserData*, TimeMs>;
TypingUsers _typing;
using SendActionUsers = QMap<UserData*, SendAction>;
SendActionUsers _sendActions;
QString _sendActionString;
Text _sendActionText;
Ui::SendActionAnimation _sendActionAnimation;
QMap<SendAction::Type, TimeMs> _mySendActions;
int _pinnedIndex = 0; // > 0 for pinned dialogs
2016-12-01 19:20:33 +00:00
2016-03-22 09:51:20 +00:00
};
2015-09-19 09:13:21 +00:00
2015-09-21 20:57:42 +00:00
class HistoryJoined;
2015-09-19 09:13:21 +00:00
class ChannelHistory : public History {
public:
using History::History;
2015-09-19 09:13:21 +00:00
void messageDetached(not_null<HistoryItem*> message);
2015-09-19 09:13:21 +00:00
2015-09-21 20:57:42 +00:00
void getRangeDifference();
void getRangeDifferenceNext(int32 pts);
HistoryJoined *insertJoinedMessage(bool unread);
2015-09-25 07:47:32 +00:00
void checkJoinedMessage(bool createUnread = false);
2015-09-21 20:57:42 +00:00
const QDateTime &maxReadMessageDate();
~ChannelHistory();
2015-09-19 09:13:21 +00:00
private:
friend class History;
2015-09-21 20:57:42 +00:00
void checkMaxReadMessageDate();
2016-04-14 19:24:42 +00:00
void cleared(bool leaveItems);
2015-09-21 20:57:42 +00:00
QDateTime _maxReadMessageDate;
HistoryJoined *_joinedMessage = nullptr;
2015-09-19 09:13:21 +00:00
2015-09-21 20:57:42 +00:00
MsgId _rangeDifferenceFromId, _rangeDifferenceToId;
int32 _rangeDifferencePts;
mtpRequestId _rangeDifferenceRequestId;
2015-07-03 08:47:16 +00:00
};
2015-09-19 09:13:21 +00:00
class HistoryBlock {
public:
using Element = HistoryView::Element;
HistoryBlock(not_null<History*> history);
HistoryBlock(const HistoryBlock &) = delete;
HistoryBlock &operator=(const HistoryBlock &) = delete;
~HistoryBlock();
std::vector<std::unique_ptr<Element>> messages;
2015-09-19 09:13:21 +00:00
void clear(bool leaveItems = false);
void remove(not_null<Element*> view);
2018-01-18 13:59:22 +00:00
void refreshView(not_null<Element*> view);
int resizeGetHeight(int newWidth, bool resizeAllItems);
int y() const {
return _y;
}
void setY(int y) {
_y = y;
}
int height() const {
return _height;
}
not_null<History*> history() const {
return _history;
}
HistoryBlock *previousBlock() const {
Expects(_indexInHistory >= 0);
return (_indexInHistory > 0)
? _history->blocks[_indexInHistory - 1].get()
: nullptr;
}
HistoryBlock *nextBlock() const {
Expects(_indexInHistory >= 0);
return (_indexInHistory + 1 < _history->blocks.size())
? _history->blocks[_indexInHistory + 1].get()
: nullptr;
}
void setIndexInHistory(int index) {
_indexInHistory = index;
}
int indexInHistory() const {
Expects(_indexInHistory >= 0);
Expects(_indexInHistory < _history->blocks.size());
Expects(_history->blocks[_indexInHistory].get() == this);
return _indexInHistory;
}
protected:
const not_null<History*> _history;
int _y = 0;
int _height = 0;
int _indexInHistory = -1;
};