Add phrases and layout for all events in log.

This commit is contained in:
John Preston 2017-06-20 19:03:18 +03:00
parent fee8690ca6
commit 4962fdf5ae
15 changed files with 454 additions and 105 deletions

View File

@ -1351,6 +1351,27 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_admin_log_deleted_message" = "{from} deleted message:";
"lng_admin_log_participant_joined" = "{from} joined the group";
"lng_admin_log_participant_left" = "{from} left the group";
"lng_admin_log_invited" = "invited {user}";
"lng_admin_log_banned" = "banned {user}";
"lng_admin_log_restricted" = "changed restrictions for {user} {until}";
"lng_admin_log_promoted" = "changed privileges for {user}";
"lng_admin_log_user_with_username" = "{name} ({mention})";
"lng_admin_log_restricted_forever" = "indefinitely";
"lng_admin_log_restricted_until" = "until {date}";
"lng_admin_log_banned_view_messages" = "Read messages";
"lng_admin_log_banned_send_messages" = "Send messages";
"lng_admin_log_banned_send_media" = "Send media";
"lng_admin_log_banned_send_stickers" = "Send stickers & GIFs";
"lng_admin_log_banned_embed_links" = "Embed links";
"lng_admin_log_admin_change_info" = "Change info";
"lng_admin_log_admin_post_messages" = "Post messages";
"lng_admin_log_admin_edit_messages" = "Edit messages";
"lng_admin_log_admin_delete_messages" = "Delete messages";
"lng_admin_log_admin_ban_users" = "Ban users";
"lng_admin_log_admin_invite_users" = "Add users";
"lng_admin_log_admin_invite_link" = "Invite users via link";
"lng_admin_log_admin_pin_messages" = "Pin messages";
"lng_admin_log_admin_add_admins" = "Add new admins";
// Not used

View File

@ -96,7 +96,6 @@ public:
EditRestrictedBox(QWidget*, gsl::not_null<ChannelData*> channel, gsl::not_null<UserData*> user, bool hasAdminRights, const MTPChannelBannedRights &rights, base::lambda<void(MTPChannelBannedRights)> callback);
static MTPChannelBannedRights DefaultRights(gsl::not_null<ChannelData*> channel);
static constexpr auto kRestrictUntilForever = TimeId(INT_MAX);
protected:
void prepare() override;
@ -109,7 +108,7 @@ private:
void showRestrictUntil();
void setRestrictUntil(int32 until);
bool isUntilForever() {
return (_until <= 0) || (_until == kRestrictUntilForever);
return ChannelData::IsRestrictedForever(_until);
}
MTPChannelBannedRights _rights;

View File

