diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index a35291eaa..64ad8af50 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -546,6 +546,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_workmode_tray" = "Show tray icon";
"lng_settings_workmode_window" = "Show taskbar icon";
"lng_settings_close_to_taskbar" = "Close to taskbar";
+"lng_settings_monochrome_icon" = "Use monochrome icon";
"lng_settings_window_system" = "Window title";
"lng_settings_title_chat_name" = "Show chat name";
"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_header_short" = "Reply";
"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_header_short" = "Link";
"lng_link_move_up" = "Move Up";
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index 1422235cb..cc75e489e 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="4.11.3.0" />
Telegram Desktop
Telegram Messenger LLP
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index 57cea3ebf..895e49214 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,11,1,0
- PRODUCTVERSION 4,11,1,0
+ FILEVERSION 4,11,3,0
+ PRODUCTVERSION 4,11,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
- VALUE "FileVersion", "4.11.1.0"
+ VALUE "FileVersion", "4.11.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "4.11.1.0"
+ VALUE "ProductVersion", "4.11.3.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index 82062cc15..6f0135c28 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,11,1,0
- PRODUCTVERSION 4,11,1,0
+ FILEVERSION 4,11,3,0
+ PRODUCTVERSION 4,11,3,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
- VALUE "FileVersion", "4.11.1.0"
+ VALUE "FileVersion", "4.11.3.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2023"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "4.11.1.0"
+ VALUE "ProductVersion", "4.11.3.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp
index 09001ae35..a0d6f4d95 100644
--- a/Telegram/SourceFiles/api/api_sending.cpp
+++ b/Telegram/SourceFiles/api/api_sending.cpp
@@ -270,7 +270,6 @@ bool SendDice(MessageToSend &message) {
flags |= MessageFlag::HasReplyInfo;
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to;
}
- const auto replyHeader = NewMessageReplyHeader(message.action);
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, message.action.options);
InnerFillMessagePostFlags(message.action.options, peer, flags);
@@ -399,7 +398,6 @@ void SendConfirmedFile(
if (file->to.replyTo) {
flags |= MessageFlag::HasReplyInfo;
}
- const auto replyHeader = NewMessageReplyHeader(action);
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, file->to.options);
FillMessagePostFlags(action, peer, flags);
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index ad6d137b9..93d289e76 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -3391,7 +3391,6 @@ void ApiWrap::sendSharedContact(
if (action.replyTo) {
flags |= MessageFlag::HasReplyInfo;
}
- const auto replyHeader = NewMessageReplyHeader(action);
FillMessagePostFlags(action, peer, flags);
if (action.options.scheduled) {
flags |= MessageFlag::IsOrWasScheduled;
@@ -3614,14 +3613,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
action.generateLocal = true;
sendAction(action);
- const auto replyTo = action.replyTo.messageId
- ? peer->owner().message(action.replyTo.messageId)
- : nullptr;
- const auto topicRootId = replyTo
- ? replyTo->topicRootId()
- : action.replyTo.topicRootId
- ? action.replyTo.topicRootId
- : Data::ForumTopic::kGeneralId;
+ const auto clearCloudDraft = action.clearDraft;
+ const auto topicRootId = action.replyTo.topicRootId;
const auto topic = peer->forumTopicFor(topicRootId);
if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer))
|| Api::SendDice(message)) {
@@ -3674,7 +3667,6 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
const auto manualWebPage = exactWebPage
&& !ignoreWebPage
&& (message.webPage.manual || (isLast && !isFirst));
- const auto replyHeader = NewMessageReplyHeader(action);
MTPMessageMedia media = MTP_messageMediaEmpty();
if (ignoreWebPage) {
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
@@ -3718,8 +3710,6 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
mediaFlags |= MTPmessages_SendMedia::Flag::f_entities;
}
- const auto clearCloudDraft = action.clearDraft;
- const auto topicRootId = action.replyTo.topicRootId;
if (clearCloudDraft) {
sendFlags |= MTPmessages_SendMessage::Flag::f_clear_draft;
mediaFlags |= MTPmessages_SendMedia::Flag::f_clear_draft;
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
index c7b59f44b..84ea6a429 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp
@@ -281,7 +281,7 @@ struct GiftCodeLink {
[[nodiscard]] object_ptr MakePeerTableValue(
not_null parent,
- not_null controller,
+ not_null controller,
PeerId id) {
auto result = object_ptr(parent);
const auto raw = result.data();
@@ -309,7 +309,7 @@ struct GiftCodeLink {
label->setTextColorOverride(st::windowActiveTextFg->c);
raw->setClickedCallback([=] {
- controller->show(PrepareShortInfoBox(peer, controller));
+ controller->uiShow()->showBox(PrepareShortInfoBox(peer, controller));
});
return result;
@@ -350,7 +350,7 @@ not_null AddTableRow(
void AddTableRow(
not_null table,
rpl::producer label,
- not_null controller,
+ not_null controller,
PeerId id) {
if (!id) {
return;
@@ -416,7 +416,7 @@ QString GiftDuration(int months) {
void GiftCodeBox(
not_null box,
- not_null controller,
+ not_null controller,
const QString &slug) {
struct State {
rpl::variable data;
@@ -552,7 +552,7 @@ void GiftCodeBox(
st::giveawayGiftCodeFooterMargin);
footer->setClickHandlerFilter([=](const auto &...) {
const auto chosen = [=](not_null thread) {
- const auto content = controller->content();
+ const auto content = controller->parentController()->content();
return content->shareUrl(
thread,
MakeGiftCodeLink(&controller->session(), slug).link,
@@ -608,13 +608,13 @@ void GiftCodeBox(
}
void ResolveGiftCode(
- not_null controller,
+ not_null controller,
const QString &slug) {
const auto done = [=](Api::GiftCode code) {
if (!code) {
controller->showToast(tr::lng_gift_link_expired(tr::now));
} else {
- controller->show(Box(GiftCodeBox, controller, slug));
+ controller->uiShow()->showBox(Box(GiftCodeBox, controller, slug));
}
};
controller->session().api().premium().checkGiftCode(
@@ -624,7 +624,7 @@ void ResolveGiftCode(
void GiveawayInfoBox(
not_null box,
- not_null controller,
+ not_null controller,
Data::Giveaway giveaway,
Api::GiveawayInfo info) {
using State = Api::GiveawayState;
@@ -784,7 +784,7 @@ void GiveawayInfoBox(
}
void ResolveGiveawayInfo(
- not_null controller,
+ not_null controller,
not_null peer,
MsgId messageId,
Data::Giveaway giveaway) {
@@ -793,7 +793,7 @@ void ResolveGiveawayInfo(
controller->showToast(
tr::lng_confirm_phone_link_invalid(tr::now));
} else {
- controller->show(
+ controller->uiShow()->showBox(
Box(GiveawayInfoBox, controller, giveaway, info));
}
};
diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h
index 3f6d273bc..a1062dead 100644
--- a/Telegram/SourceFiles/boxes/gift_premium_box.h
+++ b/Telegram/SourceFiles/boxes/gift_premium_box.h
@@ -25,6 +25,7 @@ class GenericBox;
namespace Window {
class SessionController;
+class SessionNavigation;
} // namespace Window
class GiftPremiumValidator final {
@@ -47,14 +48,14 @@ private:
void GiftCodeBox(
not_null box,
- not_null controller,
+ not_null controller,
const QString &slug);
void ResolveGiftCode(
- not_null controller,
+ not_null controller,
const QString &slug);
void ResolveGiveawayInfo(
- not_null controller,
+ not_null controller,
not_null peer,
MsgId messageId,
Data::Giveaway giveaway);
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp
index 2c1e361fb..3395a5850 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp
@@ -760,7 +760,7 @@ int PeerListRow::paintNameIconGetWidth(
nameWidth,
outerWidth,
{
- .peer = _peer,
+ .peer = peer(),
.verified = &(selected
? st::dialogsVerifiedIconOver
: st::dialogsVerifiedIcon),
diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp
index d205ab4b0..d26140916 100644
--- a/Telegram/SourceFiles/calls/calls_instance.cpp
+++ b/Telegram/SourceFiles/calls/calls_instance.cpp
@@ -712,13 +712,24 @@ void Instance::destroyCurrentCall() {
}
}
-bool Instance::hasActivePanel(not_null session) const {
+bool Instance::hasVisiblePanel(Main::Session *session) const {
if (inCall()) {
- return (&_currentCall->user()->session() == session)
- && _currentCallPanel->isActive();
+ return _currentCallPanel->isVisible()
+ && (!session || (&_currentCall->user()->session() == session));
} else if (inGroupCall()) {
- return (&_currentGroupCall->peer()->session() == session)
- && _currentGroupCallPanel->isActive();
+ return _currentGroupCallPanel->isVisible()
+ && (!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;
}
diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h
index 4113a600e..e15d800be 100644
--- a/Telegram/SourceFiles/calls/calls_instance.h
+++ b/Telegram/SourceFiles/calls/calls_instance.h
@@ -89,8 +89,10 @@ public:
[[nodiscard]] rpl::producer currentGroupCallValue() const;
[[nodiscard]] bool inCall() const;
[[nodiscard]] bool inGroupCall() const;
+ [[nodiscard]] bool hasVisiblePanel(
+ Main::Session *session = nullptr) const;
[[nodiscard]] bool hasActivePanel(
- not_null session) const;
+ Main::Session *session = nullptr) const;
bool activateCurrentCall(const QString &joinHash = QString());
bool minimizeCurrentActiveCall();
bool toggleFullScreenCurrentActiveCall();
diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp
index 46481cad6..857596475 100644
--- a/Telegram/SourceFiles/calls/calls_panel.cpp
+++ b/Telegram/SourceFiles/calls/calls_panel.cpp
@@ -106,12 +106,15 @@ Panel::Panel(not_null call)
Panel::~Panel() = default;
-bool Panel::isActive() const {
- return window()->isActiveWindow()
- && window()->isVisible()
+bool Panel::isVisible() const {
+ return window()->isVisible()
&& !(window()->windowState() & Qt::WindowMinimized);
}
+bool Panel::isActive() const {
+ return window()->isActiveWindow() && isVisible();
+}
+
void Panel::showAndActivate() {
if (window()->isHidden()) {
window()->show();
diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h
index 16836db62..dc715584a 100644
--- a/Telegram/SourceFiles/calls/calls_panel.h
+++ b/Telegram/SourceFiles/calls/calls_panel.h
@@ -61,6 +61,7 @@ public:
Panel(not_null call);
~Panel();
+ [[nodiscard]] bool isVisible() const;
[[nodiscard]] bool isActive() const;
void showAndActivate();
void minimize();
diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
index 6002963a9..d3bac6aeb 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp
@@ -258,12 +258,15 @@ not_null Panel::call() const {
return _call;
}
-bool Panel::isActive() const {
- return window()->isActiveWindow()
- && window()->isVisible()
+bool Panel::isVisible() const {
+ return window()->isVisible()
&& !(window()->windowState() & Qt::WindowMinimized);
}
+bool Panel::isActive() const {
+ return window()->isActiveWindow() && isVisible();
+}
+
base::weak_ptr Panel::showToast(
const QString &text,
crl::time duration) {
diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h
index cf9352697..851cc91d8 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_panel.h
+++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h
@@ -91,6 +91,7 @@ public:
[[nodiscard]] not_null widget() const;
[[nodiscard]] not_null call() const;
+ [[nodiscard]] bool isVisible() const;
[[nodiscard]] bool isActive() const;
base::weak_ptr showToast(
diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp
index 9c5b37751..475e87cb3 100644
--- a/Telegram/SourceFiles/chat_helpers/message_field.cpp
+++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp
@@ -493,10 +493,7 @@ InlineBotQuery ParseInlineBotQuery(
result.lookingUpBot = true;
}
}
- if (result.lookingUpBot) {
- result.query = QString();
- return result;
- } else if (result.bot
+ if (result.bot
&& (!result.bot->isBot()
|| result.bot->botInfo->inlinePlaceholder.isEmpty())) {
result.bot = nullptr;
diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp
index 156cc4ed0..428ab38c3 100644
--- a/Telegram/SourceFiles/core/core_settings.cpp
+++ b/Telegram/SourceFiles/core/core_settings.cpp
@@ -348,6 +348,8 @@ QByteArray Settings::serialize() const {
for (const auto &id : _recentEmojiSkip) {
stream << id;
}
+ stream
+ << qint32(_trayIconMonochrome.current() ? 1 : 0);
}
return result;
}
@@ -457,6 +459,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
quint64 macRoundIconDigest = _macRoundIconDigest.value_or(0);
qint32 storiesClickTooltipHidden = _storiesClickTooltipHidden.current() ? 1 : 0;
base::flat_set recentEmojiSkip;
+ qint32 trayIconMonochrome = (_trayIconMonochrome.current() ? 1 : 0);
stream >> themesAccentColors;
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) {
LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()"));
@@ -901,6 +910,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
_macRoundIconDigest = macRoundIconDigest ? macRoundIconDigest : std::optional();
_storiesClickTooltipHidden = (storiesClickTooltipHidden == 1);
_recentEmojiSkip = std::move(recentEmojiSkip);
+ _trayIconMonochrome = (trayIconMonochrome == 1);
}
QString Settings::getSoundPath(const QString &key) const {
diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h
index ea5fb1656..c06f9d817 100644
--- a/Telegram/SourceFiles/core/core_settings.h
+++ b/Telegram/SourceFiles/core/core_settings.h
@@ -708,6 +708,15 @@ public:
[[nodiscard]] rpl::producer closeToTaskbarChanges() const {
return _closeToTaskbar.changes();
}
+ void setTrayIconMonochrome(bool value) {
+ _trayIconMonochrome = value;
+ }
+ [[nodiscard]] bool trayIconMonochrome() const {
+ return _trayIconMonochrome.current();
+ }
+ [[nodiscard]] rpl::producer trayIconMonochromeChanges() const {
+ return _trayIconMonochrome.changes();
+ }
void setCustomDeviceModel(const QString &model) {
_customDeviceModel = model;
@@ -924,6 +933,7 @@ private:
rpl::variable _workMode = WorkMode::WindowAndTray;
base::flags _hiddenGroupCallTooltips;
rpl::variable _closeToTaskbar = false;
+ rpl::variable _trayIconMonochrome = true;
rpl::variable _customDeviceModel;
rpl::variable _playerRepeatMode;
rpl::variable _playerOrderMode;
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index 0aadab082..8b121852e 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs;
-constexpr auto AppVersion = 4011001;
-constexpr auto AppVersionStr = "4.11.1";
+constexpr auto AppVersion = 4011003;
+constexpr auto AppVersionStr = "4.11.3";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp
index 66bf9d540..298dae217 100644
--- a/Telegram/SourceFiles/data/data_histories.cpp
+++ b/Telegram/SourceFiles/data/data_histories.cpp
@@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/notifications_manager.h"
#include "history/history.h"
#include "history/history_item.h"
+#include "history/history_item_helpers.h"
#include "history/view/history_view_element.h"
#include "core/application.h"
#include "apiwrap.h"
@@ -52,8 +53,18 @@ MTPInputReplyTo ReplyToForMTP(
}
}
} 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
- && (replyTo.messageId.peer != history->peer->id);
+ && (replyTo.messageId.peer != history->peer->id
+ || replyingToTopicId != replyToTopicId);
const auto quoteEntities = Api::EntitiesToMTP(
&history->session(),
replyTo.quote.entities,
diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp
index 9cf49244a..cb0278a27 100644
--- a/Telegram/SourceFiles/data/data_media_types.cpp
+++ b/Telegram/SourceFiles/data/data_media_types.cpp
@@ -538,6 +538,7 @@ ItemPreview Media::toGroupPreview(
auto videoCount = 0;
auto audioCount = 0;
auto fileCount = 0;
+ auto manyCaptions = false;
for (const auto &item : items) {
if (const auto media = item->media()) {
if (media->photo()) {
@@ -571,12 +572,12 @@ ItemPreview Media::toGroupPreview(
if (result.text.text.isEmpty()) {
result.text = original;
} else {
- result.text = {};
+ manyCaptions = true;
}
}
}
}
- if (result.text.text.isEmpty()) {
+ if (manyCaptions || result.text.text.isEmpty()) {
const auto mediaCount = photoCount + videoCount;
auto genericText = (photoCount && videoCount)
? tr::lng_in_dlg_media_count(tr::now, lt_count, mediaCount)
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
index deb786a28..89a2e6b49 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp
@@ -579,11 +579,6 @@ bool InnerWidget::elementUnderCursor(
return (Element::Hovered() == view);
}
-float64 InnerWidget::elementHighlightOpacity(
- not_null item) const {
- return 0.;
-}
-
bool InnerWidget::elementInSelectionMode() {
return false;
}
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
index 36c321810..da7653b1b 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h
@@ -93,8 +93,6 @@ public:
HistoryView::Context elementContext() override;
bool elementUnderCursor(
not_null view) override;
- [[nodiscard]] float64 elementHighlightOpacity(
- not_null item) const override;
bool elementInSelectionMode() override;
bool elementIntersectsRange(
not_null view,
diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp
index 2657d872a..6059be072 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.cpp
+++ b/Telegram/SourceFiles/history/history_inner_widget.cpp
@@ -207,10 +207,6 @@ public:
not_null view) override {
return (Element::Moused() == view);
}
- [[nodiscard]] float64 elementHighlightOpacity(
- not_null item) const override {
- return _widget ? _widget->elementHighlightOpacity(item) : 0.;
- }
bool elementInSelectionMode() override {
return _widget ? _widget->inSelectionMode() : false;
}
@@ -1065,6 +1061,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto clip = e->rect();
auto context = preparePaintContext(clip);
+ context.highlightPathCache = &_highlightPathCache;
_pathGradient->startFrame(
0,
width(),
@@ -1148,7 +1145,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
} else if (item->isUnreadMention()
&& !item->isUnreadMedia()) {
readContents.insert(item);
- _widget->enqueueMessageHighlight(view);
+ _widget->enqueueMessageHighlight(view, {});
}
}
session().data().reactions().poll(item, context.now);
@@ -1190,6 +1187,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
view,
selfromy - mtop,
seltoy - mtop);
+ context.highlight = _widget->itemHighlight(view->data());
view->draw(p, context);
processPainted(view, top, height);
@@ -1224,9 +1222,10 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
const auto &sendingAnimation = _controller->sendingAnimation();
while (top < drawToY) {
const auto height = view->height();
+ const auto item = view->data();
if ((context.clip.y() < height)
&& (hdrawtop < top + height)
- && !sendingAnimation.hasAnimatedMessage(view->data())) {
+ && !sendingAnimation.hasAnimatedMessage(item)) {
context.reactionInfo
= _reactionsManager->currentReactionPaintInfo();
context.outbg = view->hasOutLayout();
@@ -1234,6 +1233,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
view,
selfromy - htop,
seltoy - htop);
+ context.highlight = _widget->itemHighlight(item);
view->draw(p, context);
processPainted(view, top, height);
}
@@ -1678,7 +1678,10 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but
Ui::MarkInactivePress(_controller->widget(), false);
}
- if (ClickHandler::getPressed()) {
+ const auto pressed = ClickHandler::getPressed();
+ if (pressed
+ && (!Element::Hovered()
+ || !Element::Hovered()->allowTextSelectionByHandler(pressed))) {
_mouseAction = MouseAction::PrepareDrag;
} else if (inSelectionMode()) {
if (_dragStateItem
@@ -2111,7 +2114,7 @@ void HistoryInner::toggleFavoriteReaction(not_null view) const {
item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick);
}
-TextWithEntities HistoryInner::selectedQuote(
+HistoryView::SelectedQuote HistoryInner::selectedQuote(
not_null item) const {
if (_selected.size() != 1
|| _selected.begin()->first != item
@@ -2401,25 +2404,22 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
: (Data::CanSendAnything(peer)
&& (!peer->isChannel() || peer->asChannel()->amIn()));
}();
- const auto canReply = canSendReply || [&] {
- 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;
- }();
+ const auto canReply = canSendReply || item->allowsForward();
if (canReply) {
- const auto itemId = item->fullId();
- const auto quote = selectedQuote(item);
- auto text = quote.empty()
- ? tr::lng_context_reply_msg(tr::now)
- : tr::lng_context_quote_and_reply(tr::now);
+ const auto selected = selectedQuote(item);
+ auto text = selected
+ ? tr::lng_context_quote_and_reply(tr::now)
+ : tr::lng_context_reply_msg(tr::now);
+ const auto replyToItem = selected.item ? selected.item : item;
+ const auto itemId = replyToItem->fullId();
+ const auto quote = selected.text;
text.replace('&', u"&&"_q);
_menu->addAction(text, [=] {
if (canSendReply) {
_widget->replyToMessage({ itemId, quote });
+ if (!quote.empty()) {
+ _widget->clearSelected();
+ }
} else {
HistoryView::Controls::ShowReplyToChatBox(
controller->uiShow(),
@@ -3484,11 +3484,6 @@ void HistoryInner::elementStartStickerLoop(
_animatedStickersPlayed.emplace(view->data());
}
-float64 HistoryInner::elementHighlightOpacity(
- not_null item) const {
- return _widget->highlightOpacity(item);
-}
-
void HistoryInner::elementShowPollResults(
not_null poll,
FullMsgId context) {
@@ -3855,6 +3850,10 @@ void HistoryInner::mouseActionUpdate() {
selState = view->adjustSelection(selState, _mouseSelectType);
}
}
+ if (!selState.empty()) {
+ // We started selecting text in web page preview.
+ ClickHandler::unpressed();
+ }
if (_selected[_mouseActionItem] != selState) {
_selected[_mouseActionItem] = selState;
repaintItem(_mouseActionItem);
diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h
index 981b9e424..810521f50 100644
--- a/Telegram/SourceFiles/history/history_inner_widget.h
+++ b/Telegram/SourceFiles/history/history_inner_widget.h
@@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/scroll_area.h"
#include "history/view/history_view_top_bar_widget.h"
+#include
+
struct ClickContext;
struct ClickHandlerContext;
@@ -33,6 +35,7 @@ class EmptyPainter;
class Element;
class TranslateTracker;
struct PinnedId;
+struct SelectedQuote;
} // namespace HistoryView
namespace HistoryView::Reactions {
@@ -136,8 +139,6 @@ public:
int from,
int till) const;
void elementStartStickerLoop(not_null view);
- [[nodiscard]] float64 elementHighlightOpacity(
- not_null item) const;
void elementShowPollResults(
not_null poll,
FullMsgId context);
@@ -314,7 +315,7 @@ private:
QPoint mapPointToItem(QPoint p, const Element *view) const;
QPoint mapPointToItem(QPoint p, const HistoryItem *item) const;
- [[nodiscard]] TextWithEntities selectedQuote(
+ [[nodiscard]] HistoryView::SelectedQuote selectedQuote(
not_null item) const;
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
@@ -462,6 +463,7 @@ private:
std::optional _chooseForReportReason;
const std::unique_ptr _pathGradient;
+ QPainterPath _highlightPathCache;
bool _isChatWide = false;
base::flat_set> _animatedStickersPlayed;
diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp
index 61ca6e17f..9199fa370 100644
--- a/Telegram/SourceFiles/history/history_item.cpp
+++ b/Telegram/SourceFiles/history/history_item.cpp
@@ -470,6 +470,14 @@ HistoryItem::HistoryItem(
const auto dropForwardInfo = original->computeDropForwardedInfo();
config.reply.messageId = config.reply.topMessageId = topicRootId;
config.reply.topicPost = (topicRootId != 0);
+ if (const auto originalReply = original->Get()) {
+ if (originalReply->external()) {
+ config.reply = originalReply->fields();
+ if (!config.reply.externalPeerId) {
+ config.reply.messageId = 0;
+ }
+ }
+ }
if (!dropForwardInfo) {
config.originalDate = original->originalDate();
if (const auto info = original->hiddenSenderInfo()) {
@@ -3389,16 +3397,28 @@ void HistoryItem::createComponentsHelper(
? replyTo.messageId.peer
: PeerId();
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
? replyToTop
: (replyTo.messageId.peer == history()->peer->id)
? replyTo.messageId.msg
: MsgId();
+ if (!config.reply.externalPeerId
+ && to
+ && config.reply.topicPost
+ && replyTo.topicRootId != to->topicRootId()) {
+ config.reply.externalPeerId = replyTo.messageId.peer;
+ }
const auto forum = _history->asForum();
- config.reply.topicPost = LookupReplyIsTopicPost(to)
- || (to && to->Has())
- || (forum && forum->creating(config.reply.topMessageId));
+ config.reply.topicPost = config.reply.externalPeerId
+ ? (replyTo.topicRootId
+ && (replyTo.topicRootId != Data::ForumTopic::kGeneralId))
+ : (LookupReplyIsTopicPost(to)
+ || (to && to->Has())
+ || (forum && forum->creating(config.reply.topMessageId)));
+ config.reply.manualQuote = !replyTo.quote.empty();
config.reply.quote = std::move(replyTo.quote);
}
config.markup = std::move(markup);
diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp
index 67dcd9b92..c9f01a2c0 100644
--- a/Telegram/SourceFiles/history/history_item_components.cpp
+++ b/Telegram/SourceFiles/history/history_item_components.cpp
@@ -397,9 +397,6 @@ ReplyFields ReplyFieldsFromMTP(
auto result = ReplyFields();
if (const auto peer = data.vreply_to_peer_id()) {
result.externalPeerId = peerFromMTP(*peer);
- if (result.externalPeerId == history->peer->id) {
- result.externalPeerId = 0;
- }
}
const auto owner = &history->owner();
if (const auto id = data.vreply_to_msg_id().value_or_empty()) {
@@ -426,6 +423,7 @@ ReplyFields ReplyFieldsFromMTP(
&owner->session(),
data.vquote_entities().value_or_empty()),
};
+ result.manualQuote = data.is_quote();
return result;
}, [&](const MTPDmessageReplyStoryHeader &data) {
return ReplyFields{
@@ -525,8 +523,7 @@ bool HistoryMessageReply::updateData(
}
}
- const auto external = _fields.externalSenderId
- || !_fields.externalSenderName.isEmpty();
+ const auto external = this->external();
if (resolvedMessage
|| resolvedStory
|| (external && (!_fields.messageId || force))) {
@@ -624,13 +621,15 @@ void HistoryMessageReply::setLinkFrom(
if (externalPeerId) {
controller->showPeerInfo(
controller->session().data().peer(externalPeerId));
- } else {
- controller->showToast(u"External reply"_q);
}
+ controller->showToast(tr::lng_reply_from_private_chat(tr::now));
}
};
_link = resolvedMessage
- ? JumpToMessageClickHandler(resolvedMessage.get(), holder->fullId())
+ ? JumpToMessageClickHandler(
+ resolvedMessage.get(),
+ holder->fullId(),
+ _fields.manualQuote ? _fields.quote : TextWithEntities())
: resolvedStory
? JumpToStoryClickHandler(resolvedStory.get())
: (external && !_fields.messageId)
@@ -656,10 +655,18 @@ void HistoryMessageReply::clearData(not_null holder) {
resolvedStory.get());
resolvedStory = nullptr;
}
+ _name.clear();
+ _text.clear();
_unavailable = 1;
refreshReplyToMedia();
}
+bool HistoryMessageReply::external() const {
+ return _fields.externalPeerId
+ || _fields.externalSenderId
+ || !_fields.externalSenderName.isEmpty();
+}
+
PeerData *HistoryMessageReply::sender(not_null holder) const {
if (resolvedStory) {
return resolvedStory->peer();
@@ -832,7 +839,7 @@ void HistoryMessageReply::paint(
y += st::historyReplyTop;
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 colorPeer = resolvedMessage
? resolvedMessage->displayFrom()
diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h
index c3bf23770..c250e91fb 100644
--- a/Telegram/SourceFiles/history/history_item_components.h
+++ b/Telegram/SourceFiles/history/history_item_components.h
@@ -239,6 +239,7 @@ struct ReplyFields {
MsgId topMessageId = 0;
StoryId storyId = 0;
bool topicPost = false;
+ bool manualQuote = false;
};
[[nodiscard]] ReplyFields ReplyFieldsFromMTP(
@@ -273,6 +274,7 @@ struct HistoryMessageReply
// Must be called before destructor.
void clearData(not_null holder);
+ [[nodiscard]] bool external() const;
[[nodiscard]] PeerData *sender(not_null holder) const;
[[nodiscard]] QString senderName(not_null holder) const;
[[nodiscard]] QString senderName(not_null peer) const;
@@ -300,6 +302,9 @@ struct HistoryMessageReply
bool inBubble) const;
void unloadPersistentAnimation();
+ [[nodiscard]] ReplyFields fields() const {
+ return _fields;
+ }
[[nodiscard]] PeerId externalPeerId() const {
return _fields.externalPeerId;
}
@@ -321,6 +326,9 @@ struct HistoryMessageReply
[[nodiscard]] bool topicPost() const {
return _fields.topicPost;
}
+ [[nodiscard]] bool manualQuote() const {
+ return _fields.manualQuote;
+ }
[[nodiscard]] QString statePhrase() const;
void setLinkFrom(not_null holder);
diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp
index f30eb32d9..375a6ad7b 100644
--- a/Telegram/SourceFiles/history/history_item_helpers.cpp
+++ b/Telegram/SourceFiles/history/history_item_helpers.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/history_item_helpers.h"
+#include "api/api_text_entities.h"
#include "calls/calls_instance.h"
#include "data/notify/data_notify_settings.h"
#include "data/data_chat_participant_status.h"
@@ -202,6 +203,14 @@ MsgId LookupReplyToTop(not_null history, HistoryItem *replyTo) {
: 0;
}
+MsgId LookupReplyToTop(not_null history, FullReplyTo replyTo) {
+ return replyTo.topicRootId
+ ? replyTo.topicRootId
+ : LookupReplyToTop(
+ history,
+ LookupReplyTo(history, replyTo.messageId));
+}
+
bool LookupReplyIsTopicPost(HistoryItem *replyTo) {
return replyTo
&& (replyTo->topicRootId() != Data::ForumTopic::kGeneralId);
@@ -259,17 +268,20 @@ bool IsItemScheduledUntilOnline(not_null item) {
ClickHandlerPtr JumpToMessageClickHandler(
not_null item,
- FullMsgId returnToId) {
+ FullMsgId returnToId,
+ TextWithEntities highlightPart) {
return JumpToMessageClickHandler(
item->history()->peer,
item->id,
- returnToId);
+ returnToId,
+ std::move(highlightPart));
}
ClickHandlerPtr JumpToMessageClickHandler(
not_null peer,
MsgId msgId,
- FullMsgId returnToId) {
+ FullMsgId returnToId,
+ TextWithEntities highlightPart) {
return std::make_shared([=] {
const auto separate = Core::App().separateWindowForPeer(peer);
const auto controller = separate
@@ -279,6 +291,7 @@ ClickHandlerPtr JumpToMessageClickHandler(
auto params = Window::SectionShow{
Window::SectionShow::Way::Forward
};
+ params.highlightPart = highlightPart;
params.origin = Window::SectionShow::OriginMessage{
returnToId
};
@@ -369,19 +382,27 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) {
const auto externalPeerId = (replyTo.messageId.peer == historyPeer)
? PeerId()
: replyTo.messageId.peer;
- const auto to = LookupReplyTo(action.history, replyTo.messageId);
- const auto replyToTop = LookupReplyToTop(action.history, to);
+ const auto replyToTop = LookupReplyToTop(action.history, replyTo);
+ auto quoteEntities = Api::EntitiesToMTP(
+ &action.history->session(),
+ replyTo.quote.entities,
+ Api::ConvertOption::SkipLocal);
return MTP_messageReplyHeader(
MTP_flags(Flag::f_reply_to_msg_id
| (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),
peerToMTP(externalPeerId),
MTPMessageFwdHeader(), // reply_from
MTPMessageMedia(), // reply_media
- MTP_int(replyToTop), // reply_to_top_id
- MTPstring(), // quote_text
- MTPVector()); // quote_entities
+ MTP_int(replyToTop),
+ MTP_string(replyTo.quote.text),
+ quoteEntities);
}
return MTPMessageReplyHeader();
}
diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h
index 8dd151274..cf1045abd 100644
--- a/Telegram/SourceFiles/history/history_item_helpers.h
+++ b/Telegram/SourceFiles/history/history_item_helpers.h
@@ -89,6 +89,9 @@ void RequestDependentMessageStory(
[[nodiscard]] MsgId LookupReplyToTop(
not_null history,
HistoryItem *replyTo);
+[[nodiscard]] MsgId LookupReplyToTop(
+ not_null history,
+ FullReplyTo replyTo);
[[nodiscard]] bool LookupReplyIsTopicPost(HistoryItem *replyTo);
struct SendingErrorRequest {
@@ -120,10 +123,12 @@ struct SendingErrorRequest {
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
not_null peer,
MsgId msgId,
- FullMsgId returnToId = FullMsgId());
+ FullMsgId returnToId = FullMsgId(),
+ TextWithEntities highlightPart = {});
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
not_null item,
- FullMsgId returnToId = FullMsgId());
+ FullMsgId returnToId = FullMsgId(),
+ TextWithEntities highlightPart = {});
[[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler(
not_null story);
ClickHandlerPtr JumpToStoryClickHandler(
diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp
index 939e56980..29e157344 100644
--- a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp
+++ b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp
@@ -8,14 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_view_highlight_manager.h"
#include "data/data_session.h"
+#include "history/history.h"
#include "history/history_item.h"
#include "history/view/history_view_element.h"
+#include "ui/chat/chat_style.h"
namespace HistoryView {
-constexpr auto kAnimationFirstPart = st::activeFadeInDuration
- / float64(st::activeFadeInDuration + st::activeFadeOutDuration);
-
ElementHighlighter::ElementHighlighter(
not_null data,
ViewForItem viewForItem,
@@ -26,63 +25,95 @@ ElementHighlighter::ElementHighlighter(
, _animation(*this) {
}
-void ElementHighlighter::enqueue(not_null view) {
- const auto item = view->data();
- const auto fullId = item->fullId();
+void ElementHighlighter::enqueue(
+ not_null view,
+ const TextWithEntities &part) {
+ const auto data = computeHighlight(view, part);
if (_queue.empty() && !_animation.animating()) {
- highlight(fullId);
- } else if (_highlightedMessageId != fullId
- && !base::contains(_queue, fullId)) {
- _queue.push_back(fullId);
+ highlight(data);
+ } else if (_highlighted != data && !base::contains(_queue, data)) {
+ _queue.push_back(data);
checkNextHighlight();
}
}
+void ElementHighlighter::highlight(
+ not_null view,
+ const TextWithEntities &part) {
+ highlight(computeHighlight(view, part));
+}
+
void ElementHighlighter::checkNextHighlight() {
if (_animation.animating()) {
return;
}
- const auto nextHighlight = [&] {
+ const auto next = [&] {
while (!_queue.empty()) {
- const auto fullId = _queue.front();
+ const auto highlight = _queue.front();
_queue.pop_front();
- if (const auto item = _data->message(fullId)) {
+ if (const auto item = _data->message(highlight.itemId)) {
if (_viewForItem(item)) {
- return fullId;
+ return highlight;
}
}
}
- return FullMsgId();
+ return Highlight();
}();
- if (!nextHighlight) {
- return;
+ if (next) {
+ highlight(next);
}
- highlight(nextHighlight);
}
-float64 ElementHighlighter::progress(
+Ui::ChatPaintHighlight ElementHighlighter::state(
not_null item) const {
- if (item->fullId() == _highlightedMessageId) {
- const auto progress = _animation.progress();
- return std::min(progress / kAnimationFirstPart, 1.)
- - ((progress - kAnimationFirstPart) / (1. - kAnimationFirstPart));
+ if (item->fullId() == _highlighted.itemId) {
+ auto result = _animation.state();
+ result.range = _highlighted.part;
+ return result;
}
- return 0.;
+ return {};
}
-void ElementHighlighter::highlight(FullMsgId itemId) {
- if (const auto item = _data->message(itemId)) {
+ElementHighlighter::Highlight ElementHighlighter::computeHighlight(
+ not_null 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 (_highlightedMessageId
- && _highlightedMessageId != itemId) {
- if (const auto was = _data->message(_highlightedMessageId)) {
+ if (_highlighted && _highlighted.itemId != data.itemId) {
+ if (const auto was = _data->message(_highlighted.itemId)) {
if (const auto view = _viewForItem(was)) {
repaintHighlightedItem(view);
}
}
}
- _highlightedMessageId = itemId;
- _animation.start();
+ _highlighted = data;
+ _animation.start(!data.part.empty()
+ && !IsSubGroupSelection(data.part));
repaintHighlightedItem(view);
}
@@ -105,7 +136,7 @@ void ElementHighlighter::repaintHighlightedItem(
}
void ElementHighlighter::updateMessage() {
- if (const auto item = _data->message(_highlightedMessageId)) {
+ if (const auto item = _data->message(_highlighted.itemId)) {
if (const auto view = _viewForItem(item)) {
repaintHighlightedItem(view);
}
@@ -114,7 +145,7 @@ void ElementHighlighter::updateMessage() {
void ElementHighlighter::clear() {
_animation.cancel();
- _highlightedMessageId = FullMsgId();
+ _highlighted = {};
_lastHighlightedMessageId = FullMsgId();
_queue.clear();
}
@@ -125,60 +156,117 @@ ElementHighlighter::AnimationManager::AnimationManager(
}
bool ElementHighlighter::AnimationManager::animating() const {
- if (anim::Disabled()) {
- return (_timer && _timer->isActive());
- } else {
+ if (_timer && _timer->isActive()) {
+ return true;
+ } else if (!anim::Disabled()) {
return _simple.animating();
}
+ return false;
}
-float64 ElementHighlighter::AnimationManager::progress() const {
+Ui::ChatPaintHighlight ElementHighlighter::AnimationManager::state() const {
if (anim::Disabled()) {
- return (_timer && _timer->isActive()) ? kAnimationFirstPart : 0.;
- } else {
- return _simple.value(0.);
+ return {
+ .opacity = !_timer ? 0. : 1.,
+ .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 {
- return _highlightedMessageId
- ? _highlightedMessageId.msg
+ return _highlighted.itemId
+ ? _highlighted.itemId.msg
: _lastHighlightedMessageId.msg;
}
-void ElementHighlighter::AnimationManager::start() {
+void ElementHighlighter::AnimationManager::start(bool withTextPart) {
+ _withTextPart = withTextPart;
const auto finish = [=] {
cancel();
_parent._lastHighlightedMessageId = base::take(
- _parent._highlightedMessageId);
+ _parent._highlighted.itemId);
_parent.checkNextHighlight();
};
cancel();
if (anim::Disabled()) {
_timer.emplace([=] {
_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();
} else {
- const auto to = 1.;
_simple.start(
[=](float64 value) {
_parent.updateMessage();
- if (value == to) {
- finish();
+ if (value == 1.) {
+ 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.,
- to,
- st::activeFadeInDuration + st::activeFadeOutDuration);
+ 1.,
+ st::activeFadeInDuration);
}
}
void ElementHighlighter::AnimationManager::cancel() {
_simple.stop();
_timer.reset();
+ _fadingOut = false;
+ _collapsed = false;
+ _collapsing = false;
}
} // namespace HistoryView
diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.h b/Telegram/SourceFiles/history/history_view_highlight_manager.h
index 43372a8b9..7fdcdfc0b 100644
--- a/Telegram/SourceFiles/history/history_view_highlight_manager.h
+++ b/Telegram/SourceFiles/history/history_view_highlight_manager.h
@@ -16,6 +16,10 @@ namespace Data {
class Session;
} // namespace Data
+namespace Ui {
+struct ChatPaintHighlight;
+} // namespace Ui
+
namespace HistoryView {
class Element;
@@ -29,40 +33,59 @@ public:
ViewForItem viewForItem,
RepaintView repaintView);
- void enqueue(not_null view);
- void highlight(FullMsgId itemId);
+ void enqueue(not_null view, const TextWithEntities &part);
+ void highlight(not_null view, const TextWithEntities &part);
void clear();
- [[nodiscard]] float64 progress(not_null item) const;
+ [[nodiscard]] Ui::ChatPaintHighlight state(
+ not_null item) const;
[[nodiscard]] MsgId latestSingleHighlightedMsgId() const;
private:
- void checkNextHighlight();
- void repaintHighlightedItem(not_null view);
- void updateMessage();
-
class AnimationManager final {
public:
AnimationManager(ElementHighlighter &parent);
[[nodiscard]] bool animating() const;
- [[nodiscard]] float64 progress() const;
- void start();
+ [[nodiscard]] Ui::ChatPaintHighlight state() const;
+ void start(bool withTextPart);
void cancel();
private:
ElementHighlighter &_parent;
Ui::Animations::Simple _simple;
std::optional _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 view,
+ const TextWithEntities &part);
+ void highlight(Highlight data);
+ void checkNextHighlight();
+ void repaintHighlightedItem(not_null view);
+ void updateMessage();
+
const not_null _data;
const ViewForItem _viewForItem;
const RepaintView _repaintView;
- FullMsgId _highlightedMessageId;
+ Highlight _highlighted;
FullMsgId _lastHighlightedMessageId;
- std::deque _queue;
+ std::deque _queue;
AnimationManager _animation;
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 059e97a73..c646a9b40 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -847,7 +847,9 @@ HistoryWidget::HistoryWidget(
});
} else {
fastShowAtEnd(action.history);
- if (cancelReply(lastKeyboardUsed) && !action.clearDraft) {
+ if (!_justMarkingAsRead
+ && cancelReply(lastKeyboardUsed)
+ && !action.clearDraft) {
saveCloudDraft();
}
}
@@ -1077,7 +1079,7 @@ void HistoryWidget::initTabbedSelector() {
if (!data.recipientOverride) {
return true;
} else if (data.recipientOverride != _peer) {
- showHistory(data.recipientOverride->id, ShowAtTheEndMsgId);
+ showHistory(data.recipientOverride->id, ShowAtTheEndMsgId, {});
}
return (data.recipientOverride == _peer);
}) | rpl::start_with_next([=](ChatHelpers::InlineChosen data) {
@@ -1272,13 +1274,14 @@ void HistoryWidget::scrollToAnimationCallback(
}
void HistoryWidget::enqueueMessageHighlight(
- not_null view) {
- _highlighter.enqueue(view);
+ not_null view,
+ const TextWithEntities &part) {
+ _highlighter.enqueue(view, part);
}
-float64 HistoryWidget::highlightOpacity(
+Ui::ChatPaintHighlight HistoryWidget::itemHighlight(
not_null item) const {
- return _highlighter.progress(item);
+ return _highlighter.state(item);
}
int HistoryWidget::itemTopForHighlight(
@@ -1395,9 +1398,7 @@ void HistoryWidget::updateInlineBotQuery() {
_inlineBotResolveRequestId = _api.request(MTPcontacts_ResolveUsername(
MTP_string(username)
)).done([=](const MTPcontacts_ResolvedPeer &result) {
- Expects(result.type() == mtpc_contacts_resolvedPeer);
-
- const auto &data = result.c_contacts_resolvedPeer();
+ const auto &data = result.data();
const auto resolvedBot = [&]() -> UserData* {
if (const auto user = session().data().processUsers(
data.vusers())) {
@@ -1976,9 +1977,10 @@ bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
void HistoryWidget::showHistory(
const PeerId &peerId,
MsgId showAtMsgId,
- bool reload) {
+ const TextWithEntities &highlightPart) {
_pinnedClickedId = FullMsgId();
_minPinnedId = std::nullopt;
+ _showAtMsgHighlightPart = {};
const auto wasDialogsEntryState = computeDialogsEntryState();
const auto startBot = (showAtMsgId == ShowAndStartBotMsgId);
@@ -1990,7 +1992,7 @@ void HistoryWidget::showHistory(
controller()->sendingAnimation().clear();
_topToast.hide(anim::type::instant);
if (_history) {
- if (_peer->id == peerId && !reload) {
+ if (_peer->id == peerId) {
updateForwarding();
if (showAtMsgId == ShowAtUnreadMsgId
@@ -2026,10 +2028,10 @@ void HistoryWidget::showHistory(
).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom())
).arg(showAtMsgId.bare));
- delayedShowAt(showAtMsgId);
+ delayedShowAt(showAtMsgId, highlightPart);
} else if (_showAtMsgId != showAtMsgId) {
clearAllLoadRequests();
- setMsgId(showAtMsgId);
+ setMsgId(showAtMsgId, highlightPart);
firstLoadMessages();
doneShow();
}
@@ -2049,7 +2051,7 @@ void HistoryWidget::showHistory(
_cornerButtons.skipReplyReturn(skipId);
}
- setMsgId(showAtMsgId);
+ setMsgId(showAtMsgId, highlightPart);
if (_historyInited) {
DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
"Showing instant at %4."
@@ -2152,6 +2154,7 @@ void HistoryWidget::showHistory(
clearInlineBot();
_showAtMsgId = showAtMsgId;
+ _showAtMsgHighlightPart = highlightPart;
_historyInited = false;
_contactStatus = nullptr;
@@ -3306,7 +3309,7 @@ void HistoryWidget::messagesReceived(
}
_delayedShowAtRequest = 0;
- setMsgId(_delayedShowAtMsgId);
+ setMsgId(_delayedShowAtMsgId, _delayedShowAtMsgHighlightPart);
historyLoaded();
}
if (session().supportMode()) {
@@ -3528,9 +3531,16 @@ void HistoryWidget::loadMessagesDown() {
});
}
-void HistoryWidget::delayedShowAt(MsgId showAtMsgId) {
- if (!_history
- || (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId)) {
+void HistoryWidget::delayedShowAt(
+ MsgId showAtMsgId,
+ const TextWithEntities &highlightPart) {
+ if (!_history) {
+ return;
+ }
+ if (_delayedShowAtMsgHighlightPart != highlightPart) {
+ _delayedShowAtMsgHighlightPart = highlightPart;
+ }
+ if (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId) {
return;
}
@@ -3974,7 +3984,12 @@ void HistoryWidget::send(Api::SendOptions options) {
ignoreSlowmodeCountdown)) {
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));
+ _justMarkingAsRead = false;
clearFieldText();
if (_preview) {
@@ -4123,7 +4138,12 @@ PeerData *HistoryWidget::peer() const {
}
// 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) {
_showAtMsgId = showAtMsgId;
if (_history) {
@@ -4244,11 +4264,11 @@ void HistoryWidget::cornerButtonsShowAtPosition(
).arg(_history->peer->name()
).arg(_history->inboxReadTillId().bare
).arg(Logs::b(_history->loadedAtBottom())));
- showHistory(_peer->id, ShowAtUnreadMsgId);
+ showHistory(_peer->id, ShowAtUnreadMsgId, {});
} 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) {
- showHistory(_peer->id, -position.fullId.msg);
+ showHistory(_peer->id, -position.fullId.msg, {});
}
}
@@ -5197,7 +5217,7 @@ void HistoryWidget::updateFieldPlaceholder() {
if (!_editMsgId && _inlineBot && !_inlineLookingUpBot) {
_field->setPlaceholder(
rpl::single(_inlineBot->botInfo->inlinePlaceholder.mid(1)),
- _inlineBot->username().size() + 2);
+ _inlineBotUsername.size() + 2);
return;
}
@@ -5699,14 +5719,16 @@ int HistoryWidget::countInitialScrollTop() {
const auto item = getItemFromHistoryOrMigrated(_showAtMsgId);
const auto itemTop = _list->itemTop(item);
if (itemTop < 0) {
- setMsgId(0);
+ setMsgId(ShowAtUnreadMsgId);
controller()->showToast(tr::lng_message_not_found(tr::now));
return countInitialScrollTop();
} else {
const auto view = item->mainView();
Assert(view != nullptr);
- enqueueMessageHighlight(view);
+ enqueueMessageHighlight(
+ view,
+ base::take(_showAtMsgHighlightPart));
const auto result = itemTopForHighlight(view);
createUnreadBarIfBelowVisibleArea(result);
return result;
@@ -6278,6 +6300,8 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
} else {
_forwardPanel->editOptions(controller()->uiShow());
}
+ } else if (_replyTo && (e->modifiers() & Qt::ControlModifier)) {
+ jumpToReply(_replyTo);
} else if (_replyTo) {
editDraftOptions();
} else if (_kbReplyTo) {
@@ -6306,12 +6330,9 @@ void HistoryWidget::editDraftOptions() {
_preview->apply(webpage);
};
const auto replyToId = reply.messageId;
- const auto highlight = [=] {
- controller()->showPeerHistory(
- replyToId.peer,
- Window::SectionShow::Way::Forward,
- replyToId.msg);
- };
+ const auto highlight = crl::guard(this, [=](FullReplyTo to) {
+ jumpToReply(to);
+ });
using namespace HistoryView::Controls;
EditDraftOptions({
@@ -6323,10 +6344,16 @@ void HistoryWidget::editDraftOptions() {
.resolver = _preview->resolver(),
.done = done,
.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) {
if (!_history) return;
@@ -6398,7 +6425,8 @@ void HistoryWidget::handlePeerMigration() {
if (_peer != channel) {
showHistory(
channel->id,
- (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId);
+ (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId,
+ {});
channel->session().api().chatParticipants().requestCountDelayed(
channel);
} else {
@@ -6494,7 +6522,7 @@ bool HistoryWidget::showSlowmodeError() {
if (const auto item = _history->latestSendingMessage()) {
if (const auto view = item->mainView()) {
animatedScrollToItem(item->id);
- enqueueMessageHighlight(view);
+ enqueueMessageHighlight(view, {});
}
return tr::lng_slowmode_no_many(tr::now);
}
diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h
index a78875e22..87b7edde9 100644
--- a/Telegram/SourceFiles/history/history_widget.h
+++ b/Telegram/SourceFiles/history/history_widget.h
@@ -75,6 +75,7 @@ class SpoilerAnimation;
enum class ReportReason;
class ChooseThemeController;
class ContinuousScroll;
+struct ChatPaintHighlight;
} // namespace Ui
namespace Window {
@@ -146,7 +147,9 @@ public:
void loadMessages();
void loadMessagesDown();
void firstLoadMessages();
- void delayedShowAt(MsgId showAtMsgId);
+ void delayedShowAt(
+ MsgId showAtMsgId,
+ const TextWithEntities &highlightPart);
bool updateReplaceMediaButton();
void updateFieldPlaceholder();
@@ -160,7 +163,9 @@ public:
History *history() const;
PeerData *peer() const;
- void setMsgId(MsgId showAtMsgId);
+ void setMsgId(
+ MsgId showAtMsgId,
+ const TextWithEntities &highlightPart = {});
MsgId msgId() const;
bool hasTopBarShadow() const {
@@ -177,8 +182,10 @@ public:
bool touchScroll(const QPoint &delta);
- void enqueueMessageHighlight(not_null view);
- [[nodiscard]] float64 highlightOpacity(
+ void enqueueMessageHighlight(
+ not_null view,
+ const TextWithEntities &part);
+ [[nodiscard]] Ui::ChatPaintHighlight itemHighlight(
not_null item) const;
MessageIdsList getSelectedItems() const;
@@ -218,7 +225,10 @@ public:
void fastShowAtEnd(not_null history);
bool applyDraft(
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(
Ui::ReportReason reason,
Fn callback);
@@ -540,6 +550,7 @@ private:
void setupPreview();
void editDraftOptions();
+ void jumpToReply(FullReplyTo to);
void messagesReceived(not_null peer, const MTPmessages_Messages &messages, int requestId);
void messagesFailed(const MTP::Error &error, int requestId);
@@ -684,12 +695,14 @@ private:
bool _canSendMessages = false;
bool _canSendTexts = false;
MsgId _showAtMsgId = ShowAtUnreadMsgId;
+ TextWithEntities _showAtMsgHighlightPart;
int _firstLoadRequest = 0; // Not real mtpRequestId.
int _preloadRequest = 0; // Not real mtpRequestId.
int _preloadDownRequest = 0; // Not real mtpRequestId.
MsgId _delayedShowAtMsgId = -1;
+ TextWithEntities _delayedShowAtMsgHighlightPart;
int _delayedShowAtRequest = 0; // Not real mtpRequestId.
History *_supportPreloadHistory = nullptr;
@@ -800,6 +813,7 @@ private:
int _itemsRevealHeight = 0;
bool _sponsoredMessagesStateKnown = false;
+ bool _justMarkingAsRead = false;
object_ptr _topShadow;
bool _inGrab = false;
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
index 09eb7e76c..21ce63e1a 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
@@ -112,6 +112,7 @@ public:
std::shared_ptr show);
void setHistory(const SetHistoryArgs &args);
+ void updateTopicRootId(MsgId topicRootId);
void init();
void editMessage(FullMsgId id, bool photoEditAllowed = false);
@@ -129,7 +130,7 @@ public:
[[nodiscard]] FullReplyTo replyingToMessage() const;
[[nodiscard]] FullMsgId editMsgId() const;
[[nodiscard]] rpl::producer editMsgIdValue() const;
- [[nodiscard]] rpl::producer scrollToItemRequests() const;
+ [[nodiscard]] rpl::producer jumpToItemRequests() const;
[[nodiscard]] rpl::producer<> editPhotoRequests() const;
[[nodiscard]] rpl::producer<> editOptionsRequests() const;
[[nodiscard]] MessageToEdit queryToEdit();
@@ -205,7 +206,7 @@ private:
QRect _shownMessagePreviewRect;
rpl::event_stream _visibleChanged;
- rpl::event_stream _scrollToItemRequests;
+ rpl::event_stream _jumpToItemRequests;
rpl::event_stream<> _editOptionsRequests;
rpl::event_stream<> _editPhotoRequests;
@@ -229,6 +230,10 @@ void FieldHeader::setHistory(const SetHistoryArgs &args) {
_topicRootId = args.topicRootId;
}
+void FieldHeader::updateTopicRootId(MsgId topicRootId) {
+ _topicRootId = topicRootId;
+}
+
void FieldHeader::init() {
sizeValue(
) | rpl::start_with_next([=](QSize size) {
@@ -367,9 +372,14 @@ void FieldHeader::init() {
if (_preview.parsed) {
_editOptionsRequests.fire({});
} else if (isEditingMessage()) {
- _scrollToItemRequests.fire(_editMsgId.current());
+ _jumpToItemRequests.fire(FullReplyTo{
+ .messageId = _editMsgId.current()
+ });
} else if (readyToForward()) {
_forwardPanel->editOptions(_show);
+ } else if (reply
+ && (e->modifiers() & Qt::ControlModifier)) {
+ _jumpToItemRequests.fire_copy(reply);
} else if (reply) {
_editOptionsRequests.fire({});
}
@@ -724,8 +734,8 @@ rpl::producer FieldHeader::editMsgIdValue() const {
return _editMsgId.value();
}
-rpl::producer FieldHeader::scrollToItemRequests() const {
- return _scrollToItemRequests.events();
+rpl::producer FieldHeader::jumpToItemRequests() const {
+ return _jumpToItemRequests.events();
}
rpl::producer<> FieldHeader::editPhotoRequests() const {
@@ -855,6 +865,11 @@ Main::Session &ComposeControls::session() const {
return _show->session();
}
+void ComposeControls::updateTopicRootId(MsgId topicRootId) {
+ _topicRootId = topicRootId;
+ _header->updateTopicRootId(_topicRootId);
+}
+
void ComposeControls::setHistory(SetHistoryArgs &&args) {
_showSlowmodeError = std::move(args.showSlowmodeError);
_sendActionFactory = std::move(args.sendActionFactory);
@@ -1332,6 +1347,7 @@ void ComposeControls::init() {
_header->editOptionsRequests(
) | rpl::start_with_next([=] {
const auto history = _history;
+ const auto topicRootId = _topicRootId;
const auto reply = _header->replyingToMessage();
const auto webpage = _preview->draft();
@@ -1344,10 +1360,11 @@ void ComposeControls::init() {
cancelReplyMessage();
}
_preview->apply(webpage);
+ _field->setFocus();
};
const auto replyToId = reply.messageId;
- const auto highlight = crl::guard(_wrap.get(), [=] {
- _scrollToItemRequests.fire_copy(replyToId);
+ const auto highlight = crl::guard(_wrap.get(), [=](FullReplyTo to) {
+ _jumpToItemRequests.fire_copy(to);
});
using namespace HistoryView::Controls;
@@ -1360,7 +1377,10 @@ void ComposeControls::init() {
.resolver = _preview->resolver(),
.done = done,
.highlight = highlight,
- .clearOldDraft = [=] { ClearDraftReplyTo(history, replyToId); },
+ .clearOldDraft = [=] { ClearDraftReplyTo(
+ history,
+ topicRootId,
+ replyToId); },
});
}, _wrap->lifetime());
@@ -1920,6 +1940,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
_header->replyToMessage({});
if (_preview) {
_preview->apply({ .removed = true });
+ _preview->setDisabled(false);
}
_canReplaceMedia = false;
_photoEditMedia = nullptr;
@@ -1958,6 +1979,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
_preview->apply(
Data::WebPageDraft::FromItem(item),
false);
+ _preview->setDisabled(media && !media->webpage());
}
return true;
}
@@ -1989,6 +2011,9 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
cancelForward();
}
_header->editMessage({});
+ if (_preview) {
+ _preview->setDisabled(false);
+ }
}
}
@@ -2873,16 +2898,10 @@ Data::WebPageDraft ComposeControls::webPageDraft() const {
return _preview ? _preview->draft() : Data::WebPageDraft();
}
-rpl::producer ComposeControls::scrollRequests() const {
+rpl::producer ComposeControls::jumpToItemRequests() const {
return rpl::merge(
- _header->scrollToItemRequests(),
- _scrollToItemRequests.events()
- ) | rpl::map([=](FullMsgId id) -> Data::MessagePosition {
- if (const auto item = session().data().message(id)) {
- return item->position();
- }
- return {};
- });
+ _header->jumpToItemRequests(),
+ _jumpToItemRequests.events());
}
bool ComposeControls::isEditingMessage() const {
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h
index 18a76132e..9ae515d6c 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h
+++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h
@@ -135,6 +135,7 @@ public:
[[nodiscard]] Main::Session &session() const;
void setHistory(SetHistoryArgs &&args);
+ void updateTopicRootId(MsgId topicRootId);
void setCurrentDialogsEntryState(Dialogs::EntryState state);
[[nodiscard]] PeerData *sendAsPeer() const;
@@ -158,7 +159,7 @@ public:
[[nodiscard]] rpl::producer> attachRequests() const;
[[nodiscard]] rpl::producer fileChosen() const;
[[nodiscard]] rpl::producer photoChosen() const;
- [[nodiscard]] rpl::producer scrollRequests() const;
+ [[nodiscard]] rpl::producer jumpToItemRequests() const;
[[nodiscard]] rpl::producer inlineResultChosen() const;
[[nodiscard]] rpl::producer sendActionUpdates() const;
[[nodiscard]] rpl::producer> viewportEvents() const;
@@ -357,7 +358,7 @@ private:
const std::unique_ptr _wrap;
const std::unique_ptr _writeRestricted;
- rpl::event_stream _scrollToItemRequests;
+ rpl::event_stream _jumpToItemRequests;
std::optional _backgroundRect;
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp
index 483a8d280..65a1cd6de 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.cpp
@@ -99,9 +99,8 @@ public:
not_null history);
~PreviewWrap();
- [[nodiscard]] rpl::producer showQuoteSelector(
- not_null item,
- const TextWithEntities "e);
+ [[nodiscard]] rpl::producer showQuoteSelector(
+ const SelectedQuote "e);
[[nodiscard]] rpl::producer showLinkSelector(
const TextWithTags &message,
Data::WebPageDraft webpage,
@@ -212,12 +211,14 @@ PreviewWrap::~PreviewWrap() {
}
}
-rpl::producer PreviewWrap::showQuoteSelector(
- not_null item,
- const TextWithEntities "e) {
+rpl::producer PreviewWrap::showQuoteSelector(
+ const SelectedQuote "e) {
_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;
if (const auto was = base::take(_draftItem)) {
@@ -233,10 +234,13 @@ rpl::producer PreviewWrap::showQuoteSelector(
initElement();
- _selection = _element->selectionFromQuote(quote);
+ _selection = _element->selectionFromQuote(item, quote.text);
return _selection.value(
) | 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 {
rpl::variable shown;
rpl::lifetime shownLifetime;
- rpl::variable quote;
+ rpl::variable quote;
Data::WebPageDraft webpage;
WebPageData *preview = nullptr;
QString link;
@@ -596,7 +600,7 @@ void DraftOptionsBox(
rpl::lifetime resolveLifetime;
};
const auto state = box->lifetime().make_state();
- state->quote = draft.reply.quote;
+ state->quote = SelectedQuote{ replyItem, draft.reply.quote };
state->webpage = draft.webpage;
state->preview = previewData;
state->shown = previewData ? Section::Link : Section::Reply;
@@ -635,7 +639,10 @@ void DraftOptionsBox(
const auto &clearOldDraft = args.clearOldDraft;
const auto resolveReply = [=] {
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;
};
const auto finish = [=](
@@ -650,21 +657,26 @@ void DraftOptionsBox(
const auto setupReplyActions = [=] {
AddFilledSkip(bottom);
- Settings::AddButton(
- bottom,
- tr::lng_reply_in_another_chat(),
- st::settingsButton,
- { &st::menuIconReplace }
- )->setClickedCallback([=] {
- ShowReplyToChatBox(show, resolveReply(), clearOldDraft);
- });
+ const auto item = state->quote.current().item;
+ if (item->allowsForward()) {
+ Settings::AddButton(
+ bottom,
+ tr::lng_reply_in_another_chat(),
+ st::settingsButton,
+ { &st::menuIconReplace }
+ )->setClickedCallback([=] {
+ ShowReplyToChatBox(show, resolveReply(), clearOldDraft);
+ });
+ }
Settings::AddButton(
bottom,
tr::lng_reply_show_in_chat(),
st::settingsButton,
{ &st::menuIconShowInChat }
- )->setClickedCallback(highlight);
+ )->setClickedCallback([=] {
+ highlight(resolveReply());
+ });
Settings::AddButton(
bottom,
@@ -675,7 +687,7 @@ void DraftOptionsBox(
finish({}, state->webpage);
});
- if (!replyItem->originalText().empty()) {
+ if (!item->originalText().empty()) {
AddFilledSkip(bottom);
Settings::AddDividerText(
bottom,
@@ -804,7 +816,6 @@ void DraftOptionsBox(
state->shownLifetime.destroy();
if (shown == Section::Reply) {
state->quote = state->wrap->showQuoteSelector(
- replyItem,
state->quote.current());
setupReplyActions();
} else {
@@ -823,8 +834,8 @@ void DraftOptionsBox(
auto save = rpl::combine(
state->quote.value(),
state->shown.value()
- ) | rpl::map([=](const TextWithEntities "e, Section shown) {
- return (quote.empty() || shown != Section::Reply)
+ ) | rpl::map([=](const SelectedQuote "e, Section shown) {
+ return (quote.text.empty() || shown != Section::Reply)
? tr::lng_settings_save()
: tr::lng_reply_quote_selected();
}) | rpl::flatten_latest();
@@ -839,14 +850,20 @@ void DraftOptionsBox(
if (replyItem) {
args.show->session().data().itemRemoved(
) | rpl::filter([=](not_null 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([=] {
if (previewData) {
state->tabs = nullptr;
box->setPinnedToTopContent(
object_ptr(nullptr));
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_quote());
state->shown = Section::Link;
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h
index 798e1fa0c..abe19b39e 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h
+++ b/Telegram/SourceFiles/history/view/controls/history_view_draft_options.h
@@ -32,7 +32,7 @@ struct EditDraftOptionsArgs {
std::vector links;
std::shared_ptr resolver;
Fn done;
- Fn highlight;
+ Fn highlight;
Fn clearOldDraft;
};
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp
index 30869de20..98799149f 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp
@@ -400,13 +400,6 @@ void ForwardPanel::paint(
});
}
-void ClearDraftReplyTo(not_null thread, FullMsgId equalTo) {
- ClearDraftReplyTo(
- thread->owningHistory(),
- thread->topicRootId(),
- equalTo);
-}
-
void ClearDraftReplyTo(
not_null history,
MsgId topicRootId,
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h
index 7e3d2fac4..59c079d6f 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h
+++ b/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.h
@@ -72,7 +72,6 @@ private:
};
-void ClearDraftReplyTo(not_null thread, FullMsgId equalTo);
void ClearDraftReplyTo(
not_null history,
MsgId topicRootId,
diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
index 9b88150c5..63b1edafe 100644
--- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp
@@ -590,7 +590,7 @@ bool AddReplyToMessageAction(
const ContextMenuRequest &request,
not_null list) {
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 peer = item ? item->history()->peer.get() : nullptr;
if (!item
@@ -601,15 +601,7 @@ bool AddReplyToMessageAction(
const auto canSendReply = topic
? Data::CanSendAnything(topic)
: Data::CanSendAnything(peer);
- const auto canReply = canSendReply || [&] {
- 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;
- }();
+ const auto canReply = canSendReply || item->allowsForward();
if (!canReply) {
return false;
}
diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.h b/Telegram/SourceFiles/history/view/history_view_context_menu.h
index 64040ae41..fb99d2cc6 100644
--- a/Telegram/SourceFiles/history/view/history_view_context_menu.h
+++ b/Telegram/SourceFiles/history/view/history_view_context_menu.h
@@ -48,6 +48,7 @@ struct ContextMenuRequest {
SelectedItems selectedItems;
TextForMimeData selectedText;
TextWithEntities quote;
+ HistoryItem *quoteItem = nullptr;
bool overSelection = false;
PointState pointState = PointState();
};
diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp
index e7d9f1385..88251cf7a 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_element.cpp
@@ -94,6 +94,42 @@ Element *MousedElement/* = nullptr*/;
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
std::unique_ptr MakePathShiftGradient(
@@ -111,11 +147,6 @@ bool DefaultElementDelegate::elementUnderCursor(
return false;
}
-float64 DefaultElementDelegate::elementHighlightOpacity(
- not_null item) const {
- return 0.;
-}
-
bool DefaultElementDelegate::elementInSelectionMode() {
return false;
}
@@ -593,6 +624,9 @@ void Element::paintHighlight(
Painter &p,
const PaintContext &context,
int geometryHeight) const {
+ if (context.highlight.opacity == 0.) {
+ return;
+ }
const auto top = marginTop();
const auto bottom = marginBottom();
const auto fill = qMin(top, bottom);
@@ -608,18 +642,9 @@ void Element::paintCustomHighlight(
int y,
int height,
not_null item) const {
- const auto opacity = delegate()->elementHighlightOpacity(item);
- if (opacity == 0.) {
- return;
- }
const auto o = p.opacity();
- p.setOpacity(o * opacity);
- p.fillRect(
- 0,
- y,
- width(),
- height,
- context.st->msgSelectOverlay());
+ p.setOpacity(o * context.highlight.opacity);
+ p.fillRect(0, y, width(), height, context.st->msgSelectOverlay());
p.setOpacity(o);
}
@@ -1402,7 +1427,12 @@ HistoryMessageReply *Element::displayedReply() const {
}
bool Element::toggleSelectionByHandlerClick(
- const ClickHandlerPtr &handler) const {
+ const ClickHandlerPtr &handler) const {
+ return false;
+}
+
+bool Element::allowTextSelectionByHandler(
+ const ClickHandlerPtr &handler) const {
return false;
}
@@ -1572,6 +1602,105 @@ TextSelection Element::adjustSelection(
return selection;
}
+SelectedQuote Element::FindSelectedQuote(
+ const Ui::Text::String &text,
+ TextSelection selection,
+ not_null 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 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(
QPoint position,
const TextState &reactionState) const {
diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h
index b51487703..db3933b98 100644
--- a/Telegram/SourceFiles/history/view/history_view_element.h
+++ b/Telegram/SourceFiles/history/view/history_view_element.h
@@ -69,8 +69,6 @@ class ElementDelegate {
public:
virtual Context elementContext() = 0;
virtual bool elementUnderCursor(not_null view) = 0;
- [[nodiscard]] virtual float64 elementHighlightOpacity(
- not_null item) const = 0;
virtual bool elementInSelectionMode() = 0;
virtual bool elementIntersectsRange(
not_null view,
@@ -120,8 +118,6 @@ public:
class DefaultElementDelegate : public ElementDelegate {
public:
bool elementUnderCursor(not_null view) override;
- [[nodiscard]] float64 elementHighlightOpacity(
- not_null item) const override;
bool elementInSelectionMode() override;
bool elementIntersectsRange(
not_null view,
@@ -270,6 +266,16 @@ struct TopicButton {
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
: public Object
, public RuntimeComposer
@@ -391,19 +397,24 @@ public:
QPoint point,
InfoDisplayType type) const;
virtual TextForMimeData selectedText(TextSelection selection) const = 0;
- virtual TextWithEntities selectedQuote(TextSelection selection) const = 0;
- virtual TextWithEntities selectedQuote(
- const Ui::Text::String &text,
+ virtual SelectedQuote selectedQuote(
TextSelection selection) const = 0;
virtual TextSelection selectionFromQuote(
- const TextWithEntities "e) const = 0;
- virtual TextSelection selectionFromQuote(
- const Ui::Text::String &text,
+ not_null item,
const TextWithEntities "e) const = 0;
[[nodiscard]] virtual TextSelection adjustSelection(
TextSelection selection,
TextSelectType type) const;
+ [[nodiscard]] static SelectedQuote FindSelectedQuote(
+ const Ui::Text::String &text,
+ TextSelection selection,
+ not_null item);
+ [[nodiscard]] static TextSelection FindSelectionFromQuote(
+ const Ui::Text::String &text,
+ not_null item,
+ const TextWithEntities "e);
+
[[nodiscard]] virtual auto reactionButtonParameters(
QPoint position,
const TextState &reactionState) const -> Reactions::ButtonParameters;
@@ -451,6 +462,8 @@ public:
}
[[nodiscard]] virtual bool toggleSelectionByHandlerClick(
const ClickHandlerPtr &handler) const;
+ [[nodiscard]] virtual bool allowTextSelectionByHandler(
+ const ClickHandlerPtr &handler) const;
struct VerticalRepaintRange {
int top = 0;
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index 2938fffdd..410b1a337 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -707,8 +707,12 @@ bool ListWidget::isBelowPosition(Data::MessagePosition position) const {
return _items.front()->data()->position() > position;
}
-void ListWidget::highlightMessage(FullMsgId itemId) {
- _highlighter.highlight(itemId);
+void ListWidget::highlightMessage(
+ FullMsgId itemId,
+ const TextWithEntities &part) {
+ if (const auto view = viewForItem(itemId)) {
+ _highlighter.highlight(view, part);
+ }
}
void ListWidget::showAroundPosition(
@@ -741,12 +745,12 @@ bool ListWidget::jumpToBottomInsteadOfUnread() const {
void ListWidget::showAtPosition(
Data::MessagePosition position,
- anim::type animated,
+ const Window::SectionShow ¶ms,
Fn done) {
const auto showAtUnread = (position == Data::UnreadMessagePosition);
if (showAtUnread && jumpToBottomInsteadOfUnread()) {
- showAtPosition(Data::MaxMessagePosition, animated, std::move(done));
+ showAtPosition(Data::MaxMessagePosition, params, std::move(done));
return;
}
@@ -766,24 +770,24 @@ void ListWidget::showAtPosition(
_bar = {};
}
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, [=] {
- return showAtPositionNow(position, animated, done);
+ return showAtPositionNow(position, params, done);
});
}
}
bool ListWidget::showAtPositionNow(
Data::MessagePosition position,
- anim::type animated,
+ const Window::SectionShow ¶ms,
Fn done) {
if (const auto scrollTop = scrollTopForPosition(position)) {
- computeScrollTo(*scrollTop, position, animated);
+ computeScrollTo(*scrollTop, position, params.animated);
if (position != Data::MaxMessagePosition
&& position != Data::UnreadMessagePosition) {
- highlightMessage(position.fullId);
+ highlightMessage(position.fullId, params.highlightPart);
}
if (done) {
const auto found = !position.fullId.peer
@@ -1655,11 +1659,6 @@ bool ListWidget::elementUnderCursor(
return (_overElement == view);
}
-float64 ListWidget::elementHighlightOpacity(
- not_null item) const {
- return _highlighter.progress(item);
-}
-
bool ListWidget::elementInSelectionMode() {
return inSelectionMode();
}
@@ -2088,6 +2087,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
});
auto context = preparePaintContext(clip);
+ context.highlightPathCache = &_highlightPathCache;
if (from == end(_items)) {
_delegate->listPaintEmpty(p, context);
return;
@@ -2108,6 +2108,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
= _reactionsManager->currentReactionPaintInfo();
context.outbg = view->hasOutLayout();
context.selection = itemRenderSelection(view);
+ context.highlight = _highlighter.state(item);
view->draw(p, context);
}
if (_translateTracker) {
@@ -2142,7 +2143,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
} else if (item->isUnreadMention()
&& !item->isUnreadMedia()) {
readContents.insert(item);
- _highlighter.enqueue(view);
+ _highlighter.enqueue(view, {});
}
}
session->data().reactions().poll(item, context.now);
@@ -2576,7 +2577,6 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
: _overElement
? _overElement->data().get()
: nullptr;
- const auto overItemView = viewForItem(overItem);
const auto clickedReaction = link
? link->property(
kReactionsCountEmojiProperty).value()
@@ -2603,9 +2603,12 @@ void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
request.view = _overElement;
request.item = overItem;
request.pointState = _overState.pointState;
- request.quote = (overItemView && _selectedTextItem == overItem)
- ? overItemView->selectedQuote(_selectedTextRange)
- : TextWithEntities();
+ const auto quote = (_overElement
+ && _selectedTextItem == _overElement->data())
+ ? _overElement->selectedQuote(_selectedTextRange)
+ : SelectedQuote();
+ request.quote = quote.text;
+ request.quoteItem = quote.item;
request.selectedText = _selectedText;
request.selectedItems = collectSelectedItems();
const auto hasSelection = !request.selectedItems.empty()
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h
index 138f39cca..95b641a04 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.h
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h
@@ -48,6 +48,10 @@ struct ChosenReaction;
struct ButtonParameters;
} // namespace HistoryView::Reactions
+namespace Window {
+struct SectionShow;
+} // namespace Window
+
namespace HistoryView {
struct TextState;
@@ -227,11 +231,13 @@ public:
[[nodiscard]] bool animatedScrolling() const;
bool isAbovePosition(Data::MessagePosition position) const;
bool isBelowPosition(Data::MessagePosition position) const;
- void highlightMessage(FullMsgId itemId);
+ void highlightMessage(
+ FullMsgId itemId,
+ const TextWithEntities &part);
void showAtPosition(
Data::MessagePosition position,
- anim::type animated = anim::type::normal,
+ const Window::SectionShow ¶ms,
Fn done = nullptr);
void refreshViewer();
@@ -292,8 +298,6 @@ public:
// ElementDelegate interface.
Context elementContext() override;
bool elementUnderCursor(not_null view) override;
- [[nodiscard]] float64 elementHighlightOpacity(
- not_null item) const override;
bool elementInSelectionMode() override;
bool elementIntersectsRange(
not_null view,
@@ -430,7 +434,7 @@ private:
Fn overrideInitialScroll);
bool showAtPositionNow(
Data::MessagePosition position,
- anim::type animated,
+ const Window::SectionShow ¶ms,
Fn done);
Ui::ChatPaintContext preparePaintContext(const QRect &clip) const;
@@ -645,6 +649,7 @@ private:
base::flat_map _hiddenSenderUserpics;
const std::unique_ptr _pathGradient;
+ QPainterPath _highlightPathCache;
base::unique_qptr _emptyInfo = nullptr;
diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp
index a5eebddfd..f5a6d655d 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_message.cpp
@@ -65,42 +65,6 @@ const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
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 {
public:
KeyboardStyle(const style::BotKeyboardButton &st);
@@ -1018,6 +982,10 @@ void Message::draw(Painter &p, const PaintContext &context) const {
p.translate(-reactionsPosition);
}
+ if (context.highlightPathCache) {
+ context.highlightInterpolateTo = g;
+ context.highlightPathCache->clear();
+ }
if (bubble) {
if (displayFromName()
&& item->displayFrom()
@@ -1110,6 +1078,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
- (_bottomInfo.height() - st::msgDateFont->height));
}
auto textSelection = context.selection;
+ auto highlightRange = context.highlight.range;
const auto mediaHeight = mediaDisplayed ? media->height() : 0;
const auto paintMedia = [&](int top) {
if (!mediaDisplayed) {
@@ -1118,6 +1087,8 @@ void Message::draw(Painter &p, const PaintContext &context) const {
const auto mediaSelection = _invertMedia
? context.selection
: skipTextSelection(context.selection);
+ const auto maybeMediaHighlight = context.highlightPathCache
+ && context.highlightPathCache->isEmpty();
auto mediaPosition = QPoint(inner.left(), top);
p.translate(mediaPosition);
media->draw(p, context.translated(
@@ -1130,6 +1101,10 @@ void Message::draw(Painter &p, const PaintContext &context) const {
context.reactionInfo->effectOffset -= add;
}
}
+ if (maybeMediaHighlight
+ && !context.highlightPathCache->isEmpty()) {
+ context.highlightPathCache->translate(mediaPosition);
+ }
p.translate(-mediaPosition);
};
if (mediaDisplayed && _invertMedia) {
@@ -1141,8 +1116,12 @@ void Message::draw(Painter &p, const PaintContext &context) const {
+ mediaHeight
+ (mediaOnBottom ? 0 : st::mediaInBubbleSkip));
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) {
paintMedia(trect.y() + trect.height() - mediaHeight);
if (context.reactionInfo && !displayInfo && !_reactions) {
@@ -1224,6 +1203,20 @@ void Message::draw(Painter &p, const PaintContext &context) const {
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) {
p.restore();
}
@@ -1651,6 +1644,7 @@ void Message::paintText(
width());
trect.setY(trect.y() + botTop->height);
}
+ auto highlightRequest = context.computeHighlightCache();
text().draw(p, {
.position = trect.topLeft(),
.availableWidth = trect.width(),
@@ -1663,6 +1657,7 @@ void Message::paintText(
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = context.selection,
+ .highlight = highlightRequest ? &*highlightRequest : nullptr,
});
}
@@ -2651,7 +2646,7 @@ TextForMimeData Message::selectedText(TextSelection selection) const {
return result;
}
-TextWithEntities Message::selectedQuote(TextSelection selection) const {
+SelectedQuote Message::selectedQuote(TextSelection selection) const {
const auto item = data();
const auto &translated = item->translatedText();
const auto &original = item->originalText();
@@ -2666,7 +2661,7 @@ TextWithEntities Message::selectedQuote(TextSelection selection) const {
const auto textSelection = mediaBefore
? media->skipSelection(selection)
: selection;
- return selectedQuote(text(), textSelection);
+ return FindSelectedQuote(text(), textSelection, data());
} else if (const auto media = this->media()) {
if (media->isDisplayed() || isHiddenByGroup()) {
return media->selectedQuote(selection);
@@ -2675,124 +2670,30 @@ TextWithEntities Message::selectedQuote(TextSelection selection) const {
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(
+ not_null item,
const TextWithEntities "e) const {
- const auto item = data();
+ if (quote.empty()) {
+ return {};
+ }
const auto &translated = item->translatedText();
const auto &original = item->originalText();
- if (&translated != &original || quote.empty()) {
+ if (&translated != &original) {
return {};
} else if (hasVisibleText()) {
const auto media = this->media();
const auto mediaDisplayed = media && media->isDisplayed();
const auto mediaBefore = mediaDisplayed && invertMedia();
- const auto result = selectionFromQuote(text(), quote);
+ const auto result = FindSelectionFromQuote(text(), item, quote);
return mediaBefore ? media->unskipSelection(result) : result;
} else if (const auto media = this->media()) {
if (media->isDisplayed() || isHiddenByGroup()) {
- return media->selectionFromQuote(quote);
+ return media->selectionFromQuote(item, quote);
}
}
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 selection,
TextSelectType type) const {
@@ -3247,6 +3148,16 @@ bool Message::toggleSelectionByHandlerClick(
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 {
switch (context()) {
case Context::AdminLog:
@@ -4148,7 +4059,7 @@ void Message::refreshInfoSkipBlock() {
return false;
} else if (item->Has()) {
return false;
- } else if (media && media->isDisplayed()) {
+ } else if (media && media->isDisplayed() && !_invertMedia) {
return false;
} else if (_reactions) {
return false;
diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h
index 7506bf355..aeba7c1aa 100644
--- a/Telegram/SourceFiles/history/view/history_view_message.h
+++ b/Telegram/SourceFiles/history/view/history_view_message.h
@@ -95,14 +95,9 @@ public:
QPoint point,
InfoDisplayType type) const override;
TextForMimeData selectedText(TextSelection selection) const override;
- TextWithEntities selectedQuote(TextSelection selection) const override;
- TextWithEntities selectedQuote(
- const Ui::Text::String &text,
- TextSelection selection) const override;
+ SelectedQuote selectedQuote(TextSelection selection) const override;
TextSelection selectionFromQuote(
- const TextWithEntities "e) const override;
- TextSelection selectionFromQuote(
- const Ui::Text::String &text,
+ not_null item,
const TextWithEntities "e) const override;
TextSelection adjustSelection(
TextSelection selection,
@@ -145,6 +140,8 @@ public:
[[nodiscard]] HistoryMessageReply *displayedReply() const override;
[[nodiscard]] bool toggleSelectionByHandlerClick(
const ClickHandlerPtr &handler) const override;
+ [[nodiscard]] bool allowTextSelectionByHandler(
+ const ClickHandlerPtr &handler) const override;
[[nodiscard]] int infoWidth() const override;
[[nodiscard]] int bottomInfoFirstLineWidth() const override;
[[nodiscard]] bool bottomInfoIsWide() const override;
diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp
index 0fbaf0349..0472e4eb1 100644
--- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp
@@ -262,7 +262,7 @@ void PinnedWidget::showAtPosition(
FullMsgId originId) {
_inner->showAtPosition(
position,
- anim::type::normal,
+ {},
_cornerButtons.doneJumpFrom(position.fullId, originId));
}
@@ -346,7 +346,7 @@ void PinnedWidget::restoreState(not_null memento) {
? FullMsgId(_history->peer->id, highlight)
: FullMsgId(_migratedPeer->id, -highlight)),
.date = TimeId(0),
- }, anim::type::instant);
+ }, { Window::SectionShow::Way::Forward, anim::type::instant });
}
}
diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
index 1048b98b9..c652ab2e8 100644
--- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
@@ -128,10 +128,12 @@ rpl::producer RootViewContent(
RepliesMemento::RepliesMemento(
not_null history,
MsgId rootId,
- MsgId highlightId)
+ MsgId highlightId,
+ const TextWithEntities &highlightPart)
: _history(history)
, _rootId(rootId)
-, _highlightId(highlightId) {
+, _highlightId(highlightId)
+, _highlightPart(highlightPart) {
if (highlightId) {
_list.setAroundPosition({
.fullId = FullMsgId(_history->peer->id, highlightId),
@@ -328,6 +330,7 @@ RepliesWidget::RepliesWidget(
Controls::ShowReplyToChatBox(controller->uiShow(), { fullId });
} else {
replyToMessage(fullId);
+ _composeControls->focus();
}
}, _inner->lifetime());
@@ -490,6 +493,7 @@ void RepliesWidget::setupTopicViewer() {
) | rpl::start_with_next([=](const Data::Session::IdChange &change) {
if (_rootId == change.oldId) {
_rootId = change.newId.msg;
+ _composeControls->updateTopicRootId(_rootId);
_sendAction = owner->sendActionManager().repliesPainter(
_history,
_rootId);
@@ -787,9 +791,11 @@ void RepliesWidget::setupComposeControls() {
sendInlineResult(chosen.result, chosen.bot, chosen.options, localId);
}, lifetime());
- _composeControls->scrollRequests(
- ) | rpl::start_with_next([=](Data::MessagePosition pos) {
- showAtPosition(pos);
+ _composeControls->jumpToItemRequests(
+ ) | rpl::start_with_next([=](FullReplyTo to) {
+ if (const auto item = session().data().message(to.messageId)) {
+ JumpToMessageClickHandler(item, {}, to.quote)->onClick({});
+ }
}, lifetime());
_composeControls->scrollKeyEvents(
@@ -1859,16 +1865,22 @@ void RepliesWidget::finishSending() {
refreshTopBarActiveChat();
}
+void RepliesWidget::showAtPosition(
+ Data::MessagePosition position,
+ FullMsgId originItemId) {
+ showAtPosition(position, originItemId, {});
+}
+
void RepliesWidget::showAtPosition(
Data::MessagePosition position,
FullMsgId originItemId,
- anim::type animated) {
+ const Window::SectionShow ¶ms) {
_lastShownAt = position.fullId;
controller()->setActiveChatEntry(activeChat());
const auto ignore = (position.fullId.msg == _rootId);
_inner->showAtPosition(
position,
- animated,
+ params,
_cornerButtons.doneJumpFrom(position.fullId, originItemId, ignore));
}
@@ -1960,7 +1972,7 @@ bool RepliesWidget::showInternal(
if (logMemento->getHistory() == history()
&& logMemento->getRootId() == _rootId) {
restoreState(logMemento);
- if (!logMemento->getHighlightId()) {
+ if (!logMemento->highlightId()) {
showAtPosition(Data::UnreadMessagePosition);
}
if (params.reapplyLocalDraft) {
@@ -2008,7 +2020,7 @@ bool RepliesWidget::showMessage(
}
const auto id = FullMsgId(_history->peer->id, messageId);
const auto message = _history->owner().message(id);
- if (!message) {
+ if (!message || !message->inThread(_rootId)) {
return false;
}
const auto originMessage = [&]() -> HistoryItem* {
@@ -2024,13 +2036,13 @@ bool RepliesWidget::showMessage(
}
return nullptr;
}();
- if (!originMessage) {
- return false;
- }
- const auto originItemId = (_cornerButtons.replyReturn() != originMessage)
+ const auto currentReplyReturn = _cornerButtons.replyReturn();
+ const auto originItemId = !originMessage
+ ? FullMsgId()
+ : (currentReplyReturn != originMessage)
? originMessage->fullId()
: FullMsgId();
- showAtPosition(message->position(), originItemId);
+ showAtPosition(message->position(), originItemId, params);
return true;
}
@@ -2132,11 +2144,15 @@ void RepliesWidget::restoreState(not_null memento) {
}
_cornerButtons.setReplyReturns(memento->replyReturns());
_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{
.fullId = FullMsgId(_history->peer->id, highlight),
.date = TimeId(0),
- }, {}, anim::type::instant);
+ }, {}, params);
}
}
diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h
index 14104e913..e8d3e6139 100644
--- a/Telegram/SourceFiles/history/view/history_view_replies_section.h
+++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h
@@ -208,8 +208,11 @@ private:
void showAtEnd();
void showAtPosition(
Data::MessagePosition position,
- FullMsgId originItemId = {},
- anim::type animated = anim::type::normal);
+ FullMsgId originItemId = {});
+ void showAtPosition(
+ Data::MessagePosition position,
+ FullMsgId originItemId,
+ const Window::SectionShow ¶ms);
void finishSending();
void setupComposeControls();
@@ -377,7 +380,8 @@ public:
RepliesMemento(
not_null history,
MsgId rootId,
- MsgId highlightId = 0);
+ MsgId highlightId = 0,
+ const TextWithEntities &highlightPart = {});
explicit RepliesMemento(
not_null commentsItem,
MsgId commentId = 0);
@@ -421,9 +425,12 @@ public:
[[nodiscard]] not_null list() {
return &_list;
}
- [[nodiscard]] MsgId getHighlightId() const {
+ [[nodiscard]] MsgId highlightId() const {
return _highlightId;
}
+ [[nodiscard]] const TextWithEntities &highlightPart() const {
+ return _highlightPart;
+ }
private:
void setupTopicViewer();
@@ -431,6 +438,7 @@ private:
const not_null _history;
MsgId _rootId = 0;
const MsgId _highlightId = 0;
+ const TextWithEntities _highlightPart;
ListMemento _list;
std::shared_ptr _replies;
QVector _replyReturns;
diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
index 1ee20e414..3f9314c40 100644
--- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
@@ -274,9 +274,15 @@ void ScheduledWidget::setupComposeControls() {
sendInlineResult(chosen.result, chosen.bot);
}, lifetime());
- _composeControls->scrollRequests(
- ) | rpl::start_with_next([=](Data::MessagePosition pos) {
- showAtPosition(pos);
+ _composeControls->jumpToItemRequests(
+ ) | rpl::start_with_next([=](FullReplyTo to) {
+ 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());
_composeControls->scrollKeyEvents(
@@ -858,7 +864,7 @@ void ScheduledWidget::showAtPosition(
FullMsgId originId) {
_inner->showAtPosition(
position,
- anim::type::normal,
+ {},
_cornerButtons.doneJumpFrom(position.fullId, originId));
}
diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp
index a3d6550c5..0705ff2a5 100644
--- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp
@@ -669,23 +669,12 @@ TextForMimeData Service::selectedText(TextSelection selection) const {
return text().toTextForMimeData(selection);
}
-TextWithEntities Service::selectedQuote(TextSelection selection) const {
- return {};
-}
-
-TextWithEntities Service::selectedQuote(
- const Ui::Text::String &text,
- TextSelection selection) const {
+SelectedQuote Service::selectedQuote(TextSelection selection) const {
return {};
}
TextSelection Service::selectionFromQuote(
- const TextWithEntities "e) const {
- return {};
-}
-
-TextSelection Service::selectionFromQuote(
- const Ui::Text::String &text,
+ not_null item,
const TextWithEntities "e) const {
return {};
}
diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.h b/Telegram/SourceFiles/history/view/history_view_service_message.h
index c862ce657..617dd1adb 100644
--- a/Telegram/SourceFiles/history/view/history_view_service_message.h
+++ b/Telegram/SourceFiles/history/view/history_view_service_message.h
@@ -43,14 +43,9 @@ public:
StateRequest request) const override;
void updatePressed(QPoint point) override;
TextForMimeData selectedText(TextSelection selection) const override;
- TextWithEntities selectedQuote(TextSelection selection) const override;
- TextWithEntities selectedQuote(
- const Ui::Text::String &text,
- TextSelection selection) const override;
+ SelectedQuote selectedQuote(TextSelection selection) const override;
TextSelection selectionFromQuote(
- const TextWithEntities "e) const override;
- TextSelection selectionFromQuote(
- const Ui::Text::String &text,
+ not_null item,
const TextWithEntities "e) const override;
TextSelection adjustSelection(
TextSelection selection,
diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
index 5e45b8877..06825b37d 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp
@@ -744,6 +744,7 @@ void Document::draw(
if (const auto captioned = Get()) {
p.setPen(stm->historyTextFg);
_parent->prepareCustomEmojiPaint(p, context, captioned->caption);
+ auto highlightRequest = context.computeHighlightCache();
captioned->caption.draw(p, {
.position = { st::msgPadding.left(), captiontop },
.availableWidth = captionw,
@@ -756,6 +757,7 @@ void Document::draw(
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = selection,
+ .highlight = highlightRequest ? &*highlightRequest : nullptr,
});
}
}
@@ -1210,7 +1212,7 @@ TextForMimeData Document::selectedText(TextSelection selection) const {
return result;
}
-TextWithEntities Document::selectedQuote(TextSelection selection) const {
+SelectedQuote Document::selectedQuote(TextSelection selection) const {
if (const auto voice = Get()) {
const auto length = voice->transcribeText.length();
if (selection.from < length) {
@@ -1221,16 +1223,21 @@ TextWithEntities Document::selectedQuote(TextSelection selection) const {
voice->transcribeText);
}
if (const auto captioned = Get()) {
- return parent()->selectedQuote(captioned->caption, selection);
+ return Element::FindSelectedQuote(
+ captioned->caption,
+ selection,
+ _realParent);
}
return {};
}
TextSelection Document::selectionFromQuote(
+ not_null item,
const TextWithEntities "e) const {
if (const auto captioned = Get()) {
- const auto result = parent()->selectionFromQuote(
+ const auto result = Element::FindSelectionFromQuote(
captioned->caption,
+ item,
quote);
if (result.empty()) {
return {};
@@ -1390,6 +1397,8 @@ void Document::drawGrouped(
float64 highlightOpacity,
not_null cacheKey,
not_null cache) const {
+ const auto maybeMediaHighlight = context.highlightPathCache
+ && context.highlightPathCache->isEmpty();
p.translate(geometry.topLeft());
draw(
p,
@@ -1397,6 +1406,10 @@ void Document::drawGrouped(
geometry.width(),
LayoutMode::Grouped,
rounding);
+ if (maybeMediaHighlight
+ && !context.highlightPathCache->isEmpty()) {
+ context.highlightPathCache->translate(geometry.topLeft());
+ }
p.translate(-geometry.topLeft());
}
diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.h b/Telegram/SourceFiles/history/view/media/history_view_document.h
index 31dd9886a..ec2dc0d78 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_document.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_document.h
@@ -46,8 +46,9 @@ public:
bool hasTextForCopy() const override;
TextForMimeData selectedText(TextSelection selection) const override;
- TextWithEntities selectedQuote(TextSelection selection) const override;
+ SelectedQuote selectedQuote(TextSelection selection) const override;
TextSelection selectionFromQuote(
+ not_null item,
const TextWithEntities "e) const override;
bool uploading() const override;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp
index 6c259748d..a82fd13fd 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp
@@ -229,6 +229,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const {
if (!_caption.isEmpty()) {
p.setPen(stm->historyTextFg);
_parent->prepareCustomEmojiPaint(p, context, _caption);
+ auto highlightRequest = context.computeHighlightCache();
_caption.draw(p, {
.position = QPoint(
st::msgPadding.left(),
@@ -243,6 +244,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const {
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = context.selection,
+ .highlight = highlightRequest ? &*highlightRequest : nullptr,
});
} else if (!inWebPage) {
auto fullRight = paintx + paintw;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.cpp b/Telegram/SourceFiles/history/view/media/history_view_game.cpp
index a91f8350d..24ba5187c 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_game.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_game.cpp
@@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text/text_utilities.h"
#include "ui/cached_round_corners.h"
#include "ui/chat/chat_style.h"
+#include "ui/effects/ripple_animation.h"
#include "ui/painter.h"
#include "ui/power_saving.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::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();
if (_titleLines) {
p.setPen(cache->icon);
@@ -322,7 +330,6 @@ TextState Game::textState(QPoint point, StateRequest request) const {
auto tshift = inner.top();
auto paintw = inner.width();
- auto inThumb = false;
auto symbolAdd = 0;
auto lineHeight = UnitedLineHeight();
if (_titleLines) {
@@ -353,11 +360,7 @@ TextState Game::textState(QPoint point, StateRequest request) const {
}
tshift += _descriptionLines * lineHeight;
}
- if (inThumb) {
- if (_parent->data()->isHistoryEntry()) {
- result.link = _openl;
- }
- } else if (_attach) {
+ if (_attach) {
auto attachAtTop = !_titleLines && !_descriptionLines;
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;
return result;
@@ -399,11 +408,41 @@ void Game::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
}
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(
+ st::defaultRippleAnimation,
+ Ui::RippleAnimation::RoundRectMask(
+ outer.size(),
+ _st.radius),
+ [=] { owner->requestViewRepaint(parent()); });
+ }
+ _ripple->add(_lastPoint);
+ } else if (_ripple) {
+ _ripple->lastStop();
+ }
+ }
if (_attach) {
_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 {
auto titleResult = _title.toTextForMimeData(selection);
auto descriptionResult = _description.toTextForMimeData(
diff --git a/Telegram/SourceFiles/history/view/media/history_view_game.h b/Telegram/SourceFiles/history/view/media/history_view_game.h
index 9cec01fdc..b7618bf69 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_game.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_game.h
@@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class ReplyMarkupClickHandler;
+namespace Ui {
+class RippleAnimation;
+} // namespace Ui
+
namespace HistoryView {
class Game : public Media {
@@ -35,12 +39,11 @@ public:
return false; // we do not add _title and _description in FullSelection text copy.
}
- bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
- return _attach && _attach->toggleSelectionByHandlerClick(p);
- }
- bool dragItemByHandler(const ClickHandlerPtr &p) const override {
- return _attach && _attach->dragItemByHandler(p);
- }
+ bool toggleSelectionByHandlerClick(
+ const ClickHandlerPtr &p) const override;
+ bool allowTextSelectionByHandler(
+ const ClickHandlerPtr &p) const override;
+ bool dragItemByHandler(const ClickHandlerPtr &p) const override;
TextForMimeData selectedText(TextSelection selection) const override;
@@ -102,7 +105,9 @@ private:
const not_null _data;
std::shared_ptr _openl;
std::unique_ptr _attach;
+ mutable std::unique_ptr _ripple;
+ mutable QPoint _lastPoint;
int _gameTagWidth = 0;
int _descriptionLines = 0;
int _titleLines = 0;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
index 70a81264d..340b1fa66 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp
@@ -716,6 +716,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
_parent->width());
top += botTop->height;
}
+ auto highlightRequest = context.computeHighlightCache();
_caption.draw(p, {
.position = QPoint(st::msgPadding.left(), top),
.availableWidth = captionw,
@@ -728,6 +729,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = context.selection,
+ .highlight = highlightRequest ? &*highlightRequest : nullptr,
});
} else if (!inWebPage && !skipDrawingSurrounding) {
auto fullRight = paintx + usex + usew;
@@ -1203,13 +1205,14 @@ TextForMimeData Gif::selectedText(TextSelection selection) const {
return _caption.toTextForMimeData(selection);
}
-TextWithEntities Gif::selectedQuote(TextSelection selection) const {
- return parent()->selectedQuote(_caption, selection);
+SelectedQuote Gif::selectedQuote(TextSelection selection) const {
+ return Element::FindSelectedQuote(_caption, selection, _realParent);
}
TextSelection Gif::selectionFromQuote(
+ not_null item,
const TextWithEntities "e) const {
- return parent()->selectionFromQuote(_caption, quote);
+ return Element::FindSelectionFromQuote(_caption, item, quote);
}
bool Gif::fullFeaturedGrouped(RectParts sides) const {
diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h
index fdba9a0af..eac33cc7a 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_gif.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h
@@ -68,8 +68,9 @@ public:
}
TextForMimeData selectedText(TextSelection selection) const override;
- TextWithEntities selectedQuote(TextSelection selection) const override;
+ SelectedQuote selectedQuote(TextSelection selection) const override;
TextSelection selectionFromQuote(
+ not_null item,
const TextWithEntities "e) const override;
bool uploading() const override;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp
index 4e19fa291..8f9923c36 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp
@@ -181,6 +181,11 @@ Storage::SharedMediaTypesMask Media::sharedMediaTypes() const {
return {};
}
+bool Media::allowTextSelectionByHandler(
+ const ClickHandlerPtr &handler) const {
+ return false;
+}
+
not_null Media::parent() const {
return _parent;
}
@@ -189,6 +194,10 @@ not_null Media::history() const {
return _parent->history();
}
+SelectedQuote Media::selectedQuote(TextSelection selection) const {
+ return {};
+}
+
bool Media::isDisplayed() const {
return true;
}
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h
index bda57224e..7dadb5f87 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_media.h
@@ -49,6 +49,7 @@ struct StateRequest;
struct MediaSpoiler;
class StickerPlayer;
class Element;
+struct SelectedQuote;
using PaintContext = Ui::ChatPaintContext;
@@ -88,11 +89,10 @@ public:
TextSelection selection) const {
return {};
}
- [[nodiscard]] virtual TextWithEntities selectedQuote(
- TextSelection selection) const {
- return {};
- }
+ [[nodiscard]] virtual SelectedQuote selectedQuote(
+ TextSelection selection) const;
[[nodiscard]] virtual TextSelection selectionFromQuote(
+ not_null item,
const TextWithEntities "e) const {
return {};
}
@@ -136,6 +136,8 @@ public:
// toggle selection instead of activating the pressed link
[[nodiscard]] virtual bool toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const = 0;
+ [[nodiscard]] virtual bool allowTextSelectionByHandler(
+ const ClickHandlerPtr &p) const;
[[nodiscard]] virtual TextSelection adjustSelection(
TextSelection selection,
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp
index 79cbcf869..bea617b23 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp
@@ -286,19 +286,45 @@ void GroupedMedia::drawHighlight(
Painter &p,
const PaintContext &context,
int top) const {
- if (_mode != Mode::Column) {
+ if (context.highlight.opacity == 0.) {
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();
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
const auto &part = _parts[i];
const auto rect = part.geometry.translated(0, skip);
- _parent->paintCustomHighlight(
- p,
- context,
- rect.y(),
- rect.height(),
- part.item);
+ const auto full = (!i && empty)
+ || (subpart && IsGroupItemSelection(selection, i))
+ || (!subpart
+ && !selection.empty()
+ && (selection.from < part.content->fullSelectionLength()));
+ 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
? Ui::BubbleRounding{ kSmall, kSmall, kSmall, kSmall }
: adjustedBubbleRoundingWithCaption(_caption);
+ auto highlight = context.highlight.range;
+ const auto subpartHighlight = IsSubGroupSelection(highlight);
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
const auto &part = _parts[i];
- const auto partContext = context.withSelection(fullSelection
+ auto partContext = context.withSelection(fullSelection
? FullSelection
: textSelection
? selection
: IsGroupItemSelection(selection, i)
? FullSelection
: 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) {
selection = part.content->skipSelection(selection);
}
- const auto highlightOpacity = (_mode == Mode::Grid)
- ? _parent->delegate()->elementHighlightOpacity(part.item)
- : 0.;
+ if (!subpartHighlight) {
+ highlight = part.content->skipSelection(highlight);
+ }
if (!part.cache.isNull()) {
wasCache = true;
}
@@ -361,6 +397,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
const auto stm = context.messageStyle();
p.setPen(stm->historyTextFg);
_parent->prepareCustomEmojiPaint(p, context, _caption);
+ auto highlightRequest = context.computeHighlightCache();
_caption.draw(p, {
.position = QPoint(
st::msgPadding.left(),
@@ -375,6 +412,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const {
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = context.selection,
+ .highlight = highlightRequest ? &*highlightRequest : nullptr,
});
} else if (_parent->media() == this) {
auto fullRight = width();
@@ -514,6 +552,7 @@ TextSelection GroupedMedia::adjustSelection(
selection.to = modified.to;
return selection;
}
+ checked = till;
}
return selection;
}
@@ -561,6 +600,50 @@ TextForMimeData GroupedMedia::selectedText(
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 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(
TextSelection selection) const
-> std::vector {
@@ -663,16 +746,15 @@ bool GroupedMedia::validateGroupParts(
}
void GroupedMedia::refreshCaption() {
- using PartPtrOpt = std::optional;
- const auto captionPart = [&]() -> PartPtrOpt {
+ const auto part = [&]() -> const Part* {
if (_mode == Mode::Column) {
- return std::nullopt;
+ return nullptr;
}
- auto result = PartPtrOpt();
+ auto result = (const Part*)nullptr;
for (const auto &part : _parts) {
if (!part.item->emptyText()) {
if (result) {
- return std::nullopt;
+ return nullptr;
} else {
result = ∂
}
@@ -680,8 +762,7 @@ void GroupedMedia::refreshCaption() {
}
return result;
}();
- if (captionPart) {
- const auto &part = (*captionPart);
+ if (part) {
_caption = createCaption(part->item);
_captionItem = part->item;
} else {
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h
index c66864063..b6fbc77d9 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h
@@ -55,6 +55,10 @@ public:
DocumentData *getDocument() const override;
TextForMimeData selectedText(TextSelection selection) const override;
+ SelectedQuote selectedQuote(TextSelection selection) const override;
+ TextSelection selectionFromQuote(
+ not_null item,
+ const TextWithEntities "e) const override;
std::vector getBubbleSelectionIntervals(
TextSelection selection) const override;
diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp
index 413046ed3..69f4875e1 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_media_unwrapped.cpp
@@ -104,6 +104,9 @@ QSize UnwrappedMedia::countCurrentSize(int newWidth) {
const auto surroundingWidth = _additionalOnTop
? std::min(newWidth - st::msgReplyPadding.left(), additional)
: (newWidth - _contentSize.width() - st::msgReplyPadding.left());
+ if (reply) {
+ [[maybe_unused]] auto h = reply->resizeToWidth(surroundingWidth);
+ }
const auto surrounding = surroundingInfo(topic, via, reply, forwarded, surroundingWidth);
if (_additionalOnTop) {
_topAdded = surrounding.height + st::msgMargin.bottom();
diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
index 81dee1c54..de72e7786 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp
@@ -401,6 +401,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
_parent->width());
top += botTop->height;
}
+ auto highlightRequest = context.computeHighlightCache();
_caption.draw(p, {
.position = QPoint(st::msgPadding.left(), top),
.availableWidth = captionw,
@@ -413,6 +414,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const {
.pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
.selection = context.selection,
+ .highlight = highlightRequest ? &*highlightRequest : nullptr,
});
} else if (!inWebPage) {
auto fullRight = paintx + paintw;
@@ -1049,13 +1051,14 @@ TextForMimeData Photo::selectedText(TextSelection selection) const {
return _caption.toTextForMimeData(selection);
}
-TextWithEntities Photo::selectedQuote(TextSelection selection) const {
- return parent()->selectedQuote(_caption, selection);
+SelectedQuote Photo::selectedQuote(TextSelection selection) const {
+ return Element::FindSelectedQuote(_caption, selection, _realParent);
}
TextSelection Photo::selectionFromQuote(
+ not_null item,
const TextWithEntities "e) const {
- return parent()->selectionFromQuote(_caption, quote);
+ return Element::FindSelectionFromQuote(_caption, item, quote);
}
void Photo::hideSpoilers() {
diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h
index b76bbe264..7213dca21 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_photo.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h
@@ -57,8 +57,9 @@ public:
}
TextForMimeData selectedText(TextSelection selection) const override;
- TextWithEntities selectedQuote(TextSelection selection) const override;
+ SelectedQuote selectedQuote(TextSelection selection) const override;
TextSelection selectionFromQuote(
+ not_null item,
const TextWithEntities "e) const override;
PhotoData *getPhoto() const override {
diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp
index 38b6dc258..a6bb07528 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp
+++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp
@@ -155,7 +155,7 @@ bool WebPage::HasButton(not_null webpage) {
}
QSize WebPage::countOptimalSize() {
- if (_data->pendingTill) {
+ if (_data->pendingTill || _data->failed) {
return { 0, 0 };
}
@@ -366,7 +366,7 @@ QSize WebPage::countOptimalSize() {
}
QSize WebPage::countCurrentSize(int newWidth) {
- if (_data->pendingTill) {
+ if (_data->pendingTill || _data->failed) {
return { newWidth, minHeight() };
}
@@ -891,6 +891,7 @@ void WebPage::playAnimation(bool autoplay) {
bool WebPage::isDisplayed() const {
const auto item = _parent->data();
return !_data->pendingTill
+ && !_data->failed
&& !item->Has();
}
@@ -903,6 +904,11 @@ bool WebPage::toggleSelectionByHandlerClick(
return _attach && _attach->toggleSelectionByHandlerClick(p);
}
+bool WebPage::allowTextSelectionByHandler(
+ const ClickHandlerPtr &p) const {
+ return (p == _openl);
+}
+
bool WebPage::dragItemByHandler(const ClickHandlerPtr &p) const {
return _attach && _attach->dragItemByHandler(p);
}
diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.h b/Telegram/SourceFiles/history/view/media/history_view_web_page.h
index 0946714ff..bf9254e65 100644
--- a/Telegram/SourceFiles/history/view/media/history_view_web_page.h
+++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.h
@@ -50,6 +50,8 @@ public:
bool toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const override;
+ bool allowTextSelectionByHandler(
+ const ClickHandlerPtr &p) const override;
bool dragItemByHandler(const ClickHandlerPtr &p) const override;
TextForMimeData selectedText(TextSelection selection) const override;
diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
index 2822879fd..6bfc8c099 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "info/profile/info_profile_actions.h"
+#include "base/options.h"
#include "data/data_peer_values.h"
#include "data/data_session.h"
#include "data/data_folder.h"
@@ -77,6 +78,12 @@ namespace Info {
namespace Profile {
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 UsernamesSubtext(
not_null peer,
rpl::producer fallback) {
@@ -148,6 +155,27 @@ namespace {
return result;
}
+[[nodiscard]] rpl::producer AboutWithIdValue(
+ not_null 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
auto AddActionButton(
not_null parent,
@@ -425,8 +453,8 @@ object_ptr DetailsFiller::setupInfo() {
? tr::lng_info_about_label()
: tr::lng_info_bio_label();
addTranslateToMenu(
- addInfoLine(std::move(label), AboutValue(user)).text,
- AboutValue(user));
+ addInfoLine(std::move(label), AboutWithIdValue(user)).text,
+ AboutWithIdValue(user));
const auto usernameLine = addInfoOneLine(
UsernamesSubtext(_peer, tr::lng_info_username_label()),
@@ -576,11 +604,11 @@ object_ptr DetailsFiller::setupInfo() {
).text->setLinksTrusted();
}
- const auto about = addInfoLine(
- tr::lng_info_about_label(),
- _topic ? rpl::single(TextWithEntities()) : AboutValue(_peer));
+ const auto about = addInfoLine(tr::lng_info_about_label(), _topic
+ ? rpl::single(TextWithEntities())
+ : AboutWithIdValue(_peer));
if (!_topic) {
- addTranslateToMenu(about.text, AboutValue(_peer));
+ addTranslateToMenu(about.text, AboutWithIdValue(_peer));
}
if (settings->showPeerId != 0 && !_topic)
@@ -1129,6 +1157,8 @@ object_ptr ActionsFiller::fill() {
} // namespace
+const char kOptionShowPeerIdBelowAbout[] = "show-peer-id-below-about";
+
object_ptr SetupDetails(
not_null controller,
not_null