tdesktop/Telegram/SourceFiles/history.cpp

2280 lines
69 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-2017 John Preston, https://desktop.telegram.org
*/
#include "history.h"
#include "history/history_media_types.h"
#include "dialogs/dialogs_indexed_list.h"
#include "styles/style_dialogs.h"
#include "data/data_drafts.h"
#include "lang.h"
#include "apiwrap.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "storage/localstorage.h"
#include "window/top_bar_widget.h"
#include "observer_peer.h"
#include "auth_session.h"
#include "window/notifications_manager.h"
namespace {
constexpr int kStatusShowClientsideTyping = 6000;
constexpr int kStatusShowClientsideRecordVideo = 6000;
constexpr int kStatusShowClientsideUploadVideo = 6000;
constexpr int kStatusShowClientsideRecordVoice = 6000;
constexpr int kStatusShowClientsideUploadVoice = 6000;
constexpr int kStatusShowClientsideUploadPhoto = 6000;
constexpr int kStatusShowClientsideUploadFile = 6000;
constexpr int kStatusShowClientsideChooseLocation = 6000;
constexpr int kStatusShowClientsideChooseContact = 6000;
constexpr int kStatusShowClientsidePlayGame = 10000;
constexpr int kSetMyActionForMs = 10000;
auto GlobalPinnedIndex = 0;
HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from) {
QString text(lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org")));
EntitiesInText entities;
textParseEntities(text, _historyTextNoMonoOptions.flags, &entities);
entities.push_front(EntityInText(EntityInTextItalic, 0, text.size()));
return HistoryMessage::create(history, msgId, flags, replyTo, viaBotId, date, from, { text, entities });
}
} // namespace
void historyInit() {
historyInitMessages();
historyInitMedia();
}
History::History(const PeerId &peerId)
: peer(App::peer(peerId))
, lastItemTextCache(st::dialogsTextWidthMin)
, cloudDraftTextCache(st::dialogsTextWidthMin)
, _mute(isNotifyMuted(peer->notify))
, _sendActionText(st::dialogsTextWidthMin) {
if (peer->isUser() && peer->asUser()->botInfo) {
outboxReadBefore = INT_MAX;
}
for (auto &countData : overviewCountData) {
countData = -1; // not loaded yet
}
}
void History::clearLastKeyboard() {
if (lastKeyboardId) {
if (lastKeyboardId == lastKeyboardHiddenId) {
lastKeyboardHiddenId = 0;
}
lastKeyboardId = 0;
if (auto main = App::main()) {
main->updateBotKeyboard(this);
}
}
lastKeyboardInited = true;
lastKeyboardFrom = 0;
}
bool History::canHaveFromPhotos() const {
if (peer->isUser() && !Adaptive::ChatWide()) {
return false;
} else if (isChannel() && !peer->isMegagroup()) {
return false;
}
return true;
}
void History::setHasPendingResizedItems() {
_flags |= Flag::f_has_pending_resized_items;
Global::RefHandleHistoryUpdate().call();
}
void History::setLocalDraft(std::unique_ptr<Data::Draft> &&draft) {
_localDraft = std::move(draft);
}
void History::takeLocalDraft(History *from) {
if (auto &draft = from->_localDraft) {
if (!draft->textWithTags.text.isEmpty() && !_localDraft) {
_localDraft = std::move(draft);
// Edit and reply to drafts can't migrate.
// Cloud drafts do not migrate automatically.
_localDraft->msgId = 0;
}
from->clearLocalDraft();
App::api()->saveDraftToCloudDelayed(from);
}
}
void History::createLocalDraftFromCloud() {
auto draft = cloudDraft();
if (Data::draftIsNull(draft) || !draft->date.isValid()) return;
auto existing = localDraft();
if (Data::draftIsNull(existing) || !existing->date.isValid() || draft->date >= existing->date) {
if (!existing) {
setLocalDraft(std::make_unique<Data::Draft>(draft->textWithTags, draft->msgId, draft->cursor, draft->previewCancelled));
existing = localDraft();
} else if (existing != draft) {
existing->textWithTags = draft->textWithTags;
existing->msgId = draft->msgId;
existing->cursor = draft->cursor;
existing->previewCancelled = draft->previewCancelled;
}
existing->date = draft->date;
}
}
void History::setCloudDraft(std::unique_ptr<Data::Draft> &&draft) {
_cloudDraft = std::move(draft);
cloudDraftTextCache.clear();
}
Data::Draft *History::createCloudDraft(Data::Draft *fromDraft) {
if (Data::draftIsNull(fromDraft)) {
setCloudDraft(std::make_unique<Data::Draft>(TextWithTags(), 0, MessageCursor(), false));
cloudDraft()->date = QDateTime();
} else {
auto existing = cloudDraft();
if (!existing) {
setCloudDraft(std::make_unique<Data::Draft>(fromDraft->textWithTags, fromDraft->msgId, fromDraft->cursor, fromDraft->previewCancelled));
existing = cloudDraft();
} else if (existing != fromDraft) {
existing->textWithTags = fromDraft->textWithTags;
existing->msgId = fromDraft->msgId;
existing->cursor = fromDraft->cursor;
existing->previewCancelled = fromDraft->previewCancelled;
}
existing->date = ::date(myunixtime());
}
cloudDraftTextCache.clear();
updateChatListSortPosition();
return cloudDraft();
}
void History::setEditDraft(std::unique_ptr<Data::Draft> &&draft) {
_editDraft = std::move(draft);
}
void History::clearLocalDraft() {
_localDraft = nullptr;
}
void History::clearCloudDraft() {
if (_cloudDraft) {
_cloudDraft = nullptr;
cloudDraftTextCache.clear();
updateChatListSortPosition();
}
}
void History::clearEditDraft() {
_editDraft = nullptr;
}
void History::draftSavedToCloud() {
updateChatListEntry();
if (App::main()) App::main()->writeDrafts(this);
}
bool History::updateSendActionNeedsAnimating(UserData *user, const MTPSendMessageAction &action) {
using Type = SendAction::Type;
if (action.type() == mtpc_sendMessageCancelAction) {
unregSendAction(user);
return false;
}
auto ms = getms();
switch (action.type()) {
case mtpc_sendMessageTypingAction: _typing.insert(user, ms + kStatusShowClientsideTyping); break;
case mtpc_sendMessageRecordVideoAction: _sendActions.insert(user, { Type::RecordVideo, ms + kStatusShowClientsideRecordVideo }); break;
case mtpc_sendMessageUploadVideoAction: _sendActions.insert(user, { Type::UploadVideo, ms + kStatusShowClientsideUploadVideo, action.c_sendMessageUploadVideoAction().vprogress.v }); break;
case mtpc_sendMessageRecordAudioAction: _sendActions.insert(user, { Type::RecordVoice, ms + kStatusShowClientsideRecordVoice }); break;
case mtpc_sendMessageUploadAudioAction: _sendActions.insert(user, { Type::UploadVoice, ms + kStatusShowClientsideUploadVoice, action.c_sendMessageUploadAudioAction().vprogress.v }); break;
case mtpc_sendMessageUploadPhotoAction: _sendActions.insert(user, { Type::UploadPhoto, ms + kStatusShowClientsideUploadPhoto, action.c_sendMessageUploadPhotoAction().vprogress.v }); break;
case mtpc_sendMessageUploadDocumentAction: _sendActions.insert(user, { Type::UploadFile, ms + kStatusShowClientsideUploadFile, action.c_sendMessageUploadDocumentAction().vprogress.v }); break;
case mtpc_sendMessageGeoLocationAction: _sendActions.insert(user, { Type::ChooseLocation, ms + kStatusShowClientsideChooseLocation }); break;
case mtpc_sendMessageChooseContactAction: _sendActions.insert(user, { Type::ChooseContact, ms + kStatusShowClientsideChooseContact }); break;
case mtpc_sendMessageGamePlayAction: {
auto it = _sendActions.find(user);
if (it == _sendActions.end() || it->type == Type::PlayGame || it->until <= ms) {
_sendActions.insert(user, { Type::PlayGame, ms + kStatusShowClientsidePlayGame });
}
} break;
default: return false;
}
return updateSendActionNeedsAnimating(ms, true);
}
bool History::mySendActionUpdated(SendAction::Type type, bool doing) {
auto ms = getms(true);
auto i = _mySendActions.find(type);
if (doing) {
if (i == _mySendActions.cend()) {
_mySendActions.insert(type, ms + kSetMyActionForMs);
} else if (i.value() > ms + (kSetMyActionForMs / 2)) {
return false;
} else {
i.value() = ms + kSetMyActionForMs;
}
} else {
if (i == _mySendActions.cend()) {
return false;
} else if (i.value() <= ms) {
return false;
} else {
_mySendActions.erase(i);
}
}
return true;
}
bool History::paintSendAction(Painter &p, int x, int y, int availableWidth, int outerWidth, style::color color, TimeMs ms) {
if (_sendActionAnimation) {
_sendActionAnimation.paint(p, color, x, y + st::normalFont->ascent, outerWidth, ms);
auto animationWidth = _sendActionAnimation.width();
x += animationWidth;
availableWidth -= animationWidth;
p.setPen(color);
_sendActionText.drawElided(p, x, y, availableWidth);
return true;
}
return false;
}
bool History::updateSendActionNeedsAnimating(TimeMs ms, bool force) {
auto changed = force;
for (auto i = _typing.begin(), e = _typing.end(); i != e;) {
if (ms >= i.value()) {
i = _typing.erase(i);
changed = true;
} else {
++i;
}
}
for (auto i = _sendActions.begin(); i != _sendActions.cend();) {
if (ms >= i.value().until) {
i = _sendActions.erase(i);
changed = true;
} else {
++i;
}
}
if (changed) {
QString newTypingString;
auto typingCount = _typing.size();
if (typingCount > 2) {
newTypingString = lng_many_typing(lt_count, typingCount);
} else if (typingCount > 1) {
newTypingString = lng_users_typing(lt_user, _typing.begin().key()->firstName, lt_second_user, (_typing.end() - 1).key()->firstName);
} else if (typingCount) {
newTypingString = peer->isUser() ? lang(lng_typing) : lng_user_typing(lt_user, _typing.begin().key()->firstName);
} else if (!_sendActions.isEmpty()) {
// Handles all actions except game playing.
using Type = SendAction::Type;
auto sendActionString = [](Type type, const QString &name) -> QString {
switch (type) {
case Type::RecordVideo: return name.isEmpty() ? lang(lng_send_action_record_video) : lng_user_action_record_video(lt_user, name);
case Type::UploadVideo: return name.isEmpty() ? lang(lng_send_action_upload_video) : lng_user_action_upload_video(lt_user, name);
case Type::RecordVoice: return name.isEmpty() ? lang(lng_send_action_record_audio) : lng_user_action_record_audio(lt_user, name);
case Type::UploadVoice: return name.isEmpty() ? lang(lng_send_action_upload_audio) : lng_user_action_upload_audio(lt_user, name);
case Type::UploadPhoto: return name.isEmpty() ? lang(lng_send_action_upload_photo) : lng_user_action_upload_photo(lt_user, name);
case Type::UploadFile: return name.isEmpty() ? lang(lng_send_action_upload_file) : lng_user_action_upload_file(lt_user, name);
case Type::ChooseLocation: return name.isEmpty() ? lang(lng_send_action_geo_location) : lng_user_action_geo_location(lt_user, name);
case Type::ChooseContact: return name.isEmpty() ? lang(lng_send_action_choose_contact) : lng_user_action_choose_contact(lt_user, name);
default: break;
};
return QString();
};
for (auto i = _sendActions.cbegin(), e = _sendActions.cend(); i != e; ++i) {
newTypingString = sendActionString(i->type, peer->isUser() ? QString() : i.key()->firstName);
if (!newTypingString.isEmpty()) {
_sendActionAnimation.start(i->type);
break;
}
}
// Everyone in sendActions are playing a game.
if (newTypingString.isEmpty()) {
int playingCount = _sendActions.size();
if (playingCount > 2) {
newTypingString = lng_many_playing_game(lt_count, playingCount);
} else if (playingCount > 1) {
newTypingString = lng_users_playing_game(lt_user, _sendActions.begin().key()->firstName, lt_second_user, (_sendActions.end() - 1).key()->firstName);
} else {
newTypingString = peer->isUser() ? lang(lng_playing_game) : lng_user_playing_game(lt_user, _sendActions.begin().key()->firstName);
}
_sendActionAnimation.start(Type::PlayGame);
}
}
if (typingCount > 0) {
_sendActionAnimation.start(SendAction::Type::Typing);
} else if (newTypingString.isEmpty()) {
_sendActionAnimation.stop();
}
if (_sendActionString != newTypingString) {
_sendActionString = newTypingString;
_sendActionText.setText(st::dialogsTextStyle, _sendActionString, _textNameOptions);
}
}
auto result = (!_typing.isEmpty() || !_sendActions.isEmpty());
if (changed || result) {
App::histories().sendActionAnimationUpdated().notify({
this,
_sendActionAnimation.width(),
st::normalFont->height,
changed
});
}
return result;
}
void ChannelHistory::getRangeDifference() {
auto fromId = MsgId(0), toId = MsgId(0);
for (auto blockIndex = 0, blocksCount = blocks.size(); blockIndex < blocksCount; ++blockIndex) {
auto block = blocks.at(blockIndex);
for (auto itemIndex = 0, itemsCount = block->items.size(); itemIndex < itemsCount; ++itemIndex) {
auto item = block->items.at(itemIndex);
if (item->id > 0) {
fromId = item->id;
break;
}
}
if (fromId) break;
}
if (!fromId) return;
for (auto blockIndex = blocks.size(); blockIndex > 0;) {
auto block = blocks.at(--blockIndex);
for (auto itemIndex = block->items.size(); itemIndex > 0;) {
auto item = block->items.at(--itemIndex);
if (item->id > 0) {
toId = item->id;
break;
}
}
if (toId) break;
}
if (fromId > 0 && peer->asChannel()->pts() > 0) {
if (_rangeDifferenceRequestId) {
MTP::cancel(_rangeDifferenceRequestId);
}
_rangeDifferenceFromId = fromId;
_rangeDifferenceToId = toId;
MTP_LOG(0, ("getChannelDifference { good - after channelDifferenceTooLong was received, validating history part }%1").arg(cTestMode() ? " TESTMODE" : ""));
getRangeDifferenceNext(peer->asChannel()->pts());
}
}
void ChannelHistory::getRangeDifferenceNext(int32 pts) {
if (!App::main() || _rangeDifferenceToId < _rangeDifferenceFromId) return;
int limit = _rangeDifferenceToId + 1 - _rangeDifferenceFromId;
auto filter = MTP_channelMessagesFilter(MTP_flags(0), MTP_vector<MTPMessageRange>(1, MTP_messageRange(MTP_int(_rangeDifferenceFromId), MTP_int(_rangeDifferenceToId))));
auto flags = MTPupdates_GetChannelDifference::Flag::f_force;
_rangeDifferenceRequestId = MTP::send(MTPupdates_GetChannelDifference(MTP_flags(flags), peer->asChannel()->inputChannel, filter, MTP_int(pts), MTP_int(limit)), App::main()->rpcDone(&MainWidget::gotRangeDifference, peer->asChannel()));
}
HistoryJoined *ChannelHistory::insertJoinedMessage(bool unread) {
if (_joinedMessage || !peer->asChannel()->amIn() || (peer->isMegagroup() && peer->asChannel()->mgInfo->joinedMessageFound)) {
return _joinedMessage;
}
UserData *inviter = (peer->asChannel()->inviter > 0) ? App::userLoaded(peer->asChannel()->inviter) : nullptr;
if (!inviter) return nullptr;
MTPDmessage::Flags flags = 0;
if (inviter->id == AuthSession::CurrentUserPeerId()) {
unread = false;
//} else if (unread) {
// flags |= MTPDmessage::Flag::f_unread;
}
QDateTime inviteDate = peer->asChannel()->inviteDate;
if (unread) _maxReadMessageDate = inviteDate;
if (isEmpty()) {
_joinedMessage = HistoryJoined::create(this, inviteDate, inviter, flags);
addNewItem(_joinedMessage, unread);
return _joinedMessage;
}
for (auto blockIndex = blocks.size(); blockIndex > 0;) {
auto block = blocks.at(--blockIndex);
for (auto itemIndex = block->items.size(); itemIndex > 0;) {
auto item = block->items.at(--itemIndex);
// Due to a server bug sometimes inviteDate is less (before) than the
// first message in the megagroup (message about migration), let us
// ignore that and think, that the inviteDate is always greater-or-equal.
if (item->isGroupMigrate() && peer->isMegagroup() && peer->migrateFrom()) {
peer->asChannel()->mgInfo->joinedMessageFound = true;
return nullptr;
}
if (item->date <= inviteDate) {
++itemIndex;
_joinedMessage = HistoryJoined::create(this, inviteDate, inviter, flags);
addNewInTheMiddle(_joinedMessage, blockIndex, itemIndex);
if (lastMsgDate.isNull() || inviteDate >= lastMsgDate) {
setLastMessage(_joinedMessage);
if (unread) {
newItemAdded(_joinedMessage);
}
}
return _joinedMessage;
}
}
}
startBuildingFrontBlock();
_joinedMessage = HistoryJoined::create(this, inviteDate, inviter, flags);
addItemToBlock(_joinedMessage);
finishBuildingFrontBlock();
return _joinedMessage;
}
void ChannelHistory::checkJoinedMessage(bool createUnread) {
if (_joinedMessage || peer->asChannel()->inviter <= 0) {
return;
}
if (isEmpty()) {
if (loadedAtTop() && loadedAtBottom()) {
if (insertJoinedMessage(createUnread)) {
if (!_joinedMessage->detached()) {
setLastMessage(_joinedMessage);
}
}
return;
}
}
QDateTime inviteDate = peer->asChannel()->inviteDate;
QDateTime firstDate, lastDate;
if (!blocks.isEmpty()) {
firstDate = blocks.front()->items.front()->date;
lastDate = blocks.back()->items.back()->date;
}
if (!firstDate.isNull() && !lastDate.isNull() && (firstDate <= inviteDate || loadedAtTop()) && (lastDate > inviteDate || loadedAtBottom())) {
bool willBeLastMsg = (inviteDate >= lastDate);
if (insertJoinedMessage(createUnread && willBeLastMsg) && willBeLastMsg) {
if (!_joinedMessage->detached()) {
setLastMessage(_joinedMessage);
}
}
}
}
void ChannelHistory::checkMaxReadMessageDate() {
if (_maxReadMessageDate.isValid()) return;
for (int blockIndex = blocks.size(); blockIndex > 0;) {
HistoryBlock *block = blocks.at(--blockIndex);
for (int itemIndex = block->items.size(); itemIndex > 0;) {
HistoryItem *item = block->items.at(--itemIndex);
if (!item->unread()) {
_maxReadMessageDate = item->date;
if (item->isGroupMigrate() && isMegagroup() && peer->migrateFrom()) {
_maxReadMessageDate = date(MTP_int(peer->asChannel()->date + 1)); // no report spam panel
}
return;
}
}
}
if (loadedAtTop() && (!isMegagroup() || !isEmpty())) {
_maxReadMessageDate = date(MTP_int(peer->asChannel()->date));
}
}
const QDateTime &ChannelHistory::maxReadMessageDate() {
return _maxReadMessageDate;
}
HistoryItem *ChannelHistory::addNewChannelMessage(const MTPMessage &msg, NewMessageType type) {
if (type == NewMessageExisting) return addToHistory(msg);
return addNewToBlocks(msg, type);
}
HistoryItem *ChannelHistory::addNewToBlocks(const MTPMessage &msg, NewMessageType type) {
if (!loadedAtBottom()) {
HistoryItem *item = addToHistory(msg);
if (item) {
setLastMessage(item);
if (type == NewMessageUnread) {
newItemAdded(item);
}
}
return item;
}
return addNewToLastBlock(msg, type);
}
void ChannelHistory::cleared(bool leaveItems) {
_joinedMessage = nullptr;
}
void ChannelHistory::messageDetached(HistoryItem *msg) {
if (_joinedMessage == msg) {
_joinedMessage = nullptr;
}
}
ChannelHistory::~ChannelHistory() {
// all items must be destroyed before ChannelHistory is destroyed
// or they will call history()->asChannelHistory() -> undefined behaviour
clearOnDestroy();
}
History *Histories::find(const PeerId &peerId) {
Map::const_iterator i = map.constFind(peerId);
return (i == map.cend()) ? 0 : i.value();
}
History *Histories::findOrInsert(const PeerId &peerId) {
auto i = map.constFind(peerId);
if (i == map.cend()) {
auto history = peerIsChannel(peerId) ? static_cast<History*>(new ChannelHistory(peerId)) : (new History(peerId));
i = map.insert(peerId, history);
}
return i.value();
}
History *Histories::findOrInsert(const PeerId &peerId, int32 unreadCount, int32 maxInboxRead, int32 maxOutboxRead) {
auto i = map.constFind(peerId);
if (i == map.cend()) {
auto history = peerIsChannel(peerId) ? static_cast<History*>(new ChannelHistory(peerId)) : (new History(peerId));
i = map.insert(peerId, history);
history->setUnreadCount(unreadCount);
history->inboxReadBefore = maxInboxRead + 1;
history->outboxReadBefore = maxOutboxRead + 1;
} else {
auto history = i.value();
if (unreadCount > history->unreadCount()) {
history->setUnreadCount(unreadCount);
}
accumulate_max(history->inboxReadBefore, maxInboxRead + 1);
accumulate_max(history->outboxReadBefore, maxOutboxRead + 1);
}
return i.value();
}
void Histories::clear() {
App::historyClearMsgs();
_pinnedDialogs.clear();
auto temp = base::take(map);
for_const (auto history, temp) {
delete history;
}
_unreadFull = _unreadMuted = 0;
Notify::unreadCounterUpdated();
App::historyClearItems();
typing.clear();
}
void Histories::regSendAction(History *history, UserData *user, const MTPSendMessageAction &action, TimeId when) {
if (history->updateSendActionNeedsAnimating(user, action)) {
user->madeAction(when);
auto i = typing.find(history);
if (i == typing.cend()) {
typing.insert(history, getms());
_a_typings.start();
}
}
}
void Histories::step_typings(TimeMs ms, bool timer) {
for (auto i = typing.begin(), e = typing.end(); i != e;) {
if (i.key()->updateSendActionNeedsAnimating(ms)) {
++i;
} else {
i = typing.erase(i);
}
}
if (typing.isEmpty()) {
_a_typings.stop();
}
}
void Histories::remove(const PeerId &peer) {
Map::iterator i = map.find(peer);
if (i != map.cend()) {
typing.remove(i.value());
delete i.value();
map.erase(i);
}
}
namespace {
void checkForSwitchInlineButton(HistoryItem *item) {
if (item->out() || !item->hasSwitchInlineButton()) {
return;
}
if (UserData *user = item->history()->peer->asUser()) {
if (!user->botInfo || !user->botInfo->inlineReturnPeerId) {
return;
}
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
for_const (auto &row, markup->rows) {
for_const (auto &button, row) {
if (button.type == HistoryMessageReplyMarkup::Button::Type::SwitchInline) {
Notify::switchInlineBotButtonReceived(QString::fromUtf8(button.data));
return;
}
}
}
}
}
}
} // namespace
HistoryItem *Histories::addNewMessage(const MTPMessage &msg, NewMessageType type) {
auto peer = peerFromMessage(msg);
if (!peer) return nullptr;
auto result = App::history(peer)->addNewMessage(msg, type);
if (result && type == NewMessageUnread) {
checkForSwitchInlineButton(result);
}
return result;
}
int Histories::unreadBadge() const {
return _unreadFull - (Global::IncludeMuted() ? 0 : _unreadMuted);
}
bool Histories::unreadOnlyMuted() const {
return Global::IncludeMuted() ? (_unreadMuted >= _unreadFull) : false;
}
void Histories::setIsPinned(History *history, bool isPinned) {
if (isPinned) {
_pinnedDialogs.insert(history);
if (_pinnedDialogs.size() > Global::PinnedDialogsCountMax()) {
auto minIndex = GlobalPinnedIndex + 1;
auto minIndexHistory = (History*)nullptr;
for_const (auto pinned, _pinnedDialogs) {
if (pinned->getPinnedIndex() < minIndex) {
minIndex = pinned->getPinnedIndex();
minIndexHistory = pinned;
}
}
t_assert(minIndexHistory != nullptr);
minIndexHistory->setPinnedDialog(false);
}
} else {
_pinnedDialogs.remove(history);
}
}
void Histories::clearPinned() {
for (auto pinned : base::take(_pinnedDialogs)) {
pinned->setPinnedDialog(false);
}
}
int Histories::pinnedCount() const {
return _pinnedDialogs.size();
}
QList<History*> Histories::getPinnedOrder() const {
QMap<int, History*> sorter;
for_const (auto pinned, _pinnedDialogs) {
sorter.insert(pinned->getPinnedIndex(), pinned);
}
QList<History*> result;
for (auto i = sorter.cend(), e = sorter.cbegin(); i != e;) {
--i;
result.push_back(i.value());
}
return result;
}
void Histories::savePinnedToServer() const {
auto order = getPinnedOrder();
auto peers = QVector<MTPInputPeer>();
peers.reserve(order.size());
for_const (auto history, order) {
peers.push_back(history->peer->input);
}
auto flags = MTPmessages_ReorderPinnedDialogs::Flag::f_force;
MTP::send(MTPmessages_ReorderPinnedDialogs(MTP_flags(flags), MTP_vector(peers)));
}
HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) {
auto msgId = MsgId(0);
switch (msg.type()) {
case mtpc_messageEmpty: msgId = msg.c_messageEmpty().vid.v; break;
case mtpc_message: msgId = msg.c_message().vid.v; break;
case mtpc_messageService: msgId = msg.c_messageService().vid.v; break;
}
if (!msgId) return nullptr;
auto result = App::histItemById(channelId(), msgId);
if (result) {
if (!result->detached() && detachExistingItem) {
result->detach();
}
if (msg.type() == mtpc_message) {
result->updateMedia(msg.c_message().has_media() ? (&msg.c_message().vmedia) : 0);
if (applyServiceAction) {
App::checkSavedGif(result);
}
}
return result;
}
switch (msg.type()) {
case mtpc_messageEmpty:
result = HistoryService::create(this, msg.c_messageEmpty().vid.v, date(), lang(lng_message_empty));
break;
case mtpc_message: {
auto &m = msg.c_message();
enum class MediaCheckResult {
Good,
Unsupported,
Empty,
};
auto badMedia = MediaCheckResult::Good;
if (m.has_media()) switch (m.vmedia.type()) {
case mtpc_messageMediaEmpty:
case mtpc_messageMediaContact: break;
case mtpc_messageMediaGeo:
switch (m.vmedia.c_messageMediaGeo().vgeo.type()) {
case mtpc_geoPoint: break;
case mtpc_geoPointEmpty: badMedia = MediaCheckResult::Empty; break;
default: badMedia = MediaCheckResult::Unsupported; break;
}
break;
case mtpc_messageMediaVenue:
switch (m.vmedia.c_messageMediaVenue().vgeo.type()) {
case mtpc_geoPoint: break;
case mtpc_geoPointEmpty: badMedia = MediaCheckResult::Empty; break;
default: badMedia = MediaCheckResult::Unsupported; break;
}
break;
case mtpc_messageMediaPhoto:
switch (m.vmedia.c_messageMediaPhoto().vphoto.type()) {
case mtpc_photo: break;
case mtpc_photoEmpty: badMedia = MediaCheckResult::Empty; break;
default: badMedia = MediaCheckResult::Unsupported; break;
}
break;
case mtpc_messageMediaDocument:
switch (m.vmedia.c_messageMediaDocument().vdocument.type()) {
case mtpc_document: break;
case mtpc_documentEmpty: badMedia = MediaCheckResult::Empty; break;
default: badMedia = MediaCheckResult::Unsupported; break;
}
break;
case mtpc_messageMediaWebPage:
switch (m.vmedia.c_messageMediaWebPage().vwebpage.type()) {
case mtpc_webPage:
case mtpc_webPageEmpty:
case mtpc_webPagePending: break;
case mtpc_webPageNotModified:
default: badMedia = MediaCheckResult::Unsupported; break;
}
break;
case mtpc_messageMediaGame:
switch (m.vmedia.c_messageMediaGame().vgame.type()) {
case mtpc_game: break;
default: badMedia = MediaCheckResult::Unsupported; break;
}
break;
case mtpc_messageMediaInvoice:
break;
case mtpc_messageMediaUnsupported:
default: badMedia = MediaCheckResult::Unsupported; break;
}
if (badMedia == MediaCheckResult::Unsupported) {
result = createUnsupportedMessage(this, m.vid.v, m.vflags.v, m.vreply_to_msg_id.v, m.vvia_bot_id.v, date(m.vdate), m.vfrom_id.v);
} else if (badMedia == MediaCheckResult::Empty) {
result = HistoryService::create(this, m.vid.v, date(m.vdate), lang(lng_message_empty), m.vflags.v, m.has_from_id() ? m.vfrom_id.v : 0);
} else {
result = HistoryMessage::create(this, m);
}
} break;
case mtpc_messageService: {
auto &m = msg.c_messageService();
result = HistoryService::create(this, m);
if (applyServiceAction) {
auto &action = m.vaction;
switch (action.type()) {
case mtpc_messageActionChatAddUser: {
auto &d = action.c_messageActionChatAddUser();
if (peer->isMegagroup()) {
auto &v = d.vusers.v;
for (auto i = 0, l = v.size(); i != l; ++i) {
if (auto user = App::userLoaded(peerFromUser(v[i]))) {
if (peer->asChannel()->mgInfo->lastParticipants.indexOf(user) < 0) {
peer->asChannel()->mgInfo->lastParticipants.push_front(user);
peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated;
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
}
if (user->botInfo) {
peer->asChannel()->mgInfo->bots.insert(user);
if (peer->asChannel()->mgInfo->botStatus != 0 && peer->asChannel()->mgInfo->botStatus < 2) {
peer->asChannel()->mgInfo->botStatus = 2;
}
}
}
}
}
} break;
case mtpc_messageActionChatJoinedByLink: {
auto &d = action.c_messageActionChatJoinedByLink();
if (peer->isMegagroup()) {
if (result->from()->isUser()) {
if (peer->asChannel()->mgInfo->lastParticipants.indexOf(result->from()->asUser()) < 0) {
peer->asChannel()->mgInfo->lastParticipants.push_front(result->from()->asUser());
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
}
if (result->from()->asUser()->botInfo) {
peer->asChannel()->mgInfo->bots.insert(result->from()->asUser());
if (peer->asChannel()->mgInfo->botStatus != 0 && peer->asChannel()->mgInfo->botStatus < 2) {
peer->asChannel()->mgInfo->botStatus = 2;
}
}
}
}
} break;
case mtpc_messageActionChatDeletePhoto: {
auto chat = peer->asChat();
if (chat) chat->setPhoto(MTP_chatPhotoEmpty());
} break;
case mtpc_messageActionChatDeleteUser: {
auto &d = action.c_messageActionChatDeleteUser();
auto uid = peerFromUser(d.vuser_id);
if (lastKeyboardFrom == uid) {
clearLastKeyboard();
}
if (peer->isMegagroup()) {
if (auto user = App::userLoaded(uid)) {
auto channel = peer->asChannel();
auto megagroupInfo = channel->mgInfo;
int32 index = megagroupInfo->lastParticipants.indexOf(user);
if (index >= 0) {
megagroupInfo->lastParticipants.removeAt(index);
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
}
if (peer->asChannel()->membersCount() > 1) {
peer->asChannel()->setMembersCount(channel->membersCount() - 1);
} else {
megagroupInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated;
megagroupInfo->lastParticipantsCount = 0;
}
if (megagroupInfo->lastAdmins.contains(user)) {
megagroupInfo->lastAdmins.remove(user);
if (channel->adminsCount() > 1) {
channel->setAdminsCount(channel->adminsCount() - 1);
}
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::AdminsChanged);
}
megagroupInfo->bots.remove(user);
if (megagroupInfo->bots.isEmpty() && megagroupInfo->botStatus > 0) {
megagroupInfo->botStatus = -1;
}
}
}
} break;
case mtpc_messageActionChatEditPhoto: {
auto &d = action.c_messageActionChatEditPhoto();
if (d.vphoto.type() == mtpc_photo) {
auto &sizes = d.vphoto.c_photo().vsizes.v;
if (!sizes.isEmpty()) {
auto photo = App::feedPhoto(d.vphoto.c_photo());
if (photo) photo->peer = peer;
auto &smallSize = sizes.front();
auto &bigSize = sizes.back();
const MTPFileLocation *smallLoc = 0, *bigLoc = 0;
switch (smallSize.type()) {
case mtpc_photoSize: smallLoc = &smallSize.c_photoSize().vlocation; break;
case mtpc_photoCachedSize: smallLoc = &smallSize.c_photoCachedSize().vlocation; break;
}
switch (bigSize.type()) {
case mtpc_photoSize: bigLoc = &bigSize.c_photoSize().vlocation; break;
case mtpc_photoCachedSize: bigLoc = &bigSize.c_photoCachedSize().vlocation; break;
}
if (smallLoc && bigLoc) {
if (peer->isChat()) {
peer->asChat()->setPhoto(MTP_chatPhoto(*smallLoc, *bigLoc), photo ? photo->id : 0);
} else if (peer->isChannel()) {
peer->asChannel()->setPhoto(MTP_chatPhoto(*smallLoc, *bigLoc), photo ? photo->id : 0);
}
peer->loadUserpic();
}
}
}
} break;
case mtpc_messageActionChatEditTitle: {
auto &d = action.c_messageActionChatEditTitle();
if (auto chat = peer->asChat()) {
chat->setName(qs(d.vtitle));
}
} break;
case mtpc_messageActionChatMigrateTo: {
peer->asChat()->flags |= MTPDchat::Flag::f_deactivated;
//auto &d = action.c_messageActionChatMigrateTo();
//auto channel = App::channelLoaded(d.vchannel_id.v);
} break;
case mtpc_messageActionChannelMigrateFrom: {
//auto &d = action.c_messageActionChannelMigrateFrom();
//auto chat = App::chatLoaded(d.vchat_id.v);
} break;
case mtpc_messageActionPinMessage: {
if (m.has_reply_to_msg_id() && result && result->history()->peer->isMegagroup()) {
result->history()->peer->asChannel()->mgInfo->pinnedMsgId = m.vreply_to_msg_id.v;
if (App::main()) emit App::main()->peerUpdated(result->history()->peer);
}
} break;
}
}
} break;
}
if (applyServiceAction) {
App::checkSavedGif(result);
}
return result;
}
HistoryItem *History::createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *msg) {
return HistoryMessage::create(this, id, flags, date, from, msg);
}
HistoryItem *History::createItemDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, doc, caption, markup);
}
HistoryItem *History::createItemPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, photo, caption, markup);
}
HistoryItem *History::createItemGame(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, GameData *game, const MTPReplyMarkup &markup) {
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, game, markup);
}
HistoryItem *History::addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags, bool newMsg) {
return addNewItem(HistoryService::create(this, msgId, date, text, flags), newMsg);
}
HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type) {
if (isChannel()) return asChannelHistory()->addNewChannelMessage(msg, type);
if (type == NewMessageExisting) return addToHistory(msg);
if (!loadedAtBottom() || peer->migrateTo()) {
HistoryItem *item = addToHistory(msg);
if (item) {
setLastMessage(item);
if (type == NewMessageUnread) {
newItemAdded(item);
}
}
return item;
}
return addNewToLastBlock(msg, type);
}
HistoryItem *History::addNewToLastBlock(const MTPMessage &msg, NewMessageType type) {
auto applyServiceAction = (type == NewMessageUnread);
auto detachExistingItem = (type != NewMessageLast);
auto item = createItem(msg, applyServiceAction, detachExistingItem);
if (!item || !item->detached()) {
return item;
}
return addNewItem(item, (type == NewMessageUnread));
}
HistoryItem *History::addToHistory(const MTPMessage &msg) {
return createItem(msg, false, false);
}
HistoryItem *History::addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *item) {
return addNewItem(createItemForwarded(id, flags, date, from, item), true);
}
HistoryItem *History::addNewDocument(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
return addNewItem(createItemDocument(id, flags, viaBotId, replyTo, date, from, doc, caption, markup), true);
}
HistoryItem *History::addNewPhoto(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
return addNewItem(createItemPhoto(id, flags, viaBotId, replyTo, date, from, photo, caption, markup), true);
}
HistoryItem *History::addNewGame(MsgId id, MTPDmessage::Flags flags, int32 viaBotId, MsgId replyTo, QDateTime date, int32 from, GameData *game, const MTPReplyMarkup &markup) {
return addNewItem(createItemGame(id, flags, viaBotId, replyTo, date, from, game, markup), true);
}
bool History::addToOverview(MediaOverviewType type, MsgId msgId, AddToOverviewMethod method) {
bool adding = false;
switch (method) {
case AddToOverviewNew:
case AddToOverviewFront: adding = (overviewIds[type].constFind(msgId) == overviewIds[type].cend()); break;
case AddToOverviewBack: adding = (overviewCountData[type] != 0); break;
}
if (!adding) return false;
overviewIds[type].insert(msgId);
switch (method) {
case AddToOverviewNew:
case AddToOverviewBack: overview[type].push_back(msgId); break;
case AddToOverviewFront: overview[type].push_front(msgId); break;
}
if (method == AddToOverviewNew) {
if (overviewCountData[type] > 0) {
++overviewCountData[type];
}
Notify::mediaOverviewUpdated(peer, type);
}
return true;
}
void History::eraseFromOverview(MediaOverviewType type, MsgId msgId) {
if (overviewIds[type].isEmpty()) return;
auto i = overviewIds[type].find(msgId);
if (i == overviewIds[type].cend()) return;
overviewIds[type].erase(i);
for (auto i = overview[type].begin(), e = overview[type].end(); i != e; ++i) {
if ((*i) == msgId) {
overview[type].erase(i);
if (overviewCountData[type] > 0) {
--overviewCountData[type];
}
break;
}
}
Notify::mediaOverviewUpdated(peer, type);
}
HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
t_assert(!isBuildingFrontBlock());
addItemToBlock(adding);
setLastMessage(adding);
if (newMsg) {
newItemAdded(adding);
}
adding->addToOverview(AddToOverviewNew);
if (adding->from()->id) {
if (adding->from()->isUser()) {
QList<UserData*> *lastAuthors = 0;
if (peer->isChat()) {
lastAuthors = &peer->asChat()->lastAuthors;
} else if (peer->isMegagroup()) {
lastAuthors = &peer->asChannel()->mgInfo->lastParticipants;
if (adding->from()->asUser()->botInfo) {
peer->asChannel()->mgInfo->bots.insert(adding->from()->asUser());
if (peer->asChannel()->mgInfo->botStatus != 0 && peer->asChannel()->mgInfo->botStatus < 2) {
peer->asChannel()->mgInfo->botStatus = 2;
}
}
}
if (lastAuthors) {
int prev = lastAuthors->indexOf(adding->from()->asUser());
if (prev > 0) {
lastAuthors->removeAt(prev);
} else if (prev < 0 && peer->isMegagroup()) { // nothing is outdated if just reordering
peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated;
}
if (prev) {
lastAuthors->push_front(adding->from()->asUser());
}
if (peer->isMegagroup()) {
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
}
}
}
if (adding->definesReplyKeyboard()) {
MTPDreplyKeyboardMarkup::Flags markupFlags = adding->replyKeyboardFlags();
if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || adding->mentionsMe()) {
OrderedSet<PeerData*> *markupSenders = 0;
if (peer->isChat()) {
markupSenders = &peer->asChat()->markupSenders;
} else if (peer->isMegagroup()) {
markupSenders = &peer->asChannel()->mgInfo->markupSenders;
}
if (markupSenders) {
markupSenders->insert(adding->from());
}
if (markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_zero) { // zero markup means replyKeyboardHide
if (lastKeyboardFrom == adding->from()->id || (!lastKeyboardInited && !peer->isChat() && !peer->isMegagroup() && !adding->out())) {
clearLastKeyboard();
}
} else {
bool botNotInChat = false;
if (peer->isChat()) {
botNotInChat = adding->from()->isUser() && (!peer->canWrite() || !peer->asChat()->participants.isEmpty()) && !peer->asChat()->participants.contains(adding->from()->asUser());
} else if (peer->isMegagroup()) {
botNotInChat = adding->from()->isUser() && (!peer->canWrite() || peer->asChannel()->mgInfo->botStatus != 0) && !peer->asChannel()->mgInfo->bots.contains(adding->from()->asUser());
}
if (botNotInChat) {
clearLastKeyboard();
} else {
lastKeyboardInited = true;
lastKeyboardId = adding->id;
lastKeyboardFrom = adding->from()->id;
lastKeyboardUsed = false;
}
}
}
}
}
return adding;
}
void History::unregSendAction(UserData *from) {
auto updateAtMs = TimeMs(0);
auto i = _typing.find(from);
if (i != _typing.cend()) {
updateAtMs = getms();
i.value() = updateAtMs;
}
auto j = _sendActions.find(from);
if (j != _sendActions.cend()) {
if (!updateAtMs) updateAtMs = getms();
j.value().until = updateAtMs;
}
if (updateAtMs) {
updateSendActionNeedsAnimating(updateAtMs, true);
}
}
void History::newItemAdded(HistoryItem *item) {
App::checkImageCacheSize();
if (item->from() && item->from()->isUser()) {
if (item->from() == item->author()) {
unregSendAction(item->from()->asUser());
}
MTPint itemServerTime;
toServerTime(item->date.toTime_t(), itemServerTime);
item->from()->asUser()->madeAction(itemServerTime.v);
}
if (item->out()) {
if (unreadBar) unreadBar->destroyUnreadBar();
if (!item->unread()) {
outboxRead(item);
}
} else if (item->unread()) {
if (!isChannel() || peer->asChannel()->amIn()) {
notifies.push_back(item);
App::main()->newUnreadMsg(this, item);
}
} else if (!item->isGroupMigrate() || !peer->isMegagroup()) {
inboxRead(item);
}
}
HistoryBlock *History::prepareBlockForAddingItem() {
if (isBuildingFrontBlock()) {
if (_buildingFrontBlock->block) {
return _buildingFrontBlock->block;
}
auto result = _buildingFrontBlock->block = new HistoryBlock(this);
if (_buildingFrontBlock->expectedItemsCount > 0) {
result->items.reserve(_buildingFrontBlock->expectedItemsCount + 1);
}
result->setIndexInHistory(0);
blocks.push_front(result);
for (int i = 1, l = blocks.size(); i < l; ++i) {
blocks.at(i)->setIndexInHistory(i);
}
return result;
}
bool addNewBlock = blocks.isEmpty() || (blocks.back()->items.size() >= MessagesPerPage);
if (!addNewBlock) {
return blocks.back();
}
auto result = new HistoryBlock(this);
result->setIndexInHistory(blocks.size());
blocks.push_back(result);
result->items.reserve(MessagesPerPage);
return result;
};
void History::addItemToBlock(HistoryItem *item) {
t_assert(item != nullptr);
t_assert(item->detached());
auto block = prepareBlockForAddingItem();
item->attachToBlock(block, block->items.size());
block->items.push_back(item);
item->previousItemChanged();
if (isBuildingFrontBlock() && _buildingFrontBlock->expectedItemsCount > 0) {
--_buildingFrontBlock->expectedItemsCount;
}
}
void History::addOlderSlice(const QVector<MTPMessage> &slice) {
if (slice.isEmpty()) {
oldLoaded = true;
if (isChannel()) {
asChannelHistory()->checkJoinedMessage();
asChannelHistory()->checkMaxReadMessageDate();
}
return;
}
startBuildingFrontBlock(slice.size());
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
--i;
auto adding = createItem(*i, false, true);
if (!adding) continue;
addItemToBlock(adding);
}
auto block = finishBuildingFrontBlock();
if (!block) {
// If no items were added it means we've loaded everything old.
oldLoaded = true;
} else if (loadedAtBottom()) { // add photos to overview and authors to lastAuthors
bool channel = isChannel();
int32 mask = 0;
QList<UserData*> *lastAuthors = nullptr;
OrderedSet<PeerData*> *markupSenders = nullptr;
if (peer->isChat()) {
lastAuthors = &peer->asChat()->lastAuthors;
markupSenders = &peer->asChat()->markupSenders;
} else if (peer->isMegagroup()) {
// We don't add users to mgInfo->lastParticipants here.
// We're scrolling back and we see messages from users that
// could be gone from the megagroup already. It is fine for
// chat->lastAuthors, because they're used only for field
// autocomplete, but this is bad for megagroups, because its
// lastParticipants are displayed in Profile as members list.
markupSenders = &peer->asChannel()->mgInfo->markupSenders;
}
for (int32 i = block->items.size(); i > 0; --i) {
auto item = block->items[i - 1];
mask |= item->addToOverview(AddToOverviewFront);
if (item->from()->id) {
if (lastAuthors) { // chats
if (auto user = item->from()->asUser()) {
if (!lastAuthors->contains(user)) {
lastAuthors->push_back(user);
if (peer->isMegagroup()) {
peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated;
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
}
}
}
}
}
if (item->author()->id) {
if (markupSenders) { // chats with bots
if (!lastKeyboardInited && item->definesReplyKeyboard() && !item->out()) {
auto markupFlags = item->replyKeyboardFlags();
if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || item->mentionsMe()) {
bool wasKeyboardHide = markupSenders->contains(item->author());
if (!wasKeyboardHide) {
markupSenders->insert(item->author());
}
if (!(markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_zero)) {
if (!lastKeyboardInited) {
bool botNotInChat = false;
if (peer->isChat()) {
botNotInChat = (!peer->canWrite() || !peer->asChat()->participants.isEmpty()) && item->author()->isUser() && !peer->asChat()->participants.contains(item->author()->asUser());
} else if (peer->isMegagroup()) {
botNotInChat = (!peer->canWrite() || peer->asChannel()->mgInfo->botStatus != 0) && item->author()->isUser() && !peer->asChannel()->mgInfo->bots.contains(item->author()->asUser());
}
if (wasKeyboardHide || botNotInChat) {
clearLastKeyboard();
} else {
lastKeyboardInited = true;
lastKeyboardId = item->id;
lastKeyboardFrom = item->author()->id;
lastKeyboardUsed = false;
}
}
}
}
}
} else if (!lastKeyboardInited && item->definesReplyKeyboard() && !item->out()) { // conversations with bots
MTPDreplyKeyboardMarkup::Flags markupFlags = item->replyKeyboardFlags();
if (!(markupFlags & MTPDreplyKeyboardMarkup::Flag::f_selective) || item->mentionsMe()) {
if (markupFlags & MTPDreplyKeyboardMarkup_ClientFlag::f_zero) {
clearLastKeyboard();
} else {
lastKeyboardInited = true;
lastKeyboardId = item->id;
lastKeyboardFrom = item->author()->id;
lastKeyboardUsed = false;
}
}
}
}
}
if (mask) {
Notify::PeerUpdate update(peer);
update.flags |= Notify::PeerUpdate::Flag::SharedMediaChanged;
update.mediaTypesMask |= mask;
Notify::peerUpdatedDelayed(update);
}
}
if (isChannel()) {
asChannelHistory()->checkJoinedMessage();
asChannelHistory()->checkMaxReadMessageDate();
}
checkLastMsg();
}
void History::addNewerSlice(const QVector<MTPMessage> &slice) {
bool wasEmpty = isEmpty(), wasLoadedAtBottom = loadedAtBottom();
if (slice.isEmpty()) {
newLoaded = true;
if (!lastMsg) {
setLastMessage(lastAvailableMessage());
}
}
t_assert(!isBuildingFrontBlock());
if (!slice.isEmpty()) {
bool atLeastOneAdded = false;
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
--i;
auto adding = createItem(*i, false, true);
if (!adding) continue;
addItemToBlock(adding);
atLeastOneAdded = true;
}
if (!atLeastOneAdded) {
newLoaded = true;
setLastMessage(lastAvailableMessage());
}
}
if (!wasLoadedAtBottom) {
checkAddAllToOverview();
}
if (isChannel()) asChannelHistory()->checkJoinedMessage();
checkLastMsg();
}
void History::checkLastMsg() {
if (lastMsg) {
if (!newLoaded && !lastMsg->detached()) {
newLoaded = true;
checkAddAllToOverview();
}
} else if (newLoaded) {
setLastMessage(lastAvailableMessage());
}
}
void History::checkAddAllToOverview() {
if (!loadedAtBottom()) {
return;
}
int32 mask = 0;
for (int32 i = 0; i < OverviewCount; ++i) {
if (overviewCountData[i] == 0) continue; // all loaded
if (!overview[i].isEmpty() || !overviewIds[i].isEmpty()) {
overview[i].clear();
overviewIds[i].clear();
mask |= (1 << i);
}
}
for_const (HistoryBlock *block, blocks) {
for_const (HistoryItem *item, block->items) {
mask |= item->addToOverview(AddToOverviewBack);
}
}
if (mask) {
Notify::PeerUpdate update(peer);
update.flags |= Notify::PeerUpdate::Flag::SharedMediaChanged;
update.mediaTypesMask |= mask;
Notify::peerUpdatedDelayed(update);
}
}
int History::countUnread(MsgId upTo) {
int result = 0;
for (auto i = blocks.cend(), e = blocks.cbegin(); i != e;) {
--i;
for (auto j = (*i)->items.cend(), en = (*i)->items.cbegin(); j != en;) {
--j;
if ((*j)->id > 0 && (*j)->id <= upTo) {
break;
} else if (!(*j)->out() && (*j)->unread() && (*j)->id > upTo) {
++result;
}
}
}
return result;
}
void History::updateShowFrom() {
if (showFrom) return;
for (auto i = blocks.cend(); i != blocks.cbegin();) {
--i;
for (auto j = (*i)->items.cend(); j != (*i)->items.cbegin();) {
--j;
if ((*j)->id > 0 && (!(*j)->out() || !showFrom)) {
if ((*j)->id >= inboxReadBefore) {
showFrom = *j;
} else {
return;
}
}
}
}
}
MsgId History::inboxRead(MsgId upTo) {
if (upTo < 0) return upTo;
if (unreadCount()) {
if (upTo && loadedAtBottom()) App::main()->historyToDown(this);
setUnreadCount(upTo ? countUnread(upTo) : 0);
}
if (!upTo) upTo = msgIdForRead();
accumulate_max(inboxReadBefore, upTo + 1);
updateChatListEntry();
if (peer->migrateTo()) {
if (auto migrateTo = App::historyLoaded(peer->migrateTo()->id)) {
migrateTo->updateChatListEntry();
}
}
showFrom = nullptr;
AuthSession::Current().notifications().clearFromHistory(this);
return upTo;
}
MsgId History::inboxRead(HistoryItem *wasRead) {
return inboxRead(wasRead ? wasRead->id : 0);
}
MsgId History::outboxRead(int32 upTo) {
if (upTo < 0) return upTo;
if (!upTo) upTo = msgIdForRead();
accumulate_max(outboxReadBefore, upTo + 1);
return upTo;
}
MsgId History::outboxRead(HistoryItem *wasRead) {
return outboxRead(wasRead ? wasRead->id : 0);
}
HistoryItem *History::lastAvailableMessage() const {
return isEmpty() ? nullptr : blocks.back()->items.back();
}
void History::setUnreadCount(int newUnreadCount) {
if (_unreadCount != newUnreadCount) {
if (newUnreadCount == 1) {
if (loadedAtBottom()) showFrom = lastAvailableMessage();
inboxReadBefore = qMax(inboxReadBefore, msgIdForRead());
} else if (!newUnreadCount) {
showFrom = nullptr;
inboxReadBefore = qMax(inboxReadBefore, msgIdForRead() + 1);
} else {
if (!showFrom && !unreadBar && loadedAtBottom()) updateShowFrom();
}
if (inChatList(Dialogs::Mode::All)) {
App::histories().unreadIncrement(newUnreadCount - _unreadCount, mute());
if (!mute() || Global::IncludeMuted()) {
Notify::unreadCounterUpdated();
}
}
_unreadCount = newUnreadCount;
if (auto main = App::main()) {
main->unreadCountChanged(this);
}
if (unreadBar) {
int32 count = _unreadCount;
if (peer->migrateTo()) {
if (History *h = App::historyLoaded(peer->migrateTo()->id)) {
count += h->unreadCount();
}
}
if (count > 0) {
unreadBar->setUnreadBarCount(count);
} else {
unreadBar->setUnreadBarFreezed();
}
}
}
}
void History::setMute(bool newMute) {
if (_mute != newMute) {
_mute = newMute;
if (inChatList(Dialogs::Mode::All)) {
if (_unreadCount) {
App::histories().unreadMuteChanged(_unreadCount, newMute);
Notify::unreadCounterUpdated();
}
Notify::historyMuteUpdated(this);
}
updateChatListEntry();
}
}
void History::getNextShowFrom(HistoryBlock *block, int i) {
if (i >= 0) {
auto l = block->items.size();
for (++i; i < l; ++i) {
if (block->items[i]->id > 0) {
showFrom = block->items.at(i);
return;
}
}
}
for (auto j = block->indexInHistory() + 1, s = blocks.size(); j < s; ++j) {
block = blocks.at(j);
for_const (auto item, block->items) {
if (item->id > 0) {
showFrom = item;
return;
}
}
}
showFrom = nullptr;
}
void History::countScrollState(int top) {
countScrollTopItem(top);
if (scrollTopItem) {
scrollTopOffset = (top - scrollTopItem->block()->y - scrollTopItem->y);
}
}
void History::countScrollTopItem(int top) {
if (isEmpty()) {
forgetScrollState();
return;
}
int itemIndex = 0, blockIndex = 0, itemTop = 0;
if (scrollTopItem && !scrollTopItem->detached()) {
itemIndex = scrollTopItem->indexInBlock();
blockIndex = scrollTopItem->block()->indexInHistory();
itemTop = blocks.at(blockIndex)->y + scrollTopItem->y;
}
if (itemTop > top) {
// go backward through history while we don't find an item that starts above
do {
HistoryBlock *block = blocks.at(blockIndex);
for (--itemIndex; itemIndex >= 0; --itemIndex) {
HistoryItem *item = block->items.at(itemIndex);
itemTop = block->y + item->y;
if (itemTop <= top) {
scrollTopItem = item;
return;
}
}
if (--blockIndex >= 0) {
itemIndex = blocks.at(blockIndex)->items.size();
} else {
break;
}
} while (true);
scrollTopItem = blocks.front()->items.front();
} else {
// go forward through history while we don't find the last item that starts above
for (int blocksCount = blocks.size(); blockIndex < blocksCount; ++blockIndex) {
HistoryBlock *block = blocks.at(blockIndex);
for (int itemsCount = block->items.size(); itemIndex < itemsCount; ++itemIndex) {
HistoryItem *item = block->items.at(itemIndex);
itemTop = block->y + item->y;
if (itemTop > top) {
t_assert(itemIndex > 0 || blockIndex > 0);
if (itemIndex > 0) {
scrollTopItem = block->items.at(itemIndex - 1);
} else {
scrollTopItem = blocks.at(blockIndex - 1)->items.back();
}
return;
}
}
itemIndex = 0;
}
scrollTopItem = blocks.back()->items.back();
}
}
void History::getNextScrollTopItem(HistoryBlock *block, int32 i) {
++i;
if (i > 0 && i < block->items.size()) {
scrollTopItem = block->items.at(i);
return;
}
int j = block->indexInHistory() + 1;
if (j > 0 && j < blocks.size()) {
scrollTopItem = blocks.at(j)->items.front();
return;
}
scrollTopItem = nullptr;
}
void History::addUnreadBar() {
if (unreadBar || !showFrom || showFrom->detached() || !unreadCount()) return;
int32 count = unreadCount();
if (peer->migrateTo()) {
if (History *h = App::historyLoaded(peer->migrateTo()->id)) {
count += h->unreadCount();
}
}
showFrom->setUnreadBarCount(count);
unreadBar = showFrom;
}
void History::destroyUnreadBar() {
if (unreadBar) {
unreadBar->destroyUnreadBar();
}
}
HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex) {
t_assert(blockIndex >= 0);
t_assert(blockIndex < blocks.size());
t_assert(itemIndex >= 0);
t_assert(itemIndex <= blocks[blockIndex]->items.size());
auto block = blocks.at(blockIndex);
newItem->attachToBlock(block, itemIndex);
block->items.insert(itemIndex, newItem);
newItem->previousItemChanged();
if (itemIndex + 1 < block->items.size()) {
for (int i = itemIndex + 1, l = block->items.size(); i < l; ++i) {
block->items[i]->setIndexInBlock(i);
}
block->items[itemIndex + 1]->previousItemChanged();
} else if (blockIndex + 1 < blocks.size() && !blocks[blockIndex + 1]->items.empty()) {
blocks[blockIndex + 1]->items.front()->previousItemChanged();
} else {
newItem->nextItemChanged();
}
return newItem;
}
void History::startBuildingFrontBlock(int expectedItemsCount) {
t_assert(!isBuildingFrontBlock());
t_assert(expectedItemsCount > 0);
_buildingFrontBlock.reset(new BuildingBlock());
_buildingFrontBlock->expectedItemsCount = expectedItemsCount;
}
HistoryBlock *History::finishBuildingFrontBlock() {
t_assert(isBuildingFrontBlock());
// Some checks if there was some message history already
auto block = _buildingFrontBlock->block;
if (block) {
if (blocks.size() > 1) {
auto last = block->items.back(); // ... item, item, item, last ], [ first, item, item ...
auto first = blocks.at(1)->items.front();
// we've added a new front block, so previous item for
// the old first item of a first block was changed
first->previousItemChanged();
} else {
block->items.back()->nextItemChanged();
}
}
_buildingFrontBlock = nullptr;
return block;
}
void History::clearNotifications() {
notifies.clear();
}
bool History::loadedAtBottom() const {
return newLoaded;
}
bool History::loadedAtTop() const {
return oldLoaded;
}
bool History::isReadyFor(MsgId msgId) {
if (msgId < 0 && -msgId < ServerMaxMsgId && peer->migrateFrom()) { // old group history
return App::history(peer->migrateFrom()->id)->isReadyFor(-msgId);
}
if (msgId == ShowAtTheEndMsgId) {
return loadedAtBottom();
}
if (msgId == ShowAtUnreadMsgId) {
if (peer->migrateFrom()) { // old group history
if (History *h = App::historyLoaded(peer->migrateFrom()->id)) {
if (h->unreadCount()) {
return h->isReadyFor(msgId);
}
}
}
if (unreadCount()) {
if (!isEmpty()) {
return (loadedAtTop() || minMsgId() <= inboxReadBefore) && (loadedAtBottom() || maxMsgId() >= inboxReadBefore);
}
return false;
}
return loadedAtBottom();
}
HistoryItem *item = App::histItemById(channelId(), msgId);
return item && (item->history() == this) && !item->detached();
}
void History::getReadyFor(MsgId msgId) {
if (msgId < 0 && -msgId < ServerMaxMsgId && peer->migrateFrom()) {
History *h = App::history(peer->migrateFrom()->id);
h->getReadyFor(-msgId);
if (h->isEmpty()) {
clear(true);
}
return;
}
if (msgId == ShowAtUnreadMsgId && peer->migrateFrom()) {
if (History *h = App::historyLoaded(peer->migrateFrom()->id)) {
if (h->unreadCount()) {
clear(true);
h->getReadyFor(msgId);
return;
}
}
}
if (!isReadyFor(msgId)) {
clear(true);
if (msgId == ShowAtTheEndMsgId) {
newLoaded = true;
}
}
}
void History::setNotLoadedAtBottom() {
newLoaded = false;
}
namespace {
uint32 _dialogsPosToTopShift = 0x80000000UL;
}
inline uint64 dialogPosFromDate(const QDateTime &date) {
if (date.isNull()) return 0;
return (uint64(date.toTime_t()) << 32) | (++_dialogsPosToTopShift);
}
inline uint64 pinnedDialogPos(int pinnedIndex) {
return 0xFFFFFFFF00000000ULL + pinnedIndex;
}
void History::setLastMessage(HistoryItem *msg) {
if (msg) {
if (!lastMsg) Local::removeSavedPeer(peer);
lastMsg = msg;
setChatsListDate(msg->date);
} else {
lastMsg = 0;
updateChatListEntry();
}
}
bool History::needUpdateInChatList() const {
if (inChatList(Dialogs::Mode::All) || isPinnedDialog()) {
return true;
} else if (peer->migrateTo()) {
return false;
}
return (!peer->isChannel() || peer->asChannel()->amIn());
}
void History::setChatsListDate(const QDateTime &date) {
if (!lastMsgDate.isNull() && lastMsgDate >= date) {
if (!needUpdateInChatList() || !inChatList(Dialogs::Mode::All)) {
return;
}
}
lastMsgDate = date;
updateChatListSortPosition();
}
void History::updateChatListSortPosition() {
auto chatListDate = [this]() {
if (auto draft = cloudDraft()) {
if (!Data::draftIsNull(draft) && draft->date > lastMsgDate) {
return draft->date;
}
}
return lastMsgDate;
};
_sortKeyInChatList = isPinnedDialog() ? pinnedDialogPos(_pinnedIndex) : dialogPosFromDate(chatListDate());
if (auto m = App::main()) {
if (needUpdateInChatList()) {
if (_sortKeyInChatList) {
m->createDialog(this);
updateChatListEntry();
} else {
m->deleteConversation(peer, false);
}
}
}
}
void History::fixLastMessage(bool wasAtBottom) {
setLastMessage(wasAtBottom ? lastAvailableMessage() : 0);
}
MsgId History::minMsgId() const {
for_const (const HistoryBlock *block, blocks) {
for_const (const HistoryItem *item, block->items) {
if (item->id > 0) {
return item->id;
}
}
}
return 0;
}
MsgId History::maxMsgId() const {
for (auto i = blocks.cend(), e = blocks.cbegin(); i != e;) {
--i;
for (auto j = (*i)->items.cend(), en = (*i)->items.cbegin(); j != en;) {
--j;
if ((*j)->id > 0) {
return (*j)->id;
}
}
}
return 0;
}
MsgId History::msgIdForRead() const {
MsgId result = (lastMsg && lastMsg->id > 0) ? lastMsg->id : 0;
if (loadedAtBottom()) result = qMax(result, maxMsgId());
return result;
}
int History::resizeGetHeight(int newWidth) {
bool resizeAllItems = (_flags & Flag::f_pending_resize) || (width != newWidth);
if (!resizeAllItems && !hasPendingResizedItems()) {
return height;
}
_flags &= ~(Flag::f_pending_resize | Flag::f_has_pending_resized_items);
width = newWidth;
int y = 0;
for_const (HistoryBlock *block, blocks) {
block->y = y;
y += block->resizeGetHeight(newWidth, resizeAllItems);
}
height = y;
return height;
}
ChannelHistory *History::asChannelHistory() {
return isChannel() ? static_cast<ChannelHistory*>(this) : 0;
}
const ChannelHistory *History::asChannelHistory() const {
return isChannel() ? static_cast<const ChannelHistory*>(this) : 0;
}
bool History::isDisplayedEmpty() const {
return isEmpty() || ((blocks.size() == 1) && blocks.front()->items.size() == 1 && blocks.front()->items.front()->isEmpty());
}
void History::clear(bool leaveItems) {
if (unreadBar) {
unreadBar = nullptr;
}
if (showFrom) {
showFrom = nullptr;
}
if (lastSentMsg) {
lastSentMsg = nullptr;
}
if (scrollTopItem) {
forgetScrollState();
}
if (!leaveItems) {
setLastMessage(nullptr);
notifies.clear();
auto &pending = Global::RefPendingRepaintItems();
for (auto i = pending.begin(); i != pending.end();) {
if ((*i)->history() == this) {
i = pending.erase(i);
} else {
++i;
}
}
}
for (int32 i = 0; i < OverviewCount; ++i) {
if (!overview[i].isEmpty() || !overviewIds[i].isEmpty()) {
if (leaveItems) {
if (overviewCountData[i] == 0) {
overviewCountData[i] = overview[i].size();
}
} else {
overviewCountData[i] = -1; // not loaded yet
}
overview[i].clear();
overviewIds[i].clear();
if (!App::quitting()) Notify::mediaOverviewUpdated(peer, MediaOverviewType(i));
}
}
clearBlocks(leaveItems);
if (leaveItems) {
lastKeyboardInited = false;
} else {
setUnreadCount(0);
if (peer->isMegagroup()) {
peer->asChannel()->mgInfo->pinnedMsgId = 0;
}
clearLastKeyboard();
}
setPendingResize();
newLoaded = oldLoaded = false;
forgetScrollState();
if (peer->isChat()) {
peer->asChat()->lastAuthors.clear();
peer->asChat()->markupSenders.clear();
} else if (isChannel()) {
asChannelHistory()->cleared(leaveItems);
if (isMegagroup()) {
peer->asChannel()->mgInfo->markupSenders.clear();
}
}
if (leaveItems && App::main()) App::main()->historyCleared(this);
}
void History::clearBlocks(bool leaveItems) {
Blocks lst;
std::swap(lst, blocks);
for_const (HistoryBlock *block, lst) {
if (leaveItems) {
block->clear(true);
}
delete block;
}
}
void History::clearOnDestroy() {
clearBlocks(false);
}
History::PositionInChatListChange History::adjustByPosInChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed) {
t_assert(indexed != nullptr);
Dialogs::Row *lnk = mainChatListLink(list);
int32 movedFrom = lnk->pos();
indexed->adjustByPos(chatListLinks(list));
int32 movedTo = lnk->pos();
return { movedFrom, movedTo };
}
int History::posInChatList(Dialogs::Mode list) const {
return mainChatListLink(list)->pos();
}
Dialogs::Row *History::addToChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed) {
t_assert(indexed != nullptr);
if (!inChatList(list)) {
chatListLinks(list) = indexed->addToEnd(this);
if (list == Dialogs::Mode::All && unreadCount()) {
App::histories().unreadIncrement(unreadCount(), mute());
Notify::unreadCounterUpdated();
}
}
return mainChatListLink(list);
}
void History::removeFromChatList(Dialogs::Mode list, Dialogs::IndexedList *indexed) {
t_assert(indexed != nullptr);
if (inChatList(list)) {
indexed->del(peer);
chatListLinks(list).clear();
if (list == Dialogs::Mode::All && unreadCount()) {
App::histories().unreadIncrement(-unreadCount(), mute());
Notify::unreadCounterUpdated();
}
}
}
void History::removeChatListEntryByLetter(Dialogs::Mode list, QChar letter) {
t_assert(letter != 0);
if (inChatList(list)) {
chatListLinks(list).remove(letter);
}
}
void History::addChatListEntryByLetter(Dialogs::Mode list, QChar letter, Dialogs::Row *row) {
t_assert(letter != 0);
if (inChatList(list)) {
chatListLinks(list).insert(letter, row);
}
}
void History::updateChatListEntry() const {
if (auto main = App::main()) {
if (inChatList(Dialogs::Mode::All)) {
main->dlgUpdated(Dialogs::Mode::All, mainChatListLink(Dialogs::Mode::All));
if (inChatList(Dialogs::Mode::Important)) {
main->dlgUpdated(Dialogs::Mode::Important, mainChatListLink(Dialogs::Mode::Important));
}
}
}
}
void History::setPinnedDialog(bool isPinned) {
setPinnedIndex(isPinned ? (++GlobalPinnedIndex) : 0);
}
void History::setPinnedIndex(int pinnedIndex) {
if (_pinnedIndex != pinnedIndex) {
auto wasPinned = isPinnedDialog();
_pinnedIndex = pinnedIndex;
updateChatListSortPosition();
updateChatListEntry();
if (wasPinned != isPinnedDialog()) {
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::PinnedChanged);
}
App::histories().setIsPinned(this, isPinnedDialog());
}
}
void History::overviewSliceDone(int32 overviewIndex, const MTPmessages_Messages &result, bool onlyCounts) {
const QVector<MTPMessage> *v = 0;
switch (result.type()) {
case mtpc_messages_messages: {
auto &d(result.c_messages_messages());
App::feedUsers(d.vusers);
App::feedChats(d.vchats);
v = &d.vmessages.v;
overviewCountData[overviewIndex] = 0;
} break;
case mtpc_messages_messagesSlice: {
auto &d(result.c_messages_messagesSlice());
App::feedUsers(d.vusers);
App::feedChats(d.vchats);
overviewCountData[overviewIndex] = d.vcount.v;
v = &d.vmessages.v;
} break;
case mtpc_messages_channelMessages: {
auto &d(result.c_messages_channelMessages());
if (peer->isChannel()) {
peer->asChannel()->ptsReceived(d.vpts.v);
} else {
LOG(("API Error: received messages.channelMessages when no channel was passed! (History::overviewSliceDone, onlyCounts %1)").arg(Logs::b(onlyCounts)));
}
App::feedUsers(d.vusers);
App::feedChats(d.vchats);
overviewCountData[overviewIndex] = d.vcount.v;
v = &d.vmessages.v;
} break;
default: return;
}
if (!onlyCounts && v->isEmpty()) {
overviewCountData[overviewIndex] = 0;
} else if (overviewCountData[overviewIndex] > 0) {
for_const (auto msgId, overviewIds[overviewIndex]) {
if (msgId < 0) {
++overviewCountData[overviewIndex];
} else {
break;
}
}
}
for (QVector<MTPMessage>::const_iterator i = v->cbegin(), e = v->cend(); i != e; ++i) {
HistoryItem *item = App::histories().addNewMessage(*i, NewMessageExisting);
if (item && overviewIds[overviewIndex].constFind(item->id) == overviewIds[overviewIndex].cend()) {
overviewIds[overviewIndex].insert(item->id);
overview[overviewIndex].push_front(item->id);
}
}
}
void History::changeMsgId(MsgId oldId, MsgId newId) {
for (int32 i = 0; i < OverviewCount; ++i) {
auto j = overviewIds[i].find(oldId);
if (j != overviewIds[i].cend()) {
overviewIds[i].erase(j);
int32 index = overview[i].indexOf(oldId);
if (overviewIds[i].constFind(newId) == overviewIds[i].cend()) {
overviewIds[i].insert(newId);
if (index >= 0) {
overview[i][index] = newId;
} else {
overview[i].push_back(newId);
}
} else if (index >= 0) {
overview[i].removeAt(index);
}
}
}
}
void History::removeBlock(HistoryBlock *block) {
t_assert(block->items.isEmpty());
if (_buildingFrontBlock && block == _buildingFrontBlock->block) {
_buildingFrontBlock->block = nullptr;
}
int index = block->indexInHistory();
blocks.removeAt(index);
if (index < blocks.size()) {
for (int i = index, l = blocks.size(); i < l; ++i) {
blocks.at(i)->setIndexInHistory(i);
}
blocks.at(index)->items.front()->previousItemChanged();
} else if (!blocks.empty() && !blocks.back()->items.empty()) {
blocks.back()->items.back()->nextItemChanged();
}
}
History::~History() {
clearOnDestroy();
}
int HistoryBlock::resizeGetHeight(int newWidth, bool resizeAllItems) {
int y = 0;
for_const (HistoryItem *item, items) {
item->y = y;
if (resizeAllItems || item->pendingResize()) {
y += item->resizeGetHeight(newWidth);
} else {
y += item->height();
}
}
height = y;
return height;
}
void HistoryBlock::clear(bool leaveItems) {
Items lst;
std::swap(lst, items);
if (leaveItems) {
for_const (HistoryItem *item, lst) {
item->detachFast();
}
} else {
for_const (HistoryItem *item, lst) {
delete item;
}
}
}
void HistoryBlock::removeItem(HistoryItem *item) {
t_assert(item->block() == this);
int blockIndex = indexInHistory();
int itemIndex = item->indexInBlock();
if (history->showFrom == item) {
history->getNextShowFrom(this, itemIndex);
}
if (history->lastSentMsg == item) {
history->lastSentMsg = nullptr;
}
if (history->unreadBar == item) {
history->unreadBar = nullptr;
}
if (history->scrollTopItem == item) {
history->getNextScrollTopItem(this, itemIndex);
}
item->detachFast();
items.remove(itemIndex);
for (int i = itemIndex, l = items.size(); i < l; ++i) {
items.at(i)->setIndexInBlock(i);
}
if (items.isEmpty()) {
history->removeBlock(this);
} else if (itemIndex < items.size()) {
items.at(itemIndex)->previousItemChanged();
} else if (blockIndex + 1 < history->blocks.size()) {
history->blocks.at(blockIndex + 1)->items.front()->previousItemChanged();
} else if (!history->blocks.empty() && !history->blocks.back()->items.empty()) {
history->blocks.back()->items.back()->nextItemChanged();
}
if (items.isEmpty()) {
delete this;
}
}