@ -142,24 +142,35 @@ QString lang(LangKey key);\n\
for (auto &entry : langpack_.entries) {
auto isPlural = !entry.keyBase.isEmpty();
auto &key = entry.key;
auto genericParams = QStringList();
auto params = QStringList();
auto applyTags = QStringList();
auto plural = QString();
auto nonPluralTagFound = false;
for (auto &tagData : entry.tags) {
auto &tag = tagData.tag;
auto isPluralTag = isPlural && (tag == kPluralTag);
genericParams.push_back("lngtag_" + tag + ", " + (isPluralTag ? "float64 " : "const ResultString &") + tag + "__val");
params.push_back("lngtag_" + tag + ", " + (isPluralTag ? "float64 " : "const QString &") + tag + "__val");
if (!isPluralTag) {
applyTags.push_back("\tresult = Lang::Tag(result, lt_" + tag + ", " + tag + "__val);\n");
if (isPluralTag) {
plural = "\tauto plural = Lang::Plural(" + key + ", " + kPluralTag + "__val);\n";
applyTags.push_back("\tresult = Lang::ReplaceTag<ResultString>::Call(std::move(result), lt_" + tag + ", Lang::StartReplacements<ResultString>::Call(std::move(plural.replacement)));\n");
} else {
nonPluralTagFound = true;
applyTags.push_back("\tresult = Lang::ReplaceTag<ResultString>::Call(std::move(result), lt_" + tag + ", " + tag + "__val);\n");
}
}
if (!entry.tags.empty() && (!isPlural || key == ComputePluralKey(entry.keyBase, 0))) {
auto initialString = isPlural ? ("Lang::Plural(" + key + ", lt_" + kPluralTag + ", " + kPluralTag + "__val)") : ("lang(" + getFullKey(entry) + ")");
auto initialString = isPlural ? ("std::move(plural.string)") : ("lang(" + getFullKey(entry) + ")");
header_->stream() << "\
inline QString " << (isPlural ? entry.keyBase : key) << "(" << params.join(QString(", ")) << ") {\n\
auto result = " << initialString << ";\n\
template <typename ResultString>\n\
inline ResultString " << (isPlural ? entry.keyBase : key) << "__generic(" << genericParams.join(QString(", ")) << ") {\n\
" << plural << "\
auto result = Lang::StartReplacements<ResultString>::Call(" << initialString << ");\n\
" << applyTags.join(QString()) << "\
return result;\n\
}\n\
constexpr auto " << (isPlural ? entry.keyBase : key) << " = &" << (isPlural ? entry.keyBase : key) << "__generic<QString>;\n\
\n";
}
}

View File

@ -26,6 +26,19 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace AdminLog {
namespace {
TextWithEntities PrepareText(const QString &value, const QString &emptyValue) {
auto result = TextWithEntities { textClean(value) };
if (result.text.isEmpty()) {
result.text = emptyValue;
if (!emptyValue.isEmpty()) {
result.entities.push_back(EntityInText(EntityInTextItalic, 0, emptyValue.size()));
}
} else {
textParseEntities(result.text, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands, &result.entities);
}
return result;
}
MTPMessage PrepareLogMessage(const MTPMessage &message, MsgId newId, int32 newDate) {
switch (message.type()) {
case mtpc_messageEmpty: return MTP_messageEmpty(MTP_int(newId));
@ -43,7 +56,7 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, MsgId newId, int32 newDa
Unexpected("Type in PrepareLogMessage()");
}
bool MessageHasCaption(const MTPMessage &message) {
bool MediaCanHaveCaption(const MTPMessage &message) {
if (message.type() != mtpc_message) {
return false;
}
@ -52,6 +65,172 @@ bool MessageHasCaption(const MTPMessage &message) {
return (mediaType == mtpc_messageMediaDocument || mediaType == mtpc_messageMediaPhoto);
}
TextWithEntities ExtractEditedText(const MTPMessage &message) {
if (message.type() != mtpc_message) {
return TextWithEntities();
}
auto &data = message.c_message();
auto mediaType = data.has_media() ? data.vmedia.type() : mtpc_messageMediaEmpty;
if (mediaType == mtpc_messageMediaDocument) {
return PrepareText(qs(data.vmedia.c_messageMediaDocument().vcaption), QString());
} else if (mediaType == mtpc_messageMediaPhoto) {
return PrepareText(qs(data.vmedia.c_messageMediaPhoto().vcaption), QString());
}
auto text = textClean(qs(data.vmessage));
auto entities = data.has_entities() ? entitiesFromMTP(data.ventities.v) : EntitiesInText();
return { text, entities };
}
PhotoData *GenerateChatPhoto(ChannelId channelId, uint64 logEntryId, MTPint date, const MTPDchatPhoto &photo) {
// We try to make a unique photoId that will stay the same for each pair (channelId, logEntryId).
static const auto RandomIdPart = rand_value<uint64>();
auto mixinIdPart = (static_cast<uint64>(static_cast<uint32>(channelId)) << 32) ^ logEntryId;
auto photoId = RandomIdPart ^ mixinIdPart;
auto photoSizes = QVector<MTPPhotoSize>();
photoSizes.reserve(2);
photoSizes.push_back(MTP_photoSize(MTP_string("a"), photo.vphoto_small, MTP_int(160), MTP_int(160), MTP_int(0)));
photoSizes.push_back(MTP_photoSize(MTP_string("c"), photo.vphoto_big, MTP_int(640), MTP_int(640), MTP_int(0)));
return App::feedPhoto(MTP_photo(MTP_flags(0), MTP_long(photoId), MTP_long(0), date, MTP_vector<MTPPhotoSize>(photoSizes)));
}
const auto CollectChanges = [](auto &phraseMap, auto plusFlags, auto minusFlags) {
auto withPrefix = [&phraseMap](auto flags, QChar prefix) {
auto result = QString();
for (auto &phrase : phraseMap) {
if (flags & phrase.first) {
result.append('\n' + (prefix + lang(phrase.second)));
}
}
return result;
};
const auto kMinus = QChar(0x2212);
return withPrefix(plusFlags & ~minusFlags, '+') + withPrefix(minusFlags & ~plusFlags, kMinus);
};
auto GenerateAdminChangeText(gsl::not_null<ChannelData*> channel, const TextWithEntities &user, const MTPChannelAdminRights *newRights, const MTPChannelAdminRights *prevRights) {
using Flag = MTPDchannelAdminRights::Flag;
using Flags = MTPDchannelAdminRights::Flags;
Expects(!newRights || newRights->type() == mtpc_channelAdminRights);
Expects(!prevRights || prevRights->type() == mtpc_channelAdminRights);
auto newFlags = newRights ? newRights->c_channelAdminRights().vflags.v : 0;
auto prevFlags = prevRights ? prevRights->c_channelAdminRights().vflags.v : 0;
auto result = lng_admin_log_promoted__generic(lt_user, user);
auto inviteKey = Flag::f_invite_users | Flag::f_invite_link;
auto useInviteLinkPhrase = channel->isMegagroup() && channel->anyoneCanAddMembers();
auto invitePhrase = (useInviteLinkPhrase ? lng_admin_log_admin_invite_link : lng_admin_log_admin_invite_users);
static auto phraseMap = std::map<Flags, LangKey> {
{ Flag::f_change_info, lng_admin_log_admin_change_info },
{ Flag::f_post_messages, lng_admin_log_admin_post_messages },
{ Flag::f_edit_messages, lng_admin_log_admin_edit_messages },
{ Flag::f_delete_messages, lng_admin_log_admin_delete_messages },
{ Flag::f_ban_users, lng_admin_log_admin_ban_users },
{ inviteKey, invitePhrase },
{ Flag::f_pin_messages, lng_admin_log_admin_pin_messages },
{ Flag::f_add_admins, lng_admin_log_admin_add_admins },
};
phraseMap[inviteKey] = invitePhrase;
auto changes = CollectChanges(phraseMap, newFlags, prevFlags);
if (!changes.isEmpty()) {
result.text.append('\n' + changes);
}
return result;
};
auto GenerateBannedChangeText(const TextWithEntities &user, const MTPChannelBannedRights *newRights, const MTPChannelBannedRights *prevRights) {
using Flag = MTPDchannelBannedRights::Flag;
using Flags = MTPDchannelBannedRights::Flags;
Expects(!newRights || newRights->type() == mtpc_channelBannedRights);
Expects(!prevRights || prevRights->type() == mtpc_channelBannedRights);
auto newFlags = newRights ? newRights->c_channelBannedRights().vflags.v : 0;
auto prevFlags = prevRights ? prevRights->c_channelBannedRights().vflags.v : 0;
auto newUntil = newRights ? newRights->c_channelBannedRights().vuntil_date : MTP_int(0);
auto indefinitely = ChannelData::IsRestrictedForever(newUntil.v);
if (newFlags & Flag::f_view_messages) {
return lng_admin_log_banned__generic(lt_user, user);
}
auto untilText = indefinitely ? lang(lng_admin_log_restricted_forever) : lng_admin_log_restricted_until(lt_date, langDateTime(::date(newUntil)));
auto result = lng_admin_log_restricted__generic(lt_user, user, lt_until, TextWithEntities { untilText });
static auto phraseMap = std::map<Flags, LangKey> {
{ Flag::f_view_messages, lng_admin_log_banned_view_messages },
{ Flag::f_send_messages, lng_admin_log_banned_send_messages },
{ Flag::f_send_media, lng_admin_log_banned_send_media },
{ Flag::f_send_stickers | Flag::f_send_gifs | Flag::f_send_inline | Flag::f_send_games, lng_admin_log_banned_send_stickers },
{ Flag::f_embed_links, lng_admin_log_banned_embed_links },
};
auto changes = CollectChanges(phraseMap, prevFlags, newFlags);
if (!changes.isEmpty()) {
result.text.append('\n' + changes);
}
return result;
};
auto GenerateUserString(MTPint userId) {
// User name in "User name (@username)" format with entities.
auto user = App::user(userId.v);
auto name = TextWithEntities { App::peerName(user) };
name.entities.push_back(EntityInText(EntityInTextMentionName, 0, name.text.size(), QString::number(user->id) + '.' + QString::number(user->access)));
auto username = user->userName();
if (username.isEmpty()) {
return name;
}
auto mention = TextWithEntities { '@' + username };
mention.entities.push_back(EntityInText(EntityInTextMention, 0, mention.text.size()));
return lng_admin_log_user_with_username__generic(lt_name, name, lt_mention, mention);
}
auto GenerateParticipantChangeTextInner(gsl::not_null<ChannelData*> channel, const MTPChannelParticipant &participant, const MTPChannelParticipant *oldParticipant) {
auto oldType = oldParticipant ? oldParticipant->type() : 0;
auto resultForParticipant = [channel, oldParticipant, oldType](auto &&data) {
auto user = GenerateUserString(data.vuser_id);
if (oldType == mtpc_channelParticipantAdmin) {
return GenerateAdminChangeText(channel, user, nullptr, &oldParticipant->c_channelParticipantAdmin().vadmin_rights);
} else if (oldType == mtpc_channelParticipantBanned) {
return GenerateBannedChangeText(user, nullptr, &oldParticipant->c_channelParticipantBanned().vbanned_rights);
}
return lng_admin_log_invited__generic(lt_user, user);
};
switch (participant.type()) {
case mtpc_channelParticipantCreator: {
// No valid string here :(
auto &data = participant.c_channelParticipantCreator();
return lng_admin_log_invited__generic(lt_user, GenerateUserString(data.vuser_id));
} break;
case mtpc_channelParticipant: return resultForParticipant(participant.c_channelParticipant());
case mtpc_channelParticipantSelf: return resultForParticipant(participant.c_channelParticipantSelf());
case mtpc_channelParticipantAdmin: {
auto &data = participant.c_channelParticipantAdmin();
auto user = GenerateUserString(data.vuser_id);
return GenerateAdminChangeText(channel, user, &data.vadmin_rights, (oldType == mtpc_channelParticipantAdmin) ? &oldParticipant->c_channelParticipantAdmin().vadmin_rights : nullptr);
} break;
case mtpc_channelParticipantBanned: {
auto &data = participant.c_channelParticipantBanned();
auto user = GenerateUserString(data.vuser_id);
return GenerateBannedChangeText(user, &data.vbanned_rights, (oldType == mtpc_channelParticipantBanned) ? &oldParticipant->c_channelParticipantBanned().vbanned_rights : nullptr);
} break;
}
Unexpected("Participant type in GenerateParticipantChangeTextInner()");
}
TextWithEntities GenerateParticipantChangeText(gsl::not_null<ChannelData*> channel, const MTPChannelParticipant &participant, const MTPChannelParticipant *oldParticipant = nullptr) {
auto result = GenerateParticipantChangeTextInner(channel, participant, oldParticipant);
result.entities.push_front(EntityInText(EntityInTextItalic, 0, result.text.size()));
return result;
}
} // namespace
Item::Item(gsl::not_null<History*> history, LocalIdManager &idManager, const MTPDchannelAdminLogEvent &event)
@ -67,10 +246,10 @@ Item::Item(gsl::not_null<History*> history, LocalIdManager &idManager, const MTP
auto fromLink = _from->openLink();
auto fromLinkText = textcmdLink(1, fromName);
auto addSimpleServiceMessage = [this, &idManager, date, fromLink](const QString &text) {
auto addSimpleServiceMessage = [this, &idManager, date, fromLink](const QString &text, PhotoData *photo = nullptr) {
auto message = HistoryService::PreparedText { text };
message.links.push_back(fromLink);
addPart(HistoryService::create(_history, idManager.next(), ::date(date), message, 0, peerToUser(_from->id)));
addPart(HistoryService::create(_history, idManager.next(), ::date(date), message, 0, peerToUser(_from->id), photo));
};
auto createChangeTitle = [this, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionChangeTitle &action) {
@ -120,9 +299,12 @@ Item::Item(gsl::not_null<History*> history, LocalIdManager &idManager, const MTP
addPart(body);
};
auto createChangePhoto = [this, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionChangePhoto &action) {
auto createChangePhoto = [this, date, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionChangePhoto &action) {
t_assert(action.vnew_photo.type() == mtpc_chatPhoto);
auto photo = GenerateChatPhoto(channel()->bareId(), _id, date, action.vnew_photo.c_chatPhoto());
auto text = (channel()->isMegagroup() ? lng_admin_log_changed_photo_group : lng_admin_log_changed_photo_channel)(lt_from, fromLinkText);
addSimpleServiceMessage(text);
addSimpleServiceMessage(text, photo);
};
auto createToggleInvites = [this, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionToggleInvites &action) {
@ -147,12 +329,22 @@ Item::Item(gsl::not_null<History*> history, LocalIdManager &idManager, const MTP
};
auto createEditMessage = [this, &idManager, date, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionEditMessage &action) {
auto text = (MessageHasCaption(action.vnew_message) ? lng_admin_log_edited_caption : lng_admin_log_edited_message)(lt_from, fromLinkText);
auto newValue = ExtractEditedText(action.vnew_message);
auto canHaveCaption = MediaCanHaveCaption(action.vnew_message);
auto text = (canHaveCaption
? (newValue.text.isEmpty() ? lng_admin_log_removed_caption : lng_admin_log_edited_caption)
: lng_admin_log_edited_message
)(lt_from, fromLinkText);
addSimpleServiceMessage(text);
auto oldValue = ExtractEditedText(action.vprev_message);
auto applyServiceAction = false;
auto detachExistingItem = false;
addPart(_history->createItem(PrepareLogMessage(action.vnew_message, idManager.next(), date.v), applyServiceAction, detachExistingItem));
auto body = _history->createItem(PrepareLogMessage(action.vnew_message, idManager.next(), date.v), applyServiceAction, detachExistingItem);
if (!oldValue.text.isEmpty()) {
body->addLogEntryOriginal(lang(canHaveCaption ? lng_admin_log_previous_caption : lng_admin_log_previous_description), oldValue);
}
addPart(body);
};
auto createDeleteMessage = [this, &idManager, date, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionDeleteMessage &action) {
@ -178,21 +370,24 @@ Item::Item(gsl::not_null<History*> history, LocalIdManager &idManager, const MTP
auto bodyFlags = Flag::f_entities | Flag::f_from_id;
auto bodyReplyTo = 0;
auto bodyViaBotId = 0;
addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), { "participant invite text", EntitiesInText() }));
auto bodyText = GenerateParticipantChangeText(channel(), action.vparticipant);
addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), bodyText));
};
auto createParticipantToggleBan = [this, &idManager, date](const MTPDchannelAdminLogEventActionParticipantToggleBan &action) {
auto bodyFlags = Flag::f_entities | Flag::f_from_id;
auto bodyReplyTo = 0;
auto bodyViaBotId = 0;
addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), { "participant toggle ban text", EntitiesInText() }));
auto bodyText = GenerateParticipantChangeText(channel(), action.vnew_participant, &action.vprev_participant);
addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), bodyText));
};
auto createParticipantToggleAdmin = [this, &idManager, date](const MTPDchannelAdminLogEventActionParticipantToggleAdmin &action) {
auto bodyFlags = Flag::f_entities | Flag::f_from_id;
auto bodyReplyTo = 0;
auto bodyViaBotId = 0;
addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), { "participant toggle admin text", EntitiesInText() }));
auto bodyText = GenerateParticipantChangeText(channel(), action.vnew_participant, &action.vprev_participant);
addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), bodyText));
};
switch (action.type()) {
@ -263,19 +458,6 @@ HistoryTextState Item::getState(QPoint point, HistoryStateRequest request) const
return HistoryTextState();
}
TextWithEntities Item::PrepareText(const QString &value, const QString &emptyValue) {
auto result = TextWithEntities { textClean(value) };
if (result.text.isEmpty()) {
result.text = emptyValue;
if (!emptyValue.isEmpty()) {
result.entities.push_back(EntityInText(EntityInTextItalic, 0, emptyValue.size()));
}
} else {
textParseEntities(result.text, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands, &result.entities);
}
return result;
}
Item::~Item() {
for (auto part : _parts) {
part->destroy();

View File

@ -54,8 +54,6 @@ private:
}
void addPart(HistoryItem *item);
static TextWithEntities PrepareText(const QString &value, const QString &emptyValue);
uint64 _id = 0;
gsl::not_null<History*> _history;
gsl::not_null<UserData*> _from;

View File

@ -222,7 +222,7 @@ void HistoryFileMedia::checkAnimationFinished() const {
HistoryFileMedia::~HistoryFileMedia() = default;
HistoryPhoto::HistoryPhoto(HistoryItem *parent, PhotoData *photo, const QString &caption) : HistoryFileMedia(parent)
HistoryPhoto::HistoryPhoto(gsl::not_null<HistoryItem*> parent, gsl::not_null<PhotoData*> photo, const QString &caption) : HistoryFileMedia(parent)
, _data(photo)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
setLinks(MakeShared<PhotoOpenClickHandler>(_data), MakeShared<PhotoSaveClickHandler>(_data), MakeShared<PhotoCancelClickHandler>(_data));
@ -232,15 +232,18 @@ HistoryPhoto::HistoryPhoto(HistoryItem *parent, PhotoData *photo, const QString
init();
}
HistoryPhoto::HistoryPhoto(HistoryItem *parent, PeerData *chat, const MTPDphoto &photo, int32 width) : HistoryFileMedia(parent)
, _data(App::feedPhoto(photo)) {
HistoryPhoto::HistoryPhoto(gsl::not_null<HistoryItem*> parent, gsl::not_null<PeerData*> chat, gsl::not_null<PhotoData*> photo, int32 width) : HistoryFileMedia(parent)
, _data(photo) {
setLinks(MakeShared<PhotoOpenClickHandler>(_data, chat), MakeShared<PhotoSaveClickHandler>(_data, chat), MakeShared<PhotoCancelClickHandler>(_data, chat));
_width = width;
init();
}
HistoryPhoto::HistoryPhoto(HistoryItem *parent, const HistoryPhoto &other) : HistoryFileMedia(parent)
HistoryPhoto::HistoryPhoto(gsl::not_null<HistoryItem*> parent, gsl::not_null<PeerData*> chat, const MTPDphoto &photo, int32 width) : HistoryPhoto(parent, chat, App::feedPhoto(photo), width) {
}
HistoryPhoto::HistoryPhoto(gsl::not_null<HistoryItem*> parent, const HistoryPhoto &other) : HistoryFileMedia(parent)
, _data(other._data)
, _pixw(other._pixw)
, _pixh(other._pixh)
@ -559,7 +562,7 @@ void HistoryPhoto::updateSentMedia(const MTPMessageMedia &media) {
bool HistoryPhoto::needReSetInlineResultMedia(const MTPMessageMedia &media) {
if (media.type() == mtpc_messageMediaPhoto) {
if (PhotoData *existing = App::feedPhoto(media.c_messageMediaPhoto().vphoto)) {
if (auto existing = App::feedPhoto(media.c_messageMediaPhoto().vphoto)) {
if (existing == _data) {
return false;
} else {
@ -612,7 +615,7 @@ ImagePtr HistoryPhoto::replyPreview() {
return _data->makeReplyPreview();
}
HistoryVideo::HistoryVideo(HistoryItem *parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent)
HistoryVideo::HistoryVideo(gsl::not_null<HistoryItem*> parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent)
, _data(document)
, _thumbw(1)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
@ -627,7 +630,7 @@ HistoryVideo::HistoryVideo(HistoryItem *parent, DocumentData *document, const QS
_data->thumb->load();
}
HistoryVideo::HistoryVideo(HistoryItem *parent, const HistoryVideo &other) : HistoryFileMedia(parent)
HistoryVideo::HistoryVideo(gsl::not_null<HistoryItem*> parent, const HistoryVideo &other) : HistoryFileMedia(parent)
, _data(other._data)
, _thumbw(other._thumbw)
, _caption(other._caption) {
@ -976,7 +979,7 @@ void HistoryDocumentVoice::stopSeeking() {
Media::Player::instance()->stopSeeking(AudioMsgId::Type::Voice);
}
HistoryDocument::HistoryDocument(HistoryItem *parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent)
HistoryDocument::HistoryDocument(gsl::not_null<HistoryItem*> parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent)
, _data(document) {
createComponents(!caption.isEmpty());
if (auto named = Get<HistoryDocumentNamed>()) {
@ -992,7 +995,7 @@ HistoryDocument::HistoryDocument(HistoryItem *parent, DocumentData *document, co
}
}
HistoryDocument::HistoryDocument(HistoryItem *parent, const HistoryDocument &other) : HistoryFileMedia(parent)
HistoryDocument::HistoryDocument(gsl::not_null<HistoryItem*> parent, const HistoryDocument &other) : HistoryFileMedia(parent)
, RuntimeComposer()
, _data(other._data) {
auto captioned = other.Get<HistoryDocumentCaptioned>();
@ -1715,7 +1718,7 @@ ImagePtr HistoryDocument::replyPreview() {
return _data->makeReplyPreview();
}
HistoryGif::HistoryGif(HistoryItem *parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent)
HistoryGif::HistoryGif(gsl::not_null<HistoryItem*> parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent)
, _data(document)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
setDocumentLinks(_data, true);
@ -1730,7 +1733,7 @@ HistoryGif::HistoryGif(HistoryItem *parent, DocumentData *document, const QStrin
_data->thumb->load();
}
HistoryGif::HistoryGif(HistoryItem *parent, const HistoryGif &other) : HistoryFileMedia(parent)
HistoryGif::HistoryGif(gsl::not_null<HistoryItem*> parent, const HistoryGif &other) : HistoryFileMedia(parent)
, _data(other._data)
, _thumbw(other._thumbw)
, _thumbh(other._thumbh)
@ -2488,7 +2491,7 @@ bool HistoryGif::dataLoaded() const {
return (!_parent || _parent->id > 0) ? _data->loaded() : false;
}
HistorySticker::HistorySticker(HistoryItem *parent, DocumentData *document) : HistoryMedia(parent)
HistorySticker::HistorySticker(gsl::not_null<HistoryItem*> parent, DocumentData *document) : HistoryMedia(parent)
, _data(document)
, _emoji(_data->sticker()->alt) {
_data->thumb->load();
@ -2770,7 +2773,7 @@ ClickHandlerPtr addContactClickHandler(HistoryItem *item) {
} // namespace
HistoryContact::HistoryContact(HistoryItem *parent, int32 userId, const QString &first, const QString &last, const QString &phone) : HistoryMedia(parent)
HistoryContact::HistoryContact(gsl::not_null<HistoryItem*> parent, int32 userId, const QString &first, const QString &last, const QString &phone) : HistoryMedia(parent)
, _userId(userId)
, _fname(first)
, _lname(last)
@ -2936,7 +2939,7 @@ void HistoryContact::updateSentMedia(const MTPMessageMedia &media) {
}
}
HistoryCall::HistoryCall(HistoryItem *parent, const MTPDmessageActionPhoneCall &call) : HistoryMedia(parent)
HistoryCall::HistoryCall(gsl::not_null<HistoryItem*> parent, const MTPDmessageActionPhoneCall &call) : HistoryMedia(parent)
, _reason(GetReason(call)) {
if (_parent->out()) {
_text = lang(_reason == FinishReason::Missed ? lng_call_cancelled : lng_call_outgoing);
@ -3087,13 +3090,13 @@ int unitedLineHeight() {
} // namespace
HistoryWebPage::HistoryWebPage(HistoryItem *parent, WebPageData *data) : HistoryMedia(parent)
HistoryWebPage::HistoryWebPage(gsl::not_null<HistoryItem*> parent, WebPageData *data) : HistoryMedia(parent)
, _data(data)
, _title(st::msgMinWidth - st::webPageLeft)
, _description(st::msgMinWidth - st::webPageLeft) {
}
HistoryWebPage::HistoryWebPage(HistoryItem *parent, const HistoryWebPage &other) : HistoryMedia(parent)
HistoryWebPage::HistoryWebPage(gsl::not_null<HistoryItem*> parent, const HistoryWebPage &other) : HistoryMedia(parent)
, _data(other._data)
, _attach(other._attach ? other._attach->clone(parent) : nullptr)
, _asArticle(other._asArticle)
@ -3613,13 +3616,13 @@ int HistoryWebPage::bottomInfoPadding() const {
return result;
}
HistoryGame::HistoryGame(HistoryItem *parent, GameData *data) : HistoryMedia(parent)
HistoryGame::HistoryGame(gsl::not_null<HistoryItem*> parent, GameData *data) : HistoryMedia(parent)
, _data(data)
, _title(st::msgMinWidth - st::webPageLeft)
, _description(st::msgMinWidth - st::webPageLeft) {
}
HistoryGame::HistoryGame(HistoryItem *parent, const HistoryGame &other) : HistoryMedia(parent)
HistoryGame::HistoryGame(gsl::not_null<HistoryItem*> parent, const HistoryGame &other) : HistoryMedia(parent)
, _data(other._data)
, _attach(other._attach ? other._attach->clone(parent) : nullptr)
, _title(other._title)
@ -3992,14 +3995,14 @@ int HistoryGame::bottomInfoPadding() const {
return result;
}
HistoryInvoice::HistoryInvoice(HistoryItem *parent, const MTPDmessageMediaInvoice &data) : HistoryMedia(parent)
HistoryInvoice::HistoryInvoice(gsl::not_null<HistoryItem*> parent, const MTPDmessageMediaInvoice &data) : HistoryMedia(parent)
, _title(st::msgMinWidth)
, _description(st::msgMinWidth)
, _status(st::msgMinWidth) {
fillFromData(data);
}
HistoryInvoice::HistoryInvoice(HistoryItem *parent, const HistoryInvoice &other) : HistoryMedia(parent)
HistoryInvoice::HistoryInvoice(gsl::not_null<HistoryItem*> parent, const HistoryInvoice &other) : HistoryMedia(parent)
, _attach(other._attach ? other._attach->clone(parent) : nullptr)
, _titleHeight(other._titleHeight)
, _descriptionHeight(other._descriptionHeight)
@ -4371,7 +4374,7 @@ int HistoryInvoice::bottomInfoPadding() const {
return result;
}
HistoryLocation::HistoryLocation(HistoryItem *parent, const LocationCoords &coords, const QString &title, const QString &description) : HistoryMedia(parent)
HistoryLocation::HistoryLocation(gsl::not_null<HistoryItem*> parent, const LocationCoords &coords, const QString &title, const QString &description) : HistoryMedia(parent)
, _data(App::location(coords))
, _title(st::msgMinWidth)
, _description(st::msgMinWidth)
@ -4384,7 +4387,7 @@ HistoryLocation::HistoryLocation(HistoryItem *parent, const LocationCoords &coor
}
}
HistoryLocation::HistoryLocation(HistoryItem *parent, const HistoryLocation &other) : HistoryMedia(parent)
HistoryLocation::HistoryLocation(gsl::not_null<HistoryItem*> parent, const HistoryLocation &other) : HistoryMedia(parent)
, _data(other._data)
, _title(other._title)
, _description(other._description)

View File

@ -116,9 +116,10 @@ protected:
class HistoryPhoto : public HistoryFileMedia {
public:
HistoryPhoto(HistoryItem *parent, PhotoData *photo, const QString &caption);
HistoryPhoto(HistoryItem *parent, PeerData *chat, const MTPDphoto &photo, int width);
HistoryPhoto(HistoryItem *parent, const HistoryPhoto &other);
HistoryPhoto(gsl::not_null<HistoryItem*> parent, gsl::not_null<PhotoData*> photo, const QString &caption);
HistoryPhoto(gsl::not_null<HistoryItem*> parent, gsl::not_null<PeerData*> chat, gsl::not_null<PhotoData*> photo, int width);
HistoryPhoto(gsl::not_null<HistoryItem*> parent, gsl::not_null<PeerData*> chat, const MTPDphoto &photo, int width);
HistoryPhoto(gsl::not_null<HistoryItem*> parent, const HistoryPhoto &other);
void init();
HistoryMediaType type() const override {
@ -212,8 +213,8 @@ private:
class HistoryVideo : public HistoryFileMedia {
public:
HistoryVideo(HistoryItem *parent, DocumentData *document, const QString &caption);
HistoryVideo(HistoryItem *parent, const HistoryVideo &other);
HistoryVideo(gsl::not_null<HistoryItem*> parent, DocumentData *document, const QString &caption);
HistoryVideo(gsl::not_null<HistoryItem*> parent, const HistoryVideo &other);
HistoryMediaType type() const override {
return MediaTypeVideo;
}
@ -370,8 +371,8 @@ private:
class HistoryDocument : public HistoryFileMedia, public RuntimeComposer {
public:
HistoryDocument(HistoryItem *parent, DocumentData *document, const QString &caption);
HistoryDocument(HistoryItem *parent, const HistoryDocument &other);
HistoryDocument(gsl::not_null<HistoryItem*> parent, DocumentData *document, const QString &caption);
HistoryDocument(gsl::not_null<HistoryItem*> parent, const HistoryDocument &other);
HistoryMediaType type() const override {
return _data->voice() ? MediaTypeVoiceFile : (_data->song() ? MediaTypeMusicFile : MediaTypeFile);
}
@ -477,8 +478,8 @@ private:
class HistoryGif : public HistoryFileMedia {
public:
HistoryGif(HistoryItem *parent, DocumentData *document, const QString &caption);
HistoryGif(HistoryItem *parent, const HistoryGif &other);
HistoryGif(gsl::not_null<HistoryItem*> parent, DocumentData *document, const QString &caption);
HistoryGif(gsl::not_null<HistoryItem*> parent, const HistoryGif &other);
HistoryMediaType type() const override {
return MediaTypeGif;
}
@ -601,7 +602,7 @@ private:
class HistorySticker : public HistoryMedia {
public:
HistorySticker(HistoryItem *parent, DocumentData *document);
HistorySticker(gsl::not_null<HistoryItem*> parent, DocumentData *document);
HistoryMediaType type() const override {
return MediaTypeSticker;
}
@ -670,7 +671,7 @@ private:
class HistoryContact : public HistoryMedia {
public:
HistoryContact(HistoryItem *parent, int32 userId, const QString &first, const QString &last, const QString &phone);
HistoryContact(gsl::not_null<HistoryItem*> parent, int32 userId, const QString &first, const QString &last, const QString &phone);
HistoryMediaType type() const override {
return MediaTypeContact;
}
@ -732,7 +733,7 @@ private:
class HistoryCall : public HistoryMedia {
public:
HistoryCall(HistoryItem *parent, const MTPDmessageActionPhoneCall &call);
HistoryCall(gsl::not_null<HistoryItem*> parent, const MTPDmessageActionPhoneCall &call);
HistoryMediaType type() const override {
return MediaTypeCall;
}
@ -787,8 +788,8 @@ private:
class HistoryWebPage : public HistoryMedia {
public:
HistoryWebPage(HistoryItem *parent, WebPageData *data);
HistoryWebPage(HistoryItem *parent, const HistoryWebPage &other);
HistoryWebPage(gsl::not_null<HistoryItem*> parent, WebPageData *data);
HistoryWebPage(gsl::not_null<HistoryItem*> parent, const HistoryWebPage &other);
HistoryMediaType type() const override {
return MediaTypeWebPage;
}
@ -886,8 +887,8 @@ private:
class HistoryGame : public HistoryMedia {
public:
HistoryGame(HistoryItem *parent, GameData *data);
HistoryGame(HistoryItem *parent, const HistoryGame &other);
HistoryGame(gsl::not_null<HistoryItem*> parent, GameData *data);
HistoryGame(gsl::not_null<HistoryItem*> parent, const HistoryGame &other);
HistoryMediaType type() const override {
return MediaTypeGame;
}
@ -986,8 +987,8 @@ private:
class HistoryInvoice : public HistoryMedia {
public:
HistoryInvoice(HistoryItem *parent, const MTPDmessageMediaInvoice &data);
HistoryInvoice(HistoryItem *parent, const HistoryInvoice &other);
HistoryInvoice(gsl::not_null<HistoryItem*> parent, const MTPDmessageMediaInvoice &data);
HistoryInvoice(gsl::not_null<HistoryItem*> parent, const HistoryInvoice &other);
HistoryMediaType type() const override {
return MediaTypeInvoice;
}
@ -1077,8 +1078,8 @@ struct LocationData;
class HistoryLocation : public HistoryMedia {
public:
HistoryLocation(HistoryItem *parent, const LocationCoords &coords, const QString &title = QString(), const QString &description = QString());
HistoryLocation(HistoryItem *parent, const HistoryLocation &other);
HistoryLocation(gsl::not_null<HistoryItem*> parent, const LocationCoords &coords, const QString &title = QString(), const QString &description = QString());
HistoryLocation(gsl::not_null<HistoryItem*> parent, const HistoryLocation &other);
HistoryMediaType type() const override {
return MediaTypeLocation;
}

View File

@ -425,11 +425,9 @@ HistoryMessage::HistoryMessage(History *history, const MTPDmessage &msg)
initMedia(msg.has_media() ? (&msg.vmedia) : nullptr);
TextWithEntities textWithEntities = {
textClean(qs(msg.vmessage)),
msg.has_entities() ? entitiesFromMTP(msg.ventities.v) : EntitiesInText(),
};
setText(textWithEntities);
auto text = textClean(qs(msg.vmessage));
auto entities = msg.has_entities() ? entitiesFromMTP(msg.ventities.v) : EntitiesInText();
setText({ text, entities });
}
HistoryMessage::HistoryMessage(History *history, const MTPDmessageService &msg)
@ -2214,9 +2212,12 @@ HistoryService::HistoryService(History *history, const MTPDmessageService &messa
setMessageByAction(message.vaction);
}
HistoryService::HistoryService(History *history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags, int32 from) :
HistoryService::HistoryService(History *history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags, int32 from, PhotoData *photo) :
HistoryItem(history, msgId, flags, date, from) {
setServiceText(message);
if (photo) {
_media = std::make_unique<HistoryPhoto>(this, history->peer, photo, st::msgServicePhotoWidth);
}
}
void HistoryService::initDimensions() {

View File

@ -281,8 +281,8 @@ public:
static HistoryService *create(History *history, const MTPDmessageService &message) {
return _create(history, message);
}
static HistoryService *create(History *history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, int32 from = 0) {
return _create(history, msgId, date, message, flags, from);
static HistoryService *create(History *history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, UserId from = 0, PhotoData *photo = nullptr) {
return _create(history, msgId, date, message, flags, from, photo);
}
bool updateDependencyItem() override;
@ -339,7 +339,7 @@ protected:
friend class HistoryLayout::ServiceMessagePainter;
HistoryService(History *history, const MTPDmessageService &message);
HistoryService(History *history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, int32 from = 0);
HistoryService(History *history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, UserId from = 0, PhotoData *photo = 0);
friend class HistoryItemInstantiated<HistoryService>;
void initDimensions() override;

View File

@ -129,19 +129,14 @@ ChoosePluralMethod ChoosePlural = ChoosePluralEn;
} // namespace
QString Tag(const QString &original, ushort tag, const QString &replacement) {
int FindTagReplacementPosition(const QString &original, ushort tag) {
for (auto s = original.constData(), ch = s, e = ch + original.size(); ch != e;) {
if (*ch == TextCommand) {
if (ch + 3 < e && (ch + 1)->unicode() == TextCommandLangTag && *(ch + 3) == TextCommand) {
if (ch + kTagReplacementSize <= e && (ch + 1)->unicode() == TextCommandLangTag && *(ch + 3) == TextCommand) {
if ((ch + 2)->unicode() == 0x0020 + tag) {
auto result = QString();
result.reserve(original.size() + replacement.size() - 4);
if (ch > s) result.append(original.midRef(0, ch - s));
result.append(replacement);
if (ch + 4 < e) result.append(original.midRef(ch - s + 4));
return result;
return ch - s;
} else {
ch += 4;
ch += kTagReplacementSize;
}
} else {
auto next = textSkipCommand(ch, e);
@ -155,10 +150,11 @@ QString Tag(const QString &original, ushort tag, const QString &replacement) {
++ch;
}
}
return original;
return -1;
}
QString Plural(ushort keyBase, ushort tag, float64 value) {
PluralResult Plural(ushort keyBase, float64 value) {
// Simplified.
auto n = qAbs(value);
auto i = qFloor(n);
@ -173,9 +169,9 @@ QString Plural(ushort keyBase, ushort tag, float64 value) {
auto shift = (useNonDefaultPlural ? ChoosePlural : ChoosePluralEn)((integer ? i : -1), i, v, w, f, t);
auto string = langpack.getValue(LangKey(keyBase + shift));
if (i == qCeil(n)) {
return Tag(string, tag, QString::number(value));
return { string, QString::number(value) };
}
return Tag(string, tag, QString::number(qRound(value)));
return { string, QString::number(qRound(value)) };
}
void UpdatePluralRules(const QString &languageId) {
@ -183,4 +179,17 @@ void UpdatePluralRules(const QString &languageId) {
ChoosePlural = kMap.value(languageId.toLower(), ChoosePluralEn);
}
QString ReplaceTag<QString>::Replace(QString &&original, const QString &replacement, int replacementPosition) {
auto result = QString();
result.reserve(original.size() + replacement.size() - kTagReplacementSize);
if (replacementPosition > 0) {
result.append(original.midRef(0, replacementPosition));
}
result.append(replacement);
if (replacementPosition + kTagReplacementSize < original.size()) {
result.append(original.midRef(replacementPosition + kTagReplacementSize));
}
return result;
}
} // namespace Lang

View File

@ -22,8 +22,41 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace Lang {
QString Tag(const QString &original, ushort tag, const QString &replacement);
QString Plural(ushort keyBase, ushort tag, float64 value);
constexpr auto kTagReplacementSize = 4;
int FindTagReplacementPosition(const QString &original, ushort tag);
struct PluralResult {
QString string;
QString replacement;
};
PluralResult Plural(ushort keyBase, float64 value);
void UpdatePluralRules(const QString &languageId);
template <typename ResultString>
struct StartReplacements;
template <>
struct StartReplacements<QString> {
static inline QString Call(QString &&langString) {
return std::move(langString);
}
};
template <typename ResultString>
struct ReplaceTag;
template <>
struct ReplaceTag<QString> {
static inline QString Call(QString &&original, ushort tag, const QString &replacement) {
auto replacementPosition = FindTagReplacementPosition(original, tag);
if (replacementPosition < 0) {
return std::move(original);
}
return Replace(std::move(original), replacement, replacementPosition);
}
static QString Replace(QString &&original, const QString &replacement, int start);
};
} // namespace Lang

View File

@ -801,6 +801,10 @@ public:
}
static MTPChannelBannedRights KickedRestrictedRights();
static constexpr auto kRestrictUntilForever = TimeId(INT_MAX);
static bool IsRestrictedForever(TimeId until) {
return !until || (until == kRestrictUntilForever);
}
void applyEditAdmin(gsl::not_null<UserData*> user, const MTPChannelAdminRights &rights);
void applyEditBanned(gsl::not_null<UserData*> user, const MTPChannelBannedRights &rights);
@ -1117,9 +1121,9 @@ private:
class PhotoClickHandler : public LeftButtonClickHandler {
public:
PhotoClickHandler(PhotoData *photo, PeerData *peer = 0) : _photo(photo), _peer(peer) {
PhotoClickHandler(gsl::not_null<PhotoData*> photo, PeerData *peer = nullptr) : _photo(photo), _peer(peer) {
}
PhotoData *photo() const {
gsl::not_null<PhotoData*> photo() const {
return _photo;
}
PeerData *peer() const {
@ -1127,7 +1131,7 @@ public:
}
private:
PhotoData *_photo;
gsl::not_null<PhotoData*> _photo;
PeerData *_peer;
};

View File

@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/text/text_entity.h"
#include "auth_session.h"
#include "lang/lang_tag.h"
namespace {
@ -1366,7 +1367,7 @@ EntitiesInText entitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
case mtpc_messageEntityHashtag: { const auto &d(entity.c_messageEntityHashtag()); result.push_back(EntityInText(EntityInTextHashtag, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityMention: { const auto &d(entity.c_messageEntityMention()); result.push_back(EntityInText(EntityInTextMention, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityMentionName: {
const auto &d(entity.c_messageEntityMentionName());
auto &d = entity.c_messageEntityMentionName();
auto data = QString::number(d.vuser_id.v);
if (auto user = App::userLoaded(peerFromUser(d.vuser_id))) {
data += '.' + QString::number(user->access);
@ -1374,7 +1375,7 @@ EntitiesInText entitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
result.push_back(EntityInText(EntityInTextMentionName, d.voffset.v, d.vlength.v, data));
} break;
case mtpc_inputMessageEntityMentionName: {
const auto &d(entity.c_inputMessageEntityMentionName());
auto &d = entity.c_inputMessageEntityMentionName();
auto data = ([&d]() -> QString {
if (d.vuser_id.type() == mtpc_inputUserSelf) {
return QString::number(AuthSession::CurrentUserId());
@ -1994,3 +1995,65 @@ void trimTextWithEntities(QString &result, EntitiesInText *inOutEntities) {
}
}
}
namespace Lang {
TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement) {
auto replacementPosition = FindTagReplacementPosition(original.text, tag);
if (replacementPosition < 0) {
return std::move(original);
}
auto result = TextWithEntities();
result.text = ReplaceTag<QString>::Replace(std::move(original.text), replacement.text, replacementPosition);
auto originalEntitiesCount = original.entities.size();
auto replacementEntitiesCount = replacement.entities.size();
if (originalEntitiesCount != 0 || replacementEntitiesCount != 0) {
result.entities.reserve(originalEntitiesCount + replacementEntitiesCount);
auto replacementEnd = replacementPosition + replacement.text.size();
auto replacementEntity = replacement.entities.cbegin();
auto addReplacementEntitiesUntil = [&replacementEntity, &replacement, &result, replacementPosition, replacementEnd](int untilPosition) {
while (replacementEntity != replacement.entities.cend()) {
auto newOffset = replacementPosition + replacementEntity->offset();
if (newOffset >= untilPosition) {
return;
}
auto newEnd = newOffset + replacementEntity->length();
newOffset = snap(newOffset, replacementPosition, replacementEnd);
newEnd = snap(newEnd, replacementPosition, replacementEnd);
if (auto newLength = newEnd - newOffset) {
result.entities.push_back(EntityInText(replacementEntity->type(), newOffset, newLength, replacementEntity->data()));
}
++replacementEntity;
}
};
for_const (auto &entity, original.entities) {
// Transform the entity by the replacement.
auto offset = entity.offset();
auto end = offset + entity.length();
if (offset > replacementPosition) {
offset = offset + replacement.text.size() - kTagReplacementSize;
}
if (end > replacementPosition) {
end = end + replacement.text.size() - kTagReplacementSize;
}
offset = snap(offset, 0, result.text.size());
end = snap(end, 0, result.text.size());
// Add all replacement entities that start before the current original entity.
addReplacementEntitiesUntil(offset);
// Add a modified original entity.
if (auto length = end - offset) {
result.entities.push_back(EntityInText(entity.type(), offset, length, entity.data()));
}
}
// Add the remaining replacement entities.
addReplacementEntitiesUntil(result.text.size());
}
return result;
}
} // namespace Lang

View File

@ -180,4 +180,27 @@ inline QString prepareText(QString result, bool checkLinks = false) {
// replace bad symbols with space and remove \r
void cleanTextWithEntities(QString &result, EntitiesInText *inOutEntities);
void trimTextWithEntities(QString &result, EntitiesInText *inOutEntities);
void trimTextWithEntities(QString &result, EntitiesInText *inOutEntities);
namespace Lang {
template <typename ResultString>
struct StartReplacements;
template <>
struct StartReplacements<TextWithEntities> {
static inline TextWithEntities Call(QString &&langString) {
return { std::move(langString), EntitiesInText() };
}
};
template <typename ResultString>
struct ReplaceTag;
template <>
struct ReplaceTag<TextWithEntities> {
static TextWithEntities Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement);
};
}

View File

@ -650,7 +650,8 @@ void ThemeExportBox::chooseBackgroundFromFile() {
_background = image;
_backgroundContent = content;
_isPng = (format == "png");
_imageText = (_isPng ? lng_theme_editor_read_from_png : lng_theme_editor_read_from_jpg)(lt_size, formatSizeText(_backgroundContent.size()));
auto sizeText = formatSizeText(_backgroundContent.size());
_imageText = _isPng ? lng_theme_editor_read_from_png(lt_size, sizeText) : lng_theme_editor_read_from_jpg(lt_size, sizeText);
_tileBackground->setChecked(false);
updateThumbnail();
}