2016-09-27 14:20:49 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2016-09-27 14:20:49 +00:00
|
|
|
*/
|
|
|
|
#include "history/history_item.h"
|
|
|
|
|
2017-04-13 08:27:10 +00:00
|
|
|
#include "lang/lang_keys.h"
|
2016-09-27 14:20:49 +00:00
|
|
|
#include "mainwidget.h"
|
2017-12-18 15:44:50 +00:00
|
|
|
#include "history/history_item_components.h"
|
2016-09-27 14:20:49 +00:00
|
|
|
#include "history/history_service_layout.h"
|
2017-06-20 19:48:53 +00:00
|
|
|
#include "history/history_media_types.h"
|
2017-12-13 18:10:48 +00:00
|
|
|
#include "history/history_media_grouped.h"
|
2017-06-22 15:11:41 +00:00
|
|
|
#include "history/history_message.h"
|
2016-09-27 14:20:49 +00:00
|
|
|
#include "media/media_clip_reader.h"
|
|
|
|
#include "styles/style_dialogs.h"
|
2016-11-16 10:44:06 +00:00
|
|
|
#include "styles/style_history.h"
|
2016-11-16 16:04:25 +00:00
|
|
|
#include "ui/effects/ripple_animation.h"
|
2017-12-28 13:06:06 +00:00
|
|
|
#include "ui/text_options.h"
|
2017-03-04 10:23:56 +00:00
|
|
|
#include "storage/file_upload.h"
|
2017-09-04 11:40:02 +00:00
|
|
|
#include "storage/storage_facade.h"
|
|
|
|
#include "storage/storage_shared_media.h"
|
2017-02-23 09:32:28 +00:00
|
|
|
#include "auth_session.h"
|
2017-12-25 14:17:00 +00:00
|
|
|
#include "apiwrap.h"
|
2017-05-18 20:18:59 +00:00
|
|
|
#include "media/media_audio.h"
|
2017-03-10 17:25:43 +00:00
|
|
|
#include "messenger.h"
|
2017-10-03 13:05:58 +00:00
|
|
|
#include "mainwindow.h"
|
|
|
|
#include "window/window_controller.h"
|
2018-01-02 13:44:12 +00:00
|
|
|
#include "core/crash_reports.h"
|
2018-01-04 10:22:53 +00:00
|
|
|
#include "data/data_session.h"
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2016-10-05 16:56:27 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
// a new message from the same sender is attached to previous within 15 minutes
|
|
|
|
constexpr int kAttachMessageToPreviousSecondsDelta = 900;
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2017-12-15 16:25:47 +00:00
|
|
|
HistoryTextState::HistoryTextState(not_null<const HistoryItem*> item)
|
|
|
|
: itemId(item->fullId()) {
|
|
|
|
}
|
|
|
|
|
|
|
|
HistoryTextState::HistoryTextState(
|
|
|
|
not_null<const HistoryItem*> item,
|
|
|
|
const Text::StateResult &state)
|
|
|
|
: itemId(item->fullId())
|
|
|
|
, cursor(state.uponSymbol
|
|
|
|
? HistoryInTextCursorState
|
|
|
|
: HistoryDefaultCursorState)
|
|
|
|
, link(state.link)
|
|
|
|
, afterSymbol(state.afterSymbol)
|
|
|
|
, symbol(state.symbol) {
|
|
|
|
}
|
|
|
|
|
|
|
|
HistoryTextState::HistoryTextState(
|
|
|
|
not_null<const HistoryItem*> item,
|
|
|
|
ClickHandlerPtr link)
|
|
|
|
: itemId(item->fullId())
|
|
|
|
, link(link) {
|
|
|
|
}
|
|
|
|
|
2017-08-31 16:28:58 +00:00
|
|
|
HistoryMediaPtr::HistoryMediaPtr() = default;
|
|
|
|
|
2017-12-13 18:10:48 +00:00
|
|
|
HistoryMediaPtr::HistoryMediaPtr(std::unique_ptr<HistoryMedia> pointer)
|
|
|
|
: _pointer(std::move(pointer)) {
|
2017-03-05 13:39:10 +00:00
|
|
|
if (_pointer) {
|
|
|
|
_pointer->attachToParent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-22 15:11:41 +00:00
|
|
|
void HistoryMediaPtr::reset(std::unique_ptr<HistoryMedia> pointer) {
|
|
|
|
*this = std::move(pointer);
|
|
|
|
}
|
|
|
|
|
2017-03-05 13:39:10 +00:00
|
|
|
HistoryMediaPtr &HistoryMediaPtr::operator=(std::unique_ptr<HistoryMedia> pointer) {
|
|
|
|
if (_pointer) {
|
|
|
|
_pointer->detachFromParent();
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
2017-03-05 13:39:10 +00:00
|
|
|
_pointer = std::move(pointer);
|
|
|
|
if (_pointer) {
|
|
|
|
_pointer->attachToParent();
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
2017-03-05 13:39:10 +00:00
|
|
|
return *this;
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
2017-06-22 15:11:41 +00:00
|
|
|
HistoryMediaPtr::~HistoryMediaPtr() {
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
namespace internal {
|
|
|
|
|
2017-06-23 19:28:42 +00:00
|
|
|
TextSelection unshiftSelection(TextSelection selection, uint16 byLength) {
|
2016-10-02 13:54:27 +00:00
|
|
|
if (selection == FullSelection) {
|
|
|
|
return selection;
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
2017-06-23 19:28:42 +00:00
|
|
|
return ::unshiftSelection(selection, byLength);
|
2016-10-02 13:54:27 +00:00
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2017-06-23 19:28:42 +00:00
|
|
|
TextSelection shiftSelection(TextSelection selection, uint16 byLength) {
|
2016-10-02 13:54:27 +00:00
|
|
|
if (selection == FullSelection) {
|
|
|
|
return selection;
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
2017-06-23 19:28:42 +00:00
|
|
|
return ::shiftSelection(selection, byLength);
|
2016-10-02 13:54:27 +00:00
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
|
|
|
} // namespace internal
|
|
|
|
|
2017-08-25 15:17:46 +00:00
|
|
|
HistoryItem::HistoryItem(
|
|
|
|
not_null<History*> history,
|
|
|
|
MsgId id,
|
|
|
|
MTPDmessage::Flags flags,
|
|
|
|
QDateTime date,
|
|
|
|
UserId from) : HistoryElement()
|
|
|
|
, id(id)
|
|
|
|
, date(date)
|
2016-09-27 14:20:49 +00:00
|
|
|
, _history(history)
|
2017-06-21 08:53:14 +00:00
|
|
|
, _from(from ? App::user(from) : history->peer)
|
2017-12-05 16:14:28 +00:00
|
|
|
, _flags(flags | MTPDmessage_ClientFlag::f_pending_init_dimensions | MTPDmessage_ClientFlag::f_pending_resize) {
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryItem::finishCreate() {
|
|
|
|
App::historyRegItem(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryItem::finishEdition(int oldKeyboardTop) {
|
|
|
|
setPendingInitDimensions();
|
2017-12-16 09:27:45 +00:00
|
|
|
invalidateChatsListEntry();
|
|
|
|
//if (groupId()) {
|
|
|
|
// history()->fixGroupAfterEdition(this);
|
|
|
|
//}
|
|
|
|
if (isHiddenByGroup()) {
|
|
|
|
// Perhaps caption was changed, we should refresh the group.
|
|
|
|
const auto group = Get<HistoryMessageGroup>();
|
|
|
|
group->leader->setPendingInitDimensions();
|
|
|
|
group->leader->invalidateChatsListEntry();
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (oldKeyboardTop >= 0) {
|
|
|
|
if (auto keyboard = Get<HistoryMessageReplyMarkup>()) {
|
|
|
|
keyboard->oldTop = oldKeyboardTop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
App::historyUpdateDependent(this);
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
HistoryMessageReplyMarkup *HistoryItem::inlineReplyMarkup() {
|
|
|
|
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
|
|
|
|
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
|
|
|
|
return markup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
ReplyKeyboard *HistoryItem::inlineReplyKeyboard() {
|
|
|
|
if (const auto markup = inlineReplyMarkup()) {
|
|
|
|
return markup->inlineKeyboard.get();
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2017-12-16 09:27:45 +00:00
|
|
|
void HistoryItem::invalidateChatsListEntry() {
|
|
|
|
if (App::main()) {
|
|
|
|
App::main()->dlgUpdated(history()->peer, id);
|
|
|
|
}
|
|
|
|
|
|
|
|
// invalidate cache for drawInDialog
|
|
|
|
if (history()->textCachedFor == this) {
|
|
|
|
history()->textCachedFor = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
void HistoryItem::finishEditionToEmpty() {
|
|
|
|
recountDisplayDate();
|
|
|
|
finishEdition(-1);
|
|
|
|
|
|
|
|
_history->removeNotification(this);
|
2017-11-21 13:23:56 +00:00
|
|
|
if (auto channel = history()->peer->asChannel()) {
|
|
|
|
if (channel->pinnedMessageId() == id) {
|
|
|
|
channel->clearPinnedMessage();
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (history()->lastKeyboardId == id) {
|
|
|
|
history()->clearLastKeyboard();
|
|
|
|
}
|
|
|
|
if ((!out() || isPost()) && unread() && history()->unreadCount() > 0) {
|
|
|
|
history()->setUnreadCount(history()->unreadCount() - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (auto next = nextItem()) {
|
|
|
|
next->previousItemChanged();
|
|
|
|
}
|
2016-11-18 16:27:47 +00:00
|
|
|
if (auto previous = previousItem()) {
|
|
|
|
previous->nextItemChanged();
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
2017-11-20 19:54:05 +00:00
|
|
|
bool HistoryItem::isMediaUnread() const {
|
|
|
|
if (!mentionsMe() && _history->peer->isChannel()) {
|
|
|
|
auto now = ::date(unixtime());
|
|
|
|
auto passed = date.secsTo(now);
|
|
|
|
if (passed >= Global::ChannelsReadMediaPeriod()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return _flags & MTPDmessage::Flag::f_media_unread;
|
|
|
|
}
|
|
|
|
|
2017-08-25 12:48:10 +00:00
|
|
|
void HistoryItem::markMediaRead() {
|
|
|
|
_flags &= ~MTPDmessage::Flag::f_media_unread;
|
|
|
|
|
|
|
|
if (mentionsMe()) {
|
|
|
|
history()->updateChatListEntry();
|
|
|
|
history()->eraseFromUnreadMentions(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
bool HistoryItem::definesReplyKeyboard() const {
|
|
|
|
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
|
|
|
|
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// optimization: don't create markup component for the case
|
|
|
|
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
|
|
|
|
return (_flags & MTPDmessage::Flag::f_reply_markup);
|
|
|
|
}
|
|
|
|
|
|
|
|
MTPDreplyKeyboardMarkup::Flags HistoryItem::replyKeyboardFlags() const {
|
|
|
|
Expects(definesReplyKeyboard());
|
|
|
|
|
|
|
|
if (const 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 MTPDreplyKeyboardMarkup_ClientFlag::f_zero | 0;
|
|
|
|
}
|
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
void HistoryItem::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
|
|
|
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
|
|
|
|
if (markup->inlineKeyboard) {
|
|
|
|
markup->inlineKeyboard->clickHandlerActiveChanged(p, active);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
App::hoveredLinkItem(active ? this : nullptr);
|
2017-10-05 15:35:52 +00:00
|
|
|
Auth().data().requestItemRepaint(this);
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryItem::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
|
|
|
|
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
|
|
|
|
if (markup->inlineKeyboard) {
|
|
|
|
markup->inlineKeyboard->clickHandlerPressedChanged(p, pressed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
App::pressedLinkItem(pressed ? this : nullptr);
|
2017-10-05 15:35:52 +00:00
|
|
|
Auth().data().requestItemRepaint(this);
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
2017-06-20 19:48:53 +00:00
|
|
|
void HistoryItem::addLogEntryOriginal(WebPageId localId, const QString &label, const TextWithEntities &content) {
|
2017-06-18 13:08:49 +00:00
|
|
|
Expects(isLogEntry());
|
|
|
|
AddComponents(HistoryMessageLogEntryOriginal::Bit());
|
|
|
|
auto original = Get<HistoryMessageLogEntryOriginal>();
|
2017-06-20 19:48:53 +00:00
|
|
|
auto webpage = App::feedWebPage(localId, label, content);
|
|
|
|
original->_page = std::make_unique<HistoryWebPage>(this, webpage);
|
2017-06-18 13:08:49 +00:00
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
UserData *HistoryItem::viaBot() const {
|
|
|
|
if (const auto via = Get<HistoryMessageVia>()) {
|
|
|
|
return via->bot;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2017-06-18 13:08:49 +00:00
|
|
|
void HistoryItem::destroy() {
|
|
|
|
if (isLogEntry()) {
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(detached());
|
2017-06-18 13:08:49 +00:00
|
|
|
} else {
|
|
|
|
// All this must be done for all items manually in History::clear(false)!
|
2017-12-08 18:27:28 +00:00
|
|
|
eraseFromUnreadMentions();
|
2017-09-04 11:40:02 +00:00
|
|
|
if (IsServerMsgId(id)) {
|
2017-12-25 14:17:00 +00:00
|
|
|
if (const auto types = sharedMediaTypes()) {
|
2017-09-04 11:40:02 +00:00
|
|
|
Auth().storage().remove(Storage::SharedMediaRemoveOne(
|
|
|
|
history()->peer->id,
|
|
|
|
types,
|
|
|
|
id));
|
|
|
|
}
|
2017-12-25 14:17:00 +00:00
|
|
|
} else {
|
|
|
|
Auth().api().cancelLocalItem(this);
|
2017-09-04 11:40:02 +00:00
|
|
|
}
|
2017-06-18 13:08:49 +00:00
|
|
|
|
|
|
|
auto wasAtBottom = history()->loadedAtBottom();
|
|
|
|
_history->removeNotification(this);
|
|
|
|
detach();
|
2017-12-25 14:17:00 +00:00
|
|
|
if (const auto channel = history()->peer->asChannel()) {
|
2017-11-21 13:23:56 +00:00
|
|
|
if (channel->pinnedMessageId() == id) {
|
|
|
|
channel->clearPinnedMessage();
|
2017-06-18 13:08:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (history()->lastMsg == this) {
|
|
|
|
history()->fixLastMessage(wasAtBottom);
|
|
|
|
}
|
|
|
|
if (history()->lastKeyboardId == id) {
|
|
|
|
history()->clearLastKeyboard();
|
|
|
|
}
|
|
|
|
if ((!out() || isPost()) && unread() && history()->unreadCount() > 0) {
|
|
|
|
history()->setUnreadCount(history()->unreadCount() - 1);
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Global::RefPendingRepaintItems().remove(this);
|
|
|
|
delete this;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryItem::detach() {
|
|
|
|
if (detached()) return;
|
|
|
|
|
|
|
|
if (_history->isChannel()) {
|
|
|
|
_history->asChannelHistory()->messageDetached(this);
|
|
|
|
}
|
|
|
|
_block->removeItem(this);
|
|
|
|
App::historyItemDetached(this);
|
|
|
|
|
|
|
|
_history->setPendingResize();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryItem::detachFast() {
|
|
|
|
_block = nullptr;
|
|
|
|
_indexInBlock = -1;
|
2017-12-13 18:10:48 +00:00
|
|
|
|
|
|
|
validateGroupId();
|
|
|
|
if (groupId()) {
|
|
|
|
makeGroupLeader({});
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
2017-09-04 11:40:02 +00:00
|
|
|
Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2017-12-21 12:11:33 +00:00
|
|
|
void HistoryItem::indexAsNewItem() {
|
|
|
|
if (IsServerMsgId(id)) {
|
2018-01-01 20:23:18 +00:00
|
|
|
CrashReports::SetAnnotation("addToUnreadMentions", QString::number(id));
|
2017-12-21 12:11:33 +00:00
|
|
|
addToUnreadMentions(UnreadMentionType::New);
|
2018-01-01 20:23:18 +00:00
|
|
|
CrashReports::ClearAnnotation("addToUnreadMentions");
|
2017-12-21 12:11:33 +00:00
|
|
|
if (const auto types = sharedMediaTypes()) {
|
|
|
|
Auth().storage().add(Storage::SharedMediaAddNew(
|
|
|
|
history()->peer->id,
|
|
|
|
types,
|
|
|
|
id));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
void HistoryItem::previousItemChanged() {
|
2017-06-21 23:54:38 +00:00
|
|
|
Expects(!isLogEntry());
|
2016-09-27 14:20:49 +00:00
|
|
|
recountDisplayDate();
|
|
|
|
recountAttachToPrevious();
|
|
|
|
}
|
|
|
|
|
2016-11-18 16:27:47 +00:00
|
|
|
// Called only if there is no more next item! Not always when it changes!
|
|
|
|
void HistoryItem::nextItemChanged() {
|
2017-06-21 23:54:38 +00:00
|
|
|
Expects(!isLogEntry());
|
2016-11-18 16:27:47 +00:00
|
|
|
setAttachToNext(false);
|
|
|
|
}
|
|
|
|
|
2017-08-17 08:31:24 +00:00
|
|
|
bool HistoryItem::computeIsAttachToPrevious(not_null<HistoryItem*> previous) {
|
2017-06-22 01:31:02 +00:00
|
|
|
if (!Has<HistoryMessageDate>() && !Has<HistoryMessageUnreadBar>()) {
|
2017-12-05 16:14:28 +00:00
|
|
|
const auto possible = !isPost() && !previous->isPost()
|
2017-06-22 01:31:02 +00:00
|
|
|
&& !serviceMsg() && !previous->serviceMsg()
|
|
|
|
&& !isEmpty() && !previous->isEmpty()
|
|
|
|
&& (qAbs(previous->date.secsTo(date)) < kAttachMessageToPreviousSecondsDelta);
|
2017-12-05 16:14:28 +00:00
|
|
|
if (possible) {
|
|
|
|
if (history()->peer->isSelf()) {
|
|
|
|
return previous->senderOriginal() == senderOriginal()
|
|
|
|
&& (previous->Has<HistoryMessageForwarded>() == Has<HistoryMessageForwarded>());
|
|
|
|
} else {
|
|
|
|
return previous->from() == from();
|
|
|
|
}
|
|
|
|
}
|
2017-06-22 01:31:02 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
void HistoryItem::recountAttachToPrevious() {
|
2017-06-21 23:54:38 +00:00
|
|
|
Expects(!isLogEntry());
|
|
|
|
auto attachToPrevious = false;
|
2016-11-18 16:27:47 +00:00
|
|
|
if (auto previous = previousItem()) {
|
2017-06-22 01:31:02 +00:00
|
|
|
attachToPrevious = computeIsAttachToPrevious(previous);
|
2017-06-21 23:54:38 +00:00
|
|
|
previous->setAttachToNext(attachToPrevious);
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
2017-06-21 23:54:38 +00:00
|
|
|
setAttachToPrevious(attachToPrevious);
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
2016-11-18 16:27:47 +00:00
|
|
|
void HistoryItem::setAttachToNext(bool attachToNext) {
|
|
|
|
if (attachToNext && !(_flags & MTPDmessage_ClientFlag::f_attach_to_next)) {
|
|
|
|
_flags |= MTPDmessage_ClientFlag::f_attach_to_next;
|
|
|
|
Global::RefPendingRepaintItems().insert(this);
|
|
|
|
} else if (!attachToNext && (_flags & MTPDmessage_ClientFlag::f_attach_to_next)) {
|
|
|
|
_flags &= ~MTPDmessage_ClientFlag::f_attach_to_next;
|
|
|
|
Global::RefPendingRepaintItems().insert(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-21 23:54:38 +00:00
|
|
|
void HistoryItem::setAttachToPrevious(bool attachToPrevious) {
|
|
|
|
if (attachToPrevious && !(_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) {
|
|
|
|
_flags |= MTPDmessage_ClientFlag::f_attach_to_previous;
|
|
|
|
setPendingInitDimensions();
|
|
|
|
} else if (!attachToPrevious && (_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) {
|
|
|
|
_flags &= ~MTPDmessage_ClientFlag::f_attach_to_previous;
|
|
|
|
setPendingInitDimensions();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
void HistoryItem::setId(MsgId newId) {
|
|
|
|
history()->changeMsgId(id, newId);
|
|
|
|
id = newId;
|
|
|
|
|
|
|
|
// We don't need to call Notify::replyMarkupUpdated(this) and update keyboard
|
|
|
|
// in history widget, because it can't exist for an outgoing message.
|
|
|
|
// Only inline keyboards can be in outgoing messages.
|
|
|
|
if (auto markup = inlineReplyMarkup()) {
|
|
|
|
if (markup->inlineKeyboard) {
|
|
|
|
markup->inlineKeyboard->updateMessageId();
|
|
|
|
}
|
|
|
|
}
|
2017-06-24 10:32:30 +00:00
|
|
|
|
|
|
|
if (_media) {
|
2017-12-18 13:13:41 +00:00
|
|
|
_media->refreshParentId(this);
|
|
|
|
if (const auto group = Get<HistoryMessageGroup>()) {
|
|
|
|
if (group->leader != this) {
|
|
|
|
if (const auto media = group->leader->getMedia()) {
|
|
|
|
media->refreshParentId(group->leader);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-24 10:32:30 +00:00
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
2017-11-21 13:23:56 +00:00
|
|
|
bool HistoryItem::isPinned() const {
|
|
|
|
if (auto channel = _history->peer->asChannel()) {
|
|
|
|
return (channel->pinnedMessageId() == id);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-04-30 13:58:45 +00:00
|
|
|
bool HistoryItem::canPin() const {
|
2017-11-21 13:23:56 +00:00
|
|
|
if (id < 0 || !toHistoryMessage()) {
|
2017-06-04 11:09:29 +00:00
|
|
|
return false;
|
|
|
|
}
|
2017-11-21 13:23:56 +00:00
|
|
|
if (auto channel = _history->peer->asChannel()) {
|
2017-06-04 11:09:29 +00:00
|
|
|
return channel->canPinMessages();
|
|
|
|
}
|
|
|
|
return false;
|
2017-04-30 13:58:45 +00:00
|
|
|
}
|
|
|
|
|
2017-04-27 21:17:00 +00:00
|
|
|
bool HistoryItem::canForward() const {
|
2017-06-29 09:32:19 +00:00
|
|
|
if (id < 0 || isLogEntry()) {
|
2017-04-27 21:17:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (auto message = toHistoryMessage()) {
|
|
|
|
if (auto media = message->getMedia()) {
|
|
|
|
if (media->type() == MediaTypeCall) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
bool HistoryItem::canEdit(const QDateTime &cur) const {
|
2017-06-04 11:09:29 +00:00
|
|
|
auto messageToMyself = _history->peer->isSelf();
|
2017-11-21 13:23:56 +00:00
|
|
|
auto canPinInMegagroup = [&] {
|
|
|
|
if (auto megagroup = _history->peer->asMegagroup()) {
|
|
|
|
return megagroup->canPinMessages();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}();
|
|
|
|
auto messageTooOld = (messageToMyself || canPinInMegagroup)
|
2017-11-20 19:54:05 +00:00
|
|
|
? false
|
|
|
|
: (date.secsTo(cur) >= Global::EditTimeLimit());
|
2017-04-02 10:25:54 +00:00
|
|
|
if (id < 0 || messageTooOld) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2017-06-04 11:09:29 +00:00
|
|
|
if (auto message = toHistoryMessage()) {
|
|
|
|
if (message->Has<HistoryMessageVia>() || message->Has<HistoryMessageForwarded>()) {
|
2017-04-02 10:25:54 +00:00
|
|
|
return false;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2017-06-04 11:09:29 +00:00
|
|
|
if (auto media = message->getMedia()) {
|
2017-04-21 10:56:59 +00:00
|
|
|
if (!media->canEditCaption() && media->type() != MediaTypeWebPage) {
|
2016-09-27 14:20:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2017-06-09 16:12:02 +00:00
|
|
|
if (messageToMyself) {
|
2017-06-04 11:09:29 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (auto channel = _history->peer->asChannel()) {
|
2017-07-03 10:13:32 +00:00
|
|
|
if (isPost() && channel->canEditMessages()) {
|
2017-06-09 16:12:02 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (out()) {
|
|
|
|
return !isPost() || channel->canPublish();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return out();
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-04-30 13:58:45 +00:00
|
|
|
bool HistoryItem::canDelete() const {
|
2017-06-29 09:32:19 +00:00
|
|
|
if (isLogEntry()) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-04-30 13:58:45 +00:00
|
|
|
auto channel = _history->peer->asChannel();
|
|
|
|
if (!channel) {
|
|
|
|
return !(_flags & MTPDmessage_ClientFlag::f_is_group_migrate);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (id == 1) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-06-09 16:12:02 +00:00
|
|
|
if (channel->canDeleteMessages()) {
|
2017-04-30 13:58:45 +00:00
|
|
|
return true;
|
|
|
|
}
|
2017-06-09 16:12:02 +00:00
|
|
|
if (out() && toHistoryMessage()) {
|
|
|
|
return isPost() ? channel->canPublish() : true;
|
2017-04-30 13:58:45 +00:00
|
|
|
}
|
2017-06-09 16:12:02 +00:00
|
|
|
return false;
|
2017-04-30 13:58:45 +00:00
|
|
|
}
|
|
|
|
|
2016-12-31 15:19:22 +00:00
|
|
|
bool HistoryItem::canDeleteForEveryone(const QDateTime &cur) const {
|
2017-06-04 11:09:29 +00:00
|
|
|
auto messageToMyself = _history->peer->isSelf();
|
2016-12-31 15:19:22 +00:00
|
|
|
auto messageTooOld = messageToMyself ? false : (date.secsTo(cur) >= Global::EditTimeLimit());
|
2017-04-30 13:58:45 +00:00
|
|
|
if (id < 0 || messageToMyself || messageTooOld || isPost()) {
|
2017-04-02 10:25:54 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (history()->peer->isChannel()) {
|
|
|
|
return false;
|
2017-11-30 11:06:30 +00:00
|
|
|
} else if (auto user = history()->peer->asUser()) {
|
|
|
|
// Bots receive all messages and there is no sense in revoking them.
|
|
|
|
// See https://github.com/telegramdesktop/tdesktop/issues/3818
|
|
|
|
if (user->botInfo) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-04-02 10:25:54 +00:00
|
|
|
}
|
2017-04-30 13:58:45 +00:00
|
|
|
if (!toHistoryMessage()) {
|
|
|
|
return false;
|
2016-12-31 15:19:22 +00:00
|
|
|
}
|
2017-04-27 21:17:00 +00:00
|
|
|
if (auto media = getMedia()) {
|
|
|
|
if (media->type() == MediaTypeCall) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2017-04-30 13:58:45 +00:00
|
|
|
if (!out()) {
|
|
|
|
if (auto chat = history()->peer->asChat()) {
|
|
|
|
if (!chat->amCreator() && (!chat->amAdmin() || !chat->adminsEnabled())) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
2016-12-31 15:19:22 +00:00
|
|
|
}
|
|
|
|
|
2017-06-04 11:09:29 +00:00
|
|
|
bool HistoryItem::suggestBanReport() const {
|
|
|
|
auto channel = history()->peer->asChannel();
|
2017-06-09 16:12:02 +00:00
|
|
|
auto fromUser = from()->asUser();
|
|
|
|
if (!channel || !fromUser || !channel->canRestrictUser(fromUser)) {
|
2017-06-04 11:09:29 +00:00
|
|
|
return false;
|
|
|
|
}
|
2017-06-09 16:12:02 +00:00
|
|
|
return !isPost() && !out() && toHistoryMessage();
|
2017-06-04 11:09:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool HistoryItem::suggestDeleteAllReport() const {
|
|
|
|
auto channel = history()->peer->asChannel();
|
|
|
|
if (!channel || !channel->canDeleteMessages()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return !isPost() && !out() && from()->isUser() && toHistoryMessage();
|
|
|
|
}
|
|
|
|
|
2017-07-11 17:21:24 +00:00
|
|
|
bool HistoryItem::hasDirectLink() const {
|
|
|
|
if (id <= 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (auto channel = _history->peer->asChannel()) {
|
|
|
|
return channel->isPublic();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-03-10 17:25:43 +00:00
|
|
|
QString HistoryItem::directLink() const {
|
2017-04-30 13:31:11 +00:00
|
|
|
if (hasDirectLink()) {
|
2017-07-11 17:21:24 +00:00
|
|
|
auto channel = _history->peer->asChannel();
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(channel != nullptr);
|
2017-07-11 17:21:24 +00:00
|
|
|
auto query = channel->username + '/' + QString::number(id);
|
|
|
|
if (!channel->isMegagroup()) {
|
|
|
|
if (auto media = getMedia()) {
|
|
|
|
if (auto document = media->getDocument()) {
|
2017-12-10 10:26:58 +00:00
|
|
|
if (document->isVideoMessage()) {
|
2017-07-11 17:21:24 +00:00
|
|
|
return qsl("https://telesco.pe/") + query;
|
|
|
|
}
|
2017-04-30 13:31:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Messenger::Instance().createInternalLinkFull(query);
|
|
|
|
}
|
|
|
|
return QString();
|
2017-03-10 17:25:43 +00:00
|
|
|
}
|
2017-04-30 13:31:11 +00:00
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
MsgId HistoryItem::replyToId() const {
|
|
|
|
if (auto reply = Get<HistoryMessageReply>()) {
|
|
|
|
return reply->replyToId();
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
QDateTime HistoryItem::dateOriginal() const {
|
|
|
|
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
|
|
|
return forwarded->originalDate;
|
|
|
|
}
|
|
|
|
return date;
|
|
|
|
}
|
|
|
|
|
|
|
|
PeerData *HistoryItem::senderOriginal() const {
|
|
|
|
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
|
|
|
return forwarded->originalSender;
|
|
|
|
}
|
|
|
|
const auto peer = history()->peer;
|
|
|
|
return (peer->isChannel() && !peer->isMegagroup()) ? peer : from();
|
|
|
|
}
|
|
|
|
|
|
|
|
PeerData *HistoryItem::fromOriginal() const {
|
|
|
|
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
|
|
|
if (const auto user = forwarded->originalSender->asUser()) {
|
|
|
|
return user;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return from();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString HistoryItem::authorOriginal() const {
|
|
|
|
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
|
|
|
return forwarded->originalAuthor;
|
|
|
|
} else if (const auto msgsigned = Get<HistoryMessageSigned>()) {
|
|
|
|
return msgsigned->author;
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
MsgId HistoryItem::idOriginal() const {
|
|
|
|
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
|
|
|
return forwarded->originalId;
|
|
|
|
}
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2017-12-05 16:14:28 +00:00
|
|
|
bool HistoryItem::hasOutLayout() const {
|
|
|
|
if (history()->peer->isSelf()) {
|
|
|
|
return !Has<HistoryMessageForwarded>();
|
|
|
|
}
|
|
|
|
return out() && !isPost();
|
|
|
|
}
|
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
bool HistoryItem::unread() const {
|
|
|
|
// Messages from myself are always read.
|
|
|
|
if (history()->peer->isSelf()) return false;
|
|
|
|
|
|
|
|
if (out()) {
|
|
|
|
// Outgoing messages in converted chats are always read.
|
2017-04-02 10:25:54 +00:00
|
|
|
if (history()->peer->migrateTo()) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
|
|
|
if (id > 0) {
|
2017-04-02 10:25:54 +00:00
|
|
|
if (id < history()->outboxReadBefore) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
if (auto user = history()->peer->asUser()) {
|
2017-04-02 10:25:54 +00:00
|
|
|
if (user->botInfo) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
} else if (auto channel = history()->peer->asChannel()) {
|
2017-04-02 10:25:54 +00:00
|
|
|
if (!channel->isMegagroup()) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (id > 0) {
|
2017-04-02 10:25:54 +00:00
|
|
|
if (id < history()->inboxReadBefore) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return (_flags & MTPDmessage_ClientFlag::f_clientside_unread);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryItem::destroyUnreadBar() {
|
|
|
|
if (Has<HistoryMessageUnreadBar>()) {
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(!isLogEntry());
|
2017-06-21 23:54:38 +00:00
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
RemoveComponents(HistoryMessageUnreadBar::Bit());
|
|
|
|
setPendingInitDimensions();
|
|
|
|
if (_history->unreadBar == this) {
|
|
|
|
_history->unreadBar = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
recountAttachToPrevious();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryItem::setUnreadBarCount(int count) {
|
2017-06-21 23:54:38 +00:00
|
|
|
Expects(!isLogEntry());
|
2016-09-27 14:20:49 +00:00
|
|
|
if (count > 0) {
|
|
|
|
HistoryMessageUnreadBar *bar;
|
|
|
|
if (!Has<HistoryMessageUnreadBar>()) {
|
|
|
|
AddComponents(HistoryMessageUnreadBar::Bit());
|
|
|
|
setPendingInitDimensions();
|
|
|
|
|
|
|
|
recountAttachToPrevious();
|
|
|
|
|
|
|
|
bar = Get<HistoryMessageUnreadBar>();
|
|
|
|
} else {
|
|
|
|
bar = Get<HistoryMessageUnreadBar>();
|
|
|
|
if (bar->_freezed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Global::RefPendingRepaintItems().insert(this);
|
|
|
|
}
|
|
|
|
bar->init(count);
|
|
|
|
} else {
|
|
|
|
destroyUnreadBar();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryItem::setUnreadBarFreezed() {
|
2017-06-21 23:54:38 +00:00
|
|
|
Expects(!isLogEntry());
|
2017-12-18 15:44:50 +00:00
|
|
|
|
|
|
|
if (const auto bar = Get<HistoryMessageUnreadBar>()) {
|
2016-09-27 14:20:49 +00:00
|
|
|
bar->_freezed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
MessageGroupId HistoryItem::groupId() const {
|
|
|
|
if (const auto group = Get<HistoryMessageGroup>()) {
|
|
|
|
return group->groupId;
|
|
|
|
}
|
|
|
|
return MessageGroupId::None;
|
|
|
|
}
|
|
|
|
|
2017-12-13 18:10:48 +00:00
|
|
|
bool HistoryItem::groupIdValidityChanged() {
|
|
|
|
if (Has<HistoryMessageGroup>()) {
|
|
|
|
if (_media && _media->canBeGrouped()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
RemoveComponents(HistoryMessageGroup::Bit());
|
|
|
|
setPendingInitDimensions();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryItem::makeGroupMember(not_null<HistoryItem*> leader) {
|
|
|
|
Expects(leader != this);
|
|
|
|
|
|
|
|
const auto group = Get<HistoryMessageGroup>();
|
|
|
|
Assert(group != nullptr);
|
|
|
|
if (group->leader == this) {
|
|
|
|
if (auto single = _media ? _media->takeLastFromGroup() : nullptr) {
|
|
|
|
_media = std::move(single);
|
|
|
|
}
|
|
|
|
_flags |= MTPDmessage_ClientFlag::f_hidden_by_group;
|
|
|
|
setPendingInitDimensions();
|
|
|
|
|
|
|
|
group->leader = leader;
|
|
|
|
base::take(group->others);
|
|
|
|
} else if (group->leader != leader) {
|
|
|
|
group->leader = leader;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ensures(isHiddenByGroup());
|
|
|
|
Ensures(group->others.empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryItem::makeGroupLeader(
|
|
|
|
std::vector<not_null<HistoryItem*>> &&others) {
|
|
|
|
const auto group = Get<HistoryMessageGroup>();
|
|
|
|
Assert(group != nullptr);
|
|
|
|
|
2017-12-16 16:32:10 +00:00
|
|
|
const auto leaderChanged = (group->leader != this);
|
|
|
|
if (leaderChanged) {
|
2017-12-17 08:33:08 +00:00
|
|
|
group->leader = this;
|
2017-12-13 18:10:48 +00:00
|
|
|
_flags &= ~MTPDmessage_ClientFlag::f_hidden_by_group;
|
|
|
|
setPendingInitDimensions();
|
|
|
|
}
|
|
|
|
group->others = std::move(others);
|
|
|
|
if (!_media || !_media->applyGroup(group->others)) {
|
|
|
|
resetGroupMedia(group->others);
|
2017-12-16 09:27:45 +00:00
|
|
|
invalidateChatsListEntry();
|
2017-12-13 18:10:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ensures(!isHiddenByGroup());
|
|
|
|
}
|
|
|
|
|
2017-12-15 21:23:20 +00:00
|
|
|
HistoryMessageGroup *HistoryItem::getFullGroup() {
|
|
|
|
if (const auto group = Get<HistoryMessageGroup>()) {
|
|
|
|
if (group->leader == this) {
|
|
|
|
return group;
|
|
|
|
}
|
|
|
|
return group->leader->Get<HistoryMessageGroup>();
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2017-12-13 18:10:48 +00:00
|
|
|
void HistoryItem::resetGroupMedia(
|
|
|
|
const std::vector<not_null<HistoryItem*>> &others) {
|
|
|
|
if (!others.empty()) {
|
|
|
|
_media = std::make_unique<HistoryGroupedMedia>(this, others);
|
|
|
|
} else if (_media) {
|
|
|
|
_media = _media->takeLastFromGroup();
|
|
|
|
}
|
|
|
|
setPendingInitDimensions();
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
int HistoryItem::displayedDateHeight() const {
|
|
|
|
if (auto date = Get<HistoryMessageDate>()) {
|
|
|
|
return date->height();
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-12-13 18:10:48 +00:00
|
|
|
int HistoryItem::marginTop() const {
|
|
|
|
auto result = 0;
|
|
|
|
if (!isHiddenByGroup()) {
|
|
|
|
if (isAttachedToPrevious()) {
|
|
|
|
result += st::msgMarginTopAttached;
|
|
|
|
} else {
|
|
|
|
result += st::msgMargin.top();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result += displayedDateHeight();
|
|
|
|
if (const auto unreadbar = Get<HistoryMessageUnreadBar>()) {
|
|
|
|
result += unreadbar->height();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
bool HistoryItem::displayDate() const {
|
|
|
|
return Has<HistoryMessageDate>();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool HistoryItem::isEmpty() const {
|
|
|
|
return _text.isEmpty()
|
|
|
|
&& !_media
|
|
|
|
&& !Has<HistoryMessageLogEntryOriginal>();
|
|
|
|
}
|
|
|
|
|
2017-12-13 18:10:48 +00:00
|
|
|
int HistoryItem::marginBottom() const {
|
|
|
|
return isHiddenByGroup() ? 0 : st::msgMargin.bottom();
|
|
|
|
}
|
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
void HistoryItem::clipCallback(Media::Clip::Notification notification) {
|
|
|
|
using namespace Media::Clip;
|
|
|
|
|
2017-04-02 10:25:54 +00:00
|
|
|
auto media = getMedia();
|
|
|
|
if (!media) {
|
|
|
|
return;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2017-05-18 20:18:59 +00:00
|
|
|
auto reader = media->getClipReader();
|
2017-04-02 10:25:54 +00:00
|
|
|
if (!reader) {
|
|
|
|
return;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
|
|
|
switch (notification) {
|
|
|
|
case NotificationReinit: {
|
2017-04-02 16:42:18 +00:00
|
|
|
auto stopped = false;
|
2016-09-27 14:20:49 +00:00
|
|
|
if (reader->autoPausedGif()) {
|
2017-06-21 23:54:38 +00:00
|
|
|
auto amVisible = false;
|
2017-08-04 14:54:32 +00:00
|
|
|
Auth().data().queryItemVisibility().notify({ this, &amVisible }, true);
|
2017-06-21 23:54:38 +00:00
|
|
|
if (!amVisible) { // stop animation if it is not visible
|
|
|
|
media->stopInline();
|
|
|
|
if (auto document = media->getDocument()) { // forget data from memory
|
|
|
|
document->forget();
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
2017-06-21 23:54:38 +00:00
|
|
|
stopped = true;
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
2017-04-02 16:42:18 +00:00
|
|
|
} else if (reader->mode() == Media::Clip::Reader::Mode::Video && reader->state() == Media::Clip::State::Finished) {
|
|
|
|
// Stop finished video message.
|
|
|
|
media->stopInline();
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
if (!stopped) {
|
|
|
|
setPendingInitDimensions();
|
2017-12-09 10:02:51 +00:00
|
|
|
if (detached()) {
|
|
|
|
// We still want to handle our pending initDimensions and
|
|
|
|
// resize state even if we're detached in history.
|
|
|
|
_history->setHasPendingResizedItems();
|
|
|
|
}
|
2017-10-05 15:35:52 +00:00
|
|
|
Auth().data().markItemLayoutChanged(this);
|
2017-05-22 15:25:49 +00:00
|
|
|
Global::RefPendingRepaintItems().insert(this);
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case NotificationRepaint: {
|
|
|
|
if (!reader->currentDisplayed()) {
|
2017-10-05 15:35:52 +00:00
|
|
|
Auth().data().requestItemRepaint(this);
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
} break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-18 20:18:59 +00:00
|
|
|
void HistoryItem::audioTrackUpdated() {
|
|
|
|
auto media = getMedia();
|
|
|
|
if (!media) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto reader = media->getClipReader();
|
|
|
|
if (!reader || reader->mode() != Media::Clip::Reader::Mode::Video) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto audio = reader->audioMsgId();
|
|
|
|
auto current = Media::Player::mixer()->currentState(audio.type());
|
2017-05-21 13:16:39 +00:00
|
|
|
if (current.id != audio || Media::Player::IsStoppedOrStopping(current.state)) {
|
2017-05-18 20:18:59 +00:00
|
|
|
media->stopInline();
|
|
|
|
} else if (Media::Player::IsPaused(current.state) || current.state == Media::Player::State::Pausing) {
|
|
|
|
if (!reader->videoPaused()) {
|
|
|
|
reader->pauseResumeVideo();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (reader->videoPaused()) {
|
|
|
|
reader->pauseResumeVideo();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
void HistoryItem::recountDisplayDate() {
|
2017-06-21 23:54:38 +00:00
|
|
|
Expects(!isLogEntry());
|
2017-12-01 18:38:44 +00:00
|
|
|
setDisplayDate([&] {
|
2017-04-02 10:25:54 +00:00
|
|
|
if (isEmpty()) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
|
|
|
if (auto previous = previousItem()) {
|
|
|
|
return previous->isEmpty() || (previous->date.date() != date.date());
|
|
|
|
}
|
|
|
|
return true;
|
2017-12-01 18:38:44 +00:00
|
|
|
}());
|
2017-06-21 23:54:38 +00:00
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2017-06-21 23:54:38 +00:00
|
|
|
void HistoryItem::setDisplayDate(bool displayDate) {
|
|
|
|
if (displayDate && !Has<HistoryMessageDate>()) {
|
2016-09-27 14:20:49 +00:00
|
|
|
AddComponents(HistoryMessageDate::Bit());
|
|
|
|
Get<HistoryMessageDate>()->init(date);
|
|
|
|
setPendingInitDimensions();
|
2017-06-21 23:54:38 +00:00
|
|
|
} else if (!displayDate && Has<HistoryMessageDate>()) {
|
2016-09-27 14:20:49 +00:00
|
|
|
RemoveComponents(HistoryMessageDate::Bit());
|
|
|
|
setPendingInitDimensions();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString HistoryItem::notificationText() const {
|
|
|
|
auto getText = [this]() {
|
|
|
|
if (emptyText()) {
|
|
|
|
return _media ? _media->notificationText() : QString();
|
|
|
|
}
|
|
|
|
return _text.originalText();
|
|
|
|
};
|
|
|
|
|
|
|
|
auto result = getText();
|
2017-04-02 10:25:54 +00:00
|
|
|
if (result.size() > 0xFF) {
|
|
|
|
result = result.mid(0, 0xFF) + qsl("...");
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-09-05 17:21:56 +00:00
|
|
|
QString HistoryItem::inDialogsText(DrawInDialog way) const {
|
2016-09-27 14:20:49 +00:00
|
|
|
auto getText = [this]() {
|
|
|
|
if (emptyText()) {
|
|
|
|
return _media ? _media->inDialogsText() : QString();
|
|
|
|
}
|
2017-07-06 11:37:42 +00:00
|
|
|
return TextUtilities::Clean(_text.originalText());
|
2016-09-27 14:20:49 +00:00
|
|
|
};
|
2017-12-05 16:38:13 +00:00
|
|
|
const auto plainText = getText();
|
|
|
|
const auto sender = [&]() -> PeerData* {
|
|
|
|
if (isPost() || isEmpty() || (way == DrawInDialog::WithoutSender)) {
|
|
|
|
return nullptr;
|
|
|
|
} else if (!_history->peer->isUser() || out()) {
|
|
|
|
return author();
|
|
|
|
} else if (_history->peer->isSelf() && !hasOutLayout()) {
|
|
|
|
return senderOriginal();
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}();
|
|
|
|
if (sender) {
|
|
|
|
auto fromText = sender->isSelf() ? lang(lng_from_you) : sender->shortName();
|
2017-07-06 11:37:42 +00:00
|
|
|
auto fromWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, TextUtilities::Clean(fromText)));
|
2016-09-27 14:20:49 +00:00
|
|
|
return lng_dialogs_text_with_from(lt_from_part, fromWrapped, lt_message, plainText);
|
|
|
|
}
|
|
|
|
return plainText;
|
|
|
|
}
|
|
|
|
|
2017-09-05 17:21:56 +00:00
|
|
|
void HistoryItem::drawInDialog(
|
|
|
|
Painter &p,
|
|
|
|
const QRect &r,
|
|
|
|
bool active,
|
|
|
|
bool selected,
|
|
|
|
DrawInDialog way,
|
|
|
|
const HistoryItem *&cacheFor,
|
|
|
|
Text &cache) const {
|
2016-09-27 14:20:49 +00:00
|
|
|
if (cacheFor != this) {
|
|
|
|
cacheFor = this;
|
2017-12-28 13:06:06 +00:00
|
|
|
cache.setText(st::dialogsTextStyle, inDialogsText(way), Ui::DialogTextOptions());
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
if (r.width()) {
|
2016-12-23 13:21:01 +00:00
|
|
|
p.setTextPalette(active ? st::dialogsTextPaletteActive : (selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette));
|
2016-09-27 14:20:49 +00:00
|
|
|
p.setFont(st::dialogsTextFont);
|
2016-11-05 08:36:24 +00:00
|
|
|
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
2016-09-27 14:20:49 +00:00
|
|
|
cache.drawElided(p, r.left(), r.top(), r.width(), r.height() / st::dialogsTextFont->height);
|
2016-12-23 13:21:01 +00:00
|
|
|
p.restoreTextPalette();
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
HistoryItem::~HistoryItem() {
|
|
|
|
App::historyUnregItem(this);
|
2017-08-04 14:54:32 +00:00
|
|
|
if (id < 0 && !App::quitting()) {
|
|
|
|
Auth().uploader().cancel(fullId());
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-16 10:44:06 +00:00
|
|
|
ClickHandlerPtr goToMessageClickHandler(PeerData *peer, MsgId msgId) {
|
2017-12-18 09:07:18 +00:00
|
|
|
return std::make_shared<LambdaClickHandler>([peer, msgId] {
|
2016-11-16 10:44:06 +00:00
|
|
|
if (App::main()) {
|
|
|
|
auto current = App::mousedItem();
|
|
|
|
if (current && current->history()->peer == peer) {
|
|
|
|
App::main()->pushReplyReturn(current);
|
|
|
|
}
|
2017-10-03 13:05:58 +00:00
|
|
|
App::wnd()->controller()->showPeerHistory(
|
|
|
|
peer,
|
|
|
|
Window::SectionShow::Way::Forward,
|
|
|
|
msgId);
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
2016-11-16 10:44:06 +00:00
|
|
|
});
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|