tdesktop/Telegram/SourceFiles/history/view/controls/history_view_forward_panel.cpp
2023-10-13 10:08:29 +04:00

399 lines
10 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/controls/history_view_forward_panel.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_helpers.h"
#include "history/history_item_components.h"
#include "history/view/history_view_item_preview.h"
#include "data/data_session.h"
#include "data/data_media_types.h"
#include "data/data_forum_topic.h"
#include "main/main_session.h"
#include "ui/chat/forward_options_box.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "core/ui_integration.h"
#include "lang/lang_keys.h"
#include "window/window_peer_menu.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_chat_helpers.h"
namespace HistoryView::Controls {
namespace {
constexpr auto kUnknownVersion = -1;
constexpr auto kNameWithCaptionsVersion = -2;
constexpr auto kNameNoCaptionsVersion = -3;
[[nodiscard]] bool HasCaptions(const HistoryItemsList &list) {
for (const auto &item : list) {
if (const auto media = item->media()) {
if (!item->originalText().text.isEmpty()
&& media->allowsEditCaption()) {
return true;
}
}
}
return false;
}
[[nodiscard]] bool HasOnlyForcedForwardedInfo(const HistoryItemsList &list) {
for (const auto &item : list) {
if (const auto media = item->media()) {
if (!media->forceForwardedInfo()) {
return false;
}
} else {
return false;
}
}
return true;
}
} // namespace
ForwardPanel::ForwardPanel(Fn<void()> repaint)
: _repaint(std::move(repaint)) {
}
void ForwardPanel::update(
Data::Thread *to,
Data::ResolvedForwardDraft draft) {
if (_to == to
&& _data.items == draft.items
&& _data.options == draft.options) {
return;
}
_dataLifetime.destroy();
_data = std::move(draft);
_to = to;
if (!empty()) {
Assert(to != nullptr);
_data.items.front()->history()->owner().itemRemoved(
) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
itemRemoved(item);
}, _dataLifetime);
if (const auto topic = _to->asTopic()) {
topic->destroyed(
) | rpl::start_with_next([=] {
update(nullptr, {});
}, _dataLifetime);
}
updateTexts();
}
_itemsUpdated.fire({});
}
rpl::producer<> ForwardPanel::itemsUpdated() const {
return _itemsUpdated.events();
}
void ForwardPanel::checkTexts() {
if (empty()) {
return;
}
const auto keepNames = (_data.options
== Data::ForwardOptions::PreserveInfo);
const auto keepCaptions = (_data.options
!= Data::ForwardOptions::NoNamesAndCaptions);
auto version = keepNames
? 0
: keepCaptions
? kNameWithCaptionsVersion
: kNameNoCaptionsVersion;
if (keepNames) {
for (const auto item : _data.items) {
if (const auto from = item->senderOriginal()) {
version += from->nameVersion();
} else if (const auto info = item->hiddenSenderInfo()) {
++version;
} else {
Unexpected("Corrupt forwarded information in message.");
}
}
}
if (_nameVersion != version) {
_nameVersion = version;
updateTexts();
}
}
void ForwardPanel::updateTexts() {
const auto repainter = gsl::finally([&] {
_repaint();
});
if (empty()) {
_from.clear();
_text.clear();
return;
}
QString from;
TextWithEntities text;
const auto keepNames = (_data.options
== Data::ForwardOptions::PreserveInfo);
const auto keepCaptions = (_data.options
!= Data::ForwardOptions::NoNamesAndCaptions);
if (const auto count = int(_data.items.size())) {
auto insertedPeers = base::flat_set<not_null<PeerData*>>();
auto insertedNames = base::flat_set<QString>();
auto fullname = QString();
auto names = std::vector<QString>();
names.reserve(_data.items.size());
for (const auto item : _data.items) {
if (const auto from = item->senderOriginal()) {
if (!insertedPeers.contains(from)) {
insertedPeers.emplace(from);
names.push_back(from->shortName());
fullname = from->name();
}
} else if (const auto info = item->hiddenSenderInfo()) {
if (!insertedNames.contains(info->name)) {
insertedNames.emplace(info->name);
names.push_back(info->firstName);
fullname = info->name;
}
} else {
Unexpected("Corrupt forwarded information in message.");
}
}
if (!keepNames) {
from = tr::lng_forward_sender_names_removed(tr::now);
} else if (names.size() > 2) {
from = tr::lng_forwarding_from(
tr::now,
lt_count,
names.size() - 1,
lt_user,
names[0]);
} else if (names.size() < 2) {
from = fullname;
} else {
from = tr::lng_forwarding_from_two(
tr::now,
lt_user,
names[0],
lt_second_user,
names[1]);
}
if (count < 2) {
const auto item = _data.items.front();
text = item->toPreview({
.hideSender = true,
.hideCaption = !keepCaptions,
.generateImages = false,
.ignoreGroup = true,
}).text;
const auto history = item->history();
const auto dropCustomEmoji = !history->session().premium()
&& !_to->peer()->isSelf()
&& (item->computeDropForwardedInfo() || !keepNames);
if (dropCustomEmoji) {
text = DropCustomEmoji(std::move(text));
}
} else {
text = Ui::Text::Colorized(
tr::lng_forward_messages(tr::now, lt_count, count));
}
}
_from.setText(st::msgNameStyle, from, Ui::NameTextOptions());
const auto context = Core::MarkedTextContext{
.session = &_to->session(),
.customEmojiRepaint = _repaint,
};
_text.setMarkedText(
st::defaultTextStyle,
text,
Ui::DialogTextOptions(),
context);
}
void ForwardPanel::refreshTexts() {
_nameVersion = kUnknownVersion;
checkTexts();
}
void ForwardPanel::itemRemoved(not_null<const HistoryItem*> item) {
const auto i = ranges::find(_data.items, item);
if (i != end(_data.items)) {
_data.items.erase(i);
refreshTexts();
_itemsUpdated.fire({});
}
}
const HistoryItemsList &ForwardPanel::items() const {
return _data.items;
}
bool ForwardPanel::empty() const {
return _data.items.empty();
}
void ForwardPanel::editOptions(std::shared_ptr<ChatHelpers::Show> show) {
using Options = Data::ForwardOptions;
const auto now = _data.options;
const auto count = _data.items.size();
const auto dropNames = (now != Options::PreserveInfo);
const auto hasCaptions = HasCaptions(_data.items);
const auto hasOnlyForcedForwardedInfo = hasCaptions
? false
: HasOnlyForcedForwardedInfo(_data.items);
const auto dropCaptions = (now == Options::NoNamesAndCaptions);
const auto weak = base::make_weak(this);
const auto changeRecipient = crl::guard(this, [=] {
if (_data.items.empty()) {
return;
}
auto data = base::take(_data);
_to->owningHistory()->setForwardDraft(_to->topicRootId(), {});
Window::ShowForwardMessagesBox(show, {
.ids = _to->owner().itemsToIds(data.items),
.options = data.options,
});
});
if (hasOnlyForcedForwardedInfo) {
changeRecipient();
return;
}
const auto optionsChanged = crl::guard(weak, [=](
Ui::ForwardOptions options) {
if (_data.items.empty()) {
return;
}
const auto newOptions = (options.hasCaptions
&& options.dropCaptions)
? Options::NoNamesAndCaptions
: options.dropNames
? Options::NoSenderNames
: Options::PreserveInfo;
if (_data.options != newOptions) {
_data.options = newOptions;
_to->owningHistory()->setForwardDraft(_to->topicRootId(), {
.ids = _to->owner().itemsToIds(_data.items),
.options = newOptions,
});
_repaint();
}
});
show->showBox(Box(
Ui::ForwardOptionsBox,
count,
Ui::ForwardOptions{
.dropNames = dropNames,
.hasCaptions = hasCaptions,
.dropCaptions = dropCaptions,
},
optionsChanged,
changeRecipient));
}
void ForwardPanel::editToNextOption() {
using Options = Data::ForwardOptions;
const auto hasCaptions = HasCaptions(_data.items);
const auto hasOnlyForcedForwardedInfo = hasCaptions
? false
: HasOnlyForcedForwardedInfo(_data.items);
if (hasOnlyForcedForwardedInfo) {
return;
}
const auto now = _data.options;
const auto next = (now == Options::PreserveInfo)
? Options::NoSenderNames
: ((now == Options::NoSenderNames) && hasCaptions)
? Options::NoNamesAndCaptions
: Options::PreserveInfo;
_to->owningHistory()->setForwardDraft(_to->topicRootId(), {
.ids = _to->owner().itemsToIds(_data.items),
.options = next,
});
_repaint();
}
void ForwardPanel::paint(
Painter &p,
int x,
int y,
int available,
int outerWidth) const {
if (empty()) {
return;
}
const_cast<ForwardPanel*>(this)->checkTexts();
const auto now = crl::now();
const auto paused = p.inactive();
const auto pausedSpoiler = paused || On(PowerSaving::kChatSpoiler);
const auto firstItem = _data.items.front();
const auto firstMedia = firstItem->media();
const auto hasPreview = (_data.items.size() < 2)
&& firstMedia
&& firstMedia->hasReplyPreview();
const auto preview = hasPreview ? firstMedia->replyPreview() : nullptr;
const auto spoiler = preview && firstMedia->hasSpoiler();
if (!spoiler) {
_spoiler = nullptr;
} else if (!_spoiler) {
_spoiler = std::make_unique<Ui::SpoilerAnimation>(_repaint);
}
if (preview) {
auto to = QRect(
x,
y + st::msgReplyPadding.top(),
st::msgReplyBarSize.height(),
st::msgReplyBarSize.height());
p.drawPixmap(to.x(), to.y(), preview->pixSingle(
preview->size() / style::DevicePixelRatio(),
{
.options = Images::Option::RoundSmall,
.outer = to.size(),
}));
if (_spoiler) {
Ui::FillSpoilerRect(p, to, Ui::DefaultImageSpoiler().frame(
_spoiler->index(now, pausedSpoiler)));
}
const auto skip = st::msgReplyBarSize.height()
+ st::msgReplyBarSkip
- st::msgReplyBarSize.width()
- st::msgReplyBarPos.x();
x += skip;
available -= skip;
}
p.setPen(st::historyReplyNameFg);
_from.drawElided(
p,
x,
y + st::msgReplyPadding.top(),
available);
p.setPen(st::historyComposeAreaFg);
_text.draw(p, {
.position = QPoint(
x,
y + st::msgReplyPadding.top() + st::msgServiceNameFont->height),
.availableWidth = available,
.palette = &st::historyComposeAreaPalette,
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = now,
.pausedEmoji = paused || On(PowerSaving::kEmojiChat),
.pausedSpoiler = pausedSpoiler,
.elisionOneLine = true,
});
}
} // namespace HistoryView::Controls