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:
ZavaruKitsu 2023-11-05 21:23:03 +03:00
commit ac1da83401
96 changed files with 1398 additions and 692 deletions

View file

@ -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";

View file

@ -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>

View file

@ -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"

View file

@ -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"

View file

@ -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);

View file

@ -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;

View file

@ -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));
}
};

View file

@ -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);

View file

@ -760,7 +760,7 @@ int PeerListRow::paintNameIconGetWidth(
nameWidth,
outerWidth,
{
.peer = _peer,
.peer = peer(),
.verified = &(selected
? st::dialogsVerifiedIconOver
: st::dialogsVerifiedIcon),

View file

@ -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;
}

View file

@ -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();

View file

@ -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();

View file

@ -61,6 +61,7 @@ public:
Panel(not_null<Call*> call);
~Panel();
[[nodiscard]] bool isVisible() const;
[[nodiscard]] bool isActive() const;
void showAndActivate();
void minimize();

View file

@ -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) {

View file

@ -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(

View file

@ -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;

View file

@ -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 {

View file

@ -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;

View file

@ -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;

View file

@ -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,

View file

@ -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)

View file

@ -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;
}

View file

@ -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,

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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()

View file

@ -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);

View file

@ -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();
}

View file

@ -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(

View file

@ -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

View file

@ -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;

View file

@ -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);
}

View file

@ -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;

View file

@ -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 {

View file

@ -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;

View file

@ -99,9 +99,8 @@ public:
not_null<History*> history);
~PreviewWrap();
[[nodiscard]] rpl::producer<TextWithEntities> showQuoteSelector(
not_null<HistoryItem*> item,
const TextWithEntities &quote);
[[nodiscard]] rpl::producer<SelectedQuote> showQuoteSelector(
const SelectedQuote &quote);
[[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 &quote) {
rpl::producer<SelectedQuote> PreviewWrap::showQuoteSelector(
const SelectedQuote &quote) {
_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 &quote, Section shown) {
return (quote.empty() || shown != Section::Reply)
) | rpl::map([=](const SelectedQuote &quote, 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;

View file

@ -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;
};

View file

@ -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,

View file

@ -72,7 +72,6 @@ private:
};
void ClearDraftReplyTo(not_null<Data::Thread*> thread, FullMsgId equalTo);
void ClearDraftReplyTo(
not_null<History*> history,
MsgId topicRootId,

View file

@ -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;
}

View file

@ -48,6 +48,7 @@ struct ContextMenuRequest {
SelectedItems selectedItems;
TextForMimeData selectedText;
TextWithEntities quote;
HistoryItem *quoteItem = nullptr;
bool overSelection = false;
PointState pointState = PointState();
};

View file

@ -94,6 +94,42 @@ Element *MousedElement/* = nullptr*/;
return session->tryResolveWindow();
}
[[nodiscard]] bool CheckQuoteEntities(
const EntitiesInText &quoteEntities,
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 &quote) {
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 {

View file

@ -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 &quote) const = 0;
virtual TextSelection selectionFromQuote(
const Ui::Text::String &text,
not_null<HistoryItem*> item,
const TextWithEntities &quote) 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 &quote);
[[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;

View file

@ -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 &params,
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 &params,
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()

View file

@ -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 &params,
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 &params,
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;

View file

@ -65,42 +65,6 @@ const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
return std::nullopt;
}
[[nodiscard]] bool CheckQuoteEntities(
const EntitiesInText &quoteEntities,
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 &quote) 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 &quote) 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;

View file

@ -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 &quote) const override;
TextSelection selectionFromQuote(
const Ui::Text::String &text,
not_null<HistoryItem*> item,
const TextWithEntities &quote) 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;

View file

@ -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 });
}
}

View file

@ -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 &params) {
_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);
}
}

View file

@ -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 &params);
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;

View file

@ -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));
}

View file

@ -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 &quote) const {
return {};
}
TextSelection Service::selectionFromQuote(
const Ui::Text::String &text,
not_null<HistoryItem*> item,
const TextWithEntities &quote) const {
return {};
}

View file

@ -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 &quote) const override;
TextSelection selectionFromQuote(
const Ui::Text::String &text,
not_null<HistoryItem*> item,
const TextWithEntities &quote) const override;
TextSelection adjustSelection(
TextSelection selection,

View file

@ -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 &quote) 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());
}

View file

@ -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 &quote) const override;
bool uploading() const override;

View file

@ -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;

View file

@ -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(

View file

@ -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;

View file

@ -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 &quote) const {
return parent()->selectionFromQuote(_caption, quote);
return Element::FindSelectionFromQuote(_caption, item, quote);
}
bool Gif::fullFeaturedGrouped(RectParts sides) const {

View file

@ -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 &quote) const override;
bool uploading() const override;

View file

@ -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;
}

View file

@ -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 &quote) 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,

View file

@ -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 &quote) 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 = &part;
}
@ -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 {

View file

@ -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 &quote) const override;
std::vector<Ui::BubbleSelectionInterval> getBubbleSelectionIntervals(
TextSelection selection) const override;

View file

@ -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();

View file

@ -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 &quote) const {
return parent()->selectionFromQuote(_caption, quote);
return Element::FindSelectionFromQuote(_caption, item, quote);
}
void Photo::hideSpoilers() {

View file

@ -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 &quote) const override;
PhotoData *getPhoto() const override {

View file

@ -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);
}

View file

@ -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;

View file

@ -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,

View file

@ -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,

View file

@ -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;
}

View file

@ -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);

View file

@ -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(

View file

@ -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));
}
}));
}

View file

@ -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(

View file

@ -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);

View file

@ -67,4 +67,8 @@ private:
};
inline bool HasMonochromeSetting() {
return false;
}
} // namespace Platform

View file

@ -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();
}
}
}
}

View file

@ -55,4 +55,8 @@ private:
};
inline bool HasMonochromeSetting() {
return false;
}
} // namespace Platform

View file

@ -11,6 +11,8 @@ namespace Platform {
class Tray;
[[nodiscard]] bool HasMonochromeSetting();
} // namespace Platform
// Platform dependent implementations.

View file

@ -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

View file

@ -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());
}

View file

@ -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"

View file

@ -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

View file

@ -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();

View file

@ -146,6 +146,7 @@ outReplyTextPaletteSelected: TextPalette(outTextPaletteSelected) {
}
imgReplyTextPalette: TextPalette(defaultTextPalette) {
linkFg: msgImgReplyBarColor;
monoFg: msgImgReplyBarColor;
}
inSemiboldPalette: TextPalette(inTextPalette) {
linkFg: msgInServiceFg;

View file

@ -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 {

View file

@ -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.) {

View file

@ -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(

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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.