mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Merge tag 'v4.11.3' into dev
# Conflicts: # Telegram/Resources/winrc/Telegram.rc # Telegram/Resources/winrc/Updater.rc # Telegram/SourceFiles/core/version.h # Telegram/SourceFiles/info/profile/info_profile_values.cpp # Telegram/lib_ui # snap/snapcraft.yaml
This commit is contained in:
commit
ac1da83401
96 changed files with 1398 additions and 692 deletions
|
@ -546,6 +546,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_settings_workmode_tray" = "Show tray icon";
|
||||
"lng_settings_workmode_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";
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="4.11.1.0" />
|
||||
Version="4.11.3.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -281,7 +281,7 @@ struct GiftCodeLink {
|
|||
|
||||
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
PeerId id) {
|
||||
auto result = object_ptr<Ui::AbstractButton>(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<Ui::FlatLabel*> AddTableRow(
|
|||
void AddTableRow(
|
||||
not_null<Ui::TableLayout*> table,
|
||||
rpl::producer<QString> label,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
PeerId id) {
|
||||
if (!id) {
|
||||
return;
|
||||
|
@ -416,7 +416,7 @@ QString GiftDuration(int months) {
|
|||
|
||||
void GiftCodeBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug) {
|
||||
struct State {
|
||||
rpl::variable<Api::GiftCode> data;
|
||||
|
@ -552,7 +552,7 @@ void GiftCodeBox(
|
|||
st::giveawayGiftCodeFooterMargin);
|
||||
footer->setClickHandlerFilter([=](const auto &...) {
|
||||
const auto chosen = [=](not_null<Data::Thread*> 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<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> 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<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
Data::Giveaway giveaway,
|
||||
Api::GiveawayInfo info) {
|
||||
using State = Api::GiveawayState;
|
||||
|
@ -784,7 +784,7 @@ void GiveawayInfoBox(
|
|||
}
|
||||
|
||||
void ResolveGiveawayInfo(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<PeerData*> 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));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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<Ui::GenericBox*> box,
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug);
|
||||
void ResolveGiftCode(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
const QString &slug);
|
||||
|
||||
void ResolveGiveawayInfo(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<Window::SessionNavigation*> controller,
|
||||
not_null<PeerData*> peer,
|
||||
MsgId messageId,
|
||||
Data::Giveaway giveaway);
|
||||
|
|
|
@ -760,7 +760,7 @@ int PeerListRow::paintNameIconGetWidth(
|
|||
nameWidth,
|
||||
outerWidth,
|
||||
{
|
||||
.peer = _peer,
|
||||
.peer = peer(),
|
||||
.verified = &(selected
|
||||
? st::dialogsVerifiedIconOver
|
||||
: st::dialogsVerifiedIcon),
|
||||
|
|
|
@ -712,13 +712,24 @@ void Instance::destroyCurrentCall() {
|
|||
}
|
||||
}
|
||||
|
||||
bool Instance::hasActivePanel(not_null<Main::Session*> session) const {
|
||||
bool Instance::hasVisiblePanel(Main::Session *session) const {
|
||||
if (inCall()) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -89,8 +89,10 @@ public:
|
|||
[[nodiscard]] rpl::producer<GroupCall*> currentGroupCallValue() const;
|
||||
[[nodiscard]] bool inCall() const;
|
||||
[[nodiscard]] bool inGroupCall() const;
|
||||
[[nodiscard]] bool hasVisiblePanel(
|
||||
Main::Session *session = nullptr) const;
|
||||
[[nodiscard]] bool hasActivePanel(
|
||||
not_null<Main::Session*> session) const;
|
||||
Main::Session *session = nullptr) const;
|
||||
bool activateCurrentCall(const QString &joinHash = QString());
|
||||
bool minimizeCurrentActiveCall();
|
||||
bool toggleFullScreenCurrentActiveCall();
|
||||
|
|
|
@ -106,12 +106,15 @@ Panel::Panel(not_null<Call*> 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();
|
||||
|
|
|
@ -61,6 +61,7 @@ public:
|
|||
Panel(not_null<Call*> call);
|
||||
~Panel();
|
||||
|
||||
[[nodiscard]] bool isVisible() const;
|
||||
[[nodiscard]] bool isActive() const;
|
||||
void showAndActivate();
|
||||
void minimize();
|
||||
|
|
|
@ -258,12 +258,15 @@ not_null<GroupCall*> 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<Ui::Toast::Instance> Panel::showToast(
|
||||
const QString &text,
|
||||
crl::time duration) {
|
||||
|
|
|
@ -91,6 +91,7 @@ public:
|
|||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
|
||||
[[nodiscard]] not_null<GroupCall*> call() const;
|
||||
[[nodiscard]] bool isVisible() const;
|
||||
[[nodiscard]] bool isActive() const;
|
||||
|
||||
base::weak_ptr<Ui::Toast::Instance> showToast(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<QString> 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<uint64>();
|
||||
_storiesClickTooltipHidden = (storiesClickTooltipHidden == 1);
|
||||
_recentEmojiSkip = std::move(recentEmojiSkip);
|
||||
_trayIconMonochrome = (trayIconMonochrome == 1);
|
||||
}
|
||||
|
||||
QString Settings::getSoundPath(const QString &key) const {
|
||||
|
|
|
@ -708,6 +708,15 @@ public:
|
|||
[[nodiscard]] rpl::producer<bool> closeToTaskbarChanges() const {
|
||||
return _closeToTaskbar.changes();
|
||||
}
|
||||
void setTrayIconMonochrome(bool value) {
|
||||
_trayIconMonochrome = value;
|
||||
}
|
||||
[[nodiscard]] bool trayIconMonochrome() const {
|
||||
return _trayIconMonochrome.current();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<bool> trayIconMonochromeChanges() const {
|
||||
return _trayIconMonochrome.changes();
|
||||
}
|
||||
|
||||
void setCustomDeviceModel(const QString &model) {
|
||||
_customDeviceModel = model;
|
||||
|
@ -924,6 +933,7 @@ private:
|
|||
rpl::variable<WorkMode> _workMode = WorkMode::WindowAndTray;
|
||||
base::flags<Calls::Group::StickedTooltip> _hiddenGroupCallTooltips;
|
||||
rpl::variable<bool> _closeToTaskbar = false;
|
||||
rpl::variable<bool> _trayIconMonochrome = true;
|
||||
rpl::variable<QString> _customDeviceModel;
|
||||
rpl::variable<Media::RepeatMode> _playerRepeatMode;
|
||||
rpl::variable<Media::OrderMode> _playerOrderMode;
|
||||
|
|
|
@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
|
|||
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
|
||||
constexpr auto 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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -579,11 +579,6 @@ bool InnerWidget::elementUnderCursor(
|
|||
return (Element::Hovered() == view);
|
||||
}
|
||||
|
||||
float64 InnerWidget::elementHighlightOpacity(
|
||||
not_null<const HistoryItem*> item) const {
|
||||
return 0.;
|
||||
}
|
||||
|
||||
bool InnerWidget::elementInSelectionMode() {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -93,8 +93,6 @@ public:
|
|||
HistoryView::Context elementContext() override;
|
||||
bool elementUnderCursor(
|
||||
not_null<const HistoryView::Element*> view) override;
|
||||
[[nodiscard]] float64 elementHighlightOpacity(
|
||||
not_null<const HistoryItem*> item) const override;
|
||||
bool elementInSelectionMode() override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const HistoryView::Element*> view,
|
||||
|
|
|
@ -207,10 +207,6 @@ public:
|
|||
not_null<const Element*> view) override {
|
||||
return (Element::Moused() == view);
|
||||
}
|
||||
[[nodiscard]] float64 elementHighlightOpacity(
|
||||
not_null<const HistoryItem*> 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<Element*> view) const {
|
|||
item->toggleReaction(favorite, HistoryItem::ReactionSource::Quick);
|
||||
}
|
||||
|
||||
TextWithEntities HistoryInner::selectedQuote(
|
||||
HistoryView::SelectedQuote HistoryInner::selectedQuote(
|
||||
not_null<HistoryItem*> 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<const HistoryItem*> item) const {
|
||||
return _widget->highlightOpacity(item);
|
||||
}
|
||||
|
||||
void HistoryInner::elementShowPollResults(
|
||||
not_null<PollData*> 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);
|
||||
|
|
|
@ -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 <QtGui/QPainterPath>
|
||||
|
||||
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<const Element*> view);
|
||||
[[nodiscard]] float64 elementHighlightOpacity(
|
||||
not_null<const HistoryItem*> item) const;
|
||||
void elementShowPollResults(
|
||||
not_null<PollData*> 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<HistoryItem*> item) const;
|
||||
|
||||
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
|
||||
|
@ -462,6 +463,7 @@ private:
|
|||
std::optional<Ui::ReportReason> _chooseForReportReason;
|
||||
|
||||
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||
QPainterPath _highlightPathCache;
|
||||
bool _isChatWide = false;
|
||||
|
||||
base::flat_set<not_null<const HistoryItem*>> _animatedStickersPlayed;
|
||||
|
|
|
@ -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<HistoryMessageReply>()) {
|
||||
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<HistoryServiceTopicInfo>())
|
||||
|| (forum && forum->creating(config.reply.topMessageId));
|
||||
config.reply.topicPost = config.reply.externalPeerId
|
||||
? (replyTo.topicRootId
|
||||
&& (replyTo.topicRootId != Data::ForumTopic::kGeneralId))
|
||||
: (LookupReplyIsTopicPost(to)
|
||||
|| (to && to->Has<HistoryServiceTopicInfo>())
|
||||
|| (forum && forum->creating(config.reply.topMessageId)));
|
||||
config.reply.manualQuote = !replyTo.quote.empty();
|
||||
config.reply.quote = std::move(replyTo.quote);
|
||||
}
|
||||
config.markup = std::move(markup);
|
||||
|
|
|
@ -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<HistoryItem*> 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<HistoryItem*> 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()
|
||||
|
|
|
@ -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<HistoryItem*> holder);
|
||||
|
||||
[[nodiscard]] bool external() const;
|
||||
[[nodiscard]] PeerData *sender(not_null<HistoryItem*> holder) const;
|
||||
[[nodiscard]] QString senderName(not_null<HistoryItem*> holder) const;
|
||||
[[nodiscard]] QString senderName(not_null<PeerData*> peer) const;
|
||||
|
@ -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<HistoryItem*> holder);
|
||||
|
|
|
@ -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*> history, HistoryItem *replyTo) {
|
|||
: 0;
|
||||
}
|
||||
|
||||
MsgId LookupReplyToTop(not_null<History*> 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<const HistoryItem*> item) {
|
|||
|
||||
ClickHandlerPtr JumpToMessageClickHandler(
|
||||
not_null<HistoryItem*> item,
|
||||
FullMsgId returnToId) {
|
||||
FullMsgId returnToId,
|
||||
TextWithEntities highlightPart) {
|
||||
return JumpToMessageClickHandler(
|
||||
item->history()->peer,
|
||||
item->id,
|
||||
returnToId);
|
||||
returnToId,
|
||||
std::move(highlightPart));
|
||||
}
|
||||
|
||||
ClickHandlerPtr JumpToMessageClickHandler(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
FullMsgId returnToId) {
|
||||
FullMsgId returnToId,
|
||||
TextWithEntities highlightPart) {
|
||||
return std::make_shared<LambdaClickHandler>([=] {
|
||||
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<MTPMessageEntity>()); // quote_entities
|
||||
MTP_int(replyToTop),
|
||||
MTP_string(replyTo.quote.text),
|
||||
quoteEntities);
|
||||
}
|
||||
return MTPMessageReplyHeader();
|
||||
}
|
||||
|
|
|
@ -89,6 +89,9 @@ void RequestDependentMessageStory(
|
|||
[[nodiscard]] MsgId LookupReplyToTop(
|
||||
not_null<History*> history,
|
||||
HistoryItem *replyTo);
|
||||
[[nodiscard]] MsgId LookupReplyToTop(
|
||||
not_null<History*> history,
|
||||
FullReplyTo replyTo);
|
||||
[[nodiscard]] bool LookupReplyIsTopicPost(HistoryItem *replyTo);
|
||||
|
||||
struct SendingErrorRequest {
|
||||
|
@ -120,10 +123,12 @@ struct SendingErrorRequest {
|
|||
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
|
||||
not_null<PeerData*> peer,
|
||||
MsgId msgId,
|
||||
FullMsgId returnToId = FullMsgId());
|
||||
FullMsgId returnToId = FullMsgId(),
|
||||
TextWithEntities highlightPart = {});
|
||||
[[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler(
|
||||
not_null<HistoryItem*> item,
|
||||
FullMsgId returnToId = FullMsgId());
|
||||
FullMsgId returnToId = FullMsgId(),
|
||||
TextWithEntities highlightPart = {});
|
||||
[[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler(
|
||||
not_null<Data::Story*> story);
|
||||
ClickHandlerPtr JumpToStoryClickHandler(
|
||||
|
|
|
@ -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::Session*> data,
|
||||
ViewForItem viewForItem,
|
||||
|
@ -26,63 +25,95 @@ ElementHighlighter::ElementHighlighter(
|
|||
, _animation(*this) {
|
||||
}
|
||||
|
||||
void ElementHighlighter::enqueue(not_null<Element*> view) {
|
||||
const auto item = view->data();
|
||||
const auto fullId = item->fullId();
|
||||
void ElementHighlighter::enqueue(
|
||||
not_null<Element*> 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<Element*> 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<const HistoryItem*> 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<const Element*> view,
|
||||
const TextWithEntities &part) {
|
||||
const auto item = view->data();
|
||||
const auto owner = &item->history()->owner();
|
||||
if (const auto group = owner->groups().find(item)) {
|
||||
const auto leader = group->items.front();
|
||||
const auto leaderId = leader->fullId();
|
||||
const auto i = ranges::find(group->items, item);
|
||||
if (i != end(group->items)) {
|
||||
const auto index = int(i - begin(group->items));
|
||||
if (part.empty()) {
|
||||
return { leaderId, AddGroupItemSelection({}, index) };
|
||||
} else if (const auto leaderView = _viewForItem(leader)) {
|
||||
return {
|
||||
leaderId,
|
||||
leaderView->selectionFromQuote(item, part),
|
||||
};
|
||||
}
|
||||
}
|
||||
return { leaderId };
|
||||
} else if (part.empty()) {
|
||||
return { item->fullId() };
|
||||
}
|
||||
return { item->fullId(), view->selectionFromQuote(item, part) };
|
||||
}
|
||||
|
||||
void ElementHighlighter::highlight(Highlight data) {
|
||||
if (const auto item = _data->message(data.itemId)) {
|
||||
if (const auto view = _viewForItem(item)) {
|
||||
if (_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
|
||||
|
|
|
@ -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<Element*> view);
|
||||
void highlight(FullMsgId itemId);
|
||||
void enqueue(not_null<Element*> view, const TextWithEntities &part);
|
||||
void highlight(not_null<Element*> view, const TextWithEntities &part);
|
||||
void clear();
|
||||
|
||||
[[nodiscard]] float64 progress(not_null<const HistoryItem*> item) const;
|
||||
[[nodiscard]] Ui::ChatPaintHighlight state(
|
||||
not_null<const HistoryItem*> item) const;
|
||||
[[nodiscard]] MsgId latestSingleHighlightedMsgId() const;
|
||||
|
||||
private:
|
||||
void checkNextHighlight();
|
||||
void repaintHighlightedItem(not_null<const Element*> 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<base::Timer> _timer;
|
||||
bool _withTextPart = false;
|
||||
bool _collapsing = false;
|
||||
bool _collapsed = false;
|
||||
bool _fadingOut = false;
|
||||
|
||||
};
|
||||
|
||||
struct Highlight {
|
||||
FullMsgId itemId;
|
||||
TextSelection part;
|
||||
|
||||
explicit operator bool() const {
|
||||
return itemId.operator bool();
|
||||
}
|
||||
friend inline bool operator==(Highlight, Highlight) = default;
|
||||
};
|
||||
|
||||
[[nodiscard]] Highlight computeHighlight(
|
||||
not_null<const Element*> view,
|
||||
const TextWithEntities &part);
|
||||
void highlight(Highlight data);
|
||||
void checkNextHighlight();
|
||||
void repaintHighlightedItem(not_null<const Element*> view);
|
||||
void updateMessage();
|
||||
|
||||
const not_null<Data::Session*> _data;
|
||||
const ViewForItem _viewForItem;
|
||||
const RepaintView _repaintView;
|
||||
|
||||
FullMsgId _highlightedMessageId;
|
||||
Highlight _highlighted;
|
||||
FullMsgId _lastHighlightedMessageId;
|
||||
std::deque<FullMsgId> _queue;
|
||||
std::deque<Highlight> _queue;
|
||||
|
||||
AnimationManager _animation;
|
||||
|
||||
|
|
|
@ -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<HistoryView::Element*> view) {
|
||||
_highlighter.enqueue(view);
|
||||
not_null<HistoryView::Element*> view,
|
||||
const TextWithEntities &part) {
|
||||
_highlighter.enqueue(view, part);
|
||||
}
|
||||
|
||||
float64 HistoryWidget::highlightOpacity(
|
||||
Ui::ChatPaintHighlight HistoryWidget::itemHighlight(
|
||||
not_null<const HistoryItem*> 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);
|
||||
}
|
||||
|
|
|
@ -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<HistoryView::Element*> view);
|
||||
[[nodiscard]] float64 highlightOpacity(
|
||||
void enqueueMessageHighlight(
|
||||
not_null<HistoryView::Element*> view,
|
||||
const TextWithEntities &part);
|
||||
[[nodiscard]] Ui::ChatPaintHighlight itemHighlight(
|
||||
not_null<const HistoryItem*> item) const;
|
||||
|
||||
MessageIdsList getSelectedItems() const;
|
||||
|
@ -218,7 +225,10 @@ public:
|
|||
void fastShowAtEnd(not_null<History*> 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<void(MessageIdsList)> callback);
|
||||
|
@ -540,6 +550,7 @@ private:
|
|||
|
||||
void setupPreview();
|
||||
void editDraftOptions();
|
||||
void jumpToReply(FullReplyTo to);
|
||||
|
||||
void messagesReceived(not_null<PeerData*> 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<Ui::PlainShadow> _topShadow;
|
||||
bool _inGrab = false;
|
||||
|
|
|
@ -112,6 +112,7 @@ public:
|
|||
std::shared_ptr<ChatHelpers::Show> 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<FullMsgId> editMsgIdValue() const;
|
||||
[[nodiscard]] rpl::producer<FullMsgId> scrollToItemRequests() const;
|
||||
[[nodiscard]] rpl::producer<FullReplyTo> 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<bool> _visibleChanged;
|
||||
rpl::event_stream<FullMsgId> _scrollToItemRequests;
|
||||
rpl::event_stream<FullReplyTo> _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<FullMsgId> FieldHeader::editMsgIdValue() const {
|
|||
return _editMsgId.value();
|
||||
}
|
||||
|
||||
rpl::producer<FullMsgId> FieldHeader::scrollToItemRequests() const {
|
||||
return _scrollToItemRequests.events();
|
||||
rpl::producer<FullReplyTo> 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<Data::MessagePosition> ComposeControls::scrollRequests() const {
|
||||
rpl::producer<FullReplyTo> 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 {
|
||||
|
|
|
@ -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<std::optional<bool>> attachRequests() const;
|
||||
[[nodiscard]] rpl::producer<FileChosen> fileChosen() const;
|
||||
[[nodiscard]] rpl::producer<PhotoChosen> photoChosen() const;
|
||||
[[nodiscard]] rpl::producer<Data::MessagePosition> scrollRequests() const;
|
||||
[[nodiscard]] rpl::producer<FullReplyTo> jumpToItemRequests() const;
|
||||
[[nodiscard]] rpl::producer<InlineChosen> inlineResultChosen() const;
|
||||
[[nodiscard]] rpl::producer<SendActionUpdate> sendActionUpdates() const;
|
||||
[[nodiscard]] rpl::producer<not_null<QEvent*>> viewportEvents() const;
|
||||
|
@ -357,7 +358,7 @@ private:
|
|||
|
||||
const std::unique_ptr<Ui::RpWidget> _wrap;
|
||||
const std::unique_ptr<Ui::RpWidget> _writeRestricted;
|
||||
rpl::event_stream<FullMsgId> _scrollToItemRequests;
|
||||
rpl::event_stream<FullReplyTo> _jumpToItemRequests;
|
||||
|
||||
std::optional<Ui::RoundRect> _backgroundRect;
|
||||
|
||||
|
|
|
@ -99,9 +99,8 @@ public:
|
|||
not_null<History*> history);
|
||||
~PreviewWrap();
|
||||
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> showQuoteSelector(
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithEntities "e);
|
||||
[[nodiscard]] rpl::producer<SelectedQuote> showQuoteSelector(
|
||||
const SelectedQuote "e);
|
||||
[[nodiscard]] rpl::producer<QString> showLinkSelector(
|
||||
const TextWithTags &message,
|
||||
Data::WebPageDraft webpage,
|
||||
|
@ -212,12 +211,14 @@ PreviewWrap::~PreviewWrap() {
|
|||
}
|
||||
}
|
||||
|
||||
rpl::producer<TextWithEntities> PreviewWrap::showQuoteSelector(
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithEntities "e) {
|
||||
rpl::producer<SelectedQuote> 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<TextWithEntities> 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<Section> shown;
|
||||
rpl::lifetime shownLifetime;
|
||||
rpl::variable<TextWithEntities> quote;
|
||||
rpl::variable<SelectedQuote> 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>();
|
||||
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<const HistoryItem*> removed) {
|
||||
return removed == replyItem;
|
||||
const auto current = state->quote.current().item;
|
||||
if ((removed == replyItem) || (removed == current)) {
|
||||
return true;
|
||||
}
|
||||
const auto group = current->history()->owner().groups().find(
|
||||
current);
|
||||
return (group && ranges::contains(group->items, removed));
|
||||
}) | rpl::start_with_next([=] {
|
||||
if (previewData) {
|
||||
state->tabs = nullptr;
|
||||
box->setPinnedToTopContent(
|
||||
object_ptr<Ui::RpWidget>(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;
|
||||
|
|
|
@ -32,7 +32,7 @@ struct EditDraftOptionsArgs {
|
|||
std::vector<MessageLinkRange> links;
|
||||
std::shared_ptr<WebpageResolver> resolver;
|
||||
Fn<void(FullReplyTo, Data::WebPageDraft)> done;
|
||||
Fn<void()> highlight;
|
||||
Fn<void(FullReplyTo)> highlight;
|
||||
Fn<void()> clearOldDraft;
|
||||
};
|
||||
|
||||
|
|
|
@ -400,13 +400,6 @@ void ForwardPanel::paint(
|
|||
});
|
||||
}
|
||||
|
||||
void ClearDraftReplyTo(not_null<Data::Thread*> thread, FullMsgId equalTo) {
|
||||
ClearDraftReplyTo(
|
||||
thread->owningHistory(),
|
||||
thread->topicRootId(),
|
||||
equalTo);
|
||||
}
|
||||
|
||||
void ClearDraftReplyTo(
|
||||
not_null<History*> history,
|
||||
MsgId topicRootId,
|
||||
|
|
|
@ -72,7 +72,6 @@ private:
|
|||
|
||||
};
|
||||
|
||||
void ClearDraftReplyTo(not_null<Data::Thread*> thread, FullMsgId equalTo);
|
||||
void ClearDraftReplyTo(
|
||||
not_null<History*> history,
|
||||
MsgId topicRootId,
|
||||
|
|
|
@ -590,7 +590,7 @@ bool AddReplyToMessageAction(
|
|||
const ContextMenuRequest &request,
|
||||
not_null<ListWidget*> 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;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ struct ContextMenuRequest {
|
|||
SelectedItems selectedItems;
|
||||
TextForMimeData selectedText;
|
||||
TextWithEntities quote;
|
||||
HistoryItem *quoteItem = nullptr;
|
||||
bool overSelection = false;
|
||||
PointState pointState = PointState();
|
||||
};
|
||||
|
|
|
@ -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<Ui::PathShiftGradient> MakePathShiftGradient(
|
||||
|
@ -111,11 +147,6 @@ bool DefaultElementDelegate::elementUnderCursor(
|
|||
return false;
|
||||
}
|
||||
|
||||
float64 DefaultElementDelegate::elementHighlightOpacity(
|
||||
not_null<const HistoryItem*> 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<const HistoryItem*> 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<HistoryItem*> item) {
|
||||
if (selection.to > text.length()) {
|
||||
return {};
|
||||
}
|
||||
auto modified = selection;
|
||||
for (const auto &modification : text.modifications()) {
|
||||
if (modification.position >= selection.to) {
|
||||
break;
|
||||
} else if (modification.position <= selection.from) {
|
||||
modified.from += modification.skipped;
|
||||
if (modification.added
|
||||
&& modification.position < selection.from) {
|
||||
--modified.from;
|
||||
}
|
||||
}
|
||||
modified.to += modification.skipped;
|
||||
if (modification.added && modified.to > modified.from) {
|
||||
--modified.to;
|
||||
}
|
||||
}
|
||||
auto result = item->originalText();
|
||||
if (modified.empty() || modified.to > result.text.size()) {
|
||||
return {};
|
||||
}
|
||||
result.text = result.text.mid(
|
||||
modified.from,
|
||||
modified.to - modified.from);
|
||||
const auto allowed = std::array{
|
||||
EntityType::Bold,
|
||||
EntityType::Italic,
|
||||
EntityType::Underline,
|
||||
EntityType::StrikeOut,
|
||||
EntityType::Spoiler,
|
||||
EntityType::CustomEmoji,
|
||||
};
|
||||
for (auto i = result.entities.begin(); i != result.entities.end();) {
|
||||
const auto offset = i->offset();
|
||||
const auto till = offset + i->length();
|
||||
if ((till <= modified.from)
|
||||
|| (offset >= modified.to)
|
||||
|| !ranges::contains(allowed, i->type())) {
|
||||
i = result.entities.erase(i);
|
||||
} else {
|
||||
if (till > modified.to) {
|
||||
i->shrinkFromRight(till - modified.to);
|
||||
}
|
||||
i->shiftLeft(modified.from);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
return { item, result };
|
||||
}
|
||||
|
||||
TextSelection Element::FindSelectionFromQuote(
|
||||
const Ui::Text::String &text,
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithEntities "e) {
|
||||
if (quote.empty()) {
|
||||
return {};
|
||||
}
|
||||
const auto &original = item->originalText();
|
||||
auto result = TextSelection();
|
||||
auto offset = 0;
|
||||
while (true) {
|
||||
const auto i = original.text.indexOf(quote.text, offset);
|
||||
if (i < 0) {
|
||||
return {};
|
||||
}
|
||||
auto selection = TextSelection{
|
||||
uint16(i),
|
||||
uint16(i + quote.text.size()),
|
||||
};
|
||||
if (CheckQuoteEntities(quote.entities, original, selection)) {
|
||||
result = selection;
|
||||
break;
|
||||
}
|
||||
offset = i + 1;
|
||||
}
|
||||
//for (const auto &modification : text.modifications()) {
|
||||
// if (modification.position >= selection.to) {
|
||||
// break;
|
||||
// } else if (modification.position <= selection.from) {
|
||||
// modified.from += modification.skipped;
|
||||
// if (modification.added
|
||||
// && modification.position < selection.from) {
|
||||
// --modified.from;
|
||||
// }
|
||||
// }
|
||||
// modified.to += modification.skipped;
|
||||
// if (modification.added && modified.to > modified.from) {
|
||||
// --modified.to;
|
||||
// }
|
||||
//}
|
||||
return result;
|
||||
}
|
||||
|
||||
Reactions::ButtonParameters Element::reactionButtonParameters(
|
||||
QPoint position,
|
||||
const TextState &reactionState) const {
|
||||
|
|
|
@ -69,8 +69,6 @@ class ElementDelegate {
|
|||
public:
|
||||
virtual Context elementContext() = 0;
|
||||
virtual bool elementUnderCursor(not_null<const Element*> view) = 0;
|
||||
[[nodiscard]] virtual float64 elementHighlightOpacity(
|
||||
not_null<const HistoryItem*> item) const = 0;
|
||||
virtual bool elementInSelectionMode() = 0;
|
||||
virtual bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
|
@ -120,8 +118,6 @@ public:
|
|||
class DefaultElementDelegate : public ElementDelegate {
|
||||
public:
|
||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||
[[nodiscard]] float64 elementHighlightOpacity(
|
||||
not_null<const HistoryItem*> item) const override;
|
||||
bool elementInSelectionMode() override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const Element*> 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<Element>
|
||||
|
@ -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<HistoryItem*> 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<HistoryItem*> item);
|
||||
[[nodiscard]] static TextSelection FindSelectionFromQuote(
|
||||
const Ui::Text::String &text,
|
||||
not_null<HistoryItem*> 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;
|
||||
|
|
|
@ -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<void(bool found)> 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<void(bool found)> 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<const HistoryItem*> 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<Data::ReactionId>()
|
||||
|
@ -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()
|
||||
|
|
|
@ -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<void(bool found)> done = nullptr);
|
||||
void refreshViewer();
|
||||
|
||||
|
@ -292,8 +298,6 @@ public:
|
|||
// ElementDelegate interface.
|
||||
Context elementContext() override;
|
||||
bool elementUnderCursor(not_null<const Element*> view) override;
|
||||
[[nodiscard]] float64 elementHighlightOpacity(
|
||||
not_null<const HistoryItem*> item) const override;
|
||||
bool elementInSelectionMode() override;
|
||||
bool elementIntersectsRange(
|
||||
not_null<const Element*> view,
|
||||
|
@ -430,7 +434,7 @@ private:
|
|||
Fn<bool()> overrideInitialScroll);
|
||||
bool showAtPositionNow(
|
||||
Data::MessagePosition position,
|
||||
anim::type animated,
|
||||
const Window::SectionShow ¶ms,
|
||||
Fn<void(bool found)> done);
|
||||
|
||||
Ui::ChatPaintContext preparePaintContext(const QRect &clip) const;
|
||||
|
@ -645,6 +649,7 @@ private:
|
|||
base::flat_map<MsgId, Ui::PeerUserpicView> _hiddenSenderUserpics;
|
||||
|
||||
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||
QPainterPath _highlightPathCache;
|
||||
|
||||
base::unique_qptr<Ui::RpWidget> _emptyInfo = nullptr;
|
||||
|
||||
|
|
|
@ -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<HistoryItem*> 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<HistoryMessageLogEntryOriginal>()) {
|
||||
return false;
|
||||
} else if (media && media->isDisplayed()) {
|
||||
} else if (media && media->isDisplayed() && !_invertMedia) {
|
||||
return false;
|
||||
} else if (_reactions) {
|
||||
return false;
|
||||
|
|
|
@ -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<HistoryItem*> 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;
|
||||
|
|
|
@ -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<PinnedMemento*> memento) {
|
|||
? FullMsgId(_history->peer->id, highlight)
|
||||
: FullMsgId(_migratedPeer->id, -highlight)),
|
||||
.date = TimeId(0),
|
||||
}, anim::type::instant);
|
||||
}, { Window::SectionShow::Way::Forward, anim::type::instant });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -128,10 +128,12 @@ rpl::producer<Ui::MessageBarContent> RootViewContent(
|
|||
RepliesMemento::RepliesMemento(
|
||||
not_null<History*> 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<RepliesMemento*> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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*> history,
|
||||
MsgId rootId,
|
||||
MsgId highlightId = 0);
|
||||
MsgId highlightId = 0,
|
||||
const TextWithEntities &highlightPart = {});
|
||||
explicit RepliesMemento(
|
||||
not_null<HistoryItem*> commentsItem,
|
||||
MsgId commentId = 0);
|
||||
|
@ -421,9 +425,12 @@ public:
|
|||
[[nodiscard]] not_null<ListMemento*> 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*> _history;
|
||||
MsgId _rootId = 0;
|
||||
const MsgId _highlightId = 0;
|
||||
const TextWithEntities _highlightPart;
|
||||
ListMemento _list;
|
||||
std::shared_ptr<Data::RepliesList> _replies;
|
||||
QVector<FullMsgId> _replyReturns;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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<HistoryItem*> item,
|
||||
const TextWithEntities "e) const {
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -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<HistoryItem*> item,
|
||||
const TextWithEntities "e) const override;
|
||||
TextSelection adjustSelection(
|
||||
TextSelection selection,
|
||||
|
|
|
@ -744,6 +744,7 @@ void Document::draw(
|
|||
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||
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<HistoryDocumentVoice>()) {
|
||||
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<HistoryDocumentCaptioned>()) {
|
||||
return parent()->selectedQuote(captioned->caption, selection);
|
||||
return Element::FindSelectedQuote(
|
||||
captioned->caption,
|
||||
selection,
|
||||
_realParent);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
TextSelection Document::selectionFromQuote(
|
||||
not_null<HistoryItem*> item,
|
||||
const TextWithEntities "e) const {
|
||||
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||
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<uint64*> cacheKey,
|
||||
not_null<QPixmap*> 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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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<HistoryItem*> item,
|
||||
const TextWithEntities "e) const override;
|
||||
|
||||
bool uploading() const override;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Ui::RippleAnimation>(
|
||||
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(
|
||||
|
|
|
@ -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<GameData*> _data;
|
||||
std::shared_ptr<ReplyMarkupClickHandler> _openl;
|
||||
std::unique_ptr<Media> _attach;
|
||||
mutable std::unique_ptr<Ui::RippleAnimation> _ripple;
|
||||
|
||||
mutable QPoint _lastPoint;
|
||||
int _gameTagWidth = 0;
|
||||
int _descriptionLines = 0;
|
||||
int _titleLines = 0;
|
||||
|
|
|
@ -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<HistoryItem*> item,
|
||||
const TextWithEntities "e) const {
|
||||
return parent()->selectionFromQuote(_caption, quote);
|
||||
return Element::FindSelectionFromQuote(_caption, item, quote);
|
||||
}
|
||||
|
||||
bool Gif::fullFeaturedGrouped(RectParts sides) const {
|
||||
|
|
|
@ -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<HistoryItem*> item,
|
||||
const TextWithEntities "e) const override;
|
||||
|
||||
bool uploading() const override;
|
||||
|
|
|
@ -181,6 +181,11 @@ Storage::SharedMediaTypesMask Media::sharedMediaTypes() const {
|
|||
return {};
|
||||
}
|
||||
|
||||
bool Media::allowTextSelectionByHandler(
|
||||
const ClickHandlerPtr &handler) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
not_null<Element*> Media::parent() const {
|
||||
return _parent;
|
||||
}
|
||||
|
@ -189,6 +194,10 @@ not_null<History*> Media::history() const {
|
|||
return _parent->history();
|
||||
}
|
||||
|
||||
SelectedQuote Media::selectedQuote(TextSelection selection) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Media::isDisplayed() const {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -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<HistoryItem*> 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,
|
||||
|
|
|
@ -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<HistoryItem*> item,
|
||||
const TextWithEntities "e) const {
|
||||
if (_mode != Mode::Column) {
|
||||
return (_captionItem == item)
|
||||
? Element::FindSelectionFromQuote(_caption, item, quote)
|
||||
: TextSelection();
|
||||
}
|
||||
const auto i = ranges::find(_parts, item, &Part::item);
|
||||
if (i == end(_parts)) {
|
||||
return {};
|
||||
}
|
||||
const auto index = int(i - begin(_parts));
|
||||
auto result = i->content->selectionFromQuote(item, quote);
|
||||
if (result.empty()) {
|
||||
return AddGroupItemSelection({}, index);
|
||||
}
|
||||
for (auto j = i; j != begin(_parts);) {
|
||||
result = (--j)->content->unskipSelection(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto GroupedMedia::getBubbleSelectionIntervals(
|
||||
TextSelection selection) const
|
||||
-> std::vector<Ui::BubbleSelectionInterval> {
|
||||
|
@ -663,16 +746,15 @@ bool GroupedMedia::validateGroupParts(
|
|||
}
|
||||
|
||||
void GroupedMedia::refreshCaption() {
|
||||
using PartPtrOpt = std::optional<const Part*>;
|
||||
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 {
|
||||
|
|
|
@ -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<HistoryItem*> item,
|
||||
const TextWithEntities "e) const override;
|
||||
|
||||
std::vector<Ui::BubbleSelectionInterval> getBubbleSelectionIntervals(
|
||||
TextSelection selection) const override;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<HistoryItem*> item,
|
||||
const TextWithEntities "e) const {
|
||||
return parent()->selectionFromQuote(_caption, quote);
|
||||
return Element::FindSelectionFromQuote(_caption, item, quote);
|
||||
}
|
||||
|
||||
void Photo::hideSpoilers() {
|
||||
|
|
|
@ -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<HistoryItem*> item,
|
||||
const TextWithEntities "e) const override;
|
||||
|
||||
PhotoData *getPhoto() const override {
|
||||
|
|
|
@ -155,7 +155,7 @@ bool WebPage::HasButton(not_null<WebPageData*> 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<HistoryMessageLogEntryOriginal>();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<TextWithEntities> UsernamesSubtext(
|
||||
not_null<PeerData*> peer,
|
||||
rpl::producer<QString> fallback) {
|
||||
|
@ -148,6 +155,27 @@ namespace {
|
|||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<TextWithEntities> AboutWithIdValue(
|
||||
not_null<PeerData*> peer) {
|
||||
|
||||
return AboutValue(
|
||||
peer
|
||||
) | rpl::map([=](TextWithEntities &&value) {
|
||||
if (!ShowPeerIdBelowAbout.value()) {
|
||||
return std::move(value);
|
||||
}
|
||||
using namespace Ui::Text;
|
||||
if (!value.empty()) {
|
||||
value.append("\n");
|
||||
}
|
||||
value.append(Italic(u"id: "_q));
|
||||
const auto raw = peer->id.value & PeerId::kChatTypeMask;
|
||||
const auto id = QString::number(raw);
|
||||
value.append(Link(Italic(id), "internal:copy:" + id));
|
||||
return std::move(value);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Text, typename ToggleOn, typename Callback>
|
||||
auto AddActionButton(
|
||||
not_null<Ui::VerticalLayout*> parent,
|
||||
|
@ -425,8 +453,8 @@ object_ptr<Ui::RpWidget> 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<Ui::RpWidget> 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<Ui::RpWidget> ActionsFiller::fill() {
|
|||
|
||||
} // namespace
|
||||
|
||||
const char kOptionShowPeerIdBelowAbout[] = "show-peer-id-below-about";
|
||||
|
||||
object_ptr<Ui::RpWidget> SetupDetails(
|
||||
not_null<Controller*> controller,
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
|
|
|
@ -23,6 +23,8 @@ class Controller;
|
|||
|
||||
namespace Info::Profile {
|
||||
|
||||
extern const char kOptionShowPeerIdBelowAbout[];
|
||||
|
||||
object_ptr<Ui::RpWidget> SetupDetails(
|
||||
not_null<Controller*> controller,
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
|
|
|
@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "info/profile/info_profile_values.h"
|
||||
|
||||
#include "base/options.h"
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
|
@ -38,13 +37,6 @@ namespace {
|
|||
|
||||
using UpdateFlag = Data::PeerUpdate::Flag;
|
||||
|
||||
base::options::toggle ShowPeerIdBelowAbout({
|
||||
.id = kOptionShowPeerIdBelowAbout,
|
||||
.name = "Show Peer IDs in Profile",
|
||||
.description = "Show peer IDs from API below their Bio / Description.",
|
||||
.scope = static_cast<base::options::details::ScopeFlag>(0),
|
||||
});
|
||||
|
||||
auto PlainAboutValue(not_null<PeerData*> peer) {
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
|
@ -95,8 +87,6 @@ void StripExternalLinks(TextWithEntities &text) {
|
|||
|
||||
} // namespace
|
||||
|
||||
const char kOptionShowPeerIdBelowAbout[] = "show-peer-id-below-about";
|
||||
|
||||
rpl::producer<QString> NameValue(not_null<PeerData*> peer) {
|
||||
return peer->session().changes().peerFlagsValue(
|
||||
peer,
|
||||
|
@ -219,16 +209,6 @@ TextWithEntities AboutWithEntities(
|
|||
if (stripExternal) {
|
||||
StripExternalLinks(result);
|
||||
}
|
||||
if (ShowPeerIdBelowAbout.value()) {
|
||||
using namespace Ui::Text;
|
||||
if (!result.empty()) {
|
||||
result.append("\n");
|
||||
}
|
||||
result.append(Italic(u"id: "_q));
|
||||
const auto raw = peer->id.value & PeerId::kChatTypeMask;
|
||||
const auto id = QString::number(raw);
|
||||
result.append(Link(Italic(id), "internal:copy:" + id));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,8 +35,6 @@ enum class SharedMediaType : signed char;
|
|||
|
||||
namespace Info::Profile {
|
||||
|
||||
extern const char kOptionShowPeerIdBelowAbout[];
|
||||
|
||||
inline auto ToSingleLine() {
|
||||
return rpl::map([](const QString &text) {
|
||||
return TextUtilities::SingleLine(text);
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "api/api_statistics.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history_item.h"
|
||||
|
@ -28,9 +29,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/rect.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_menu_icons.h"
|
||||
#include "styles/style_settings.h"
|
||||
#include "styles/style_statistics.h"
|
||||
|
||||
|
@ -43,6 +46,39 @@ struct Descriptor final {
|
|||
not_null<QWidget*> toastParent;
|
||||
};
|
||||
|
||||
void AddContextMenu(
|
||||
not_null<Ui::RpWidget*> button,
|
||||
not_null<Controller*> controller,
|
||||
not_null<HistoryItem*> item) {
|
||||
const auto fullId = item->fullId();
|
||||
const auto contextMenu = button->lifetime()
|
||||
.make_state<base::unique_qptr<Ui::PopupMenu>>();
|
||||
const auto showMenu = [=] {
|
||||
*contextMenu = base::make_unique_q<Ui::PopupMenu>(
|
||||
button,
|
||||
st::popupMenuWithIcons);
|
||||
const auto go = [=] {
|
||||
const auto &session = controller->parentController();
|
||||
if (const auto item = session->session().data().message(fullId)) {
|
||||
session->showMessage(item);
|
||||
}
|
||||
};
|
||||
contextMenu->get()->addAction(
|
||||
tr::lng_context_to_msg(tr::now),
|
||||
crl::guard(controller, go),
|
||||
&st::menuIconShowInChat);
|
||||
contextMenu->get()->popup(QCursor::pos());
|
||||
};
|
||||
|
||||
base::install_event_filter(button, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::ContextMenu) {
|
||||
showMenu();
|
||||
return base::EventFilterResult::Cancel;
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void ProcessZoom(
|
||||
const Descriptor &d,
|
||||
not_null<Statistic::ChartWidget*> widget,
|
||||
|
@ -544,7 +580,9 @@ void InnerWidget::fill() {
|
|||
if (_state.stats.message) {
|
||||
if (const auto i = _peer->owner().message(_contextId)) {
|
||||
::Settings::AddSkip(inner);
|
||||
inner->add(object_ptr<MessagePreview>(this, i, -1, -1, QImage()));
|
||||
const auto preview = inner->add(
|
||||
object_ptr<MessagePreview>(this, i, -1, -1, QImage()));
|
||||
AddContextMenu(preview, _controller, i);
|
||||
::Settings::AddSkip(inner);
|
||||
::Settings::AddDivider(inner);
|
||||
}
|
||||
|
@ -639,6 +677,8 @@ void InnerWidget::fillRecentPosts() {
|
|||
info.forwardsCount,
|
||||
std::move(cachedPreview));
|
||||
|
||||
AddContextMenu(button, _controller, item);
|
||||
|
||||
_messagePreviews.push_back(raw);
|
||||
raw->show();
|
||||
button->sizeValue(
|
||||
|
|
|
@ -828,6 +828,19 @@ void AttachWebView::requestWithOptionalConfirm(
|
|||
void AttachWebView::request(const WebViewButton &button) {
|
||||
Expects(_context != nullptr && _bot != nullptr);
|
||||
|
||||
if (button.fromAttachMenu) {
|
||||
const auto bot = ranges::find(
|
||||
_attachBots,
|
||||
not_null{ _bot },
|
||||
&AttachWebViewBot::user);
|
||||
if (bot == end(_attachBots) || bot->inactive) {
|
||||
requestAddToMenu(_bot, AddToMenuOpenAttach{
|
||||
.startCommand = button.startCommand,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_startCommand = button.startCommand;
|
||||
const auto &action = _context->action;
|
||||
|
||||
|
@ -1524,8 +1537,10 @@ void AttachWebView::confirmAddToMenu(
|
|||
}
|
||||
_confirmAddBox = active->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
const auto allowed = std::make_shared<Ui::Checkbox*>();
|
||||
const auto disclaimer = !disclaimerAccepted(bot);
|
||||
const auto done = [=](Fn<void()> close) {
|
||||
const auto state = ((*allowed) && (*allowed)->checked())
|
||||
const auto state = (disclaimer
|
||||
|| ((*allowed) && (*allowed)->checked()))
|
||||
? ToggledState::AllowedToWrite
|
||||
: ToggledState::Added;
|
||||
toggleInMenu(bot.user, state, [=] {
|
||||
|
@ -1538,13 +1553,22 @@ void AttachWebView::confirmAddToMenu(
|
|||
});
|
||||
close();
|
||||
};
|
||||
const auto disclaimer = !disclaimerAccepted(bot);
|
||||
if (disclaimer) {
|
||||
FillDisclaimerBox(box, [=] {
|
||||
_disclaimerAccepted.emplace(bot.user);
|
||||
_attachBotsUpdates.fire({});
|
||||
done([] {});
|
||||
});
|
||||
box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||
box,
|
||||
st::boxRowPadding.left()));
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_bot_will_be_added(
|
||||
lt_bot,
|
||||
rpl::single(Ui::Text::Bold(bot.name)),
|
||||
Ui::Text::WithEntities),
|
||||
st::boxLabel));
|
||||
} else {
|
||||
Ui::ConfirmBox(box, {
|
||||
(bot.inMainMenu
|
||||
|
@ -1556,40 +1580,26 @@ void AttachWebView::confirmAddToMenu(
|
|||
Ui::Text::WithEntities),
|
||||
done,
|
||||
});
|
||||
}
|
||||
if (bot.requestWriteAccess) {
|
||||
(*allowed) = box->addRow(
|
||||
object_ptr<Ui::Checkbox>(
|
||||
box,
|
||||
tr::lng_url_auth_allow_messages(
|
||||
tr::now,
|
||||
lt_bot,
|
||||
Ui::Text::Bold(bot.name),
|
||||
Ui::Text::WithEntities),
|
||||
true,
|
||||
st::urlAuthCheckbox),
|
||||
style::margins(
|
||||
st::boxRowPadding.left(),
|
||||
(disclaimer
|
||||
? st::boxPhotoCaptionSkip
|
||||
: st::boxRowPadding.left()),
|
||||
st::boxRowPadding.right(),
|
||||
st::boxRowPadding.left()));
|
||||
(*allowed)->setAllowTextLines();
|
||||
}
|
||||
if (disclaimer) {
|
||||
if (!bot.requestWriteAccess) {
|
||||
box->addRow(object_ptr<Ui::FixedHeightWidget>(
|
||||
box,
|
||||
st::boxRowPadding.left()));
|
||||
if (bot.requestWriteAccess) {
|
||||
(*allowed) = box->addRow(
|
||||
object_ptr<Ui::Checkbox>(
|
||||
box,
|
||||
tr::lng_url_auth_allow_messages(
|
||||
tr::now,
|
||||
lt_bot,
|
||||
Ui::Text::Bold(bot.name),
|
||||
Ui::Text::WithEntities),
|
||||
true,
|
||||
st::urlAuthCheckbox),
|
||||
style::margins(
|
||||
st::boxRowPadding.left(),
|
||||
(disclaimer
|
||||
? st::boxPhotoCaptionSkip
|
||||
: st::boxRowPadding.left()),
|
||||
st::boxRowPadding.right(),
|
||||
st::boxRowPadding.left()));
|
||||
(*allowed)->setAllowTextLines();
|
||||
}
|
||||
box->addRow(object_ptr<Ui::FlatLabel>(
|
||||
box,
|
||||
tr::lng_bot_will_be_added(
|
||||
lt_bot,
|
||||
rpl::single(Ui::Text::Bold(bot.name)),
|
||||
Ui::Text::WithEntities),
|
||||
st::boxLabel));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -134,6 +134,9 @@ public:
|
|||
[[nodiscard]] rpl::producer<> attachBotsUpdates() const {
|
||||
return _attachBotsUpdates.events();
|
||||
}
|
||||
void notifyBotIconLoaded() {
|
||||
_attachBotsUpdates.fire({});
|
||||
}
|
||||
[[nodiscard]] bool disclaimerAccepted(
|
||||
const AttachWebViewBot &bot) const;
|
||||
[[nodiscard]] bool showMainMenuNewBadge(
|
||||
|
|
|
@ -1411,7 +1411,7 @@ void MainWidget::showHistory(
|
|||
&& way != Way::Forward) {
|
||||
clearBotStartToken(_history->peer());
|
||||
}
|
||||
_history->showHistory(peerId, showAtMsgId);
|
||||
_history->showHistory(peerId, showAtMsgId, params.highlightPart);
|
||||
if (alreadyThatPeer && params.reapplyLocalDraft) {
|
||||
_history->applyDraft(HistoryWidget::FieldHistoryAction::NewEntry);
|
||||
}
|
||||
|
@ -1772,7 +1772,7 @@ void MainWidget::showNewSection(
|
|||
} else {
|
||||
_mainSection = std::move(newMainSection);
|
||||
_history->finishAnimating();
|
||||
_history->showHistory(0, 0);
|
||||
_history->showHistory(0, 0, {});
|
||||
|
||||
if (const auto entry = _mainSection->activeChat(); entry.key) {
|
||||
_controller->setActiveChatEntry(entry);
|
||||
|
|
|
@ -67,4 +67,8 @@ private:
|
|||
|
||||
};
|
||||
|
||||
inline bool HasMonochromeSetting() {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "mainwindow.h"
|
||||
#include "mainwidget.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "core/sandbox.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
|
@ -175,7 +176,11 @@ ApplicationDelegate *_sharedDelegate = nil;
|
|||
Core::App().handleAppActivated();
|
||||
if (const auto window = Core::App().activeWindow()) {
|
||||
if (window->widget()->isHidden()) {
|
||||
window->widget()->showFromTray();
|
||||
if (Core::App().calls().hasVisiblePanel()) {
|
||||
Core::App().calls().activateCurrentCall();
|
||||
} else {
|
||||
window->widget()->showFromTray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,4 +55,8 @@ private:
|
|||
|
||||
};
|
||||
|
||||
inline bool HasMonochromeSetting() {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
|
|
|
@ -11,6 +11,8 @@ namespace Platform {
|
|||
|
||||
class Tray;
|
||||
|
||||
[[nodiscard]] bool HasMonochromeSetting();
|
||||
|
||||
} // namespace Platform
|
||||
|
||||
// Platform dependent implementations.
|
||||
|
|
|
@ -233,18 +233,19 @@ void Tray::updateIcon() {
|
|||
: !controller->sessionController()
|
||||
? nullptr
|
||||
: &controller->sessionController()->session();
|
||||
const auto monochrome = Core::App().settings().trayIconMonochrome();
|
||||
const auto supportMode = session && session->supportMode();
|
||||
const auto iconSizeWidth = GetSystemMetrics(SM_CXSMICON);
|
||||
|
||||
auto iconSmallPixmap16 = Tray::IconWithCounter(
|
||||
CounterLayerArgs(16, counter, muted),
|
||||
true,
|
||||
true,
|
||||
monochrome,
|
||||
supportMode);
|
||||
auto iconSmallPixmap32 = Tray::IconWithCounter(
|
||||
CounterLayerArgs(32, counter, muted),
|
||||
true,
|
||||
true,
|
||||
monochrome,
|
||||
supportMode);
|
||||
auto iconSmall = QIcon();
|
||||
iconSmall.addPixmap(iconSmallPixmap16);
|
||||
|
@ -351,8 +352,15 @@ QPixmap Tray::IconWithCounter(
|
|||
bool smallIcon,
|
||||
bool monochrome,
|
||||
bool supportMode) {
|
||||
return Ui::PixmapFromImage(
|
||||
ImageIconWithCounter(std::move(args), supportMode, smallIcon, monochrome));
|
||||
return Ui::PixmapFromImage(ImageIconWithCounter(
|
||||
std::move(args),
|
||||
supportMode,
|
||||
smallIcon,
|
||||
monochrome));
|
||||
}
|
||||
|
||||
bool HasMonochromeSetting() {
|
||||
return IsDarkTaskbar().has_value();
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
|
|
|
@ -453,18 +453,35 @@ void SetupSystemIntegrationContent(
|
|||
st::settingsCheckboxPadding));
|
||||
};
|
||||
|
||||
const auto settings = &Core::App().settings();
|
||||
if (Platform::TrayIconSupported()) {
|
||||
const auto trayEnabled = [] {
|
||||
const auto workMode = Core::App().settings().workMode();
|
||||
const auto trayEnabled = [=] {
|
||||
const auto workMode = settings->workMode();
|
||||
return (workMode == WorkMode::TrayOnly)
|
||||
|| (workMode == WorkMode::WindowAndTray);
|
||||
};
|
||||
const auto tray = addCheckbox(
|
||||
tr::lng_settings_workmode_tray(),
|
||||
trayEnabled());
|
||||
const auto monochrome = Platform::HasMonochromeSetting()
|
||||
? addSlidingCheckbox(
|
||||
tr::lng_settings_monochrome_icon(),
|
||||
settings->trayIconMonochrome())
|
||||
: nullptr;
|
||||
if (monochrome) {
|
||||
monochrome->toggle(tray->checked(), anim::type::instant);
|
||||
|
||||
const auto taskbarEnabled = [] {
|
||||
const auto workMode = Core::App().settings().workMode();
|
||||
monochrome->entity()->checkedChanges(
|
||||
) | rpl::filter([=](bool value) {
|
||||
return (value != settings->trayIconMonochrome());
|
||||
}) | rpl::start_with_next([=](bool value) {
|
||||
settings->setTrayIconMonochrome(value);
|
||||
Core::App().saveSettingsDelayed();
|
||||
}, monochrome->lifetime());
|
||||
}
|
||||
|
||||
const auto taskbarEnabled = [=] {
|
||||
const auto workMode = settings->workMode();
|
||||
return (workMode == WorkMode::WindowOnly)
|
||||
|| (workMode == WorkMode::WindowAndTray);
|
||||
};
|
||||
|
@ -482,10 +499,10 @@ void SetupSystemIntegrationContent(
|
|||
: WorkMode::WindowOnly;
|
||||
if ((newMode == WorkMode::WindowAndTray
|
||||
|| newMode == WorkMode::TrayOnly)
|
||||
&& Core::App().settings().workMode() != newMode) {
|
||||
&& settings->workMode() != newMode) {
|
||||
cSetSeenTrayTooltip(false);
|
||||
}
|
||||
Core::App().settings().setWorkMode(newMode);
|
||||
settings->setWorkMode(newMode);
|
||||
Core::App().saveSettingsDelayed();
|
||||
};
|
||||
|
||||
|
@ -498,6 +515,9 @@ void SetupSystemIntegrationContent(
|
|||
} else {
|
||||
updateWorkmode();
|
||||
}
|
||||
if (monochrome) {
|
||||
monochrome->toggle(checked, anim::type::normal);
|
||||
}
|
||||
}, tray->lifetime());
|
||||
|
||||
if (taskbar) {
|
||||
|
@ -519,19 +539,19 @@ void SetupSystemIntegrationContent(
|
|||
tr::lng_settings_mac_warn_before_quit(
|
||||
lt_text,
|
||||
rpl::single(Platform::ConfirmQuit::QuitKeysString())),
|
||||
Core::App().settings().macWarnBeforeQuit());
|
||||
settings->macWarnBeforeQuit());
|
||||
warnBeforeQuit->checkedChanges(
|
||||
) | rpl::filter([=](bool checked) {
|
||||
return (checked != Core::App().settings().macWarnBeforeQuit());
|
||||
return (checked != settings->macWarnBeforeQuit());
|
||||
}) | rpl::start_with_next([=](bool checked) {
|
||||
Core::App().settings().setMacWarnBeforeQuit(checked);
|
||||
settings->setMacWarnBeforeQuit(checked);
|
||||
Core::App().saveSettingsDelayed();
|
||||
}, warnBeforeQuit->lifetime());
|
||||
|
||||
#ifndef OS_MAC_STORE
|
||||
const auto enabled = [] {
|
||||
const auto enabled = [=] {
|
||||
const auto digest = base::Platform::CurrentCustomAppIconDigest();
|
||||
return digest && (Core::App().settings().macRoundIconDigest() == digest);
|
||||
return digest && (settings->macRoundIconDigest() == digest);
|
||||
};
|
||||
const auto roundIcon = addCheckbox(
|
||||
tr::lng_settings_mac_round_icon(),
|
||||
|
@ -548,7 +568,7 @@ void SetupSystemIntegrationContent(
|
|||
}
|
||||
Window::OverrideApplicationIcon(checked ? IconMacRound() : QImage());
|
||||
Core::App().refreshApplicationIcon();
|
||||
Core::App().settings().setMacRoundIconDigest(digest);
|
||||
settings->setMacRoundIconDigest(digest);
|
||||
Core::App().saveSettings();
|
||||
}, roundIcon->lifetime());
|
||||
#endif // OS_MAC_STORE
|
||||
|
@ -557,10 +577,10 @@ void SetupSystemIntegrationContent(
|
|||
if (!Platform::RunInBackground()) {
|
||||
const auto closeToTaskbar = addSlidingCheckbox(
|
||||
tr::lng_settings_close_to_taskbar(),
|
||||
Core::App().settings().closeToTaskbar());
|
||||
settings->closeToTaskbar());
|
||||
|
||||
const auto closeToTaskbarShown = std::make_shared<rpl::variable<bool>>(false);
|
||||
Core::App().settings().workModeValue(
|
||||
settings->workModeValue(
|
||||
) | rpl::start_with_next([=](WorkMode workMode) {
|
||||
*closeToTaskbarShown = !Core::App().tray().has();
|
||||
}, closeToTaskbar->lifetime());
|
||||
|
@ -568,9 +588,9 @@ void SetupSystemIntegrationContent(
|
|||
closeToTaskbar->toggleOn(closeToTaskbarShown->value());
|
||||
closeToTaskbar->entity()->checkedChanges(
|
||||
) | rpl::filter([=](bool checked) {
|
||||
return (checked != Core::App().settings().closeToTaskbar());
|
||||
return (checked != settings->closeToTaskbar());
|
||||
}) | rpl::start_with_next([=](bool checked) {
|
||||
Core::App().settings().setCloseToTaskbar(checked);
|
||||
settings->setCloseToTaskbar(checked);
|
||||
Local::writeSettings();
|
||||
}, closeToTaskbar->lifetime());
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "core/launcher.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "dialogs/dialogs_widget.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/profile/info_profile_actions.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
|
|
|
@ -1018,9 +1018,9 @@ void ChartWidget::updateBottomDates() {
|
|||
const auto k = _chartArea->width() / d;
|
||||
const auto stepRaw = int(k / 6);
|
||||
|
||||
const auto by = int(_chartArea->width() / float64(_chartData.x.size()));
|
||||
_bottomLine.captionIndicesOffset = 0
|
||||
+ st::statisticsChartBottomCaptionMaxWidth
|
||||
/ int(_chartArea->width() / float64(_chartData.x.size()));
|
||||
+ st::statisticsChartBottomCaptionMaxWidth / std::max(by, 1);
|
||||
|
||||
const auto isCurrentNull = (_bottomLine.current.stepMinFast == 0);
|
||||
if (!isCurrentNull
|
||||
|
|
|
@ -46,6 +46,11 @@ void Tray::create() {
|
|||
}
|
||||
}, _tray.lifetime());
|
||||
|
||||
Core::App().settings().trayIconMonochromeChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateIconCounters();
|
||||
}, _tray.lifetime());
|
||||
|
||||
Core::App().passcodeLockChanges(
|
||||
) | rpl::start_with_next([=] {
|
||||
rebuildMenu();
|
||||
|
|
|
@ -146,6 +146,7 @@ outReplyTextPaletteSelected: TextPalette(outTextPaletteSelected) {
|
|||
}
|
||||
imgReplyTextPalette: TextPalette(defaultTextPalette) {
|
||||
linkFg: msgImgReplyBarColor;
|
||||
monoFg: msgImgReplyBarColor;
|
||||
}
|
||||
inSemiboldPalette: TextPalette(inTextPalette) {
|
||||
linkFg: msgInServiceFg;
|
||||
|
|
|
@ -142,6 +142,12 @@ struct BackgroundEmojiData {
|
|||
uint8 colorIndexPlusOne);
|
||||
};
|
||||
|
||||
struct ChatPaintHighlight {
|
||||
float64 opacity = 0.;
|
||||
float64 collapsion = 0.;
|
||||
TextSelection range;
|
||||
};
|
||||
|
||||
struct ChatPaintContext {
|
||||
not_null<const ChatStyle*> st;
|
||||
const BubblePattern *bubblesPattern = nullptr;
|
||||
|
@ -149,11 +155,15 @@ struct ChatPaintContext {
|
|||
QRect viewport;
|
||||
QRect clip;
|
||||
TextSelection selection;
|
||||
ChatPaintHighlight highlight;
|
||||
QPainterPath *highlightPathCache = nullptr;
|
||||
mutable QRect highlightInterpolateTo;
|
||||
crl::time now = 0;
|
||||
|
||||
void translate(int x, int y) {
|
||||
viewport.translate(x, y);
|
||||
clip.translate(x, y);
|
||||
highlightInterpolateTo.translate(x, y);
|
||||
}
|
||||
void translate(QPoint point) {
|
||||
translate(point.x(), point.y());
|
||||
|
@ -181,6 +191,19 @@ struct ChatPaintContext {
|
|||
result.selection = selection;
|
||||
return result;
|
||||
}
|
||||
[[nodiscard]] auto computeHighlightCache() const
|
||||
-> std::optional<Ui::Text::HighlightInfoRequest> {
|
||||
if (highlight.range.empty() || highlight.collapsion <= 0.) {
|
||||
return {};
|
||||
}
|
||||
return Ui::Text::HighlightInfoRequest{
|
||||
.range = highlight.range,
|
||||
.interpolateTo = highlightInterpolateTo,
|
||||
.interpolateProgress = (1. - highlight.collapsion),
|
||||
.outPath = highlightPathCache,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// This is supported only in unwrapped media for now.
|
||||
enum class SkipDrawingParts {
|
||||
|
|
|
@ -11,6 +11,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "styles/style_basic.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kAlmostIndex = float64(.99);
|
||||
|
||||
} // namespace
|
||||
|
||||
PickerAnimation::PickerAnimation() = default;
|
||||
|
||||
|
@ -26,6 +31,23 @@ void PickerAnimation::jumpToOffset(int offset) {
|
|||
value);
|
||||
_updates.fire(_result.current - was);
|
||||
};
|
||||
if (anim::Disabled()) {
|
||||
auto value = float64(0.);
|
||||
const auto diff = _result.to - _result.from;
|
||||
const auto step = std::min(
|
||||
kAlmostIndex,
|
||||
1. / (std::max(1. - kAlmostIndex, std::abs(diff) + 1)));
|
||||
while (true) {
|
||||
value += step;
|
||||
if (value >= 1.) {
|
||||
callback(1.);
|
||||
break;
|
||||
} else {
|
||||
callback(value);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
_animation.start(
|
||||
std::move(callback),
|
||||
0.,
|
||||
|
@ -94,20 +116,14 @@ VerticalDrumPicker::VerticalDrumPicker(
|
|||
}, lifetime());
|
||||
|
||||
_animation.updates(
|
||||
) | rpl::distinct_until_changed(
|
||||
) | rpl::start_with_next([=](PickerAnimation::Shift shift) {
|
||||
increaseShift(shift);
|
||||
if (anim::Disabled()) {
|
||||
animationDataFromIndex();
|
||||
_animation.jumpToOffset(0);
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void VerticalDrumPicker::increaseShift(float64 by) {
|
||||
{
|
||||
// Guard input.
|
||||
constexpr auto kAlmostIndex = .99;
|
||||
if (by >= 1.) {
|
||||
by = kAlmostIndex;
|
||||
} else if (by <= -1.) {
|
||||
|
|
|
@ -59,6 +59,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "main/main_domain.h"
|
||||
#include "mtproto/mtp_instance.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_folder.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
|
@ -216,6 +217,9 @@ void SetupMenuBots(
|
|||
const auto wrap = container->add(
|
||||
object_ptr<Ui::VerticalLayout>(container));
|
||||
const auto bots = &controller->session().attachWebView();
|
||||
const auto iconLoadLifetime = wrap->lifetime().make_state<
|
||||
rpl::lifetime
|
||||
>();
|
||||
|
||||
rpl::single(
|
||||
rpl::empty
|
||||
|
@ -225,7 +229,20 @@ void SetupMenuBots(
|
|||
const auto width = container->widthNoMargins();
|
||||
wrap->clear();
|
||||
for (const auto &bot : bots->attachBots()) {
|
||||
if (!bot.inMainMenu) {
|
||||
const auto user = bot.user;
|
||||
if (!bot.inMainMenu || !bot.media) {
|
||||
continue;
|
||||
} else if (const auto media = bot.media; !media->loaded()) {
|
||||
if (!*iconLoadLifetime) {
|
||||
auto &session = user->session();
|
||||
*iconLoadLifetime = session.downloaderTaskFinished(
|
||||
) | rpl::start_with_next([=] {
|
||||
if (media->loaded()) {
|
||||
iconLoadLifetime->destroy();
|
||||
bots->notifyBotIconLoaded();
|
||||
}
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto button = Settings::AddButton(
|
||||
|
@ -244,7 +261,6 @@ void SetupMenuBots(
|
|||
st::mainMenuButton.iconLeft,
|
||||
(height - icon->height()) / 2);
|
||||
}, button->lifetime());
|
||||
const auto user = bot.user;
|
||||
const auto weak = Ui::MakeWeak(container);
|
||||
button->setAcceptBoth(true);
|
||||
button->clicks(
|
||||
|
|
|
@ -689,6 +689,7 @@ void SessionNavigation::applyBoost(
|
|||
done(false);
|
||||
return;
|
||||
}
|
||||
auto slot = int();
|
||||
auto different = PeerId();
|
||||
auto earliest = TimeId(-1);
|
||||
const auto now = base::unixtime::now();
|
||||
|
@ -699,13 +700,13 @@ void SessionNavigation::applyBoost(
|
|||
? peerFromMTP(*data.vpeer())
|
||||
: PeerId();
|
||||
if (!peerId && cooldown <= now) {
|
||||
applyBoostChecked(channel, done);
|
||||
applyBoostChecked(channel, data.vslot().v, done);
|
||||
return;
|
||||
} else if (peerId != channel->id) {
|
||||
} else if (peerId != channel->id
|
||||
&& (earliest < 0 || cooldown < earliest)) {
|
||||
slot = data.vslot().v;
|
||||
different = peerId;
|
||||
if (earliest < 0 || cooldown < earliest) {
|
||||
earliest = cooldown;
|
||||
}
|
||||
earliest = cooldown;
|
||||
}
|
||||
}
|
||||
if (different) {
|
||||
|
@ -731,7 +732,7 @@ void SessionNavigation::applyBoost(
|
|||
done(false);
|
||||
} else {
|
||||
const auto peer = _session->data().peer(different);
|
||||
replaceBoostConfirm(peer, channel, done);
|
||||
replaceBoostConfirm(peer, channel, slot, done);
|
||||
}
|
||||
} else {
|
||||
uiShow()->show(Ui::MakeConfirmBox({
|
||||
|
@ -752,11 +753,12 @@ void SessionNavigation::applyBoost(
|
|||
void SessionNavigation::replaceBoostConfirm(
|
||||
not_null<PeerData*> from,
|
||||
not_null<ChannelData*> channel,
|
||||
int slot,
|
||||
Fn<void(bool)> done) {
|
||||
const auto forwarded = std::make_shared<bool>(false);
|
||||
const auto confirmed = [=](Fn<void()> close) {
|
||||
*forwarded = true;
|
||||
applyBoostChecked(channel, done);
|
||||
applyBoostChecked(channel, slot, done);
|
||||
close();
|
||||
};
|
||||
const auto box = uiShow()->show(Box([=](not_null<Ui::GenericBox*> box) {
|
||||
|
@ -785,10 +787,11 @@ void SessionNavigation::replaceBoostConfirm(
|
|||
|
||||
void SessionNavigation::applyBoostChecked(
|
||||
not_null<ChannelData*> channel,
|
||||
int slot,
|
||||
Fn<void(bool)> done) {
|
||||
_api.request(MTPpremium_ApplyBoost(
|
||||
MTP_flags(0),
|
||||
MTPVector<MTPint>(), // slots
|
||||
MTP_flags(MTPpremium_ApplyBoost::Flag::f_slots),
|
||||
MTP_vector<MTPint>({ MTP_int(slot) }),
|
||||
channel->input
|
||||
)).done([=](const MTPpremium_MyBoosts &result) {
|
||||
done(true);
|
||||
|
@ -854,7 +857,8 @@ void SessionNavigation::showRepliesForMessage(
|
|||
auto memento = std::make_shared<HistoryView::RepliesMemento>(
|
||||
history,
|
||||
rootId,
|
||||
commentId);
|
||||
commentId,
|
||||
params.highlightPart);
|
||||
memento->setFromTopic(topic);
|
||||
showSection(std::move(memento), params);
|
||||
return;
|
||||
|
|
|
@ -166,6 +166,7 @@ struct SectionShow {
|
|||
return copy;
|
||||
}
|
||||
|
||||
TextWithEntities highlightPart;
|
||||
Way way = Way::Forward;
|
||||
anim::type animated = anim::type::normal;
|
||||
anim::activation activation = anim::activation::normal;
|
||||
|
@ -317,9 +318,11 @@ private:
|
|||
void replaceBoostConfirm(
|
||||
not_null<PeerData*> from,
|
||||
not_null<ChannelData*> channel,
|
||||
int slot,
|
||||
Fn<void(bool)> done);
|
||||
void applyBoostChecked(
|
||||
not_null<ChannelData*> channel,
|
||||
int slot,
|
||||
Fn<void(bool)> done);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
|
|
@ -1211,7 +1211,7 @@ stage('crashpad', """
|
|||
mac:
|
||||
git clone https://github.com/desktop-app/crashpad.git
|
||||
cd crashpad
|
||||
git checkout 171b601938
|
||||
git checkout f07f49e287
|
||||
git submodule init
|
||||
git submodule update third_party/mini_chromium
|
||||
ZLIB_PATH=$USED_PREFIX/include
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
AppVersion 4011001
|
||||
AppVersion 4011003
|
||||
AppVersionStrMajor 4.11
|
||||
AppVersionStrSmall 4.11.1
|
||||
AppVersionStr 4.11.1
|
||||
AppVersionStrSmall 4.11.3
|
||||
AppVersionStr 4.11.3
|
||||
BetaChannel 0
|
||||
AlphaVersion 0
|
||||
AppVersionOriginal 4.11.1
|
||||
AppVersionOriginal 4.11.3
|
||||
|
|
|
@ -1,3 +1,17 @@
|
|||
4.11.3 (02.11.23)
|
||||
|
||||
- Fix adding a link to media captions in scheduled / comments.
|
||||
- Fix crash in link preview options saving.
|
||||
- Fix possible crash in statistics.
|
||||
|
||||
4.11.2 (01.11.23)
|
||||
|
||||
- Highlight quoted parts in jump-to-message from replies.
|
||||
- Ctrl+Click on message field reply bar to jump to message.
|
||||
- Fix empty link preview displaying when generation failed.
|
||||
- Fix external replies in topic groups.
|
||||
- Allow enabling legacy tray icon on Windows.
|
||||
|
||||
4.11.1 (29.10.23)
|
||||
|
||||
- Fix crash in emoji status select.
|
||||
|
|
Loading…
Add table
Reference in a new issue