mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-01-10 08:51:12 +00:00
0e0956451a
Scheme updated for bot callbacks sending data bytearrays. ReplyKeyboard edition is supported when editing the messages.
3197 lines
90 KiB
C++
3197 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, bool edited = false) {
|
|
}
|
|
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 ¤tText);
|
|
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, bool edited = false) override {
|
|
if (!edited && 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>;
|
|
|
|
};
|