2017-12-18 15:44:50 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2017-12-18 15:44:50 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2017-12-18 15:44:50 +00:00
|
|
|
*/
|
|
|
|
#include "history/history_item_components.h"
|
|
|
|
|
2022-01-24 16:58:35 +00:00
|
|
|
#include "base/qt/qt_key_modifiers.h"
|
2017-12-18 15:44:50 +00:00
|
|
|
#include "lang/lang_keys.h"
|
|
|
|
#include "ui/effects/ripple_animation.h"
|
2018-10-23 09:44:42 +00:00
|
|
|
#include "ui/image/image.h"
|
2019-03-15 15:15:56 +00:00
|
|
|
#include "ui/toast/toast.h"
|
2020-10-10 09:15:37 +00:00
|
|
|
#include "ui/text/text_options.h"
|
2021-12-26 16:29:25 +00:00
|
|
|
#include "ui/text/text_utilities.h"
|
2021-09-02 22:08:57 +00:00
|
|
|
#include "ui/chat/chat_style.h"
|
|
|
|
#include "ui/chat/chat_theme.h"
|
2022-09-16 20:23:27 +00:00
|
|
|
#include "ui/painter.h"
|
2019-01-03 12:36:01 +00:00
|
|
|
#include "history/history.h"
|
2017-12-18 15:44:50 +00:00
|
|
|
#include "history/history_message.h"
|
2022-11-23 11:13:01 +00:00
|
|
|
#include "history/view/history_view_message.h" // FromNameFg.
|
2018-01-09 17:08:31 +00:00
|
|
|
#include "history/view/history_view_service_message.h"
|
2019-08-02 13:21:09 +00:00
|
|
|
#include "history/view/media/history_view_document.h"
|
2020-11-10 18:51:20 +00:00
|
|
|
#include "core/click_handler_types.h"
|
2022-07-04 08:49:31 +00:00
|
|
|
#include "core/ui_integration.h"
|
2021-07-25 22:20:42 +00:00
|
|
|
#include "layout/layout_position.h"
|
2019-07-12 07:57:04 +00:00
|
|
|
#include "mainwindow.h"
|
2019-02-13 12:36:59 +00:00
|
|
|
#include "media/audio/media_audio.h"
|
2017-12-18 15:44:50 +00:00
|
|
|
#include "media/player/media_player_instance.h"
|
2018-01-14 16:02:25 +00:00
|
|
|
#include "data/data_media_types.h"
|
2018-01-13 12:45:11 +00:00
|
|
|
#include "data/data_session.h"
|
2019-01-04 11:09:48 +00:00
|
|
|
#include "data/data_user.h"
|
|
|
|
#include "data/data_file_origin.h"
|
2019-12-27 12:15:16 +00:00
|
|
|
#include "data/data_document.h"
|
2021-11-18 12:03:12 +00:00
|
|
|
#include "data/data_web_page.h"
|
2021-06-17 23:28:09 +00:00
|
|
|
#include "data/data_file_click_handler.h"
|
2019-07-24 11:45:24 +00:00
|
|
|
#include "main/main_session.h"
|
2019-09-13 06:06:02 +00:00
|
|
|
#include "window/window_session_controller.h"
|
2022-07-05 07:56:29 +00:00
|
|
|
#include "api/api_bot.h"
|
2017-12-18 15:44:50 +00:00
|
|
|
#include "styles/style_widgets.h"
|
2020-10-10 09:15:37 +00:00
|
|
|
#include "styles/style_chat.h"
|
2017-12-18 15:44:50 +00:00
|
|
|
|
2019-09-04 07:19:15 +00:00
|
|
|
#include <QtGui/QGuiApplication>
|
|
|
|
|
2020-04-28 19:49:55 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_";
|
2022-11-23 11:13:01 +00:00
|
|
|
constexpr auto kReplyBarAlpha = 230. / 255.;
|
2020-04-28 19:49:55 +00:00
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2020-06-08 15:17:33 +00:00
|
|
|
void HistoryMessageVia::create(
|
|
|
|
not_null<Data::Session*> owner,
|
|
|
|
UserId userId) {
|
|
|
|
bot = owner->user(userId);
|
2017-12-18 15:44:50 +00:00
|
|
|
maxWidth = st::msgServiceNameFont->width(
|
2022-10-05 11:32:16 +00:00
|
|
|
tr::lng_inline_bot_via(
|
|
|
|
tr::now,
|
|
|
|
lt_inline_bot,
|
|
|
|
'@' + bot->username()));
|
2020-11-10 18:51:20 +00:00
|
|
|
link = std::make_shared<LambdaClickHandler>([bot = this->bot](
|
2022-07-05 07:56:29 +00:00
|
|
|
ClickContext context) {
|
|
|
|
const auto my = context.other.value<ClickHandlerContext>();
|
|
|
|
if (const auto controller = my.sessionWindow.get()) {
|
|
|
|
if (base::IsCtrlPressed()) {
|
|
|
|
controller->showPeerInfo(bot);
|
|
|
|
return;
|
|
|
|
} else if (!bot->isBot()
|
|
|
|
|| bot->botInfo->inlinePlaceholder.isEmpty()) {
|
|
|
|
controller->showPeerHistory(
|
|
|
|
bot->id,
|
|
|
|
Window::SectionShow::Way::Forward);
|
|
|
|
return;
|
2019-07-12 07:57:04 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-05 07:56:29 +00:00
|
|
|
const auto delegate = my.elementDelegate
|
|
|
|
? my.elementDelegate()
|
|
|
|
: nullptr;
|
|
|
|
if (delegate) {
|
2020-11-10 18:51:20 +00:00
|
|
|
delegate->elementHandleViaClick(bot);
|
|
|
|
}
|
2017-12-18 15:44:50 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryMessageVia::resize(int32 availw) const {
|
|
|
|
if (availw < 0) {
|
|
|
|
text = QString();
|
|
|
|
width = 0;
|
|
|
|
} else {
|
2022-10-05 11:32:16 +00:00
|
|
|
text = tr::lng_inline_bot_via(
|
|
|
|
tr::now,
|
|
|
|
lt_inline_bot,
|
|
|
|
'@' + bot->username());
|
2017-12-18 15:44:50 +00:00
|
|
|
if (availw < maxWidth) {
|
|
|
|
text = st::msgServiceNameFont->elided(text, availw);
|
|
|
|
width = st::msgServiceNameFont->width(text);
|
|
|
|
} else if (width < maxWidth) {
|
|
|
|
width = maxWidth;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-20 14:09:45 +00:00
|
|
|
HiddenSenderInfo::HiddenSenderInfo(const QString &name, bool external)
|
2019-03-15 15:15:56 +00:00
|
|
|
: name(name)
|
|
|
|
, colorPeerId(Data::FakePeerIdForJustName(name))
|
2022-02-15 02:20:55 +00:00
|
|
|
, emptyUserpic(
|
2021-01-20 14:09:45 +00:00
|
|
|
Data::PeerUserpicColor(colorPeerId),
|
|
|
|
(external
|
|
|
|
? Ui::EmptyUserpic::ExternalName()
|
|
|
|
: name)) {
|
2021-12-01 14:52:16 +00:00
|
|
|
Expects(!name.isEmpty());
|
|
|
|
|
2021-10-19 13:00:21 +00:00
|
|
|
const auto parts = name.trimmed().split(' ', Qt::SkipEmptyParts);
|
2019-03-15 15:15:56 +00:00
|
|
|
firstName = parts[0];
|
|
|
|
for (const auto &part : parts.mid(1)) {
|
|
|
|
if (!lastName.isEmpty()) {
|
|
|
|
lastName.append(' ');
|
|
|
|
}
|
|
|
|
lastName.append(part);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-09 11:12:19 +00:00
|
|
|
const Ui::Text::String &HiddenSenderInfo::nameText() const {
|
|
|
|
if (_nameText.isEmpty()) {
|
|
|
|
_nameText.setText(st::msgNameStyle, name, Ui::NameTextOptions());
|
|
|
|
}
|
|
|
|
return _nameText;
|
|
|
|
}
|
|
|
|
|
2022-06-14 09:48:25 +00:00
|
|
|
ClickHandlerPtr HiddenSenderInfo::ForwardClickHandler() {
|
|
|
|
static const auto hidden = std::make_shared<LambdaClickHandler>([](
|
|
|
|
ClickContext context) {
|
|
|
|
const auto my = context.other.value<ClickHandlerContext>();
|
|
|
|
const auto weak = my.sessionWindow;
|
|
|
|
if (const auto strong = weak.get()) {
|
|
|
|
Ui::Toast::Show(
|
|
|
|
Window::Show(strong).toastParent(),
|
|
|
|
tr::lng_forwarded_hidden(tr::now));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return hidden;
|
|
|
|
}
|
|
|
|
|
2022-02-15 02:20:55 +00:00
|
|
|
bool HiddenSenderInfo::paintCustomUserpic(
|
|
|
|
Painter &p,
|
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
int outerWidth,
|
|
|
|
int size) const {
|
|
|
|
const auto view = customUserpic.activeView();
|
|
|
|
if (const auto image = view ? view->image() : nullptr) {
|
|
|
|
const auto circled = Images::Option::RoundCircle;
|
|
|
|
p.drawPixmap(
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
image->pix(size, size, { .options = circled }));
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
emptyUserpic.paint(p, x, y, outerWidth, size);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
|
2021-12-26 16:29:25 +00:00
|
|
|
auto phrase = TextWithEntities();
|
2019-03-15 15:15:56 +00:00
|
|
|
const auto fromChannel = originalSender
|
|
|
|
&& originalSender->isChannel()
|
|
|
|
&& !originalSender->isMegagroup();
|
2021-12-26 16:29:25 +00:00
|
|
|
const auto name = TextWithEntities{
|
2022-08-09 11:12:19 +00:00
|
|
|
.text = (originalSender
|
|
|
|
? originalSender->name()
|
|
|
|
: hiddenSenderInfo->name)
|
2021-12-26 16:29:25 +00:00
|
|
|
};
|
2017-12-18 15:44:50 +00:00
|
|
|
if (!originalAuthor.isEmpty()) {
|
2019-06-19 16:39:25 +00:00
|
|
|
phrase = tr::lng_forwarded_signed(
|
|
|
|
tr::now,
|
2017-12-18 15:44:50 +00:00
|
|
|
lt_channel,
|
2019-03-15 15:15:56 +00:00
|
|
|
name,
|
2017-12-18 15:44:50 +00:00
|
|
|
lt_user,
|
2021-12-26 16:29:25 +00:00
|
|
|
{ .text = originalAuthor },
|
|
|
|
Ui::Text::WithEntities);
|
2017-12-18 15:44:50 +00:00
|
|
|
} else {
|
2019-03-15 15:15:56 +00:00
|
|
|
phrase = name;
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
2020-04-28 19:49:55 +00:00
|
|
|
if (via && psaType.isEmpty()) {
|
2017-12-18 15:44:50 +00:00
|
|
|
if (fromChannel) {
|
2019-06-19 16:39:25 +00:00
|
|
|
phrase = tr::lng_forwarded_channel_via(
|
|
|
|
tr::now,
|
2017-12-18 15:44:50 +00:00
|
|
|
lt_channel,
|
2022-02-03 20:37:55 +00:00
|
|
|
Ui::Text::Link(phrase.text, 1), // Link 1.
|
2017-12-18 15:44:50 +00:00
|
|
|
lt_inline_bot,
|
2022-10-05 11:32:16 +00:00
|
|
|
Ui::Text::Link('@' + via->bot->username(), 2), // Link 2.
|
2021-12-26 16:29:25 +00:00
|
|
|
Ui::Text::WithEntities);
|
2017-12-18 15:44:50 +00:00
|
|
|
} else {
|
2019-06-19 16:39:25 +00:00
|
|
|
phrase = tr::lng_forwarded_via(
|
|
|
|
tr::now,
|
2017-12-18 15:44:50 +00:00
|
|
|
lt_user,
|
2022-02-03 20:37:55 +00:00
|
|
|
Ui::Text::Link(phrase.text, 1), // Link 1.
|
2017-12-18 15:44:50 +00:00
|
|
|
lt_inline_bot,
|
2022-10-05 11:32:16 +00:00
|
|
|
Ui::Text::Link('@' + via->bot->username(), 2), // Link 2.
|
2021-12-26 16:29:25 +00:00
|
|
|
Ui::Text::WithEntities);
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-04-28 19:49:55 +00:00
|
|
|
if (fromChannel || !psaType.isEmpty()) {
|
|
|
|
auto custom = psaType.isEmpty()
|
|
|
|
? QString()
|
2020-09-30 09:11:44 +00:00
|
|
|
: Lang::GetNonDefaultValue(
|
2020-04-28 19:49:55 +00:00
|
|
|
kPsaForwardedPrefix + psaType.toUtf8());
|
2021-12-26 16:29:25 +00:00
|
|
|
if (!custom.isEmpty()) {
|
|
|
|
custom = custom.replace("{channel}", phrase.text);
|
|
|
|
const auto index = int(custom.indexOf(phrase.text));
|
|
|
|
const auto size = int(phrase.text.size());
|
|
|
|
phrase = TextWithEntities{
|
|
|
|
.text = custom,
|
|
|
|
.entities = {{ EntityType::CustomUrl, index, size, {} }},
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
phrase = (psaType.isEmpty()
|
2020-04-28 19:49:55 +00:00
|
|
|
? tr::lng_forwarded_channel
|
|
|
|
: tr::lng_forwarded_psa_default)(
|
|
|
|
tr::now,
|
|
|
|
lt_channel,
|
2021-12-26 16:29:25 +00:00
|
|
|
Ui::Text::Link(phrase.text, QString()), // Link 1.
|
|
|
|
Ui::Text::WithEntities);
|
|
|
|
}
|
2017-12-18 15:44:50 +00:00
|
|
|
} else {
|
2019-06-19 16:39:25 +00:00
|
|
|
phrase = tr::lng_forwarded(
|
|
|
|
tr::now,
|
2017-12-18 15:44:50 +00:00
|
|
|
lt_user,
|
2021-12-26 16:29:25 +00:00
|
|
|
Ui::Text::Link(phrase.text, QString()), // Link 1.
|
|
|
|
Ui::Text::WithEntities);
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
2022-01-07 03:50:06 +00:00
|
|
|
text.setMarkedText(st::fwdTextStyle, phrase);
|
2019-03-15 15:15:56 +00:00
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
text.setLink(1, fromChannel
|
|
|
|
? goToMessageClickHandler(originalSender, originalId)
|
2019-03-15 15:15:56 +00:00
|
|
|
: originalSender
|
|
|
|
? originalSender->openLink()
|
2022-06-14 09:48:25 +00:00
|
|
|
: HiddenSenderInfo::ForwardClickHandler());
|
2017-12-18 15:44:50 +00:00
|
|
|
if (via) {
|
|
|
|
text.setLink(2, via->link);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-26 15:40:11 +00:00
|
|
|
bool HistoryMessageReply::updateData(
|
|
|
|
not_null<HistoryMessage*> holder,
|
|
|
|
bool force) {
|
2021-11-18 12:03:12 +00:00
|
|
|
const auto guard = gsl::finally([&] { refreshReplyToMedia(); });
|
2017-12-18 15:44:50 +00:00
|
|
|
if (!force) {
|
|
|
|
if (replyToMsg || !replyToMsgId) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!replyToMsg) {
|
2019-04-25 12:45:15 +00:00
|
|
|
replyToMsg = holder->history()->owner().message(
|
2020-09-11 16:22:14 +00:00
|
|
|
(replyToPeerId
|
2021-12-09 07:32:54 +00:00
|
|
|
? replyToPeerId
|
|
|
|
: holder->history()->peer->id),
|
2019-04-25 12:45:15 +00:00
|
|
|
replyToMsgId);
|
2017-12-18 15:44:50 +00:00
|
|
|
if (replyToMsg) {
|
|
|
|
if (replyToMsg->isEmpty()) {
|
|
|
|
// Really it is deleted.
|
|
|
|
replyToMsg = nullptr;
|
|
|
|
force = true;
|
|
|
|
} else {
|
2019-04-25 12:45:15 +00:00
|
|
|
holder->history()->owner().registerDependentMessage(
|
|
|
|
holder,
|
2022-09-26 13:37:32 +00:00
|
|
|
replyToMsg.get());
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (replyToMsg) {
|
2022-07-04 08:49:31 +00:00
|
|
|
const auto context = Core::MarkedTextContext{
|
|
|
|
.session = &holder->history()->session(),
|
|
|
|
.customEmojiRepaint = [=] { holder->customEmojiRepaint(); },
|
|
|
|
};
|
2021-12-26 16:29:25 +00:00
|
|
|
replyToText.setMarkedText(
|
2017-12-28 13:06:06 +00:00
|
|
|
st::messageTextStyle,
|
2018-05-31 11:13:11 +00:00
|
|
|
replyToMsg->inReplyText(),
|
2022-07-04 08:49:31 +00:00
|
|
|
Ui::DialogTextOptions(),
|
|
|
|
context);
|
2017-12-18 15:44:50 +00:00
|
|
|
|
2022-03-16 07:02:45 +00:00
|
|
|
updateName(holder);
|
2017-12-18 15:44:50 +00:00
|
|
|
|
2018-06-04 16:58:35 +00:00
|
|
|
setReplyToLinkFrom(holder);
|
2017-12-18 15:44:50 +00:00
|
|
|
if (!replyToMsg->Has<HistoryMessageForwarded>()) {
|
|
|
|
if (auto bot = replyToMsg->viaBot()) {
|
|
|
|
replyToVia = std::make_unique<HistoryMessageVia>();
|
2020-06-08 15:17:33 +00:00
|
|
|
replyToVia->create(
|
|
|
|
&holder->history()->owner(),
|
|
|
|
peerToUser(bot->id));
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-23 11:13:01 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
const auto peerId = replyToMsg->fullId().peer;
|
|
|
|
replyToColorKey = (peerIsChannel(peerId) || peerIsChat(peerId))
|
|
|
|
? replyToMsg->from()->id
|
|
|
|
: PeerId(0);
|
|
|
|
}
|
2017-12-18 15:44:50 +00:00
|
|
|
} else if (force) {
|
|
|
|
replyToMsgId = 0;
|
2022-11-23 11:13:01 +00:00
|
|
|
replyToColorKey = PeerId(0);
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
if (force) {
|
2019-01-03 12:36:01 +00:00
|
|
|
holder->history()->owner().requestItemResize(holder);
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
return (replyToMsg || !replyToMsgId);
|
|
|
|
}
|
|
|
|
|
2018-06-04 16:58:35 +00:00
|
|
|
void HistoryMessageReply::setReplyToLinkFrom(
|
|
|
|
not_null<HistoryMessage*> holder) {
|
|
|
|
replyToLnk = replyToMsg
|
2022-09-26 13:37:32 +00:00
|
|
|
? goToMessageClickHandler(replyToMsg.get(), holder->fullId())
|
2018-06-04 16:58:35 +00:00
|
|
|
: nullptr;
|
|
|
|
}
|
|
|
|
|
2018-01-26 15:40:11 +00:00
|
|
|
void HistoryMessageReply::clearData(not_null<HistoryMessage*> holder) {
|
2017-12-18 15:44:50 +00:00
|
|
|
replyToVia = nullptr;
|
|
|
|
if (replyToMsg) {
|
2019-04-25 12:45:15 +00:00
|
|
|
holder->history()->owner().unregisterDependentMessage(
|
|
|
|
holder,
|
2022-09-26 13:37:32 +00:00
|
|
|
replyToMsg.get());
|
2017-12-18 15:44:50 +00:00
|
|
|
replyToMsg = nullptr;
|
|
|
|
}
|
|
|
|
replyToMsgId = 0;
|
2021-11-18 12:03:12 +00:00
|
|
|
refreshReplyToMedia();
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
|
2022-03-16 07:02:45 +00:00
|
|
|
PeerData *HistoryMessageReply::replyToFrom(
|
|
|
|
not_null<HistoryMessage*> holder) const {
|
|
|
|
if (!replyToMsg) {
|
|
|
|
return nullptr;
|
|
|
|
} else if (holder->Has<HistoryMessageForwarded>()) {
|
|
|
|
if (const auto fwd = replyToMsg->Get<HistoryMessageForwarded>()) {
|
|
|
|
return fwd->originalSender;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (const auto from = replyToMsg->displayFrom()) {
|
|
|
|
return from;
|
|
|
|
}
|
|
|
|
return replyToMsg->author().get();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString HistoryMessageReply::replyToFromName(
|
|
|
|
not_null<HistoryMessage*> holder) const {
|
|
|
|
if (!replyToMsg) {
|
|
|
|
return QString();
|
|
|
|
} else if (holder->Has<HistoryMessageForwarded>()) {
|
|
|
|
if (const auto fwd = replyToMsg->Get<HistoryMessageForwarded>()) {
|
|
|
|
return fwd->originalSender
|
|
|
|
? replyToFromName(fwd->originalSender)
|
|
|
|
: fwd->hiddenSenderInfo->name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (const auto from = replyToMsg->displayFrom()) {
|
|
|
|
return replyToFromName(from);
|
|
|
|
}
|
|
|
|
return replyToFromName(replyToMsg->author());
|
|
|
|
}
|
|
|
|
|
|
|
|
QString HistoryMessageReply::replyToFromName(
|
|
|
|
not_null<PeerData*> peer) const {
|
|
|
|
if (const auto user = replyToVia ? peer->asUser() : nullptr) {
|
|
|
|
return user->firstName;
|
|
|
|
}
|
2022-08-09 11:12:19 +00:00
|
|
|
return peer->name();
|
2022-03-16 07:02:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool HistoryMessageReply::isNameUpdated(
|
|
|
|
not_null<HistoryMessage*> holder) const {
|
|
|
|
if (const auto from = replyToFrom(holder)) {
|
2022-08-09 11:12:19 +00:00
|
|
|
if (replyToVersion < from->nameVersion()) {
|
2022-03-16 07:02:45 +00:00
|
|
|
updateName(holder);
|
|
|
|
return true;
|
|
|
|
}
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-03-16 07:02:45 +00:00
|
|
|
void HistoryMessageReply::updateName(
|
|
|
|
not_null<HistoryMessage*> holder) const {
|
|
|
|
if (const auto name = replyToFromName(holder); !name.isEmpty()) {
|
2017-12-28 13:06:06 +00:00
|
|
|
replyToName.setText(st::fwdTextStyle, name, Ui::NameTextOptions());
|
2022-05-27 15:42:05 +00:00
|
|
|
if (const auto from = replyToFrom(holder)) {
|
2022-08-09 11:12:19 +00:00
|
|
|
replyToVersion = from->nameVersion();
|
2022-05-27 15:42:05 +00:00
|
|
|
} else {
|
2022-08-09 11:12:19 +00:00
|
|
|
replyToVersion = replyToMsg->author()->nameVersion();
|
2022-05-27 15:42:05 +00:00
|
|
|
}
|
2018-01-14 16:02:25 +00:00
|
|
|
bool hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false;
|
2017-12-18 15:44:50 +00:00
|
|
|
int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
|
|
|
|
int32 w = replyToName.maxWidth();
|
|
|
|
if (replyToVia) {
|
|
|
|
w += st::msgServiceFont->spacew + replyToVia->maxWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
maxReplyWidth = previewSkip + qMax(w, qMin(replyToText.maxWidth(), int32(st::maxSignatureSize)));
|
|
|
|
} else {
|
2019-06-19 15:09:03 +00:00
|
|
|
maxReplyWidth = st::msgDateFont->width(replyToMsgId ? tr::lng_profile_loading(tr::now) : tr::lng_deleted_message(tr::now));
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
maxReplyWidth = st::msgReplyPadding.left() + st::msgReplyBarSkip + maxReplyWidth + st::msgReplyPadding.right();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryMessageReply::resize(int width) const {
|
|
|
|
if (replyToVia) {
|
2018-01-14 16:02:25 +00:00
|
|
|
bool hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false;
|
2017-12-18 15:44:50 +00:00
|
|
|
int previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
|
|
|
|
replyToVia->resize(width - st::msgReplyBarSkip - previewSkip - replyToName.maxWidth() - st::msgServiceFont->spacew);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
void HistoryMessageReply::itemRemoved(
|
|
|
|
HistoryMessage *holder,
|
|
|
|
HistoryItem *removed) {
|
2022-09-26 13:37:32 +00:00
|
|
|
if (replyToMsg.get() == removed) {
|
2017-12-18 15:44:50 +00:00
|
|
|
clearData(holder);
|
2019-01-03 12:36:01 +00:00
|
|
|
holder->history()->owner().requestItemResize(holder);
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
void HistoryMessageReply::paint(
|
|
|
|
Painter &p,
|
|
|
|
not_null<const HistoryView::Element*> holder,
|
2021-09-02 22:08:57 +00:00
|
|
|
const Ui::ChatPaintContext &context,
|
2018-01-14 16:02:25 +00:00
|
|
|
int x,
|
|
|
|
int y,
|
|
|
|
int w,
|
2021-09-02 22:08:57 +00:00
|
|
|
bool inBubble) const {
|
|
|
|
const auto st = context.st;
|
|
|
|
const auto stm = context.messageStyle();
|
2017-12-18 15:44:50 +00:00
|
|
|
|
2022-11-23 11:13:01 +00:00
|
|
|
{
|
|
|
|
const auto &bar = !inBubble
|
|
|
|
? st->msgImgReplyBarColor()
|
|
|
|
: replyToColorKey
|
|
|
|
? HistoryView::FromNameFg(context, replyToColorKey)
|
|
|
|
: stm->msgReplyBarColor;
|
|
|
|
const auto rbar = style::rtlrect(
|
|
|
|
x + st::msgReplyBarPos.x(),
|
|
|
|
y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(),
|
|
|
|
st::msgReplyBarSize.width(),
|
|
|
|
st::msgReplyBarSize.height(),
|
|
|
|
w + 2 * x);
|
|
|
|
const auto opacity = p.opacity();
|
|
|
|
p.setOpacity(opacity * kReplyBarAlpha);
|
|
|
|
p.fillRect(rbar, bar);
|
|
|
|
p.setOpacity(opacity);
|
|
|
|
}
|
2017-12-18 15:44:50 +00:00
|
|
|
|
|
|
|
if (w > st::msgReplyBarSkip) {
|
|
|
|
if (replyToMsg) {
|
2018-01-14 16:02:25 +00:00
|
|
|
auto hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false;
|
2017-12-18 15:44:50 +00:00
|
|
|
if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) {
|
|
|
|
hasPreview = false;
|
|
|
|
}
|
|
|
|
auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
|
|
|
|
|
|
|
|
if (hasPreview) {
|
2018-11-09 15:03:38 +00:00
|
|
|
if (const auto image = replyToMsg->media()->replyPreview()) {
|
2019-09-13 12:22:54 +00:00
|
|
|
auto to = style::rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x);
|
2022-01-21 12:31:39 +00:00
|
|
|
const auto preview = image->pixSingle(
|
|
|
|
image->size() / style::DevicePixelRatio(),
|
|
|
|
{
|
|
|
|
.colored = (context.selected()
|
|
|
|
? &st->msgStickerOverlay()
|
|
|
|
: nullptr),
|
|
|
|
.options = Images::Option::RoundSmall,
|
|
|
|
.outer = to.size(),
|
|
|
|
});
|
2017-12-18 15:44:50 +00:00
|
|
|
p.drawPixmap(to.x(), to.y(), preview);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (w > st::msgReplyBarSkip + previewSkip) {
|
2022-11-23 11:13:01 +00:00
|
|
|
p.setPen(!inBubble
|
|
|
|
? st->msgImgReplyBarColor()
|
|
|
|
: replyToColorKey
|
|
|
|
? HistoryView::FromNameFg(context, replyToColorKey)
|
|
|
|
: stm->msgServiceFg);
|
2017-12-18 15:44:50 +00:00
|
|
|
replyToName.drawLeftElided(p, x + st::msgReplyBarSkip + previewSkip, y + st::msgReplyPadding.top(), w - st::msgReplyBarSkip - previewSkip, w + 2 * x);
|
|
|
|
if (replyToVia && w > st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew) {
|
|
|
|
p.setFont(st::msgServiceFont);
|
|
|
|
p.drawText(x + st::msgReplyBarSkip + previewSkip + replyToName.maxWidth() + st::msgServiceFont->spacew, y + st::msgReplyPadding.top() + st::msgServiceFont->ascent, replyToVia->text);
|
|
|
|
}
|
|
|
|
|
2021-09-02 22:08:57 +00:00
|
|
|
p.setPen(inBubble
|
|
|
|
? stm->historyTextFg
|
|
|
|
: st->msgImgReplyBarColor());
|
2022-09-09 16:09:51 +00:00
|
|
|
holder->prepareCustomEmojiPaint(p, context, replyToText);
|
2022-09-16 20:23:27 +00:00
|
|
|
replyToText.draw(p, {
|
|
|
|
.position = QPoint(
|
|
|
|
x + st::msgReplyBarSkip + previewSkip,
|
|
|
|
y + st::msgReplyPadding.top() + st::msgServiceNameFont->height),
|
|
|
|
.availableWidth = w - st::msgReplyBarSkip - previewSkip,
|
|
|
|
.palette = &(inBubble
|
|
|
|
? stm->replyTextPalette
|
|
|
|
: st->imgReplyTextPalette()),
|
|
|
|
.spoiler = Ui::Text::DefaultSpoilerCache(),
|
2022-11-09 08:16:20 +00:00
|
|
|
.now = context.now,
|
|
|
|
.paused = context.paused,
|
2022-09-16 20:23:27 +00:00
|
|
|
.elisionLines = 1,
|
|
|
|
});
|
2021-09-02 22:08:57 +00:00
|
|
|
p.setTextPalette(stm->textPalette);
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
p.setFont(st::msgDateFont);
|
2021-09-02 22:08:57 +00:00
|
|
|
p.setPen(inBubble
|
|
|
|
? stm->msgDateFg
|
|
|
|
: st->msgDateImgFg());
|
2019-06-19 15:09:03 +00:00
|
|
|
p.drawTextLeft(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2, w + 2 * x, st::msgDateFont->elided(replyToMsgId ? tr::lng_profile_loading(tr::now) : tr::lng_deleted_message(tr::now), w - st::msgReplyBarSkip));
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-18 12:03:12 +00:00
|
|
|
void HistoryMessageReply::refreshReplyToMedia() {
|
2019-12-27 12:15:16 +00:00
|
|
|
replyToDocumentId = 0;
|
2021-11-18 12:03:12 +00:00
|
|
|
replyToWebPageId = 0;
|
2019-12-27 10:41:30 +00:00
|
|
|
if (const auto media = replyToMsg ? replyToMsg->media() : nullptr) {
|
2019-12-27 12:15:16 +00:00
|
|
|
if (const auto document = media->document()) {
|
|
|
|
replyToDocumentId = document->id;
|
2021-11-18 12:03:12 +00:00
|
|
|
} else if (const auto webpage = media->webpage()) {
|
|
|
|
replyToWebPageId = webpage->id;
|
2019-12-27 12:15:16 +00:00
|
|
|
}
|
2019-12-27 10:41:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
ReplyMarkupClickHandler::ReplyMarkupClickHandler(
|
2020-06-09 09:36:40 +00:00
|
|
|
not_null<Data::Session*> owner,
|
2017-12-18 15:44:50 +00:00
|
|
|
int row,
|
|
|
|
int column,
|
|
|
|
FullMsgId context)
|
2020-06-09 09:36:40 +00:00
|
|
|
: _owner(owner)
|
|
|
|
, _itemId(context)
|
2017-12-18 15:44:50 +00:00
|
|
|
, _row(row)
|
|
|
|
, _column(column) {
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy to clipboard support.
|
2018-01-25 10:10:52 +00:00
|
|
|
QString ReplyMarkupClickHandler::copyToClipboardText() const {
|
2021-01-21 23:36:22 +00:00
|
|
|
const auto button = getUrlButton();
|
|
|
|
return button ? QString::fromUtf8(button->data) : QString();
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QString ReplyMarkupClickHandler::copyToClipboardContextItemText() const {
|
2021-01-21 23:36:22 +00:00
|
|
|
const auto button = getUrlButton();
|
|
|
|
return button ? tr::lng_context_copy_link(tr::now) : QString();
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Finds the corresponding button in the items markup struct.
|
|
|
|
// If the button is not found it returns nullptr.
|
|
|
|
// Note: it is possible that we will point to the different button
|
|
|
|
// than the one was used when constructing the handler, but not a big deal.
|
|
|
|
const HistoryMessageMarkupButton *ReplyMarkupClickHandler::getButton() const {
|
2020-06-09 09:36:40 +00:00
|
|
|
return HistoryMessageMarkupButton::Get(_owner, _itemId, _row, _column);
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
|
2021-01-21 23:36:22 +00:00
|
|
|
auto ReplyMarkupClickHandler::getUrlButton() const
|
|
|
|
-> const HistoryMessageMarkupButton* {
|
|
|
|
if (const auto button = getButton()) {
|
|
|
|
using Type = HistoryMessageMarkupButton::Type;
|
|
|
|
if (button->type == Type::Url || button->type == Type::Auth) {
|
|
|
|
return button;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2021-07-26 23:45:01 +00:00
|
|
|
void ReplyMarkupClickHandler::onClick(ClickContext context) const {
|
|
|
|
if (context.button != Qt::LeftButton) {
|
|
|
|
return;
|
|
|
|
}
|
2022-07-28 17:03:38 +00:00
|
|
|
auto my = context.other.value<ClickHandlerContext>();
|
|
|
|
my.itemId = _itemId;
|
|
|
|
Api::ActivateBotCommand(my, _row, _column);
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the full text of the corresponding button.
|
|
|
|
QString ReplyMarkupClickHandler::buttonText() const {
|
|
|
|
if (const auto button = getButton()) {
|
|
|
|
return button->text;
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2021-01-21 23:36:22 +00:00
|
|
|
QString ReplyMarkupClickHandler::tooltip() const {
|
|
|
|
const auto button = getUrlButton();
|
|
|
|
const auto url = button ? QString::fromUtf8(button->data) : QString();
|
|
|
|
const auto text = _fullDisplayed ? QString() : buttonText();
|
|
|
|
if (!url.isEmpty() && !text.isEmpty()) {
|
2021-03-13 11:50:34 +00:00
|
|
|
return QString("%1\n\n%2").arg(text, url);
|
2021-01-21 23:36:22 +00:00
|
|
|
} else if (url.isEmpty() != text.isEmpty()) {
|
|
|
|
return text + url;
|
|
|
|
} else {
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
ReplyKeyboard::Button::Button() = default;
|
|
|
|
ReplyKeyboard::Button::Button(Button &&other) = default;
|
|
|
|
ReplyKeyboard::Button &ReplyKeyboard::Button::operator=(
|
|
|
|
Button &&other) = default;
|
|
|
|
ReplyKeyboard::Button::~Button() = default;
|
|
|
|
|
|
|
|
ReplyKeyboard::ReplyKeyboard(
|
|
|
|
not_null<const HistoryItem*> item,
|
|
|
|
std::unique_ptr<Style> &&s)
|
|
|
|
: _item(item)
|
2019-04-01 17:44:54 +00:00
|
|
|
, _selectedAnimation([=](crl::time now) {
|
|
|
|
return selectedAnimationCallback(now);
|
|
|
|
})
|
2017-12-18 15:44:50 +00:00
|
|
|
, _st(std::move(s)) {
|
|
|
|
if (const auto markup = _item->Get<HistoryMessageReplyMarkup>()) {
|
2020-06-09 09:36:40 +00:00
|
|
|
const auto owner = &_item->history()->owner();
|
2017-12-18 15:44:50 +00:00
|
|
|
const auto context = _item->fullId();
|
2021-10-02 11:28:21 +00:00
|
|
|
const auto rowCount = int(markup->data.rows.size());
|
2017-12-18 15:44:50 +00:00
|
|
|
_rows.reserve(rowCount);
|
|
|
|
for (auto i = 0; i != rowCount; ++i) {
|
2021-10-02 11:28:21 +00:00
|
|
|
const auto &row = markup->data.rows[i];
|
2017-12-18 15:44:50 +00:00
|
|
|
const auto rowSize = int(row.size());
|
|
|
|
auto newRow = std::vector<Button>();
|
|
|
|
newRow.reserve(rowSize);
|
|
|
|
for (auto j = 0; j != rowSize; ++j) {
|
|
|
|
auto button = Button();
|
|
|
|
const auto text = row[j].text;
|
|
|
|
button.type = row.at(j).type;
|
|
|
|
button.link = std::make_shared<ReplyMarkupClickHandler>(
|
2020-06-09 09:36:40 +00:00
|
|
|
owner,
|
2017-12-18 15:44:50 +00:00
|
|
|
i,
|
|
|
|
j,
|
|
|
|
context);
|
|
|
|
button.text.setText(
|
|
|
|
_st->textStyle(),
|
|
|
|
TextUtilities::SingleLine(text),
|
2022-02-27 17:19:16 +00:00
|
|
|
kPlainTextOptions);
|
2017-12-18 15:44:50 +00:00
|
|
|
button.characters = text.isEmpty() ? 1 : text.size();
|
|
|
|
newRow.push_back(std::move(button));
|
|
|
|
}
|
|
|
|
_rows.push_back(std::move(newRow));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReplyKeyboard::updateMessageId() {
|
|
|
|
const auto msgId = _item->fullId();
|
|
|
|
for (const auto &row : _rows) {
|
|
|
|
for (const auto &button : row) {
|
|
|
|
button.link->setMessageId(msgId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReplyKeyboard::resize(int width, int height) {
|
|
|
|
_width = width;
|
|
|
|
|
|
|
|
auto y = 0.;
|
|
|
|
auto buttonHeight = _rows.empty()
|
|
|
|
? float64(_st->buttonHeight())
|
|
|
|
: (float64(height + _st->buttonSkip()) / _rows.size());
|
|
|
|
for (auto &row : _rows) {
|
|
|
|
int s = row.size();
|
|
|
|
|
|
|
|
int widthForButtons = _width - ((s - 1) * _st->buttonSkip());
|
|
|
|
int widthForText = widthForButtons;
|
|
|
|
int widthOfText = 0;
|
|
|
|
int maxMinButtonWidth = 0;
|
2021-09-03 11:09:57 +00:00
|
|
|
for (const auto &button : row) {
|
2017-12-18 15:44:50 +00:00
|
|
|
widthOfText += qMax(button.text.maxWidth(), 1);
|
|
|
|
int minButtonWidth = _st->minButtonWidth(button.type);
|
|
|
|
widthForText -= minButtonWidth;
|
|
|
|
accumulate_max(maxMinButtonWidth, minButtonWidth);
|
|
|
|
}
|
|
|
|
bool exact = (widthForText == widthOfText);
|
|
|
|
bool enough = (widthForButtons - s * maxMinButtonWidth) >= widthOfText;
|
|
|
|
|
|
|
|
float64 x = 0;
|
2021-09-03 11:09:57 +00:00
|
|
|
for (auto &button : row) {
|
2017-12-18 15:44:50 +00:00
|
|
|
int buttonw = qMax(button.text.maxWidth(), 1);
|
|
|
|
float64 textw = buttonw, minw = _st->minButtonWidth(button.type);
|
|
|
|
float64 w = textw;
|
|
|
|
if (exact) {
|
|
|
|
w += minw;
|
|
|
|
} else if (enough) {
|
|
|
|
w = (widthForButtons / float64(s));
|
|
|
|
textw = w - minw;
|
|
|
|
} else {
|
|
|
|
textw = (widthForText / float64(s));
|
|
|
|
w = minw + textw;
|
|
|
|
accumulate_max(w, 2 * float64(_st->buttonPadding()));
|
|
|
|
}
|
|
|
|
|
|
|
|
int rectx = static_cast<int>(std::floor(x));
|
|
|
|
int rectw = static_cast<int>(std::floor(x + w)) - rectx;
|
|
|
|
button.rect = QRect(rectx, qRound(y), rectw, qRound(buttonHeight - _st->buttonSkip()));
|
|
|
|
if (rtl()) button.rect.setX(_width - button.rect.x() - button.rect.width());
|
|
|
|
x += w + _st->buttonSkip();
|
|
|
|
|
|
|
|
button.link->setFullDisplayed(textw >= buttonw);
|
|
|
|
}
|
|
|
|
y += buttonHeight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ReplyKeyboard::isEnoughSpace(int width, const style::BotKeyboardButton &st) const {
|
2021-09-03 11:09:57 +00:00
|
|
|
for (const auto &row : _rows) {
|
2017-12-18 15:44:50 +00:00
|
|
|
int s = row.size();
|
|
|
|
int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding);
|
2021-09-03 11:09:57 +00:00
|
|
|
for (const auto &button : row) {
|
2017-12-18 15:44:50 +00:00
|
|
|
widthLeft -= qMax(button.text.maxWidth(), 1);
|
|
|
|
if (widthLeft < 0) {
|
|
|
|
if (row.size() > 3) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReplyKeyboard::setStyle(std::unique_ptr<Style> &&st) {
|
|
|
|
_st = std::move(st);
|
|
|
|
}
|
|
|
|
|
|
|
|
int ReplyKeyboard::naturalWidth() const {
|
|
|
|
auto result = 0;
|
|
|
|
for (const auto &row : _rows) {
|
|
|
|
auto maxMinButtonWidth = 0;
|
|
|
|
for (const auto &button : row) {
|
|
|
|
accumulate_max(
|
|
|
|
maxMinButtonWidth,
|
|
|
|
_st->minButtonWidth(button.type));
|
|
|
|
}
|
|
|
|
auto rowMaxButtonWidth = 0;
|
|
|
|
for (const auto &button : row) {
|
|
|
|
accumulate_max(
|
|
|
|
rowMaxButtonWidth,
|
|
|
|
qMax(button.text.maxWidth(), 1) + maxMinButtonWidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto rowSize = int(row.size());
|
|
|
|
accumulate_max(
|
|
|
|
result,
|
|
|
|
rowSize * rowMaxButtonWidth + (rowSize - 1) * _st->buttonSkip());
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ReplyKeyboard::naturalHeight() const {
|
|
|
|
return (_rows.size() - 1) * _st->buttonSkip() + _rows.size() * _st->buttonHeight();
|
|
|
|
}
|
|
|
|
|
2021-09-02 22:08:57 +00:00
|
|
|
void ReplyKeyboard::paint(
|
|
|
|
Painter &p,
|
|
|
|
const Ui::ChatStyle *st,
|
2022-10-03 11:11:05 +00:00
|
|
|
Ui::BubbleRounding rounding,
|
2021-09-02 22:08:57 +00:00
|
|
|
int outerWidth,
|
|
|
|
const QRect &clip) const {
|
2017-12-18 15:44:50 +00:00
|
|
|
Assert(_st != nullptr);
|
|
|
|
Assert(_width > 0);
|
|
|
|
|
2021-09-02 22:08:57 +00:00
|
|
|
_st->startPaint(p, st);
|
2022-10-03 11:11:05 +00:00
|
|
|
for (auto y = 0, rowsCount = int(_rows.size()); y != rowsCount; ++y) {
|
|
|
|
for (auto x = 0, count = int(_rows[y].size()); x != count; ++x) {
|
|
|
|
const auto &button = _rows[y][x];
|
2019-04-02 09:13:30 +00:00
|
|
|
const auto rect = button.rect;
|
2017-12-18 15:44:50 +00:00
|
|
|
if (rect.y() >= clip.y() + clip.height()) return;
|
|
|
|
if (rect.y() + rect.height() < clip.y()) continue;
|
|
|
|
|
|
|
|
// just ignore the buttons that didn't layout well
|
|
|
|
if (rect.x() + rect.width() > _width) break;
|
|
|
|
|
2022-10-03 11:11:05 +00:00
|
|
|
auto buttonRounding = Ui::BubbleRounding();
|
|
|
|
using Corner = Ui::BubbleCornerRounding;
|
|
|
|
buttonRounding.topLeft = buttonRounding.topRight = Corner::Small;
|
|
|
|
buttonRounding.bottomLeft = ((y + 1 == rowsCount)
|
|
|
|
&& !x
|
|
|
|
&& (rounding.bottomLeft == Corner::Large))
|
|
|
|
? Corner::Large
|
|
|
|
: Corner::Small;
|
|
|
|
buttonRounding.bottomRight = ((y + 1 == rowsCount)
|
|
|
|
&& (x + 1 == count)
|
|
|
|
&& (rounding.bottomRight == Corner::Large))
|
|
|
|
? Corner::Large
|
|
|
|
: Corner::Small;
|
|
|
|
_st->paintButton(p, st, outerWidth, button, buttonRounding);
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-27 13:59:24 +00:00
|
|
|
ClickHandlerPtr ReplyKeyboard::getLink(QPoint point) const {
|
2017-12-18 15:44:50 +00:00
|
|
|
Assert(_width > 0);
|
|
|
|
|
2021-09-03 11:09:57 +00:00
|
|
|
for (const auto &row : _rows) {
|
|
|
|
for (const auto &button : row) {
|
2017-12-18 15:44:50 +00:00
|
|
|
QRect rect(button.rect);
|
|
|
|
|
|
|
|
// just ignore the buttons that didn't layout well
|
|
|
|
if (rect.x() + rect.width() > _width) break;
|
|
|
|
|
|
|
|
if (rect.contains(point)) {
|
|
|
|
_savedCoords = point;
|
|
|
|
return button.link;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ClickHandlerPtr();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
|
|
|
if (!p) return;
|
|
|
|
|
|
|
|
_savedActive = active ? p : ClickHandlerPtr();
|
|
|
|
auto coords = findButtonCoordsByClickHandler(p);
|
|
|
|
if (coords.i >= 0 && _savedPressed != p) {
|
|
|
|
startAnimation(coords.i, coords.j, active ? 1 : -1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ReplyKeyboard::ButtonCoords ReplyKeyboard::findButtonCoordsByClickHandler(const ClickHandlerPtr &p) {
|
|
|
|
for (int i = 0, rows = _rows.size(); i != rows; ++i) {
|
|
|
|
auto &row = _rows[i];
|
|
|
|
for (int j = 0, cols = row.size(); j != cols; ++j) {
|
|
|
|
if (row[j].link == p) {
|
|
|
|
return { i, j };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return { -1, -1 };
|
|
|
|
}
|
|
|
|
|
2018-01-17 16:21:01 +00:00
|
|
|
void ReplyKeyboard::clickHandlerPressedChanged(
|
|
|
|
const ClickHandlerPtr &handler,
|
2022-10-03 11:11:05 +00:00
|
|
|
bool pressed,
|
|
|
|
Ui::BubbleRounding rounding) {
|
2018-01-17 16:21:01 +00:00
|
|
|
if (!handler) return;
|
2017-12-18 15:44:50 +00:00
|
|
|
|
2018-01-17 16:21:01 +00:00
|
|
|
_savedPressed = pressed ? handler : ClickHandlerPtr();
|
|
|
|
auto coords = findButtonCoordsByClickHandler(handler);
|
2017-12-18 15:44:50 +00:00
|
|
|
if (coords.i >= 0) {
|
|
|
|
auto &button = _rows[coords.i][coords.j];
|
|
|
|
if (pressed) {
|
|
|
|
if (!button.ripple) {
|
2022-10-03 11:11:05 +00:00
|
|
|
const auto sides = RectPart()
|
|
|
|
| (!coords.i ? RectPart::Top : RectPart())
|
|
|
|
| (!coords.j ? RectPart::Left : RectPart())
|
|
|
|
| ((coords.i + 1 == _rows.size())
|
|
|
|
? RectPart::Bottom
|
|
|
|
: RectPart())
|
|
|
|
| ((coords.j + 1 == _rows[coords.i].size())
|
|
|
|
? RectPart::Right
|
|
|
|
: RectPart());
|
|
|
|
auto mask = Ui::RippleAnimation::RoundRectMask(
|
2017-12-18 15:44:50 +00:00
|
|
|
button.rect.size(),
|
2022-10-03 11:11:05 +00:00
|
|
|
_st->buttonRounding(rounding, sides));
|
2017-12-18 15:44:50 +00:00
|
|
|
button.ripple = std::make_unique<Ui::RippleAnimation>(
|
|
|
|
_st->_st->ripple,
|
|
|
|
std::move(mask),
|
2022-10-03 11:11:05 +00:00
|
|
|
[=] { _st->repaint(_item); });
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
button.ripple->add(_savedCoords - button.rect.topLeft());
|
|
|
|
} else {
|
|
|
|
if (button.ripple) {
|
|
|
|
button.ripple->lastStop();
|
|
|
|
}
|
2018-01-17 16:21:01 +00:00
|
|
|
if (_savedActive != handler) {
|
2017-12-18 15:44:50 +00:00
|
|
|
startAnimation(coords.i, coords.j, -1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReplyKeyboard::startAnimation(int i, int j, int direction) {
|
|
|
|
auto notStarted = _animations.empty();
|
|
|
|
|
2021-07-22 23:11:27 +00:00
|
|
|
int indexForAnimation = Layout::PositionToIndex(i, j + 1) * direction;
|
2017-12-18 15:44:50 +00:00
|
|
|
|
|
|
|
_animations.remove(-indexForAnimation);
|
|
|
|
if (!_animations.contains(indexForAnimation)) {
|
2019-02-19 06:57:53 +00:00
|
|
|
_animations.emplace(indexForAnimation, crl::now());
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
|
2019-04-01 17:44:54 +00:00
|
|
|
if (notStarted && !_selectedAnimation.animating()) {
|
|
|
|
_selectedAnimation.start();
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-01 17:44:54 +00:00
|
|
|
bool ReplyKeyboard::selectedAnimationCallback(crl::time now) {
|
2018-09-20 16:39:59 +00:00
|
|
|
if (anim::Disabled()) {
|
2019-04-01 17:44:54 +00:00
|
|
|
now += st::botKbDuration;
|
2018-09-20 16:39:59 +00:00
|
|
|
}
|
2017-12-18 15:44:50 +00:00
|
|
|
for (auto i = _animations.begin(); i != _animations.end();) {
|
|
|
|
const auto index = std::abs(i->first) - 1;
|
2021-07-22 23:11:27 +00:00
|
|
|
const auto &[row, col] = Layout::IndexToPosition(index);
|
2019-04-01 17:44:54 +00:00
|
|
|
const auto dt = float64(now - i->second) / st::botKbDuration;
|
2017-12-18 15:44:50 +00:00
|
|
|
if (dt >= 1) {
|
|
|
|
_rows[row][col].howMuchOver = (i->first > 0) ? 1 : 0;
|
|
|
|
i = _animations.erase(i);
|
|
|
|
} else {
|
|
|
|
_rows[row][col].howMuchOver = (i->first > 0) ? dt : (1 - dt);
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
}
|
2019-04-01 17:44:54 +00:00
|
|
|
_st->repaint(_item);
|
|
|
|
return !_animations.empty();
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ReplyKeyboard::clearSelection() {
|
2021-03-13 08:26:58 +00:00
|
|
|
for (const auto &[relativeIndex, time] : _animations) {
|
2017-12-18 15:44:50 +00:00
|
|
|
const auto index = std::abs(relativeIndex) - 1;
|
2021-07-22 23:11:27 +00:00
|
|
|
const auto &[row, col] = Layout::IndexToPosition(index);
|
2017-12-18 15:44:50 +00:00
|
|
|
_rows[row][col].howMuchOver = 0;
|
|
|
|
}
|
|
|
|
_animations.clear();
|
2019-04-01 17:44:54 +00:00
|
|
|
_selectedAnimation.stop();
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int ReplyKeyboard::Style::buttonSkip() const {
|
|
|
|
return _st->margin;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ReplyKeyboard::Style::buttonPadding() const {
|
|
|
|
return _st->padding;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ReplyKeyboard::Style::buttonHeight() const {
|
|
|
|
return _st->height;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReplyKeyboard::Style::paintButton(
|
|
|
|
Painter &p,
|
2021-09-02 22:08:57 +00:00
|
|
|
const Ui::ChatStyle *st,
|
2017-12-18 15:44:50 +00:00
|
|
|
int outerWidth,
|
2022-10-03 11:11:05 +00:00
|
|
|
const ReplyKeyboard::Button &button,
|
|
|
|
Ui::BubbleRounding rounding) const {
|
2017-12-18 15:44:50 +00:00
|
|
|
const QRect &rect = button.rect;
|
2022-10-03 11:11:05 +00:00
|
|
|
paintButtonBg(p, st, rect, rounding, button.howMuchOver);
|
2017-12-18 15:44:50 +00:00
|
|
|
if (button.ripple) {
|
2021-09-03 15:51:48 +00:00
|
|
|
const auto color = st ? &st->msgBotKbRippleBg()->c : nullptr;
|
|
|
|
button.ripple->paint(p, rect.x(), rect.y(), outerWidth, color);
|
2017-12-18 15:44:50 +00:00
|
|
|
if (button.ripple->empty()) {
|
|
|
|
button.ripple.reset();
|
|
|
|
}
|
|
|
|
}
|
2021-09-02 22:08:57 +00:00
|
|
|
paintButtonIcon(p, st, rect, outerWidth, button.type);
|
2020-08-25 10:57:17 +00:00
|
|
|
if (button.type == HistoryMessageMarkupButton::Type::CallbackWithPassword
|
|
|
|
|| button.type == HistoryMessageMarkupButton::Type::Callback
|
2017-12-18 15:44:50 +00:00
|
|
|
|| button.type == HistoryMessageMarkupButton::Type::Game) {
|
2021-09-02 22:08:57 +00:00
|
|
|
if (const auto data = button.link->getButton()) {
|
2017-12-18 15:44:50 +00:00
|
|
|
if (data->requestId) {
|
2021-09-02 22:08:57 +00:00
|
|
|
paintButtonLoading(p, st, rect);
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int tx = rect.x(), tw = rect.width();
|
|
|
|
if (tw >= st::botKbStyle.font->elidew + _st->padding * 2) {
|
|
|
|
tx += _st->padding;
|
|
|
|
tw -= _st->padding * 2;
|
|
|
|
} else if (tw > st::botKbStyle.font->elidew) {
|
|
|
|
tx += (tw - st::botKbStyle.font->elidew) / 2;
|
|
|
|
tw = st::botKbStyle.font->elidew;
|
|
|
|
}
|
|
|
|
button.text.drawElided(p, tx, rect.y() + _st->textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top);
|
|
|
|
}
|
|
|
|
|
2021-10-02 11:28:21 +00:00
|
|
|
void HistoryMessageReplyMarkup::createForwarded(
|
|
|
|
const HistoryMessageReplyMarkup &original) {
|
|
|
|
Expects(!inlineKeyboard);
|
2017-12-18 15:44:50 +00:00
|
|
|
|
2021-10-02 11:28:21 +00:00
|
|
|
data.fillForwardedData(original.data);
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
|
2021-10-02 11:28:21 +00:00
|
|
|
void HistoryMessageReplyMarkup::updateData(
|
|
|
|
HistoryMessageMarkupData &&markup) {
|
|
|
|
data = std::move(markup);
|
2017-12-18 15:44:50 +00:00
|
|
|
inlineKeyboard = nullptr;
|
|
|
|
}
|
|
|
|
|
2022-09-12 12:10:30 +00:00
|
|
|
bool HistoryMessageReplyMarkup::hiddenBy(Data::Media *media) const {
|
|
|
|
if (media && (data.flags & ReplyMarkupFlag::OnlyBuyButton)) {
|
|
|
|
if (const auto invoice = media->invoice()) {
|
2022-09-12 14:18:31 +00:00
|
|
|
if (invoice->extendedPreview
|
2022-09-12 12:10:30 +00:00
|
|
|
&& (!invoice->extendedMedia || !invoice->receiptMsgId)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal() = default;
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal(
|
|
|
|
HistoryMessageLogEntryOriginal &&other)
|
|
|
|
: page(std::move(other.page)) {
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
HistoryMessageLogEntryOriginal &HistoryMessageLogEntryOriginal::operator=(
|
|
|
|
HistoryMessageLogEntryOriginal &&other) {
|
|
|
|
page = std::move(other.page);
|
2017-12-18 15:44:50 +00:00
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default;
|
|
|
|
|
|
|
|
HistoryDocumentCaptioned::HistoryDocumentCaptioned()
|
2022-09-30 14:49:48 +00:00
|
|
|
: caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) {
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
|
2019-04-01 17:44:54 +00:00
|
|
|
HistoryDocumentVoicePlayback::HistoryDocumentVoicePlayback(
|
2019-08-02 13:21:09 +00:00
|
|
|
const HistoryView::Document *that)
|
2019-04-01 17:44:54 +00:00
|
|
|
: progress(0., 0.)
|
|
|
|
, progressAnimation([=](crl::time now) {
|
2019-08-02 13:21:09 +00:00
|
|
|
const auto nonconst = const_cast<HistoryView::Document*>(that);
|
2019-04-01 17:44:54 +00:00
|
|
|
return nonconst->voiceProgressAnimationCallback(now);
|
|
|
|
}) {
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
|
2019-04-01 17:44:54 +00:00
|
|
|
void HistoryDocumentVoice::ensurePlayback(
|
2019-08-02 13:21:09 +00:00
|
|
|
const HistoryView::Document *that) const {
|
2022-09-30 14:49:48 +00:00
|
|
|
if (!playback) {
|
|
|
|
playback = std::make_unique<HistoryDocumentVoicePlayback>(that);
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryDocumentVoice::checkPlaybackFinished() const {
|
2022-09-30 14:49:48 +00:00
|
|
|
if (playback && !playback->progressAnimation.animating()) {
|
|
|
|
playback.reset();
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryDocumentVoice::startSeeking() {
|
|
|
|
_seeking = true;
|
|
|
|
_seekingCurrent = _seekingStart;
|
|
|
|
Media::Player::instance()->startSeeking(AudioMsgId::Type::Voice);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryDocumentVoice::stopSeeking() {
|
|
|
|
_seeking = false;
|
2019-02-28 21:03:25 +00:00
|
|
|
Media::Player::instance()->cancelSeeking(AudioMsgId::Type::Voice);
|
2017-12-18 15:44:50 +00:00
|
|
|
}
|
2022-05-24 15:38:46 +00:00
|
|
|
|
|
|
|
bool HistoryDocumentVoice::seeking() const {
|
|
|
|
return _seeking;
|
|
|
|
}
|
|
|
|
|
|
|
|
float64 HistoryDocumentVoice::seekingStart() const {
|
|
|
|
return _seekingStart / kFloatToIntMultiplier;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryDocumentVoice::setSeekingStart(float64 seekingStart) const {
|
|
|
|
_seekingStart = qRound(seekingStart * kFloatToIntMultiplier);
|
|
|
|
}
|
|
|
|
|
|
|
|
float64 HistoryDocumentVoice::seekingCurrent() const {
|
|
|
|
return _seekingCurrent / kFloatToIntMultiplier;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryDocumentVoice::setSeekingCurrent(float64 seekingCurrent) {
|
|
|
|
_seekingCurrent = qRound(seekingCurrent * kFloatToIntMultiplier);
|
|
|
|
}
|