tdesktop/Telegram/SourceFiles/history/history_item_components.h

746 lines
18 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "data/data_cloud_file.h"
#include "history/history_item.h"
#include "spellcheck/spellcheck_types.h" // LanguageId.
#include "ui/empty_userpic.h"
#include "ui/effects/animations.h"
#include "ui/effects/ripple_animation.h"
#include "ui/chat/message_bubble.h"
struct WebPageData;
class VoiceSeekClickHandler;
namespace Ui {
struct ChatPaintContext;
class ChatStyle;
struct PeerUserpicView;
class SpoilerAnimation;
} // namespace Ui
namespace Data {
class Session;
class Story;
} // namespace Data
namespace Media::Player {
class RoundPainter;
} // namespace Media::Player
namespace Images {
struct CornersMaskRef;
} // namespace Images
namespace HistoryView {
class Element;
class Document;
class TranscribeButton;
} // namespace HistoryView
struct HistoryMessageVia : public RuntimeComponent<HistoryMessageVia, HistoryItem> {
void create(not_null<Data::Session*> owner, UserId userId);
void resize(int32 availw) const;
UserData *bot = nullptr;
mutable QString text;
mutable int width = 0;
mutable int maxWidth = 0;
ClickHandlerPtr link;
};
struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, HistoryItem> {
static constexpr auto kMaxRecentRepliers = 3;
struct Part {
QString text;
int textWidth = 0;
int count = -1;
};
std::vector<PeerId> recentRepliers;
Part views;
Part replies;
Part repliesSmall;
ChannelId commentsMegagroupId = 0;
MsgId commentsRootId = 0;
MsgId commentsInboxReadTillId = 0;
MsgId commentsMaxId = 0;
int forwardsCount = 0;
};
struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned, HistoryItem> {
QString postAuthor;
bool isAnonymousRank = false;
};
struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited, HistoryItem> {
TimeId date = 0;
};
class HiddenSenderInfo {
public:
HiddenSenderInfo(
const QString &name,
bool external,
std::optional<uint8> colorIndex = {});
QString name;
QString firstName;
QString lastName;
uint8 colorIndex = 0;
Ui::EmptyUserpic emptyUserpic;
mutable Data::CloudImage customUserpic;
[[nodiscard]] static ClickHandlerPtr ForwardClickHandler();
[[nodiscard]] const Ui::Text::String &nameText() const;
[[nodiscard]] bool paintCustomUserpic(
Painter &p,
Ui::PeerUserpicView &view,
int x,
int y,
int outerWidth,
int size) const;
inline bool operator==(const HiddenSenderInfo &other) const {
return name == other.name;
}
inline bool operator!=(const HiddenSenderInfo &other) const {
return !(*this == other);
}
private:
mutable Ui::Text::String _nameText;
};
struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded, HistoryItem> {
void create(const HistoryMessageVia *via) const;
TimeId originalDate = 0;
PeerData *originalSender = nullptr;
std::unique_ptr<HiddenSenderInfo> hiddenSenderInfo;
QString originalPostAuthor;
QString psaType;
MsgId originalId = 0;
mutable Ui::Text::String text = { 1 };
PeerData *savedFromPeer = nullptr;
MsgId savedFromMsgId = 0;
bool imported = false;
bool story = false;
};
struct HistoryMessageSponsored : public RuntimeComponent<HistoryMessageSponsored, HistoryItem> {
enum class Type : uchar {
User,
Group,
Broadcast,
Post,
Bot,
ExternalLink,
};
std::unique_ptr<HiddenSenderInfo> sender;
Type type = Type::User;
bool recommended = false;
bool isForceUserpicDisplay = false;
QString externalLink;
};
class ReplyToMessagePointer final {
public:
ReplyToMessagePointer(HistoryItem *item = nullptr) : _data(item) {
}
ReplyToMessagePointer(ReplyToMessagePointer &&other)
: _data(base::take(other._data)) {
}
ReplyToMessagePointer &operator=(ReplyToMessagePointer &&other) {
_data = base::take(other._data);
return *this;
}
ReplyToMessagePointer &operator=(HistoryItem *item) {
_data = item;
return *this;
}
[[nodiscard]] bool empty() const {
return !_data;
}
[[nodiscard]] HistoryItem *get() const {
return _data;
}
explicit operator bool() const {
return !empty();
}
[[nodiscard]] HistoryItem *operator->() const {
return _data;
}
[[nodiscard]] HistoryItem &operator*() const {
return *_data;
}
private:
HistoryItem *_data = nullptr;
};
class ReplyToStoryPointer final {
public:
ReplyToStoryPointer(Data::Story *story = nullptr) : _data(story) {
}
ReplyToStoryPointer(ReplyToStoryPointer &&other)
: _data(base::take(other._data)) {
}
ReplyToStoryPointer &operator=(ReplyToStoryPointer &&other) {
_data = base::take(other._data);
return *this;
}
ReplyToStoryPointer &operator=(Data::Story *item) {
_data = item;
return *this;
}
[[nodiscard]] bool empty() const {
return !_data;
}
[[nodiscard]] Data::Story *get() const {
return _data;
}
explicit operator bool() const {
return !empty();
}
[[nodiscard]] Data::Story *operator->() const {
return _data;
}
[[nodiscard]] Data::Story &operator*() const {
return *_data;
}
private:
Data::Story *_data = nullptr;
};
struct ReplyFields {
TextWithEntities quote;
PeerId externalSenderId = 0;
QString externalSenderName;
QString externalPostAuthor;
PeerId externalPeerId = 0;
MsgId messageId = 0;
MsgId topMessageId = 0;
StoryId storyId = 0;
bool topicPost = false;
bool manualQuote = false;
};
[[nodiscard]] ReplyFields ReplyFieldsFromMTP(
not_null<History*> history,
const MTPMessageReplyHeader &reply);
[[nodiscard]] FullReplyTo ReplyToFromMTP(
not_null<History*> history,
const MTPInputReplyTo &reply);
struct HistoryMessageReply
: public RuntimeComponent<HistoryMessageReply, HistoryItem> {
HistoryMessageReply();
HistoryMessageReply(const HistoryMessageReply &other) = delete;
HistoryMessageReply(HistoryMessageReply &&other) = delete;
HistoryMessageReply &operator=(
const HistoryMessageReply &other) = delete;
HistoryMessageReply &operator=(HistoryMessageReply &&other);
~HistoryMessageReply();
static constexpr auto kBarAlpha = 230. / 255.;
void set(ReplyFields fields);
void updateFields(
not_null<HistoryItem*> holder,
MsgId messageId,
MsgId topMessageId,
bool topicPost);
bool updateData(not_null<HistoryItem*> holder, bool force = false);
// Must be called before destructor.
void clearData(not_null<HistoryItem*> holder);
[[nodiscard]] bool external() const;
[[nodiscard]] PeerData *sender(not_null<HistoryItem*> holder) const;
[[nodiscard]] QString senderName(not_null<HistoryItem*> holder) const;
[[nodiscard]] QString senderName(not_null<PeerData*> peer) const;
[[nodiscard]] bool isNameUpdated(not_null<HistoryItem*> holder) const;
void updateName(
not_null<HistoryItem*> holder,
std::optional<PeerData*> resolvedSender = std::nullopt) const;
[[nodiscard]] int resizeToWidth(int width) const;
[[nodiscard]] int height() const;
[[nodiscard]] QMargins margins() const;
void itemRemoved(
not_null<HistoryItem*> holder,
not_null<HistoryItem*> removed);
void storyRemoved(
not_null<HistoryItem*> holder,
not_null<Data::Story*> removed);
void paint(
Painter &p,
not_null<const HistoryView::Element*> holder,
const Ui::ChatPaintContext &context,
int x,
int y,
int w,
bool inBubble) const;
void unloadPersistentAnimation();
[[nodiscard]] ReplyFields fields() const {
return _fields;
}
[[nodiscard]] PeerId externalPeerId() const {
return _fields.externalPeerId;
}
[[nodiscard]] MsgId messageId() const {
return _fields.messageId;
}
[[nodiscard]] StoryId storyId() const {
return _fields.storyId;
}
[[nodiscard]] MsgId topMessageId() const {
return _fields.topMessageId;
}
[[nodiscard]] int maxWidth() const {
return _maxWidth;
}
[[nodiscard]] ClickHandlerPtr link() const {
return _link;
}
[[nodiscard]] bool topicPost() const {
return _fields.topicPost;
}
[[nodiscard]] bool manualQuote() const {
return _fields.manualQuote;
}
[[nodiscard]] QString statePhrase() const;
void setLinkFrom(not_null<HistoryItem*> holder);
void setTopMessageId(MsgId topMessageId);
void refreshReplyToMedia();
DocumentId replyToDocumentId = 0;
WebPageId replyToWebPageId = 0;
ReplyToMessagePointer resolvedMessage;
ReplyToStoryPointer resolvedStory;
std::unique_ptr<HistoryMessageVia> originalVia;
std::unique_ptr<Ui::SpoilerAnimation> spoiler;
struct {
mutable std::unique_ptr<Ui::RippleAnimation> animation;
QPoint lastPoint;
} ripple;
private:
ReplyFields _fields;
ClickHandlerPtr _link;
mutable Ui::Text::String _name;
mutable Ui::Text::String _text;
mutable PeerData *_externalSender = nullptr;
mutable int _maxWidth = 0;
mutable int _minHeight = 0;
mutable int _height = 0;
mutable int _nameVersion = 0;
uint8 _unavailable : 1 = 0;
};
struct HistoryMessageTranslation
: public RuntimeComponent<HistoryMessageTranslation, HistoryItem> {
TextWithEntities text;
LanguageId to;
bool requested = false;
bool failed = false;
bool used = false;
};
struct HistoryMessageReplyMarkup
: public RuntimeComponent<HistoryMessageReplyMarkup, HistoryItem> {
using Button = HistoryMessageMarkupButton;
void createForwarded(const HistoryMessageReplyMarkup &original);
void updateData(HistoryMessageMarkupData &&markup);
[[nodiscard]] bool hiddenBy(Data::Media *media) const;
HistoryMessageMarkupData data;
std::unique_ptr<ReplyKeyboard> inlineKeyboard;
};
class ReplyMarkupClickHandler : public ClickHandler {
public:
ReplyMarkupClickHandler(
not_null<Data::Session*> owner,
int row,
int column,
FullMsgId context);
QString tooltip() const override;
void setFullDisplayed(bool full) {
_fullDisplayed = full;
}
// Copy to clipboard support.
QString copyToClipboardText() 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 HistoryMessageMarkupButton *getButton() const;
const HistoryMessageMarkupButton *getUrlButton() 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;
}
void onClick(ClickContext context) const override;
private:
const not_null<Data::Session*> _owner;
FullMsgId _itemId;
int _row = 0;
int _column = 0;
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(
QPainter &p,
const Ui::ChatStyle *st) const = 0;
virtual const style::TextStyle &textStyle() const = 0;
int buttonSkip() const;
int buttonPadding() const;
int buttonHeight() const;
[[nodiscard]] virtual Images::CornersMaskRef buttonRounding(
Ui::BubbleRounding outer,
RectParts sides) const = 0;
virtual void repaint(not_null<const HistoryItem*> item) const = 0;
virtual ~Style() {
}
protected:
virtual void paintButtonBg(
QPainter &p,
const Ui::ChatStyle *st,
const QRect &rect,
Ui::BubbleRounding rounding,
float64 howMuchOver) const = 0;
virtual void paintButtonIcon(
QPainter &p,
const Ui::ChatStyle *st,
const QRect &rect,
int outerWidth,
HistoryMessageMarkupButton::Type type) const = 0;
virtual void paintButtonLoading(
QPainter &p,
const Ui::ChatStyle *st,
const QRect &rect,
int outerWidth,
Ui::BubbleRounding rounding) const = 0;
virtual int minButtonWidth(
HistoryMessageMarkupButton::Type type) const = 0;
private:
const style::BotKeyboardButton *_st;
void paintButton(
Painter &p,
const Ui::ChatStyle *st,
int outerWidth,
const ReplyKeyboard::Button &button,
Ui::BubbleRounding rounding) const;
friend class ReplyKeyboard;
};
ReplyKeyboard(
not_null<const HistoryItem*> item,
std::unique_ptr<Style> &&s);
ReplyKeyboard(const ReplyKeyboard &other) = delete;
ReplyKeyboard &operator=(const ReplyKeyboard &other) = delete;
bool isEnoughSpace(int width, const style::BotKeyboardButton &st) const;
void setStyle(std::unique_ptr<Style> &&s);
void resize(int width, int height);
// what width and height will best fit this keyboard
int naturalWidth() const;
int naturalHeight() const;
void paint(
Painter &p,
const Ui::ChatStyle *st,
Ui::BubbleRounding rounding,
int outerWidth,
const QRect &clip) const;
ClickHandlerPtr getLink(QPoint point) const;
void clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active);
void clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed,
Ui::BubbleRounding rounding);
void clearSelection();
void updateMessageId();
private:
friend class Style;
struct Button {
Button();
Button(Button &&other);
Button &operator=(Button &&other);
~Button();
Ui::Text::String text = { 1 };
QRect rect;
int characters = 0;
float64 howMuchOver = 0.;
HistoryMessageMarkupButton::Type type;
std::shared_ptr<ReplyMarkupClickHandler> link;
mutable std::unique_ptr<Ui::RippleAnimation> ripple;
};
struct ButtonCoords {
int i, j;
};
void startAnimation(int i, int j, int direction);
ButtonCoords findButtonCoordsByClickHandler(const ClickHandlerPtr &p);
bool selectedAnimationCallback(crl::time now);
const not_null<const HistoryItem*> _item;
int _width = 0;
std::vector<std::vector<Button>> _rows;
base::flat_map<int, crl::time> _animations;
Ui::Animations::Basic _selectedAnimation;
std::unique_ptr<Style> _st;
ClickHandlerPtr _savedPressed;
ClickHandlerPtr _savedActive;
mutable QPoint _savedCoords;
};
// Special type of Component for the channel actions log.
struct HistoryMessageLogEntryOriginal
: public RuntimeComponent<HistoryMessageLogEntryOriginal, HistoryItem> {
HistoryMessageLogEntryOriginal();
HistoryMessageLogEntryOriginal(HistoryMessageLogEntryOriginal &&other);
HistoryMessageLogEntryOriginal &operator=(HistoryMessageLogEntryOriginal &&other);
~HistoryMessageLogEntryOriginal();
WebPageData *page = nullptr;
};
struct HistoryServiceData
: public RuntimeComponent<HistoryServiceData, HistoryItem> {
std::vector<ClickHandlerPtr> textLinks;
};
struct HistoryServiceDependentData {
PeerId peerId = 0;
HistoryItem *msg = nullptr;
ClickHandlerPtr lnk;
MsgId msgId = 0;
MsgId topId = 0;
bool topicPost = false;
};
struct HistoryServicePinned
: public RuntimeComponent<HistoryServicePinned, HistoryItem>
, public HistoryServiceDependentData {
};
struct HistoryServiceTopicInfo
: public RuntimeComponent<HistoryServiceTopicInfo, HistoryItem>
, public HistoryServiceDependentData {
QString title;
DocumentId iconId = 0;
bool closed = false;
bool reopened = false;
bool reiconed = false;
bool renamed = false;
bool hidden = false;
bool unhidden = false;
[[nodiscard]] bool created() const {
return !closed
&& !reopened
&& !reiconed
&& !renamed
&& !hidden
&& !unhidden;
}
};
struct HistoryServiceGameScore
: public RuntimeComponent<HistoryServiceGameScore, HistoryItem>
, public HistoryServiceDependentData {
int score = 0;
};
struct HistoryServicePayment
: public RuntimeComponent<HistoryServicePayment, HistoryItem>
, public HistoryServiceDependentData {
QString slug;
QString amount;
ClickHandlerPtr invoiceLink;
bool recurringInit = false;
bool recurringUsed = false;
};
struct HistoryServiceSameBackground
: public RuntimeComponent<HistoryServiceSameBackground, HistoryItem>
, public HistoryServiceDependentData {
};
enum class HistorySelfDestructType {
Photo,
Video,
};
struct TimeToLiveSingleView {
friend inline auto operator<=>(
TimeToLiveSingleView,
TimeToLiveSingleView) = default;
friend inline bool operator==(
TimeToLiveSingleView,
TimeToLiveSingleView) = default;
};
struct HistoryServiceSelfDestruct
: public RuntimeComponent<HistoryServiceSelfDestruct, HistoryItem> {
using Type = HistorySelfDestructType;
Type type = Type::Photo;
std::variant<crl::time, TimeToLiveSingleView> timeToLive = crl::time();
std::variant<crl::time, TimeToLiveSingleView> destructAt = crl::time();
};
struct HistoryServiceOngoingCall
: public RuntimeComponent<HistoryServiceOngoingCall, HistoryItem> {
CallId id = 0;
ClickHandlerPtr link;
rpl::lifetime lifetime;
};
struct HistoryServiceChatThemeChange
: public RuntimeComponent<HistoryServiceChatThemeChange, HistoryItem> {
ClickHandlerPtr link;
};
struct HistoryServiceTTLChange
: public RuntimeComponent<HistoryServiceTTLChange, HistoryItem> {
ClickHandlerPtr link;
};
class FileClickHandler;
struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed, HistoryView::Document> {
std::shared_ptr<FileClickHandler> linksavel;
std::shared_ptr<FileClickHandler> linkopenwithl;
std::shared_ptr<FileClickHandler> linkcancell;
mutable QImage thumbnail;
mutable QString link;
int thumbw = 0;
mutable int linkw = 0;
mutable Ui::BubbleRounding rounding;
mutable bool blurred : 1 = false;
};
struct HistoryDocumentCaptioned : public RuntimeComponent<HistoryDocumentCaptioned, HistoryView::Document> {
HistoryDocumentCaptioned();
Ui::Text::String caption;
};
struct HistoryDocumentNamed : public RuntimeComponent<HistoryDocumentNamed, HistoryView::Document> {
QString name;
int namew = 0;
};
struct HistoryDocumentVoicePlayback {
HistoryDocumentVoicePlayback(const HistoryView::Document *that);
int32 position = 0;
anim::value progress;
Ui::Animations::Basic progressAnimation;
};
class HistoryDocumentVoice : public RuntimeComponent<HistoryDocumentVoice, HistoryView::Document> {
// We don't use float64 because components should align to pointer even on 32bit systems.
static constexpr float64 kFloatToIntMultiplier = 65536.;
public:
void ensurePlayback(const HistoryView::Document *interfaces) const;
void checkPlaybackFinished() const;
mutable std::unique_ptr<HistoryDocumentVoicePlayback> playback;
std::shared_ptr<VoiceSeekClickHandler> seekl;
mutable int lastDurationMs = 0;
[[nodiscard]] bool seeking() const;
void startSeeking();
void stopSeeking();
[[nodiscard]] float64 seekingStart() const;
void setSeekingStart(float64 seekingStart) const;
[[nodiscard]] float64 seekingCurrent() const;
void setSeekingCurrent(float64 seekingCurrent);
std::unique_ptr<HistoryView::TranscribeButton> transcribe;
Ui::Text::String transcribeText;
std::unique_ptr<Media::Player::RoundPainter> round;
private:
bool _seeking = false;
mutable int _seekingStart = 0;
mutable int _seekingCurrent = 0;
};