mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Merge tag 'v4.11.3' into dev
# Conflicts: # Telegram/Resources/winrc/Telegram.rc # Telegram/Resources/winrc/Updater.rc # Telegram/SourceFiles/core/version.h # Telegram/SourceFiles/info/profile/info_profile_values.cpp # Telegram/lib_ui # snap/snapcraft.yaml
This commit is contained in:
commit
ac1da83401
96 changed files with 1398 additions and 692 deletions
|
@ -546,6 +546,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_settings_workmode_tray" = "Show tray icon";
|
"lng_settings_workmode_tray" = "Show tray icon";
|
||||||
"lng_settings_workmode_window" = "Show taskbar icon";
|
"lng_settings_workmode_window" = "Show taskbar icon";
|
||||||
"lng_settings_close_to_taskbar" = "Close to taskbar";
|
"lng_settings_close_to_taskbar" = "Close to taskbar";
|
||||||
|
"lng_settings_monochrome_icon" = "Use monochrome icon";
|
||||||
"lng_settings_window_system" = "Window title";
|
"lng_settings_window_system" = "Window title";
|
||||||
"lng_settings_title_chat_name" = "Show chat name";
|
"lng_settings_title_chat_name" = "Show chat name";
|
||||||
"lng_settings_title_account_name" = "Show active account";
|
"lng_settings_title_account_name" = "Show active account";
|
||||||
|
@ -2684,6 +2685,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_reply_options_quote" = "Update Quote";
|
"lng_reply_options_quote" = "Update Quote";
|
||||||
"lng_reply_header_short" = "Reply";
|
"lng_reply_header_short" = "Reply";
|
||||||
"lng_reply_quote_selected" = "Quote Selected";
|
"lng_reply_quote_selected" = "Quote Selected";
|
||||||
|
"lng_reply_from_private_chat" = "This reply is from a private chat.";
|
||||||
"lng_link_options_header" = "Link Preview Settings";
|
"lng_link_options_header" = "Link Preview Settings";
|
||||||
"lng_link_header_short" = "Link";
|
"lng_link_header_short" = "Link";
|
||||||
"lng_link_move_up" = "Move Up";
|
"lng_link_move_up" = "Move Up";
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||||
ProcessorArchitecture="ARCHITECTURE"
|
ProcessorArchitecture="ARCHITECTURE"
|
||||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||||
Version="4.11.1.0" />
|
Version="4.11.3.0" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Telegram Desktop</DisplayName>
|
<DisplayName>Telegram Desktop</DisplayName>
|
||||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||||
|
|
|
@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 4,11,1,0
|
FILEVERSION 4,11,3,0
|
||||||
PRODUCTVERSION 4,11,1,0
|
PRODUCTVERSION 4,11,3,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -62,10 +62,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Radolyn Labs"
|
VALUE "CompanyName", "Radolyn Labs"
|
||||||
VALUE "FileDescription", "AyuGram Desktop"
|
VALUE "FileDescription", "AyuGram Desktop"
|
||||||
VALUE "FileVersion", "4.11.1.0"
|
VALUE "FileVersion", "4.11.3.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||||
VALUE "ProductName", "AyuGram Desktop"
|
VALUE "ProductName", "AyuGram Desktop"
|
||||||
VALUE "ProductVersion", "4.11.1.0"
|
VALUE "ProductVersion", "4.11.3.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||||
//
|
//
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION 4,11,1,0
|
FILEVERSION 4,11,3,0
|
||||||
PRODUCTVERSION 4,11,1,0
|
PRODUCTVERSION 4,11,3,0
|
||||||
FILEFLAGSMASK 0x3fL
|
FILEFLAGSMASK 0x3fL
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
FILEFLAGS 0x1L
|
FILEFLAGS 0x1L
|
||||||
|
@ -53,10 +53,10 @@ BEGIN
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "Radolyn Labs"
|
VALUE "CompanyName", "Radolyn Labs"
|
||||||
VALUE "FileDescription", "AyuGram Desktop Updater"
|
VALUE "FileDescription", "AyuGram Desktop Updater"
|
||||||
VALUE "FileVersion", "4.11.1.0"
|
VALUE "FileVersion", "4.11.3.0"
|
||||||
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
|
||||||
VALUE "ProductName", "AyuGram Desktop"
|
VALUE "ProductName", "AyuGram Desktop"
|
||||||
VALUE "ProductVersion", "4.11.1.0"
|
VALUE "ProductVersion", "4.11.3.0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|
|
@ -270,7 +270,6 @@ bool SendDice(MessageToSend &message) {
|
||||||
flags |= MessageFlag::HasReplyInfo;
|
flags |= MessageFlag::HasReplyInfo;
|
||||||
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
|
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
|
||||||
}
|
}
|
||||||
const auto replyHeader = NewMessageReplyHeader(message.action);
|
|
||||||
const auto anonymousPost = peer->amAnonymous();
|
const auto anonymousPost = peer->amAnonymous();
|
||||||
const auto silentPost = ShouldSendSilent(peer, message.action.options);
|
const auto silentPost = ShouldSendSilent(peer, message.action.options);
|
||||||
InnerFillMessagePostFlags(message.action.options, peer, flags);
|
InnerFillMessagePostFlags(message.action.options, peer, flags);
|
||||||
|
@ -399,7 +398,6 @@ void SendConfirmedFile(
|
||||||
if (file->to.replyTo) {
|
if (file->to.replyTo) {
|
||||||
flags |= MessageFlag::HasReplyInfo;
|
flags |= MessageFlag::HasReplyInfo;
|
||||||
}
|
}
|
||||||
const auto replyHeader = NewMessageReplyHeader(action);
|
|
||||||
const auto anonymousPost = peer->amAnonymous();
|
const auto anonymousPost = peer->amAnonymous();
|
||||||
const auto silentPost = ShouldSendSilent(peer, file->to.options);
|
const auto silentPost = ShouldSendSilent(peer, file->to.options);
|
||||||
FillMessagePostFlags(action, peer, flags);
|
FillMessagePostFlags(action, peer, flags);
|
||||||
|
|
|
@ -3391,7 +3391,6 @@ void ApiWrap::sendSharedContact(
|
||||||
if (action.replyTo) {
|
if (action.replyTo) {
|
||||||
flags |= MessageFlag::HasReplyInfo;
|
flags |= MessageFlag::HasReplyInfo;
|
||||||
}
|
}
|
||||||
const auto replyHeader = NewMessageReplyHeader(action);
|
|
||||||
FillMessagePostFlags(action, peer, flags);
|
FillMessagePostFlags(action, peer, flags);
|
||||||
if (action.options.scheduled) {
|
if (action.options.scheduled) {
|
||||||
flags |= MessageFlag::IsOrWasScheduled;
|
flags |= MessageFlag::IsOrWasScheduled;
|
||||||
|
@ -3614,14 +3613,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
action.generateLocal = true;
|
action.generateLocal = true;
|
||||||
sendAction(action);
|
sendAction(action);
|
||||||
|
|
||||||
const auto replyTo = action.replyTo.messageId
|
const auto clearCloudDraft = action.clearDraft;
|
||||||
? peer->owner().message(action.replyTo.messageId)
|
const auto topicRootId = action.replyTo.topicRootId;
|
||||||
: nullptr;
|
|
||||||
const auto topicRootId = replyTo
|
|
||||||
? replyTo->topicRootId()
|
|
||||||
: action.replyTo.topicRootId
|
|
||||||
? action.replyTo.topicRootId
|
|
||||||
: Data::ForumTopic::kGeneralId;
|
|
||||||
const auto topic = peer->forumTopicFor(topicRootId);
|
const auto topic = peer->forumTopicFor(topicRootId);
|
||||||
if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer))
|
if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer))
|
||||||
|| Api::SendDice(message)) {
|
|| Api::SendDice(message)) {
|
||||||
|
@ -3674,7 +3667,6 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
const auto manualWebPage = exactWebPage
|
const auto manualWebPage = exactWebPage
|
||||||
&& !ignoreWebPage
|
&& !ignoreWebPage
|
||||||
&& (message.webPage.manual || (isLast && !isFirst));
|
&& (message.webPage.manual || (isLast && !isFirst));
|
||||||
const auto replyHeader = NewMessageReplyHeader(action);
|
|
||||||
MTPMessageMedia media = MTP_messageMediaEmpty();
|
MTPMessageMedia media = MTP_messageMediaEmpty();
|
||||||
if (ignoreWebPage) {
|
if (ignoreWebPage) {
|
||||||
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
|
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
|
||||||
|
@ -3718,8 +3710,6 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
|
||||||
sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
|
sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
|
||||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_entities;
|
mediaFlags |= MTPmessages_SendMedia::Flag::f_entities;
|
||||||
}
|
}
|
||||||
const auto clearCloudDraft = action.clearDraft;
|
|
||||||
const auto topicRootId = action.replyTo.topicRootId;
|
|
||||||
if (clearCloudDraft) {
|
if (clearCloudDraft) {
|
||||||
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
|
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
|
||||||
mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
|
mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
|
||||||
|
|
|
@ -281,7 +281,7 @@ struct GiftCodeLink {
|
||||||
|
|
||||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue(
|
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue(
|
||||||
not_null<QWidget*> parent,
|
not_null<QWidget*> parent,
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionNavigation*> controller,
|
||||||
PeerId id) {
|
PeerId id) {
|
||||||
auto result = object_ptr<Ui::AbstractButton>(parent);
|
auto result = object_ptr<Ui::AbstractButton>(parent);
|
||||||
const auto raw = result.data();
|
const auto raw = result.data();
|
||||||
|
@ -309,7 +309,7 @@ struct GiftCodeLink {
|
||||||
label->setTextColorOverride(st::windowActiveTextFg->c);
|
label->setTextColorOverride(st::windowActiveTextFg->c);
|
||||||
|
|
||||||
raw->setClickedCallback([=] {
|
raw->setClickedCallback([=] {
|
||||||
controller->show(PrepareShortInfoBox(peer, controller));
|
controller->uiShow()->showBox(PrepareShortInfoBox(peer, controller));
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -350,7 +350,7 @@ not_null<Ui::FlatLabel*> AddTableRow(
|
||||||
void AddTableRow(
|
void AddTableRow(
|
||||||
not_null<Ui::TableLayout*> table,
|
not_null<Ui::TableLayout*> table,
|
||||||
rpl::producer<QString> label,
|
rpl::producer<QString> label,
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionNavigation*> controller,
|
||||||
PeerId id) {
|
PeerId id) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
|
@ -416,7 +416,7 @@ QString GiftDuration(int months) {
|
||||||
|
|
||||||
void GiftCodeBox(
|
void GiftCodeBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionNavigation*> controller,
|
||||||
const QString &slug) {
|
const QString &slug) {
|
||||||
struct State {
|
struct State {
|
||||||
rpl::variable<Api::GiftCode> data;
|
rpl::variable<Api::GiftCode> data;
|
||||||
|
@ -552,7 +552,7 @@ void GiftCodeBox(
|
||||||
st::giveawayGiftCodeFooterMargin);
|
st::giveawayGiftCodeFooterMargin);
|
||||||
footer->setClickHandlerFilter([=](const auto &...) {
|
footer->setClickHandlerFilter([=](const auto &...) {
|
||||||
const auto chosen = [=](not_null<Data::Thread*> thread) {
|
const auto chosen = [=](not_null<Data::Thread*> thread) {
|
||||||
const auto content = controller->content();
|
const auto content = controller->parentController()->content();
|
||||||
return content->shareUrl(
|
return content->shareUrl(
|
||||||
thread,
|
thread,
|
||||||
MakeGiftCodeLink(&controller->session(), slug).link,
|
MakeGiftCodeLink(&controller->session(), slug).link,
|
||||||
|
@ -608,13 +608,13 @@ void GiftCodeBox(
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResolveGiftCode(
|
void ResolveGiftCode(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionNavigation*> controller,
|
||||||
const QString &slug) {
|
const QString &slug) {
|
||||||
const auto done = [=](Api::GiftCode code) {
|
const auto done = [=](Api::GiftCode code) {
|
||||||
if (!code) {
|
if (!code) {
|
||||||
controller->showToast(tr::lng_gift_link_expired(tr::now));
|
controller->showToast(tr::lng_gift_link_expired(tr::now));
|
||||||
} else {
|
} else {
|
||||||
controller->show(Box(GiftCodeBox, controller, slug));
|
controller->uiShow()->showBox(Box(GiftCodeBox, controller, slug));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
controller->session().api().premium().checkGiftCode(
|
controller->session().api().premium().checkGiftCode(
|
||||||
|
@ -624,7 +624,7 @@ void ResolveGiftCode(
|
||||||
|
|
||||||
void GiveawayInfoBox(
|
void GiveawayInfoBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionNavigation*> controller,
|
||||||
Data::Giveaway giveaway,
|
Data::Giveaway giveaway,
|
||||||
Api::GiveawayInfo info) {
|
Api::GiveawayInfo info) {
|
||||||
using State = Api::GiveawayState;
|
using State = Api::GiveawayState;
|
||||||
|
@ -784,7 +784,7 @@ void GiveawayInfoBox(
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResolveGiveawayInfo(
|
void ResolveGiveawayInfo(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionNavigation*> controller,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
MsgId messageId,
|
MsgId messageId,
|
||||||
Data::Giveaway giveaway) {
|
Data::Giveaway giveaway) {
|
||||||
|
@ -793,7 +793,7 @@ void ResolveGiveawayInfo(
|
||||||
controller->showToast(
|
controller->showToast(
|
||||||
tr::lng_confirm_phone_link_invalid(tr::now));
|
tr::lng_confirm_phone_link_invalid(tr::now));
|
||||||
} else {
|
} else {
|
||||||
controller->show(
|
controller->uiShow()->showBox(
|
||||||
Box(GiveawayInfoBox, controller, giveaway, info));
|
Box(GiveawayInfoBox, controller, giveaway, info));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,7 @@ class GenericBox;
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
class SessionController;
|
class SessionController;
|
||||||
|
class SessionNavigation;
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
|
||||||
class GiftPremiumValidator final {
|
class GiftPremiumValidator final {
|
||||||
|
@ -47,14 +48,14 @@ private:
|
||||||
|
|
||||||
void GiftCodeBox(
|
void GiftCodeBox(
|
||||||
not_null<Ui::GenericBox*> box,
|
not_null<Ui::GenericBox*> box,
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionNavigation*> controller,
|
||||||
const QString &slug);
|
const QString &slug);
|
||||||
void ResolveGiftCode(
|
void ResolveGiftCode(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionNavigation*> controller,
|
||||||
const QString &slug);
|
const QString &slug);
|
||||||
|
|
||||||
void ResolveGiveawayInfo(
|
void ResolveGiveawayInfo(
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionNavigation*> controller,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
MsgId messageId,
|
MsgId messageId,
|
||||||
Data::Giveaway giveaway);
|
Data::Giveaway giveaway);
|
||||||
|
|
|
@ -760,7 +760,7 @@ int PeerListRow::paintNameIconGetWidth(
|
||||||
nameWidth,
|
nameWidth,
|
||||||
outerWidth,
|
outerWidth,
|
||||||
{
|
{
|
||||||
.peer = _peer,
|
.peer = peer(),
|
||||||
.verified = &(selected
|
.verified = &(selected
|
||||||
? st::dialogsVerifiedIconOver
|
? st::dialogsVerifiedIconOver
|
||||||
: st::dialogsVerifiedIcon),
|
: st::dialogsVerifiedIcon),
|
||||||
|
|
|
@ -712,13 +712,24 @@ void Instance::destroyCurrentCall() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Instance::hasActivePanel(not_null<Main::Session*> session) const {
|
bool Instance::hasVisiblePanel(Main::Session *session) const {
|
||||||
if (inCall()) {
|
if (inCall()) {
|
||||||
return (&_currentCall->user()->session() == session)
|
return _currentCallPanel->isVisible()
|
||||||
&& _currentCallPanel->isActive();
|
&& (!session || (&_currentCall->user()->session() == session));
|
||||||
} else if (inGroupCall()) {
|
} else if (inGroupCall()) {
|
||||||
return (&_currentGroupCall->peer()->session() == session)
|
return _currentGroupCallPanel->isVisible()
|
||||||
&& _currentGroupCallPanel->isActive();
|
&& (!session || (&_currentGroupCall->peer()->session() == session));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Instance::hasActivePanel(Main::Session *session) const {
|
||||||
|
if (inCall()) {
|
||||||
|
return _currentCallPanel->isActive()
|
||||||
|
&& (!session || (&_currentCall->user()->session() == session));
|
||||||
|
} else if (inGroupCall()) {
|
||||||
|
return _currentGroupCallPanel->isActive()
|
||||||
|
&& (!session || (&_currentGroupCall->peer()->session() == session));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,8 +89,10 @@ public:
|
||||||
[[nodiscard]] rpl::producer<GroupCall*> currentGroupCallValue() const;
|
[[nodiscard]] rpl::producer<GroupCall*> currentGroupCallValue() const;
|
||||||
[[nodiscard]] bool inCall() const;
|
[[nodiscard]] bool inCall() const;
|
||||||
[[nodiscard]] bool inGroupCall() const;
|
[[nodiscard]] bool inGroupCall() const;
|
||||||
|
[[nodiscard]] bool hasVisiblePanel(
|
||||||
|
Main::Session *session = nullptr) const;
|
||||||
[[nodiscard]] bool hasActivePanel(
|
[[nodiscard]] bool hasActivePanel(
|
||||||
not_null<Main::Session*> session) const;
|
Main::Session *session = nullptr) const;
|
||||||
bool activateCurrentCall(const QString &joinHash = QString());
|
bool activateCurrentCall(const QString &joinHash = QString());
|
||||||
bool minimizeCurrentActiveCall();
|
bool minimizeCurrentActiveCall();
|
||||||
bool toggleFullScreenCurrentActiveCall();
|
bool toggleFullScreenCurrentActiveCall();
|
||||||
|
|
|
@ -106,12 +106,15 @@ Panel::Panel(not_null<Call*> call)
|
||||||
|
|
||||||
Panel::~Panel() = default;
|
Panel::~Panel() = default;
|
||||||
|
|
||||||
bool Panel::isActive() const {
|
bool Panel::isVisible() const {
|
||||||
return window()->isActiveWindow()
|
return window()->isVisible()
|
||||||
&& window()->isVisible()
|
|
||||||
&& !(window()->windowState() & Qt::WindowMinimized);
|
&& !(window()->windowState() & Qt::WindowMinimized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Panel::isActive() const {
|
||||||
|
return window()->isActiveWindow() && isVisible();
|
||||||
|
}
|
||||||
|
|
||||||
void Panel::showAndActivate() {
|
void Panel::showAndActivate() {
|
||||||
if (window()->isHidden()) {
|
if (window()->isHidden()) {
|
||||||
window()->show();
|
window()->show();
|
||||||
|
|
|
@ -61,6 +61,7 @@ public:
|
||||||
Panel(not_null<Call*> call);
|
Panel(not_null<Call*> call);
|
||||||
~Panel();
|
~Panel();
|
||||||
|
|
||||||
|
[[nodiscard]] bool isVisible() const;
|
||||||
[[nodiscard]] bool isActive() const;
|
[[nodiscard]] bool isActive() const;
|
||||||
void showAndActivate();
|
void showAndActivate();
|
||||||
void minimize();
|
void minimize();
|
||||||
|
|
|
@ -258,12 +258,15 @@ not_null<GroupCall*> Panel::call() const {
|
||||||
return _call;
|
return _call;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Panel::isActive() const {
|
bool Panel::isVisible() const {
|
||||||
return window()->isActiveWindow()
|
return window()->isVisible()
|
||||||
&& window()->isVisible()
|
|
||||||
&& !(window()->windowState() & Qt::WindowMinimized);
|
&& !(window()->windowState() & Qt::WindowMinimized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Panel::isActive() const {
|
||||||
|
return window()->isActiveWindow() && isVisible();
|
||||||
|
}
|
||||||
|
|
||||||
base::weak_ptr<Ui::Toast::Instance> Panel::showToast(
|
base::weak_ptr<Ui::Toast::Instance> Panel::showToast(
|
||||||
const QString &text,
|
const QString &text,
|
||||||
crl::time duration) {
|
crl::time duration) {
|
||||||
|
|
|
@ -91,6 +91,7 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
|
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
|
||||||
[[nodiscard]] not_null<GroupCall*> call() const;
|
[[nodiscard]] not_null<GroupCall*> call() const;
|
||||||
|
[[nodiscard]] bool isVisible() const;
|
||||||
[[nodiscard]] bool isActive() const;
|
[[nodiscard]] bool isActive() const;
|
||||||
|
|
||||||
base::weak_ptr<Ui::Toast::Instance> showToast(
|
base::weak_ptr<Ui::Toast::Instance> showToast(
|
||||||
|
|
|
@ -493,10 +493,7 @@ InlineBotQuery ParseInlineBotQuery(
|
||||||
result.lookingUpBot = true;
|
result.lookingUpBot = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result.lookingUpBot) {
|
if (result.bot
|
||||||
result.query = QString();
|
|
||||||
return result;
|
|
||||||
} else if (result.bot
|
|
||||||
&& (!result.bot->isBot()
|
&& (!result.bot->isBot()
|
||||||
|| result.bot->botInfo->inlinePlaceholder.isEmpty())) {
|
|| result.bot->botInfo->inlinePlaceholder.isEmpty())) {
|
||||||
result.bot = nullptr;
|
result.bot = nullptr;
|
||||||
|
|
|
@ -348,6 +348,8 @@ QByteArray Settings::serialize() const {
|
||||||
for (const auto &id : _recentEmojiSkip) {
|
for (const auto &id : _recentEmojiSkip) {
|
||||||
stream << id;
|
stream << id;
|
||||||
}
|
}
|
||||||
|
stream
|
||||||
|
<< qint32(_trayIconMonochrome.current() ? 1 : 0);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -457,6 +459,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||||
quint64 macRoundIconDigest = _macRoundIconDigest.value_or(0);
|
quint64 macRoundIconDigest = _macRoundIconDigest.value_or(0);
|
||||||
qint32 storiesClickTooltipHidden = _storiesClickTooltipHidden.current() ? 1 : 0;
|
qint32 storiesClickTooltipHidden = _storiesClickTooltipHidden.current() ? 1 : 0;
|
||||||
base::flat_set<QString> recentEmojiSkip;
|
base::flat_set<QString> recentEmojiSkip;
|
||||||
|
qint32 trayIconMonochrome = (_trayIconMonochrome.current() ? 1 : 0);
|
||||||
|
|
||||||
stream >> themesAccentColors;
|
stream >> themesAccentColors;
|
||||||
if (!stream.atEnd()) {
|
if (!stream.atEnd()) {
|
||||||
|
@ -707,6 +710,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!stream.atEnd()) {
|
||||||
|
stream >> trayIconMonochrome;
|
||||||
|
} else {
|
||||||
|
// Let existing clients use the old value.
|
||||||
|
trayIconMonochrome = 0;
|
||||||
|
}
|
||||||
if (stream.status() != QDataStream::Ok) {
|
if (stream.status() != QDataStream::Ok) {
|
||||||
LOG(("App Error: "
|
LOG(("App Error: "
|
||||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||||
|
@ -901,6 +910,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||||
_macRoundIconDigest = macRoundIconDigest ? macRoundIconDigest : std::optional<uint64>();
|
_macRoundIconDigest = macRoundIconDigest ? macRoundIconDigest : std::optional<uint64>();
|
||||||
_storiesClickTooltipHidden = (storiesClickTooltipHidden == 1);
|
_storiesClickTooltipHidden = (storiesClickTooltipHidden == 1);
|
||||||
_recentEmojiSkip = std::move(recentEmojiSkip);
|
_recentEmojiSkip = std::move(recentEmojiSkip);
|
||||||
|
_trayIconMonochrome = (trayIconMonochrome == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Settings::getSoundPath(const QString &key) const {
|
QString Settings::getSoundPath(const QString &key) const {
|
||||||
|
|
|
@ -708,6 +708,15 @@ public:
|
||||||
[[nodiscard]] rpl::producer<bool> closeToTaskbarChanges() const {
|
[[nodiscard]] rpl::producer<bool> closeToTaskbarChanges() const {
|
||||||
return _closeToTaskbar.changes();
|
return _closeToTaskbar.changes();
|
||||||
}
|
}
|
||||||
|
void setTrayIconMonochrome(bool value) {
|
||||||
|
_trayIconMonochrome = value;
|
||||||
|
}
|
||||||
|
[[nodiscard]] bool trayIconMonochrome() const {
|
||||||
|
return _trayIconMonochrome.current();
|
||||||
|
}
|
||||||
|
[[nodiscard]] rpl::producer<bool> trayIconMonochromeChanges() const {
|
||||||
|
return _trayIconMonochrome.changes();
|
||||||
|
}
|
||||||
|
|
||||||
void setCustomDeviceModel(const QString &model) {
|
void setCustomDeviceModel(const QString &model) {
|
||||||
_customDeviceModel = model;
|
_customDeviceModel = model;
|
||||||
|
@ -924,6 +933,7 @@ private:
|
||||||
rpl::variable<WorkMode> _workMode = WorkMode::WindowAndTray;
|
rpl::variable<WorkMode> _workMode = WorkMode::WindowAndTray;
|
||||||
base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips;
|
base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips;
|
||||||
rpl::variable<bool> _closeToTaskbar = false;
|
rpl::variable<bool> _closeToTaskbar = false;
|
||||||
|
rpl::variable<bool> _trayIconMonochrome = true;
|
||||||
rpl::variable<QString> _customDeviceModel;
|
rpl::variable<QString> _customDeviceModel;
|
||||||
rpl::variable<Media::RepeatMode> _playerRepeatMode;
|
rpl::variable<Media::RepeatMode> _playerRepeatMode;
|
||||||
rpl::variable<Media::OrderMode> _playerOrderMode;
|
rpl::variable<Media::OrderMode> _playerOrderMode;
|
||||||
|
|
|
@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
|
||||||
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
||||||
constexpr auto AppName = "AyuGram Desktop"_cs;
|
constexpr auto AppName = "AyuGram Desktop"_cs;
|
||||||
constexpr auto AppFile = "AyuGram"_cs;
|
constexpr auto AppFile = "AyuGram"_cs;
|
||||||
constexpr auto AppVersion = 4011001;
|
constexpr auto AppVersion = 4011003;
|
||||||
constexpr auto AppVersionStr = "4.11.1";
|
constexpr auto AppVersionStr = "4.11.3";
|
||||||
constexpr auto AppBetaVersion = false;
|
constexpr auto AppBetaVersion = false;
|
||||||
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
|
||||||
|
|
|
@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "window/notifications_manager.h"
|
#include "window/notifications_manager.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
#include "history/history_item_helpers.h"
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
@ -52,8 +53,18 @@ MTPInputReplyTo ReplyToForMTP(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (replyTo.messageId || replyTo.topicRootId) {
|
} else if (replyTo.messageId || replyTo.topicRootId) {
|
||||||
|
const auto to = LookupReplyTo(history, replyTo.messageId);
|
||||||
|
const auto replyingToTopicId = replyTo.topicRootId
|
||||||
|
? replyTo.topicRootId
|
||||||
|
: Data::ForumTopic::kGeneralId;
|
||||||
|
const auto replyToTopicId = !to
|
||||||
|
? replyingToTopicId
|
||||||
|
: to->topicRootId()
|
||||||
|
? to->topicRootId()
|
||||||
|
: Data::ForumTopic::kGeneralId;
|
||||||
const auto external = replyTo.messageId
|
const auto external = replyTo.messageId
|
||||||
&& (replyTo.messageId.peer != history->peer->id);
|
&& (replyTo.messageId.peer != history->peer->id
|
||||||
|
|| replyingToTopicId != replyToTopicId);
|
||||||
const auto quoteEntities = Api::EntitiesToMTP(
|
const auto quoteEntities = Api::EntitiesToMTP(
|
||||||
&history->session(),
|
&history->session(),
|
||||||
replyTo.quote.entities,
|
replyTo.quote.entities,
|
||||||
|
|
|
@ -538,6 +538,7 @@ ItemPreview Media::toGroupPreview(
|
||||||
auto videoCount = 0;
|
auto videoCount = 0;
|
||||||
auto audioCount = 0;
|
auto audioCount = 0;
|
||||||
auto fileCount = 0;
|
auto fileCount = 0;
|
||||||
|
auto manyCaptions = false;
|
||||||
for (const auto &item : items) {
|
for (const auto &item : items) {
|
||||||
if (const auto media = item->media()) {
|
if (const auto media = item->media()) {
|
||||||
if (media->photo()) {
|
if (media->photo()) {
|
||||||
|
@ -571,12 +572,12 @@ ItemPreview Media::toGroupPreview(
|
||||||
if (result.text.text.isEmpty()) {
|
if (result.text.text.isEmpty()) {
|
||||||
result.text = original;
|
result.text = original;
|
||||||
} else {
|
} else {
|
||||||
result.text = {};
|
manyCaptions = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result.text.text.isEmpty()) {
|
if (manyCaptions || result.text.text.isEmpty()) {
|
||||||
const auto mediaCount = photoCount + videoCount;
|
const auto mediaCount = photoCount + videoCount;
|
||||||
auto genericText = (photoCount && videoCount)
|
auto genericText = (photoCount && videoCount)
|
||||||
? tr::lng_in_dlg_media_count(tr::now, lt_count, mediaCount)
|
? tr::lng_in_dlg_media_count(tr::now, lt_count, mediaCount)
|
||||||
|
|
|
@ -579,11 +579,6 @@ bool InnerWidget::elementUnderCursor(
|
||||||
return (Element::Hovered() == view);
|
return (Element::Hovered() == view);
|
||||||
}
|
}
|
||||||
|
|
||||||
float64 InnerWidget::elementHighlightOpacity(
|
|
||||||
not_null<const HistoryItem*> item) const {
|
|
||||||
return 0.;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool InnerWidget::elementInSelectionMode() {
|
bool InnerWidget::elementInSelectionMode() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,8 +93,6 @@ public:
|
||||||
HistoryView::Context elementContext() override;
|
HistoryView::Context elementContext() override;
|
||||||
bool elementUnderCursor(
|
bool elementUnderCursor(
|
||||||
not_null<const HistoryView::Element*> view) override;
|
not_null<const HistoryView::Element*> view) override;
|
||||||
[[nodiscard]] float64 elementHighlightOpacity(
|
|
||||||
not_null<const HistoryItem*> item) const override;
|
|
||||||
bool elementInSelectionMode() override;
|
bool elementInSelectionMode() override;
|
||||||
bool elementIntersectsRange(
|
bool elementIntersectsRange(
|
||||||
not_null<const HistoryView::Element*> view,
|
not_null<const HistoryView::Element*> view,
|
||||||
|
|
|
@ -207,10 +207,6 @@ public:
|
||||||
not_null<const Element*> view) override {
|
not_null<const Element*> view) override {
|
||||||
return (Element::Moused() == view);
|
return (Element::Moused() == view);
|
||||||
}
|
}
|
||||||
[[nodiscard]] float64 elementHighlightOpacity(
|
|
||||||
not_null<const HistoryItem*> item) const override {
|
|
||||||
return _widget ? _widget->elementHighlightOpacity(item) : 0.;
|
|
||||||
}
|
|
||||||
bool elementInSelectionMode() override {
|
bool elementInSelectionMode() override {
|
||||||
return _widget ? _widget->inSelectionMode() : false;
|
return _widget ? _widget->inSelectionMode() : false;
|
||||||
}
|
}
|
||||||
|
@ -1065,6 +1061,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
auto clip = e->rect();
|
auto clip = e->rect();
|
||||||
|
|
||||||
auto context = preparePaintContext(clip);
|
auto context = preparePaintContext(clip);
|
||||||
|
context.highlightPathCache = &_highlightPathCache;
|
||||||
_pathGradient->startFrame(
|
_pathGradient->startFrame(
|
||||||
0,
|
0,
|
||||||
width(),
|
width(),
|
||||||
|
@ -1148,7 +1145,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
} else if (item->isUnreadMention()
|
} else if (item->isUnreadMention()
|
||||||
&& !item->isUnreadMedia()) {
|
&& !item->isUnreadMedia()) {
|
||||||
readContents.insert(item);
|
readContents.insert(item);
|
||||||
_widget->enqueueMessageHighlight(view);
|
_widget->enqueueMessageHighlight(view, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
session().data().reactions().poll(item, context.now);
|
session().data().reactions().poll(item, context.now);
|
||||||
|
@ -1190,6 +1187,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
view,
|
view,
|
||||||
selfromy - mtop,
|
selfromy - mtop,
|
||||||
seltoy - mtop);
|
seltoy - mtop);
|
||||||
|
context.highlight = _widget->itemHighlight(view->data());
|
||||||
view->draw(p, context);
|
view->draw(p, context);
|
||||||
processPainted(view, top, height);
|
processPainted(view, top, height);
|
||||||
|
|
||||||
|
@ -1224,9 +1222,10 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
const auto &sendingAnimation = _controller->sendingAnimation();
|
const auto &sendingAnimation = _controller->sendingAnimation();
|
||||||
while (top < drawToY) {
|
while (top < drawToY) {
|
||||||
const auto height = view->height();
|
const auto height = view->height();
|
||||||
|
const auto item = view->data();
|
||||||
if ((context.clip.y() < height)
|
if ((context.clip.y() < height)
|
||||||
&& (hdrawtop < top + height)
|
&& (hdrawtop < top + height)
|
||||||
&& !sendingAnimation.hasAnimatedMessage(view->data())) {
|
&& !sendingAnimation.hasAnimatedMessage(item)) {
|
||||||
context.reactionInfo
|
context.reactionInfo
|
||||||
= _reactionsManager->currentReactionPaintInfo();
|
= _reactionsManager->currentReactionPaintInfo();
|
||||||
context.outbg = view->hasOutLayout();
|
context.outbg = view->hasOutLayout();
|
||||||
|
@ -1234,6 +1233,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
view,
|
view,
|
||||||
selfromy - htop,
|
selfromy - htop,
|
||||||
seltoy - htop);
|
seltoy - htop);
|
||||||
|
context.highlight = _widget->itemHighlight(item);
|
||||||
view->draw(p, context);
|
view->draw(p, context);
|
||||||
processPainted(view, top, height);
|
processPainted(view, top, height);
|
||||||
}
|
}
|
||||||
|
@ -1678,7 +1678,10 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but
|
||||||
Ui::MarkInactivePress(_controller->widget(), false);
|
Ui::MarkInactivePress(_controller->widget(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ClickHandler::getPressed()) {
|
const auto pressed = ClickHandler::getPressed();
|
||||||
|
if (pressed
|
||||||
|
&& (!Element::Hovered()
|
||||||
|
|| !Element::Hovered()->allowTextSelectionByHandler(pressed))) {
|
||||||
_mouseAction = MouseAction::PrepareDrag;
|
_mouseAction = MouseAction::PrepareDrag;
|
||||||
} else if (inSelectionMode()) {
|
} else if (inSelectionMode()) {
|
||||||
if (_dragStateItem
|
if (_dragStateItem
|
||||||
|
@ -2111,7 +2114,7 @@ void HistoryInner::toggleFavoriteReaction(not_null<Element*> view) const {
|
||||||
item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick);
|
item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities HistoryInner::selectedQuote(
|
HistoryView::SelectedQuote HistoryInner::selectedQuote(
|
||||||
not_null<HistoryItem*> item) const {
|
not_null<HistoryItem*> item) const {
|
||||||
if (_selected.size() != 1
|
if (_selected.size() != 1
|
||||||
|| _selected.begin()->first != item
|
|| _selected.begin()->first != item
|
||||||
|
@ -2401,25 +2404,22 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
: (Data::CanSendAnything(peer)
|
: (Data::CanSendAnything(peer)
|
||||||
&& (!peer->isChannel() || peer->asChannel()->amIn()));
|
&& (!peer->isChannel() || peer->asChannel()->amIn()));
|
||||||
}();
|
}();
|
||||||
const auto canReply = canSendReply || [&] {
|
const auto canReply = canSendReply || item->allowsForward();
|
||||||
const auto peer = item->history()->peer;
|
|
||||||
if (const auto chat = peer->asChat()) {
|
|
||||||
return !chat->isForbidden();
|
|
||||||
} else if (const auto channel = peer->asChannel()) {
|
|
||||||
return !channel->isForbidden();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}();
|
|
||||||
if (canReply) {
|
if (canReply) {
|
||||||
const auto itemId = item->fullId();
|
const auto selected = selectedQuote(item);
|
||||||
const auto quote = selectedQuote(item);
|
auto text = selected
|
||||||
auto text = quote.empty()
|
? tr::lng_context_quote_and_reply(tr::now)
|
||||||
? tr::lng_context_reply_msg(tr::now)
|
: tr::lng_context_reply_msg(tr::now);
|
||||||
: tr::lng_context_quote_and_reply(tr::now);
|
const auto replyToItem = selected.item ? selected.item : item;
|
||||||
|
const auto itemId = replyToItem->fullId();
|
||||||
|
const auto quote = selected.text;
|
||||||
text.replace('&', u"&&"_q);
|
text.replace('&', u"&&"_q);
|
||||||
_menu->addAction(text, [=] {
|
_menu->addAction(text, [=] {
|
||||||
if (canSendReply) {
|
if (canSendReply) {
|
||||||
_widget->replyToMessage({ itemId, quote });
|
_widget->replyToMessage({ itemId, quote });
|
||||||
|
if (!quote.empty()) {
|
||||||
|
_widget->clearSelected();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
HistoryView::Controls::ShowReplyToChatBox(
|
HistoryView::Controls::ShowReplyToChatBox(
|
||||||
controller->uiShow(),
|
controller->uiShow(),
|
||||||
|
@ -3484,11 +3484,6 @@ void HistoryInner::elementStartStickerLoop(
|
||||||
_animatedStickersPlayed.emplace(view->data());
|
_animatedStickersPlayed.emplace(view->data());
|
||||||
}
|
}
|
||||||
|
|
||||||
float64 HistoryInner::elementHighlightOpacity(
|
|
||||||
not_null<const HistoryItem*> item) const {
|
|
||||||
return _widget->highlightOpacity(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HistoryInner::elementShowPollResults(
|
void HistoryInner::elementShowPollResults(
|
||||||
not_null<PollData*> poll,
|
not_null<PollData*> poll,
|
||||||
FullMsgId context) {
|
FullMsgId context) {
|
||||||
|
@ -3855,6 +3850,10 @@ void HistoryInner::mouseActionUpdate() {
|
||||||
selState = view->adjustSelection(selState, _mouseSelectType);
|
selState = view->adjustSelection(selState, _mouseSelectType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!selState.empty()) {
|
||||||
|
// We started selecting text in web page preview.
|
||||||
|
ClickHandler::unpressed();
|
||||||
|
}
|
||||||
if (_selected[_mouseActionItem] != selState) {
|
if (_selected[_mouseActionItem] != selState) {
|
||||||
_selected[_mouseActionItem] = selState;
|
_selected[_mouseActionItem] = selState;
|
||||||
repaintItem(_mouseActionItem);
|
repaintItem(_mouseActionItem);
|
||||||
|
|
|
@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/widgets/scroll_area.h"
|
#include "ui/widgets/scroll_area.h"
|
||||||
#include "history/view/history_view_top_bar_widget.h"
|
#include "history/view/history_view_top_bar_widget.h"
|
||||||
|
|
||||||
|
#include <QtGui/QPainterPath>
|
||||||
|
|
||||||
struct ClickContext;
|
struct ClickContext;
|
||||||
struct ClickHandlerContext;
|
struct ClickHandlerContext;
|
||||||
|
|
||||||
|
@ -33,6 +35,7 @@ class EmptyPainter;
|
||||||
class Element;
|
class Element;
|
||||||
class TranslateTracker;
|
class TranslateTracker;
|
||||||
struct PinnedId;
|
struct PinnedId;
|
||||||
|
struct SelectedQuote;
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
||||||
namespace HistoryView::Reactions {
|
namespace HistoryView::Reactions {
|
||||||
|
@ -136,8 +139,6 @@ public:
|
||||||
int from,
|
int from,
|
||||||
int till) const;
|
int till) const;
|
||||||
void elementStartStickerLoop(not_null<const Element*> view);
|
void elementStartStickerLoop(not_null<const Element*> view);
|
||||||
[[nodiscard]] float64 elementHighlightOpacity(
|
|
||||||
not_null<const HistoryItem*> item) const;
|
|
||||||
void elementShowPollResults(
|
void elementShowPollResults(
|
||||||
not_null<PollData*> poll,
|
not_null<PollData*> poll,
|
||||||
FullMsgId context);
|
FullMsgId context);
|
||||||
|
@ -314,7 +315,7 @@ private:
|
||||||
|
|
||||||
QPoint mapPointToItem(QPoint p, const Element *view) const;
|
QPoint mapPointToItem(QPoint p, const Element *view) const;
|
||||||
QPoint mapPointToItem(QPoint p, const HistoryItem *item) const;
|
QPoint mapPointToItem(QPoint p, const HistoryItem *item) const;
|
||||||
[[nodiscard]] TextWithEntities selectedQuote(
|
[[nodiscard]] HistoryView::SelectedQuote selectedQuote(
|
||||||
not_null<HistoryItem*> item) const;
|
not_null<HistoryItem*> item) const;
|
||||||
|
|
||||||
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
|
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
|
||||||
|
@ -462,6 +463,7 @@ private:
|
||||||
std::optional<Ui::ReportReason> _chooseForReportReason;
|
std::optional<Ui::ReportReason> _chooseForReportReason;
|
||||||
|
|
||||||
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||||
|
QPainterPath _highlightPathCache;
|
||||||
bool _isChatWide = false;
|
bool _isChatWide = false;
|
||||||
|
|
||||||
base::flat_set<not_null<const HistoryItem*>> _animatedStickersPlayed;
|
base::flat_set<not_null<const HistoryItem*>> _animatedStickersPlayed;
|
||||||
|
|
|
@ -470,6 +470,14 @@ HistoryItem::HistoryItem(
|
||||||
const auto dropForwardInfo = original->computeDropForwardedInfo();
|
const auto dropForwardInfo = original->computeDropForwardedInfo();
|
||||||
config.reply.messageId = config.reply.topMessageId = topicRootId;
|
config.reply.messageId = config.reply.topMessageId = topicRootId;
|
||||||
config.reply.topicPost = (topicRootId != 0);
|
config.reply.topicPost = (topicRootId != 0);
|
||||||
|
if (const auto originalReply = original->Get<HistoryMessageReply>()) {
|
||||||
|
if (originalReply->external()) {
|
||||||
|
config.reply = originalReply->fields();
|
||||||
|
if (!config.reply.externalPeerId) {
|
||||||
|
config.reply.messageId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!dropForwardInfo) {
|
if (!dropForwardInfo) {
|
||||||
config.originalDate = original->originalDate();
|
config.originalDate = original->originalDate();
|
||||||
if (const auto info = original->hiddenSenderInfo()) {
|
if (const auto info = original->hiddenSenderInfo()) {
|
||||||
|
@ -3389,16 +3397,28 @@ void HistoryItem::createComponentsHelper(
|
||||||
? replyTo.messageId.peer
|
? replyTo.messageId.peer
|
||||||
: PeerId();
|
: PeerId();
|
||||||
const auto to = LookupReplyTo(_history, replyTo.messageId);
|
const auto to = LookupReplyTo(_history, replyTo.messageId);
|
||||||
const auto replyToTop = LookupReplyToTop(_history, to);
|
const auto replyToTop = replyTo.topicRootId
|
||||||
|
? replyTo.topicRootId
|
||||||
|
: LookupReplyToTop(_history, to);
|
||||||
config.reply.topMessageId = replyToTop
|
config.reply.topMessageId = replyToTop
|
||||||
? replyToTop
|
? replyToTop
|
||||||
: (replyTo.messageId.peer == history()->peer->id)
|
: (replyTo.messageId.peer == history()->peer->id)
|
||||||
? replyTo.messageId.msg
|
? replyTo.messageId.msg
|
||||||
: MsgId();
|
: MsgId();
|
||||||
|
if (!config.reply.externalPeerId
|
||||||
|
&& to
|
||||||
|
&& config.reply.topicPost
|
||||||
|
&& replyTo.topicRootId != to->topicRootId()) {
|
||||||
|
config.reply.externalPeerId = replyTo.messageId.peer;
|
||||||
|
}
|
||||||
const auto forum = _history->asForum();
|
const auto forum = _history->asForum();
|
||||||
config.reply.topicPost = LookupReplyIsTopicPost(to)
|
config.reply.topicPost = config.reply.externalPeerId
|
||||||
|| (to && to->Has<HistoryServiceTopicInfo>())
|
? (replyTo.topicRootId
|
||||||
|| (forum && forum->creating(config.reply.topMessageId));
|
&& (replyTo.topicRootId != Data::ForumTopic::kGeneralId))
|
||||||
|
: (LookupReplyIsTopicPost(to)
|
||||||
|
|| (to && to->Has<HistoryServiceTopicInfo>())
|
||||||
|
|| (forum && forum->creating(config.reply.topMessageId)));
|
||||||
|
config.reply.manualQuote = !replyTo.quote.empty();
|
||||||
config.reply.quote = std::move(replyTo.quote);
|
config.reply.quote = std::move(replyTo.quote);
|
||||||
}
|
}
|
||||||
config.markup = std::move(markup);
|
config.markup = std::move(markup);
|
||||||
|
|
|
@ -397,9 +397,6 @@ ReplyFields ReplyFieldsFromMTP(
|
||||||
auto result = ReplyFields();
|
auto result = ReplyFields();
|
||||||
if (const auto peer = data.vreply_to_peer_id()) {
|
if (const auto peer = data.vreply_to_peer_id()) {
|
||||||
result.externalPeerId = peerFromMTP(*peer);
|
result.externalPeerId = peerFromMTP(*peer);
|
||||||
if (result.externalPeerId == history->peer->id) {
|
|
||||||
result.externalPeerId = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const auto owner = &history->owner();
|
const auto owner = &history->owner();
|
||||||
if (const auto id = data.vreply_to_msg_id().value_or_empty()) {
|
if (const auto id = data.vreply_to_msg_id().value_or_empty()) {
|
||||||
|
@ -426,6 +423,7 @@ ReplyFields ReplyFieldsFromMTP(
|
||||||
&owner->session(),
|
&owner->session(),
|
||||||
data.vquote_entities().value_or_empty()),
|
data.vquote_entities().value_or_empty()),
|
||||||
};
|
};
|
||||||
|
result.manualQuote = data.is_quote();
|
||||||
return result;
|
return result;
|
||||||
}, [&](const MTPDmessageReplyStoryHeader &data) {
|
}, [&](const MTPDmessageReplyStoryHeader &data) {
|
||||||
return ReplyFields{
|
return ReplyFields{
|
||||||
|
@ -525,8 +523,7 @@ bool HistoryMessageReply::updateData(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto external = _fields.externalSenderId
|
const auto external = this->external();
|
||||||
|| !_fields.externalSenderName.isEmpty();
|
|
||||||
if (resolvedMessage
|
if (resolvedMessage
|
||||||
|| resolvedStory
|
|| resolvedStory
|
||||||
|| (external && (!_fields.messageId || force))) {
|
|| (external && (!_fields.messageId || force))) {
|
||||||
|
@ -624,13 +621,15 @@ void HistoryMessageReply::setLinkFrom(
|
||||||
if (externalPeerId) {
|
if (externalPeerId) {
|
||||||
controller->showPeerInfo(
|
controller->showPeerInfo(
|
||||||
controller->session().data().peer(externalPeerId));
|
controller->session().data().peer(externalPeerId));
|
||||||
} else {
|
|
||||||
controller->showToast(u"External reply"_q);
|
|
||||||
}
|
}
|
||||||
|
controller->showToast(tr::lng_reply_from_private_chat(tr::now));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_link = resolvedMessage
|
_link = resolvedMessage
|
||||||
? JumpToMessageClickHandler(resolvedMessage.get(), holder->fullId())
|
? JumpToMessageClickHandler(
|
||||||
|
resolvedMessage.get(),
|
||||||
|
holder->fullId(),
|
||||||
|
_fields.manualQuote ? _fields.quote : TextWithEntities())
|
||||||
: resolvedStory
|
: resolvedStory
|
||||||
? JumpToStoryClickHandler(resolvedStory.get())
|
? JumpToStoryClickHandler(resolvedStory.get())
|
||||||
: (external && !_fields.messageId)
|
: (external && !_fields.messageId)
|
||||||
|
@ -656,10 +655,18 @@ void HistoryMessageReply::clearData(not_null<HistoryItem*> holder) {
|
||||||
resolvedStory.get());
|
resolvedStory.get());
|
||||||
resolvedStory = nullptr;
|
resolvedStory = nullptr;
|
||||||
}
|
}
|
||||||
|
_name.clear();
|
||||||
|
_text.clear();
|
||||||
_unavailable = 1;
|
_unavailable = 1;
|
||||||
refreshReplyToMedia();
|
refreshReplyToMedia();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HistoryMessageReply::external() const {
|
||||||
|
return _fields.externalPeerId
|
||||||
|
|| _fields.externalSenderId
|
||||||
|
|| !_fields.externalSenderName.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
PeerData *HistoryMessageReply::sender(not_null<HistoryItem*> holder) const {
|
PeerData *HistoryMessageReply::sender(not_null<HistoryItem*> holder) const {
|
||||||
if (resolvedStory) {
|
if (resolvedStory) {
|
||||||
return resolvedStory->peer();
|
return resolvedStory->peer();
|
||||||
|
@ -832,7 +839,7 @@ void HistoryMessageReply::paint(
|
||||||
|
|
||||||
y += st::historyReplyTop;
|
y += st::historyReplyTop;
|
||||||
const auto rect = QRect(x, y, w, _height);
|
const auto rect = QRect(x, y, w, _height);
|
||||||
const auto hasQuote = !_fields.quote.empty();
|
const auto hasQuote = _fields.manualQuote && !_fields.quote.empty();
|
||||||
const auto selected = context.selected();
|
const auto selected = context.selected();
|
||||||
const auto colorPeer = resolvedMessage
|
const auto colorPeer = resolvedMessage
|
||||||
? resolvedMessage->displayFrom()
|
? resolvedMessage->displayFrom()
|
||||||
|
|
|
@ -239,6 +239,7 @@ struct ReplyFields {
|
||||||
MsgId topMessageId = 0;
|
MsgId topMessageId = 0;
|
||||||
StoryId storyId = 0;
|
StoryId storyId = 0;
|
||||||
bool topicPost = false;
|
bool topicPost = false;
|
||||||
|
bool manualQuote = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] ReplyFields ReplyFieldsFromMTP(
|
[[nodiscard]] ReplyFields ReplyFieldsFromMTP(
|
||||||
|
@ -273,6 +274,7 @@ struct HistoryMessageReply
|
||||||
// Must be called before destructor.
|
// Must be called before destructor.
|
||||||
void clearData(not_null<HistoryItem*> holder);
|
void clearData(not_null<HistoryItem*> holder);
|
||||||
|
|
||||||
|
[[nodiscard]] bool external() const;
|
||||||
[[nodiscard]] PeerData *sender(not_null<HistoryItem*> holder) const;
|
[[nodiscard]] PeerData *sender(not_null<HistoryItem*> holder) const;
|
||||||
[[nodiscard]] QString senderName(not_null<HistoryItem*> holder) const;
|
[[nodiscard]] QString senderName(not_null<HistoryItem*> holder) const;
|
||||||
[[nodiscard]] QString senderName(not_null<PeerData*> peer) const;
|
[[nodiscard]] QString senderName(not_null<PeerData*> peer) const;
|
||||||
|
@ -300,6 +302,9 @@ struct HistoryMessageReply
|
||||||
bool inBubble) const;
|
bool inBubble) const;
|
||||||
void unloadPersistentAnimation();
|
void unloadPersistentAnimation();
|
||||||
|
|
||||||
|
[[nodiscard]] ReplyFields fields() const {
|
||||||
|
return _fields;
|
||||||
|
}
|
||||||
[[nodiscard]] PeerId externalPeerId() const {
|
[[nodiscard]] PeerId externalPeerId() const {
|
||||||
return _fields.externalPeerId;
|
return _fields.externalPeerId;
|
||||||
}
|
}
|
||||||
|
@ -321,6 +326,9 @@ struct HistoryMessageReply
|
||||||
[[nodiscard]] bool topicPost() const {
|
[[nodiscard]] bool topicPost() const {
|
||||||
return _fields.topicPost;
|
return _fields.topicPost;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] bool manualQuote() const {
|
||||||
|
return _fields.manualQuote;
|
||||||
|
}
|
||||||
[[nodiscard]] QString statePhrase() const;
|
[[nodiscard]] QString statePhrase() const;
|
||||||
|
|
||||||
void setLinkFrom(not_null<HistoryItem*> holder);
|
void setLinkFrom(not_null<HistoryItem*> holder);
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "history/history_item_helpers.h"
|
#include "history/history_item_helpers.h"
|
||||||
|
|
||||||
|
#include "api/api_text_entities.h"
|
||||||
#include "calls/calls_instance.h"
|
#include "calls/calls_instance.h"
|
||||||
#include "data/notify/data_notify_settings.h"
|
#include "data/notify/data_notify_settings.h"
|
||||||
#include "data/data_chat_participant_status.h"
|
#include "data/data_chat_participant_status.h"
|
||||||
|
@ -202,6 +203,14 @@ MsgId LookupReplyToTop(not_null<History*> history, HistoryItem *replyTo) {
|
||||||
: 0;
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MsgId LookupReplyToTop(not_null<History*> history, FullReplyTo replyTo) {
|
||||||
|
return replyTo.topicRootId
|
||||||
|
? replyTo.topicRootId
|
||||||
|
: LookupReplyToTop(
|
||||||
|
history,
|
||||||
|
LookupReplyTo(history, replyTo.messageId));
|
||||||
|
}
|
||||||
|
|
||||||
bool LookupReplyIsTopicPost(HistoryItem *replyTo) {
|
bool LookupReplyIsTopicPost(HistoryItem *replyTo) {
|
||||||
return replyTo
|
return replyTo
|
||||||
&& (replyTo->topicRootId() != Data::ForumTopic::kGeneralId);
|
&& (replyTo->topicRootId() != Data::ForumTopic::kGeneralId);
|
||||||
|
@ -259,17 +268,20 @@ bool IsItemScheduledUntilOnline(not_null<const HistoryItem*> item) {
|
||||||
|
|
||||||
ClickHandlerPtr JumpToMessageClickHandler(
|
ClickHandlerPtr JumpToMessageClickHandler(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
FullMsgId returnToId) {
|
FullMsgId returnToId,
|
||||||
|
TextWithEntities highlightPart) {
|
||||||
return JumpToMessageClickHandler(
|
return JumpToMessageClickHandler(
|
||||||
item->history()->peer,
|
item->history()->peer,
|
||||||
item->id,
|
item->id,
|
||||||
returnToId);
|
returnToId,
|
||||||
|
std::move(highlightPart));
|
||||||
}
|
}
|
||||||
|
|
||||||
ClickHandlerPtr JumpToMessageClickHandler(
|
ClickHandlerPtr JumpToMessageClickHandler(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
MsgId msgId,
|
MsgId msgId,
|
||||||
FullMsgId returnToId) {
|
FullMsgId returnToId,
|
||||||
|
TextWithEntities highlightPart) {
|
||||||
return std::make_shared<LambdaClickHandler>([=] {
|
return std::make_shared<LambdaClickHandler>([=] {
|
||||||
const auto separate = Core::App().separateWindowForPeer(peer);
|
const auto separate = Core::App().separateWindowForPeer(peer);
|
||||||
const auto controller = separate
|
const auto controller = separate
|
||||||
|
@ -279,6 +291,7 @@ ClickHandlerPtr JumpToMessageClickHandler(
|
||||||
auto params = Window::SectionShow{
|
auto params = Window::SectionShow{
|
||||||
Window::SectionShow::Way::Forward
|
Window::SectionShow::Way::Forward
|
||||||
};
|
};
|
||||||
|
params.highlightPart = highlightPart;
|
||||||
params.origin = Window::SectionShow::OriginMessage{
|
params.origin = Window::SectionShow::OriginMessage{
|
||||||
returnToId
|
returnToId
|
||||||
};
|
};
|
||||||
|
@ -369,19 +382,27 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) {
|
||||||
const auto externalPeerId = (replyTo.messageId.peer == historyPeer)
|
const auto externalPeerId = (replyTo.messageId.peer == historyPeer)
|
||||||
? PeerId()
|
? PeerId()
|
||||||
: replyTo.messageId.peer;
|
: replyTo.messageId.peer;
|
||||||
const auto to = LookupReplyTo(action.history, replyTo.messageId);
|
const auto replyToTop = LookupReplyToTop(action.history, replyTo);
|
||||||
const auto replyToTop = LookupReplyToTop(action.history, to);
|
auto quoteEntities = Api::EntitiesToMTP(
|
||||||
|
&action.history->session(),
|
||||||
|
replyTo.quote.entities,
|
||||||
|
Api::ConvertOption::SkipLocal);
|
||||||
return MTP_messageReplyHeader(
|
return MTP_messageReplyHeader(
|
||||||
MTP_flags(Flag::f_reply_to_msg_id
|
MTP_flags(Flag::f_reply_to_msg_id
|
||||||
| (replyToTop ? Flag::f_reply_to_top_id : Flag())
|
| (replyToTop ? Flag::f_reply_to_top_id : Flag())
|
||||||
| (externalPeerId ? Flag::f_reply_to_peer_id : Flag())),
|
| (externalPeerId ? Flag::f_reply_to_peer_id : Flag())
|
||||||
|
| (replyTo.quote.empty() ? Flag() : Flag::f_quote)
|
||||||
|
| (replyTo.quote.empty() ? Flag() : Flag::f_quote_text)
|
||||||
|
| (quoteEntities.v.empty()
|
||||||
|
? Flag()
|
||||||
|
: Flag::f_quote_entities)),
|
||||||
MTP_int(replyTo.messageId.msg),
|
MTP_int(replyTo.messageId.msg),
|
||||||
peerToMTP(externalPeerId),
|
peerToMTP(externalPeerId),
|
||||||
MTPMessageFwdHeader(), // reply_from
|
MTPMessageFwdHeader(), // reply_from
|
||||||
MTPMessageMedia(), // reply_media
|
MTPMessageMedia(), // reply_media
|
||||||
MTP_int(replyToTop), // reply_to_top_id
|
MTP_int(replyToTop),
|
||||||
MTPstring(), // quote_text
|
MTP_string(replyTo.quote.text),
|
||||||
MTPVector<MTPMessageEntity>()); // quote_entities
|
quoteEntities);
|
||||||
}
|
}
|
||||||
return MTPMessageReplyHeader();
|
return MTPMessageReplyHeader();
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,9 @@ void RequestDependentMessageStory(
|
||||||
[[nodiscard]] MsgId LookupReplyToTop(
|
[[nodiscard]] MsgId LookupReplyToTop(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
HistoryItem *replyTo);
|
HistoryItem *replyTo);
|
||||||
|
[[nodiscard]] MsgId LookupReplyToTop(
|
||||||
|
not_null<History*> history,
|
||||||
|
FullReplyTo replyTo);
|
||||||
[[nodiscard]] bool LookupReplyIsTopicPost(HistoryItem *replyTo);
|
[[nodiscard]] bool LookupReplyIsTopicPost(HistoryItem *replyTo);
|
||||||
|
|
||||||
struct SendingErrorRequest {
|
struct SendingErrorRequest {
|
||||||
|
@ -120,10 +123,12 @@ struct SendingErrorRequest {
|
||||||
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
|
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
MsgId msgId,
|
MsgId msgId,
|
||||||
FullMsgId returnToId = FullMsgId());
|
FullMsgId returnToId = FullMsgId(),
|
||||||
|
TextWithEntities highlightPart = {});
|
||||||
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
|
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
FullMsgId returnToId = FullMsgId());
|
FullMsgId returnToId = FullMsgId(),
|
||||||
|
TextWithEntities highlightPart = {});
|
||||||
[[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler(
|
[[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler(
|
||||||
not_null<Data::Story*> story);
|
not_null<Data::Story*> story);
|
||||||
ClickHandlerPtr JumpToStoryClickHandler(
|
ClickHandlerPtr JumpToStoryClickHandler(
|
||||||
|
|
|
@ -8,14 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history_view_highlight_manager.h"
|
#include "history/history_view_highlight_manager.h"
|
||||||
|
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "history/history.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
|
#include "ui/chat/chat_style.h"
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
constexpr auto kAnimationFirstPart = st::activeFadeInDuration
|
|
||||||
/ float64(st::activeFadeInDuration + st::activeFadeOutDuration);
|
|
||||||
|
|
||||||
ElementHighlighter::ElementHighlighter(
|
ElementHighlighter::ElementHighlighter(
|
||||||
not_null<Data::Session*> data,
|
not_null<Data::Session*> data,
|
||||||
ViewForItem viewForItem,
|
ViewForItem viewForItem,
|
||||||
|
@ -26,63 +25,95 @@ ElementHighlighter::ElementHighlighter(
|
||||||
, _animation(*this) {
|
, _animation(*this) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ElementHighlighter::enqueue(not_null<Element*> view) {
|
void ElementHighlighter::enqueue(
|
||||||
const auto item = view->data();
|
not_null<Element*> view,
|
||||||
const auto fullId = item->fullId();
|
const TextWithEntities &part) {
|
||||||
|
const auto data = computeHighlight(view, part);
|
||||||
if (_queue.empty() && !_animation.animating()) {
|
if (_queue.empty() && !_animation.animating()) {
|
||||||
highlight(fullId);
|
highlight(data);
|
||||||
} else if (_highlightedMessageId != fullId
|
} else if (_highlighted != data && !base::contains(_queue, data)) {
|
||||||
&& !base::contains(_queue, fullId)) {
|
_queue.push_back(data);
|
||||||
_queue.push_back(fullId);
|
|
||||||
checkNextHighlight();
|
checkNextHighlight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ElementHighlighter::highlight(
|
||||||
|
not_null<Element*> view,
|
||||||
|
const TextWithEntities &part) {
|
||||||
|
highlight(computeHighlight(view, part));
|
||||||
|
}
|
||||||
|
|
||||||
void ElementHighlighter::checkNextHighlight() {
|
void ElementHighlighter::checkNextHighlight() {
|
||||||
if (_animation.animating()) {
|
if (_animation.animating()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto nextHighlight = [&] {
|
const auto next = [&] {
|
||||||
while (!_queue.empty()) {
|
while (!_queue.empty()) {
|
||||||
const auto fullId = _queue.front();
|
const auto highlight = _queue.front();
|
||||||
_queue.pop_front();
|
_queue.pop_front();
|
||||||
if (const auto item = _data->message(fullId)) {
|
if (const auto item = _data->message(highlight.itemId)) {
|
||||||
if (_viewForItem(item)) {
|
if (_viewForItem(item)) {
|
||||||
return fullId;
|
return highlight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return FullMsgId();
|
return Highlight();
|
||||||
}();
|
}();
|
||||||
if (!nextHighlight) {
|
if (next) {
|
||||||
return;
|
highlight(next);
|
||||||
}
|
}
|
||||||
highlight(nextHighlight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float64 ElementHighlighter::progress(
|
Ui::ChatPaintHighlight ElementHighlighter::state(
|
||||||
not_null<const HistoryItem*> item) const {
|
not_null<const HistoryItem*> item) const {
|
||||||
if (item->fullId() == _highlightedMessageId) {
|
if (item->fullId() == _highlighted.itemId) {
|
||||||
const auto progress = _animation.progress();
|
auto result = _animation.state();
|
||||||
return std::min(progress / kAnimationFirstPart, 1.)
|
result.range = _highlighted.part;
|
||||||
- ((progress - kAnimationFirstPart) / (1. - kAnimationFirstPart));
|
return result;
|
||||||
}
|
}
|
||||||
return 0.;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void ElementHighlighter::highlight(FullMsgId itemId) {
|
ElementHighlighter::Highlight ElementHighlighter::computeHighlight(
|
||||||
if (const auto item = _data->message(itemId)) {
|
not_null<const Element*> view,
|
||||||
|
const TextWithEntities &part) {
|
||||||
|
const auto item = view->data();
|
||||||
|
const auto owner = &item->history()->owner();
|
||||||
|
if (const auto group = owner->groups().find(item)) {
|
||||||
|
const auto leader = group->items.front();
|
||||||
|
const auto leaderId = leader->fullId();
|
||||||
|
const auto i = ranges::find(group->items, item);
|
||||||
|
if (i != end(group->items)) {
|
||||||
|
const auto index = int(i - begin(group->items));
|
||||||
|
if (part.empty()) {
|
||||||
|
return { leaderId, AddGroupItemSelection({}, index) };
|
||||||
|
} else if (const auto leaderView = _viewForItem(leader)) {
|
||||||
|
return {
|
||||||
|
leaderId,
|
||||||
|
leaderView->selectionFromQuote(item, part),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { leaderId };
|
||||||
|
} else if (part.empty()) {
|
||||||
|
return { item->fullId() };
|
||||||
|
}
|
||||||
|
return { item->fullId(), view->selectionFromQuote(item, part) };
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElementHighlighter::highlight(Highlight data) {
|
||||||
|
if (const auto item = _data->message(data.itemId)) {
|
||||||
if (const auto view = _viewForItem(item)) {
|
if (const auto view = _viewForItem(item)) {
|
||||||
if (_highlightedMessageId
|
if (_highlighted && _highlighted.itemId != data.itemId) {
|
||||||
&& _highlightedMessageId != itemId) {
|
if (const auto was = _data->message(_highlighted.itemId)) {
|
||||||
if (const auto was = _data->message(_highlightedMessageId)) {
|
|
||||||
if (const auto view = _viewForItem(was)) {
|
if (const auto view = _viewForItem(was)) {
|
||||||
repaintHighlightedItem(view);
|
repaintHighlightedItem(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_highlightedMessageId = itemId;
|
_highlighted = data;
|
||||||
_animation.start();
|
_animation.start(!data.part.empty()
|
||||||
|
&& !IsSubGroupSelection(data.part));
|
||||||
|
|
||||||
repaintHighlightedItem(view);
|
repaintHighlightedItem(view);
|
||||||
}
|
}
|
||||||
|
@ -105,7 +136,7 @@ void ElementHighlighter::repaintHighlightedItem(
|
||||||
}
|
}
|
||||||
|
|
||||||
void ElementHighlighter::updateMessage() {
|
void ElementHighlighter::updateMessage() {
|
||||||
if (const auto item = _data->message(_highlightedMessageId)) {
|
if (const auto item = _data->message(_highlighted.itemId)) {
|
||||||
if (const auto view = _viewForItem(item)) {
|
if (const auto view = _viewForItem(item)) {
|
||||||
repaintHighlightedItem(view);
|
repaintHighlightedItem(view);
|
||||||
}
|
}
|
||||||
|
@ -114,7 +145,7 @@ void ElementHighlighter::updateMessage() {
|
||||||
|
|
||||||
void ElementHighlighter::clear() {
|
void ElementHighlighter::clear() {
|
||||||
_animation.cancel();
|
_animation.cancel();
|
||||||
_highlightedMessageId = FullMsgId();
|
_highlighted = {};
|
||||||
_lastHighlightedMessageId = FullMsgId();
|
_lastHighlightedMessageId = FullMsgId();
|
||||||
_queue.clear();
|
_queue.clear();
|
||||||
}
|
}
|
||||||
|
@ -125,60 +156,117 @@ ElementHighlighter::AnimationManager::AnimationManager(
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ElementHighlighter::AnimationManager::animating() const {
|
bool ElementHighlighter::AnimationManager::animating() const {
|
||||||
if (anim::Disabled()) {
|
if (_timer && _timer->isActive()) {
|
||||||
return (_timer && _timer->isActive());
|
return true;
|
||||||
} else {
|
} else if (!anim::Disabled()) {
|
||||||
return _simple.animating();
|
return _simple.animating();
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float64 ElementHighlighter::AnimationManager::progress() const {
|
Ui::ChatPaintHighlight ElementHighlighter::AnimationManager::state() const {
|
||||||
if (anim::Disabled()) {
|
if (anim::Disabled()) {
|
||||||
return (_timer && _timer->isActive()) ? kAnimationFirstPart : 0.;
|
return {
|
||||||
} else {
|
.opacity = !_timer ? 0. : 1.,
|
||||||
return _simple.value(0.);
|
.collapsion = !_timer ? 0. : _fadingOut ? 1. : 0.,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
.opacity = ((!_fadingOut && _collapsing)
|
||||||
|
? 1.
|
||||||
|
: _simple.value(_fadingOut ? 0. : 1.)),
|
||||||
|
.collapsion = ((!_withTextPart || !_collapsing)
|
||||||
|
? 0.
|
||||||
|
: _fadingOut
|
||||||
|
? 1.
|
||||||
|
: _simple.value(1.)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
MsgId ElementHighlighter::latestSingleHighlightedMsgId() const {
|
MsgId ElementHighlighter::latestSingleHighlightedMsgId() const {
|
||||||
return _highlightedMessageId
|
return _highlighted.itemId
|
||||||
? _highlightedMessageId.msg
|
? _highlighted.itemId.msg
|
||||||
: _lastHighlightedMessageId.msg;
|
: _lastHighlightedMessageId.msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ElementHighlighter::AnimationManager::start() {
|
void ElementHighlighter::AnimationManager::start(bool withTextPart) {
|
||||||
|
_withTextPart = withTextPart;
|
||||||
const auto finish = [=] {
|
const auto finish = [=] {
|
||||||
cancel();
|
cancel();
|
||||||
_parent._lastHighlightedMessageId = base::take(
|
_parent._lastHighlightedMessageId = base::take(
|
||||||
_parent._highlightedMessageId);
|
_parent._highlighted.itemId);
|
||||||
_parent.checkNextHighlight();
|
_parent.checkNextHighlight();
|
||||||
};
|
};
|
||||||
cancel();
|
cancel();
|
||||||
if (anim::Disabled()) {
|
if (anim::Disabled()) {
|
||||||
_timer.emplace([=] {
|
_timer.emplace([=] {
|
||||||
_parent.updateMessage();
|
_parent.updateMessage();
|
||||||
finish();
|
if (_withTextPart && !_fadingOut) {
|
||||||
|
_fadingOut = true;
|
||||||
|
_timer->callOnce(st::activeFadeOutDuration);
|
||||||
|
} else {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
_timer->callOnce(st::activeFadeOutDuration);
|
_timer->callOnce(_withTextPart
|
||||||
|
? st::activeFadeInDuration
|
||||||
|
: st::activeFadeOutDuration);
|
||||||
_parent.updateMessage();
|
_parent.updateMessage();
|
||||||
} else {
|
} else {
|
||||||
const auto to = 1.;
|
|
||||||
_simple.start(
|
_simple.start(
|
||||||
[=](float64 value) {
|
[=](float64 value) {
|
||||||
_parent.updateMessage();
|
_parent.updateMessage();
|
||||||
if (value == to) {
|
if (value == 1.) {
|
||||||
finish();
|
if (_withTextPart) {
|
||||||
|
_timer.emplace([=] {
|
||||||
|
_parent.updateMessage();
|
||||||
|
if (_collapsing) {
|
||||||
|
_fadingOut = true;
|
||||||
|
} else {
|
||||||
|
_collapsing = true;
|
||||||
|
}
|
||||||
|
_simple.start([=](float64 value) {
|
||||||
|
_parent.updateMessage();
|
||||||
|
if (_fadingOut && value == 0.) {
|
||||||
|
finish();
|
||||||
|
} else if (!_fadingOut && value == 1.) {
|
||||||
|
_timer->callOnce(
|
||||||
|
st::activeFadeOutDuration);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_fadingOut ? 1. : 0.,
|
||||||
|
_fadingOut ? 0. : 1.,
|
||||||
|
(_fadingOut
|
||||||
|
? st::activeFadeInDuration
|
||||||
|
: st::fadeWrapDuration));
|
||||||
|
});
|
||||||
|
_timer->callOnce(st::activeFadeInDuration);
|
||||||
|
} else {
|
||||||
|
_fadingOut = true;
|
||||||
|
_simple.start([=](float64 value) {
|
||||||
|
_parent.updateMessage();
|
||||||
|
if (value == 0.) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
1.,
|
||||||
|
0.,
|
||||||
|
st::activeFadeOutDuration);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
0.,
|
0.,
|
||||||
to,
|
1.,
|
||||||
st::activeFadeInDuration + st::activeFadeOutDuration);
|
st::activeFadeInDuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ElementHighlighter::AnimationManager::cancel() {
|
void ElementHighlighter::AnimationManager::cancel() {
|
||||||
_simple.stop();
|
_simple.stop();
|
||||||
_timer.reset();
|
_timer.reset();
|
||||||
|
_fadingOut = false;
|
||||||
|
_collapsed = false;
|
||||||
|
_collapsing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
|
@ -16,6 +16,10 @@ namespace Data {
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
struct ChatPaintHighlight;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
class Element;
|
class Element;
|
||||||
|
@ -29,40 +33,59 @@ public:
|
||||||
ViewForItem viewForItem,
|
ViewForItem viewForItem,
|
||||||
RepaintView repaintView);
|
RepaintView repaintView);
|
||||||
|
|
||||||
void enqueue(not_null<Element*> view);
|
void enqueue(not_null<Element*> view, const TextWithEntities &part);
|
||||||
void highlight(FullMsgId itemId);
|
void highlight(not_null<Element*> view, const TextWithEntities &part);
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
[[nodiscard]] float64 progress(not_null<const HistoryItem*> item) const;
|
[[nodiscard]] Ui::ChatPaintHighlight state(
|
||||||
|
not_null<const HistoryItem*> item) const;
|
||||||
[[nodiscard]] MsgId latestSingleHighlightedMsgId() const;
|
[[nodiscard]] MsgId latestSingleHighlightedMsgId() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void checkNextHighlight();
|
|
||||||
void repaintHighlightedItem(not_null<const Element*> view);
|
|
||||||
void updateMessage();
|
|
||||||
|
|
||||||
class AnimationManager final {
|
class AnimationManager final {
|
||||||
public:
|
public:
|
||||||
AnimationManager(ElementHighlighter &parent);
|
AnimationManager(ElementHighlighter &parent);
|
||||||
[[nodiscard]] bool animating() const;
|
[[nodiscard]] bool animating() const;
|
||||||
[[nodiscard]] float64 progress() const;
|
[[nodiscard]] Ui::ChatPaintHighlight state() const;
|
||||||
void start();
|
void start(bool withTextPart);
|
||||||
void cancel();
|
void cancel();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ElementHighlighter &_parent;
|
ElementHighlighter &_parent;
|
||||||
Ui::Animations::Simple _simple;
|
Ui::Animations::Simple _simple;
|
||||||
std::optional<base::Timer> _timer;
|
std::optional<base::Timer> _timer;
|
||||||
|
bool _withTextPart = false;
|
||||||
|
bool _collapsing = false;
|
||||||
|
bool _collapsed = false;
|
||||||
|
bool _fadingOut = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Highlight {
|
||||||
|
FullMsgId itemId;
|
||||||
|
TextSelection part;
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return itemId.operator bool();
|
||||||
|
}
|
||||||
|
friend inline bool operator==(Highlight, Highlight) = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] Highlight computeHighlight(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
const TextWithEntities &part);
|
||||||
|
void highlight(Highlight data);
|
||||||
|
void checkNextHighlight();
|
||||||
|
void repaintHighlightedItem(not_null<const Element*> view);
|
||||||
|
void updateMessage();
|
||||||
|
|
||||||
const not_null<Data::Session*> _data;
|
const not_null<Data::Session*> _data;
|
||||||
const ViewForItem _viewForItem;
|
const ViewForItem _viewForItem;
|
||||||
const RepaintView _repaintView;
|
const RepaintView _repaintView;
|
||||||
|
|
||||||
FullMsgId _highlightedMessageId;
|
Highlight _highlighted;
|
||||||
FullMsgId _lastHighlightedMessageId;
|
FullMsgId _lastHighlightedMessageId;
|
||||||
std::deque<FullMsgId> _queue;
|
std::deque<Highlight> _queue;
|
||||||
|
|
||||||
AnimationManager _animation;
|
AnimationManager _animation;
|
||||||
|
|
||||||
|
|
|
@ -847,7 +847,9 @@ HistoryWidget::HistoryWidget(
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
fastShowAtEnd(action.history);
|
fastShowAtEnd(action.history);
|
||||||
if (cancelReply(lastKeyboardUsed) && !action.clearDraft) {
|
if (!_justMarkingAsRead
|
||||||
|
&& cancelReply(lastKeyboardUsed)
|
||||||
|
&& !action.clearDraft) {
|
||||||
saveCloudDraft();
|
saveCloudDraft();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1077,7 +1079,7 @@ void HistoryWidget::initTabbedSelector() {
|
||||||
if (!data.recipientOverride) {
|
if (!data.recipientOverride) {
|
||||||
return true;
|
return true;
|
||||||
} else if (data.recipientOverride != _peer) {
|
} else if (data.recipientOverride != _peer) {
|
||||||
showHistory(data.recipientOverride->id, ShowAtTheEndMsgId);
|
showHistory(data.recipientOverride->id, ShowAtTheEndMsgId, {});
|
||||||
}
|
}
|
||||||
return (data.recipientOverride == _peer);
|
return (data.recipientOverride == _peer);
|
||||||
}) | rpl::start_with_next([=](ChatHelpers::InlineChosen data) {
|
}) | rpl::start_with_next([=](ChatHelpers::InlineChosen data) {
|
||||||
|
@ -1272,13 +1274,14 @@ void HistoryWidget::scrollToAnimationCallback(
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::enqueueMessageHighlight(
|
void HistoryWidget::enqueueMessageHighlight(
|
||||||
not_null<HistoryView::Element*> view) {
|
not_null<HistoryView::Element*> view,
|
||||||
_highlighter.enqueue(view);
|
const TextWithEntities &part) {
|
||||||
|
_highlighter.enqueue(view, part);
|
||||||
}
|
}
|
||||||
|
|
||||||
float64 HistoryWidget::highlightOpacity(
|
Ui::ChatPaintHighlight HistoryWidget::itemHighlight(
|
||||||
not_null<const HistoryItem*> item) const {
|
not_null<const HistoryItem*> item) const {
|
||||||
return _highlighter.progress(item);
|
return _highlighter.state(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
int HistoryWidget::itemTopForHighlight(
|
int HistoryWidget::itemTopForHighlight(
|
||||||
|
@ -1395,9 +1398,7 @@ void HistoryWidget::updateInlineBotQuery() {
|
||||||
_inlineBotResolveRequestId = _api.request(MTPcontacts_ResolveUsername(
|
_inlineBotResolveRequestId = _api.request(MTPcontacts_ResolveUsername(
|
||||||
MTP_string(username)
|
MTP_string(username)
|
||||||
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||||
Expects(result.type() == mtpc_contacts_resolvedPeer);
|
const auto &data = result.data();
|
||||||
|
|
||||||
const auto &data = result.c_contacts_resolvedPeer();
|
|
||||||
const auto resolvedBot = [&]() -> UserData* {
|
const auto resolvedBot = [&]() -> UserData* {
|
||||||
if (const auto user = session().data().processUsers(
|
if (const auto user = session().data().processUsers(
|
||||||
data.vusers())) {
|
data.vusers())) {
|
||||||
|
@ -1976,9 +1977,10 @@ bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
|
||||||
void HistoryWidget::showHistory(
|
void HistoryWidget::showHistory(
|
||||||
const PeerId &peerId,
|
const PeerId &peerId,
|
||||||
MsgId showAtMsgId,
|
MsgId showAtMsgId,
|
||||||
bool reload) {
|
const TextWithEntities &highlightPart) {
|
||||||
_pinnedClickedId = FullMsgId();
|
_pinnedClickedId = FullMsgId();
|
||||||
_minPinnedId = std::nullopt;
|
_minPinnedId = std::nullopt;
|
||||||
|
_showAtMsgHighlightPart = {};
|
||||||
|
|
||||||
const auto wasDialogsEntryState = computeDialogsEntryState();
|
const auto wasDialogsEntryState = computeDialogsEntryState();
|
||||||
const auto startBot = (showAtMsgId == ShowAndStartBotMsgId);
|
const auto startBot = (showAtMsgId == ShowAndStartBotMsgId);
|
||||||
|
@ -1990,7 +1992,7 @@ void HistoryWidget::showHistory(
|
||||||
controller()->sendingAnimation().clear();
|
controller()->sendingAnimation().clear();
|
||||||
_topToast.hide(anim::type::instant);
|
_topToast.hide(anim::type::instant);
|
||||||
if (_history) {
|
if (_history) {
|
||||||
if (_peer->id == peerId && !reload) {
|
if (_peer->id == peerId) {
|
||||||
updateForwarding();
|
updateForwarding();
|
||||||
|
|
||||||
if (showAtMsgId == ShowAtUnreadMsgId
|
if (showAtMsgId == ShowAtUnreadMsgId
|
||||||
|
@ -2026,10 +2028,10 @@ void HistoryWidget::showHistory(
|
||||||
).arg(_history->inboxReadTillId().bare
|
).arg(_history->inboxReadTillId().bare
|
||||||
).arg(Logs::b(_history->loadedAtBottom())
|
).arg(Logs::b(_history->loadedAtBottom())
|
||||||
).arg(showAtMsgId.bare));
|
).arg(showAtMsgId.bare));
|
||||||
delayedShowAt(showAtMsgId);
|
delayedShowAt(showAtMsgId, highlightPart);
|
||||||
} else if (_showAtMsgId != showAtMsgId) {
|
} else if (_showAtMsgId != showAtMsgId) {
|
||||||
clearAllLoadRequests();
|
clearAllLoadRequests();
|
||||||
setMsgId(showAtMsgId);
|
setMsgId(showAtMsgId, highlightPart);
|
||||||
firstLoadMessages();
|
firstLoadMessages();
|
||||||
doneShow();
|
doneShow();
|
||||||
}
|
}
|
||||||
|
@ -2049,7 +2051,7 @@ void HistoryWidget::showHistory(
|
||||||
_cornerButtons.skipReplyReturn(skipId);
|
_cornerButtons.skipReplyReturn(skipId);
|
||||||
}
|
}
|
||||||
|
|
||||||
setMsgId(showAtMsgId);
|
setMsgId(showAtMsgId, highlightPart);
|
||||||
if (_historyInited) {
|
if (_historyInited) {
|
||||||
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
|
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
|
||||||
"Showing instant at %4."
|
"Showing instant at %4."
|
||||||
|
@ -2152,6 +2154,7 @@ void HistoryWidget::showHistory(
|
||||||
clearInlineBot();
|
clearInlineBot();
|
||||||
|
|
||||||
_showAtMsgId = showAtMsgId;
|
_showAtMsgId = showAtMsgId;
|
||||||
|
_showAtMsgHighlightPart = highlightPart;
|
||||||
_historyInited = false;
|
_historyInited = false;
|
||||||
_contactStatus = nullptr;
|
_contactStatus = nullptr;
|
||||||
|
|
||||||
|
@ -3306,7 +3309,7 @@ void HistoryWidget::messagesReceived(
|
||||||
}
|
}
|
||||||
|
|
||||||
_delayedShowAtRequest = 0;
|
_delayedShowAtRequest = 0;
|
||||||
setMsgId(_delayedShowAtMsgId);
|
setMsgId(_delayedShowAtMsgId, _delayedShowAtMsgHighlightPart);
|
||||||
historyLoaded();
|
historyLoaded();
|
||||||
}
|
}
|
||||||
if (session().supportMode()) {
|
if (session().supportMode()) {
|
||||||
|
@ -3528,9 +3531,16 @@ void HistoryWidget::loadMessagesDown() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::delayedShowAt(MsgId showAtMsgId) {
|
void HistoryWidget::delayedShowAt(
|
||||||
if (!_history
|
MsgId showAtMsgId,
|
||||||
|| (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId)) {
|
const TextWithEntities &highlightPart) {
|
||||||
|
if (!_history) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_delayedShowAtMsgHighlightPart != highlightPart) {
|
||||||
|
_delayedShowAtMsgHighlightPart = highlightPart;
|
||||||
|
}
|
||||||
|
if (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3974,7 +3984,12 @@ void HistoryWidget::send(Api::SendOptions options) {
|
||||||
ignoreSlowmodeCountdown)) {
|
ignoreSlowmodeCountdown)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Just a flag not to drop reply info if we're not sending anything.
|
||||||
|
_justMarkingAsRead = !HasSendText(_field)
|
||||||
|
&& message.webPage.url.isEmpty();
|
||||||
session().api().sendMessage(std::move(message));
|
session().api().sendMessage(std::move(message));
|
||||||
|
_justMarkingAsRead = false;
|
||||||
|
|
||||||
clearFieldText();
|
clearFieldText();
|
||||||
if (_preview) {
|
if (_preview) {
|
||||||
|
@ -4123,7 +4138,12 @@ PeerData *HistoryWidget::peer() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sometimes _showAtMsgId is set directly.
|
// Sometimes _showAtMsgId is set directly.
|
||||||
void HistoryWidget::setMsgId(MsgId showAtMsgId) {
|
void HistoryWidget::setMsgId(
|
||||||
|
MsgId showAtMsgId,
|
||||||
|
const TextWithEntities &highlightPart) {
|
||||||
|
if (_showAtMsgHighlightPart != highlightPart) {
|
||||||
|
_showAtMsgHighlightPart = highlightPart;
|
||||||
|
}
|
||||||
if (_showAtMsgId != showAtMsgId) {
|
if (_showAtMsgId != showAtMsgId) {
|
||||||
_showAtMsgId = showAtMsgId;
|
_showAtMsgId = showAtMsgId;
|
||||||
if (_history) {
|
if (_history) {
|
||||||
|
@ -4244,11 +4264,11 @@ void HistoryWidget::cornerButtonsShowAtPosition(
|
||||||
).arg(_history->peer->name()
|
).arg(_history->peer->name()
|
||||||
).arg(_history->inboxReadTillId().bare
|
).arg(_history->inboxReadTillId().bare
|
||||||
).arg(Logs::b(_history->loadedAtBottom())));
|
).arg(Logs::b(_history->loadedAtBottom())));
|
||||||
showHistory(_peer->id, ShowAtUnreadMsgId);
|
showHistory(_peer->id, ShowAtUnreadMsgId, {});
|
||||||
} else if (_peer && position.fullId.peer == _peer->id) {
|
} else if (_peer && position.fullId.peer == _peer->id) {
|
||||||
showHistory(_peer->id, position.fullId.msg);
|
showHistory(_peer->id, position.fullId.msg, {});
|
||||||
} else if (_migrated && position.fullId.peer == _migrated->peer->id) {
|
} else if (_migrated && position.fullId.peer == _migrated->peer->id) {
|
||||||
showHistory(_peer->id, -position.fullId.msg);
|
showHistory(_peer->id, -position.fullId.msg, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5197,7 +5217,7 @@ void HistoryWidget::updateFieldPlaceholder() {
|
||||||
if (!_editMsgId && _inlineBot && !_inlineLookingUpBot) {
|
if (!_editMsgId && _inlineBot && !_inlineLookingUpBot) {
|
||||||
_field->setPlaceholder(
|
_field->setPlaceholder(
|
||||||
rpl::single(_inlineBot->botInfo->inlinePlaceholder.mid(1)),
|
rpl::single(_inlineBot->botInfo->inlinePlaceholder.mid(1)),
|
||||||
_inlineBot->username().size() + 2);
|
_inlineBotUsername.size() + 2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5699,14 +5719,16 @@ int HistoryWidget::countInitialScrollTop() {
|
||||||
const auto item = getItemFromHistoryOrMigrated(_showAtMsgId);
|
const auto item = getItemFromHistoryOrMigrated(_showAtMsgId);
|
||||||
const auto itemTop = _list->itemTop(item);
|
const auto itemTop = _list->itemTop(item);
|
||||||
if (itemTop < 0) {
|
if (itemTop < 0) {
|
||||||
setMsgId(0);
|
setMsgId(ShowAtUnreadMsgId);
|
||||||
controller()->showToast(tr::lng_message_not_found(tr::now));
|
controller()->showToast(tr::lng_message_not_found(tr::now));
|
||||||
return countInitialScrollTop();
|
return countInitialScrollTop();
|
||||||
} else {
|
} else {
|
||||||
const auto view = item->mainView();
|
const auto view = item->mainView();
|
||||||
Assert(view != nullptr);
|
Assert(view != nullptr);
|
||||||
|
|
||||||
enqueueMessageHighlight(view);
|
enqueueMessageHighlight(
|
||||||
|
view,
|
||||||
|
base::take(_showAtMsgHighlightPart));
|
||||||
const auto result = itemTopForHighlight(view);
|
const auto result = itemTopForHighlight(view);
|
||||||
createUnreadBarIfBelowVisibleArea(result);
|
createUnreadBarIfBelowVisibleArea(result);
|
||||||
return result;
|
return result;
|
||||||
|
@ -6278,6 +6300,8 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
|
||||||
} else {
|
} else {
|
||||||
_forwardPanel->editOptions(controller()->uiShow());
|
_forwardPanel->editOptions(controller()->uiShow());
|
||||||
}
|
}
|
||||||
|
} else if (_replyTo && (e->modifiers() & Qt::ControlModifier)) {
|
||||||
|
jumpToReply(_replyTo);
|
||||||
} else if (_replyTo) {
|
} else if (_replyTo) {
|
||||||
editDraftOptions();
|
editDraftOptions();
|
||||||
} else if (_kbReplyTo) {
|
} else if (_kbReplyTo) {
|
||||||
|
@ -6306,12 +6330,9 @@ void HistoryWidget::editDraftOptions() {
|
||||||
_preview->apply(webpage);
|
_preview->apply(webpage);
|
||||||
};
|
};
|
||||||
const auto replyToId = reply.messageId;
|
const auto replyToId = reply.messageId;
|
||||||
const auto highlight = [=] {
|
const auto highlight = crl::guard(this, [=](FullReplyTo to) {
|
||||||
controller()->showPeerHistory(
|
jumpToReply(to);
|
||||||
replyToId.peer,
|
});
|
||||||
Window::SectionShow::Way::Forward,
|
|
||||||
replyToId.msg);
|
|
||||||
};
|
|
||||||
|
|
||||||
using namespace HistoryView::Controls;
|
using namespace HistoryView::Controls;
|
||||||
EditDraftOptions({
|
EditDraftOptions({
|
||||||
|
@ -6323,10 +6344,16 @@ void HistoryWidget::editDraftOptions() {
|
||||||
.resolver = _preview->resolver(),
|
.resolver = _preview->resolver(),
|
||||||
.done = done,
|
.done = done,
|
||||||
.highlight = highlight,
|
.highlight = highlight,
|
||||||
.clearOldDraft = [=] { ClearDraftReplyTo(history, replyToId); },
|
.clearOldDraft = [=] { ClearDraftReplyTo(history, 0, replyToId); },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryWidget::jumpToReply(FullReplyTo to) {
|
||||||
|
if (const auto item = session().data().message(to.messageId)) {
|
||||||
|
JumpToMessageClickHandler(item, {}, to.quote)->onClick({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryWidget::keyPressEvent(QKeyEvent *e) {
|
void HistoryWidget::keyPressEvent(QKeyEvent *e) {
|
||||||
if (!_history) return;
|
if (!_history) return;
|
||||||
|
|
||||||
|
@ -6398,7 +6425,8 @@ void HistoryWidget::handlePeerMigration() {
|
||||||
if (_peer != channel) {
|
if (_peer != channel) {
|
||||||
showHistory(
|
showHistory(
|
||||||
channel->id,
|
channel->id,
|
||||||
(_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId);
|
(_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId,
|
||||||
|
{});
|
||||||
channel->session().api().chatParticipants().requestCountDelayed(
|
channel->session().api().chatParticipants().requestCountDelayed(
|
||||||
channel);
|
channel);
|
||||||
} else {
|
} else {
|
||||||
|
@ -6494,7 +6522,7 @@ bool HistoryWidget::showSlowmodeError() {
|
||||||
if (const auto item = _history->latestSendingMessage()) {
|
if (const auto item = _history->latestSendingMessage()) {
|
||||||
if (const auto view = item->mainView()) {
|
if (const auto view = item->mainView()) {
|
||||||
animatedScrollToItem(item->id);
|
animatedScrollToItem(item->id);
|
||||||
enqueueMessageHighlight(view);
|
enqueueMessageHighlight(view, {});
|
||||||
}
|
}
|
||||||
return tr::lng_slowmode_no_many(tr::now);
|
return tr::lng_slowmode_no_many(tr::now);
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ class SpoilerAnimation;
|
||||||
enum class ReportReason;
|
enum class ReportReason;
|
||||||
class ChooseThemeController;
|
class ChooseThemeController;
|
||||||
class ContinuousScroll;
|
class ContinuousScroll;
|
||||||
|
struct ChatPaintHighlight;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
|
@ -146,7 +147,9 @@ public:
|
||||||
void loadMessages();
|
void loadMessages();
|
||||||
void loadMessagesDown();
|
void loadMessagesDown();
|
||||||
void firstLoadMessages();
|
void firstLoadMessages();
|
||||||
void delayedShowAt(MsgId showAtMsgId);
|
void delayedShowAt(
|
||||||
|
MsgId showAtMsgId,
|
||||||
|
const TextWithEntities &highlightPart);
|
||||||
|
|
||||||
bool updateReplaceMediaButton();
|
bool updateReplaceMediaButton();
|
||||||
void updateFieldPlaceholder();
|
void updateFieldPlaceholder();
|
||||||
|
@ -160,7 +163,9 @@ public:
|
||||||
|
|
||||||
History *history() const;
|
History *history() const;
|
||||||
PeerData *peer() const;
|
PeerData *peer() const;
|
||||||
void setMsgId(MsgId showAtMsgId);
|
void setMsgId(
|
||||||
|
MsgId showAtMsgId,
|
||||||
|
const TextWithEntities &highlightPart = {});
|
||||||
MsgId msgId() const;
|
MsgId msgId() const;
|
||||||
|
|
||||||
bool hasTopBarShadow() const {
|
bool hasTopBarShadow() const {
|
||||||
|
@ -177,8 +182,10 @@ public:
|
||||||
|
|
||||||
bool touchScroll(const QPoint &delta);
|
bool touchScroll(const QPoint &delta);
|
||||||
|
|
||||||
void enqueueMessageHighlight(not_null<HistoryView::Element*> view);
|
void enqueueMessageHighlight(
|
||||||
[[nodiscard]] float64 highlightOpacity(
|
not_null<HistoryView::Element*> view,
|
||||||
|
const TextWithEntities &part);
|
||||||
|
[[nodiscard]] Ui::ChatPaintHighlight itemHighlight(
|
||||||
not_null<const HistoryItem*> item) const;
|
not_null<const HistoryItem*> item) const;
|
||||||
|
|
||||||
MessageIdsList getSelectedItems() const;
|
MessageIdsList getSelectedItems() const;
|
||||||
|
@ -218,7 +225,10 @@ public:
|
||||||
void fastShowAtEnd(not_null<History*> history);
|
void fastShowAtEnd(not_null<History*> history);
|
||||||
bool applyDraft(
|
bool applyDraft(
|
||||||
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
|
FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear);
|
||||||
void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false);
|
void showHistory(
|
||||||
|
const PeerId &peer,
|
||||||
|
MsgId showAtMsgId,
|
||||||
|
const TextWithEntities &highlightPart);
|
||||||
void setChooseReportMessagesDetails(
|
void setChooseReportMessagesDetails(
|
||||||
Ui::ReportReason reason,
|
Ui::ReportReason reason,
|
||||||
Fn<void(MessageIdsList)> callback);
|
Fn<void(MessageIdsList)> callback);
|
||||||
|
@ -540,6 +550,7 @@ private:
|
||||||
|
|
||||||
void setupPreview();
|
void setupPreview();
|
||||||
void editDraftOptions();
|
void editDraftOptions();
|
||||||
|
void jumpToReply(FullReplyTo to);
|
||||||
|
|
||||||
void messagesReceived(not_null<PeerData*> peer, const MTPmessages_Messages &messages, int requestId);
|
void messagesReceived(not_null<PeerData*> peer, const MTPmessages_Messages &messages, int requestId);
|
||||||
void messagesFailed(const MTP::Error &error, int requestId);
|
void messagesFailed(const MTP::Error &error, int requestId);
|
||||||
|
@ -684,12 +695,14 @@ private:
|
||||||
bool _canSendMessages = false;
|
bool _canSendMessages = false;
|
||||||
bool _canSendTexts = false;
|
bool _canSendTexts = false;
|
||||||
MsgId _showAtMsgId = ShowAtUnreadMsgId;
|
MsgId _showAtMsgId = ShowAtUnreadMsgId;
|
||||||
|
TextWithEntities _showAtMsgHighlightPart;
|
||||||
|
|
||||||
int _firstLoadRequest = 0; // Not real mtpRequestId.
|
int _firstLoadRequest = 0; // Not real mtpRequestId.
|
||||||
int _preloadRequest = 0; // Not real mtpRequestId.
|
int _preloadRequest = 0; // Not real mtpRequestId.
|
||||||
int _preloadDownRequest = 0; // Not real mtpRequestId.
|
int _preloadDownRequest = 0; // Not real mtpRequestId.
|
||||||
|
|
||||||
MsgId _delayedShowAtMsgId = -1;
|
MsgId _delayedShowAtMsgId = -1;
|
||||||
|
TextWithEntities _delayedShowAtMsgHighlightPart;
|
||||||
int _delayedShowAtRequest = 0; // Not real mtpRequestId.
|
int _delayedShowAtRequest = 0; // Not real mtpRequestId.
|
||||||
|
|
||||||
History *_supportPreloadHistory = nullptr;
|
History *_supportPreloadHistory = nullptr;
|
||||||
|
@ -800,6 +813,7 @@ private:
|
||||||
int _itemsRevealHeight = 0;
|
int _itemsRevealHeight = 0;
|
||||||
|
|
||||||
bool _sponsoredMessagesStateKnown = false;
|
bool _sponsoredMessagesStateKnown = false;
|
||||||
|
bool _justMarkingAsRead = false;
|
||||||
|
|
||||||
object_ptr<Ui::PlainShadow> _topShadow;
|
object_ptr<Ui::PlainShadow> _topShadow;
|
||||||
bool _inGrab = false;
|
bool _inGrab = false;
|
||||||
|
|
|
@ -112,6 +112,7 @@ public:
|
||||||
std::shared_ptr<ChatHelpers::Show> show);
|
std::shared_ptr<ChatHelpers::Show> show);
|
||||||
|
|
||||||
void setHistory(const SetHistoryArgs &args);
|
void setHistory(const SetHistoryArgs &args);
|
||||||
|
void updateTopicRootId(MsgId topicRootId);
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
void editMessage(FullMsgId id, bool photoEditAllowed = false);
|
void editMessage(FullMsgId id, bool photoEditAllowed = false);
|
||||||
|
@ -129,7 +130,7 @@ public:
|
||||||
[[nodiscard]] FullReplyTo replyingToMessage() const;
|
[[nodiscard]] FullReplyTo replyingToMessage() const;
|
||||||
[[nodiscard]] FullMsgId editMsgId() const;
|
[[nodiscard]] FullMsgId editMsgId() const;
|
||||||
[[nodiscard]] rpl::producer<FullMsgId> editMsgIdValue() const;
|
[[nodiscard]] rpl::producer<FullMsgId> editMsgIdValue() const;
|
||||||
[[nodiscard]] rpl::producer<FullMsgId> scrollToItemRequests() const;
|
[[nodiscard]] rpl::producer<FullReplyTo> jumpToItemRequests() const;
|
||||||
[[nodiscard]] rpl::producer<> editPhotoRequests() const;
|
[[nodiscard]] rpl::producer<> editPhotoRequests() const;
|
||||||
[[nodiscard]] rpl::producer<> editOptionsRequests() const;
|
[[nodiscard]] rpl::producer<> editOptionsRequests() const;
|
||||||
[[nodiscard]] MessageToEdit queryToEdit();
|
[[nodiscard]] MessageToEdit queryToEdit();
|
||||||
|
@ -205,7 +206,7 @@ private:
|
||||||
QRect _shownMessagePreviewRect;
|
QRect _shownMessagePreviewRect;
|
||||||
|
|
||||||
rpl::event_stream<bool> _visibleChanged;
|
rpl::event_stream<bool> _visibleChanged;
|
||||||
rpl::event_stream<FullMsgId> _scrollToItemRequests;
|
rpl::event_stream<FullReplyTo> _jumpToItemRequests;
|
||||||
rpl::event_stream<> _editOptionsRequests;
|
rpl::event_stream<> _editOptionsRequests;
|
||||||
rpl::event_stream<> _editPhotoRequests;
|
rpl::event_stream<> _editPhotoRequests;
|
||||||
|
|
||||||
|
@ -229,6 +230,10 @@ void FieldHeader::setHistory(const SetHistoryArgs &args) {
|
||||||
_topicRootId = args.topicRootId;
|
_topicRootId = args.topicRootId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FieldHeader::updateTopicRootId(MsgId topicRootId) {
|
||||||
|
_topicRootId = topicRootId;
|
||||||
|
}
|
||||||
|
|
||||||
void FieldHeader::init() {
|
void FieldHeader::init() {
|
||||||
sizeValue(
|
sizeValue(
|
||||||
) | rpl::start_with_next([=](QSize size) {
|
) | rpl::start_with_next([=](QSize size) {
|
||||||
|
@ -367,9 +372,14 @@ void FieldHeader::init() {
|
||||||
if (_preview.parsed) {
|
if (_preview.parsed) {
|
||||||
_editOptionsRequests.fire({});
|
_editOptionsRequests.fire({});
|
||||||
} else if (isEditingMessage()) {
|
} else if (isEditingMessage()) {
|
||||||
_scrollToItemRequests.fire(_editMsgId.current());
|
_jumpToItemRequests.fire(FullReplyTo{
|
||||||
|
.messageId = _editMsgId.current()
|
||||||
|
});
|
||||||
} else if (readyToForward()) {
|
} else if (readyToForward()) {
|
||||||
_forwardPanel->editOptions(_show);
|
_forwardPanel->editOptions(_show);
|
||||||
|
} else if (reply
|
||||||
|
&& (e->modifiers() & Qt::ControlModifier)) {
|
||||||
|
_jumpToItemRequests.fire_copy(reply);
|
||||||
} else if (reply) {
|
} else if (reply) {
|
||||||
_editOptionsRequests.fire({});
|
_editOptionsRequests.fire({});
|
||||||
}
|
}
|
||||||
|
@ -724,8 +734,8 @@ rpl::producer<FullMsgId> FieldHeader::editMsgIdValue() const {
|
||||||
return _editMsgId.value();
|
return _editMsgId.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<FullMsgId> FieldHeader::scrollToItemRequests() const {
|
rpl::producer<FullReplyTo> FieldHeader::jumpToItemRequests() const {
|
||||||
return _scrollToItemRequests.events();
|
return _jumpToItemRequests.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<> FieldHeader::editPhotoRequests() const {
|
rpl::producer<> FieldHeader::editPhotoRequests() const {
|
||||||
|
@ -855,6 +865,11 @@ Main::Session &ComposeControls::session() const {
|
||||||
return _show->session();
|
return _show->session();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ComposeControls::updateTopicRootId(MsgId topicRootId) {
|
||||||
|
_topicRootId = topicRootId;
|
||||||
|
_header->updateTopicRootId(_topicRootId);
|
||||||
|
}
|
||||||
|
|
||||||
void ComposeControls::setHistory(SetHistoryArgs &&args) {
|
void ComposeControls::setHistory(SetHistoryArgs &&args) {
|
||||||
_showSlowmodeError = std::move(args.showSlowmodeError);
|
_showSlowmodeError = std::move(args.showSlowmodeError);
|
||||||
_sendActionFactory = std::move(args.sendActionFactory);
|
_sendActionFactory = std::move(args.sendActionFactory);
|
||||||
|
@ -1332,6 +1347,7 @@ void ComposeControls::init() {
|
||||||
_header->editOptionsRequests(
|
_header->editOptionsRequests(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
const auto history = _history;
|
const auto history = _history;
|
||||||
|
const auto topicRootId = _topicRootId;
|
||||||
const auto reply = _header->replyingToMessage();
|
const auto reply = _header->replyingToMessage();
|
||||||
const auto webpage = _preview->draft();
|
const auto webpage = _preview->draft();
|
||||||
|
|
||||||
|
@ -1344,10 +1360,11 @@ void ComposeControls::init() {
|
||||||
cancelReplyMessage();
|
cancelReplyMessage();
|
||||||
}
|
}
|
||||||
_preview->apply(webpage);
|
_preview->apply(webpage);
|
||||||
|
_field->setFocus();
|
||||||
};
|
};
|
||||||
const auto replyToId = reply.messageId;
|
const auto replyToId = reply.messageId;
|
||||||
const auto highlight = crl::guard(_wrap.get(), [=] {
|
const auto highlight = crl::guard(_wrap.get(), [=](FullReplyTo to) {
|
||||||
_scrollToItemRequests.fire_copy(replyToId);
|
_jumpToItemRequests.fire_copy(to);
|
||||||
});
|
});
|
||||||
|
|
||||||
using namespace HistoryView::Controls;
|
using namespace HistoryView::Controls;
|
||||||
|
@ -1360,7 +1377,10 @@ void ComposeControls::init() {
|
||||||
.resolver = _preview->resolver(),
|
.resolver = _preview->resolver(),
|
||||||
.done = done,
|
.done = done,
|
||||||
.highlight = highlight,
|
.highlight = highlight,
|
||||||
.clearOldDraft = [=] { ClearDraftReplyTo(history, replyToId); },
|
.clearOldDraft = [=] { ClearDraftReplyTo(
|
||||||
|
history,
|
||||||
|
topicRootId,
|
||||||
|
replyToId); },
|
||||||
});
|
});
|
||||||
}, _wrap->lifetime());
|
}, _wrap->lifetime());
|
||||||
|
|
||||||
|
@ -1920,6 +1940,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||||
_header->replyToMessage({});
|
_header->replyToMessage({});
|
||||||
if (_preview) {
|
if (_preview) {
|
||||||
_preview->apply({ .removed = true });
|
_preview->apply({ .removed = true });
|
||||||
|
_preview->setDisabled(false);
|
||||||
}
|
}
|
||||||
_canReplaceMedia = false;
|
_canReplaceMedia = false;
|
||||||
_photoEditMedia = nullptr;
|
_photoEditMedia = nullptr;
|
||||||
|
@ -1958,6 +1979,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||||
_preview->apply(
|
_preview->apply(
|
||||||
Data::WebPageDraft::FromItem(item),
|
Data::WebPageDraft::FromItem(item),
|
||||||
false);
|
false);
|
||||||
|
_preview->setDisabled(media && !media->webpage());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1989,6 +2011,9 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
||||||
cancelForward();
|
cancelForward();
|
||||||
}
|
}
|
||||||
_header->editMessage({});
|
_header->editMessage({});
|
||||||
|
if (_preview) {
|
||||||
|
_preview->setDisabled(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2873,16 +2898,10 @@ Data::WebPageDraft ComposeControls::webPageDraft() const {
|
||||||
return _preview ? _preview->draft() : Data::WebPageDraft();
|
return _preview ? _preview->draft() : Data::WebPageDraft();
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<Data::MessagePosition> ComposeControls::scrollRequests() const {
|
rpl::producer<FullReplyTo> ComposeControls::jumpToItemRequests() const {
|
||||||
return rpl::merge(
|
return rpl::merge(
|
||||||
_header->scrollToItemRequests(),
|
_header->jumpToItemRequests(),
|
||||||
_scrollToItemRequests.events()
|
_jumpToItemRequests.events());
|
||||||
) | rpl::map([=](FullMsgId id) -> Data::MessagePosition {
|
|
||||||
if (const auto item = session().data().message(id)) {
|
|
||||||
return item->position();
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ComposeControls::isEditingMessage() const {
|
bool ComposeControls::isEditingMessage() const {
|
||||||
|
|
|
@ -135,6 +135,7 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] Main::Session &session() const;
|
[[nodiscard]] Main::Session &session() const;
|
||||||
void setHistory(SetHistoryArgs &&args);
|
void setHistory(SetHistoryArgs &&args);
|
||||||
|
void updateTopicRootId(MsgId topicRootId);
|
||||||
void setCurrentDialogsEntryState(Dialogs::EntryState state);
|
void setCurrentDialogsEntryState(Dialogs::EntryState state);
|
||||||
[[nodiscard]] PeerData *sendAsPeer() const;
|
[[nodiscard]] PeerData *sendAsPeer() const;
|
||||||
|
|
||||||
|
@ -158,7 +159,7 @@ public:
|
||||||
[[nodiscard]] rpl::producer<std::optional<bool>> attachRequests() const;
|
[[nodiscard]] rpl::producer<std::optional<bool>> attachRequests() const;
|
||||||
[[nodiscard]] rpl::producer<FileChosen> fileChosen() const;
|
[[nodiscard]] rpl::producer<FileChosen> fileChosen() const;
|
||||||
[[nodiscard]] rpl::producer<PhotoChosen> photoChosen() const;
|
[[nodiscard]] rpl::producer<PhotoChosen> photoChosen() const;
|
||||||
[[nodiscard]] rpl::producer<Data::MessagePosition> scrollRequests() const;
|
[[nodiscard]] rpl::producer<FullReplyTo> jumpToItemRequests() const;
|
||||||
[[nodiscard]] rpl::producer<InlineChosen> inlineResultChosen() const;
|
[[nodiscard]] rpl::producer<InlineChosen> inlineResultChosen() const;
|
||||||
[[nodiscard]] rpl::producer<SendActionUpdate> sendActionUpdates() const;
|
[[nodiscard]] rpl::producer<SendActionUpdate> sendActionUpdates() const;
|
||||||
[[nodiscard]] rpl::producer<not_null<QEvent*>> viewportEvents() const;
|
[[nodiscard]] rpl::producer<not_null<QEvent*>> viewportEvents() const;
|
||||||
|
@ -357,7 +358,7 @@ private:
|
||||||
|
|
||||||
const std::unique_ptr<Ui::RpWidget> _wrap;
|
const std::unique_ptr<Ui::RpWidget> _wrap;
|
||||||
const std::unique_ptr<Ui::RpWidget> _writeRestricted;
|
const std::unique_ptr<Ui::RpWidget> _writeRestricted;
|
||||||
rpl::event_stream<FullMsgId> _scrollToItemRequests;
|
rpl::event_stream<FullReplyTo> _jumpToItemRequests;
|
||||||
|
|
||||||
std::optional<Ui::RoundRect> _backgroundRect;
|
std::optional<Ui::RoundRect> _backgroundRect;
|
||||||
|
|
||||||
|
|
|
@ -99,9 +99,8 @@ public:
|
||||||
not_null<History*> history);
|
not_null<History*> history);
|
||||||
~PreviewWrap();
|
~PreviewWrap();
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<TextWithEntities> showQuoteSelector(
|
[[nodiscard]] rpl::producer<SelectedQuote> showQuoteSelector(
|
||||||
not_null<HistoryItem*> item,
|
const SelectedQuote "e);
|
||||||
const TextWithEntities "e);
|
|
||||||
[[nodiscard]] rpl::producer<QString> showLinkSelector(
|
[[nodiscard]] rpl::producer<QString> showLinkSelector(
|
||||||
const TextWithTags &message,
|
const TextWithTags &message,
|
||||||
Data::WebPageDraft webpage,
|
Data::WebPageDraft webpage,
|
||||||
|
@ -212,12 +211,14 @@ PreviewWrap::~PreviewWrap() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<TextWithEntities> PreviewWrap::showQuoteSelector(
|
rpl::producer<SelectedQuote> PreviewWrap::showQuoteSelector(
|
||||||
not_null<HistoryItem*> item,
|
const SelectedQuote "e) {
|
||||||
const TextWithEntities "e) {
|
|
||||||
_selection.reset(TextSelection());
|
_selection.reset(TextSelection());
|
||||||
|
|
||||||
_element = item->createView(_delegate.get());
|
const auto item = quote.item;
|
||||||
|
const auto group = item->history()->owner().groups().find(item);
|
||||||
|
const auto leader = group ? group->items.front().get() : item;
|
||||||
|
_element = leader->createView(_delegate.get());
|
||||||
_link = _pressedLink = nullptr;
|
_link = _pressedLink = nullptr;
|
||||||
|
|
||||||
if (const auto was = base::take(_draftItem)) {
|
if (const auto was = base::take(_draftItem)) {
|
||||||
|
@ -233,10 +234,13 @@ rpl::producer<TextWithEntities> PreviewWrap::showQuoteSelector(
|
||||||
|
|
||||||
initElement();
|
initElement();
|
||||||
|
|
||||||
_selection = _element->selectionFromQuote(quote);
|
_selection = _element->selectionFromQuote(item, quote.text);
|
||||||
return _selection.value(
|
return _selection.value(
|
||||||
) | rpl::map([=](TextSelection selection) {
|
) | rpl::map([=](TextSelection selection) {
|
||||||
return _element->selectedQuote(selection);
|
if (const auto result = _element->selectedQuote(selection)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return SelectedQuote{ item };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -584,7 +588,7 @@ void DraftOptionsBox(
|
||||||
struct State {
|
struct State {
|
||||||
rpl::variable<Section> shown;
|
rpl::variable<Section> shown;
|
||||||
rpl::lifetime shownLifetime;
|
rpl::lifetime shownLifetime;
|
||||||
rpl::variable<TextWithEntities> quote;
|
rpl::variable<SelectedQuote> quote;
|
||||||
Data::WebPageDraft webpage;
|
Data::WebPageDraft webpage;
|
||||||
WebPageData *preview = nullptr;
|
WebPageData *preview = nullptr;
|
||||||
QString link;
|
QString link;
|
||||||
|
@ -596,7 +600,7 @@ void DraftOptionsBox(
|
||||||
rpl::lifetime resolveLifetime;
|
rpl::lifetime resolveLifetime;
|
||||||
};
|
};
|
||||||
const auto state = box->lifetime().make_state<State>();
|
const auto state = box->lifetime().make_state<State>();
|
||||||
state->quote = draft.reply.quote;
|
state->quote = SelectedQuote{ replyItem, draft.reply.quote };
|
||||||
state->webpage = draft.webpage;
|
state->webpage = draft.webpage;
|
||||||
state->preview = previewData;
|
state->preview = previewData;
|
||||||
state->shown = previewData ? Section::Link : Section::Reply;
|
state->shown = previewData ? Section::Link : Section::Reply;
|
||||||
|
@ -635,7 +639,10 @@ void DraftOptionsBox(
|
||||||
const auto &clearOldDraft = args.clearOldDraft;
|
const auto &clearOldDraft = args.clearOldDraft;
|
||||||
const auto resolveReply = [=] {
|
const auto resolveReply = [=] {
|
||||||
auto result = draft.reply;
|
auto result = draft.reply;
|
||||||
result.quote = state->quote.current();
|
if (const auto current = state->quote.current()) {
|
||||||
|
result.messageId = current.item->fullId();
|
||||||
|
result.quote = current.text;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
const auto finish = [=](
|
const auto finish = [=](
|
||||||
|
@ -650,21 +657,26 @@ void DraftOptionsBox(
|
||||||
const auto setupReplyActions = [=] {
|
const auto setupReplyActions = [=] {
|
||||||
AddFilledSkip(bottom);
|
AddFilledSkip(bottom);
|
||||||
|
|
||||||
Settings::AddButton(
|
const auto item = state->quote.current().item;
|
||||||
bottom,
|
if (item->allowsForward()) {
|
||||||
tr::lng_reply_in_another_chat(),
|
Settings::AddButton(
|
||||||
st::settingsButton,
|
bottom,
|
||||||
{ &st::menuIconReplace }
|
tr::lng_reply_in_another_chat(),
|
||||||
)->setClickedCallback([=] {
|
st::settingsButton,
|
||||||
ShowReplyToChatBox(show, resolveReply(), clearOldDraft);
|
{ &st::menuIconReplace }
|
||||||
});
|
)->setClickedCallback([=] {
|
||||||
|
ShowReplyToChatBox(show, resolveReply(), clearOldDraft);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Settings::AddButton(
|
Settings::AddButton(
|
||||||
bottom,
|
bottom,
|
||||||
tr::lng_reply_show_in_chat(),
|
tr::lng_reply_show_in_chat(),
|
||||||
st::settingsButton,
|
st::settingsButton,
|
||||||
{ &st::menuIconShowInChat }
|
{ &st::menuIconShowInChat }
|
||||||
)->setClickedCallback(highlight);
|
)->setClickedCallback([=] {
|
||||||
|
highlight(resolveReply());
|
||||||
|
});
|
||||||
|
|
||||||
Settings::AddButton(
|
Settings::AddButton(
|
||||||
bottom,
|
bottom,
|
||||||
|
@ -675,7 +687,7 @@ void DraftOptionsBox(
|
||||||
finish({}, state->webpage);
|
finish({}, state->webpage);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!replyItem->originalText().empty()) {
|
if (!item->originalText().empty()) {
|
||||||
AddFilledSkip(bottom);
|
AddFilledSkip(bottom);
|
||||||
Settings::AddDividerText(
|
Settings::AddDividerText(
|
||||||
bottom,
|
bottom,
|
||||||
|
@ -804,7 +816,6 @@ void DraftOptionsBox(
|
||||||
state->shownLifetime.destroy();
|
state->shownLifetime.destroy();
|
||||||
if (shown == Section::Reply) {
|
if (shown == Section::Reply) {
|
||||||
state->quote = state->wrap->showQuoteSelector(
|
state->quote = state->wrap->showQuoteSelector(
|
||||||
replyItem,
|
|
||||||
state->quote.current());
|
state->quote.current());
|
||||||
setupReplyActions();
|
setupReplyActions();
|
||||||
} else {
|
} else {
|
||||||
|
@ -823,8 +834,8 @@ void DraftOptionsBox(
|
||||||
auto save = rpl::combine(
|
auto save = rpl::combine(
|
||||||
state->quote.value(),
|
state->quote.value(),
|
||||||
state->shown.value()
|
state->shown.value()
|
||||||
) | rpl::map([=](const TextWithEntities "e, Section shown) {
|
) | rpl::map([=](const SelectedQuote "e, Section shown) {
|
||||||
return (quote.empty() || shown != Section::Reply)
|
return (quote.text.empty() || shown != Section::Reply)
|
||||||
? tr::lng_settings_save()
|
? tr::lng_settings_save()
|
||||||
: tr::lng_reply_quote_selected();
|
: tr::lng_reply_quote_selected();
|
||||||
}) | rpl::flatten_latest();
|
}) | rpl::flatten_latest();
|
||||||
|
@ -839,14 +850,20 @@ void DraftOptionsBox(
|
||||||
if (replyItem) {
|
if (replyItem) {
|
||||||
args.show->session().data().itemRemoved(
|
args.show->session().data().itemRemoved(
|
||||||
) | rpl::filter([=](not_null<const HistoryItem*> removed) {
|
) | rpl::filter([=](not_null<const HistoryItem*> removed) {
|
||||||
return removed == replyItem;
|
const auto current = state->quote.current().item;
|
||||||
|
if ((removed == replyItem) || (removed == current)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto group = current->history()->owner().groups().find(
|
||||||
|
current);
|
||||||
|
return (group && ranges::contains(group->items, removed));
|
||||||
}) | rpl::start_with_next([=] {
|
}) | rpl::start_with_next([=] {
|
||||||
if (previewData) {
|
if (previewData) {
|
||||||
state->tabs = nullptr;
|
state->tabs = nullptr;
|
||||||
box->setPinnedToTopContent(
|
box->setPinnedToTopContent(
|
||||||
object_ptr<Ui::RpWidget>(nullptr));
|
object_ptr<Ui::RpWidget>(nullptr));
|
||||||
box->setNoContentMargin(false);
|
box->setNoContentMargin(false);
|
||||||
box->setTitle(state->quote.current().empty()
|
box->setTitle(state->quote.current().text.empty()
|
||||||
? tr::lng_reply_options_header()
|
? tr::lng_reply_options_header()
|
||||||
: tr::lng_reply_options_quote());
|
: tr::lng_reply_options_quote());
|
||||||
state->shown = Section::Link;
|
state->shown = Section::Link;
|
||||||
|
|
|
@ -32,7 +32,7 @@ struct EditDraftOptionsArgs {
|
||||||
std::vector<MessageLinkRange> links;
|
std::vector<MessageLinkRange> links;
|
||||||
std::shared_ptr<WebpageResolver> resolver;
|
std::shared_ptr<WebpageResolver> resolver;
|
||||||
Fn<void(FullReplyTo, Data::WebPageDraft)> done;
|
Fn<void(FullReplyTo, Data::WebPageDraft)> done;
|
||||||
Fn<void()> highlight;
|
Fn<void(FullReplyTo)> highlight;
|
||||||
Fn<void()> clearOldDraft;
|
Fn<void()> clearOldDraft;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -400,13 +400,6 @@ void ForwardPanel::paint(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClearDraftReplyTo(not_null<Data::Thread*> thread, FullMsgId equalTo) {
|
|
||||||
ClearDraftReplyTo(
|
|
||||||
thread->owningHistory(),
|
|
||||||
thread->topicRootId(),
|
|
||||||
equalTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClearDraftReplyTo(
|
void ClearDraftReplyTo(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
MsgId topicRootId,
|
MsgId topicRootId,
|
||||||
|
|
|
@ -72,7 +72,6 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void ClearDraftReplyTo(not_null<Data::Thread*> thread, FullMsgId equalTo);
|
|
||||||
void ClearDraftReplyTo(
|
void ClearDraftReplyTo(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
MsgId topicRootId,
|
MsgId topicRootId,
|
||||||
|
|
|
@ -590,7 +590,7 @@ bool AddReplyToMessageAction(
|
||||||
const ContextMenuRequest &request,
|
const ContextMenuRequest &request,
|
||||||
not_null<ListWidget*> list) {
|
not_null<ListWidget*> list) {
|
||||||
const auto context = list->elementContext();
|
const auto context = list->elementContext();
|
||||||
const auto item = request.item;
|
const auto item = request.quoteItem ? request.quoteItem : request.item;
|
||||||
const auto topic = item ? item->topic() : nullptr;
|
const auto topic = item ? item->topic() : nullptr;
|
||||||
const auto peer = item ? item->history()->peer.get() : nullptr;
|
const auto peer = item ? item->history()->peer.get() : nullptr;
|
||||||
if (!item
|
if (!item
|
||||||
|
@ -601,15 +601,7 @@ bool AddReplyToMessageAction(
|
||||||
const auto canSendReply = topic
|
const auto canSendReply = topic
|
||||||
? Data::CanSendAnything(topic)
|
? Data::CanSendAnything(topic)
|
||||||
: Data::CanSendAnything(peer);
|
: Data::CanSendAnything(peer);
|
||||||
const auto canReply = canSendReply || [&] {
|
const auto canReply = canSendReply || item->allowsForward();
|
||||||
const auto peer = item->history()->peer;
|
|
||||||
if (const auto chat = peer->asChat()) {
|
|
||||||
return !chat->isForbidden();
|
|
||||||
} else if (const auto channel = peer->asChannel()) {
|
|
||||||
return !channel->isForbidden();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}();
|
|
||||||
if (!canReply) {
|
if (!canReply) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ struct ContextMenuRequest {
|
||||||
SelectedItems selectedItems;
|
SelectedItems selectedItems;
|
||||||
TextForMimeData selectedText;
|
TextForMimeData selectedText;
|
||||||
TextWithEntities quote;
|
TextWithEntities quote;
|
||||||
|
HistoryItem *quoteItem = nullptr;
|
||||||
bool overSelection = false;
|
bool overSelection = false;
|
||||||
PointState pointState = PointState();
|
PointState pointState = PointState();
|
||||||
};
|
};
|
||||||
|
|
|
@ -94,6 +94,42 @@ Element *MousedElement/* = nullptr*/;
|
||||||
return session->tryResolveWindow();
|
return session->tryResolveWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool CheckQuoteEntities(
|
||||||
|
const EntitiesInText "eEntities,
|
||||||
|
const TextWithEntities &original,
|
||||||
|
TextSelection selection) {
|
||||||
|
auto left = quoteEntities;
|
||||||
|
const auto allowed = std::array{
|
||||||
|
EntityType::Bold,
|
||||||
|
EntityType::Italic,
|
||||||
|
EntityType::Underline,
|
||||||
|
EntityType::StrikeOut,
|
||||||
|
EntityType::Spoiler,
|
||||||
|
EntityType::CustomEmoji,
|
||||||
|
};
|
||||||
|
for (const auto &entity : original.entities) {
|
||||||
|
const auto from = entity.offset();
|
||||||
|
const auto till = from + entity.length();
|
||||||
|
if (till <= selection.from || from >= selection.to) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto quoteFrom = std::max(from, int(selection.from));
|
||||||
|
const auto quoteTill = std::min(till, int(selection.to));
|
||||||
|
const auto cut = EntityInText(
|
||||||
|
entity.type(),
|
||||||
|
quoteFrom - int(selection.from),
|
||||||
|
quoteTill - quoteFrom,
|
||||||
|
entity.data());
|
||||||
|
const auto i = ranges::find(left, cut);
|
||||||
|
if (i != left.end()) {
|
||||||
|
left.erase(i);
|
||||||
|
} else if (ranges::contains(allowed, cut.type())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return left.empty();
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::unique_ptr<Ui::PathShiftGradient> MakePathShiftGradient(
|
std::unique_ptr<Ui::PathShiftGradient> MakePathShiftGradient(
|
||||||
|
@ -111,11 +147,6 @@ bool DefaultElementDelegate::elementUnderCursor(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float64 DefaultElementDelegate::elementHighlightOpacity(
|
|
||||||
not_null<const HistoryItem*> item) const {
|
|
||||||
return 0.;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DefaultElementDelegate::elementInSelectionMode() {
|
bool DefaultElementDelegate::elementInSelectionMode() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -593,6 +624,9 @@ void Element::paintHighlight(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const PaintContext &context,
|
const PaintContext &context,
|
||||||
int geometryHeight) const {
|
int geometryHeight) const {
|
||||||
|
if (context.highlight.opacity == 0.) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const auto top = marginTop();
|
const auto top = marginTop();
|
||||||
const auto bottom = marginBottom();
|
const auto bottom = marginBottom();
|
||||||
const auto fill = qMin(top, bottom);
|
const auto fill = qMin(top, bottom);
|
||||||
|
@ -608,18 +642,9 @@ void Element::paintCustomHighlight(
|
||||||
int y,
|
int y,
|
||||||
int height,
|
int height,
|
||||||
not_null<const HistoryItem*> item) const {
|
not_null<const HistoryItem*> item) const {
|
||||||
const auto opacity = delegate()->elementHighlightOpacity(item);
|
|
||||||
if (opacity == 0.) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto o = p.opacity();
|
const auto o = p.opacity();
|
||||||
p.setOpacity(o * opacity);
|
p.setOpacity(o * context.highlight.opacity);
|
||||||
p.fillRect(
|
p.fillRect(0, y, width(), height, context.st->msgSelectOverlay());
|
||||||
0,
|
|
||||||
y,
|
|
||||||
width(),
|
|
||||||
height,
|
|
||||||
context.st->msgSelectOverlay());
|
|
||||||
p.setOpacity(o);
|
p.setOpacity(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1402,7 +1427,12 @@ HistoryMessageReply *Element::displayedReply() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Element::toggleSelectionByHandlerClick(
|
bool Element::toggleSelectionByHandlerClick(
|
||||||
const ClickHandlerPtr &handler) const {
|
const ClickHandlerPtr &handler) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Element::allowTextSelectionByHandler(
|
||||||
|
const ClickHandlerPtr &handler) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1572,6 +1602,105 @@ TextSelection Element::adjustSelection(
|
||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelectedQuote Element::FindSelectedQuote(
|
||||||
|
const Ui::Text::String &text,
|
||||||
|
TextSelection selection,
|
||||||
|
not_null<HistoryItem*> item) {
|
||||||
|
if (selection.to > text.length()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto modified = selection;
|
||||||
|
for (const auto &modification : text.modifications()) {
|
||||||
|
if (modification.position >= selection.to) {
|
||||||
|
break;
|
||||||
|
} else if (modification.position <= selection.from) {
|
||||||
|
modified.from += modification.skipped;
|
||||||
|
if (modification.added
|
||||||
|
&& modification.position < selection.from) {
|
||||||
|
--modified.from;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modified.to += modification.skipped;
|
||||||
|
if (modification.added && modified.to > modified.from) {
|
||||||
|
--modified.to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto result = item->originalText();
|
||||||
|
if (modified.empty() || modified.to > result.text.size()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
result.text = result.text.mid(
|
||||||
|
modified.from,
|
||||||
|
modified.to - modified.from);
|
||||||
|
const auto allowed = std::array{
|
||||||
|
EntityType::Bold,
|
||||||
|
EntityType::Italic,
|
||||||
|
EntityType::Underline,
|
||||||
|
EntityType::StrikeOut,
|
||||||
|
EntityType::Spoiler,
|
||||||
|
EntityType::CustomEmoji,
|
||||||
|
};
|
||||||
|
for (auto i = result.entities.begin(); i != result.entities.end();) {
|
||||||
|
const auto offset = i->offset();
|
||||||
|
const auto till = offset + i->length();
|
||||||
|
if ((till <= modified.from)
|
||||||
|
|| (offset >= modified.to)
|
||||||
|
|| !ranges::contains(allowed, i->type())) {
|
||||||
|
i = result.entities.erase(i);
|
||||||
|
} else {
|
||||||
|
if (till > modified.to) {
|
||||||
|
i->shrinkFromRight(till - modified.to);
|
||||||
|
}
|
||||||
|
i->shiftLeft(modified.from);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { item, result };
|
||||||
|
}
|
||||||
|
|
||||||
|
TextSelection Element::FindSelectionFromQuote(
|
||||||
|
const Ui::Text::String &text,
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
const TextWithEntities "e) {
|
||||||
|
if (quote.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto &original = item->originalText();
|
||||||
|
auto result = TextSelection();
|
||||||
|
auto offset = 0;
|
||||||
|
while (true) {
|
||||||
|
const auto i = original.text.indexOf(quote.text, offset);
|
||||||
|
if (i < 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto selection = TextSelection{
|
||||||
|
uint16(i),
|
||||||
|
uint16(i + quote.text.size()),
|
||||||
|
};
|
||||||
|
if (CheckQuoteEntities(quote.entities, original, selection)) {
|
||||||
|
result = selection;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
offset = i + 1;
|
||||||
|
}
|
||||||
|
//for (const auto &modification : text.modifications()) {
|
||||||
|
// if (modification.position >= selection.to) {
|
||||||
|
// break;
|
||||||
|
// } else if (modification.position <= selection.from) {
|
||||||
|
// modified.from += modification.skipped;
|
||||||
|
// if (modification.added
|
||||||
|
// && modification.position < selection.from) {
|
||||||
|
// --modified.from;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// modified.to += modification.skipped;
|
||||||
|
// if (modification.added && modified.to > modified.from) {
|
||||||
|
// --modified.to;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Reactions::ButtonParameters Element::reactionButtonParameters(
|
Reactions::ButtonParameters Element::reactionButtonParameters(
|
||||||
QPoint position,
|
QPoint position,
|
||||||
const TextState &reactionState) const {
|
const TextState &reactionState) const {
|
||||||
|
|
|
@ -69,8 +69,6 @@ class ElementDelegate {
|
||||||
public:
|
public:
|
||||||
virtual Context elementContext() = 0;
|
virtual Context elementContext() = 0;
|
||||||
virtual bool elementUnderCursor(not_null<const Element*> view) = 0;
|
virtual bool elementUnderCursor(not_null<const Element*> view) = 0;
|
||||||
[[nodiscard]] virtual float64 elementHighlightOpacity(
|
|
||||||
not_null<const HistoryItem*> item) const = 0;
|
|
||||||
virtual bool elementInSelectionMode() = 0;
|
virtual bool elementInSelectionMode() = 0;
|
||||||
virtual bool elementIntersectsRange(
|
virtual bool elementIntersectsRange(
|
||||||
not_null<const Element*> view,
|
not_null<const Element*> view,
|
||||||
|
@ -120,8 +118,6 @@ public:
|
||||||
class DefaultElementDelegate : public ElementDelegate {
|
class DefaultElementDelegate : public ElementDelegate {
|
||||||
public:
|
public:
|
||||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||||
[[nodiscard]] float64 elementHighlightOpacity(
|
|
||||||
not_null<const HistoryItem*> item) const override;
|
|
||||||
bool elementInSelectionMode() override;
|
bool elementInSelectionMode() override;
|
||||||
bool elementIntersectsRange(
|
bool elementIntersectsRange(
|
||||||
not_null<const Element*> view,
|
not_null<const Element*> view,
|
||||||
|
@ -270,6 +266,16 @@ struct TopicButton {
|
||||||
int nameVersion = 0;
|
int nameVersion = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SelectedQuote {
|
||||||
|
HistoryItem *item = nullptr;
|
||||||
|
TextWithEntities text;
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return item && !text.empty();
|
||||||
|
}
|
||||||
|
friend inline bool operator==(SelectedQuote, SelectedQuote) = default;
|
||||||
|
};
|
||||||
|
|
||||||
class Element
|
class Element
|
||||||
: public Object
|
: public Object
|
||||||
, public RuntimeComposer<Element>
|
, public RuntimeComposer<Element>
|
||||||
|
@ -391,19 +397,24 @@ public:
|
||||||
QPoint point,
|
QPoint point,
|
||||||
InfoDisplayType type) const;
|
InfoDisplayType type) const;
|
||||||
virtual TextForMimeData selectedText(TextSelection selection) const = 0;
|
virtual TextForMimeData selectedText(TextSelection selection) const = 0;
|
||||||
virtual TextWithEntities selectedQuote(TextSelection selection) const = 0;
|
virtual SelectedQuote selectedQuote(
|
||||||
virtual TextWithEntities selectedQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
TextSelection selection) const = 0;
|
TextSelection selection) const = 0;
|
||||||
virtual TextSelection selectionFromQuote(
|
virtual TextSelection selectionFromQuote(
|
||||||
const TextWithEntities "e) const = 0;
|
not_null<HistoryItem*> item,
|
||||||
virtual TextSelection selectionFromQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
const TextWithEntities "e) const = 0;
|
const TextWithEntities "e) const = 0;
|
||||||
[[nodiscard]] virtual TextSelection adjustSelection(
|
[[nodiscard]] virtual TextSelection adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
TextSelectType type) const;
|
TextSelectType type) const;
|
||||||
|
|
||||||
|
[[nodiscard]] static SelectedQuote FindSelectedQuote(
|
||||||
|
const Ui::Text::String &text,
|
||||||
|
TextSelection selection,
|
||||||
|
not_null<HistoryItem*> item);
|
||||||
|
[[nodiscard]] static TextSelection FindSelectionFromQuote(
|
||||||
|
const Ui::Text::String &text,
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
const TextWithEntities "e);
|
||||||
|
|
||||||
[[nodiscard]] virtual auto reactionButtonParameters(
|
[[nodiscard]] virtual auto reactionButtonParameters(
|
||||||
QPoint position,
|
QPoint position,
|
||||||
const TextState &reactionState) const -> Reactions::ButtonParameters;
|
const TextState &reactionState) const -> Reactions::ButtonParameters;
|
||||||
|
@ -451,6 +462,8 @@ public:
|
||||||
}
|
}
|
||||||
[[nodiscard]] virtual bool toggleSelectionByHandlerClick(
|
[[nodiscard]] virtual bool toggleSelectionByHandlerClick(
|
||||||
const ClickHandlerPtr &handler) const;
|
const ClickHandlerPtr &handler) const;
|
||||||
|
[[nodiscard]] virtual bool allowTextSelectionByHandler(
|
||||||
|
const ClickHandlerPtr &handler) const;
|
||||||
|
|
||||||
struct VerticalRepaintRange {
|
struct VerticalRepaintRange {
|
||||||
int top = 0;
|
int top = 0;
|
||||||
|
|
|
@ -707,8 +707,12 @@ bool ListWidget::isBelowPosition(Data::MessagePosition position) const {
|
||||||
return _items.front()->data()->position() > position;
|
return _items.front()->data()->position() > position;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListWidget::highlightMessage(FullMsgId itemId) {
|
void ListWidget::highlightMessage(
|
||||||
_highlighter.highlight(itemId);
|
FullMsgId itemId,
|
||||||
|
const TextWithEntities &part) {
|
||||||
|
if (const auto view = viewForItem(itemId)) {
|
||||||
|
_highlighter.highlight(view, part);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListWidget::showAroundPosition(
|
void ListWidget::showAroundPosition(
|
||||||
|
@ -741,12 +745,12 @@ bool ListWidget::jumpToBottomInsteadOfUnread() const {
|
||||||
|
|
||||||
void ListWidget::showAtPosition(
|
void ListWidget::showAtPosition(
|
||||||
Data::MessagePosition position,
|
Data::MessagePosition position,
|
||||||
anim::type animated,
|
const Window::SectionShow ¶ms,
|
||||||
Fn<void(bool found)> done) {
|
Fn<void(bool found)> done) {
|
||||||
const auto showAtUnread = (position == Data::UnreadMessagePosition);
|
const auto showAtUnread = (position == Data::UnreadMessagePosition);
|
||||||
|
|
||||||
if (showAtUnread && jumpToBottomInsteadOfUnread()) {
|
if (showAtUnread && jumpToBottomInsteadOfUnread()) {
|
||||||
showAtPosition(Data::MaxMessagePosition, animated, std::move(done));
|
showAtPosition(Data::MaxMessagePosition, params, std::move(done));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -766,24 +770,24 @@ void ListWidget::showAtPosition(
|
||||||
_bar = {};
|
_bar = {};
|
||||||
}
|
}
|
||||||
checkUnreadBarCreation();
|
checkUnreadBarCreation();
|
||||||
return showAtPositionNow(position, animated, done);
|
return showAtPositionNow(position, params, done);
|
||||||
});
|
});
|
||||||
} else if (!showAtPositionNow(position, animated, done)) {
|
} else if (!showAtPositionNow(position, params, done)) {
|
||||||
showAroundPosition(position, [=] {
|
showAroundPosition(position, [=] {
|
||||||
return showAtPositionNow(position, animated, done);
|
return showAtPositionNow(position, params, done);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ListWidget::showAtPositionNow(
|
bool ListWidget::showAtPositionNow(
|
||||||
Data::MessagePosition position,
|
Data::MessagePosition position,
|
||||||
anim::type animated,
|
const Window::SectionShow ¶ms,
|
||||||
Fn<void(bool found)> done) {
|
Fn<void(bool found)> done) {
|
||||||
if (const auto scrollTop = scrollTopForPosition(position)) {
|
if (const auto scrollTop = scrollTopForPosition(position)) {
|
||||||
computeScrollTo(*scrollTop, position, animated);
|
computeScrollTo(*scrollTop, position, params.animated);
|
||||||
if (position != Data::MaxMessagePosition
|
if (position != Data::MaxMessagePosition
|
||||||
&& position != Data::UnreadMessagePosition) {
|
&& position != Data::UnreadMessagePosition) {
|
||||||
highlightMessage(position.fullId);
|
highlightMessage(position.fullId, params.highlightPart);
|
||||||
}
|
}
|
||||||
if (done) {
|
if (done) {
|
||||||
const auto found = !position.fullId.peer
|
const auto found = !position.fullId.peer
|
||||||
|
@ -1655,11 +1659,6 @@ bool ListWidget::elementUnderCursor(
|
||||||
return (_overElement == view);
|
return (_overElement == view);
|
||||||
}
|
}
|
||||||
|
|
||||||
float64 ListWidget::elementHighlightOpacity(
|
|
||||||
not_null<const HistoryItem*> item) const {
|
|
||||||
return _highlighter.progress(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ListWidget::elementInSelectionMode() {
|
bool ListWidget::elementInSelectionMode() {
|
||||||
return inSelectionMode();
|
return inSelectionMode();
|
||||||
}
|
}
|
||||||
|
@ -2088,6 +2087,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
|
||||||
});
|
});
|
||||||
|
|
||||||
auto context = preparePaintContext(clip);
|
auto context = preparePaintContext(clip);
|
||||||
|
context.highlightPathCache = &_highlightPathCache;
|
||||||
if (from == end(_items)) {
|
if (from == end(_items)) {
|
||||||
_delegate->listPaintEmpty(p, context);
|
_delegate->listPaintEmpty(p, context);
|
||||||
return;
|
return;
|
||||||
|
@ -2108,6 +2108,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
|
||||||
= _reactionsManager->currentReactionPaintInfo();
|
= _reactionsManager->currentReactionPaintInfo();
|
||||||
context.outbg = view->hasOutLayout();
|
context.outbg = view->hasOutLayout();
|
||||||
context.selection = itemRenderSelection(view);
|
context.selection = itemRenderSelection(view);
|
||||||
|
context.highlight = _highlighter.state(item);
|
||||||
view->draw(p, context);
|
view->draw(p, context);
|
||||||
}
|
}
|
||||||
if (_translateTracker) {
|
if (_translateTracker) {
|
||||||
|
@ -2142,7 +2143,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
|
||||||
} else if (item->isUnreadMention()
|
} else if (item->isUnreadMention()
|
||||||
&& !item->isUnreadMedia()) {
|
&& !item->isUnreadMedia()) {
|
||||||
readContents.insert(item);
|
readContents.insert(item);
|
||||||
_highlighter.enqueue(view);
|
_highlighter.enqueue(view, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
session->data().reactions().poll(item, context.now);
|
session->data().reactions().poll(item, context.now);
|
||||||
|
@ -2576,7 +2577,6 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
: _overElement
|
: _overElement
|
||||||
? _overElement->data().get()
|
? _overElement->data().get()
|
||||||
: nullptr;
|
: nullptr;
|
||||||
const auto overItemView = viewForItem(overItem);
|
|
||||||
const auto clickedReaction = link
|
const auto clickedReaction = link
|
||||||
? link->property(
|
? link->property(
|
||||||
kReactionsCountEmojiProperty).value<Data::ReactionId>()
|
kReactionsCountEmojiProperty).value<Data::ReactionId>()
|
||||||
|
@ -2603,9 +2603,12 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
request.view = _overElement;
|
request.view = _overElement;
|
||||||
request.item = overItem;
|
request.item = overItem;
|
||||||
request.pointState = _overState.pointState;
|
request.pointState = _overState.pointState;
|
||||||
request.quote = (overItemView && _selectedTextItem == overItem)
|
const auto quote = (_overElement
|
||||||
? overItemView->selectedQuote(_selectedTextRange)
|
&& _selectedTextItem == _overElement->data())
|
||||||
: TextWithEntities();
|
? _overElement->selectedQuote(_selectedTextRange)
|
||||||
|
: SelectedQuote();
|
||||||
|
request.quote = quote.text;
|
||||||
|
request.quoteItem = quote.item;
|
||||||
request.selectedText = _selectedText;
|
request.selectedText = _selectedText;
|
||||||
request.selectedItems = collectSelectedItems();
|
request.selectedItems = collectSelectedItems();
|
||||||
const auto hasSelection = !request.selectedItems.empty()
|
const auto hasSelection = !request.selectedItems.empty()
|
||||||
|
|
|
@ -48,6 +48,10 @@ struct ChosenReaction;
|
||||||
struct ButtonParameters;
|
struct ButtonParameters;
|
||||||
} // namespace HistoryView::Reactions
|
} // namespace HistoryView::Reactions
|
||||||
|
|
||||||
|
namespace Window {
|
||||||
|
struct SectionShow;
|
||||||
|
} // namespace Window
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
struct TextState;
|
struct TextState;
|
||||||
|
@ -227,11 +231,13 @@ public:
|
||||||
[[nodiscard]] bool animatedScrolling() const;
|
[[nodiscard]] bool animatedScrolling() const;
|
||||||
bool isAbovePosition(Data::MessagePosition position) const;
|
bool isAbovePosition(Data::MessagePosition position) const;
|
||||||
bool isBelowPosition(Data::MessagePosition position) const;
|
bool isBelowPosition(Data::MessagePosition position) const;
|
||||||
void highlightMessage(FullMsgId itemId);
|
void highlightMessage(
|
||||||
|
FullMsgId itemId,
|
||||||
|
const TextWithEntities &part);
|
||||||
|
|
||||||
void showAtPosition(
|
void showAtPosition(
|
||||||
Data::MessagePosition position,
|
Data::MessagePosition position,
|
||||||
anim::type animated = anim::type::normal,
|
const Window::SectionShow ¶ms,
|
||||||
Fn<void(bool found)> done = nullptr);
|
Fn<void(bool found)> done = nullptr);
|
||||||
void refreshViewer();
|
void refreshViewer();
|
||||||
|
|
||||||
|
@ -292,8 +298,6 @@ public:
|
||||||
// ElementDelegate interface.
|
// ElementDelegate interface.
|
||||||
Context elementContext() override;
|
Context elementContext() override;
|
||||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||||
[[nodiscard]] float64 elementHighlightOpacity(
|
|
||||||
not_null<const HistoryItem*> item) const override;
|
|
||||||
bool elementInSelectionMode() override;
|
bool elementInSelectionMode() override;
|
||||||
bool elementIntersectsRange(
|
bool elementIntersectsRange(
|
||||||
not_null<const Element*> view,
|
not_null<const Element*> view,
|
||||||
|
@ -430,7 +434,7 @@ private:
|
||||||
Fn<bool()> overrideInitialScroll);
|
Fn<bool()> overrideInitialScroll);
|
||||||
bool showAtPositionNow(
|
bool showAtPositionNow(
|
||||||
Data::MessagePosition position,
|
Data::MessagePosition position,
|
||||||
anim::type animated,
|
const Window::SectionShow ¶ms,
|
||||||
Fn<void(bool found)> done);
|
Fn<void(bool found)> done);
|
||||||
|
|
||||||
Ui::ChatPaintContext preparePaintContext(const QRect &clip) const;
|
Ui::ChatPaintContext preparePaintContext(const QRect &clip) const;
|
||||||
|
@ -645,6 +649,7 @@ private:
|
||||||
base::flat_map<MsgId, Ui::PeerUserpicView> _hiddenSenderUserpics;
|
base::flat_map<MsgId, Ui::PeerUserpicView> _hiddenSenderUserpics;
|
||||||
|
|
||||||
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||||
|
QPainterPath _highlightPathCache;
|
||||||
|
|
||||||
base::unique_qptr<Ui::RpWidget> _emptyInfo = nullptr;
|
base::unique_qptr<Ui::RpWidget> _emptyInfo = nullptr;
|
||||||
|
|
||||||
|
|
|
@ -65,42 +65,6 @@ const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool CheckQuoteEntities(
|
|
||||||
const EntitiesInText "eEntities,
|
|
||||||
const TextWithEntities &original,
|
|
||||||
TextSelection selection) {
|
|
||||||
auto left = quoteEntities;
|
|
||||||
const auto allowed = std::array{
|
|
||||||
EntityType::Bold,
|
|
||||||
EntityType::Italic,
|
|
||||||
EntityType::Underline,
|
|
||||||
EntityType::StrikeOut,
|
|
||||||
EntityType::Spoiler,
|
|
||||||
EntityType::CustomEmoji,
|
|
||||||
};
|
|
||||||
for (const auto &entity : original.entities) {
|
|
||||||
const auto from = entity.offset();
|
|
||||||
const auto till = from + entity.length();
|
|
||||||
if (till <= selection.from || from >= selection.to) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto quoteFrom = std::max(from, int(selection.from));
|
|
||||||
const auto quoteTill = std::min(till, int(selection.to));
|
|
||||||
const auto cut = EntityInText(
|
|
||||||
entity.type(),
|
|
||||||
quoteFrom - int(selection.from),
|
|
||||||
quoteTill - quoteFrom,
|
|
||||||
entity.data());
|
|
||||||
const auto i = ranges::find(left, cut);
|
|
||||||
if (i != left.end()) {
|
|
||||||
left.erase(i);
|
|
||||||
} else if (ranges::contains(allowed, cut.type())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return left.empty();
|
|
||||||
};
|
|
||||||
|
|
||||||
class KeyboardStyle : public ReplyKeyboard::Style {
|
class KeyboardStyle : public ReplyKeyboard::Style {
|
||||||
public:
|
public:
|
||||||
KeyboardStyle(const style::BotKeyboardButton &st);
|
KeyboardStyle(const style::BotKeyboardButton &st);
|
||||||
|
@ -1018,6 +982,10 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||||
p.translate(-reactionsPosition);
|
p.translate(-reactionsPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.highlightPathCache) {
|
||||||
|
context.highlightInterpolateTo = g;
|
||||||
|
context.highlightPathCache->clear();
|
||||||
|
}
|
||||||
if (bubble) {
|
if (bubble) {
|
||||||
if (displayFromName()
|
if (displayFromName()
|
||||||
&& item->displayFrom()
|
&& item->displayFrom()
|
||||||
|
@ -1110,6 +1078,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||||
- (_bottomInfo.height() - st::msgDateFont->height));
|
- (_bottomInfo.height() - st::msgDateFont->height));
|
||||||
}
|
}
|
||||||
auto textSelection = context.selection;
|
auto textSelection = context.selection;
|
||||||
|
auto highlightRange = context.highlight.range;
|
||||||
const auto mediaHeight = mediaDisplayed ? media->height() : 0;
|
const auto mediaHeight = mediaDisplayed ? media->height() : 0;
|
||||||
const auto paintMedia = [&](int top) {
|
const auto paintMedia = [&](int top) {
|
||||||
if (!mediaDisplayed) {
|
if (!mediaDisplayed) {
|
||||||
|
@ -1118,6 +1087,8 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||||
const auto mediaSelection = _invertMedia
|
const auto mediaSelection = _invertMedia
|
||||||
? context.selection
|
? context.selection
|
||||||
: skipTextSelection(context.selection);
|
: skipTextSelection(context.selection);
|
||||||
|
const auto maybeMediaHighlight = context.highlightPathCache
|
||||||
|
&& context.highlightPathCache->isEmpty();
|
||||||
auto mediaPosition = QPoint(inner.left(), top);
|
auto mediaPosition = QPoint(inner.left(), top);
|
||||||
p.translate(mediaPosition);
|
p.translate(mediaPosition);
|
||||||
media->draw(p, context.translated(
|
media->draw(p, context.translated(
|
||||||
|
@ -1130,6 +1101,10 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||||
context.reactionInfo->effectOffset -= add;
|
context.reactionInfo->effectOffset -= add;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (maybeMediaHighlight
|
||||||
|
&& !context.highlightPathCache->isEmpty()) {
|
||||||
|
context.highlightPathCache->translate(mediaPosition);
|
||||||
|
}
|
||||||
p.translate(-mediaPosition);
|
p.translate(-mediaPosition);
|
||||||
};
|
};
|
||||||
if (mediaDisplayed && _invertMedia) {
|
if (mediaDisplayed && _invertMedia) {
|
||||||
|
@ -1141,8 +1116,12 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||||
+ mediaHeight
|
+ mediaHeight
|
||||||
+ (mediaOnBottom ? 0 : st::mediaInBubbleSkip));
|
+ (mediaOnBottom ? 0 : st::mediaInBubbleSkip));
|
||||||
textSelection = media->skipSelection(textSelection);
|
textSelection = media->skipSelection(textSelection);
|
||||||
|
highlightRange = media->skipSelection(highlightRange);
|
||||||
}
|
}
|
||||||
paintText(p, trect, context.withSelection(textSelection));
|
auto copy = context;
|
||||||
|
copy.selection = textSelection;
|
||||||
|
copy.highlight.range = highlightRange;
|
||||||
|
paintText(p, trect, copy);
|
||||||
if (mediaDisplayed && !_invertMedia) {
|
if (mediaDisplayed && !_invertMedia) {
|
||||||
paintMedia(trect.y() + trect.height() - mediaHeight);
|
paintMedia(trect.y() + trect.height() - mediaHeight);
|
||||||
if (context.reactionInfo && !displayInfo && !_reactions) {
|
if (context.reactionInfo && !displayInfo && !_reactions) {
|
||||||
|
@ -1224,6 +1203,20 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||||
|
|
||||||
p.restoreTextPalette();
|
p.restoreTextPalette();
|
||||||
|
|
||||||
|
if (context.highlightPathCache
|
||||||
|
&& !context.highlightPathCache->isEmpty()) {
|
||||||
|
const auto alpha = int(0.25
|
||||||
|
* context.highlight.collapsion
|
||||||
|
* context.highlight.opacity
|
||||||
|
* 255);
|
||||||
|
if (alpha > 0) {
|
||||||
|
context.highlightPathCache->setFillRule(Qt::WindingFill);
|
||||||
|
auto color = context.messageStyle()->textPalette.linkFg->c;
|
||||||
|
color.setAlpha(alpha);
|
||||||
|
p.fillPath(*context.highlightPathCache, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (roll) {
|
if (roll) {
|
||||||
p.restore();
|
p.restore();
|
||||||
}
|
}
|
||||||
|
@ -1651,6 +1644,7 @@ void Message::paintText(
|
||||||
width());
|
width());
|
||||||
trect.setY(trect.y() + botTop->height);
|
trect.setY(trect.y() + botTop->height);
|
||||||
}
|
}
|
||||||
|
auto highlightRequest = context.computeHighlightCache();
|
||||||
text().draw(p, {
|
text().draw(p, {
|
||||||
.position = trect.topLeft(),
|
.position = trect.topLeft(),
|
||||||
.availableWidth = trect.width(),
|
.availableWidth = trect.width(),
|
||||||
|
@ -1663,6 +1657,7 @@ void Message::paintText(
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||||
.selection = context.selection,
|
.selection = context.selection,
|
||||||
|
.highlight = highlightRequest ? &*highlightRequest : nullptr,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2651,7 +2646,7 @@ TextForMimeData Message::selectedText(TextSelection selection) const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities Message::selectedQuote(TextSelection selection) const {
|
SelectedQuote Message::selectedQuote(TextSelection selection) const {
|
||||||
const auto item = data();
|
const auto item = data();
|
||||||
const auto &translated = item->translatedText();
|
const auto &translated = item->translatedText();
|
||||||
const auto &original = item->originalText();
|
const auto &original = item->originalText();
|
||||||
|
@ -2666,7 +2661,7 @@ TextWithEntities Message::selectedQuote(TextSelection selection) const {
|
||||||
const auto textSelection = mediaBefore
|
const auto textSelection = mediaBefore
|
||||||
? media->skipSelection(selection)
|
? media->skipSelection(selection)
|
||||||
: selection;
|
: selection;
|
||||||
return selectedQuote(text(), textSelection);
|
return FindSelectedQuote(text(), textSelection, data());
|
||||||
} else if (const auto media = this->media()) {
|
} else if (const auto media = this->media()) {
|
||||||
if (media->isDisplayed() || isHiddenByGroup()) {
|
if (media->isDisplayed() || isHiddenByGroup()) {
|
||||||
return media->selectedQuote(selection);
|
return media->selectedQuote(selection);
|
||||||
|
@ -2675,124 +2670,30 @@ TextWithEntities Message::selectedQuote(TextSelection selection) const {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities Message::selectedQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
TextSelection selection) const {
|
|
||||||
if (selection.to > text.length()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
auto modified = selection;
|
|
||||||
for (const auto &modification : text.modifications()) {
|
|
||||||
if (modification.position >= selection.to) {
|
|
||||||
break;
|
|
||||||
} else if (modification.position <= selection.from) {
|
|
||||||
modified.from += modification.skipped;
|
|
||||||
if (modification.added
|
|
||||||
&& modification.position < selection.from) {
|
|
||||||
--modified.from;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modified.to += modification.skipped;
|
|
||||||
if (modification.added && modified.to > modified.from) {
|
|
||||||
--modified.to;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto result = data()->originalText();
|
|
||||||
if (modified.empty() || modified.to > result.text.size()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
result.text = result.text.mid(
|
|
||||||
modified.from,
|
|
||||||
modified.to - modified.from);
|
|
||||||
const auto allowed = std::array{
|
|
||||||
EntityType::Bold,
|
|
||||||
EntityType::Italic,
|
|
||||||
EntityType::Underline,
|
|
||||||
EntityType::StrikeOut,
|
|
||||||
EntityType::Spoiler,
|
|
||||||
EntityType::CustomEmoji,
|
|
||||||
};
|
|
||||||
for (auto i = result.entities.begin(); i != result.entities.end();) {
|
|
||||||
const auto offset = i->offset();
|
|
||||||
const auto till = offset + i->length();
|
|
||||||
if ((till <= modified.from)
|
|
||||||
|| (offset >= modified.to)
|
|
||||||
|| !ranges::contains(allowed, i->type())) {
|
|
||||||
i = result.entities.erase(i);
|
|
||||||
} else {
|
|
||||||
if (till > modified.to) {
|
|
||||||
i->shrinkFromRight(till - modified.to);
|
|
||||||
}
|
|
||||||
i->shiftLeft(modified.from);
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TextSelection Message::selectionFromQuote(
|
TextSelection Message::selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const {
|
const TextWithEntities "e) const {
|
||||||
const auto item = data();
|
if (quote.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
const auto &translated = item->translatedText();
|
const auto &translated = item->translatedText();
|
||||||
const auto &original = item->originalText();
|
const auto &original = item->originalText();
|
||||||
if (&translated != &original || quote.empty()) {
|
if (&translated != &original) {
|
||||||
return {};
|
return {};
|
||||||
} else if (hasVisibleText()) {
|
} else if (hasVisibleText()) {
|
||||||
const auto media = this->media();
|
const auto media = this->media();
|
||||||
const auto mediaDisplayed = media && media->isDisplayed();
|
const auto mediaDisplayed = media && media->isDisplayed();
|
||||||
const auto mediaBefore = mediaDisplayed && invertMedia();
|
const auto mediaBefore = mediaDisplayed && invertMedia();
|
||||||
const auto result = selectionFromQuote(text(), quote);
|
const auto result = FindSelectionFromQuote(text(), item, quote);
|
||||||
return mediaBefore ? media->unskipSelection(result) : result;
|
return mediaBefore ? media->unskipSelection(result) : result;
|
||||||
} else if (const auto media = this->media()) {
|
} else if (const auto media = this->media()) {
|
||||||
if (media->isDisplayed() || isHiddenByGroup()) {
|
if (media->isDisplayed() || isHiddenByGroup()) {
|
||||||
return media->selectionFromQuote(quote);
|
return media->selectionFromQuote(item, quote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSelection Message::selectionFromQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
const TextWithEntities "e) const {
|
|
||||||
if (quote.empty()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const auto &original = data()->originalText();
|
|
||||||
auto result = TextSelection();
|
|
||||||
auto offset = 0;
|
|
||||||
while (true) {
|
|
||||||
const auto i = original.text.indexOf(quote.text, offset);
|
|
||||||
if (i < 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
auto selection = TextSelection{
|
|
||||||
uint16(i),
|
|
||||||
uint16(i + quote.text.size()),
|
|
||||||
};
|
|
||||||
if (CheckQuoteEntities(quote.entities, original, selection)) {
|
|
||||||
result = selection;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
offset = i + 1;
|
|
||||||
}
|
|
||||||
//for (const auto &modification : text.modifications()) {
|
|
||||||
// if (modification.position >= selection.to) {
|
|
||||||
// break;
|
|
||||||
// } else if (modification.position <= selection.from) {
|
|
||||||
// modified.from += modification.skipped;
|
|
||||||
// if (modification.added
|
|
||||||
// && modification.position < selection.from) {
|
|
||||||
// --modified.from;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// modified.to += modification.skipped;
|
|
||||||
// if (modification.added && modified.to > modified.from) {
|
|
||||||
// --modified.to;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TextSelection Message::adjustSelection(
|
TextSelection Message::adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
TextSelectType type) const {
|
TextSelectType type) const {
|
||||||
|
@ -3247,6 +3148,16 @@ bool Message::toggleSelectionByHandlerClick(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Message::allowTextSelectionByHandler(
|
||||||
|
const ClickHandlerPtr &handler) const {
|
||||||
|
if (const auto media = this->media()) {
|
||||||
|
if (media->allowTextSelectionByHandler(handler)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool Message::hasFromName() const {
|
bool Message::hasFromName() const {
|
||||||
switch (context()) {
|
switch (context()) {
|
||||||
case Context::AdminLog:
|
case Context::AdminLog:
|
||||||
|
@ -4148,7 +4059,7 @@ void Message::refreshInfoSkipBlock() {
|
||||||
return false;
|
return false;
|
||||||
} else if (item->Has<HistoryMessageLogEntryOriginal>()) {
|
} else if (item->Has<HistoryMessageLogEntryOriginal>()) {
|
||||||
return false;
|
return false;
|
||||||
} else if (media && media->isDisplayed()) {
|
} else if (media && media->isDisplayed() && !_invertMedia) {
|
||||||
return false;
|
return false;
|
||||||
} else if (_reactions) {
|
} else if (_reactions) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -95,14 +95,9 @@ public:
|
||||||
QPoint point,
|
QPoint point,
|
||||||
InfoDisplayType type) const override;
|
InfoDisplayType type) const override;
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(TextSelection selection) const override;
|
SelectedQuote selectedQuote(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
TextSelection selection) const override;
|
|
||||||
TextSelection selectionFromQuote(
|
TextSelection selectionFromQuote(
|
||||||
const TextWithEntities "e) const override;
|
not_null<HistoryItem*> item,
|
||||||
TextSelection selectionFromQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
const TextWithEntities "e) const override;
|
const TextWithEntities "e) const override;
|
||||||
TextSelection adjustSelection(
|
TextSelection adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
|
@ -145,6 +140,8 @@ public:
|
||||||
[[nodiscard]] HistoryMessageReply *displayedReply() 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(
|
||||||
|
const ClickHandlerPtr &handler) const override;
|
||||||
[[nodiscard]] int infoWidth() const override;
|
[[nodiscard]] int infoWidth() const override;
|
||||||
[[nodiscard]] int bottomInfoFirstLineWidth() const override;
|
[[nodiscard]] int bottomInfoFirstLineWidth() const override;
|
||||||
[[nodiscard]] bool bottomInfoIsWide() const override;
|
[[nodiscard]] bool bottomInfoIsWide() const override;
|
||||||
|
|
|
@ -262,7 +262,7 @@ void PinnedWidget::showAtPosition(
|
||||||
FullMsgId originId) {
|
FullMsgId originId) {
|
||||||
_inner->showAtPosition(
|
_inner->showAtPosition(
|
||||||
position,
|
position,
|
||||||
anim::type::normal,
|
{},
|
||||||
_cornerButtons.doneJumpFrom(position.fullId, originId));
|
_cornerButtons.doneJumpFrom(position.fullId, originId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,7 +346,7 @@ void PinnedWidget::restoreState(not_null<PinnedMemento*> memento) {
|
||||||
? FullMsgId(_history->peer->id, highlight)
|
? FullMsgId(_history->peer->id, highlight)
|
||||||
: FullMsgId(_migratedPeer->id, -highlight)),
|
: FullMsgId(_migratedPeer->id, -highlight)),
|
||||||
.date = TimeId(0),
|
.date = TimeId(0),
|
||||||
}, anim::type::instant);
|
}, { Window::SectionShow::Way::Forward, anim::type::instant });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -128,10 +128,12 @@ rpl::producer<Ui::MessageBarContent> RootViewContent(
|
||||||
RepliesMemento::RepliesMemento(
|
RepliesMemento::RepliesMemento(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
MsgId rootId,
|
MsgId rootId,
|
||||||
MsgId highlightId)
|
MsgId highlightId,
|
||||||
|
const TextWithEntities &highlightPart)
|
||||||
: _history(history)
|
: _history(history)
|
||||||
, _rootId(rootId)
|
, _rootId(rootId)
|
||||||
, _highlightId(highlightId) {
|
, _highlightId(highlightId)
|
||||||
|
, _highlightPart(highlightPart) {
|
||||||
if (highlightId) {
|
if (highlightId) {
|
||||||
_list.setAroundPosition({
|
_list.setAroundPosition({
|
||||||
.fullId = FullMsgId(_history->peer->id, highlightId),
|
.fullId = FullMsgId(_history->peer->id, highlightId),
|
||||||
|
@ -328,6 +330,7 @@ RepliesWidget::RepliesWidget(
|
||||||
Controls::ShowReplyToChatBox(controller->uiShow(), { fullId });
|
Controls::ShowReplyToChatBox(controller->uiShow(), { fullId });
|
||||||
} else {
|
} else {
|
||||||
replyToMessage(fullId);
|
replyToMessage(fullId);
|
||||||
|
_composeControls->focus();
|
||||||
}
|
}
|
||||||
}, _inner->lifetime());
|
}, _inner->lifetime());
|
||||||
|
|
||||||
|
@ -490,6 +493,7 @@ void RepliesWidget::setupTopicViewer() {
|
||||||
) | rpl::start_with_next([=](const Data::Session::IdChange &change) {
|
) | rpl::start_with_next([=](const Data::Session::IdChange &change) {
|
||||||
if (_rootId == change.oldId) {
|
if (_rootId == change.oldId) {
|
||||||
_rootId = change.newId.msg;
|
_rootId = change.newId.msg;
|
||||||
|
_composeControls->updateTopicRootId(_rootId);
|
||||||
_sendAction = owner->sendActionManager().repliesPainter(
|
_sendAction = owner->sendActionManager().repliesPainter(
|
||||||
_history,
|
_history,
|
||||||
_rootId);
|
_rootId);
|
||||||
|
@ -787,9 +791,11 @@ void RepliesWidget::setupComposeControls() {
|
||||||
sendInlineResult(chosen.result, chosen.bot, chosen.options, localId);
|
sendInlineResult(chosen.result, chosen.bot, chosen.options, localId);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_composeControls->scrollRequests(
|
_composeControls->jumpToItemRequests(
|
||||||
) | rpl::start_with_next([=](Data::MessagePosition pos) {
|
) | rpl::start_with_next([=](FullReplyTo to) {
|
||||||
showAtPosition(pos);
|
if (const auto item = session().data().message(to.messageId)) {
|
||||||
|
JumpToMessageClickHandler(item, {}, to.quote)->onClick({});
|
||||||
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_composeControls->scrollKeyEvents(
|
_composeControls->scrollKeyEvents(
|
||||||
|
@ -1859,16 +1865,22 @@ void RepliesWidget::finishSending() {
|
||||||
refreshTopBarActiveChat();
|
refreshTopBarActiveChat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RepliesWidget::showAtPosition(
|
||||||
|
Data::MessagePosition position,
|
||||||
|
FullMsgId originItemId) {
|
||||||
|
showAtPosition(position, originItemId, {});
|
||||||
|
}
|
||||||
|
|
||||||
void RepliesWidget::showAtPosition(
|
void RepliesWidget::showAtPosition(
|
||||||
Data::MessagePosition position,
|
Data::MessagePosition position,
|
||||||
FullMsgId originItemId,
|
FullMsgId originItemId,
|
||||||
anim::type animated) {
|
const Window::SectionShow ¶ms) {
|
||||||
_lastShownAt = position.fullId;
|
_lastShownAt = position.fullId;
|
||||||
controller()->setActiveChatEntry(activeChat());
|
controller()->setActiveChatEntry(activeChat());
|
||||||
const auto ignore = (position.fullId.msg == _rootId);
|
const auto ignore = (position.fullId.msg == _rootId);
|
||||||
_inner->showAtPosition(
|
_inner->showAtPosition(
|
||||||
position,
|
position,
|
||||||
animated,
|
params,
|
||||||
_cornerButtons.doneJumpFrom(position.fullId, originItemId, ignore));
|
_cornerButtons.doneJumpFrom(position.fullId, originItemId, ignore));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1960,7 +1972,7 @@ bool RepliesWidget::showInternal(
|
||||||
if (logMemento->getHistory() == history()
|
if (logMemento->getHistory() == history()
|
||||||
&& logMemento->getRootId() == _rootId) {
|
&& logMemento->getRootId() == _rootId) {
|
||||||
restoreState(logMemento);
|
restoreState(logMemento);
|
||||||
if (!logMemento->getHighlightId()) {
|
if (!logMemento->highlightId()) {
|
||||||
showAtPosition(Data::UnreadMessagePosition);
|
showAtPosition(Data::UnreadMessagePosition);
|
||||||
}
|
}
|
||||||
if (params.reapplyLocalDraft) {
|
if (params.reapplyLocalDraft) {
|
||||||
|
@ -2008,7 +2020,7 @@ bool RepliesWidget::showMessage(
|
||||||
}
|
}
|
||||||
const auto id = FullMsgId(_history->peer->id, messageId);
|
const auto id = FullMsgId(_history->peer->id, messageId);
|
||||||
const auto message = _history->owner().message(id);
|
const auto message = _history->owner().message(id);
|
||||||
if (!message) {
|
if (!message || !message->inThread(_rootId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto originMessage = [&]() -> HistoryItem* {
|
const auto originMessage = [&]() -> HistoryItem* {
|
||||||
|
@ -2024,13 +2036,13 @@ bool RepliesWidget::showMessage(
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}();
|
}();
|
||||||
if (!originMessage) {
|
const auto currentReplyReturn = _cornerButtons.replyReturn();
|
||||||
return false;
|
const auto originItemId = !originMessage
|
||||||
}
|
? FullMsgId()
|
||||||
const auto originItemId = (_cornerButtons.replyReturn() != originMessage)
|
: (currentReplyReturn != originMessage)
|
||||||
? originMessage->fullId()
|
? originMessage->fullId()
|
||||||
: FullMsgId();
|
: FullMsgId();
|
||||||
showAtPosition(message->position(), originItemId);
|
showAtPosition(message->position(), originItemId, params);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2132,11 +2144,15 @@ void RepliesWidget::restoreState(not_null<RepliesMemento*> memento) {
|
||||||
}
|
}
|
||||||
_cornerButtons.setReplyReturns(memento->replyReturns());
|
_cornerButtons.setReplyReturns(memento->replyReturns());
|
||||||
_inner->restoreState(memento->list());
|
_inner->restoreState(memento->list());
|
||||||
if (const auto highlight = memento->getHighlightId()) {
|
if (const auto highlight = memento->highlightId()) {
|
||||||
|
auto params = Window::SectionShow(
|
||||||
|
Window::SectionShow::Way::Forward,
|
||||||
|
anim::type::instant);
|
||||||
|
params.highlightPart = memento->highlightPart();
|
||||||
showAtPosition(Data::MessagePosition{
|
showAtPosition(Data::MessagePosition{
|
||||||
.fullId = FullMsgId(_history->peer->id, highlight),
|
.fullId = FullMsgId(_history->peer->id, highlight),
|
||||||
.date = TimeId(0),
|
.date = TimeId(0),
|
||||||
}, {}, anim::type::instant);
|
}, {}, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -208,8 +208,11 @@ private:
|
||||||
void showAtEnd();
|
void showAtEnd();
|
||||||
void showAtPosition(
|
void showAtPosition(
|
||||||
Data::MessagePosition position,
|
Data::MessagePosition position,
|
||||||
FullMsgId originItemId = {},
|
FullMsgId originItemId = {});
|
||||||
anim::type animated = anim::type::normal);
|
void showAtPosition(
|
||||||
|
Data::MessagePosition position,
|
||||||
|
FullMsgId originItemId,
|
||||||
|
const Window::SectionShow ¶ms);
|
||||||
void finishSending();
|
void finishSending();
|
||||||
|
|
||||||
void setupComposeControls();
|
void setupComposeControls();
|
||||||
|
@ -377,7 +380,8 @@ public:
|
||||||
RepliesMemento(
|
RepliesMemento(
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
MsgId rootId,
|
MsgId rootId,
|
||||||
MsgId highlightId = 0);
|
MsgId highlightId = 0,
|
||||||
|
const TextWithEntities &highlightPart = {});
|
||||||
explicit RepliesMemento(
|
explicit RepliesMemento(
|
||||||
not_null<HistoryItem*> commentsItem,
|
not_null<HistoryItem*> commentsItem,
|
||||||
MsgId commentId = 0);
|
MsgId commentId = 0);
|
||||||
|
@ -421,9 +425,12 @@ public:
|
||||||
[[nodiscard]] not_null<ListMemento*> list() {
|
[[nodiscard]] not_null<ListMemento*> list() {
|
||||||
return &_list;
|
return &_list;
|
||||||
}
|
}
|
||||||
[[nodiscard]] MsgId getHighlightId() const {
|
[[nodiscard]] MsgId highlightId() const {
|
||||||
return _highlightId;
|
return _highlightId;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] const TextWithEntities &highlightPart() const {
|
||||||
|
return _highlightPart;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupTopicViewer();
|
void setupTopicViewer();
|
||||||
|
@ -431,6 +438,7 @@ private:
|
||||||
const not_null<History*> _history;
|
const not_null<History*> _history;
|
||||||
MsgId _rootId = 0;
|
MsgId _rootId = 0;
|
||||||
const MsgId _highlightId = 0;
|
const MsgId _highlightId = 0;
|
||||||
|
const TextWithEntities _highlightPart;
|
||||||
ListMemento _list;
|
ListMemento _list;
|
||||||
std::shared_ptr<Data::RepliesList> _replies;
|
std::shared_ptr<Data::RepliesList> _replies;
|
||||||
QVector<FullMsgId> _replyReturns;
|
QVector<FullMsgId> _replyReturns;
|
||||||
|
|
|
@ -274,9 +274,15 @@ void ScheduledWidget::setupComposeControls() {
|
||||||
sendInlineResult(chosen.result, chosen.bot);
|
sendInlineResult(chosen.result, chosen.bot);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_composeControls->scrollRequests(
|
_composeControls->jumpToItemRequests(
|
||||||
) | rpl::start_with_next([=](Data::MessagePosition pos) {
|
) | rpl::start_with_next([=](FullReplyTo to) {
|
||||||
showAtPosition(pos);
|
if (const auto item = session().data().message(to.messageId)) {
|
||||||
|
if (item->isScheduled() && item->history() == _history) {
|
||||||
|
showAtPosition(item->position());
|
||||||
|
} else {
|
||||||
|
JumpToMessageClickHandler(item, {}, to.quote)->onClick({});
|
||||||
|
}
|
||||||
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_composeControls->scrollKeyEvents(
|
_composeControls->scrollKeyEvents(
|
||||||
|
@ -858,7 +864,7 @@ void ScheduledWidget::showAtPosition(
|
||||||
FullMsgId originId) {
|
FullMsgId originId) {
|
||||||
_inner->showAtPosition(
|
_inner->showAtPosition(
|
||||||
position,
|
position,
|
||||||
anim::type::normal,
|
{},
|
||||||
_cornerButtons.doneJumpFrom(position.fullId, originId));
|
_cornerButtons.doneJumpFrom(position.fullId, originId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -669,23 +669,12 @@ TextForMimeData Service::selectedText(TextSelection selection) const {
|
||||||
return text().toTextForMimeData(selection);
|
return text().toTextForMimeData(selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities Service::selectedQuote(TextSelection selection) const {
|
SelectedQuote Service::selectedQuote(TextSelection selection) const {
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
TextWithEntities Service::selectedQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
TextSelection selection) const {
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSelection Service::selectionFromQuote(
|
TextSelection Service::selectionFromQuote(
|
||||||
const TextWithEntities "e) const {
|
not_null<HistoryItem*> item,
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
TextSelection Service::selectionFromQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
const TextWithEntities "e) const {
|
const TextWithEntities "e) const {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,14 +43,9 @@ public:
|
||||||
StateRequest request) const override;
|
StateRequest request) const override;
|
||||||
void updatePressed(QPoint point) override;
|
void updatePressed(QPoint point) override;
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(TextSelection selection) const override;
|
SelectedQuote selectedQuote(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
TextSelection selection) const override;
|
|
||||||
TextSelection selectionFromQuote(
|
TextSelection selectionFromQuote(
|
||||||
const TextWithEntities "e) const override;
|
not_null<HistoryItem*> item,
|
||||||
TextSelection selectionFromQuote(
|
|
||||||
const Ui::Text::String &text,
|
|
||||||
const TextWithEntities "e) const override;
|
const TextWithEntities "e) const override;
|
||||||
TextSelection adjustSelection(
|
TextSelection adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
|
|
|
@ -744,6 +744,7 @@ void Document::draw(
|
||||||
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||||
p.setPen(stm->historyTextFg);
|
p.setPen(stm->historyTextFg);
|
||||||
_parent->prepareCustomEmojiPaint(p, context, captioned->caption);
|
_parent->prepareCustomEmojiPaint(p, context, captioned->caption);
|
||||||
|
auto highlightRequest = context.computeHighlightCache();
|
||||||
captioned->caption.draw(p, {
|
captioned->caption.draw(p, {
|
||||||
.position = { st::msgPadding.left(), captiontop },
|
.position = { st::msgPadding.left(), captiontop },
|
||||||
.availableWidth = captionw,
|
.availableWidth = captionw,
|
||||||
|
@ -756,6 +757,7 @@ void Document::draw(
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||||
.selection = selection,
|
.selection = selection,
|
||||||
|
.highlight = highlightRequest ? &*highlightRequest : nullptr,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1210,7 +1212,7 @@ TextForMimeData Document::selectedText(TextSelection selection) const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities Document::selectedQuote(TextSelection selection) const {
|
SelectedQuote Document::selectedQuote(TextSelection selection) const {
|
||||||
if (const auto voice = Get<HistoryDocumentVoice>()) {
|
if (const auto voice = Get<HistoryDocumentVoice>()) {
|
||||||
const auto length = voice->transcribeText.length();
|
const auto length = voice->transcribeText.length();
|
||||||
if (selection.from < length) {
|
if (selection.from < length) {
|
||||||
|
@ -1221,16 +1223,21 @@ TextWithEntities Document::selectedQuote(TextSelection selection) const {
|
||||||
voice->transcribeText);
|
voice->transcribeText);
|
||||||
}
|
}
|
||||||
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||||
return parent()->selectedQuote(captioned->caption, selection);
|
return Element::FindSelectedQuote(
|
||||||
|
captioned->caption,
|
||||||
|
selection,
|
||||||
|
_realParent);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSelection Document::selectionFromQuote(
|
TextSelection Document::selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const {
|
const TextWithEntities "e) const {
|
||||||
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||||
const auto result = parent()->selectionFromQuote(
|
const auto result = Element::FindSelectionFromQuote(
|
||||||
captioned->caption,
|
captioned->caption,
|
||||||
|
item,
|
||||||
quote);
|
quote);
|
||||||
if (result.empty()) {
|
if (result.empty()) {
|
||||||
return {};
|
return {};
|
||||||
|
@ -1390,6 +1397,8 @@ void Document::drawGrouped(
|
||||||
float64 highlightOpacity,
|
float64 highlightOpacity,
|
||||||
not_null<uint64*> cacheKey,
|
not_null<uint64*> cacheKey,
|
||||||
not_null<QPixmap*> cache) const {
|
not_null<QPixmap*> cache) const {
|
||||||
|
const auto maybeMediaHighlight = context.highlightPathCache
|
||||||
|
&& context.highlightPathCache->isEmpty();
|
||||||
p.translate(geometry.topLeft());
|
p.translate(geometry.topLeft());
|
||||||
draw(
|
draw(
|
||||||
p,
|
p,
|
||||||
|
@ -1397,6 +1406,10 @@ void Document::drawGrouped(
|
||||||
geometry.width(),
|
geometry.width(),
|
||||||
LayoutMode::Grouped,
|
LayoutMode::Grouped,
|
||||||
rounding);
|
rounding);
|
||||||
|
if (maybeMediaHighlight
|
||||||
|
&& !context.highlightPathCache->isEmpty()) {
|
||||||
|
context.highlightPathCache->translate(geometry.topLeft());
|
||||||
|
}
|
||||||
p.translate(-geometry.topLeft());
|
p.translate(-geometry.topLeft());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,9 @@ public:
|
||||||
bool hasTextForCopy() const override;
|
bool hasTextForCopy() const override;
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(TextSelection selection) const override;
|
SelectedQuote selectedQuote(TextSelection selection) const override;
|
||||||
TextSelection selectionFromQuote(
|
TextSelection selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const override;
|
const TextWithEntities "e) const override;
|
||||||
|
|
||||||
bool uploading() const override;
|
bool uploading() const override;
|
||||||
|
|
|
@ -229,6 +229,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const {
|
||||||
if (!_caption.isEmpty()) {
|
if (!_caption.isEmpty()) {
|
||||||
p.setPen(stm->historyTextFg);
|
p.setPen(stm->historyTextFg);
|
||||||
_parent->prepareCustomEmojiPaint(p, context, _caption);
|
_parent->prepareCustomEmojiPaint(p, context, _caption);
|
||||||
|
auto highlightRequest = context.computeHighlightCache();
|
||||||
_caption.draw(p, {
|
_caption.draw(p, {
|
||||||
.position = QPoint(
|
.position = QPoint(
|
||||||
st::msgPadding.left(),
|
st::msgPadding.left(),
|
||||||
|
@ -243,6 +244,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const {
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||||
.selection = context.selection,
|
.selection = context.selection,
|
||||||
|
.highlight = highlightRequest ? &*highlightRequest : nullptr,
|
||||||
});
|
});
|
||||||
} else if (!inWebPage) {
|
} else if (!inWebPage) {
|
||||||
auto fullRight = paintx + paintw;
|
auto fullRight = paintx + paintw;
|
||||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/text/text_utilities.h"
|
#include "ui/text/text_utilities.h"
|
||||||
#include "ui/cached_round_corners.h"
|
#include "ui/cached_round_corners.h"
|
||||||
#include "ui/chat/chat_style.h"
|
#include "ui/chat/chat_style.h"
|
||||||
|
#include "ui/effects/ripple_animation.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
#include "ui/power_saving.h"
|
#include "ui/power_saving.h"
|
||||||
#include "core/ui_integration.h"
|
#include "core/ui_integration.h"
|
||||||
|
@ -226,6 +227,13 @@ void Game::draw(Painter &p, const PaintContext &context) const {
|
||||||
Ui::Text::ValidateQuotePaintCache(*cache, _st);
|
Ui::Text::ValidateQuotePaintCache(*cache, _st);
|
||||||
Ui::Text::FillQuotePaint(p, outer, *cache, _st);
|
Ui::Text::FillQuotePaint(p, outer, *cache, _st);
|
||||||
|
|
||||||
|
if (_ripple) {
|
||||||
|
_ripple->paint(p, outer.x(), outer.y(), width(), &cache->bg);
|
||||||
|
if (_ripple->empty()) {
|
||||||
|
_ripple = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto lineHeight = UnitedLineHeight();
|
auto lineHeight = UnitedLineHeight();
|
||||||
if (_titleLines) {
|
if (_titleLines) {
|
||||||
p.setPen(cache->icon);
|
p.setPen(cache->icon);
|
||||||
|
@ -322,7 +330,6 @@ TextState Game::textState(QPoint point, StateRequest request) const {
|
||||||
auto tshift = inner.top();
|
auto tshift = inner.top();
|
||||||
auto paintw = inner.width();
|
auto paintw = inner.width();
|
||||||
|
|
||||||
auto inThumb = false;
|
|
||||||
auto symbolAdd = 0;
|
auto symbolAdd = 0;
|
||||||
auto lineHeight = UnitedLineHeight();
|
auto lineHeight = UnitedLineHeight();
|
||||||
if (_titleLines) {
|
if (_titleLines) {
|
||||||
|
@ -353,11 +360,7 @@ TextState Game::textState(QPoint point, StateRequest request) const {
|
||||||
}
|
}
|
||||||
tshift += _descriptionLines * lineHeight;
|
tshift += _descriptionLines * lineHeight;
|
||||||
}
|
}
|
||||||
if (inThumb) {
|
if (_attach) {
|
||||||
if (_parent->data()->isHistoryEntry()) {
|
|
||||||
result.link = _openl;
|
|
||||||
}
|
|
||||||
} else if (_attach) {
|
|
||||||
auto attachAtTop = !_titleLines && !_descriptionLines;
|
auto attachAtTop = !_titleLines && !_descriptionLines;
|
||||||
if (!attachAtTop) tshift += st::mediaInBubbleSkip;
|
if (!attachAtTop) tshift += st::mediaInBubbleSkip;
|
||||||
|
|
||||||
|
@ -375,6 +378,12 @@ TextState Game::textState(QPoint point, StateRequest request) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (_parent->data()->isHistoryEntry()) {
|
||||||
|
if (!result.link && outer.contains(point)) {
|
||||||
|
result.link = _openl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_lastPoint = point - outer.topLeft();
|
||||||
|
|
||||||
result.symbol += symbolAdd;
|
result.symbol += symbolAdd;
|
||||||
return result;
|
return result;
|
||||||
|
@ -399,11 +408,41 @@ void Game::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
|
void Game::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
|
||||||
|
if (p == _openl) {
|
||||||
|
if (pressed) {
|
||||||
|
if (!_ripple) {
|
||||||
|
const auto full = QRect(0, 0, width(), height());
|
||||||
|
const auto outer = full.marginsRemoved(inBubblePadding());
|
||||||
|
const auto owner = &parent()->history()->owner();
|
||||||
|
_ripple = std::make_unique<Ui::RippleAnimation>(
|
||||||
|
st::defaultRippleAnimation,
|
||||||
|
Ui::RippleAnimation::RoundRectMask(
|
||||||
|
outer.size(),
|
||||||
|
_st.radius),
|
||||||
|
[=] { owner->requestViewRepaint(parent()); });
|
||||||
|
}
|
||||||
|
_ripple->add(_lastPoint);
|
||||||
|
} else if (_ripple) {
|
||||||
|
_ripple->lastStop();
|
||||||
|
}
|
||||||
|
}
|
||||||
if (_attach) {
|
if (_attach) {
|
||||||
_attach->clickHandlerPressedChanged(p, pressed);
|
_attach->clickHandlerPressedChanged(p, pressed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Game::toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const {
|
||||||
|
return _attach && _attach->toggleSelectionByHandlerClick(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Game::allowTextSelectionByHandler(const ClickHandlerPtr &p) const {
|
||||||
|
return (p == _openl);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Game::dragItemByHandler(const ClickHandlerPtr &p) const {
|
||||||
|
return _attach && _attach->dragItemByHandler(p);
|
||||||
|
}
|
||||||
|
|
||||||
TextForMimeData Game::selectedText(TextSelection selection) const {
|
TextForMimeData Game::selectedText(TextSelection selection) const {
|
||||||
auto titleResult = _title.toTextForMimeData(selection);
|
auto titleResult = _title.toTextForMimeData(selection);
|
||||||
auto descriptionResult = _description.toTextForMimeData(
|
auto descriptionResult = _description.toTextForMimeData(
|
||||||
|
|
|
@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
class ReplyMarkupClickHandler;
|
class ReplyMarkupClickHandler;
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class RippleAnimation;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
|
||||||
class Game : public Media {
|
class Game : public Media {
|
||||||
|
@ -35,12 +39,11 @@ public:
|
||||||
return false; // we do not add _title and _description in FullSelection text copy.
|
return false; // we do not add _title and _description in FullSelection text copy.
|
||||||
}
|
}
|
||||||
|
|
||||||
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
|
bool toggleSelectionByHandlerClick(
|
||||||
return _attach && _attach->toggleSelectionByHandlerClick(p);
|
const ClickHandlerPtr &p) const override;
|
||||||
}
|
bool allowTextSelectionByHandler(
|
||||||
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
|
const ClickHandlerPtr &p) const override;
|
||||||
return _attach && _attach->dragItemByHandler(p);
|
bool dragItemByHandler(const ClickHandlerPtr &p) const override;
|
||||||
}
|
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
|
|
||||||
|
@ -102,7 +105,9 @@ private:
|
||||||
const not_null<GameData*> _data;
|
const not_null<GameData*> _data;
|
||||||
std::shared_ptr<ReplyMarkupClickHandler> _openl;
|
std::shared_ptr<ReplyMarkupClickHandler> _openl;
|
||||||
std::unique_ptr<Media> _attach;
|
std::unique_ptr<Media> _attach;
|
||||||
|
mutable std::unique_ptr<Ui::RippleAnimation> _ripple;
|
||||||
|
|
||||||
|
mutable QPoint _lastPoint;
|
||||||
int _gameTagWidth = 0;
|
int _gameTagWidth = 0;
|
||||||
int _descriptionLines = 0;
|
int _descriptionLines = 0;
|
||||||
int _titleLines = 0;
|
int _titleLines = 0;
|
||||||
|
|
|
@ -716,6 +716,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
|
||||||
_parent->width());
|
_parent->width());
|
||||||
top += botTop->height;
|
top += botTop->height;
|
||||||
}
|
}
|
||||||
|
auto highlightRequest = context.computeHighlightCache();
|
||||||
_caption.draw(p, {
|
_caption.draw(p, {
|
||||||
.position = QPoint(st::msgPadding.left(), top),
|
.position = QPoint(st::msgPadding.left(), top),
|
||||||
.availableWidth = captionw,
|
.availableWidth = captionw,
|
||||||
|
@ -728,6 +729,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||||
.selection = context.selection,
|
.selection = context.selection,
|
||||||
|
.highlight = highlightRequest ? &*highlightRequest : nullptr,
|
||||||
});
|
});
|
||||||
} else if (!inWebPage && !skipDrawingSurrounding) {
|
} else if (!inWebPage && !skipDrawingSurrounding) {
|
||||||
auto fullRight = paintx + usex + usew;
|
auto fullRight = paintx + usex + usew;
|
||||||
|
@ -1203,13 +1205,14 @@ TextForMimeData Gif::selectedText(TextSelection selection) const {
|
||||||
return _caption.toTextForMimeData(selection);
|
return _caption.toTextForMimeData(selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities Gif::selectedQuote(TextSelection selection) const {
|
SelectedQuote Gif::selectedQuote(TextSelection selection) const {
|
||||||
return parent()->selectedQuote(_caption, selection);
|
return Element::FindSelectedQuote(_caption, selection, _realParent);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSelection Gif::selectionFromQuote(
|
TextSelection Gif::selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const {
|
const TextWithEntities "e) const {
|
||||||
return parent()->selectionFromQuote(_caption, quote);
|
return Element::FindSelectionFromQuote(_caption, item, quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Gif::fullFeaturedGrouped(RectParts sides) const {
|
bool Gif::fullFeaturedGrouped(RectParts sides) const {
|
||||||
|
|
|
@ -68,8 +68,9 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(TextSelection selection) const override;
|
SelectedQuote selectedQuote(TextSelection selection) const override;
|
||||||
TextSelection selectionFromQuote(
|
TextSelection selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const override;
|
const TextWithEntities "e) const override;
|
||||||
|
|
||||||
bool uploading() const override;
|
bool uploading() const override;
|
||||||
|
|
|
@ -181,6 +181,11 @@ Storage::SharedMediaTypesMask Media::sharedMediaTypes() const {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Media::allowTextSelectionByHandler(
|
||||||
|
const ClickHandlerPtr &handler) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
not_null<Element*> Media::parent() const {
|
not_null<Element*> Media::parent() const {
|
||||||
return _parent;
|
return _parent;
|
||||||
}
|
}
|
||||||
|
@ -189,6 +194,10 @@ not_null<History*> Media::history() const {
|
||||||
return _parent->history();
|
return _parent->history();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelectedQuote Media::selectedQuote(TextSelection selection) const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
bool Media::isDisplayed() const {
|
bool Media::isDisplayed() const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ struct StateRequest;
|
||||||
struct MediaSpoiler;
|
struct MediaSpoiler;
|
||||||
class StickerPlayer;
|
class StickerPlayer;
|
||||||
class Element;
|
class Element;
|
||||||
|
struct SelectedQuote;
|
||||||
|
|
||||||
using PaintContext = Ui::ChatPaintContext;
|
using PaintContext = Ui::ChatPaintContext;
|
||||||
|
|
||||||
|
@ -88,11 +89,10 @@ public:
|
||||||
TextSelection selection) const {
|
TextSelection selection) const {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
[[nodiscard]] virtual TextWithEntities selectedQuote(
|
[[nodiscard]] virtual SelectedQuote selectedQuote(
|
||||||
TextSelection selection) const {
|
TextSelection selection) const;
|
||||||
return {};
|
|
||||||
}
|
|
||||||
[[nodiscard]] virtual TextSelection selectionFromQuote(
|
[[nodiscard]] virtual TextSelection selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const {
|
const TextWithEntities "e) const {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -136,6 +136,8 @@ public:
|
||||||
// toggle selection instead of activating the pressed link
|
// toggle selection instead of activating the pressed link
|
||||||
[[nodiscard]] virtual bool toggleSelectionByHandlerClick(
|
[[nodiscard]] virtual bool toggleSelectionByHandlerClick(
|
||||||
const ClickHandlerPtr &p) const = 0;
|
const ClickHandlerPtr &p) const = 0;
|
||||||
|
[[nodiscard]] virtual bool allowTextSelectionByHandler(
|
||||||
|
const ClickHandlerPtr &p) const;
|
||||||
|
|
||||||
[[nodiscard]] virtual TextSelection adjustSelection(
|
[[nodiscard]] virtual TextSelection adjustSelection(
|
||||||
TextSelection selection,
|
TextSelection selection,
|
||||||
|
|
|
@ -286,19 +286,45 @@ void GroupedMedia::drawHighlight(
|
||||||
Painter &p,
|
Painter &p,
|
||||||
const PaintContext &context,
|
const PaintContext &context,
|
||||||
int top) const {
|
int top) const {
|
||||||
if (_mode != Mode::Column) {
|
if (context.highlight.opacity == 0.) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto selection = context.highlight.range;
|
||||||
|
if (_mode != Mode::Column) {
|
||||||
|
if (!selection.empty() && !IsSubGroupSelection(selection)) {
|
||||||
|
_parent->paintCustomHighlight(
|
||||||
|
p,
|
||||||
|
context,
|
||||||
|
top,
|
||||||
|
height(),
|
||||||
|
_parent->data().get());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto empty = selection.empty();
|
||||||
|
const auto subpart = IsSubGroupSelection(selection);
|
||||||
const auto skip = top + groupedPadding().top();
|
const auto skip = top + groupedPadding().top();
|
||||||
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
|
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
|
||||||
const auto &part = _parts[i];
|
const auto &part = _parts[i];
|
||||||
const auto rect = part.geometry.translated(0, skip);
|
const auto rect = part.geometry.translated(0, skip);
|
||||||
_parent->paintCustomHighlight(
|
const auto full = (!i && empty)
|
||||||
p,
|
|| (subpart && IsGroupItemSelection(selection, i))
|
||||||
context,
|
|| (!subpart
|
||||||
rect.y(),
|
&& !selection.empty()
|
||||||
rect.height(),
|
&& (selection.from < part.content->fullSelectionLength()));
|
||||||
part.item);
|
if (!subpart) {
|
||||||
|
selection = part.content->skipSelection(selection);
|
||||||
|
}
|
||||||
|
if (full) {
|
||||||
|
auto copy = context;
|
||||||
|
copy.highlight.range = {};
|
||||||
|
_parent->paintCustomHighlight(
|
||||||
|
p,
|
||||||
|
copy,
|
||||||
|
rect.y(),
|
||||||
|
rect.height(),
|
||||||
|
part.item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,21 +342,31 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
|
||||||
const auto rounding = inWebPage
|
const auto rounding = inWebPage
|
||||||
? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall }
|
? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall }
|
||||||
: adjustedBubbleRoundingWithCaption(_caption);
|
: adjustedBubbleRoundingWithCaption(_caption);
|
||||||
|
auto highlight = context.highlight.range;
|
||||||
|
const auto subpartHighlight = IsSubGroupSelection(highlight);
|
||||||
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
|
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
|
||||||
const auto &part = _parts[i];
|
const auto &part = _parts[i];
|
||||||
const auto partContext = context.withSelection(fullSelection
|
auto partContext = context.withSelection(fullSelection
|
||||||
? FullSelection
|
? FullSelection
|
||||||
: textSelection
|
: textSelection
|
||||||
? selection
|
? selection
|
||||||
: IsGroupItemSelection(selection, i)
|
: IsGroupItemSelection(selection, i)
|
||||||
? FullSelection
|
? FullSelection
|
||||||
: TextSelection());
|
: TextSelection());
|
||||||
|
const auto highlighted = (highlight.empty() && !i)
|
||||||
|
|| IsGroupItemSelection(highlight, i);
|
||||||
|
const auto highlightOpacity = highlighted
|
||||||
|
? context.highlight.opacity
|
||||||
|
: 0.;
|
||||||
|
partContext.highlight.range = highlighted
|
||||||
|
? TextSelection()
|
||||||
|
: highlight;
|
||||||
if (textSelection) {
|
if (textSelection) {
|
||||||
selection = part.content->skipSelection(selection);
|
selection = part.content->skipSelection(selection);
|
||||||
}
|
}
|
||||||
const auto highlightOpacity = (_mode == Mode::Grid)
|
if (!subpartHighlight) {
|
||||||
? _parent->delegate()->elementHighlightOpacity(part.item)
|
highlight = part.content->skipSelection(highlight);
|
||||||
: 0.;
|
}
|
||||||
if (!part.cache.isNull()) {
|
if (!part.cache.isNull()) {
|
||||||
wasCache = true;
|
wasCache = true;
|
||||||
}
|
}
|
||||||
|
@ -361,6 +397,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
|
||||||
const auto stm = context.messageStyle();
|
const auto stm = context.messageStyle();
|
||||||
p.setPen(stm->historyTextFg);
|
p.setPen(stm->historyTextFg);
|
||||||
_parent->prepareCustomEmojiPaint(p, context, _caption);
|
_parent->prepareCustomEmojiPaint(p, context, _caption);
|
||||||
|
auto highlightRequest = context.computeHighlightCache();
|
||||||
_caption.draw(p, {
|
_caption.draw(p, {
|
||||||
.position = QPoint(
|
.position = QPoint(
|
||||||
st::msgPadding.left(),
|
st::msgPadding.left(),
|
||||||
|
@ -375,6 +412,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||||
.selection = context.selection,
|
.selection = context.selection,
|
||||||
|
.highlight = highlightRequest ? &*highlightRequest : nullptr,
|
||||||
});
|
});
|
||||||
} else if (_parent->media() == this) {
|
} else if (_parent->media() == this) {
|
||||||
auto fullRight = width();
|
auto fullRight = width();
|
||||||
|
@ -514,6 +552,7 @@ TextSelection GroupedMedia::adjustSelection(
|
||||||
selection.to = modified.to;
|
selection.to = modified.to;
|
||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
|
checked = till;
|
||||||
}
|
}
|
||||||
return selection;
|
return selection;
|
||||||
}
|
}
|
||||||
|
@ -561,6 +600,50 @@ TextForMimeData GroupedMedia::selectedText(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelectedQuote GroupedMedia::selectedQuote(TextSelection selection) const {
|
||||||
|
if (_mode != Mode::Column) {
|
||||||
|
return _captionItem
|
||||||
|
? Element::FindSelectedQuote(_caption, selection, _captionItem)
|
||||||
|
: SelectedQuote();
|
||||||
|
}
|
||||||
|
for (const auto &part : _parts) {
|
||||||
|
const auto next = part.content->skipSelection(selection);
|
||||||
|
if (next.to - next.from != selection.to - selection.from) {
|
||||||
|
if (!next.empty()) {
|
||||||
|
return SelectedQuote();
|
||||||
|
}
|
||||||
|
auto result = part.content->selectedQuote(selection);
|
||||||
|
result.item = part.item;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
selection = next;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
TextSelection GroupedMedia::selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
const TextWithEntities "e) const {
|
||||||
|
if (_mode != Mode::Column) {
|
||||||
|
return (_captionItem == item)
|
||||||
|
? Element::FindSelectionFromQuote(_caption, item, quote)
|
||||||
|
: TextSelection();
|
||||||
|
}
|
||||||
|
const auto i = ranges::find(_parts, item, &Part::item);
|
||||||
|
if (i == end(_parts)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto index = int(i - begin(_parts));
|
||||||
|
auto result = i->content->selectionFromQuote(item, quote);
|
||||||
|
if (result.empty()) {
|
||||||
|
return AddGroupItemSelection({}, index);
|
||||||
|
}
|
||||||
|
for (auto j = i; j != begin(_parts);) {
|
||||||
|
result = (--j)->content->unskipSelection(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
auto GroupedMedia::getBubbleSelectionIntervals(
|
auto GroupedMedia::getBubbleSelectionIntervals(
|
||||||
TextSelection selection) const
|
TextSelection selection) const
|
||||||
-> std::vector<Ui::BubbleSelectionInterval> {
|
-> std::vector<Ui::BubbleSelectionInterval> {
|
||||||
|
@ -663,16 +746,15 @@ bool GroupedMedia::validateGroupParts(
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupedMedia::refreshCaption() {
|
void GroupedMedia::refreshCaption() {
|
||||||
using PartPtrOpt = std::optional<const Part*>;
|
const auto part = [&]() -> const Part* {
|
||||||
const auto captionPart = [&]() -> PartPtrOpt {
|
|
||||||
if (_mode == Mode::Column) {
|
if (_mode == Mode::Column) {
|
||||||
return std::nullopt;
|
return nullptr;
|
||||||
}
|
}
|
||||||
auto result = PartPtrOpt();
|
auto result = (const Part*)nullptr;
|
||||||
for (const auto &part : _parts) {
|
for (const auto &part : _parts) {
|
||||||
if (!part.item->emptyText()) {
|
if (!part.item->emptyText()) {
|
||||||
if (result) {
|
if (result) {
|
||||||
return std::nullopt;
|
return nullptr;
|
||||||
} else {
|
} else {
|
||||||
result = ∂
|
result = ∂
|
||||||
}
|
}
|
||||||
|
@ -680,8 +762,7 @@ void GroupedMedia::refreshCaption() {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}();
|
}();
|
||||||
if (captionPart) {
|
if (part) {
|
||||||
const auto &part = (*captionPart);
|
|
||||||
_caption = createCaption(part->item);
|
_caption = createCaption(part->item);
|
||||||
_captionItem = part->item;
|
_captionItem = part->item;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -55,6 +55,10 @@ public:
|
||||||
DocumentData *getDocument() const override;
|
DocumentData *getDocument() const override;
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
|
SelectedQuote selectedQuote(TextSelection selection) const override;
|
||||||
|
TextSelection selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
const TextWithEntities "e) const override;
|
||||||
|
|
||||||
std::vector<Ui::BubbleSelectionInterval> getBubbleSelectionIntervals(
|
std::vector<Ui::BubbleSelectionInterval> getBubbleSelectionIntervals(
|
||||||
TextSelection selection) const override;
|
TextSelection selection) const override;
|
||||||
|
|
|
@ -104,6 +104,9 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) {
|
||||||
const auto surroundingWidth = _additionalOnTop
|
const auto surroundingWidth = _additionalOnTop
|
||||||
? std::min(newWidth - st::msgReplyPadding.left(), additional)
|
? std::min(newWidth - st::msgReplyPadding.left(), additional)
|
||||||
: (newWidth - _contentSize.width() - st::msgReplyPadding.left());
|
: (newWidth - _contentSize.width() - st::msgReplyPadding.left());
|
||||||
|
if (reply) {
|
||||||
|
[[maybe_unused]] auto h = reply->resizeToWidth(surroundingWidth);
|
||||||
|
}
|
||||||
const auto surrounding = surroundingInfo(topic, via, reply, forwarded, surroundingWidth);
|
const auto surrounding = surroundingInfo(topic, via, reply, forwarded, surroundingWidth);
|
||||||
if (_additionalOnTop) {
|
if (_additionalOnTop) {
|
||||||
_topAdded = surrounding.height + st::msgMargin.bottom();
|
_topAdded = surrounding.height + st::msgMargin.bottom();
|
||||||
|
|
|
@ -401,6 +401,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
|
||||||
_parent->width());
|
_parent->width());
|
||||||
top += botTop->height;
|
top += botTop->height;
|
||||||
}
|
}
|
||||||
|
auto highlightRequest = context.computeHighlightCache();
|
||||||
_caption.draw(p, {
|
_caption.draw(p, {
|
||||||
.position = QPoint(st::msgPadding.left(), top),
|
.position = QPoint(st::msgPadding.left(), top),
|
||||||
.availableWidth = captionw,
|
.availableWidth = captionw,
|
||||||
|
@ -413,6 +414,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
|
||||||
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
|
||||||
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
|
||||||
.selection = context.selection,
|
.selection = context.selection,
|
||||||
|
.highlight = highlightRequest ? &*highlightRequest : nullptr,
|
||||||
});
|
});
|
||||||
} else if (!inWebPage) {
|
} else if (!inWebPage) {
|
||||||
auto fullRight = paintx + paintw;
|
auto fullRight = paintx + paintw;
|
||||||
|
@ -1049,13 +1051,14 @@ TextForMimeData Photo::selectedText(TextSelection selection) const {
|
||||||
return _caption.toTextForMimeData(selection);
|
return _caption.toTextForMimeData(selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities Photo::selectedQuote(TextSelection selection) const {
|
SelectedQuote Photo::selectedQuote(TextSelection selection) const {
|
||||||
return parent()->selectedQuote(_caption, selection);
|
return Element::FindSelectedQuote(_caption, selection, _realParent);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSelection Photo::selectionFromQuote(
|
TextSelection Photo::selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const {
|
const TextWithEntities "e) const {
|
||||||
return parent()->selectionFromQuote(_caption, quote);
|
return Element::FindSelectionFromQuote(_caption, item, quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Photo::hideSpoilers() {
|
void Photo::hideSpoilers() {
|
||||||
|
|
|
@ -57,8 +57,9 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
TextWithEntities selectedQuote(TextSelection selection) const override;
|
SelectedQuote selectedQuote(TextSelection selection) const override;
|
||||||
TextSelection selectionFromQuote(
|
TextSelection selectionFromQuote(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
const TextWithEntities "e) const override;
|
const TextWithEntities "e) const override;
|
||||||
|
|
||||||
PhotoData *getPhoto() const override {
|
PhotoData *getPhoto() const override {
|
||||||
|
|
|
@ -155,7 +155,7 @@ bool WebPage::HasButton(not_null<WebPageData*> webpage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize WebPage::countOptimalSize() {
|
QSize WebPage::countOptimalSize() {
|
||||||
if (_data->pendingTill) {
|
if (_data->pendingTill || _data->failed) {
|
||||||
return { 0, 0 };
|
return { 0, 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,7 +366,7 @@ QSize WebPage::countOptimalSize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize WebPage::countCurrentSize(int newWidth) {
|
QSize WebPage::countCurrentSize(int newWidth) {
|
||||||
if (_data->pendingTill) {
|
if (_data->pendingTill || _data->failed) {
|
||||||
return { newWidth, minHeight() };
|
return { newWidth, minHeight() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -891,6 +891,7 @@ void WebPage::playAnimation(bool autoplay) {
|
||||||
bool WebPage::isDisplayed() const {
|
bool WebPage::isDisplayed() const {
|
||||||
const auto item = _parent->data();
|
const auto item = _parent->data();
|
||||||
return !_data->pendingTill
|
return !_data->pendingTill
|
||||||
|
&& !_data->failed
|
||||||
&& !item->Has<HistoryMessageLogEntryOriginal>();
|
&& !item->Has<HistoryMessageLogEntryOriginal>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -903,6 +904,11 @@ bool WebPage::toggleSelectionByHandlerClick(
|
||||||
return _attach && _attach->toggleSelectionByHandlerClick(p);
|
return _attach && _attach->toggleSelectionByHandlerClick(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WebPage::allowTextSelectionByHandler(
|
||||||
|
const ClickHandlerPtr &p) const {
|
||||||
|
return (p == _openl);
|
||||||
|
}
|
||||||
|
|
||||||
bool WebPage::dragItemByHandler(const ClickHandlerPtr &p) const {
|
bool WebPage::dragItemByHandler(const ClickHandlerPtr &p) const {
|
||||||
return _attach && _attach->dragItemByHandler(p);
|
return _attach && _attach->dragItemByHandler(p);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,8 @@ public:
|
||||||
|
|
||||||
bool toggleSelectionByHandlerClick(
|
bool toggleSelectionByHandlerClick(
|
||||||
const ClickHandlerPtr &p) const override;
|
const ClickHandlerPtr &p) const override;
|
||||||
|
bool allowTextSelectionByHandler(
|
||||||
|
const ClickHandlerPtr &p) const override;
|
||||||
bool dragItemByHandler(const ClickHandlerPtr &p) const override;
|
bool dragItemByHandler(const ClickHandlerPtr &p) const override;
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "info/profile/info_profile_actions.h"
|
#include "info/profile/info_profile_actions.h"
|
||||||
|
|
||||||
|
#include "base/options.h"
|
||||||
#include "data/data_peer_values.h"
|
#include "data/data_peer_values.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_folder.h"
|
#include "data/data_folder.h"
|
||||||
|
@ -77,6 +78,12 @@ namespace Info {
|
||||||
namespace Profile {
|
namespace Profile {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
base::options::toggle ShowPeerIdBelowAbout({
|
||||||
|
.id = kOptionShowPeerIdBelowAbout,
|
||||||
|
.name = "Show Peer IDs in Profile",
|
||||||
|
.description = "Show peer IDs from API below their Bio / Description.",
|
||||||
|
});
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<TextWithEntities> UsernamesSubtext(
|
[[nodiscard]] rpl::producer<TextWithEntities> UsernamesSubtext(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
rpl::producer<QString> fallback) {
|
rpl::producer<QString> fallback) {
|
||||||
|
@ -148,6 +155,27 @@ namespace {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<TextWithEntities> AboutWithIdValue(
|
||||||
|
not_null<PeerData*> peer) {
|
||||||
|
|
||||||
|
return AboutValue(
|
||||||
|
peer
|
||||||
|
) | rpl::map([=](TextWithEntities &&value) {
|
||||||
|
if (!ShowPeerIdBelowAbout.value()) {
|
||||||
|
return std::move(value);
|
||||||
|
}
|
||||||
|
using namespace Ui::Text;
|
||||||
|
if (!value.empty()) {
|
||||||
|
value.append("\n");
|
||||||
|
}
|
||||||
|
value.append(Italic(u"id: "_q));
|
||||||
|
const auto raw = peer->id.value & PeerId::kChatTypeMask;
|
||||||
|
const auto id = QString::number(raw);
|
||||||
|
value.append(Link(Italic(id), "internal:copy:" + id));
|
||||||
|
return std::move(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Text, typename ToggleOn, typename Callback>
|
template <typename Text, typename ToggleOn, typename Callback>
|
||||||
auto AddActionButton(
|
auto AddActionButton(
|
||||||
not_null<Ui::VerticalLayout*> parent,
|
not_null<Ui::VerticalLayout*> parent,
|
||||||
|
@ -425,8 +453,8 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
||||||
? tr::lng_info_about_label()
|
? tr::lng_info_about_label()
|
||||||
: tr::lng_info_bio_label();
|
: tr::lng_info_bio_label();
|
||||||
addTranslateToMenu(
|
addTranslateToMenu(
|
||||||
addInfoLine(std::move(label), AboutValue(user)).text,
|
addInfoLine(std::move(label), AboutWithIdValue(user)).text,
|
||||||
AboutValue(user));
|
AboutWithIdValue(user));
|
||||||
|
|
||||||
const auto usernameLine = addInfoOneLine(
|
const auto usernameLine = addInfoOneLine(
|
||||||
UsernamesSubtext(_peer, tr::lng_info_username_label()),
|
UsernamesSubtext(_peer, tr::lng_info_username_label()),
|
||||||
|
@ -576,11 +604,11 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupInfo() {
|
||||||
).text->setLinksTrusted();
|
).text->setLinksTrusted();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto about = addInfoLine(
|
const auto about = addInfoLine(tr::lng_info_about_label(), _topic
|
||||||
tr::lng_info_about_label(),
|
? rpl::single(TextWithEntities())
|
||||||
_topic ? rpl::single(TextWithEntities()) : AboutValue(_peer));
|
: AboutWithIdValue(_peer));
|
||||||
if (!_topic) {
|
if (!_topic) {
|
||||||
addTranslateToMenu(about.text, AboutValue(_peer));
|
addTranslateToMenu(about.text, AboutWithIdValue(_peer));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings->showPeerId != 0 && !_topic)
|
if (settings->showPeerId != 0 && !_topic)
|
||||||
|
@ -1129,6 +1157,8 @@ object_ptr<Ui::RpWidget> ActionsFiller::fill() {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
const char kOptionShowPeerIdBelowAbout[] = "show-peer-id-below-about";
|
||||||
|
|
||||||
object_ptr<Ui::RpWidget> SetupDetails(
|
object_ptr<Ui::RpWidget> SetupDetails(
|
||||||
not_null<Controller*> controller,
|
not_null<Controller*> controller,
|
||||||
not_null<Ui::RpWidget*> parent,
|
not_null<Ui::RpWidget*> parent,
|
||||||
|
|
|
@ -23,6 +23,8 @@ class Controller;
|
||||||
|
|
||||||
namespace Info::Profile {
|
namespace Info::Profile {
|
||||||
|
|
||||||
|
extern const char kOptionShowPeerIdBelowAbout[];
|
||||||
|
|
||||||
object_ptr<Ui::RpWidget> SetupDetails(
|
object_ptr<Ui::RpWidget> SetupDetails(
|
||||||
not_null<Controller*> controller,
|
not_null<Controller*> controller,
|
||||||
not_null<Ui::RpWidget*> parent,
|
not_null<Ui::RpWidget*> parent,
|
||||||
|
|
|
@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "info/profile/info_profile_values.h"
|
#include "info/profile/info_profile_values.h"
|
||||||
|
|
||||||
#include "base/options.h"
|
|
||||||
#include "info/profile/info_profile_badge.h"
|
#include "info/profile/info_profile_badge.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
|
@ -38,13 +37,6 @@ namespace {
|
||||||
|
|
||||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||||
|
|
||||||
base::options::toggle ShowPeerIdBelowAbout({
|
|
||||||
.id = kOptionShowPeerIdBelowAbout,
|
|
||||||
.name = "Show Peer IDs in Profile",
|
|
||||||
.description = "Show peer IDs from API below their Bio / Description.",
|
|
||||||
.scope = static_cast<base::options::details::ScopeFlag>(0),
|
|
||||||
});
|
|
||||||
|
|
||||||
auto PlainAboutValue(not_null<PeerData*> peer) {
|
auto PlainAboutValue(not_null<PeerData*> peer) {
|
||||||
return peer->session().changes().peerFlagsValue(
|
return peer->session().changes().peerFlagsValue(
|
||||||
peer,
|
peer,
|
||||||
|
@ -95,8 +87,6 @@ void StripExternalLinks(TextWithEntities &text) {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
const char kOptionShowPeerIdBelowAbout[] = "show-peer-id-below-about";
|
|
||||||
|
|
||||||
rpl::producer<QString> NameValue(not_null<PeerData*> peer) {
|
rpl::producer<QString> NameValue(not_null<PeerData*> peer) {
|
||||||
return peer->session().changes().peerFlagsValue(
|
return peer->session().changes().peerFlagsValue(
|
||||||
peer,
|
peer,
|
||||||
|
@ -219,16 +209,6 @@ TextWithEntities AboutWithEntities(
|
||||||
if (stripExternal) {
|
if (stripExternal) {
|
||||||
StripExternalLinks(result);
|
StripExternalLinks(result);
|
||||||
}
|
}
|
||||||
if (ShowPeerIdBelowAbout.value()) {
|
|
||||||
using namespace Ui::Text;
|
|
||||||
if (!result.empty()) {
|
|
||||||
result.append("\n");
|
|
||||||
}
|
|
||||||
result.append(Italic(u"id: "_q));
|
|
||||||
const auto raw = peer->id.value & PeerId::kChatTypeMask;
|
|
||||||
const auto id = QString::number(raw);
|
|
||||||
result.append(Link(Italic(id), "internal:copy:" + id));
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,6 @@ enum class SharedMediaType : signed char;
|
||||||
|
|
||||||
namespace Info::Profile {
|
namespace Info::Profile {
|
||||||
|
|
||||||
extern const char kOptionShowPeerIdBelowAbout[];
|
|
||||||
|
|
||||||
inline auto ToSingleLine() {
|
inline auto ToSingleLine() {
|
||||||
return rpl::map([](const QString &text) {
|
return rpl::map([](const QString &text) {
|
||||||
return TextUtilities::SingleLine(text);
|
return TextUtilities::SingleLine(text);
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "api/api_statistics.h"
|
#include "api/api_statistics.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
#include "base/event_filter.h"
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
@ -28,9 +29,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/rect.h"
|
#include "ui/rect.h"
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/widgets/scroll_area.h"
|
#include "ui/widgets/scroll_area.h"
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
|
#include "styles/style_menu_icons.h"
|
||||||
#include "styles/style_settings.h"
|
#include "styles/style_settings.h"
|
||||||
#include "styles/style_statistics.h"
|
#include "styles/style_statistics.h"
|
||||||
|
|
||||||
|
@ -43,6 +46,39 @@ struct Descriptor final {
|
||||||
not_null<QWidget*> toastParent;
|
not_null<QWidget*> toastParent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void AddContextMenu(
|
||||||
|
not_null<Ui::RpWidget*> button,
|
||||||
|
not_null<Controller*> controller,
|
||||||
|
not_null<HistoryItem*> item) {
|
||||||
|
const auto fullId = item->fullId();
|
||||||
|
const auto contextMenu = button->lifetime()
|
||||||
|
.make_state<base::unique_qptr<Ui::PopupMenu>>();
|
||||||
|
const auto showMenu = [=] {
|
||||||
|
*contextMenu = base::make_unique_q<Ui::PopupMenu>(
|
||||||
|
button,
|
||||||
|
st::popupMenuWithIcons);
|
||||||
|
const auto go = [=] {
|
||||||
|
const auto &session = controller->parentController();
|
||||||
|
if (const auto item = session->session().data().message(fullId)) {
|
||||||
|
session->showMessage(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
contextMenu->get()->addAction(
|
||||||
|
tr::lng_context_to_msg(tr::now),
|
||||||
|
crl::guard(controller, go),
|
||||||
|
&st::menuIconShowInChat);
|
||||||
|
contextMenu->get()->popup(QCursor::pos());
|
||||||
|
};
|
||||||
|
|
||||||
|
base::install_event_filter(button, [=](not_null<QEvent*> e) {
|
||||||
|
if (e->type() == QEvent::ContextMenu) {
|
||||||
|
showMenu();
|
||||||
|
return base::EventFilterResult::Cancel;
|
||||||
|
}
|
||||||
|
return base::EventFilterResult::Continue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void ProcessZoom(
|
void ProcessZoom(
|
||||||
const Descriptor &d,
|
const Descriptor &d,
|
||||||
not_null<Statistic::ChartWidget*> widget,
|
not_null<Statistic::ChartWidget*> widget,
|
||||||
|
@ -544,7 +580,9 @@ void InnerWidget::fill() {
|
||||||
if (_state.stats.message) {
|
if (_state.stats.message) {
|
||||||
if (const auto i = _peer->owner().message(_contextId)) {
|
if (const auto i = _peer->owner().message(_contextId)) {
|
||||||
::Settings::AddSkip(inner);
|
::Settings::AddSkip(inner);
|
||||||
inner->add(object_ptr<MessagePreview>(this, i, -1, -1, QImage()));
|
const auto preview = inner->add(
|
||||||
|
object_ptr<MessagePreview>(this, i, -1, -1, QImage()));
|
||||||
|
AddContextMenu(preview, _controller, i);
|
||||||
::Settings::AddSkip(inner);
|
::Settings::AddSkip(inner);
|
||||||
::Settings::AddDivider(inner);
|
::Settings::AddDivider(inner);
|
||||||
}
|
}
|
||||||
|
@ -639,6 +677,8 @@ void InnerWidget::fillRecentPosts() {
|
||||||
info.forwardsCount,
|
info.forwardsCount,
|
||||||
std::move(cachedPreview));
|
std::move(cachedPreview));
|
||||||
|
|
||||||
|
AddContextMenu(button, _controller, item);
|
||||||
|
|
||||||
_messagePreviews.push_back(raw);
|
_messagePreviews.push_back(raw);
|
||||||
raw->show();
|
raw->show();
|
||||||
button->sizeValue(
|
button->sizeValue(
|
||||||
|
|
|
@ -828,6 +828,19 @@ void AttachWebView::requestWithOptionalConfirm(
|
||||||
void AttachWebView::request(const WebViewButton &button) {
|
void AttachWebView::request(const WebViewButton &button) {
|
||||||
Expects(_context != nullptr && _bot != nullptr);
|
Expects(_context != nullptr && _bot != nullptr);
|
||||||
|
|
||||||
|
if (button.fromAttachMenu) {
|
||||||
|
const auto bot = ranges::find(
|
||||||
|
_attachBots,
|
||||||
|
not_null{ _bot },
|
||||||
|
&AttachWebViewBot::user);
|
||||||
|
if (bot == end(_attachBots) || bot->inactive) {
|
||||||
|
requestAddToMenu(_bot, AddToMenuOpenAttach{
|
||||||
|
.startCommand = button.startCommand,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_startCommand = button.startCommand;
|
_startCommand = button.startCommand;
|
||||||
const auto &action = _context->action;
|
const auto &action = _context->action;
|
||||||
|
|
||||||
|
@ -1524,8 +1537,10 @@ void AttachWebView::confirmAddToMenu(
|
||||||
}
|
}
|
||||||
_confirmAddBox = active->show(Box([=](not_null<Ui::GenericBox*> box) {
|
_confirmAddBox = active->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||||
const auto allowed = std::make_shared<Ui::Checkbox*>();
|
const auto allowed = std::make_shared<Ui::Checkbox*>();
|
||||||
|
const auto disclaimer = !disclaimerAccepted(bot);
|
||||||
const auto done = [=](Fn<void()> close) {
|
const auto done = [=](Fn<void()> close) {
|
||||||
const auto state = ((*allowed) && (*allowed)->checked())
|
const auto state = (disclaimer
|
||||||
|
|| ((*allowed) && (*allowed)->checked()))
|
||||||
? ToggledState::AllowedToWrite
|
? ToggledState::AllowedToWrite
|
||||||
: ToggledState::Added;
|
: ToggledState::Added;
|
||||||
toggleInMenu(bot.user, state, [=] {
|
toggleInMenu(bot.user, state, [=] {
|
||||||
|
@ -1538,13 +1553,22 @@ void AttachWebView::confirmAddToMenu(
|
||||||
});
|
});
|
||||||
close();
|
close();
|
||||||
};
|
};
|
||||||
const auto disclaimer = !disclaimerAccepted(bot);
|
|
||||||
if (disclaimer) {
|
if (disclaimer) {
|
||||||
FillDisclaimerBox(box, [=] {
|
FillDisclaimerBox(box, [=] {
|
||||||
_disclaimerAccepted.emplace(bot.user);
|
_disclaimerAccepted.emplace(bot.user);
|
||||||
_attachBotsUpdates.fire({});
|
_attachBotsUpdates.fire({});
|
||||||
done([] {});
|
done([] {});
|
||||||
});
|
});
|
||||||
|
box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||||
|
box,
|
||||||
|
st::boxRowPadding.left()));
|
||||||
|
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||||
|
box,
|
||||||
|
tr::lng_bot_will_be_added(
|
||||||
|
lt_bot,
|
||||||
|
rpl::single(Ui::Text::Bold(bot.name)),
|
||||||
|
Ui::Text::WithEntities),
|
||||||
|
st::boxLabel));
|
||||||
} else {
|
} else {
|
||||||
Ui::ConfirmBox(box, {
|
Ui::ConfirmBox(box, {
|
||||||
(bot.inMainMenu
|
(bot.inMainMenu
|
||||||
|
@ -1556,40 +1580,26 @@ void AttachWebView::confirmAddToMenu(
|
||||||
Ui::Text::WithEntities),
|
Ui::Text::WithEntities),
|
||||||
done,
|
done,
|
||||||
});
|
});
|
||||||
}
|
if (bot.requestWriteAccess) {
|
||||||
if (bot.requestWriteAccess) {
|
(*allowed) = box->addRow(
|
||||||
(*allowed) = box->addRow(
|
object_ptr<Ui::Checkbox>(
|
||||||
object_ptr<Ui::Checkbox>(
|
box,
|
||||||
box,
|
tr::lng_url_auth_allow_messages(
|
||||||
tr::lng_url_auth_allow_messages(
|
tr::now,
|
||||||
tr::now,
|
lt_bot,
|
||||||
lt_bot,
|
Ui::Text::Bold(bot.name),
|
||||||
Ui::Text::Bold(bot.name),
|
Ui::Text::WithEntities),
|
||||||
Ui::Text::WithEntities),
|
true,
|
||||||
true,
|
st::urlAuthCheckbox),
|
||||||
st::urlAuthCheckbox),
|
style::margins(
|
||||||
style::margins(
|
st::boxRowPadding.left(),
|
||||||
st::boxRowPadding.left(),
|
(disclaimer
|
||||||
(disclaimer
|
? st::boxPhotoCaptionSkip
|
||||||
? st::boxPhotoCaptionSkip
|
: st::boxRowPadding.left()),
|
||||||
: st::boxRowPadding.left()),
|
st::boxRowPadding.right(),
|
||||||
st::boxRowPadding.right(),
|
st::boxRowPadding.left()));
|
||||||
st::boxRowPadding.left()));
|
(*allowed)->setAllowTextLines();
|
||||||
(*allowed)->setAllowTextLines();
|
|
||||||
}
|
|
||||||
if (disclaimer) {
|
|
||||||
if (!bot.requestWriteAccess) {
|
|
||||||
box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
|
||||||
box,
|
|
||||||
st::boxRowPadding.left()));
|
|
||||||
}
|
}
|
||||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
|
||||||
box,
|
|
||||||
tr::lng_bot_will_be_added(
|
|
||||||
lt_bot,
|
|
||||||
rpl::single(Ui::Text::Bold(bot.name)),
|
|
||||||
Ui::Text::WithEntities),
|
|
||||||
st::boxLabel));
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,6 +134,9 @@ public:
|
||||||
[[nodiscard]] rpl::producer<> attachBotsUpdates() const {
|
[[nodiscard]] rpl::producer<> attachBotsUpdates() const {
|
||||||
return _attachBotsUpdates.events();
|
return _attachBotsUpdates.events();
|
||||||
}
|
}
|
||||||
|
void notifyBotIconLoaded() {
|
||||||
|
_attachBotsUpdates.fire({});
|
||||||
|
}
|
||||||
[[nodiscard]] bool disclaimerAccepted(
|
[[nodiscard]] bool disclaimerAccepted(
|
||||||
const AttachWebViewBot &bot) const;
|
const AttachWebViewBot &bot) const;
|
||||||
[[nodiscard]] bool showMainMenuNewBadge(
|
[[nodiscard]] bool showMainMenuNewBadge(
|
||||||
|
|
|
@ -1411,7 +1411,7 @@ void MainWidget::showHistory(
|
||||||
&& way != Way::Forward) {
|
&& way != Way::Forward) {
|
||||||
clearBotStartToken(_history->peer());
|
clearBotStartToken(_history->peer());
|
||||||
}
|
}
|
||||||
_history->showHistory(peerId, showAtMsgId);
|
_history->showHistory(peerId, showAtMsgId, params.highlightPart);
|
||||||
if (alreadyThatPeer && params.reapplyLocalDraft) {
|
if (alreadyThatPeer && params.reapplyLocalDraft) {
|
||||||
_history->applyDraft(HistoryWidget::FieldHistoryAction::NewEntry);
|
_history->applyDraft(HistoryWidget::FieldHistoryAction::NewEntry);
|
||||||
}
|
}
|
||||||
|
@ -1772,7 +1772,7 @@ void MainWidget::showNewSection(
|
||||||
} else {
|
} else {
|
||||||
_mainSection = std::move(newMainSection);
|
_mainSection = std::move(newMainSection);
|
||||||
_history->finishAnimating();
|
_history->finishAnimating();
|
||||||
_history->showHistory(0, 0);
|
_history->showHistory(0, 0, {});
|
||||||
|
|
||||||
if (const auto entry = _mainSection->activeChat(); entry.key) {
|
if (const auto entry = _mainSection->activeChat(); entry.key) {
|
||||||
_controller->setActiveChatEntry(entry);
|
_controller->setActiveChatEntry(entry);
|
||||||
|
|
|
@ -67,4 +67,8 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline bool HasMonochromeSetting() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Platform
|
} // namespace Platform
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
|
#include "calls/calls_instance.h"
|
||||||
#include "core/sandbox.h"
|
#include "core/sandbox.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/core_settings.h"
|
#include "core/core_settings.h"
|
||||||
|
@ -175,7 +176,11 @@ ApplicationDelegate *_sharedDelegate = nil;
|
||||||
Core::App().handleAppActivated();
|
Core::App().handleAppActivated();
|
||||||
if (const auto window = Core::App().activeWindow()) {
|
if (const auto window = Core::App().activeWindow()) {
|
||||||
if (window->widget()->isHidden()) {
|
if (window->widget()->isHidden()) {
|
||||||
window->widget()->showFromTray();
|
if (Core::App().calls().hasVisiblePanel()) {
|
||||||
|
Core::App().calls().activateCurrentCall();
|
||||||
|
} else {
|
||||||
|
window->widget()->showFromTray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,4 +55,8 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline bool HasMonochromeSetting() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Platform
|
} // namespace Platform
|
||||||
|
|
|
@ -11,6 +11,8 @@ namespace Platform {
|
||||||
|
|
||||||
class Tray;
|
class Tray;
|
||||||
|
|
||||||
|
[[nodiscard]] bool HasMonochromeSetting();
|
||||||
|
|
||||||
} // namespace Platform
|
} // namespace Platform
|
||||||
|
|
||||||
// Platform dependent implementations.
|
// Platform dependent implementations.
|
||||||
|
|
|
@ -233,18 +233,19 @@ void Tray::updateIcon() {
|
||||||
: !controller->sessionController()
|
: !controller->sessionController()
|
||||||
? nullptr
|
? nullptr
|
||||||
: &controller->sessionController()->session();
|
: &controller->sessionController()->session();
|
||||||
|
const auto monochrome = Core::App().settings().trayIconMonochrome();
|
||||||
const auto supportMode = session && session->supportMode();
|
const auto supportMode = session && session->supportMode();
|
||||||
const auto iconSizeWidth = GetSystemMetrics(SM_CXSMICON);
|
const auto iconSizeWidth = GetSystemMetrics(SM_CXSMICON);
|
||||||
|
|
||||||
auto iconSmallPixmap16 = Tray::IconWithCounter(
|
auto iconSmallPixmap16 = Tray::IconWithCounter(
|
||||||
CounterLayerArgs(16, counter, muted),
|
CounterLayerArgs(16, counter, muted),
|
||||||
true,
|
true,
|
||||||
true,
|
monochrome,
|
||||||
supportMode);
|
supportMode);
|
||||||
auto iconSmallPixmap32 = Tray::IconWithCounter(
|
auto iconSmallPixmap32 = Tray::IconWithCounter(
|
||||||
CounterLayerArgs(32, counter, muted),
|
CounterLayerArgs(32, counter, muted),
|
||||||
true,
|
true,
|
||||||
true,
|
monochrome,
|
||||||
supportMode);
|
supportMode);
|
||||||
auto iconSmall = QIcon();
|
auto iconSmall = QIcon();
|
||||||
iconSmall.addPixmap(iconSmallPixmap16);
|
iconSmall.addPixmap(iconSmallPixmap16);
|
||||||
|
@ -351,8 +352,15 @@ QPixmap Tray::IconWithCounter(
|
||||||
bool smallIcon,
|
bool smallIcon,
|
||||||
bool monochrome,
|
bool monochrome,
|
||||||
bool supportMode) {
|
bool supportMode) {
|
||||||
return Ui::PixmapFromImage(
|
return Ui::PixmapFromImage(ImageIconWithCounter(
|
||||||
ImageIconWithCounter(std::move(args), supportMode, smallIcon, monochrome));
|
std::move(args),
|
||||||
|
supportMode,
|
||||||
|
smallIcon,
|
||||||
|
monochrome));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasMonochromeSetting() {
|
||||||
|
return IsDarkTaskbar().has_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Platform
|
} // namespace Platform
|
||||||
|
|
|
@ -453,18 +453,35 @@ void SetupSystemIntegrationContent(
|
||||||
st::settingsCheckboxPadding));
|
st::settingsCheckboxPadding));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const auto settings = &Core::App().settings();
|
||||||
if (Platform::TrayIconSupported()) {
|
if (Platform::TrayIconSupported()) {
|
||||||
const auto trayEnabled = [] {
|
const auto trayEnabled = [=] {
|
||||||
const auto workMode = Core::App().settings().workMode();
|
const auto workMode = settings->workMode();
|
||||||
return (workMode == WorkMode::TrayOnly)
|
return (workMode == WorkMode::TrayOnly)
|
||||||
|| (workMode == WorkMode::WindowAndTray);
|
|| (workMode == WorkMode::WindowAndTray);
|
||||||
};
|
};
|
||||||
const auto tray = addCheckbox(
|
const auto tray = addCheckbox(
|
||||||
tr::lng_settings_workmode_tray(),
|
tr::lng_settings_workmode_tray(),
|
||||||
trayEnabled());
|
trayEnabled());
|
||||||
|
const auto monochrome = Platform::HasMonochromeSetting()
|
||||||
|
? addSlidingCheckbox(
|
||||||
|
tr::lng_settings_monochrome_icon(),
|
||||||
|
settings->trayIconMonochrome())
|
||||||
|
: nullptr;
|
||||||
|
if (monochrome) {
|
||||||
|
monochrome->toggle(tray->checked(), anim::type::instant);
|
||||||
|
|
||||||
const auto taskbarEnabled = [] {
|
monochrome->entity()->checkedChanges(
|
||||||
const auto workMode = Core::App().settings().workMode();
|
) | rpl::filter([=](bool value) {
|
||||||
|
return (value != settings->trayIconMonochrome());
|
||||||
|
}) | rpl::start_with_next([=](bool value) {
|
||||||
|
settings->setTrayIconMonochrome(value);
|
||||||
|
Core::App().saveSettingsDelayed();
|
||||||
|
}, monochrome->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto taskbarEnabled = [=] {
|
||||||
|
const auto workMode = settings->workMode();
|
||||||
return (workMode == WorkMode::WindowOnly)
|
return (workMode == WorkMode::WindowOnly)
|
||||||
|| (workMode == WorkMode::WindowAndTray);
|
|| (workMode == WorkMode::WindowAndTray);
|
||||||
};
|
};
|
||||||
|
@ -482,10 +499,10 @@ void SetupSystemIntegrationContent(
|
||||||
: WorkMode::WindowOnly;
|
: WorkMode::WindowOnly;
|
||||||
if ((newMode == WorkMode::WindowAndTray
|
if ((newMode == WorkMode::WindowAndTray
|
||||||
|| newMode == WorkMode::TrayOnly)
|
|| newMode == WorkMode::TrayOnly)
|
||||||
&& Core::App().settings().workMode() != newMode) {
|
&& settings->workMode() != newMode) {
|
||||||
cSetSeenTrayTooltip(false);
|
cSetSeenTrayTooltip(false);
|
||||||
}
|
}
|
||||||
Core::App().settings().setWorkMode(newMode);
|
settings->setWorkMode(newMode);
|
||||||
Core::App().saveSettingsDelayed();
|
Core::App().saveSettingsDelayed();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -498,6 +515,9 @@ void SetupSystemIntegrationContent(
|
||||||
} else {
|
} else {
|
||||||
updateWorkmode();
|
updateWorkmode();
|
||||||
}
|
}
|
||||||
|
if (monochrome) {
|
||||||
|
monochrome->toggle(checked, anim::type::normal);
|
||||||
|
}
|
||||||
}, tray->lifetime());
|
}, tray->lifetime());
|
||||||
|
|
||||||
if (taskbar) {
|
if (taskbar) {
|
||||||
|
@ -519,19 +539,19 @@ void SetupSystemIntegrationContent(
|
||||||
tr::lng_settings_mac_warn_before_quit(
|
tr::lng_settings_mac_warn_before_quit(
|
||||||
lt_text,
|
lt_text,
|
||||||
rpl::single(Platform::ConfirmQuit::QuitKeysString())),
|
rpl::single(Platform::ConfirmQuit::QuitKeysString())),
|
||||||
Core::App().settings().macWarnBeforeQuit());
|
settings->macWarnBeforeQuit());
|
||||||
warnBeforeQuit->checkedChanges(
|
warnBeforeQuit->checkedChanges(
|
||||||
) | rpl::filter([=](bool checked) {
|
) | rpl::filter([=](bool checked) {
|
||||||
return (checked != Core::App().settings().macWarnBeforeQuit());
|
return (checked != settings->macWarnBeforeQuit());
|
||||||
}) | rpl::start_with_next([=](bool checked) {
|
}) | rpl::start_with_next([=](bool checked) {
|
||||||
Core::App().settings().setMacWarnBeforeQuit(checked);
|
settings->setMacWarnBeforeQuit(checked);
|
||||||
Core::App().saveSettingsDelayed();
|
Core::App().saveSettingsDelayed();
|
||||||
}, warnBeforeQuit->lifetime());
|
}, warnBeforeQuit->lifetime());
|
||||||
|
|
||||||
#ifndef OS_MAC_STORE
|
#ifndef OS_MAC_STORE
|
||||||
const auto enabled = [] {
|
const auto enabled = [=] {
|
||||||
const auto digest = base::Platform::CurrentCustomAppIconDigest();
|
const auto digest = base::Platform::CurrentCustomAppIconDigest();
|
||||||
return digest && (Core::App().settings().macRoundIconDigest() == digest);
|
return digest && (settings->macRoundIconDigest() == digest);
|
||||||
};
|
};
|
||||||
const auto roundIcon = addCheckbox(
|
const auto roundIcon = addCheckbox(
|
||||||
tr::lng_settings_mac_round_icon(),
|
tr::lng_settings_mac_round_icon(),
|
||||||
|
@ -548,7 +568,7 @@ void SetupSystemIntegrationContent(
|
||||||
}
|
}
|
||||||
Window::OverrideApplicationIcon(checked ? IconMacRound() : QImage());
|
Window::OverrideApplicationIcon(checked ? IconMacRound() : QImage());
|
||||||
Core::App().refreshApplicationIcon();
|
Core::App().refreshApplicationIcon();
|
||||||
Core::App().settings().setMacRoundIconDigest(digest);
|
settings->setMacRoundIconDigest(digest);
|
||||||
Core::App().saveSettings();
|
Core::App().saveSettings();
|
||||||
}, roundIcon->lifetime());
|
}, roundIcon->lifetime());
|
||||||
#endif // OS_MAC_STORE
|
#endif // OS_MAC_STORE
|
||||||
|
@ -557,10 +577,10 @@ void SetupSystemIntegrationContent(
|
||||||
if (!Platform::RunInBackground()) {
|
if (!Platform::RunInBackground()) {
|
||||||
const auto closeToTaskbar = addSlidingCheckbox(
|
const auto closeToTaskbar = addSlidingCheckbox(
|
||||||
tr::lng_settings_close_to_taskbar(),
|
tr::lng_settings_close_to_taskbar(),
|
||||||
Core::App().settings().closeToTaskbar());
|
settings->closeToTaskbar());
|
||||||
|
|
||||||
const auto closeToTaskbarShown = std::make_shared<rpl::variable<bool>>(false);
|
const auto closeToTaskbarShown = std::make_shared<rpl::variable<bool>>(false);
|
||||||
Core::App().settings().workModeValue(
|
settings->workModeValue(
|
||||||
) | rpl::start_with_next([=](WorkMode workMode) {
|
) | rpl::start_with_next([=](WorkMode workMode) {
|
||||||
*closeToTaskbarShown = !Core::App().tray().has();
|
*closeToTaskbarShown = !Core::App().tray().has();
|
||||||
}, closeToTaskbar->lifetime());
|
}, closeToTaskbar->lifetime());
|
||||||
|
@ -568,9 +588,9 @@ void SetupSystemIntegrationContent(
|
||||||
closeToTaskbar->toggleOn(closeToTaskbarShown->value());
|
closeToTaskbar->toggleOn(closeToTaskbarShown->value());
|
||||||
closeToTaskbar->entity()->checkedChanges(
|
closeToTaskbar->entity()->checkedChanges(
|
||||||
) | rpl::filter([=](bool checked) {
|
) | rpl::filter([=](bool checked) {
|
||||||
return (checked != Core::App().settings().closeToTaskbar());
|
return (checked != settings->closeToTaskbar());
|
||||||
}) | rpl::start_with_next([=](bool checked) {
|
}) | rpl::start_with_next([=](bool checked) {
|
||||||
Core::App().settings().setCloseToTaskbar(checked);
|
settings->setCloseToTaskbar(checked);
|
||||||
Local::writeSettings();
|
Local::writeSettings();
|
||||||
}, closeToTaskbar->lifetime());
|
}, closeToTaskbar->lifetime());
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "core/launcher.h"
|
#include "core/launcher.h"
|
||||||
#include "chat_helpers/tabbed_panel.h"
|
#include "chat_helpers/tabbed_panel.h"
|
||||||
#include "dialogs/dialogs_widget.h"
|
#include "dialogs/dialogs_widget.h"
|
||||||
#include "info/profile/info_profile_values.h"
|
#include "info/profile/info_profile_actions.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "media/player/media_player_instance.h"
|
#include "media/player/media_player_instance.h"
|
||||||
|
|
|
@ -1018,9 +1018,9 @@ void ChartWidget::updateBottomDates() {
|
||||||
const auto k = _chartArea->width() / d;
|
const auto k = _chartArea->width() / d;
|
||||||
const auto stepRaw = int(k / 6);
|
const auto stepRaw = int(k / 6);
|
||||||
|
|
||||||
|
const auto by = int(_chartArea->width() / float64(_chartData.x.size()));
|
||||||
_bottomLine.captionIndicesOffset = 0
|
_bottomLine.captionIndicesOffset = 0
|
||||||
+ st::statisticsChartBottomCaptionMaxWidth
|
+ st::statisticsChartBottomCaptionMaxWidth / std::max(by, 1);
|
||||||
/ int(_chartArea->width() / float64(_chartData.x.size()));
|
|
||||||
|
|
||||||
const auto isCurrentNull = (_bottomLine.current.stepMinFast == 0);
|
const auto isCurrentNull = (_bottomLine.current.stepMinFast == 0);
|
||||||
if (!isCurrentNull
|
if (!isCurrentNull
|
||||||
|
|
|
@ -46,6 +46,11 @@ void Tray::create() {
|
||||||
}
|
}
|
||||||
}, _tray.lifetime());
|
}, _tray.lifetime());
|
||||||
|
|
||||||
|
Core::App().settings().trayIconMonochromeChanges(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
updateIconCounters();
|
||||||
|
}, _tray.lifetime());
|
||||||
|
|
||||||
Core::App().passcodeLockChanges(
|
Core::App().passcodeLockChanges(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
rebuildMenu();
|
rebuildMenu();
|
||||||
|
|
|
@ -146,6 +146,7 @@ outReplyTextPaletteSelected: TextPalette(outTextPaletteSelected) {
|
||||||
}
|
}
|
||||||
imgReplyTextPalette: TextPalette(defaultTextPalette) {
|
imgReplyTextPalette: TextPalette(defaultTextPalette) {
|
||||||
linkFg: msgImgReplyBarColor;
|
linkFg: msgImgReplyBarColor;
|
||||||
|
monoFg: msgImgReplyBarColor;
|
||||||
}
|
}
|
||||||
inSemiboldPalette: TextPalette(inTextPalette) {
|
inSemiboldPalette: TextPalette(inTextPalette) {
|
||||||
linkFg: msgInServiceFg;
|
linkFg: msgInServiceFg;
|
||||||
|
|
|
@ -142,6 +142,12 @@ struct BackgroundEmojiData {
|
||||||
uint8 colorIndexPlusOne);
|
uint8 colorIndexPlusOne);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ChatPaintHighlight {
|
||||||
|
float64 opacity = 0.;
|
||||||
|
float64 collapsion = 0.;
|
||||||
|
TextSelection range;
|
||||||
|
};
|
||||||
|
|
||||||
struct ChatPaintContext {
|
struct ChatPaintContext {
|
||||||
not_null<const ChatStyle*> st;
|
not_null<const ChatStyle*> st;
|
||||||
const BubblePattern *bubblesPattern = nullptr;
|
const BubblePattern *bubblesPattern = nullptr;
|
||||||
|
@ -149,11 +155,15 @@ struct ChatPaintContext {
|
||||||
QRect viewport;
|
QRect viewport;
|
||||||
QRect clip;
|
QRect clip;
|
||||||
TextSelection selection;
|
TextSelection selection;
|
||||||
|
ChatPaintHighlight highlight;
|
||||||
|
QPainterPath *highlightPathCache = nullptr;
|
||||||
|
mutable QRect highlightInterpolateTo;
|
||||||
crl::time now = 0;
|
crl::time now = 0;
|
||||||
|
|
||||||
void translate(int x, int y) {
|
void translate(int x, int y) {
|
||||||
viewport.translate(x, y);
|
viewport.translate(x, y);
|
||||||
clip.translate(x, y);
|
clip.translate(x, y);
|
||||||
|
highlightInterpolateTo.translate(x, y);
|
||||||
}
|
}
|
||||||
void translate(QPoint point) {
|
void translate(QPoint point) {
|
||||||
translate(point.x(), point.y());
|
translate(point.x(), point.y());
|
||||||
|
@ -181,6 +191,19 @@ struct ChatPaintContext {
|
||||||
result.selection = selection;
|
result.selection = selection;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] auto computeHighlightCache() const
|
||||||
|
-> std::optional<Ui::Text::HighlightInfoRequest> {
|
||||||
|
if (highlight.range.empty() || highlight.collapsion <= 0.) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return Ui::Text::HighlightInfoRequest{
|
||||||
|
.range = highlight.range,
|
||||||
|
.interpolateTo = highlightInterpolateTo,
|
||||||
|
.interpolateProgress = (1. - highlight.collapsion),
|
||||||
|
.outPath = highlightPathCache,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// This is supported only in unwrapped media for now.
|
// This is supported only in unwrapped media for now.
|
||||||
enum class SkipDrawingParts {
|
enum class SkipDrawingParts {
|
||||||
|
|
|
@ -11,6 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "styles/style_basic.h"
|
#include "styles/style_basic.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kAlmostIndex = float64(.99);
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
PickerAnimation::PickerAnimation() = default;
|
PickerAnimation::PickerAnimation() = default;
|
||||||
|
|
||||||
|
@ -26,6 +31,23 @@ void PickerAnimation::jumpToOffset(int offset) {
|
||||||
value);
|
value);
|
||||||
_updates.fire(_result.current - was);
|
_updates.fire(_result.current - was);
|
||||||
};
|
};
|
||||||
|
if (anim::Disabled()) {
|
||||||
|
auto value = float64(0.);
|
||||||
|
const auto diff = _result.to - _result.from;
|
||||||
|
const auto step = std::min(
|
||||||
|
kAlmostIndex,
|
||||||
|
1. / (std::max(1. - kAlmostIndex, std::abs(diff) + 1)));
|
||||||
|
while (true) {
|
||||||
|
value += step;
|
||||||
|
if (value >= 1.) {
|
||||||
|
callback(1.);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
callback(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
_animation.start(
|
_animation.start(
|
||||||
std::move(callback),
|
std::move(callback),
|
||||||
0.,
|
0.,
|
||||||
|
@ -94,20 +116,14 @@ VerticalDrumPicker::VerticalDrumPicker(
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_animation.updates(
|
_animation.updates(
|
||||||
) | rpl::distinct_until_changed(
|
|
||||||
) | rpl::start_with_next([=](PickerAnimation::Shift shift) {
|
) | rpl::start_with_next([=](PickerAnimation::Shift shift) {
|
||||||
increaseShift(shift);
|
increaseShift(shift);
|
||||||
if (anim::Disabled()) {
|
|
||||||
animationDataFromIndex();
|
|
||||||
_animation.jumpToOffset(0);
|
|
||||||
}
|
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void VerticalDrumPicker::increaseShift(float64 by) {
|
void VerticalDrumPicker::increaseShift(float64 by) {
|
||||||
{
|
{
|
||||||
// Guard input.
|
// Guard input.
|
||||||
constexpr auto kAlmostIndex = .99;
|
|
||||||
if (by >= 1.) {
|
if (by >= 1.) {
|
||||||
by = kAlmostIndex;
|
by = kAlmostIndex;
|
||||||
} else if (by <= -1.) {
|
} else if (by <= -1.) {
|
||||||
|
|
|
@ -59,6 +59,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "main/main_domain.h"
|
#include "main/main_domain.h"
|
||||||
#include "mtproto/mtp_instance.h"
|
#include "mtproto/mtp_instance.h"
|
||||||
#include "mtproto/mtproto_config.h"
|
#include "mtproto/mtproto_config.h"
|
||||||
|
#include "data/data_document_media.h"
|
||||||
#include "data/data_folder.h"
|
#include "data/data_folder.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
@ -216,6 +217,9 @@ void SetupMenuBots(
|
||||||
const auto wrap = container->add(
|
const auto wrap = container->add(
|
||||||
object_ptr<Ui::VerticalLayout>(container));
|
object_ptr<Ui::VerticalLayout>(container));
|
||||||
const auto bots = &controller->session().attachWebView();
|
const auto bots = &controller->session().attachWebView();
|
||||||
|
const auto iconLoadLifetime = wrap->lifetime().make_state<
|
||||||
|
rpl::lifetime
|
||||||
|
>();
|
||||||
|
|
||||||
rpl::single(
|
rpl::single(
|
||||||
rpl::empty
|
rpl::empty
|
||||||
|
@ -225,7 +229,20 @@ void SetupMenuBots(
|
||||||
const auto width = container->widthNoMargins();
|
const auto width = container->widthNoMargins();
|
||||||
wrap->clear();
|
wrap->clear();
|
||||||
for (const auto &bot : bots->attachBots()) {
|
for (const auto &bot : bots->attachBots()) {
|
||||||
if (!bot.inMainMenu) {
|
const auto user = bot.user;
|
||||||
|
if (!bot.inMainMenu || !bot.media) {
|
||||||
|
continue;
|
||||||
|
} else if (const auto media = bot.media; !media->loaded()) {
|
||||||
|
if (!*iconLoadLifetime) {
|
||||||
|
auto &session = user->session();
|
||||||
|
*iconLoadLifetime = session.downloaderTaskFinished(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
if (media->loaded()) {
|
||||||
|
iconLoadLifetime->destroy();
|
||||||
|
bots->notifyBotIconLoaded();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto button = Settings::AddButton(
|
const auto button = Settings::AddButton(
|
||||||
|
@ -244,7 +261,6 @@ void SetupMenuBots(
|
||||||
st::mainMenuButton.iconLeft,
|
st::mainMenuButton.iconLeft,
|
||||||
(height - icon->height()) / 2);
|
(height - icon->height()) / 2);
|
||||||
}, button->lifetime());
|
}, button->lifetime());
|
||||||
const auto user = bot.user;
|
|
||||||
const auto weak = Ui::MakeWeak(container);
|
const auto weak = Ui::MakeWeak(container);
|
||||||
button->setAcceptBoth(true);
|
button->setAcceptBoth(true);
|
||||||
button->clicks(
|
button->clicks(
|
||||||
|
|
|
@ -689,6 +689,7 @@ void SessionNavigation::applyBoost(
|
||||||
done(false);
|
done(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto slot = int();
|
||||||
auto different = PeerId();
|
auto different = PeerId();
|
||||||
auto earliest = TimeId(-1);
|
auto earliest = TimeId(-1);
|
||||||
const auto now = base::unixtime::now();
|
const auto now = base::unixtime::now();
|
||||||
|
@ -699,13 +700,13 @@ void SessionNavigation::applyBoost(
|
||||||
? peerFromMTP(*data.vpeer())
|
? peerFromMTP(*data.vpeer())
|
||||||
: PeerId();
|
: PeerId();
|
||||||
if (!peerId && cooldown <= now) {
|
if (!peerId && cooldown <= now) {
|
||||||
applyBoostChecked(channel, done);
|
applyBoostChecked(channel, data.vslot().v, done);
|
||||||
return;
|
return;
|
||||||
} else if (peerId != channel->id) {
|
} else if (peerId != channel->id
|
||||||
|
&& (earliest < 0 || cooldown < earliest)) {
|
||||||
|
slot = data.vslot().v;
|
||||||
different = peerId;
|
different = peerId;
|
||||||
if (earliest < 0 || cooldown < earliest) {
|
earliest = cooldown;
|
||||||
earliest = cooldown;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (different) {
|
if (different) {
|
||||||
|
@ -731,7 +732,7 @@ void SessionNavigation::applyBoost(
|
||||||
done(false);
|
done(false);
|
||||||
} else {
|
} else {
|
||||||
const auto peer = _session->data().peer(different);
|
const auto peer = _session->data().peer(different);
|
||||||
replaceBoostConfirm(peer, channel, done);
|
replaceBoostConfirm(peer, channel, slot, done);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uiShow()->show(Ui::MakeConfirmBox({
|
uiShow()->show(Ui::MakeConfirmBox({
|
||||||
|
@ -752,11 +753,12 @@ void SessionNavigation::applyBoost(
|
||||||
void SessionNavigation::replaceBoostConfirm(
|
void SessionNavigation::replaceBoostConfirm(
|
||||||
not_null<PeerData*> from,
|
not_null<PeerData*> from,
|
||||||
not_null<ChannelData*> channel,
|
not_null<ChannelData*> channel,
|
||||||
|
int slot,
|
||||||
Fn<void(bool)> done) {
|
Fn<void(bool)> done) {
|
||||||
const auto forwarded = std::make_shared<bool>(false);
|
const auto forwarded = std::make_shared<bool>(false);
|
||||||
const auto confirmed = [=](Fn<void()> close) {
|
const auto confirmed = [=](Fn<void()> close) {
|
||||||
*forwarded = true;
|
*forwarded = true;
|
||||||
applyBoostChecked(channel, done);
|
applyBoostChecked(channel, slot, done);
|
||||||
close();
|
close();
|
||||||
};
|
};
|
||||||
const auto box = uiShow()->show(Box([=](not_null<Ui::GenericBox*> box) {
|
const auto box = uiShow()->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||||
|
@ -785,10 +787,11 @@ void SessionNavigation::replaceBoostConfirm(
|
||||||
|
|
||||||
void SessionNavigation::applyBoostChecked(
|
void SessionNavigation::applyBoostChecked(
|
||||||
not_null<ChannelData*> channel,
|
not_null<ChannelData*> channel,
|
||||||
|
int slot,
|
||||||
Fn<void(bool)> done) {
|
Fn<void(bool)> done) {
|
||||||
_api.request(MTPpremium_ApplyBoost(
|
_api.request(MTPpremium_ApplyBoost(
|
||||||
MTP_flags(0),
|
MTP_flags(MTPpremium_ApplyBoost::Flag::f_slots),
|
||||||
MTPVector<MTPint>(), // slots
|
MTP_vector<MTPint>({ MTP_int(slot) }),
|
||||||
channel->input
|
channel->input
|
||||||
)).done([=](const MTPpremium_MyBoosts &result) {
|
)).done([=](const MTPpremium_MyBoosts &result) {
|
||||||
done(true);
|
done(true);
|
||||||
|
@ -854,7 +857,8 @@ void SessionNavigation::showRepliesForMessage(
|
||||||
auto memento = std::make_shared<HistoryView::RepliesMemento>(
|
auto memento = std::make_shared<HistoryView::RepliesMemento>(
|
||||||
history,
|
history,
|
||||||
rootId,
|
rootId,
|
||||||
commentId);
|
commentId,
|
||||||
|
params.highlightPart);
|
||||||
memento->setFromTopic(topic);
|
memento->setFromTopic(topic);
|
||||||
showSection(std::move(memento), params);
|
showSection(std::move(memento), params);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -166,6 +166,7 @@ struct SectionShow {
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextWithEntities highlightPart;
|
||||||
Way way = Way::Forward;
|
Way way = Way::Forward;
|
||||||
anim::type animated = anim::type::normal;
|
anim::type animated = anim::type::normal;
|
||||||
anim::activation activation = anim::activation::normal;
|
anim::activation activation = anim::activation::normal;
|
||||||
|
@ -317,9 +318,11 @@ private:
|
||||||
void replaceBoostConfirm(
|
void replaceBoostConfirm(
|
||||||
not_null<PeerData*> from,
|
not_null<PeerData*> from,
|
||||||
not_null<ChannelData*> channel,
|
not_null<ChannelData*> channel,
|
||||||
|
int slot,
|
||||||
Fn<void(bool)> done);
|
Fn<void(bool)> done);
|
||||||
void applyBoostChecked(
|
void applyBoostChecked(
|
||||||
not_null<ChannelData*> channel,
|
not_null<ChannelData*> channel,
|
||||||
|
int slot,
|
||||||
Fn<void(bool)> done);
|
Fn<void(bool)> done);
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
|
|
|
@ -1211,7 +1211,7 @@ stage('crashpad', """
|
||||||
mac:
|
mac:
|
||||||
git clone https://github.com/desktop-app/crashpad.git
|
git clone https://github.com/desktop-app/crashpad.git
|
||||||
cd crashpad
|
cd crashpad
|
||||||
git checkout 171b601938
|
git checkout f07f49e287
|
||||||
git submodule init
|
git submodule init
|
||||||
git submodule update third_party/mini_chromium
|
git submodule update third_party/mini_chromium
|
||||||
ZLIB_PATH=$USED_PREFIX/include
|
ZLIB_PATH=$USED_PREFIX/include
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
AppVersion 4011001
|
AppVersion 4011003
|
||||||
AppVersionStrMajor 4.11
|
AppVersionStrMajor 4.11
|
||||||
AppVersionStrSmall 4.11.1
|
AppVersionStrSmall 4.11.3
|
||||||
AppVersionStr 4.11.1
|
AppVersionStr 4.11.3
|
||||||
BetaChannel 0
|
BetaChannel 0
|
||||||
AlphaVersion 0
|
AlphaVersion 0
|
||||||
AppVersionOriginal 4.11.1
|
AppVersionOriginal 4.11.3
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
4.11.3 (02.11.23)
|
||||||
|
|
||||||
|
- Fix adding a link to media captions in scheduled / comments.
|
||||||
|
- Fix crash in link preview options saving.
|
||||||
|
- Fix possible crash in statistics.
|
||||||
|
|
||||||
|
4.11.2 (01.11.23)
|
||||||
|
|
||||||
|
- Highlight quoted parts in jump-to-message from replies.
|
||||||
|
- Ctrl+Click on message field reply bar to jump to message.
|
||||||
|
- Fix empty link preview displaying when generation failed.
|
||||||
|
- Fix external replies in topic groups.
|
||||||
|
- Allow enabling legacy tray icon on Windows.
|
||||||
|
|
||||||
4.11.1 (29.10.23)
|
4.11.1 (29.10.23)
|
||||||
|
|
||||||
- Fix crash in emoji status select.
|
- Fix crash in emoji status select.
|
||||||
|
|
Loading…
Add table
Reference in a new issue