mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Extract reply view to a separate component.
This commit is contained in:
parent
56ad825693
commit
4e0490494e
18 changed files with 1037 additions and 880 deletions
|
@ -770,6 +770,8 @@ PRIVATE
|
||||||
history/view/history_view_quick_action.h
|
history/view/history_view_quick_action.h
|
||||||
history/view/history_view_replies_section.cpp
|
history/view/history_view_replies_section.cpp
|
||||||
history/view/history_view_replies_section.h
|
history/view/history_view_replies_section.h
|
||||||
|
history/view/history_view_reply.cpp
|
||||||
|
history/view/history_view_reply.h
|
||||||
history/view/history_view_requests_bar.cpp
|
history/view/history_view_requests_bar.cpp
|
||||||
history/view/history_view_requests_bar.h
|
history/view/history_view_requests_bar.h
|
||||||
history/view/history_view_schedule_box.cpp
|
history/view/history_view_schedule_box.cpp
|
||||||
|
|
|
@ -723,7 +723,7 @@ HistoryItem::HistoryItem(
|
||||||
HistoryItem::~HistoryItem() {
|
HistoryItem::~HistoryItem() {
|
||||||
_media = nullptr;
|
_media = nullptr;
|
||||||
clearSavedMedia();
|
clearSavedMedia();
|
||||||
if (auto reply = Get<HistoryMessageReply>()) {
|
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||||
reply->clearData(this);
|
reply->clearData(this);
|
||||||
}
|
}
|
||||||
clearDependencyMessage();
|
clearDependencyMessage();
|
||||||
|
@ -1674,7 +1674,6 @@ void HistoryItem::applySentMessage(const MTPDmessage &data) {
|
||||||
setForwardsCount(data.vforwards().value_or(-1));
|
setForwardsCount(data.vforwards().value_or(-1));
|
||||||
if (const auto reply = data.vreply_to()) {
|
if (const auto reply = data.vreply_to()) {
|
||||||
reply->match([&](const MTPDmessageReplyHeader &data) {
|
reply->match([&](const MTPDmessageReplyHeader &data) {
|
||||||
// #TODO replies
|
|
||||||
const auto replyToPeer = data.vreply_to_peer_id()
|
const auto replyToPeer = data.vreply_to_peer_id()
|
||||||
? peerFromMTP(*data.vreply_to_peer_id())
|
? peerFromMTP(*data.vreply_to_peer_id())
|
||||||
: PeerId();
|
: PeerId();
|
||||||
|
@ -1980,9 +1979,6 @@ void HistoryItem::setRealId(MsgId newId) {
|
||||||
_history->owner().requestItemResize(this);
|
_history->owner().requestItemResize(this);
|
||||||
|
|
||||||
if (const auto reply = Get<HistoryMessageReply>()) {
|
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||||
if (reply->link()) {
|
|
||||||
reply->setLinkFrom(this);
|
|
||||||
}
|
|
||||||
incrementReplyToTopCounter();
|
incrementReplyToTopCounter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,130 +55,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kNonExpandedLinesLimit = 5;
|
|
||||||
const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_";
|
const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_";
|
||||||
|
|
||||||
void ValidateBackgroundEmoji(
|
|
||||||
DocumentId backgroundEmojiId,
|
|
||||||
not_null<Ui::BackgroundEmojiData*> data,
|
|
||||||
not_null<Ui::BackgroundEmojiCache*> cache,
|
|
||||||
not_null<Ui::Text::QuotePaintCache*> quote,
|
|
||||||
not_null<const HistoryView::Element*> holder) {
|
|
||||||
if (data->firstFrameMask.isNull()) {
|
|
||||||
if (!cache->frames[0].isNull()) {
|
|
||||||
for (auto &frame : cache->frames) {
|
|
||||||
frame = QImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const auto tag = Data::CustomEmojiSizeTag::Isolated;
|
|
||||||
if (!data->emoji) {
|
|
||||||
const auto owner = &holder->history()->owner();
|
|
||||||
const auto repaint = crl::guard(holder, [=] {
|
|
||||||
holder->history()->owner().requestViewRepaint(holder);
|
|
||||||
});
|
|
||||||
data->emoji = owner->customEmojiManager().create(
|
|
||||||
backgroundEmojiId,
|
|
||||||
repaint,
|
|
||||||
tag);
|
|
||||||
}
|
|
||||||
if (!data->emoji->ready()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto size = Data::FrameSizeFromTag(tag);
|
|
||||||
data->firstFrameMask = QImage(
|
|
||||||
QSize(size, size),
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
data->firstFrameMask.fill(Qt::transparent);
|
|
||||||
data->firstFrameMask.setDevicePixelRatio(style::DevicePixelRatio());
|
|
||||||
auto p = Painter(&data->firstFrameMask);
|
|
||||||
data->emoji->paint(p, {
|
|
||||||
.textColor = QColor(255, 255, 255),
|
|
||||||
.position = QPoint(0, 0),
|
|
||||||
.internal = {
|
|
||||||
.forceFirstFrame = true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
p.end();
|
|
||||||
|
|
||||||
data->emoji = nullptr;
|
|
||||||
}
|
|
||||||
if (!cache->frames[0].isNull() && cache->color == quote->icon) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cache->color = quote->icon;
|
|
||||||
const auto ratio = style::DevicePixelRatio();
|
|
||||||
auto colorized = QImage(
|
|
||||||
data->firstFrameMask.size(),
|
|
||||||
QImage::Format_ARGB32_Premultiplied);
|
|
||||||
colorized.setDevicePixelRatio(ratio);
|
|
||||||
style::colorizeImage(
|
|
||||||
data->firstFrameMask,
|
|
||||||
cache->color,
|
|
||||||
&colorized,
|
|
||||||
QRect(), // src
|
|
||||||
QPoint(), // dst
|
|
||||||
true); // use alpha
|
|
||||||
const auto make = [&](int size) {
|
|
||||||
size = style::ConvertScale(size) * ratio;
|
|
||||||
auto result = colorized.scaled(
|
|
||||||
size,
|
|
||||||
size,
|
|
||||||
Qt::IgnoreAspectRatio,
|
|
||||||
Qt::SmoothTransformation);
|
|
||||||
result.setDevicePixelRatio(ratio);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr auto kSize1 = 12;
|
|
||||||
constexpr auto kSize2 = 16;
|
|
||||||
constexpr auto kSize3 = 20;
|
|
||||||
cache->frames[0] = make(kSize1);
|
|
||||||
cache->frames[1] = make(kSize2);
|
|
||||||
cache->frames[2] = make(kSize3);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FillBackgroundEmoji(
|
|
||||||
Painter &p,
|
|
||||||
const QRect &rect,
|
|
||||||
bool quote,
|
|
||||||
const Ui::BackgroundEmojiCache &cache) {
|
|
||||||
p.setClipRect(rect);
|
|
||||||
|
|
||||||
const auto &frames = cache.frames;
|
|
||||||
const auto right = rect.x() + rect.width();
|
|
||||||
const auto paint = [&](int x, int y, int index, float64 opacity) {
|
|
||||||
y = style::ConvertScale(y);
|
|
||||||
if (y >= rect.height()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
p.setOpacity(opacity);
|
|
||||||
p.drawImage(
|
|
||||||
right - style::ConvertScale(x + (quote ? 12 : 0)),
|
|
||||||
rect.y() + y,
|
|
||||||
frames[index]);
|
|
||||||
};
|
|
||||||
|
|
||||||
paint(28, 4, 2, 0.32);
|
|
||||||
paint(51, 15, 1, 0.32);
|
|
||||||
paint(64, -2, 0, 0.28);
|
|
||||||
paint(87, 11, 1, 0.24);
|
|
||||||
paint(125, -2, 2, 0.16);
|
|
||||||
|
|
||||||
paint(28, 31, 1, 0.24);
|
|
||||||
paint(72, 33, 2, 0.2);
|
|
||||||
|
|
||||||
paint(46, 52, 1, 0.24);
|
|
||||||
paint(24, 55, 2, 0.18);
|
|
||||||
|
|
||||||
if (quote) {
|
|
||||||
paint(4, 23, 1, 0.28);
|
|
||||||
paint(0, 48, 0, 0.24);
|
|
||||||
}
|
|
||||||
|
|
||||||
p.setClipping(false);
|
|
||||||
p.setOpacity(1.);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void HistoryMessageVia::create(
|
void HistoryMessageVia::create(
|
||||||
|
@ -471,10 +349,7 @@ FullReplyTo ReplyToFromMTP(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryMessageReply::HistoryMessageReply()
|
HistoryMessageReply::HistoryMessageReply() = default;
|
||||||
: _name(st::maxSignatureSize / 2)
|
|
||||||
, _text(st::maxSignatureSize / 2) {
|
|
||||||
}
|
|
||||||
|
|
||||||
HistoryMessageReply &HistoryMessageReply::operator=(
|
HistoryMessageReply &HistoryMessageReply::operator=(
|
||||||
HistoryMessageReply &&other) = default;
|
HistoryMessageReply &&other) = default;
|
||||||
|
@ -527,11 +402,6 @@ bool HistoryMessageReply::updateData(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto repaint = [=] { holder->customEmojiRepaint(); };
|
|
||||||
const auto context = Core::MarkedTextContext{
|
|
||||||
.session = &holder->history()->session(),
|
|
||||||
.customEmojiRepaint = repaint,
|
|
||||||
};
|
|
||||||
const auto external = this->external();
|
const auto external = this->external();
|
||||||
_multiline = !_fields.storyId && (external || !_fields.quote.empty());
|
_multiline = !_fields.storyId && (external || !_fields.quote.empty());
|
||||||
|
|
||||||
|
@ -545,38 +415,10 @@ bool HistoryMessageReply::updateData(
|
||||||
&& ((!_fields.storyId && !_fields.messageId) || force);
|
&& ((!_fields.storyId && !_fields.messageId) || force);
|
||||||
_unavailable = unavailable ? 1 : 0;
|
_unavailable = unavailable ? 1 : 0;
|
||||||
|
|
||||||
const auto text = !_fields.quote.empty()
|
if (force) {
|
||||||
? _fields.quote
|
if (!_displaying && (_fields.messageId || _fields.storyId)) {
|
||||||
: resolvedMessage
|
|
||||||
? resolvedMessage->inReplyText()
|
|
||||||
: resolvedStory
|
|
||||||
? resolvedStory->inReplyText()
|
|
||||||
: TextWithEntities{ u"..."_q };
|
|
||||||
_text.setMarkedText(
|
|
||||||
st::defaultTextStyle,
|
|
||||||
text,
|
|
||||||
Ui::DialogTextOptions(),
|
|
||||||
context);
|
|
||||||
|
|
||||||
updateName(holder);
|
|
||||||
|
|
||||||
if (_displaying) {
|
|
||||||
setLinkFrom(holder);
|
|
||||||
const auto media = resolvedMessage
|
|
||||||
? resolvedMessage->media()
|
|
||||||
: nullptr;
|
|
||||||
if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) {
|
|
||||||
spoiler = nullptr;
|
|
||||||
} else if (!spoiler) {
|
|
||||||
spoiler = std::make_unique<Ui::SpoilerAnimation>(repaint);
|
|
||||||
}
|
|
||||||
} else if (force) {
|
|
||||||
if (_fields.messageId || _fields.storyId) {
|
|
||||||
_unavailable = 1;
|
_unavailable = 1;
|
||||||
}
|
}
|
||||||
spoiler = nullptr;
|
|
||||||
}
|
|
||||||
if (force) {
|
|
||||||
holder->history()->owner().requestItemResize(holder);
|
holder->history()->owner().requestItemResize(holder);
|
||||||
}
|
}
|
||||||
return resolvedMessage
|
return resolvedMessage
|
||||||
|
@ -611,74 +453,6 @@ void HistoryMessageReply::updateFields(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryMessageReply::expand() {
|
|
||||||
if (!_expandable || _expanded) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_expanded = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HistoryMessageReply::setLinkFrom(
|
|
||||||
not_null<HistoryItem*> holder) {
|
|
||||||
const auto externalChannelId = peerToChannel(_fields.externalPeerId);
|
|
||||||
const auto messageId = _fields.messageId;
|
|
||||||
const auto quote = _fields.manualQuote
|
|
||||||
? _fields.quote
|
|
||||||
: TextWithEntities();
|
|
||||||
const auto returnToId = holder->fullId();
|
|
||||||
const auto externalLink = [=](ClickContext context) {
|
|
||||||
const auto my = context.other.value<ClickHandlerContext>();
|
|
||||||
if (const auto controller = my.sessionWindow.get()) {
|
|
||||||
auto error = QString();
|
|
||||||
const auto owner = &controller->session().data();
|
|
||||||
if (const auto item = owner->message(returnToId)) {
|
|
||||||
if (const auto reply = item->Get<HistoryMessageReply>()) {
|
|
||||||
if (reply->expand()) {
|
|
||||||
owner->requestItemResize(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (externalChannelId) {
|
|
||||||
const auto channel = owner->channel(externalChannelId);
|
|
||||||
if (!channel->isForbidden()) {
|
|
||||||
if (messageId) {
|
|
||||||
JumpToMessageClickHandler(
|
|
||||||
channel,
|
|
||||||
messageId,
|
|
||||||
returnToId,
|
|
||||||
quote
|
|
||||||
)->onClick(context);
|
|
||||||
} else {
|
|
||||||
controller->showPeerInfo(channel);
|
|
||||||
}
|
|
||||||
} else if (channel->isBroadcast()) {
|
|
||||||
error = tr::lng_channel_not_accessible(tr::now);
|
|
||||||
} else {
|
|
||||||
error = tr::lng_group_not_accessible(tr::now);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error = tr::lng_reply_from_private_chat(tr::now);
|
|
||||||
}
|
|
||||||
if (!error.isEmpty()) {
|
|
||||||
controller->showToast(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
_link = resolvedMessage
|
|
||||||
? JumpToMessageClickHandler(
|
|
||||||
resolvedMessage.get(),
|
|
||||||
returnToId,
|
|
||||||
quote)
|
|
||||||
: resolvedStory
|
|
||||||
? JumpToStoryClickHandler(resolvedStory.get())
|
|
||||||
: (external()
|
|
||||||
&& (!_fields.messageId || (_unavailable && externalChannelId)))
|
|
||||||
? std::make_shared<LambdaClickHandler>(externalLink)
|
|
||||||
: nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HistoryMessageReply::setTopMessageId(MsgId topMessageId) {
|
void HistoryMessageReply::setTopMessageId(MsgId topMessageId) {
|
||||||
_fields.topMessageId = topMessageId;
|
_fields.topMessageId = topMessageId;
|
||||||
}
|
}
|
||||||
|
@ -696,11 +470,8 @@ void HistoryMessageReply::clearData(not_null<HistoryItem*> holder) {
|
||||||
resolvedStory.get());
|
resolvedStory.get());
|
||||||
resolvedStory = nullptr;
|
resolvedStory = nullptr;
|
||||||
}
|
}
|
||||||
_name.clear();
|
|
||||||
_text.clear();
|
|
||||||
_unavailable = 1;
|
_unavailable = 1;
|
||||||
_displaying = 0;
|
_displaying = 0;
|
||||||
_expandable = 0;
|
|
||||||
if (_multiline) {
|
if (_multiline) {
|
||||||
holder->history()->owner().requestItemResize(holder);
|
holder->history()->owner().requestItemResize(holder);
|
||||||
_multiline = 0;
|
_multiline = 0;
|
||||||
|
@ -714,236 +485,6 @@ bool HistoryMessageReply::external() const {
|
||||||
|| !_fields.externalSenderName.isEmpty();
|
|| !_fields.externalSenderName.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
PeerData *HistoryMessageReply::sender(not_null<HistoryItem*> holder) const {
|
|
||||||
if (resolvedStory) {
|
|
||||||
return resolvedStory->peer();
|
|
||||||
} else if (!resolvedMessage) {
|
|
||||||
if (!_externalSender && _fields.externalSenderId) {
|
|
||||||
_externalSender = holder->history()->owner().peer(
|
|
||||||
_fields.externalSenderId);
|
|
||||||
}
|
|
||||||
return _externalSender;
|
|
||||||
} else if (holder->Has<HistoryMessageForwarded>()) {
|
|
||||||
// Forward of a reply. Show reply-to original sender.
|
|
||||||
const auto forwarded
|
|
||||||
= resolvedMessage->Get<HistoryMessageForwarded>();
|
|
||||||
if (forwarded) {
|
|
||||||
return forwarded->originalSender;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (const auto from = resolvedMessage->displayFrom()) {
|
|
||||||
return from;
|
|
||||||
}
|
|
||||||
return resolvedMessage->author().get();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString HistoryMessageReply::senderName(
|
|
||||||
not_null<HistoryItem*> holder,
|
|
||||||
bool shorten) const {
|
|
||||||
if (const auto peer = sender(holder)) {
|
|
||||||
return senderName(peer, shorten);
|
|
||||||
} else if (!resolvedMessage) {
|
|
||||||
return _fields.externalSenderName;
|
|
||||||
} else if (holder->Has<HistoryMessageForwarded>()) {
|
|
||||||
// Forward of a reply. Show reply-to original sender.
|
|
||||||
const auto forwarded
|
|
||||||
= resolvedMessage->Get<HistoryMessageForwarded>();
|
|
||||||
if (forwarded) {
|
|
||||||
Assert(forwarded->hiddenSenderInfo != nullptr);
|
|
||||||
return forwarded->hiddenSenderInfo->name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString HistoryMessageReply::senderName(
|
|
||||||
not_null<PeerData*> peer,
|
|
||||||
bool shorten) const {
|
|
||||||
const auto user = shorten ? peer->asUser() : nullptr;
|
|
||||||
return user ? user->firstName : peer->name();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HistoryMessageReply::isNameUpdated(
|
|
||||||
not_null<HistoryItem*> holder) const {
|
|
||||||
if (const auto from = sender(holder)) {
|
|
||||||
if (_nameVersion < from->nameVersion()) {
|
|
||||||
updateName(holder, from);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HistoryMessageReply::updateName(
|
|
||||||
not_null<HistoryItem*> holder,
|
|
||||||
std::optional<PeerData*> resolvedSender) const {
|
|
||||||
auto viaBotUsername = QString();
|
|
||||||
if (resolvedMessage
|
|
||||||
&& !resolvedMessage->Has<HistoryMessageForwarded>()) {
|
|
||||||
if (const auto bot = resolvedMessage->viaBot()) {
|
|
||||||
viaBotUsername = bot->username();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const auto sender = resolvedSender.value_or(this->sender(holder));
|
|
||||||
const auto externalPeer = _fields.externalPeerId
|
|
||||||
? holder->history()->owner().peer(_fields.externalPeerId).get()
|
|
||||||
: nullptr;
|
|
||||||
const auto groupNameAdded = (externalPeer && externalPeer != sender);
|
|
||||||
const auto shorten = !viaBotUsername.isEmpty() || groupNameAdded;
|
|
||||||
const auto name = sender
|
|
||||||
? senderName(sender, shorten)
|
|
||||||
: senderName(holder, shorten);
|
|
||||||
const auto hasPreview = (resolvedStory
|
|
||||||
&& resolvedStory->hasReplyPreview())
|
|
||||||
|| (resolvedMessage
|
|
||||||
&& resolvedMessage->media()
|
|
||||||
&& resolvedMessage->media()->hasReplyPreview());
|
|
||||||
const auto previewSkip = hasPreview
|
|
||||||
? (st::messageQuoteStyle.outline
|
|
||||||
+ st::historyReplyPreviewMargin.left()
|
|
||||||
+ st::historyReplyPreview
|
|
||||||
+ st::historyReplyPreviewMargin.right()
|
|
||||||
- st::historyReplyPadding.left())
|
|
||||||
: 0;
|
|
||||||
const auto peerIcon = [](PeerData *peer) {
|
|
||||||
return !peer
|
|
||||||
? &st::historyReplyUser
|
|
||||||
: peer->isBroadcast()
|
|
||||||
? &st::historyReplyChannel
|
|
||||||
: (peer->isChannel() || peer->isChat())
|
|
||||||
? &st::historyReplyGroup
|
|
||||||
: &st::historyReplyUser;
|
|
||||||
};
|
|
||||||
const auto peerEmoji = [&](PeerData *peer) {
|
|
||||||
const auto owner = &holder->history()->owner();
|
|
||||||
return Ui::Text::SingleCustomEmoji(
|
|
||||||
owner->customEmojiManager().registerInternalEmoji(
|
|
||||||
*peerIcon(peer)));
|
|
||||||
};
|
|
||||||
auto nameFull = TextWithEntities();
|
|
||||||
if (!groupNameAdded && external() && !_fields.storyId) {
|
|
||||||
nameFull.append(peerEmoji(sender));
|
|
||||||
}
|
|
||||||
nameFull.append(name);
|
|
||||||
if (groupNameAdded) {
|
|
||||||
nameFull.append(peerEmoji(externalPeer));
|
|
||||||
nameFull.append(externalPeer->name());
|
|
||||||
}
|
|
||||||
if (!viaBotUsername.isEmpty()) {
|
|
||||||
nameFull.append(u" @"_q).append(viaBotUsername);
|
|
||||||
}
|
|
||||||
const auto context = Core::MarkedTextContext{
|
|
||||||
.session = &holder->history()->session(),
|
|
||||||
.customEmojiRepaint = [] {},
|
|
||||||
.customEmojiLoopLimit = 1,
|
|
||||||
};
|
|
||||||
_name.setMarkedText(
|
|
||||||
st::fwdTextStyle,
|
|
||||||
nameFull,
|
|
||||||
Ui::NameTextOptions(),
|
|
||||||
context);
|
|
||||||
if (sender) {
|
|
||||||
_nameVersion = sender->nameVersion();
|
|
||||||
}
|
|
||||||
const auto nameMaxWidth = previewSkip
|
|
||||||
+ _name.maxWidth()
|
|
||||||
+ (hasQuoteIcon()
|
|
||||||
? st::messageTextStyle.blockquote.icon.width()
|
|
||||||
: 0);
|
|
||||||
const auto storySkip = _fields.storyId
|
|
||||||
? (st::dialogsMiniReplyStory.skipText
|
|
||||||
+ st::dialogsMiniReplyStory.icon.icon.width())
|
|
||||||
: 0;
|
|
||||||
const auto optimalTextSize = _multiline
|
|
||||||
? countMultilineOptimalSize(previewSkip)
|
|
||||||
: QSize(
|
|
||||||
(previewSkip
|
|
||||||
+ storySkip
|
|
||||||
+ std::min(_text.maxWidth(), st::maxSignatureSize)),
|
|
||||||
st::normalFont->height);
|
|
||||||
_maxWidth = std::max(nameMaxWidth, optimalTextSize.width());
|
|
||||||
if (!_displaying) {
|
|
||||||
const auto phraseWidth = st::msgDateFont->width(statePhrase());
|
|
||||||
_maxWidth = _unavailable
|
|
||||||
? phraseWidth
|
|
||||||
: std::max(_maxWidth, phraseWidth);
|
|
||||||
}
|
|
||||||
_maxWidth = st::historyReplyPadding.left()
|
|
||||||
+ _maxWidth
|
|
||||||
+ st::historyReplyPadding.right();
|
|
||||||
_minHeight = st::historyReplyPadding.top()
|
|
||||||
+ st::msgServiceNameFont->height
|
|
||||||
+ optimalTextSize.height()
|
|
||||||
+ st::historyReplyPadding.bottom();
|
|
||||||
}
|
|
||||||
|
|
||||||
int HistoryMessageReply::resizeToWidth(int width) const {
|
|
||||||
const auto hasPreview = (resolvedStory
|
|
||||||
&& resolvedStory->hasReplyPreview())
|
|
||||||
|| (resolvedMessage
|
|
||||||
&& resolvedMessage->media()
|
|
||||||
&& resolvedMessage->media()->hasReplyPreview());
|
|
||||||
const auto previewSkip = hasPreview
|
|
||||||
? (st::messageQuoteStyle.outline
|
|
||||||
+ st::historyReplyPreviewMargin.left()
|
|
||||||
+ st::historyReplyPreview
|
|
||||||
+ st::historyReplyPreviewMargin.right()
|
|
||||||
- st::historyReplyPadding.left())
|
|
||||||
: 0;
|
|
||||||
if (width >= _maxWidth || !_multiline) {
|
|
||||||
_nameTwoLines = 0;
|
|
||||||
_expandable = 0;
|
|
||||||
_height = _minHeight;
|
|
||||||
return height();
|
|
||||||
}
|
|
||||||
const auto innerw = width
|
|
||||||
- st::historyReplyPadding.left()
|
|
||||||
- st::historyReplyPadding.right();
|
|
||||||
const auto namew = innerw - previewSkip;
|
|
||||||
const auto desiredNameHeight = _name.countHeight(namew);
|
|
||||||
_nameTwoLines = (desiredNameHeight > st::semiboldFont->height) ? 1 : 0;
|
|
||||||
const auto nameh = (_nameTwoLines ? 2 : 1) * st::semiboldFont->height;
|
|
||||||
const auto firstLineSkip = _nameTwoLines ? 0 : previewSkip;
|
|
||||||
auto lineCounter = 0;
|
|
||||||
auto elided = false;
|
|
||||||
const auto texth = _text.countDimensions(
|
|
||||||
textGeometry(innerw, firstLineSkip, &lineCounter, &elided)).height;
|
|
||||||
_expandable = (_multiline && elided) ? 1 : 0;
|
|
||||||
_height = st::historyReplyPadding.top()
|
|
||||||
+ nameh
|
|
||||||
+ (elided ? kNonExpandedLinesLimit * st::normalFont->height : texth)
|
|
||||||
+ st::historyReplyPadding.bottom();
|
|
||||||
return height();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ui::Text::GeometryDescriptor HistoryMessageReply::textGeometry(
|
|
||||||
int available,
|
|
||||||
int firstLineSkip,
|
|
||||||
not_null<int*> line,
|
|
||||||
not_null<bool*> outElided) const {
|
|
||||||
return { .layout = [=](Ui::Text::LineGeometry in) {
|
|
||||||
const auto skip = (*line ? 0 : firstLineSkip);
|
|
||||||
++*line;
|
|
||||||
*outElided = *outElided
|
|
||||||
|| !_multiline
|
|
||||||
|| (!_expanded
|
|
||||||
&& (*line == kNonExpandedLinesLimit)
|
|
||||||
&& in.width > available - skip);
|
|
||||||
in.width = available - skip;
|
|
||||||
in.left += skip;
|
|
||||||
in.elided = *outElided;
|
|
||||||
return in;
|
|
||||||
} };
|
|
||||||
}
|
|
||||||
|
|
||||||
int HistoryMessageReply::height() const {
|
|
||||||
return _height + st::historyReplyTop + st::historyReplyBottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
QMargins HistoryMessageReply::margins() const {
|
|
||||||
return QMargins(0, st::historyReplyTop, 0, st::historyReplyBottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HistoryMessageReply::itemRemoved(
|
void HistoryMessageReply::itemRemoved(
|
||||||
not_null<HistoryItem*> holder,
|
not_null<HistoryItem*> holder,
|
||||||
not_null<HistoryItem*> removed) {
|
not_null<HistoryItem*> removed) {
|
||||||
|
@ -962,254 +503,6 @@ void HistoryMessageReply::storyRemoved(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryMessageReply::hasQuoteIcon() const {
|
|
||||||
return _fields.manualQuote && !_fields.quote.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize HistoryMessageReply::countMultilineOptimalSize(
|
|
||||||
int previewSkip) const {
|
|
||||||
auto elided = false;
|
|
||||||
auto lineCounter = 0;
|
|
||||||
const auto max = previewSkip + _text.maxWidth();
|
|
||||||
const auto result = _text.countDimensions(
|
|
||||||
textGeometry(max, previewSkip, &lineCounter, &elided));
|
|
||||||
return { result.width, result.height };
|
|
||||||
}
|
|
||||||
|
|
||||||
void HistoryMessageReply::paint(
|
|
||||||
Painter &p,
|
|
||||||
not_null<const HistoryView::Element*> holder,
|
|
||||||
const Ui::ChatPaintContext &context,
|
|
||||||
int x,
|
|
||||||
int y,
|
|
||||||
int w,
|
|
||||||
bool inBubble) const {
|
|
||||||
const auto st = context.st;
|
|
||||||
const auto stm = context.messageStyle();
|
|
||||||
|
|
||||||
y += st::historyReplyTop;
|
|
||||||
const auto rect = QRect(x, y, w, _height);
|
|
||||||
const auto hasQuote = hasQuoteIcon();
|
|
||||||
const auto selected = context.selected();
|
|
||||||
const auto colorPeer = resolvedMessage
|
|
||||||
? resolvedMessage->displayFrom()
|
|
||||||
: resolvedStory
|
|
||||||
? resolvedStory->peer().get()
|
|
||||||
: _externalSender
|
|
||||||
? _externalSender
|
|
||||||
: nullptr;
|
|
||||||
const auto backgroundEmojiId = colorPeer
|
|
||||||
? colorPeer->backgroundEmojiId()
|
|
||||||
: DocumentId();
|
|
||||||
const auto colorIndexPlusOne = colorPeer
|
|
||||||
? (colorPeer->colorIndex() + 1)
|
|
||||||
: resolvedMessage
|
|
||||||
? (resolvedMessage->hiddenSenderInfo()->colorIndex + 1)
|
|
||||||
: 0;
|
|
||||||
const auto useColorIndex = colorIndexPlusOne && !context.outbg;
|
|
||||||
const auto colorPattern = colorIndexPlusOne
|
|
||||||
? st->colorPatternIndex(colorIndexPlusOne - 1)
|
|
||||||
: 0;
|
|
||||||
const auto cache = !inBubble
|
|
||||||
? (hasQuote
|
|
||||||
? st->serviceQuoteCache(colorPattern)
|
|
||||||
: st->serviceReplyCache(colorPattern)).get()
|
|
||||||
: useColorIndex
|
|
||||||
? (hasQuote
|
|
||||||
? st->coloredQuoteCache(selected, colorIndexPlusOne - 1)
|
|
||||||
: st->coloredReplyCache(selected, colorIndexPlusOne - 1)).get()
|
|
||||||
: (hasQuote
|
|
||||||
? stm->quoteCache[colorPattern]
|
|
||||||
: stm->replyCache[colorPattern]).get();
|
|
||||||
const auto "eSt = hasQuote
|
|
||||||
? st::messageTextStyle.blockquote
|
|
||||||
: st::messageQuoteStyle;
|
|
||||||
const auto backgroundEmoji = backgroundEmojiId
|
|
||||||
? st->backgroundEmojiData(backgroundEmojiId).get()
|
|
||||||
: nullptr;
|
|
||||||
const auto backgroundEmojiCache = backgroundEmoji
|
|
||||||
? &backgroundEmoji->caches[Ui::BackgroundEmojiData::CacheIndex(
|
|
||||||
selected,
|
|
||||||
context.outbg,
|
|
||||||
inBubble,
|
|
||||||
colorIndexPlusOne)]
|
|
||||||
: nullptr;
|
|
||||||
const auto rippleColor = cache->bg;
|
|
||||||
if (!inBubble) {
|
|
||||||
cache->bg = QColor(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
Ui::Text::ValidateQuotePaintCache(*cache, quoteSt);
|
|
||||||
Ui::Text::FillQuotePaint(p, rect, *cache, quoteSt);
|
|
||||||
if (backgroundEmoji) {
|
|
||||||
ValidateBackgroundEmoji(
|
|
||||||
backgroundEmojiId,
|
|
||||||
backgroundEmoji,
|
|
||||||
backgroundEmojiCache,
|
|
||||||
cache,
|
|
||||||
holder);
|
|
||||||
if (!backgroundEmojiCache->frames[0].isNull()) {
|
|
||||||
FillBackgroundEmoji(p, rect, hasQuote, *backgroundEmojiCache);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!inBubble) {
|
|
||||||
cache->bg = rippleColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ripple.animation) {
|
|
||||||
ripple.animation->paint(p, x, y, w, &rippleColor);
|
|
||||||
if (ripple.animation->empty()) {
|
|
||||||
ripple.animation.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto hasPreview = (resolvedStory
|
|
||||||
&& resolvedStory->hasReplyPreview())
|
|
||||||
|| (resolvedMessage
|
|
||||||
&& resolvedMessage->media()
|
|
||||||
&& resolvedMessage->media()->hasReplyPreview());
|
|
||||||
auto previewSkip = hasPreview
|
|
||||||
? (st::messageQuoteStyle.outline
|
|
||||||
+ st::historyReplyPreviewMargin.left()
|
|
||||||
+ st::historyReplyPreview
|
|
||||||
+ st::historyReplyPreviewMargin.right()
|
|
||||||
- st::historyReplyPadding.left())
|
|
||||||
: 0;
|
|
||||||
if (hasPreview && w <= st::historyReplyPadding.left() + previewSkip) {
|
|
||||||
hasPreview = false;
|
|
||||||
previewSkip = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto pausedSpoiler = context.paused
|
|
||||||
|| On(PowerSaving::kChatSpoiler);
|
|
||||||
auto textLeft = x + st::historyReplyPadding.left();
|
|
||||||
auto textTop = y
|
|
||||||
+ st::historyReplyPadding.top()
|
|
||||||
+ (st::msgServiceNameFont->height * (_nameTwoLines ? 2 : 1));
|
|
||||||
if (w > st::historyReplyPadding.left()) {
|
|
||||||
if (_displaying) {
|
|
||||||
const auto media = resolvedMessage ? resolvedMessage->media() : nullptr;
|
|
||||||
if (hasPreview) {
|
|
||||||
const auto image = media
|
|
||||||
? media->replyPreview()
|
|
||||||
: resolvedStory->replyPreview();
|
|
||||||
if (image) {
|
|
||||||
auto to = style::rtlrect(
|
|
||||||
x + st::historyReplyPreviewMargin.left(),
|
|
||||||
y + st::historyReplyPreviewMargin.top(),
|
|
||||||
st::historyReplyPreview,
|
|
||||||
st::historyReplyPreview,
|
|
||||||
w + 2 * x);
|
|
||||||
const auto preview = image->pixSingle(
|
|
||||||
image->size() / style::DevicePixelRatio(),
|
|
||||||
{
|
|
||||||
.colored = (context.selected()
|
|
||||||
? &st->msgStickerOverlay()
|
|
||||||
: nullptr),
|
|
||||||
.options = Images::Option::RoundSmall,
|
|
||||||
.outer = to.size(),
|
|
||||||
});
|
|
||||||
p.drawPixmap(to.x(), to.y(), preview);
|
|
||||||
if (spoiler) {
|
|
||||||
holder->clearCustomEmojiRepaint();
|
|
||||||
Ui::FillSpoilerRect(
|
|
||||||
p,
|
|
||||||
to,
|
|
||||||
Ui::DefaultImageSpoiler().frame(
|
|
||||||
spoiler->index(
|
|
||||||
context.now,
|
|
||||||
pausedSpoiler)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const auto textw = w
|
|
||||||
- st::historyReplyPadding.left()
|
|
||||||
- st::historyReplyPadding.right();
|
|
||||||
const auto namew = textw - previewSkip;
|
|
||||||
auto firstLineSkip = _nameTwoLines ? 0 : previewSkip;
|
|
||||||
if (namew > 0) {
|
|
||||||
p.setPen(!inBubble
|
|
||||||
? st->msgImgReplyBarColor()->c
|
|
||||||
: useColorIndex
|
|
||||||
? FromNameFg(context, colorIndexPlusOne - 1)
|
|
||||||
: stm->msgServiceFg->c);
|
|
||||||
_name.drawLeftElided(
|
|
||||||
p,
|
|
||||||
x + st::historyReplyPadding.left() + previewSkip,
|
|
||||||
y + st::historyReplyPadding.top(),
|
|
||||||
namew,
|
|
||||||
w + 2 * x,
|
|
||||||
_nameTwoLines ? 2 : 1);
|
|
||||||
|
|
||||||
p.setPen(inBubble
|
|
||||||
? stm->historyTextFg
|
|
||||||
: st->msgImgReplyBarColor());
|
|
||||||
holder->prepareCustomEmojiPaint(p, context, _text);
|
|
||||||
auto replyToTextPalette = &(!inBubble
|
|
||||||
? st->imgReplyTextPalette()
|
|
||||||
: useColorIndex
|
|
||||||
? st->coloredTextPalette(selected, colorIndexPlusOne - 1)
|
|
||||||
: stm->replyTextPalette);
|
|
||||||
if (_fields.storyId) {
|
|
||||||
st::dialogsMiniReplyStory.icon.icon.paint(
|
|
||||||
p,
|
|
||||||
textLeft + firstLineSkip,
|
|
||||||
textTop,
|
|
||||||
w + 2 * x,
|
|
||||||
replyToTextPalette->linkFg->c);
|
|
||||||
firstLineSkip += st::dialogsMiniReplyStory.skipText
|
|
||||||
+ st::dialogsMiniReplyStory.icon.icon.width();
|
|
||||||
}
|
|
||||||
auto owned = std::optional<style::owned_color>();
|
|
||||||
auto copy = std::optional<style::TextPalette>();
|
|
||||||
if (inBubble && colorIndexPlusOne) {
|
|
||||||
copy.emplace(*replyToTextPalette);
|
|
||||||
owned.emplace(cache->icon);
|
|
||||||
copy->linkFg = owned->color();
|
|
||||||
replyToTextPalette = &*copy;
|
|
||||||
}
|
|
||||||
auto l = 0;
|
|
||||||
auto e = false;
|
|
||||||
_text.draw(p, {
|
|
||||||
.position = { textLeft, textTop },
|
|
||||||
.geometry = textGeometry(textw, firstLineSkip, &l, &e),
|
|
||||||
.palette = replyToTextPalette,
|
|
||||||
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
|
||||||
.now = context.now,
|
|
||||||
.pausedEmoji = (context.paused
|
|
||||||
|| On(PowerSaving::kEmojiChat)),
|
|
||||||
.pausedSpoiler = pausedSpoiler,
|
|
||||||
.elisionOneLine = true,
|
|
||||||
});
|
|
||||||
p.setTextPalette(stm->textPalette);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.setFont(st::msgDateFont);
|
|
||||||
p.setPen(cache->icon);
|
|
||||||
p.drawTextLeft(
|
|
||||||
textLeft,
|
|
||||||
(y
|
|
||||||
+ st::historyReplyPadding.top()
|
|
||||||
+ (st::msgDateFont->height / 2)),
|
|
||||||
w + 2 * x,
|
|
||||||
st::msgDateFont->elided(
|
|
||||||
statePhrase(),
|
|
||||||
x + w - textLeft - st::historyReplyPadding.right()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HistoryMessageReply::unloadPersistentAnimation() {
|
|
||||||
_text.unloadPersistentAnimation();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString HistoryMessageReply::statePhrase() const {
|
|
||||||
return ((_fields.messageId || _fields.storyId) && !_unavailable)
|
|
||||||
? tr::lng_profile_loading(tr::now)
|
|
||||||
: _fields.storyId
|
|
||||||
? tr::lng_deleted_story(tr::now)
|
|
||||||
: tr::lng_deleted_message(tr::now);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HistoryMessageReply::refreshReplyToMedia() {
|
void HistoryMessageReply::refreshReplyToMedia() {
|
||||||
replyToDocumentId = 0;
|
replyToDocumentId = 0;
|
||||||
replyToWebPageId = 0;
|
replyToWebPageId = 0;
|
||||||
|
|
|
@ -22,7 +22,6 @@ namespace Ui {
|
||||||
struct ChatPaintContext;
|
struct ChatPaintContext;
|
||||||
class ChatStyle;
|
class ChatStyle;
|
||||||
struct PeerUserpicView;
|
struct PeerUserpicView;
|
||||||
class SpoilerAnimation;
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Ui::Text {
|
namespace Ui::Text {
|
||||||
|
@ -264,8 +263,6 @@ struct HistoryMessageReply
|
||||||
HistoryMessageReply &operator=(HistoryMessageReply &&other);
|
HistoryMessageReply &operator=(HistoryMessageReply &&other);
|
||||||
~HistoryMessageReply();
|
~HistoryMessageReply();
|
||||||
|
|
||||||
static constexpr auto kBarAlpha = 230. / 255.;
|
|
||||||
|
|
||||||
void set(ReplyFields fields);
|
void set(ReplyFields fields);
|
||||||
|
|
||||||
void updateFields(
|
void updateFields(
|
||||||
|
@ -279,20 +276,6 @@ struct HistoryMessageReply
|
||||||
void clearData(not_null<HistoryItem*> holder);
|
void clearData(not_null<HistoryItem*> holder);
|
||||||
|
|
||||||
[[nodiscard]] bool external() const;
|
[[nodiscard]] bool external() const;
|
||||||
[[nodiscard]] PeerData *sender(not_null<HistoryItem*> holder) const;
|
|
||||||
[[nodiscard]] QString senderName(
|
|
||||||
not_null<HistoryItem*> holder,
|
|
||||||
bool shorten) const;
|
|
||||||
[[nodiscard]] QString senderName(
|
|
||||||
not_null<PeerData*> peer,
|
|
||||||
bool shorten) 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(
|
void itemRemoved(
|
||||||
not_null<HistoryItem*> holder,
|
not_null<HistoryItem*> holder,
|
||||||
not_null<HistoryItem*> removed);
|
not_null<HistoryItem*> removed);
|
||||||
|
@ -300,19 +283,7 @@ struct HistoryMessageReply
|
||||||
not_null<HistoryItem*> holder,
|
not_null<HistoryItem*> holder,
|
||||||
not_null<Data::Story*> removed);
|
not_null<Data::Story*> removed);
|
||||||
|
|
||||||
bool expand();
|
[[nodiscard]] const ReplyFields &fields() const {
|
||||||
|
|
||||||
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;
|
return _fields;
|
||||||
}
|
}
|
||||||
[[nodiscard]] PeerId externalPeerId() const {
|
[[nodiscard]] PeerId externalPeerId() const {
|
||||||
|
@ -327,21 +298,22 @@ struct HistoryMessageReply
|
||||||
[[nodiscard]] MsgId topMessageId() const {
|
[[nodiscard]] MsgId topMessageId() const {
|
||||||
return _fields.topMessageId;
|
return _fields.topMessageId;
|
||||||
}
|
}
|
||||||
[[nodiscard]] int maxWidth() const {
|
|
||||||
return _maxWidth;
|
|
||||||
}
|
|
||||||
[[nodiscard]] ClickHandlerPtr link() const {
|
|
||||||
return _link;
|
|
||||||
}
|
|
||||||
[[nodiscard]] bool topicPost() const {
|
[[nodiscard]] bool topicPost() const {
|
||||||
return _fields.topicPost;
|
return _fields.topicPost;
|
||||||
}
|
}
|
||||||
[[nodiscard]] bool manualQuote() const {
|
[[nodiscard]] bool manualQuote() const {
|
||||||
return _fields.manualQuote;
|
return _fields.manualQuote;
|
||||||
}
|
}
|
||||||
[[nodiscard]] QString statePhrase() const;
|
[[nodiscard]] bool unavailable() const {
|
||||||
|
return _unavailable;
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool displaying() const {
|
||||||
|
return _displaying;
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool multiline() const {
|
||||||
|
return _multiline;
|
||||||
|
}
|
||||||
|
|
||||||
void setLinkFrom(not_null<HistoryItem*> holder);
|
|
||||||
void setTopMessageId(MsgId topMessageId);
|
void setTopMessageId(MsgId topMessageId);
|
||||||
|
|
||||||
void refreshReplyToMedia();
|
void refreshReplyToMedia();
|
||||||
|
@ -350,38 +322,12 @@ struct HistoryMessageReply
|
||||||
WebPageId replyToWebPageId = 0;
|
WebPageId replyToWebPageId = 0;
|
||||||
ReplyToMessagePointer resolvedMessage;
|
ReplyToMessagePointer resolvedMessage;
|
||||||
ReplyToStoryPointer resolvedStory;
|
ReplyToStoryPointer resolvedStory;
|
||||||
std::unique_ptr<Ui::SpoilerAnimation> spoiler;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
mutable std::unique_ptr<Ui::RippleAnimation> animation;
|
|
||||||
QPoint lastPoint;
|
|
||||||
} ripple;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] bool hasQuoteIcon() const;
|
|
||||||
[[nodiscard]] Ui::Text::GeometryDescriptor textGeometry(
|
|
||||||
int available,
|
|
||||||
int firstLineSkip,
|
|
||||||
not_null<int*> line,
|
|
||||||
not_null<bool*> outElided) const;
|
|
||||||
[[nodiscard]] QSize countMultilineOptimalSize(
|
|
||||||
int firstLineSkip) const;
|
|
||||||
|
|
||||||
ReplyFields _fields;
|
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;
|
uint8 _unavailable : 1 = 0;
|
||||||
uint8 _displaying : 1 = 0;
|
uint8 _displaying : 1 = 0;
|
||||||
uint8 _multiline : 1 = 0;
|
uint8 _multiline : 1 = 0;
|
||||||
mutable uint8 _expandable : 1 = 0;
|
|
||||||
uint8 _expanded : 1 = 0;
|
|
||||||
mutable uint8 _nameTwoLines : 1 = 0;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/reactions/history_view_reactions_button.h"
|
#include "history/view/reactions/history_view_reactions_button.h"
|
||||||
#include "history/view/reactions/history_view_reactions.h"
|
#include "history/view/reactions/history_view_reactions.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
|
#include "history/view/history_view_reply.h"
|
||||||
#include "history/view/history_view_spoiler_click_handler.h"
|
#include "history/view/history_view_spoiler_click_handler.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
@ -1362,6 +1363,10 @@ bool Element::hasFromName() const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Element::displayReply() const {
|
||||||
|
return Has<Reply>();
|
||||||
|
}
|
||||||
|
|
||||||
bool Element::displayFromName() const {
|
bool Element::displayFromName() const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1419,10 +1424,6 @@ TimeId Element::displayedEditDate() const {
|
||||||
return TimeId(0);
|
return TimeId(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryMessageReply *Element::displayedReply() const {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Element::toggleSelectionByHandlerClick(
|
bool Element::toggleSelectionByHandlerClick(
|
||||||
const ClickHandlerPtr &handler) const {
|
const ClickHandlerPtr &handler) const {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1482,7 +1483,7 @@ void Element::unloadHeavyPart() {
|
||||||
if (_flags & Flag::HeavyCustomEmoji) {
|
if (_flags & Flag::HeavyCustomEmoji) {
|
||||||
_flags &= ~Flag::HeavyCustomEmoji;
|
_flags &= ~Flag::HeavyCustomEmoji;
|
||||||
_text.unloadPersistentAnimation();
|
_text.unloadPersistentAnimation();
|
||||||
if (const auto reply = data()->Get<HistoryMessageReply>()) {
|
if (const auto reply = Get<Reply>()) {
|
||||||
reply->unloadPersistentAnimation();
|
reply->unloadPersistentAnimation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ enum class InfoDisplayType : char;
|
||||||
struct StateRequest;
|
struct StateRequest;
|
||||||
struct TextState;
|
struct TextState;
|
||||||
class Media;
|
class Media;
|
||||||
|
class Reply;
|
||||||
|
|
||||||
enum class Context : char {
|
enum class Context : char {
|
||||||
History,
|
History,
|
||||||
|
@ -433,6 +434,7 @@ public:
|
||||||
[[nodiscard]] virtual bool hasFromPhoto() const;
|
[[nodiscard]] virtual bool hasFromPhoto() const;
|
||||||
[[nodiscard]] virtual bool displayFromPhoto() const;
|
[[nodiscard]] virtual bool displayFromPhoto() const;
|
||||||
[[nodiscard]] virtual bool hasFromName() const;
|
[[nodiscard]] virtual bool hasFromName() const;
|
||||||
|
[[nodiscard]] bool displayReply() const;
|
||||||
[[nodiscard]] virtual bool displayFromName() const;
|
[[nodiscard]] virtual bool displayFromName() const;
|
||||||
[[nodiscard]] virtual TopicButton *displayedTopicButton() const;
|
[[nodiscard]] virtual TopicButton *displayedTopicButton() const;
|
||||||
[[nodiscard]] virtual bool displayForwardedFrom() const;
|
[[nodiscard]] virtual bool displayForwardedFrom() const;
|
||||||
|
@ -456,7 +458,6 @@ public:
|
||||||
std::optional<QPoint> pressPoint) const;
|
std::optional<QPoint> pressPoint) const;
|
||||||
[[nodiscard]] virtual TimeId displayedEditDate() const;
|
[[nodiscard]] virtual TimeId displayedEditDate() const;
|
||||||
[[nodiscard]] virtual bool hasVisibleText() const;
|
[[nodiscard]] virtual bool hasVisibleText() const;
|
||||||
[[nodiscard]] virtual HistoryMessageReply *displayedReply() const;
|
|
||||||
virtual void applyGroupAdminChanges(
|
virtual void applyGroupAdminChanges(
|
||||||
const base::flat_set<UserId> &changes) {
|
const base::flat_set<UserId> &changes) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/reactions/history_view_reactions.h"
|
#include "history/view/reactions/history_view_reactions.h"
|
||||||
#include "history/view/reactions/history_view_reactions_button.h"
|
#include "history/view/reactions/history_view_reactions_button.h"
|
||||||
#include "history/view/history_view_group_call_bar.h" // UserpicInRow.
|
#include "history/view/history_view_group_call_bar.h" // UserpicInRow.
|
||||||
|
#include "history/view/history_view_reply.h"
|
||||||
#include "history/view/history_view_view_button.h" // ViewButton.
|
#include "history/view/history_view_view_button.h" // ViewButton.
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "boxes/share_box.h"
|
#include "boxes/share_box.h"
|
||||||
|
@ -406,6 +407,7 @@ Message::Message(
|
||||||
Element *replacing)
|
Element *replacing)
|
||||||
: Element(delegate, data, replacing, Flag(0))
|
: Element(delegate, data, replacing, Flag(0))
|
||||||
, _invertMedia(data->invertMedia() && !data->emptyText())
|
, _invertMedia(data->invertMedia() && !data->emptyText())
|
||||||
|
, _hideReply(delegate->elementHideReply(this))
|
||||||
, _bottomInfo(
|
, _bottomInfo(
|
||||||
&data->history()->owner().reactions(),
|
&data->history()->owner().reactions(),
|
||||||
BottomInfoDataFromMessage(this)) {
|
BottomInfoDataFromMessage(this)) {
|
||||||
|
@ -597,6 +599,14 @@ auto Message::takeReactionAnimations()
|
||||||
|
|
||||||
QSize Message::performCountOptimalSize() {
|
QSize Message::performCountOptimalSize() {
|
||||||
const auto item = data();
|
const auto item = data();
|
||||||
|
|
||||||
|
const auto replyData = item->Get<HistoryMessageReply>();
|
||||||
|
if (replyData) {
|
||||||
|
AddComponents(Reply::Bit());
|
||||||
|
} else {
|
||||||
|
RemoveComponents(Reply::Bit());
|
||||||
|
}
|
||||||
|
|
||||||
const auto markup = item->inlineReplyMarkup();
|
const auto markup = item->inlineReplyMarkup();
|
||||||
const auto reactionsKey = [&] {
|
const auto reactionsKey = [&] {
|
||||||
return embedReactionsInBottomInfo()
|
return embedReactionsInBottomInfo()
|
||||||
|
@ -633,17 +643,19 @@ QSize Message::performCountOptimalSize() {
|
||||||
if (_reactions) {
|
if (_reactions) {
|
||||||
_reactions->initDimensions();
|
_reactions->initDimensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto reply = Get<Reply>();
|
||||||
|
if (reply) {
|
||||||
|
reply->update(this, replyData);
|
||||||
|
}
|
||||||
|
|
||||||
if (drawBubble()) {
|
if (drawBubble()) {
|
||||||
const auto forwarded = item->Get<HistoryMessageForwarded>();
|
const auto forwarded = item->Get<HistoryMessageForwarded>();
|
||||||
const auto reply = displayedReply();
|
|
||||||
const auto via = item->Get<HistoryMessageVia>();
|
const auto via = item->Get<HistoryMessageVia>();
|
||||||
const auto entry = logEntryOriginal();
|
const auto entry = logEntryOriginal();
|
||||||
if (forwarded) {
|
if (forwarded) {
|
||||||
forwarded->create(via);
|
forwarded->create(via);
|
||||||
}
|
}
|
||||||
if (reply) {
|
|
||||||
reply->updateName(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto mediaDisplayed = false;
|
auto mediaDisplayed = false;
|
||||||
if (media) {
|
if (media) {
|
||||||
|
@ -1217,9 +1229,11 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||||
p.restore();
|
p.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto reply = displayedReply()) {
|
if (const auto reply = Get<Reply>()) {
|
||||||
if (reply->isNameUpdated(data())) {
|
if (const auto replyData = item->Get<HistoryMessageReply>()) {
|
||||||
const_cast<Message*>(this)->setPendingResize();
|
if (reply->isNameUpdated(this, replyData)) {
|
||||||
|
const_cast<Message*>(this)->setPendingResize();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1598,8 +1612,15 @@ void Message::paintReplyInfo(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
QRect &trect,
|
QRect &trect,
|
||||||
const PaintContext &context) const {
|
const PaintContext &context) const {
|
||||||
if (const auto reply = displayedReply()) {
|
if (const auto reply = Get<Reply>()) {
|
||||||
reply->paint(p, this, context, trect.x(), trect.y(), trect.width(), true);
|
reply->paint(
|
||||||
|
p,
|
||||||
|
this,
|
||||||
|
context,
|
||||||
|
trect.x(),
|
||||||
|
trect.y(),
|
||||||
|
trect.width(),
|
||||||
|
true);
|
||||||
trect.setY(trect.y() + reply->height());
|
trect.setY(trect.y() + reply->height());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1753,7 +1774,7 @@ void Message::clickHandlerPressedChanged(
|
||||||
toggleTopicButtonRipple(pressed);
|
toggleTopicButtonRipple(pressed);
|
||||||
} else if (_viewButton) {
|
} else if (_viewButton) {
|
||||||
_viewButton->checkLink(handler, pressed);
|
_viewButton->checkLink(handler, pressed);
|
||||||
} else if (const auto reply = displayedReply()
|
} else if (const auto reply = Get<Reply>()
|
||||||
; reply && (handler == reply->link())) {
|
; reply && (handler == reply->link())) {
|
||||||
toggleReplyRipple(pressed);
|
toggleReplyRipple(pressed);
|
||||||
}
|
}
|
||||||
|
@ -1796,13 +1817,13 @@ void Message::toggleRightActionRipple(bool pressed) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Message::toggleReplyRipple(bool pressed) {
|
void Message::toggleReplyRipple(bool pressed) {
|
||||||
const auto reply = displayedReply();
|
const auto reply = Get<Reply>();
|
||||||
if (!reply) {
|
if (!reply) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pressed) {
|
if (pressed) {
|
||||||
if (!reply->ripple.animation && !unwrapped()) {
|
if (!unwrapped()) {
|
||||||
const auto &padding = st::msgPadding;
|
const auto &padding = st::msgPadding;
|
||||||
const auto geometry = countGeometry();
|
const auto geometry = countGeometry();
|
||||||
const auto item = data();
|
const auto item = data();
|
||||||
|
@ -1810,18 +1831,11 @@ void Message::toggleReplyRipple(bool pressed) {
|
||||||
const auto size = QSize(
|
const auto size = QSize(
|
||||||
geometry.width() - padding.left() - padding.right(),
|
geometry.width() - padding.left() - padding.right(),
|
||||||
reply->height() - margins.top() - margins.bottom());
|
reply->height() - margins.top() - margins.bottom());
|
||||||
reply->ripple.animation = std::make_unique<Ui::RippleAnimation>(
|
reply->createRippleAnimation(this, size);
|
||||||
st::defaultRippleAnimation,
|
|
||||||
Ui::RippleAnimation::RoundRectMask(
|
|
||||||
size,
|
|
||||||
st::messageQuoteStyle.radius),
|
|
||||||
[=] { item->history()->owner().requestItemRepaint(item); });
|
|
||||||
}
|
}
|
||||||
if (reply->ripple.animation) {
|
reply->addRipple();
|
||||||
reply->ripple.animation->add(reply->ripple.lastPoint);
|
} else {
|
||||||
}
|
reply->stopLastRipple();
|
||||||
} else if (reply->ripple.animation) {
|
|
||||||
reply->ripple.animation->lastStop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2482,7 +2496,7 @@ bool Message::getStateReplyInfo(
|
||||||
QPoint point,
|
QPoint point,
|
||||||
QRect &trect,
|
QRect &trect,
|
||||||
not_null<TextState*> outResult) const {
|
not_null<TextState*> outResult) const {
|
||||||
if (const auto reply = displayedReply()) {
|
if (const auto reply = Get<Reply>()) {
|
||||||
const auto margins = reply->margins();
|
const auto margins = reply->margins();
|
||||||
const auto height = reply->height();
|
const auto height = reply->height();
|
||||||
if (point.y() >= trect.top() && point.y() < trect.top() + height) {
|
if (point.y() >= trect.top() && point.y() < trect.top() + height) {
|
||||||
|
@ -2494,7 +2508,7 @@ bool Message::getStateReplyInfo(
|
||||||
if (g.contains(point)) {
|
if (g.contains(point)) {
|
||||||
if (const auto link = reply->link()) {
|
if (const auto link = reply->link()) {
|
||||||
outResult->link = reply->link();
|
outResult->link = reply->link();
|
||||||
reply->ripple.lastPoint = point - g.topLeft();
|
reply->saveRipplePoint(point - g.topLeft());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -2577,7 +2591,7 @@ void Message::updatePressed(QPoint point) {
|
||||||
auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
|
auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
|
||||||
trect.setTop(trect.top() + fwdheight);
|
trect.setTop(trect.top() + fwdheight);
|
||||||
}
|
}
|
||||||
if (const auto reply = item->Get<HistoryMessageReply>()) {
|
if (const auto reply = Get<Reply>()) {
|
||||||
trect.setTop(trect.top() + reply->height());
|
trect.setTop(trect.top() + reply->height());
|
||||||
}
|
}
|
||||||
if (const auto via = item->Get<HistoryMessageVia>()) {
|
if (const auto via = item->Get<HistoryMessageVia>()) {
|
||||||
|
@ -3123,13 +3137,6 @@ WebPage *Message::logEntryOriginal() const {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryMessageReply *Message::displayedReply() const {
|
|
||||||
if (const auto reply = data()->Get<HistoryMessageReply>()) {
|
|
||||||
return delegate()->elementHideReply(this) ? nullptr : reply;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Message::toggleSelectionByHandlerClick(
|
bool Message::toggleSelectionByHandlerClick(
|
||||||
const ClickHandlerPtr &handler) const {
|
const ClickHandlerPtr &handler) const {
|
||||||
if (_comments && _comments->link == handler) {
|
if (_comments && _comments->link == handler) {
|
||||||
|
@ -3580,7 +3587,7 @@ void Message::updateMediaInBubbleState() {
|
||||||
return displayFromName()
|
return displayFromName()
|
||||||
|| displayedTopicButton()
|
|| displayedTopicButton()
|
||||||
|| displayForwardedFrom()
|
|| displayForwardedFrom()
|
||||||
|| displayedReply()
|
|| Has<Reply>()
|
||||||
|| item->Has<HistoryMessageVia>();
|
|| item->Has<HistoryMessageVia>();
|
||||||
};
|
};
|
||||||
auto entry = logEntryOriginal();
|
auto entry = logEntryOriginal();
|
||||||
|
@ -3705,7 +3712,7 @@ QRect Message::innerGeometry() const {
|
||||||
+ st::topicButtonSkip);
|
+ st::topicButtonSkip);
|
||||||
}
|
}
|
||||||
// Skip displayForwardedFrom() until there are no animations for it.
|
// Skip displayForwardedFrom() until there are no animations for it.
|
||||||
if (const auto reply = displayedReply()) {
|
if (const auto reply = Get<Reply>()) {
|
||||||
// See paintReplyInfo().
|
// See paintReplyInfo().
|
||||||
result.translate(0, reply->height());
|
result.translate(0, reply->height());
|
||||||
}
|
}
|
||||||
|
@ -3872,7 +3879,7 @@ int Message::resizeContentGetHeight(int newWidth) {
|
||||||
textWidth - 2 * st::msgDateDelta.x()));
|
textWidth - 2 * st::msgDateDelta.x()));
|
||||||
|
|
||||||
if (bubble) {
|
if (bubble) {
|
||||||
auto reply = displayedReply();
|
auto reply = Get<Reply>();
|
||||||
auto via = item->Get<HistoryMessageVia>();
|
auto via = item->Get<HistoryMessageVia>();
|
||||||
auto entry = logEntryOriginal();
|
auto entry = logEntryOriginal();
|
||||||
|
|
||||||
|
@ -3962,7 +3969,6 @@ int Message::resizeContentGetHeight(int newWidth) {
|
||||||
newHeight += reply->resizeToWidth(contentWidth
|
newHeight += reply->resizeToWidth(contentWidth
|
||||||
- st::msgPadding.left()
|
- st::msgPadding.left()
|
||||||
- st::msgPadding.right());
|
- st::msgPadding.right());
|
||||||
reply->ripple.animation = nullptr;
|
|
||||||
}
|
}
|
||||||
if (needInfoDisplay()) {
|
if (needInfoDisplay()) {
|
||||||
newHeight += (bottomInfoHeight - st::msgDateFont->height);
|
newHeight += (bottomInfoHeight - st::msgDateFont->height);
|
||||||
|
|
|
@ -137,7 +137,6 @@ public:
|
||||||
[[nodiscard]] ClickHandlerPtr rightActionLink(
|
[[nodiscard]] ClickHandlerPtr rightActionLink(
|
||||||
std::optional<QPoint> pressPoint) const override;
|
std::optional<QPoint> pressPoint) const override;
|
||||||
[[nodiscard]] TimeId displayedEditDate() const override;
|
[[nodiscard]] TimeId displayedEditDate() const override;
|
||||||
[[nodiscard]] HistoryMessageReply *displayedReply() const override;
|
|
||||||
[[nodiscard]] bool toggleSelectionByHandlerClick(
|
[[nodiscard]] bool toggleSelectionByHandlerClick(
|
||||||
const ClickHandlerPtr &handler) const override;
|
const ClickHandlerPtr &handler) const override;
|
||||||
[[nodiscard]] bool allowTextSelectionByHandler(
|
[[nodiscard]] bool allowTextSelectionByHandler(
|
||||||
|
@ -308,8 +307,9 @@ private:
|
||||||
mutable std::unique_ptr<FromNameStatus> _fromNameStatus;
|
mutable std::unique_ptr<FromNameStatus> _fromNameStatus;
|
||||||
Ui::Text::String _rightBadge;
|
Ui::Text::String _rightBadge;
|
||||||
mutable int _fromNameVersion = 0;
|
mutable int _fromNameVersion = 0;
|
||||||
uint32 _bubbleWidthLimit : 31 = 0;
|
uint32 _bubbleWidthLimit : 30 = 0;
|
||||||
uint32 _invertMedia : 1 = 0;
|
uint32 _invertMedia : 1 = 0;
|
||||||
|
uint32 _hideReply : 1 = 0;
|
||||||
|
|
||||||
BottomInfo _bottomInfo;
|
BottomInfo _bottomInfo;
|
||||||
|
|
||||||
|
|
804
Telegram/SourceFiles/history/view/history_view_reply.cpp
Normal file
804
Telegram/SourceFiles/history/view/history_view_reply.cpp
Normal file
|
@ -0,0 +1,804 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
#include "history/view/history_view_reply.h"
|
||||||
|
|
||||||
|
#include "core/click_handler_types.h"
|
||||||
|
#include "core/ui_integration.h"
|
||||||
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_peer.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_story.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
|
#include "history/history_item_helpers.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "ui/chat/chat_style.h"
|
||||||
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
#include "ui/effects/spoiler_mess.h"
|
||||||
|
#include "ui/text/text_options.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
|
#include "ui/power_saving.h"
|
||||||
|
#include "window/window_session_controller.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
#include "styles/style_dialogs.h"
|
||||||
|
|
||||||
|
namespace HistoryView {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kNonExpandedLinesLimit = 5;
|
||||||
|
|
||||||
|
void ValidateBackgroundEmoji(
|
||||||
|
DocumentId backgroundEmojiId,
|
||||||
|
not_null<Ui::BackgroundEmojiData*> data,
|
||||||
|
not_null<Ui::BackgroundEmojiCache*> cache,
|
||||||
|
not_null<Ui::Text::QuotePaintCache*> quote,
|
||||||
|
not_null<const Element*> view) {
|
||||||
|
if (data->firstFrameMask.isNull()) {
|
||||||
|
if (!cache->frames[0].isNull()) {
|
||||||
|
for (auto &frame : cache->frames) {
|
||||||
|
frame = QImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto tag = Data::CustomEmojiSizeTag::Isolated;
|
||||||
|
if (!data->emoji) {
|
||||||
|
const auto owner = &view->history()->owner();
|
||||||
|
const auto repaint = crl::guard(view, [=] {
|
||||||
|
view->history()->owner().requestViewRepaint(view);
|
||||||
|
});
|
||||||
|
data->emoji = owner->customEmojiManager().create(
|
||||||
|
backgroundEmojiId,
|
||||||
|
repaint,
|
||||||
|
tag);
|
||||||
|
}
|
||||||
|
if (!data->emoji->ready()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto size = Data::FrameSizeFromTag(tag);
|
||||||
|
data->firstFrameMask = QImage(
|
||||||
|
QSize(size, size),
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
data->firstFrameMask.fill(Qt::transparent);
|
||||||
|
data->firstFrameMask.setDevicePixelRatio(style::DevicePixelRatio());
|
||||||
|
auto p = Painter(&data->firstFrameMask);
|
||||||
|
data->emoji->paint(p, {
|
||||||
|
.textColor = QColor(255, 255, 255),
|
||||||
|
.position = QPoint(0, 0),
|
||||||
|
.internal = {
|
||||||
|
.forceFirstFrame = true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
p.end();
|
||||||
|
|
||||||
|
data->emoji = nullptr;
|
||||||
|
}
|
||||||
|
if (!cache->frames[0].isNull() && cache->color == quote->icon) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cache->color = quote->icon;
|
||||||
|
const auto ratio = style::DevicePixelRatio();
|
||||||
|
auto colorized = QImage(
|
||||||
|
data->firstFrameMask.size(),
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
colorized.setDevicePixelRatio(ratio);
|
||||||
|
style::colorizeImage(
|
||||||
|
data->firstFrameMask,
|
||||||
|
cache->color,
|
||||||
|
&colorized,
|
||||||
|
QRect(), // src
|
||||||
|
QPoint(), // dst
|
||||||
|
true); // use alpha
|
||||||
|
const auto make = [&](int size) {
|
||||||
|
size = style::ConvertScale(size) * ratio;
|
||||||
|
auto result = colorized.scaled(
|
||||||
|
size,
|
||||||
|
size,
|
||||||
|
Qt::IgnoreAspectRatio,
|
||||||
|
Qt::SmoothTransformation);
|
||||||
|
result.setDevicePixelRatio(ratio);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr auto kSize1 = 12;
|
||||||
|
constexpr auto kSize2 = 16;
|
||||||
|
constexpr auto kSize3 = 20;
|
||||||
|
cache->frames[0] = make(kSize1);
|
||||||
|
cache->frames[1] = make(kSize2);
|
||||||
|
cache->frames[2] = make(kSize3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FillBackgroundEmoji(
|
||||||
|
Painter &p,
|
||||||
|
const QRect &rect,
|
||||||
|
bool quote,
|
||||||
|
const Ui::BackgroundEmojiCache &cache) {
|
||||||
|
p.setClipRect(rect);
|
||||||
|
|
||||||
|
const auto &frames = cache.frames;
|
||||||
|
const auto right = rect.x() + rect.width();
|
||||||
|
const auto paint = [&](int x, int y, int index, float64 opacity) {
|
||||||
|
y = style::ConvertScale(y);
|
||||||
|
if (y >= rect.height()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p.setOpacity(opacity);
|
||||||
|
p.drawImage(
|
||||||
|
right - style::ConvertScale(x + (quote ? 12 : 0)),
|
||||||
|
rect.y() + y,
|
||||||
|
frames[index]);
|
||||||
|
};
|
||||||
|
|
||||||
|
paint(28, 4, 2, 0.32);
|
||||||
|
paint(51, 15, 1, 0.32);
|
||||||
|
paint(64, -2, 0, 0.28);
|
||||||
|
paint(87, 11, 1, 0.24);
|
||||||
|
paint(125, -2, 2, 0.16);
|
||||||
|
|
||||||
|
paint(28, 31, 1, 0.24);
|
||||||
|
paint(72, 33, 2, 0.2);
|
||||||
|
|
||||||
|
paint(46, 52, 1, 0.24);
|
||||||
|
paint(24, 55, 2, 0.18);
|
||||||
|
|
||||||
|
if (quote) {
|
||||||
|
paint(4, 23, 1, 0.28);
|
||||||
|
paint(0, 48, 0, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.setClipping(false);
|
||||||
|
p.setOpacity(1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Reply::Reply()
|
||||||
|
: _name(st::maxSignatureSize / 2)
|
||||||
|
, _text(st::maxSignatureSize / 2) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Reply &Reply::operator=(Reply &&other) = default;
|
||||||
|
|
||||||
|
Reply::~Reply() = default;
|
||||||
|
|
||||||
|
void Reply::update(
|
||||||
|
not_null<Element*> view,
|
||||||
|
not_null<HistoryMessageReply*> data) {
|
||||||
|
const auto item = view->data();
|
||||||
|
const auto &fields = data->fields();
|
||||||
|
const auto message = data->resolvedMessage.get();
|
||||||
|
const auto story = data->resolvedStory.get();
|
||||||
|
if (!_externalSender) {
|
||||||
|
if (const auto id = fields.externalSenderId) {
|
||||||
|
_externalSender = view->history()->owner().peer(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_colorPeer = message
|
||||||
|
? message->displayFrom()
|
||||||
|
: story
|
||||||
|
? story->peer().get()
|
||||||
|
: _externalSender
|
||||||
|
? _externalSender
|
||||||
|
: nullptr;
|
||||||
|
_hiddenSenderColorIndexPlusOne = (!_colorPeer && message)
|
||||||
|
? (message->hiddenSenderInfo()->colorIndex + 1)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const auto hasPreview = (story && story->hasReplyPreview())
|
||||||
|
|| (message
|
||||||
|
&& message->media()
|
||||||
|
&& message->media()->hasReplyPreview());
|
||||||
|
_hasPreview = hasPreview ? 1 : 0;
|
||||||
|
_displaying = data->displaying() ? 1 : 0;
|
||||||
|
_multiline = data->multiline() ? 1 : 0;
|
||||||
|
_replyToStory = (fields.storyId != 0);
|
||||||
|
const auto hasQuoteIcon = _displaying
|
||||||
|
&& fields.manualQuote
|
||||||
|
&& !fields.quote.empty();
|
||||||
|
_hasQuoteIcon = hasQuoteIcon ? 1 : 0;
|
||||||
|
|
||||||
|
const auto text = (!_displaying && data->unavailable())
|
||||||
|
? TextWithEntities()
|
||||||
|
: !fields.quote.empty()
|
||||||
|
? fields.quote
|
||||||
|
: message
|
||||||
|
? message->inReplyText()
|
||||||
|
: story
|
||||||
|
? story->inReplyText()
|
||||||
|
: TextWithEntities();
|
||||||
|
const auto repaint = [=] { item->customEmojiRepaint(); };
|
||||||
|
const auto context = Core::MarkedTextContext{
|
||||||
|
.session = &view->history()->session(),
|
||||||
|
.customEmojiRepaint = repaint,
|
||||||
|
};
|
||||||
|
_text.setMarkedText(
|
||||||
|
st::defaultTextStyle,
|
||||||
|
text,
|
||||||
|
Ui::DialogTextOptions(),
|
||||||
|
context);
|
||||||
|
|
||||||
|
updateName(view, data);
|
||||||
|
|
||||||
|
if (_displaying) {
|
||||||
|
setLinkFrom(view, data);
|
||||||
|
const auto media = message ? message->media() : nullptr;
|
||||||
|
if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) {
|
||||||
|
_spoiler = nullptr;
|
||||||
|
} else if (!_spoiler) {
|
||||||
|
_spoiler = std::make_unique<Ui::SpoilerAnimation>(repaint);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_spoiler = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Reply::expand() {
|
||||||
|
if (!_expandable || _expanded) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_expanded = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reply::setLinkFrom(
|
||||||
|
not_null<Element*> view,
|
||||||
|
not_null<HistoryMessageReply*> data) {
|
||||||
|
const auto weak = base::make_weak(view);
|
||||||
|
const auto &fields = data->fields();
|
||||||
|
const auto externalChannelId = peerToChannel(fields.externalPeerId);
|
||||||
|
const auto messageId = fields.messageId;
|
||||||
|
const auto quote = fields.manualQuote
|
||||||
|
? fields.quote
|
||||||
|
: TextWithEntities();
|
||||||
|
const auto returnToId = view->data()->fullId();
|
||||||
|
const auto externalLink = [=](ClickContext context) {
|
||||||
|
const auto my = context.other.value<ClickHandlerContext>();
|
||||||
|
if (const auto controller = my.sessionWindow.get()) {
|
||||||
|
auto error = QString();
|
||||||
|
const auto owner = &controller->session().data();
|
||||||
|
if (const auto view = weak.get()) {
|
||||||
|
if (const auto reply = view->Get<Reply>()) {
|
||||||
|
if (reply->expand()) {
|
||||||
|
owner->requestViewResize(view);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (externalChannelId) {
|
||||||
|
const auto channel = owner->channel(externalChannelId);
|
||||||
|
if (!channel->isForbidden()) {
|
||||||
|
if (messageId) {
|
||||||
|
JumpToMessageClickHandler(
|
||||||
|
channel,
|
||||||
|
messageId,
|
||||||
|
returnToId,
|
||||||
|
quote
|
||||||
|
)->onClick(context);
|
||||||
|
} else {
|
||||||
|
controller->showPeerInfo(channel);
|
||||||
|
}
|
||||||
|
} else if (channel->isBroadcast()) {
|
||||||
|
error = tr::lng_channel_not_accessible(tr::now);
|
||||||
|
} else {
|
||||||
|
error = tr::lng_group_not_accessible(tr::now);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error = tr::lng_reply_from_private_chat(tr::now);
|
||||||
|
}
|
||||||
|
if (!error.isEmpty()) {
|
||||||
|
controller->showToast(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const auto message = data->resolvedMessage.get();
|
||||||
|
const auto story = data->resolvedStory.get();
|
||||||
|
_link = message
|
||||||
|
? JumpToMessageClickHandler(message, returnToId, quote)
|
||||||
|
: story
|
||||||
|
? JumpToStoryClickHandler(story)
|
||||||
|
: (data->external()
|
||||||
|
&& (!fields.messageId
|
||||||
|
|| (data->unavailable() && externalChannelId)))
|
||||||
|
? std::make_shared<LambdaClickHandler>(externalLink)
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
PeerData *Reply::sender(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
not_null<HistoryMessageReply*> data) const {
|
||||||
|
const auto message = data->resolvedMessage.get();
|
||||||
|
if (const auto story = data->resolvedStory.get()) {
|
||||||
|
return story->peer();
|
||||||
|
} else if (!message) {
|
||||||
|
return _externalSender;
|
||||||
|
} else if (view->data()->Has<HistoryMessageForwarded>()) {
|
||||||
|
// Forward of a reply. Show reply-to original sender.
|
||||||
|
const auto forwarded = message->Get<HistoryMessageForwarded>();
|
||||||
|
if (forwarded) {
|
||||||
|
return forwarded->originalSender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (const auto from = message->displayFrom()) {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
return message->author().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Reply::senderName(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
not_null<HistoryMessageReply*> data,
|
||||||
|
bool shorten) const {
|
||||||
|
if (const auto peer = sender(view, data)) {
|
||||||
|
return senderName(peer, shorten);
|
||||||
|
} else if (!data->resolvedMessage) {
|
||||||
|
return data->fields().externalSenderName;
|
||||||
|
} else if (view->data()->Has<HistoryMessageForwarded>()) {
|
||||||
|
// Forward of a reply. Show reply-to original sender.
|
||||||
|
const auto forwarded
|
||||||
|
= data->resolvedMessage->Get<HistoryMessageForwarded>();
|
||||||
|
if (forwarded) {
|
||||||
|
Assert(forwarded->hiddenSenderInfo != nullptr);
|
||||||
|
return forwarded->hiddenSenderInfo->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Reply::senderName(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
bool shorten) const {
|
||||||
|
const auto user = shorten ? peer->asUser() : nullptr;
|
||||||
|
return user ? user->firstName : peer->name();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Reply::isNameUpdated(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
not_null<HistoryMessageReply*> data) const {
|
||||||
|
if (const auto from = sender(view, data)) {
|
||||||
|
if (_nameVersion < from->nameVersion()) {
|
||||||
|
updateName(view, data, from);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reply::updateName(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
not_null<HistoryMessageReply*> data,
|
||||||
|
std::optional<PeerData*> resolvedSender) const {
|
||||||
|
auto viaBotUsername = QString();
|
||||||
|
const auto story = data->resolvedStory.get();
|
||||||
|
const auto message = data->resolvedMessage.get();
|
||||||
|
if (message && !message->Has<HistoryMessageForwarded>()) {
|
||||||
|
if (const auto bot = message->viaBot()) {
|
||||||
|
viaBotUsername = bot->username();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto &fields = data->fields();
|
||||||
|
const auto sender = resolvedSender.value_or(this->sender(view, data));
|
||||||
|
const auto externalPeer = fields.externalPeerId
|
||||||
|
? view->history()->owner().peer(fields.externalPeerId).get()
|
||||||
|
: nullptr;
|
||||||
|
const auto groupNameAdded = (externalPeer && externalPeer != sender);
|
||||||
|
const auto shorten = !viaBotUsername.isEmpty() || groupNameAdded;
|
||||||
|
const auto name = sender
|
||||||
|
? senderName(sender, shorten)
|
||||||
|
: senderName(view, data, shorten);
|
||||||
|
const auto previewSkip = _hasPreview
|
||||||
|
? (st::messageQuoteStyle.outline
|
||||||
|
+ st::historyReplyPreviewMargin.left()
|
||||||
|
+ st::historyReplyPreview
|
||||||
|
+ st::historyReplyPreviewMargin.right()
|
||||||
|
- st::historyReplyPadding.left())
|
||||||
|
: 0;
|
||||||
|
const auto peerIcon = [](PeerData *peer) {
|
||||||
|
return !peer
|
||||||
|
? &st::historyReplyUser
|
||||||
|
: peer->isBroadcast()
|
||||||
|
? &st::historyReplyChannel
|
||||||
|
: (peer->isChannel() || peer->isChat())
|
||||||
|
? &st::historyReplyGroup
|
||||||
|
: &st::historyReplyUser;
|
||||||
|
};
|
||||||
|
const auto peerEmoji = [&](PeerData *peer) {
|
||||||
|
const auto owner = &view->history()->owner();
|
||||||
|
return Ui::Text::SingleCustomEmoji(
|
||||||
|
owner->customEmojiManager().registerInternalEmoji(
|
||||||
|
*peerIcon(peer)));
|
||||||
|
};
|
||||||
|
auto nameFull = TextWithEntities();
|
||||||
|
if (!groupNameAdded && data->external() && !fields.storyId) {
|
||||||
|
nameFull.append(peerEmoji(sender));
|
||||||
|
}
|
||||||
|
nameFull.append(name);
|
||||||
|
if (groupNameAdded) {
|
||||||
|
nameFull.append(peerEmoji(externalPeer));
|
||||||
|
nameFull.append(externalPeer->name());
|
||||||
|
}
|
||||||
|
if (!viaBotUsername.isEmpty()) {
|
||||||
|
nameFull.append(u" @"_q).append(viaBotUsername);
|
||||||
|
}
|
||||||
|
const auto context = Core::MarkedTextContext{
|
||||||
|
.session = &view->history()->session(),
|
||||||
|
.customEmojiRepaint = [] {},
|
||||||
|
.customEmojiLoopLimit = 1,
|
||||||
|
};
|
||||||
|
_name.setMarkedText(
|
||||||
|
st::fwdTextStyle,
|
||||||
|
nameFull,
|
||||||
|
Ui::NameTextOptions(),
|
||||||
|
context);
|
||||||
|
if (sender) {
|
||||||
|
_nameVersion = sender->nameVersion();
|
||||||
|
}
|
||||||
|
const auto nameMaxWidth = previewSkip
|
||||||
|
+ _name.maxWidth()
|
||||||
|
+ (_hasQuoteIcon
|
||||||
|
? st::messageTextStyle.blockquote.icon.width()
|
||||||
|
: 0);
|
||||||
|
const auto storySkip = fields.storyId
|
||||||
|
? (st::dialogsMiniReplyStory.skipText
|
||||||
|
+ st::dialogsMiniReplyStory.icon.icon.width())
|
||||||
|
: 0;
|
||||||
|
const auto optimalTextSize = _multiline
|
||||||
|
? countMultilineOptimalSize(previewSkip)
|
||||||
|
: QSize(
|
||||||
|
(previewSkip
|
||||||
|
+ storySkip
|
||||||
|
+ std::min(_text.maxWidth(), st::maxSignatureSize)),
|
||||||
|
st::normalFont->height);
|
||||||
|
_maxWidth = std::max(nameMaxWidth, optimalTextSize.width());
|
||||||
|
if (!data->displaying()) {
|
||||||
|
const auto unavailable = data->unavailable();
|
||||||
|
_stateText = ((fields.messageId || fields.storyId) && !unavailable)
|
||||||
|
? tr::lng_profile_loading(tr::now)
|
||||||
|
: fields.storyId
|
||||||
|
? tr::lng_deleted_story(tr::now)
|
||||||
|
: tr::lng_deleted_message(tr::now);
|
||||||
|
const auto phraseWidth = st::msgDateFont->width(_stateText);
|
||||||
|
_maxWidth = unavailable
|
||||||
|
? phraseWidth
|
||||||
|
: std::max(_maxWidth, phraseWidth);
|
||||||
|
} else {
|
||||||
|
_stateText = QString();
|
||||||
|
}
|
||||||
|
_maxWidth = st::historyReplyPadding.left()
|
||||||
|
+ _maxWidth
|
||||||
|
+ st::historyReplyPadding.right();
|
||||||
|
_minHeight = st::historyReplyPadding.top()
|
||||||
|
+ st::msgServiceNameFont->height
|
||||||
|
+ optimalTextSize.height()
|
||||||
|
+ st::historyReplyPadding.bottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Reply::resizeToWidth(int width) const {
|
||||||
|
_ripple.animation = nullptr;
|
||||||
|
|
||||||
|
const auto previewSkip = _hasPreview
|
||||||
|
? (st::messageQuoteStyle.outline
|
||||||
|
+ st::historyReplyPreviewMargin.left()
|
||||||
|
+ st::historyReplyPreview
|
||||||
|
+ st::historyReplyPreviewMargin.right()
|
||||||
|
- st::historyReplyPadding.left())
|
||||||
|
: 0;
|
||||||
|
if (width >= _maxWidth || !_multiline) {
|
||||||
|
_nameTwoLines = 0;
|
||||||
|
_expandable = 0;
|
||||||
|
_height = _minHeight;
|
||||||
|
return height();
|
||||||
|
}
|
||||||
|
const auto innerw = width
|
||||||
|
- st::historyReplyPadding.left()
|
||||||
|
- st::historyReplyPadding.right();
|
||||||
|
const auto namew = innerw - previewSkip;
|
||||||
|
const auto desiredNameHeight = _name.countHeight(namew);
|
||||||
|
_nameTwoLines = (desiredNameHeight > st::semiboldFont->height) ? 1 : 0;
|
||||||
|
const auto nameh = (_nameTwoLines ? 2 : 1) * st::semiboldFont->height;
|
||||||
|
const auto firstLineSkip = _nameTwoLines ? 0 : previewSkip;
|
||||||
|
auto lineCounter = 0;
|
||||||
|
auto elided = false;
|
||||||
|
const auto texth = _text.countDimensions(
|
||||||
|
textGeometry(innerw, firstLineSkip, &lineCounter, &elided)).height;
|
||||||
|
_expandable = (_multiline && elided) ? 1 : 0;
|
||||||
|
_height = st::historyReplyPadding.top()
|
||||||
|
+ nameh
|
||||||
|
+ (elided ? kNonExpandedLinesLimit * st::normalFont->height : texth)
|
||||||
|
+ st::historyReplyPadding.bottom();
|
||||||
|
return height();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ui::Text::GeometryDescriptor Reply::textGeometry(
|
||||||
|
int available,
|
||||||
|
int firstLineSkip,
|
||||||
|
not_null<int*> line,
|
||||||
|
not_null<bool*> outElided) const {
|
||||||
|
return { .layout = [=](Ui::Text::LineGeometry in) {
|
||||||
|
const auto skip = (*line ? 0 : firstLineSkip);
|
||||||
|
++*line;
|
||||||
|
*outElided = *outElided
|
||||||
|
|| !_multiline
|
||||||
|
|| (!_expanded
|
||||||
|
&& (*line == kNonExpandedLinesLimit)
|
||||||
|
&& in.width > available - skip);
|
||||||
|
in.width = available - skip;
|
||||||
|
in.left += skip;
|
||||||
|
in.elided = *outElided;
|
||||||
|
return in;
|
||||||
|
} };
|
||||||
|
}
|
||||||
|
|
||||||
|
int Reply::height() const {
|
||||||
|
return _height + st::historyReplyTop + st::historyReplyBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMargins Reply::margins() const {
|
||||||
|
return QMargins(0, st::historyReplyTop, 0, st::historyReplyBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize Reply::countMultilineOptimalSize(
|
||||||
|
int previewSkip) const {
|
||||||
|
auto elided = false;
|
||||||
|
auto lineCounter = 0;
|
||||||
|
const auto max = previewSkip + _text.maxWidth();
|
||||||
|
const auto result = _text.countDimensions(
|
||||||
|
textGeometry(max, previewSkip, &lineCounter, &elided));
|
||||||
|
return { result.width, result.height };
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reply::paint(
|
||||||
|
Painter &p,
|
||||||
|
not_null<const Element*> view,
|
||||||
|
const Ui::ChatPaintContext &context,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int w,
|
||||||
|
bool inBubble) const {
|
||||||
|
const auto st = context.st;
|
||||||
|
const auto stm = context.messageStyle();
|
||||||
|
|
||||||
|
y += st::historyReplyTop;
|
||||||
|
const auto rect = QRect(x, y, w, _height);
|
||||||
|
const auto selected = context.selected();
|
||||||
|
const auto backgroundEmojiId = _colorPeer
|
||||||
|
? _colorPeer->backgroundEmojiId()
|
||||||
|
: DocumentId();
|
||||||
|
const auto colorIndexPlusOne = _colorPeer
|
||||||
|
? (_colorPeer->colorIndex() + 1)
|
||||||
|
: _hiddenSenderColorIndexPlusOne;
|
||||||
|
const auto useColorIndex = colorIndexPlusOne && !context.outbg;
|
||||||
|
const auto colorPattern = colorIndexPlusOne
|
||||||
|
? st->colorPatternIndex(colorIndexPlusOne - 1)
|
||||||
|
: 0;
|
||||||
|
const auto cache = !inBubble
|
||||||
|
? (_hasQuoteIcon
|
||||||
|
? st->serviceQuoteCache(colorPattern)
|
||||||
|
: st->serviceReplyCache(colorPattern)).get()
|
||||||
|
: useColorIndex
|
||||||
|
? (_hasQuoteIcon
|
||||||
|
? st->coloredQuoteCache(selected, colorIndexPlusOne - 1)
|
||||||
|
: st->coloredReplyCache(selected, colorIndexPlusOne - 1)).get()
|
||||||
|
: (_hasQuoteIcon
|
||||||
|
? stm->quoteCache[colorPattern]
|
||||||
|
: stm->replyCache[colorPattern]).get();
|
||||||
|
const auto "eSt = _hasQuoteIcon
|
||||||
|
? st::messageTextStyle.blockquote
|
||||||
|
: st::messageQuoteStyle;
|
||||||
|
const auto backgroundEmoji = backgroundEmojiId
|
||||||
|
? st->backgroundEmojiData(backgroundEmojiId).get()
|
||||||
|
: nullptr;
|
||||||
|
const auto backgroundEmojiCache = backgroundEmoji
|
||||||
|
? &backgroundEmoji->caches[Ui::BackgroundEmojiData::CacheIndex(
|
||||||
|
selected,
|
||||||
|
context.outbg,
|
||||||
|
inBubble,
|
||||||
|
colorIndexPlusOne)]
|
||||||
|
: nullptr;
|
||||||
|
const auto rippleColor = cache->bg;
|
||||||
|
if (!inBubble) {
|
||||||
|
cache->bg = QColor(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
Ui::Text::ValidateQuotePaintCache(*cache, quoteSt);
|
||||||
|
Ui::Text::FillQuotePaint(p, rect, *cache, quoteSt);
|
||||||
|
if (backgroundEmoji) {
|
||||||
|
ValidateBackgroundEmoji(
|
||||||
|
backgroundEmojiId,
|
||||||
|
backgroundEmoji,
|
||||||
|
backgroundEmojiCache,
|
||||||
|
cache,
|
||||||
|
view);
|
||||||
|
if (!backgroundEmojiCache->frames[0].isNull()) {
|
||||||
|
FillBackgroundEmoji(p, rect, _hasQuoteIcon, *backgroundEmojiCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!inBubble) {
|
||||||
|
cache->bg = rippleColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_ripple.animation) {
|
||||||
|
_ripple.animation->paint(p, x, y, w, &rippleColor);
|
||||||
|
if (_ripple.animation->empty()) {
|
||||||
|
_ripple.animation.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto hasPreview = (_hasPreview != 0);
|
||||||
|
auto previewSkip = hasPreview
|
||||||
|
? (st::messageQuoteStyle.outline
|
||||||
|
+ st::historyReplyPreviewMargin.left()
|
||||||
|
+ st::historyReplyPreview
|
||||||
|
+ st::historyReplyPreviewMargin.right()
|
||||||
|
- st::historyReplyPadding.left())
|
||||||
|
: 0;
|
||||||
|
if (hasPreview && w <= st::historyReplyPadding.left() + previewSkip) {
|
||||||
|
hasPreview = false;
|
||||||
|
previewSkip = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto pausedSpoiler = context.paused
|
||||||
|
|| On(PowerSaving::kChatSpoiler);
|
||||||
|
auto textLeft = x + st::historyReplyPadding.left();
|
||||||
|
auto textTop = y
|
||||||
|
+ st::historyReplyPadding.top()
|
||||||
|
+ (st::msgServiceNameFont->height * (_nameTwoLines ? 2 : 1));
|
||||||
|
if (w > st::historyReplyPadding.left()) {
|
||||||
|
if (_displaying) {
|
||||||
|
if (hasPreview) {
|
||||||
|
const auto data = view->data()->Get<HistoryMessageReply>();
|
||||||
|
const auto message = data
|
||||||
|
? data->resolvedMessage.get()
|
||||||
|
: nullptr;
|
||||||
|
const auto media = message ? message->media() : nullptr;
|
||||||
|
const auto image = media
|
||||||
|
? media->replyPreview()
|
||||||
|
: !data
|
||||||
|
? nullptr
|
||||||
|
: data->resolvedStory
|
||||||
|
? data->resolvedStory->replyPreview()
|
||||||
|
: nullptr;
|
||||||
|
if (image) {
|
||||||
|
auto to = style::rtlrect(
|
||||||
|
x + st::historyReplyPreviewMargin.left(),
|
||||||
|
y + st::historyReplyPreviewMargin.top(),
|
||||||
|
st::historyReplyPreview,
|
||||||
|
st::historyReplyPreview,
|
||||||
|
w + 2 * x);
|
||||||
|
const auto preview = image->pixSingle(
|
||||||
|
image->size() / style::DevicePixelRatio(),
|
||||||
|
{
|
||||||
|
.colored = (context.selected()
|
||||||
|
? &st->msgStickerOverlay()
|
||||||
|
: nullptr),
|
||||||
|
.options = Images::Option::RoundSmall,
|
||||||
|
.outer = to.size(),
|
||||||
|
});
|
||||||
|
p.drawPixmap(to.x(), to.y(), preview);
|
||||||
|
if (_spoiler) {
|
||||||
|
view->clearCustomEmojiRepaint();
|
||||||
|
Ui::FillSpoilerRect(
|
||||||
|
p,
|
||||||
|
to,
|
||||||
|
Ui::DefaultImageSpoiler().frame(
|
||||||
|
_spoiler->index(
|
||||||
|
context.now,
|
||||||
|
pausedSpoiler)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto textw = w
|
||||||
|
- st::historyReplyPadding.left()
|
||||||
|
- st::historyReplyPadding.right();
|
||||||
|
const auto namew = textw - previewSkip;
|
||||||
|
auto firstLineSkip = _nameTwoLines ? 0 : previewSkip;
|
||||||
|
if (namew > 0) {
|
||||||
|
p.setPen(!inBubble
|
||||||
|
? st->msgImgReplyBarColor()->c
|
||||||
|
: useColorIndex
|
||||||
|
? FromNameFg(context, colorIndexPlusOne - 1)
|
||||||
|
: stm->msgServiceFg->c);
|
||||||
|
_name.drawLeftElided(
|
||||||
|
p,
|
||||||
|
x + st::historyReplyPadding.left() + previewSkip,
|
||||||
|
y + st::historyReplyPadding.top(),
|
||||||
|
namew,
|
||||||
|
w + 2 * x,
|
||||||
|
_nameTwoLines ? 2 : 1);
|
||||||
|
|
||||||
|
p.setPen(inBubble
|
||||||
|
? stm->historyTextFg
|
||||||
|
: st->msgImgReplyBarColor());
|
||||||
|
view->prepareCustomEmojiPaint(p, context, _text);
|
||||||
|
auto replyToTextPalette = &(!inBubble
|
||||||
|
? st->imgReplyTextPalette()
|
||||||
|
: useColorIndex
|
||||||
|
? st->coloredTextPalette(selected, colorIndexPlusOne - 1)
|
||||||
|
: stm->replyTextPalette);
|
||||||
|
if (_replyToStory) {
|
||||||
|
st::dialogsMiniReplyStory.icon.icon.paint(
|
||||||
|
p,
|
||||||
|
textLeft + firstLineSkip,
|
||||||
|
textTop,
|
||||||
|
w + 2 * x,
|
||||||
|
replyToTextPalette->linkFg->c);
|
||||||
|
firstLineSkip += st::dialogsMiniReplyStory.skipText
|
||||||
|
+ st::dialogsMiniReplyStory.icon.icon.width();
|
||||||
|
}
|
||||||
|
auto owned = std::optional<style::owned_color>();
|
||||||
|
auto copy = std::optional<style::TextPalette>();
|
||||||
|
if (inBubble && colorIndexPlusOne) {
|
||||||
|
copy.emplace(*replyToTextPalette);
|
||||||
|
owned.emplace(cache->icon);
|
||||||
|
copy->linkFg = owned->color();
|
||||||
|
replyToTextPalette = &*copy;
|
||||||
|
}
|
||||||
|
auto l = 0;
|
||||||
|
auto e = false;
|
||||||
|
_text.draw(p, {
|
||||||
|
.position = { textLeft, textTop },
|
||||||
|
.geometry = textGeometry(textw, firstLineSkip, &l, &e),
|
||||||
|
.palette = replyToTextPalette,
|
||||||
|
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
||||||
|
.now = context.now,
|
||||||
|
.pausedEmoji = (context.paused
|
||||||
|
|| On(PowerSaving::kEmojiChat)),
|
||||||
|
.pausedSpoiler = pausedSpoiler,
|
||||||
|
.elisionOneLine = true,
|
||||||
|
});
|
||||||
|
p.setTextPalette(stm->textPalette);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.setFont(st::msgDateFont);
|
||||||
|
p.setPen(cache->icon);
|
||||||
|
p.drawTextLeft(
|
||||||
|
textLeft,
|
||||||
|
(y
|
||||||
|
+ st::historyReplyPadding.top()
|
||||||
|
+ (st::msgDateFont->height / 2)),
|
||||||
|
w + 2 * x,
|
||||||
|
st::msgDateFont->elided(
|
||||||
|
_stateText,
|
||||||
|
x + w - textLeft - st::historyReplyPadding.right()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reply::createRippleAnimation(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
QSize size) {
|
||||||
|
_ripple.animation = std::make_unique<Ui::RippleAnimation>(
|
||||||
|
st::defaultRippleAnimation,
|
||||||
|
Ui::RippleAnimation::RoundRectMask(
|
||||||
|
size,
|
||||||
|
st::messageQuoteStyle.radius),
|
||||||
|
[=] { view->history()->owner().requestViewRepaint(view); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reply::saveRipplePoint(QPoint point) const {
|
||||||
|
_ripple.lastPoint = point;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reply::addRipple() {
|
||||||
|
if (_ripple.animation) {
|
||||||
|
_ripple.animation->add(_ripple.lastPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reply::stopLastRipple() {
|
||||||
|
if (_ripple.animation) {
|
||||||
|
_ripple.animation->lastStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reply::unloadPersistentAnimation() {
|
||||||
|
_text.unloadPersistentAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace HistoryView
|
116
Telegram/SourceFiles/history/view/history_view_reply.h
Normal file
116
Telegram/SourceFiles/history/view/history_view_reply.h
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
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 "history/view/history_view_element.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class SpoilerAnimation;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace HistoryView {
|
||||||
|
|
||||||
|
class Reply final : public RuntimeComponent<Reply, Element> {
|
||||||
|
public:
|
||||||
|
Reply();
|
||||||
|
Reply(const Reply &other) = delete;
|
||||||
|
Reply(Reply &&other) = delete;
|
||||||
|
Reply &operator=(const Reply &other) = delete;
|
||||||
|
Reply &operator=(Reply &&other);
|
||||||
|
~Reply();
|
||||||
|
|
||||||
|
void update(
|
||||||
|
not_null<Element*> view,
|
||||||
|
not_null<HistoryMessageReply*> data);
|
||||||
|
|
||||||
|
[[nodiscard]] bool isNameUpdated(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
not_null<HistoryMessageReply*> data) const;
|
||||||
|
void updateName(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
not_null<HistoryMessageReply*> data,
|
||||||
|
std::optional<PeerData*> resolvedSender = std::nullopt) const;
|
||||||
|
[[nodiscard]] int resizeToWidth(int width) const;
|
||||||
|
[[nodiscard]] int height() const;
|
||||||
|
[[nodiscard]] QMargins margins() const;
|
||||||
|
|
||||||
|
bool expand();
|
||||||
|
|
||||||
|
void paint(
|
||||||
|
Painter &p,
|
||||||
|
not_null<const Element*> view,
|
||||||
|
const Ui::ChatPaintContext &context,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int w,
|
||||||
|
bool inBubble) const;
|
||||||
|
void unloadPersistentAnimation();
|
||||||
|
|
||||||
|
void createRippleAnimation(not_null<const Element*> view, QSize size);
|
||||||
|
void saveRipplePoint(QPoint point) const;
|
||||||
|
void addRipple();
|
||||||
|
void stopLastRipple();
|
||||||
|
|
||||||
|
[[nodiscard]] int maxWidth() const {
|
||||||
|
return _maxWidth;
|
||||||
|
}
|
||||||
|
[[nodiscard]] ClickHandlerPtr link() const {
|
||||||
|
return _link;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] Ui::Text::GeometryDescriptor textGeometry(
|
||||||
|
int available,
|
||||||
|
int firstLineSkip,
|
||||||
|
not_null<int*> line,
|
||||||
|
not_null<bool*> outElided) const;
|
||||||
|
[[nodiscard]] QSize countMultilineOptimalSize(
|
||||||
|
int firstLineSkip) const;
|
||||||
|
void setLinkFrom(
|
||||||
|
not_null<Element*> view,
|
||||||
|
not_null<HistoryMessageReply*> data);
|
||||||
|
|
||||||
|
[[nodiscard]] PeerData *sender(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
not_null<HistoryMessageReply*> data) const;
|
||||||
|
[[nodiscard]] QString senderName(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
not_null<HistoryMessageReply*> data,
|
||||||
|
bool shorten) const;
|
||||||
|
[[nodiscard]] QString senderName(
|
||||||
|
not_null<PeerData*> peer,
|
||||||
|
bool shorten) const;
|
||||||
|
|
||||||
|
ClickHandlerPtr _link;
|
||||||
|
std::unique_ptr<Ui::SpoilerAnimation> _spoiler;
|
||||||
|
mutable PeerData *_externalSender = nullptr;
|
||||||
|
mutable PeerData *_colorPeer = nullptr;
|
||||||
|
mutable struct {
|
||||||
|
mutable std::unique_ptr<Ui::RippleAnimation> animation;
|
||||||
|
QPoint lastPoint;
|
||||||
|
} _ripple;
|
||||||
|
mutable Ui::Text::String _name;
|
||||||
|
mutable Ui::Text::String _text;
|
||||||
|
mutable QString _stateText;
|
||||||
|
mutable int _maxWidth = 0;
|
||||||
|
mutable int _minHeight = 0;
|
||||||
|
mutable int _height = 0;
|
||||||
|
mutable int _nameVersion = 0;
|
||||||
|
uint8 _hiddenSenderColorIndexPlusOne = 0;
|
||||||
|
uint8 _hasQuoteIcon : 1 = 0;
|
||||||
|
uint8 _replyToStory : 1 = 0;
|
||||||
|
uint8 _expanded : 1 = 0;
|
||||||
|
mutable uint8 _expandable : 1 = 0;
|
||||||
|
mutable uint8 _nameTwoLines : 1 = 0;
|
||||||
|
mutable uint8 _hasPreview : 1 = 0;
|
||||||
|
mutable uint8 _displaying : 1 = 0;
|
||||||
|
mutable uint8 _multiline : 1 = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace HistoryView
|
|
@ -419,7 +419,7 @@ bool ExtendedPreview::needsBubble() const {
|
||||||
&& (item->repliesAreComments()
|
&& (item->repliesAreComments()
|
||||||
|| item->externalReply()
|
|| item->externalReply()
|
||||||
|| item->viaBot()
|
|| item->viaBot()
|
||||||
|| _parent->displayedReply()
|
|| _parent->displayReply()
|
||||||
|| _parent->displayForwardedFrom()
|
|| _parent->displayForwardedFrom()
|
||||||
|| _parent->displayFromName()
|
|| _parent->displayFromName()
|
||||||
|| _parent->displayedTopicButton());
|
|| _parent->displayedTopicButton());
|
||||||
|
|
|
@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
|
#include "history/view/history_view_reply.h"
|
||||||
#include "history/view/history_view_transcribe_button.h"
|
#include "history/view/history_view_transcribe_button.h"
|
||||||
#include "history/view/media/history_view_media_common.h"
|
#include "history/view/media/history_view_media_common.h"
|
||||||
#include "history/view/media/history_view_media_spoiler.h"
|
#include "history/view/media/history_view_media_spoiler.h"
|
||||||
|
@ -217,12 +218,12 @@ QSize Gif::countOptimalSize() {
|
||||||
} else if (isUnwrapped()) {
|
} else if (isUnwrapped()) {
|
||||||
const auto item = _parent->data();
|
const auto item = _parent->data();
|
||||||
auto via = item->Get<HistoryMessageVia>();
|
auto via = item->Get<HistoryMessageVia>();
|
||||||
auto reply = _parent->displayedReply();
|
auto reply = _parent->Get<Reply>();
|
||||||
auto forwarded = item->Get<HistoryMessageForwarded>();
|
auto forwarded = item->Get<HistoryMessageForwarded>();
|
||||||
if (forwarded) {
|
if (forwarded) {
|
||||||
forwarded->create(via);
|
forwarded->create(via);
|
||||||
}
|
}
|
||||||
maxWidth += additionalWidth(via, reply, forwarded);
|
maxWidth += additionalWidth(reply, via, forwarded);
|
||||||
accumulate_max(maxWidth, _parent->reactionsOptimalWidth());
|
accumulate_max(maxWidth, _parent->reactionsOptimalWidth());
|
||||||
}
|
}
|
||||||
return { maxWidth, minHeight };
|
return { maxWidth, minHeight };
|
||||||
|
@ -274,10 +275,10 @@ QSize Gif::countCurrentSize(int newWidth) {
|
||||||
|
|
||||||
const auto item = _parent->data();
|
const auto item = _parent->data();
|
||||||
auto via = item->Get<HistoryMessageVia>();
|
auto via = item->Get<HistoryMessageVia>();
|
||||||
auto reply = _parent->displayedReply();
|
auto reply = _parent->Get<Reply>();
|
||||||
auto forwarded = item->Get<HistoryMessageForwarded>();
|
auto forwarded = item->Get<HistoryMessageForwarded>();
|
||||||
if (via || reply || forwarded) {
|
if (via || reply || forwarded) {
|
||||||
auto additional = additionalWidth(via, reply, forwarded);
|
auto additional = additionalWidth(reply, via, forwarded);
|
||||||
newWidth += additional;
|
newWidth += additional;
|
||||||
accumulate_min(newWidth, availableWidth);
|
accumulate_min(newWidth, availableWidth);
|
||||||
auto usew = maxWidth() - additional;
|
auto usew = maxWidth() - additional;
|
||||||
|
@ -385,13 +386,13 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
|
||||||
auto usex = 0, usew = paintw;
|
auto usex = 0, usew = paintw;
|
||||||
const auto unwrapped = isUnwrapped();
|
const auto unwrapped = isUnwrapped();
|
||||||
const auto via = unwrapped ? item->Get<HistoryMessageVia>() : nullptr;
|
const auto via = unwrapped ? item->Get<HistoryMessageVia>() : nullptr;
|
||||||
const auto reply = unwrapped ? _parent->displayedReply() : nullptr;
|
const auto reply = unwrapped ? _parent->Get<Reply>() : nullptr;
|
||||||
const auto forwarded = unwrapped ? item->Get<HistoryMessageForwarded>() : nullptr;
|
const auto forwarded = unwrapped ? item->Get<HistoryMessageForwarded>() : nullptr;
|
||||||
const auto rightAligned = unwrapped
|
const auto rightAligned = unwrapped
|
||||||
&& outbg
|
&& outbg
|
||||||
&& !_parent->delegate()->elementIsChatWide();
|
&& !_parent->delegate()->elementIsChatWide();
|
||||||
if (via || reply || forwarded) {
|
if (via || reply || forwarded) {
|
||||||
usew = maxWidth() - additionalWidth(via, reply, forwarded);
|
usew = maxWidth() - additionalWidth(reply, via, forwarded);
|
||||||
if (rightAligned) {
|
if (rightAligned) {
|
||||||
usex = width() - usew;
|
usex = width() - usew;
|
||||||
}
|
}
|
||||||
|
@ -1013,13 +1014,13 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
|
||||||
const auto item = _parent->data();
|
const auto item = _parent->data();
|
||||||
auto usew = paintw, usex = 0;
|
auto usew = paintw, usex = 0;
|
||||||
const auto via = unwrapped ? item->Get<HistoryMessageVia>() : nullptr;
|
const auto via = unwrapped ? item->Get<HistoryMessageVia>() : nullptr;
|
||||||
const auto reply = unwrapped ? _parent->displayedReply() : nullptr;
|
const auto reply = unwrapped ? _parent->Get<Reply>() : nullptr;
|
||||||
const auto forwarded = unwrapped ? item->Get<HistoryMessageForwarded>() : nullptr;
|
const auto forwarded = unwrapped ? item->Get<HistoryMessageForwarded>() : nullptr;
|
||||||
const auto rightAligned = unwrapped
|
const auto rightAligned = unwrapped
|
||||||
&& outbg
|
&& outbg
|
||||||
&& !_parent->delegate()->elementIsChatWide();
|
&& !_parent->delegate()->elementIsChatWide();
|
||||||
if (via || reply || forwarded) {
|
if (via || reply || forwarded) {
|
||||||
usew = maxWidth() - additionalWidth(via, reply, forwarded);
|
usew = maxWidth() - additionalWidth(reply, via, forwarded);
|
||||||
if (rightAligned) {
|
if (rightAligned) {
|
||||||
usex = width() - usew;
|
usex = width() - usew;
|
||||||
}
|
}
|
||||||
|
@ -1094,15 +1095,8 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
|
||||||
const auto replyRect = QRect(rectx, recty, rectw, recth);
|
const auto replyRect = QRect(rectx, recty, rectw, recth);
|
||||||
if (replyRect.contains(point)) {
|
if (replyRect.contains(point)) {
|
||||||
result.link = reply->link();
|
result.link = reply->link();
|
||||||
reply->ripple.lastPoint = point - replyRect.topLeft();
|
reply->saveRipplePoint(point - replyRect.topLeft());
|
||||||
if (!reply->ripple.animation) {
|
reply->createRippleAnimation(_parent, replyRect.size());
|
||||||
reply->ripple.animation = std::make_unique<Ui::RippleAnimation>(
|
|
||||||
st::defaultRippleAnimation,
|
|
||||||
Ui::RippleAnimation::RoundRectMask(
|
|
||||||
replyRect.size(),
|
|
||||||
st::messageQuoteStyle.radius),
|
|
||||||
[=] { item->history()->owner().requestItemRepaint(item); });
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1520,7 +1514,7 @@ bool Gif::needsBubble() const {
|
||||||
return item->repliesAreComments()
|
return item->repliesAreComments()
|
||||||
|| item->externalReply()
|
|| item->externalReply()
|
||||||
|| item->viaBot()
|
|| item->viaBot()
|
||||||
|| _parent->displayedReply()
|
|| _parent->displayReply()
|
||||||
|| _parent->displayForwardedFrom()
|
|| _parent->displayForwardedFrom()
|
||||||
|| _parent->displayFromName()
|
|| _parent->displayFromName()
|
||||||
|| _parent->displayedTopicButton();
|
|| _parent->displayedTopicButton();
|
||||||
|
@ -1542,10 +1536,10 @@ QRect Gif::contentRectForReactions() const {
|
||||||
&& !_parent->delegate()->elementIsChatWide();
|
&& !_parent->delegate()->elementIsChatWide();
|
||||||
const auto item = _parent->data();
|
const auto item = _parent->data();
|
||||||
const auto via = item->Get<HistoryMessageVia>();
|
const auto via = item->Get<HistoryMessageVia>();
|
||||||
const auto reply = _parent->displayedReply();
|
const auto reply = _parent->Get<Reply>();
|
||||||
const auto forwarded = item->Get<HistoryMessageForwarded>();
|
const auto forwarded = item->Get<HistoryMessageForwarded>();
|
||||||
if (via || reply || forwarded) {
|
if (via || reply || forwarded) {
|
||||||
usew = maxWidth() - additionalWidth(via, reply, forwarded);
|
usew = maxWidth() - additionalWidth(reply, via, forwarded);
|
||||||
}
|
}
|
||||||
accumulate_max(usew, _parent->reactionsOptimalWidth());
|
accumulate_max(usew, _parent->reactionsOptimalWidth());
|
||||||
if (rightAligned) {
|
if (rightAligned) {
|
||||||
|
@ -1602,8 +1596,8 @@ QPoint Gif::resolveCustomInfoRightBottom() const {
|
||||||
int Gif::additionalWidth() const {
|
int Gif::additionalWidth() const {
|
||||||
const auto item = _parent->data();
|
const auto item = _parent->data();
|
||||||
return additionalWidth(
|
return additionalWidth(
|
||||||
|
_parent->Get<Reply>(),
|
||||||
item->Get<HistoryMessageVia>(),
|
item->Get<HistoryMessageVia>(),
|
||||||
item->Get<HistoryMessageReply>(),
|
|
||||||
item->Get<HistoryMessageForwarded>());
|
item->Get<HistoryMessageForwarded>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1763,7 +1757,10 @@ void Gif::refreshCaption() {
|
||||||
_caption = createCaption(_parent->data());
|
_caption = createCaption(_parent->data());
|
||||||
}
|
}
|
||||||
|
|
||||||
int Gif::additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply, const HistoryMessageForwarded *forwarded) const {
|
int Gif::additionalWidth(
|
||||||
|
const Reply *reply,
|
||||||
|
const HistoryMessageVia *via,
|
||||||
|
const HistoryMessageForwarded *forwarded) const {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
if (forwarded) {
|
if (forwarded) {
|
||||||
accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right());
|
accumulate_max(result, st::msgReplyPadding.left() + st::msgReplyPadding.left() + forwarded->text.maxWidth() + st::msgReplyPadding.right());
|
||||||
|
|
|
@ -37,6 +37,7 @@ enum class Error;
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
|
class Reply;
|
||||||
class TranscribeButton;
|
class TranscribeButton;
|
||||||
|
|
||||||
class Gif final : public File {
|
class Gif final : public File {
|
||||||
|
@ -176,8 +177,8 @@ private:
|
||||||
[[nodiscard]] bool needInfoDisplay() const;
|
[[nodiscard]] bool needInfoDisplay() const;
|
||||||
[[nodiscard]] bool needCornerStatusDisplay() const;
|
[[nodiscard]] bool needCornerStatusDisplay() const;
|
||||||
[[nodiscard]] int additionalWidth(
|
[[nodiscard]] int additionalWidth(
|
||||||
|
const Reply *reply,
|
||||||
const HistoryMessageVia *via,
|
const HistoryMessageVia *via,
|
||||||
const HistoryMessageReply *reply,
|
|
||||||
const HistoryMessageForwarded *forwarded) const;
|
const HistoryMessageForwarded *forwarded) const;
|
||||||
[[nodiscard]] int additionalWidth() const;
|
[[nodiscard]] int additionalWidth() const;
|
||||||
[[nodiscard]] bool isUnwrapped() const;
|
[[nodiscard]] bool isUnwrapped() const;
|
||||||
|
|
|
@ -375,7 +375,7 @@ bool Location::needsBubble() const {
|
||||||
return item->repliesAreComments()
|
return item->repliesAreComments()
|
||||||
|| item->externalReply()
|
|| item->externalReply()
|
||||||
|| item->viaBot()
|
|| item->viaBot()
|
||||||
|| _parent->displayedReply()
|
|| _parent->displayReply()
|
||||||
|| _parent->displayForwardedFrom()
|
|| _parent->displayForwardedFrom()
|
||||||
|| _parent->displayFromName()
|
|| _parent->displayFromName()
|
||||||
|| _parent->displayedTopicButton();
|
|| _parent->displayedTopicButton();
|
||||||
|
|
|
@ -869,7 +869,7 @@ bool GroupedMedia::computeNeedBubble() const {
|
||||||
if (item->repliesAreComments()
|
if (item->repliesAreComments()
|
||||||
|| item->externalReply()
|
|| item->externalReply()
|
||||||
|| item->viaBot()
|
|| item->viaBot()
|
||||||
|| _parent->displayedReply()
|
|| _parent->displayReply()
|
||||||
|| _parent->displayForwardedFrom()
|
|| _parent->displayForwardedFrom()
|
||||||
|| _parent->displayFromName()
|
|| _parent->displayFromName()
|
||||||
|| _parent->displayedTopicButton()
|
|| _parent->displayedTopicButton()
|
||||||
|
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/media/history_view_sticker.h"
|
#include "history/view/media/history_view_sticker.h"
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
|
#include "history/view/history_view_reply.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/history_item_components.h"
|
#include "history/history_item_components.h"
|
||||||
#include "lottie/lottie_single_player.h"
|
#include "lottie/lottie_single_player.h"
|
||||||
|
@ -54,13 +55,13 @@ QSize UnwrappedMedia::countOptimalSize() {
|
||||||
if (_parent->media() == this) {
|
if (_parent->media() == this) {
|
||||||
const auto item = _parent->data();
|
const auto item = _parent->data();
|
||||||
const auto via = item->Get<HistoryMessageVia>();
|
const auto via = item->Get<HistoryMessageVia>();
|
||||||
const auto reply = _parent->displayedReply();
|
const auto reply = _parent->Get<Reply>();
|
||||||
const auto topic = _parent->displayedTopicButton();
|
const auto topic = _parent->displayedTopicButton();
|
||||||
const auto forwarded = getDisplayedForwardedInfo();
|
const auto forwarded = getDisplayedForwardedInfo();
|
||||||
if (forwarded) {
|
if (forwarded) {
|
||||||
forwarded->create(via);
|
forwarded->create(via);
|
||||||
}
|
}
|
||||||
maxWidth += additionalWidth(topic, via, reply, forwarded);
|
maxWidth += additionalWidth(topic, reply, via, forwarded);
|
||||||
accumulate_max(maxWidth, _parent->reactionsOptimalWidth());
|
accumulate_max(maxWidth, _parent->reactionsOptimalWidth());
|
||||||
if (const auto size = _parent->rightActionSize()) {
|
if (const auto size = _parent->rightActionSize()) {
|
||||||
minHeight = std::max(
|
minHeight = std::max(
|
||||||
|
@ -93,11 +94,11 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) {
|
||||||
accumulate_max(newWidth, _parent->reactionsOptimalWidth());
|
accumulate_max(newWidth, _parent->reactionsOptimalWidth());
|
||||||
_topAdded = 0;
|
_topAdded = 0;
|
||||||
const auto via = item->Get<HistoryMessageVia>();
|
const auto via = item->Get<HistoryMessageVia>();
|
||||||
const auto reply = _parent->displayedReply();
|
const auto reply = _parent->Get<Reply>();
|
||||||
const auto topic = _parent->displayedTopicButton();
|
const auto topic = _parent->displayedTopicButton();
|
||||||
const auto forwarded = getDisplayedForwardedInfo();
|
const auto forwarded = getDisplayedForwardedInfo();
|
||||||
if (topic || via || reply || forwarded) {
|
if (topic || via || reply || forwarded) {
|
||||||
const auto additional = additionalWidth(topic, via, reply, forwarded);
|
const auto additional = additionalWidth(topic, reply, via, forwarded);
|
||||||
const auto optimalw = maxWidth() - additional;
|
const auto optimalw = maxWidth() - additional;
|
||||||
const auto additionalMinWidth = std::min(additional, st::msgReplyPadding.left() + st::msgMinWidth / 2);
|
const auto additionalMinWidth = std::min(additional, st::msgReplyPadding.left() + st::msgMinWidth / 2);
|
||||||
_additionalOnTop = (optimalw + additionalMinWidth) > newWidth;
|
_additionalOnTop = (optimalw + additionalMinWidth) > newWidth;
|
||||||
|
@ -107,7 +108,7 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) {
|
||||||
if (reply) {
|
if (reply) {
|
||||||
[[maybe_unused]] auto h = reply->resizeToWidth(surroundingWidth);
|
[[maybe_unused]] auto h = reply->resizeToWidth(surroundingWidth);
|
||||||
}
|
}
|
||||||
const auto surrounding = surroundingInfo(topic, via, reply, forwarded, surroundingWidth);
|
const auto surrounding = surroundingInfo(topic, reply, via, forwarded, surroundingWidth);
|
||||||
if (_additionalOnTop) {
|
if (_additionalOnTop) {
|
||||||
_topAdded = surrounding.height + st::msgMargin.bottom();
|
_topAdded = surrounding.height + st::msgMargin.bottom();
|
||||||
newHeight += _topAdded;
|
newHeight += _topAdded;
|
||||||
|
@ -166,17 +167,17 @@ void UnwrappedMedia::draw(Painter &p, const PaintContext &context) const {
|
||||||
if (!inWebPage && (context.skipDrawingParts
|
if (!inWebPage && (context.skipDrawingParts
|
||||||
!= PaintContext::SkipDrawingParts::Surrounding)) {
|
!= PaintContext::SkipDrawingParts::Surrounding)) {
|
||||||
const auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
|
const auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
|
||||||
const auto reply = inWebPage ? nullptr : _parent->displayedReply();
|
const auto reply = inWebPage ? nullptr : _parent->Get<Reply>();
|
||||||
const auto topic = inWebPage ? nullptr : _parent->displayedTopicButton();
|
const auto topic = inWebPage ? nullptr : _parent->displayedTopicButton();
|
||||||
const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo();
|
const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo();
|
||||||
drawSurrounding(p, inner, context, topic, via, reply, forwarded);
|
drawSurrounding(p, inner, context, topic, reply, via, forwarded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UnwrappedMedia::SurroundingInfo UnwrappedMedia::surroundingInfo(
|
UnwrappedMedia::SurroundingInfo UnwrappedMedia::surroundingInfo(
|
||||||
const TopicButton *topic,
|
const TopicButton *topic,
|
||||||
|
const Reply *reply,
|
||||||
const HistoryMessageVia *via,
|
const HistoryMessageVia *via,
|
||||||
const HistoryMessageReply *reply,
|
|
||||||
const HistoryMessageForwarded *forwarded,
|
const HistoryMessageForwarded *forwarded,
|
||||||
int outerw) const {
|
int outerw) const {
|
||||||
if (!topic && !via && !reply && !forwarded) {
|
if (!topic && !via && !reply && !forwarded) {
|
||||||
|
@ -242,8 +243,8 @@ void UnwrappedMedia::drawSurrounding(
|
||||||
const QRect &inner,
|
const QRect &inner,
|
||||||
const PaintContext &context,
|
const PaintContext &context,
|
||||||
const TopicButton *topic,
|
const TopicButton *topic,
|
||||||
|
const Reply *reply,
|
||||||
const HistoryMessageVia *via,
|
const HistoryMessageVia *via,
|
||||||
const HistoryMessageReply *reply,
|
|
||||||
const HistoryMessageForwarded *forwarded) const {
|
const HistoryMessageForwarded *forwarded) const {
|
||||||
const auto st = context.st;
|
const auto st = context.st;
|
||||||
const auto sti = context.imageStyle();
|
const auto sti = context.imageStyle();
|
||||||
|
@ -263,9 +264,9 @@ void UnwrappedMedia::drawSurrounding(
|
||||||
}
|
}
|
||||||
auto replyRight = 0;
|
auto replyRight = 0;
|
||||||
auto rectw = _additionalOnTop
|
auto rectw = _additionalOnTop
|
||||||
? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, via, reply, forwarded))
|
? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, reply, via, forwarded))
|
||||||
: (width() - inner.width() - st::msgReplyPadding.left());
|
: (width() - inner.width() - st::msgReplyPadding.left());
|
||||||
if (const auto surrounding = surroundingInfo(topic, via, reply, forwarded, rectw)) {
|
if (const auto surrounding = surroundingInfo(topic, reply, via, forwarded, rectw)) {
|
||||||
auto recth = surrounding.panelHeight;
|
auto recth = surrounding.panelHeight;
|
||||||
if (!surrounding.topicSize.isEmpty()) {
|
if (!surrounding.topicSize.isEmpty()) {
|
||||||
auto rectw = surrounding.topicSize.width();
|
auto rectw = surrounding.topicSize.width();
|
||||||
|
@ -416,14 +417,14 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const {
|
||||||
|
|
||||||
if (_parent->media() == this) {
|
if (_parent->media() == this) {
|
||||||
const auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
|
const auto via = inWebPage ? nullptr : item->Get<HistoryMessageVia>();
|
||||||
const auto reply = inWebPage ? nullptr : _parent->displayedReply();
|
const auto reply = inWebPage ? nullptr : _parent->Get<Reply>();
|
||||||
const auto topic = inWebPage ? nullptr : _parent->displayedTopicButton();
|
const auto topic = inWebPage ? nullptr : _parent->displayedTopicButton();
|
||||||
const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo();
|
const auto forwarded = inWebPage ? nullptr : getDisplayedForwardedInfo();
|
||||||
auto replyRight = 0;
|
auto replyRight = 0;
|
||||||
auto rectw = _additionalOnTop
|
auto rectw = _additionalOnTop
|
||||||
? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, via, reply, forwarded))
|
? std::min(width() - st::msgReplyPadding.left(), additionalWidth(topic, reply, via, forwarded))
|
||||||
: (width() - inner.width() - st::msgReplyPadding.left());
|
: (width() - inner.width() - st::msgReplyPadding.left());
|
||||||
if (const auto surrounding = surroundingInfo(topic, via, reply, forwarded, rectw)) {
|
if (const auto surrounding = surroundingInfo(topic, reply, via, forwarded, rectw)) {
|
||||||
auto recth = surrounding.panelHeight;
|
auto recth = surrounding.panelHeight;
|
||||||
if (!surrounding.topicSize.isEmpty()) {
|
if (!surrounding.topicSize.isEmpty()) {
|
||||||
auto rectw = surrounding.topicSize.width();
|
auto rectw = surrounding.topicSize.width();
|
||||||
|
@ -486,16 +487,8 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const {
|
||||||
const auto replyRect = QRect(rectx, recty, rectw, recth);
|
const auto replyRect = QRect(rectx, recty, rectw, recth);
|
||||||
if (replyRect.contains(point)) {
|
if (replyRect.contains(point)) {
|
||||||
result.link = reply->link();
|
result.link = reply->link();
|
||||||
reply->ripple.lastPoint = point - replyRect.topLeft();
|
reply->saveRipplePoint(point - replyRect.topLeft());
|
||||||
if (!reply->ripple.animation) {
|
reply->createRippleAnimation(_parent, replyRect.size());
|
||||||
reply->ripple.animation = std::make_unique<Ui::RippleAnimation>(
|
|
||||||
st::defaultRippleAnimation,
|
|
||||||
Ui::RippleAnimation::RoundRectMask(
|
|
||||||
replyRect.size(),
|
|
||||||
st::messageQuoteStyle.radius),
|
|
||||||
[=] { item->history()->owner().requestItemRepaint(item); });
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
replyRight = rectx + rectw - st::msgReplyPadding.right();
|
replyRight = rectx + rectw - st::msgReplyPadding.right();
|
||||||
|
@ -542,7 +535,7 @@ bool UnwrappedMedia::hasTextForCopy() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UnwrappedMedia::dragItemByHandler(const ClickHandlerPtr &p) const {
|
bool UnwrappedMedia::dragItemByHandler(const ClickHandlerPtr &p) const {
|
||||||
const auto reply = _parent->displayedReply();
|
const auto reply = _parent->Get<Reply>();
|
||||||
return !reply || (reply->link() != p);
|
return !reply || (reply->link() != p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,8 +642,8 @@ bool UnwrappedMedia::needInfoDisplay() const {
|
||||||
|
|
||||||
int UnwrappedMedia::additionalWidth(
|
int UnwrappedMedia::additionalWidth(
|
||||||
const TopicButton *topic,
|
const TopicButton *topic,
|
||||||
|
const Reply *reply,
|
||||||
const HistoryMessageVia *via,
|
const HistoryMessageVia *via,
|
||||||
const HistoryMessageReply *reply,
|
|
||||||
const HistoryMessageForwarded *forwarded) const {
|
const HistoryMessageForwarded *forwarded) const {
|
||||||
auto result = st::msgReplyPadding.left() + _parent->infoWidth() + 2 * st::msgDateImgPadding.x();
|
auto result = st::msgReplyPadding.left() + _parent->infoWidth() + 2 * st::msgDateImgPadding.x();
|
||||||
if (topic) {
|
if (topic) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ struct HistoryMessageForwarded;
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
|
class Reply;
|
||||||
struct TopicButton;
|
struct TopicButton;
|
||||||
|
|
||||||
class UnwrappedMedia final : public Media {
|
class UnwrappedMedia final : public Media {
|
||||||
|
@ -120,8 +121,8 @@ private:
|
||||||
};
|
};
|
||||||
[[nodiscard]] SurroundingInfo surroundingInfo(
|
[[nodiscard]] SurroundingInfo surroundingInfo(
|
||||||
const TopicButton *topic,
|
const TopicButton *topic,
|
||||||
|
const Reply *reply,
|
||||||
const HistoryMessageVia *via,
|
const HistoryMessageVia *via,
|
||||||
const HistoryMessageReply *reply,
|
|
||||||
const HistoryMessageForwarded *forwarded,
|
const HistoryMessageForwarded *forwarded,
|
||||||
int outerw) const;
|
int outerw) const;
|
||||||
void drawSurrounding(
|
void drawSurrounding(
|
||||||
|
@ -129,8 +130,8 @@ private:
|
||||||
const QRect &inner,
|
const QRect &inner,
|
||||||
const PaintContext &context,
|
const PaintContext &context,
|
||||||
const TopicButton *topic,
|
const TopicButton *topic,
|
||||||
|
const Reply *reply,
|
||||||
const HistoryMessageVia *via,
|
const HistoryMessageVia *via,
|
||||||
const HistoryMessageReply *reply,
|
|
||||||
const HistoryMessageForwarded *forwarded) const;
|
const HistoryMessageForwarded *forwarded) const;
|
||||||
|
|
||||||
QSize countOptimalSize() override;
|
QSize countOptimalSize() override;
|
||||||
|
@ -139,8 +140,8 @@ private:
|
||||||
bool needInfoDisplay() const;
|
bool needInfoDisplay() const;
|
||||||
int additionalWidth(
|
int additionalWidth(
|
||||||
const TopicButton *topic,
|
const TopicButton *topic,
|
||||||
|
const Reply *reply,
|
||||||
const HistoryMessageVia *via,
|
const HistoryMessageVia *via,
|
||||||
const HistoryMessageReply *reply,
|
|
||||||
const HistoryMessageForwarded *forwarded) const;
|
const HistoryMessageForwarded *forwarded) const;
|
||||||
|
|
||||||
int calculateFullRight(const QRect &inner) const;
|
int calculateFullRight(const QRect &inner) const;
|
||||||
|
|
|
@ -1077,7 +1077,7 @@ bool Photo::needsBubble() const {
|
||||||
&& (item->repliesAreComments()
|
&& (item->repliesAreComments()
|
||||||
|| item->externalReply()
|
|| item->externalReply()
|
||||||
|| item->viaBot()
|
|| item->viaBot()
|
||||||
|| _parent->displayedReply()
|
|| _parent->displayReply()
|
||||||
|| _parent->displayForwardedFrom()
|
|| _parent->displayForwardedFrom()
|
||||||
|| _parent->displayFromName()
|
|| _parent->displayFromName()
|
||||||
|| _parent->displayedTopicButton());
|
|| _parent->displayedTopicButton());
|
||||||
|
|
Loading…
Add table
Reference in a new issue