tdesktop/Telegram/SourceFiles/history/history_item.h

1063 lines
28 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
*/
#pragma once
#include "base/runtime_composer.h"
namespace Ui {
class RippleAnimation;
} // namespace Ui
namespace style {
struct BotKeyboardButton;
struct RippleAnimation;
} // namespace style
class HistoryElement {
public:
HistoryElement() = default;
HistoryElement(const HistoryElement &other) = delete;
HistoryElement &operator=(const HistoryElement &other) = delete;
int maxWidth() const {
return _maxw;
}
int minHeight() const {
return _minh;
}
int height() const {
return _height;
}
virtual ~HistoryElement() = default;
protected:
mutable int _maxw = 0;
mutable int _minh = 0;
mutable int _height = 0;
};
class HistoryMessage;
enum HistoryCursorState {
HistoryDefaultCursorState,
HistoryInTextCursorState,
HistoryInDateCursorState,
HistoryInForwardedCursorState,
};
struct HistoryTextState {
HistoryTextState() = default;
HistoryTextState(const Text::StateResult &state)
: cursor(state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState)
, link(state.link)
, afterSymbol(state.afterSymbol)
, symbol(state.symbol) {
}
HistoryTextState &operator=(const Text::StateResult &state) {
cursor = state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState;
link = state.link;
afterSymbol = state.afterSymbol;
symbol = state.symbol;
return *this;
}
HistoryCursorState cursor = HistoryDefaultCursorState;
ClickHandlerPtr link;
bool afterSymbol = false;
uint16 symbol = 0;
};
struct HistoryStateRequest {
Text::StateRequest::Flags flags = Text::StateRequest::Flag::LookupLink;
Text::StateRequest forText() const {
Text::StateRequest result;
result.flags = flags;
return result;
}
};
enum InfoDisplayType {
InfoDisplayDefault,
InfoDisplayOverImage,
InfoDisplayOverBackground,
};
struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia> {
void create(int32 userId);
void resize(int32 availw) const;
UserData *_bot = nullptr;
mutable QString _text;
mutable int _width = 0;
mutable int _maxWidth = 0;
ClickHandlerPtr _lnk;
};
struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews> {
QString _viewsText;
int _views = 0;
int _viewsWidth = 0;
};
struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned> {
void create(const QString &author, const QString &date);
int maxWidth() const;
QString _author;
Text _signature;
};
struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited> {
void create(const QDateTime &editDate, const QString &date);
int maxWidth() const;
QDateTime _editDate;
Text _edited;
};
struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded> {
void create(const HistoryMessageVia *via) const;
QDateTime _originalDate;
PeerData *_originalSender = nullptr;
QString _originalAuthor;
MsgId _originalId = 0;
mutable Text _text = { 1 };
};
struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply> {
HistoryMessageReply &operator=(HistoryMessageReply &&other) {
replyToMsgId = other.replyToMsgId;
std::swap(replyToMsg, other.replyToMsg);
replyToLnk = std::move(other.replyToLnk);
replyToName = std::move(other.replyToName);
replyToText = std::move(other.replyToText);
replyToVersion = other.replyToVersion;
_maxReplyWidth = other._maxReplyWidth;
_replyToVia = std::move(other._replyToVia);
return *this;
}
~HistoryMessageReply() {
// clearData() should be called by holder
Expects(replyToMsg == nullptr);
Expects(_replyToVia == nullptr);
}
bool updateData(HistoryMessage *holder, bool force = false);
void clearData(HistoryMessage *holder); // must be called before destructor
bool isNameUpdated() const;
void updateName() const;
void resize(int width) const;
void itemRemoved(HistoryMessage *holder, HistoryItem *removed);
enum PaintFlag {
PaintInBubble = 0x01,
PaintSelected = 0x02,
};
using PaintFlags = QFlags<PaintFlag>;
void paint(Painter &p, const HistoryItem *holder, int x, int y, int w, PaintFlags flags) const;
MsgId replyToId() const {
return replyToMsgId;
}
int replyToWidth() const {
return _maxReplyWidth;
}
ClickHandlerPtr replyToLink() const {
return replyToLnk;
}
MsgId replyToMsgId = 0;
HistoryItem *replyToMsg = nullptr;
ClickHandlerPtr replyToLnk;
mutable Text replyToName, replyToText;
mutable int replyToVersion = 0;
mutable int _maxReplyWidth = 0;
std::unique_ptr<HistoryMessageVia> _replyToVia;
int toWidth = 0;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(HistoryMessageReply::PaintFlags);
class ReplyKeyboard;
struct HistoryMessageReplyMarkup : public RuntimeComponent<HistoryMessageReplyMarkup> {
HistoryMessageReplyMarkup() = default;
HistoryMessageReplyMarkup(MTPDreplyKeyboardMarkup::Flags f) : flags(f) {
}
void create(const MTPReplyMarkup &markup);
void create(const HistoryMessageReplyMarkup &markup);
struct Button {
enum class Type {
Default,
Url,
Callback,
RequestPhone,
RequestLocation,
SwitchInline,
SwitchInlineSame,
Game,
Buy,
};
Type type;
QString text;
QByteArray data;
mutable mtpRequestId requestId;
};
using ButtonRow = QVector<Button>;
using ButtonRows = QVector<ButtonRow>;
ButtonRows rows;
MTPDreplyKeyboardMarkup::Flags flags = 0;
std::unique_ptr<ReplyKeyboard> inlineKeyboard;
// If >= 0 it holds the y coord of the inlineKeyboard before the last edition.
int oldTop = -1;
private:
void createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v);
};
class ReplyMarkupClickHandler : public LeftButtonClickHandler {
public:
ReplyMarkupClickHandler(const HistoryItem *item, int row, int col);
QString tooltip() const override {
return _fullDisplayed ? QString() : buttonText();
}
void setFullDisplayed(bool full) {
_fullDisplayed = full;
}
// Copy to clipboard support.
void copyToClipboard() const override;
QString copyToClipboardContextItemText() const override;
// Finds the corresponding button in the items markup struct.
// If the button is not found it returns nullptr.
// Note: it is possible that we will point to the different button
// than the one was used when constructing the handler, but not a big deal.
const HistoryMessageReplyMarkup::Button *getButton() const;
// We hold only FullMsgId, not HistoryItem*, because all click handlers
// are activated async and the item may be already destroyed.
void setMessageId(const FullMsgId &msgId) {
_itemId = msgId;
}
protected:
void onClickImpl() const override;
private:
FullMsgId _itemId;
int _row, _col;
bool _fullDisplayed = true;
// Returns the full text of the corresponding button.
QString buttonText() const;
};
class ReplyKeyboard {
private:
struct Button;
public:
class Style {
public:
Style(const style::BotKeyboardButton &st) : _st(&st) {
}
virtual void startPaint(Painter &p) const = 0;
virtual const style::TextStyle &textStyle() const = 0;
int buttonSkip() const;
int buttonPadding() const;
int buttonHeight() const;
virtual int buttonRadius() const = 0;
virtual void repaint(not_null<const HistoryItem*> item) const = 0;
virtual ~Style() {
}
protected:
virtual void paintButtonBg(Painter &p, const QRect &rect, float64 howMuchOver) const = 0;
virtual void paintButtonIcon(Painter &p, const QRect &rect, int outerWidth, HistoryMessageReplyMarkup::Button::Type type) const = 0;
virtual void paintButtonLoading(Painter &p, const QRect &rect) const = 0;
virtual int minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const = 0;
private:
const style::BotKeyboardButton *_st;
void paintButton(Painter &p, int outerWidth, const ReplyKeyboard::Button &button, TimeMs ms) const;
friend class ReplyKeyboard;
};
typedef std::unique_ptr<Style> StylePtr;
ReplyKeyboard(const HistoryItem *item, StylePtr &&s);
ReplyKeyboard(const ReplyKeyboard &other) = delete;
ReplyKeyboard &operator=(const ReplyKeyboard &other) = delete;
bool isEnoughSpace(int width, const style::BotKeyboardButton &st) const;
void setStyle(StylePtr &&s);
void resize(int width, int height);
// what width and height will best fit this keyboard
int naturalWidth() const;
int naturalHeight() const;
void paint(Painter &p, int outerWidth, const QRect &clip, TimeMs ms) const;
ClickHandlerPtr getState(QPoint point) const;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active);
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed);
void clearSelection();
void updateMessageId();
private:
void startAnimation(int i, int j, int direction);
friend class Style;
using ReplyMarkupClickHandlerPtr = QSharedPointer<ReplyMarkupClickHandler>;
struct Button {
Text text = { 1 };
QRect rect;
int characters = 0;
float64 howMuchOver = 0.;
HistoryMessageReplyMarkup::Button::Type type;
ReplyMarkupClickHandlerPtr link;
mutable QSharedPointer<Ui::RippleAnimation> ripple;
};
using ButtonRow = QVector<Button>;
using ButtonRows = QVector<ButtonRow>;
struct ButtonCoords {
int i, j;
};
ButtonCoords findButtonCoordsByClickHandler(const ClickHandlerPtr &p);
using Animations = QMap<int, TimeMs>;
void step_selected(TimeMs ms, bool timer);
const HistoryItem *_item;
int _width = 0;
ButtonRows _rows;
Animations _animations;
BasicAnimation _a_selected;
StylePtr _st;
ClickHandlerPtr _savedPressed;
ClickHandlerPtr _savedActive;
mutable QPoint _savedCoords;
};
// Any HistoryItem can have this Component for
// displaying the day mark above the message.
struct HistoryMessageDate : public RuntimeComponent<HistoryMessageDate> {
void init(const QDateTime &date);
int height() const;
void paint(Painter &p, int y, int w) const;
QString _text;
int _width = 0;
};
// Any HistoryItem can have this Component for
// displaying the unread messages bar above the message.
struct HistoryMessageUnreadBar : public RuntimeComponent<HistoryMessageUnreadBar> {
void init(int count);
static int height();
static int marginTop();
void paint(Painter &p, int y, int w) const;
QString _text;
int _width = 0;
// If unread bar is freezed the new messages do not
// increment the counter displayed by this bar.
//
// It happens when we've opened the conversation and
// we've seen the bar and new messages are marked as read
// as soon as they are added to the chat history.
bool _freezed = false;
};
class HistoryWebPage;
// Special type of Component for the channel actions log.
struct HistoryMessageLogEntryOriginal : public RuntimeComponent<HistoryMessageLogEntryOriginal> {
HistoryMessageLogEntryOriginal();
HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other);
HistoryMessageLogEntryOriginal &operator=(HistoryMessageLogEntryOriginal &&other);
~HistoryMessageLogEntryOriginal();
std::unique_ptr<HistoryWebPage> _page;
};
// HistoryMedia has a special owning smart pointer
// which regs/unregs this media to the holding HistoryItem
class HistoryMedia;
class HistoryMediaPtr {
public:
HistoryMediaPtr() = default;
HistoryMediaPtr(const HistoryMediaPtr &other) = delete;
HistoryMediaPtr &operator=(const HistoryMediaPtr &other) = delete;
HistoryMediaPtr(std::unique_ptr<HistoryMedia> other);
HistoryMediaPtr &operator=(std::unique_ptr<HistoryMedia> other);
HistoryMedia *get() const {
return _pointer.get();
}
void reset(std::unique_ptr<HistoryMedia> pointer = nullptr);
bool isNull() const {
return !_pointer;
}
HistoryMedia *operator->() const {
return get();
}
HistoryMedia &operator*() const {
Expects(!isNull());
return *get();
}
explicit operator bool() const {
return !isNull();
}
~HistoryMediaPtr();
private:
std::unique_ptr<HistoryMedia> _pointer;
};
namespace internal {
TextSelection unshiftSelection(TextSelection selection, uint16 byLength);
TextSelection shiftSelection(TextSelection selection, uint16 byLength);
inline TextSelection unshiftSelection(TextSelection selection, const Text &byText) {
return ::internal::unshiftSelection(selection, byText.length());
}
inline TextSelection shiftSelection(TextSelection selection, const Text &byText) {
return ::internal::shiftSelection(selection, byText.length());
}
} // namespace internal
class HistoryItem : public HistoryElement, public RuntimeComposer, public ClickHandlerHost {
public:
int resizeGetHeight(int newWidth) {
if (_flags & MTPDmessage_ClientFlag::f_pending_init_dimensions) {
_flags &= ~MTPDmessage_ClientFlag::f_pending_init_dimensions;
initDimensions();
}
if (_flags & MTPDmessage_ClientFlag::f_pending_resize) {
_flags &= ~MTPDmessage_ClientFlag::f_pending_resize;
}
_width = newWidth;
return resizeContentGetHeight();
}
virtual void draw(Painter &p, QRect clip, TextSelection selection, TimeMs ms) const = 0;
virtual void dependencyItemRemoved(HistoryItem *dependency) {
}
virtual bool updateDependencyItem() {
return true;
}
virtual MsgId dependencyMsgId() const {
return 0;
}
virtual bool notificationReady() const {
return true;
}
UserData *viaBot() const {
if (auto via = Get<HistoryMessageVia>()) {
return via->_bot;
}
return nullptr;
}
UserData *getMessageBot() const {
if (auto bot = viaBot()) {
return bot;
}
auto bot = from()->asUser();
if (!bot) {
bot = history()->peer->asUser();
}
return (bot && bot->botInfo) ? bot : nullptr;
};
bool isLogEntry() const {
return (id > ServerMaxMsgId);
}
void addLogEntryOriginal(WebPageId localId, const QString &label, const TextWithEntities &content);
not_null<History*> history() const {
return _history;
}
PeerData *from() const {
return _from;
}
HistoryBlock *block() {
return _block;
}
const HistoryBlock *block() const {
return _block;
}
void destroy();
void detach();
void detachFast();
bool detached() const {
return !_block;
}
void attachToBlock(HistoryBlock *block, int index) {
Expects(!isLogEntry());
Expects(_block == nullptr);
Expects(_indexInBlock < 0);
Expects(block != nullptr);
Expects(index >= 0);
_block = block;
_indexInBlock = index;
setPendingResize();
}
void setIndexInBlock(int index) {
Expects(_block != nullptr);
Expects(index >= 0);
_indexInBlock = index;
}
int indexInBlock() const {
Expects((_indexInBlock >= 0) == (_block != nullptr));
Expects((_block == nullptr) || (_block->items[_indexInBlock] == this));
return _indexInBlock;
}
bool out() const {
return _flags & MTPDmessage::Flag::f_out;
}
bool unread() const;
bool mentionsMe() const {
return _flags & MTPDmessage::Flag::f_mentioned;
}
bool isMediaUnread() const {
return _flags & MTPDmessage::Flag::f_media_unread;
}
void markMediaRead();
// Zero result means this message is not self-destructing right now.
virtual TimeMs getSelfDestructIn(TimeMs now) {
return 0;
}
bool definesReplyKeyboard() const {
if (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 replyKeyboardFlags() const {
Expects(definesReplyKeyboard());
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
return markup->flags;
}
// optimization: don't create markup component for the case
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
return qFlags(MTPDreplyKeyboardMarkup_ClientFlag::f_zero);
}
bool hasSwitchInlineButton() const {
return _flags & MTPDmessage_ClientFlag::f_has_switch_inline_button;
}
bool hasTextLinks() const {
return _flags & MTPDmessage_ClientFlag::f_has_text_links;
}
bool isGroupMigrate() const {
return _flags & MTPDmessage_ClientFlag::f_is_group_migrate;
}
bool hasViews() const {
return _flags & MTPDmessage::Flag::f_views;
}
bool isPost() const {
return _flags & MTPDmessage::Flag::f_post;
}
bool indexInOverview() const {
return (id > 0);
}
bool isSilent() const {
return _flags & MTPDmessage::Flag::f_silent;
}
bool hasOutLayout() const {
return out() && !isPost();
}
virtual int32 viewsCount() const {
return hasViews() ? 1 : -1;
}
virtual bool needCheck() const {
return out() || (id < 0 && history()->peer->isSelf());
}
virtual bool hasPoint(QPoint point) const {
return false;
}
virtual HistoryTextState getState(QPoint point, HistoryStateRequest request) const WARN_UNUSED_RESULT = 0;
virtual void updatePressed(QPoint point) {
}
virtual TextSelection adjustSelection(TextSelection selection, TextSelectType type) const WARN_UNUSED_RESULT {
return selection;
}
// ClickHandlerHost interface
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
virtual bool serviceMsg() const {
return false;
}
virtual void applyEdition(const MTPDmessage &message) {
}
virtual void applyEdition(const MTPDmessageService &message) {
}
virtual void updateMedia(const MTPMessageMedia *media) {
}
virtual void updateReplyMarkup(const MTPReplyMarkup *markup) {
}
virtual int32 addToOverview(AddToOverviewMethod method) {
return 0;
}
virtual void eraseFromOverview() {
}
virtual bool hasBubble() const {
return false;
}
void previousItemChanged();
void nextItemChanged();
virtual TextWithEntities selectedText(TextSelection selection) const {
return { qsl("[-]"), EntitiesInText() };
}
virtual QString notificationHeader() const {
return QString();
}
virtual QString notificationText() const;
// Returns text with link-start and link-end commands for service-color highlighting.
// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
virtual QString inDialogsText() const;
virtual QString inReplyText() const {
return notificationText();
}
virtual TextWithEntities originalText() const {
return { QString(), EntitiesInText() };
}
virtual void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const {
}
virtual ClickHandlerPtr fastShareLink() const {
return ClickHandlerPtr();
}
virtual bool displayFastShare() const {
return false;
}
virtual void drawFastShare(Painter &p, int left, int top, int outerWidth) const {
}
virtual void setViewsCount(int32 count) {
}
virtual void setId(MsgId newId);
void drawInDialog(Painter &p, const QRect &r, bool active, bool selected, const HistoryItem *&cacheFor, Text &cache) const;
bool emptyText() const {
return _text.isEmpty();
}
bool canPin() const;
bool canForward() const;
bool canEdit(const QDateTime &cur) const;
bool canDelete() const;
bool canDeleteForEveryone(const QDateTime &cur) const;
bool suggestBanReport() const;
bool suggestDeleteAllReport() const;
bool hasDirectLink() const;
QString directLink() const;
int y() const {
return _y;
}
void setY(int y) {
_y = y;
}
MsgId id;
QDateTime date;
ChannelId channelId() const {
return _history->channelId();
}
FullMsgId fullId() const {
return FullMsgId(channelId(), id);
}
HistoryMedia *getMedia() const {
return _media.get();
}
virtual void setText(const TextWithEntities &textWithEntities) {
}
virtual bool textHasLinks() const {
return false;
}
virtual int infoWidth() const {
return 0;
}
virtual int timeLeft() const {
return 0;
}
virtual int timeWidth() const {
return 0;
}
virtual bool pointInTime(int right, int bottom, QPoint point, InfoDisplayType type) const {
return false;
}
int skipBlockWidth() const {
return st::msgDateSpace + infoWidth() - st::msgDateDelta.x();
}
int skipBlockHeight() const {
return st::msgDateFont->height - st::msgDateDelta.y();
}
QString skipBlock() const {
return textcmdSkipBlock(skipBlockWidth(), skipBlockHeight());
}
virtual HistoryMessage *toHistoryMessage() { // dynamic_cast optimize
return nullptr;
}
virtual const HistoryMessage *toHistoryMessage() const { // dynamic_cast optimize
return nullptr;
}
MsgId replyToId() const {
if (auto reply = Get<HistoryMessageReply>()) {
return reply->replyToId();
}
return 0;
}
bool hasFromName() const {
return (!out() || isPost()) && !history()->peer->isUser();
}
PeerData *author() const {
return isPost() ? history()->peer : from();
}
QDateTime dateOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_originalDate;
}
return date;
}
PeerData *senderOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_originalSender;
}
auto peer = history()->peer;
return (peer->isChannel() && !peer->isMegagroup()) ? peer : from();
}
PeerData *fromOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
if (auto user = forwarded->_originalSender->asUser()) {
return user;
}
}
return from();
}
QString authorOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_originalAuthor;
} else if (auto msgsigned = Get<HistoryMessageSigned>()) {
return msgsigned->_author;
}
return QString();
}
MsgId idOriginal() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
return forwarded->_originalId;
}
return id;
}
// count > 0 - creates the unread bar if necessary and
// sets unread messages count if bar is not freezed yet
// count <= 0 - destroys the unread bar
void setUnreadBarCount(int count);
void destroyUnreadBar();
// marks the unread bar as freezed so that unread
// messages count will not change for this bar
// when the new messages arrive in this chat history
void setUnreadBarFreezed();
bool pendingResize() const {
return _flags & MTPDmessage_ClientFlag::f_pending_resize;
}
void setPendingResize() {
_flags |= MTPDmessage_ClientFlag::f_pending_resize;
if (!detached() || isLogEntry()) {
_history->setHasPendingResizedItems();
}
}
bool pendingInitDimensions() const {
return _flags & MTPDmessage_ClientFlag::f_pending_init_dimensions;
}
void setPendingInitDimensions() {
_flags |= MTPDmessage_ClientFlag::f_pending_init_dimensions;
setPendingResize();
}
int displayedDateHeight() const {
if (auto date = Get<HistoryMessageDate>()) {
return date->height();
}
return 0;
}
int marginTop() const {
int result = 0;
if (isAttachedToPrevious()) {
result += st::msgMarginTopAttached;
} else {
result += st::msgMargin.top();
}
result += displayedDateHeight();
if (auto unreadbar = Get<HistoryMessageUnreadBar>()) {
result += unreadbar->height();
}
return result;
}
int marginBottom() const {
return st::msgMargin.bottom();
}
bool isAttachedToPrevious() const {
return _flags & MTPDmessage_ClientFlag::f_attach_to_previous;
}
bool isAttachedToNext() const {
return _flags & MTPDmessage_ClientFlag::f_attach_to_next;
}
bool displayDate() const {
return Has<HistoryMessageDate>();
}
bool isInOneDayWithPrevious() const {
return !isEmpty() && !displayDate();
}
bool isEmpty() const {
return _text.isEmpty() && !_media && !Has<HistoryMessageLogEntryOriginal>();
}
int width() const {
return _width;
}
void clipCallback(Media::Clip::Notification notification);
void audioTrackUpdated();
bool computeIsAttachToPrevious(not_null<HistoryItem*> previous);
void setLogEntryDisplayDate(bool displayDate) {
Expects(isLogEntry());
setDisplayDate(displayDate);
}
void setLogEntryAttachToPrevious(bool attachToPrevious) {
Expects(isLogEntry());
setAttachToPrevious(attachToPrevious);
}
void setLogEntryAttachToNext(bool attachToNext) {
Expects(isLogEntry());
setAttachToNext(attachToNext);
}
~HistoryItem();
protected:
HistoryItem(
not_null<History*> history,
MsgId id,
MTPDmessage::Flags flags,
QDateTime date,
UserId from);
// To completely create history item we need to call
// a virtual method, it can not be done from constructor.
virtual void finishCreate();
// Called from resizeGetHeight() when MTPDmessage_ClientFlag::f_pending_init_dimensions is set.
virtual void initDimensions() = 0;
virtual void markMediaAsReadHook() {
}
virtual int resizeContentGetHeight() = 0;
void finishEdition(int oldKeyboardTop);
void finishEditionToEmpty();
const not_null<History*> _history;
not_null<PeerData*> _from;
HistoryBlock *_block = nullptr;
int _indexInBlock = -1;
MTPDmessage::Flags _flags = 0;
mutable int32 _authorNameVersion = 0;
HistoryItem *previousItem() const {
if (_block && _indexInBlock >= 0) {
if (_indexInBlock > 0) {
return _block->items.at(_indexInBlock - 1);
}
if (auto previous = _block->previousBlock()) {
Assert(!previous->items.isEmpty());
return previous->items.back();
}
}
return nullptr;
}
HistoryItem *nextItem() const {
if (_block && _indexInBlock >= 0) {
if (_indexInBlock + 1 < _block->items.size()) {
return _block->items.at(_indexInBlock + 1);
}
if (auto next = _block->nextBlock()) {
Assert(!next->items.isEmpty());
return next->items.front();
}
}
return nullptr;
}
// This should be called only from previousItemChanged()
// to add required bits to the Composer mask
// after that always use Has<HistoryMessageDate>().
void recountDisplayDate();
// This should be called only from previousItemChanged() or when
// HistoryMessageDate or HistoryMessageUnreadBar bit is changed in the Composer mask
// then the result should be cached in a client side flag MTPDmessage_ClientFlag::f_attach_to_previous.
void recountAttachToPrevious();
// This should be called only from recountDisplayDate().
// Also this is called from setLogEntryDisplayDate() for channel log entries.
void setDisplayDate(bool displayDate);
// This should be called only from recountAttachToPrevious().
// Also this is called from setLogEntryAttachToPrevious() for channel log entries.
void setAttachToPrevious(bool attachToNext);
// This should be called only from recountAttachToPrevious() of the next item
// or when the next item is removed through nextItemChanged() call.
// Also this is called from setLogEntryAttachToNext() for channel log entries.
void setAttachToNext(bool attachToNext);
const HistoryMessageReplyMarkup *inlineReplyMarkup() const {
return const_cast<HistoryItem*>(this)->inlineReplyMarkup();
}
const ReplyKeyboard *inlineReplyKeyboard() const {
return const_cast<HistoryItem*>(this)->inlineReplyKeyboard();
}
HistoryMessageReplyMarkup *inlineReplyMarkup() {
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
return markup;
}
}
return nullptr;
}
ReplyKeyboard *inlineReplyKeyboard() {
if (auto markup = inlineReplyMarkup()) {
return markup->inlineKeyboard.get();
}
return nullptr;
}
TextSelection skipTextSelection(TextSelection selection) const WARN_UNUSED_RESULT {
return internal::unshiftSelection(selection, _text);
}
TextSelection unskipTextSelection(TextSelection selection) const WARN_UNUSED_RESULT {
return internal::shiftSelection(selection, _text);
}
Text _text = { int(st::msgMinWidth) };
int _textWidth = -1;
int _textHeight = 0;
HistoryMediaPtr _media;
private:
int _y = 0;
int _width = 0;
};
// make all the constructors in HistoryItem children protected
// and wrapped with a static create() call with the same args
// so that history item can not be created directly, without
// calling a virtual finishCreate() method
template <typename T>
class HistoryItemInstantiated {
public:
template <typename ...Args>
static not_null<T*> _create(Args &&... args) {
auto result = new T(std::forward<Args>(args)...);
result->finishCreate();
return result;
}
};
ClickHandlerPtr goToMessageClickHandler(PeerData *peer, MsgId msgId);
inline ClickHandlerPtr goToMessageClickHandler(HistoryItem *item) {
return goToMessageClickHandler(item->history()->peer, item->id);
}