mirror of https://github.com/procxx/kepka.git
Support grouped media rendering.
This commit is contained in:
parent
0a4038d061
commit
4c9931ab02
|
@ -936,6 +936,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
"lng_stickers_group_from_featured" = "Choose from trending stickers";
|
"lng_stickers_group_from_featured" = "Choose from trending stickers";
|
||||||
|
|
||||||
"lng_in_dlg_photo" = "Photo";
|
"lng_in_dlg_photo" = "Photo";
|
||||||
|
"lng_in_dlg_album" = "Album";
|
||||||
"lng_in_dlg_video" = "Video";
|
"lng_in_dlg_video" = "Video";
|
||||||
"lng_in_dlg_audio_file" = "Audio file";
|
"lng_in_dlg_audio_file" = "Audio file";
|
||||||
"lng_in_dlg_contact" = "Contact";
|
"lng_in_dlg_contact" = "Contact";
|
||||||
|
|
|
@ -1608,18 +1608,18 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!v) return;
|
if (!v) return;
|
||||||
QMap<uint64, int32> msgsIds; // copied from feedMsgs
|
|
||||||
for (int32 i = 0, l = v->size(); i < l; ++i) {
|
auto indices = base::flat_map<uint64, int>(); // copied from feedMsgs
|
||||||
const auto &msg(v->at(i));
|
for (auto i = 0, l = v->size(); i != l; ++i) {
|
||||||
switch (msg.type()) {
|
const auto msgId = idFromMessage(v->at(i));
|
||||||
case mtpc_message: msgsIds.insert((uint64(uint32(msg.c_message().vid.v)) << 32) | uint64(i), i); break;
|
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
|
||||||
case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i); break;
|
|
||||||
case mtpc_messageService: msgsIds.insert((uint64(uint32(msg.c_messageService().vid.v)) << 32) | uint64(i), i); break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for_const (auto msgId, msgsIds) {
|
for (const auto [position, index] : indices) {
|
||||||
if (auto item = App::histories().addNewMessage(v->at(msgId), NewMessageExisting)) {
|
const auto item = App::histories().addNewMessage(
|
||||||
|
v->at(index),
|
||||||
|
NewMessageExisting);
|
||||||
|
if (item) {
|
||||||
item->setPendingInitDimensions();
|
item->setPendingInitDimensions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1105,29 +1105,23 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
void feedMsgs(const QVector<MTPMessage> &msgs, NewMessageType type) {
|
void feedMsgs(const QVector<MTPMessage> &msgs, NewMessageType type) {
|
||||||
QMap<uint64, int32> msgsIds;
|
auto indices = base::flat_map<uint64, int>();
|
||||||
for (int32 i = 0, l = msgs.size(); i < l; ++i) {
|
for (int i = 0, l = msgs.size(); i != l; ++i) {
|
||||||
const auto &msg(msgs.at(i));
|
const auto &msg = msgs[i];
|
||||||
switch (msg.type()) {
|
if (msg.type() == mtpc_message) {
|
||||||
case mtpc_message: {
|
const auto &data = msg.c_message();
|
||||||
const auto &d(msg.c_message());
|
|
||||||
bool needToAdd = true;
|
|
||||||
if (type == NewMessageUnread) { // new message, index my forwarded messages to links overview
|
if (type == NewMessageUnread) { // new message, index my forwarded messages to links overview
|
||||||
if (checkEntitiesAndViewsUpdate(d)) { // already in blocks
|
if (checkEntitiesAndViewsUpdate(data)) { // already in blocks
|
||||||
LOG(("Skipping message, because it is already in blocks!"));
|
LOG(("Skipping message, because it is already in blocks!"));
|
||||||
needToAdd = false;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (needToAdd) {
|
|
||||||
msgsIds.insert((uint64(uint32(d.vid.v)) << 32) | uint64(i), i);
|
|
||||||
}
|
}
|
||||||
} break;
|
const auto msgId = idFromMessage(msg);
|
||||||
case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i); break;
|
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
|
||||||
case mtpc_messageService: msgsIds.insert((uint64(uint32(msg.c_messageService().vid.v)) << 32) | uint64(i), i); break;
|
|
||||||
}
|
}
|
||||||
}
|
for (const auto [position, index] : indices) {
|
||||||
for (QMap<uint64, int32>::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) {
|
histories().addNewMessage(msgs[index], type);
|
||||||
histories().addNewMessage(msgs.at(i.value()), type);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2671,7 +2665,9 @@ namespace {
|
||||||
p.setBrush(p.textPalette().selectOverlay);
|
p.setBrush(p.textPalette().selectOverlay);
|
||||||
p.drawEllipse(rect);
|
p.drawEllipse(rect);
|
||||||
} else {
|
} else {
|
||||||
auto overlayCorners = (radius == ImageRoundRadius::Small) ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners;
|
auto overlayCorners = (radius == ImageRoundRadius::Small)
|
||||||
|
? SelectedOverlaySmallCorners
|
||||||
|
: SelectedOverlayLargeCorners;
|
||||||
auto overlayParts = RectPart::Full | RectPart::None;
|
auto overlayParts = RectPart::Full | RectPart::None;
|
||||||
if (radius == ImageRoundRadius::Large) {
|
if (radius == ImageRoundRadius::Large) {
|
||||||
complexAdjustRect(corners, rect, overlayParts);
|
complexAdjustRect(corners, rect, overlayParts);
|
||||||
|
|
|
@ -291,7 +291,7 @@ base::optional<bool> SharedMediaWithLastSlice::IsLastIsolated(
|
||||||
| [](HistoryItem *item) { return item ? item->getMedia() : nullptr; }
|
| [](HistoryItem *item) { return item ? item->getMedia() : nullptr; }
|
||||||
| [](HistoryMedia *media) {
|
| [](HistoryMedia *media) {
|
||||||
return (media && media->type() == MediaTypePhoto)
|
return (media && media->type() == MediaTypePhoto)
|
||||||
? static_cast<HistoryPhoto*>(media)->photo()
|
? static_cast<HistoryPhoto*>(media)->photo().get()
|
||||||
: nullptr;
|
: nullptr;
|
||||||
}
|
}
|
||||||
| [](PhotoData *photo) { return photo ? photo->id : 0; }
|
| [](PhotoData *photo) { return photo ? photo->id : 0; }
|
||||||
|
|
|
@ -796,12 +796,7 @@ void Histories::checkSelfDestructItems() {
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) {
|
HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) {
|
||||||
auto msgId = MsgId(0);
|
const auto msgId = idFromMessage(msg);
|
||||||
switch (msg.type()) {
|
|
||||||
case mtpc_messageEmpty: msgId = msg.c_messageEmpty().vid.v; break;
|
|
||||||
case mtpc_message: msgId = msg.c_message().vid.v; break;
|
|
||||||
case mtpc_messageService: msgId = msg.c_messageService().vid.v; break;
|
|
||||||
}
|
|
||||||
if (!msgId) return nullptr;
|
if (!msgId) return nullptr;
|
||||||
|
|
||||||
auto result = App::histItemById(channelId(), msgId);
|
auto result = App::histItemById(channelId(), msgId);
|
||||||
|
@ -810,7 +805,10 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
|
||||||
result->detach();
|
result->detach();
|
||||||
}
|
}
|
||||||
if (msg.type() == mtpc_message) {
|
if (msg.type() == mtpc_message) {
|
||||||
result->updateMedia(msg.c_message().has_media() ? (&msg.c_message().vmedia) : 0);
|
const auto media = msg.c_message().has_media()
|
||||||
|
? &msg.c_message().vmedia
|
||||||
|
: nullptr;
|
||||||
|
result->updateMedia(media);
|
||||||
if (applyServiceAction) {
|
if (applyServiceAction) {
|
||||||
App::checkSavedGif(result);
|
App::checkSavedGif(result);
|
||||||
}
|
}
|
||||||
|
@ -1094,23 +1092,23 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem *History::createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg) {
|
not_null<HistoryItem*> History::createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg) {
|
||||||
return HistoryMessage::create(this, id, flags, date, from, postAuthor, msg);
|
return HistoryMessage::create(this, id, flags, date, from, postAuthor, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem *History::createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
not_null<HistoryItem*> History::createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
||||||
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, doc, caption, markup);
|
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, doc, caption, markup);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem *History::createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
not_null<HistoryItem*> History::createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
||||||
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, photo, caption, markup);
|
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, photo, caption, markup);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem *History::createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
|
not_null<HistoryItem*> History::createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
|
||||||
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, game, markup);
|
return HistoryMessage::create(this, id, flags, replyTo, viaBotId, date, from, postAuthor, game, markup);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem *History::addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags, bool newMsg) {
|
not_null<HistoryItem*> History::addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags, bool newMsg) {
|
||||||
auto message = HistoryService::PreparedText { text };
|
auto message = HistoryService::PreparedText { text };
|
||||||
return addNewItem(HistoryService::create(this, msgId, date, message, flags), newMsg);
|
return addNewItem(HistoryService::create(this, msgId, date, message, flags), newMsg);
|
||||||
}
|
}
|
||||||
|
@ -1147,19 +1145,19 @@ HistoryItem *History::addToHistory(const MTPMessage &msg) {
|
||||||
return createItem(msg, false, false);
|
return createItem(msg, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem *History::addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item) {
|
not_null<HistoryItem*> History::addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item) {
|
||||||
return addNewItem(createItemForwarded(id, flags, date, from, postAuthor, item), true);
|
return addNewItem(createItemForwarded(id, flags, date, from, postAuthor, item), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem *History::addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
not_null<HistoryItem*> History::addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
||||||
return addNewItem(createItemDocument(id, flags, viaBotId, replyTo, date, from, postAuthor, doc, caption, markup), true);
|
return addNewItem(createItemDocument(id, flags, viaBotId, replyTo, date, from, postAuthor, doc, caption, markup), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem *History::addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
not_null<HistoryItem*> History::addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
||||||
return addNewItem(createItemPhoto(id, flags, viaBotId, replyTo, date, from, postAuthor, photo, caption, markup), true);
|
return addNewItem(createItemPhoto(id, flags, viaBotId, replyTo, date, from, postAuthor, photo, caption, markup), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem *History::addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
|
not_null<HistoryItem*> History::addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
|
||||||
return addNewItem(createItemGame(id, flags, viaBotId, replyTo, date, from, postAuthor, game, markup), true);
|
return addNewItem(createItemGame(id, flags, viaBotId, replyTo, date, from, postAuthor, game, markup), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1251,10 +1249,15 @@ void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) {
|
||||||
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged);
|
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
|
not_null<HistoryItem*> History::addNewItem(not_null<HistoryItem*> adding, bool newMsg) {
|
||||||
Expects(!isBuildingFrontBlock());
|
Expects(!isBuildingFrontBlock());
|
||||||
addItemToBlock(adding);
|
addItemToBlock(adding);
|
||||||
|
|
||||||
|
const auto [groupFrom, groupTill] = recountGroupingFromTill(adding);
|
||||||
|
if (groupFrom != groupTill || groupFrom->groupId()) {
|
||||||
|
recountGrouping(groupFrom, groupTill);
|
||||||
|
}
|
||||||
|
|
||||||
setLastMessage(adding);
|
setLastMessage(adding);
|
||||||
if (newMsg) {
|
if (newMsg) {
|
||||||
newItemAdded(adding);
|
newItemAdded(adding);
|
||||||
|
@ -1434,8 +1437,7 @@ HistoryBlock *History::prepareBlockForAddingItem() {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
void History::addItemToBlock(HistoryItem *item) {
|
void History::addItemToBlock(not_null<HistoryItem*> item) {
|
||||||
Expects(item != nullptr);
|
|
||||||
Expects(item->detached());
|
Expects(item->detached());
|
||||||
|
|
||||||
auto block = prepareBlockForAddingItem();
|
auto block = prepareBlockForAddingItem();
|
||||||
|
@ -1528,6 +1530,9 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto firstAdded = (HistoryItem*)nullptr;
|
||||||
|
auto lastAdded = (HistoryItem*)nullptr;
|
||||||
|
|
||||||
auto logged = QStringList();
|
auto logged = QStringList();
|
||||||
logged.push_back(QString::number(minMsgId()));
|
logged.push_back(QString::number(minMsgId()));
|
||||||
logged.push_back(QString::number(maxMsgId()));
|
logged.push_back(QString::number(maxMsgId()));
|
||||||
|
@ -1539,9 +1544,12 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||||
|
|
||||||
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
|
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
|
||||||
--i;
|
--i;
|
||||||
auto adding = createItem(*i, false, true);
|
const auto adding = createItem(*i, false, true);
|
||||||
if (!adding) continue;
|
if (!adding) continue;
|
||||||
|
|
||||||
|
if (!firstAdded) firstAdded = adding;
|
||||||
|
lastAdded = adding;
|
||||||
|
|
||||||
if (minAdded < 0 || minAdded > adding->id) {
|
if (minAdded < 0 || minAdded > adding->id) {
|
||||||
minAdded = adding->id;
|
minAdded = adding->id;
|
||||||
}
|
}
|
||||||
|
@ -1638,6 +1646,11 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||||
|
|
||||||
CrashReports::ClearAnnotation("old_minmaxwas_minmaxadd");
|
CrashReports::ClearAnnotation("old_minmaxwas_minmaxadd");
|
||||||
|
|
||||||
|
if (lastAdded) {
|
||||||
|
const auto [from, till] = recountGroupingFromTill(lastAdded);
|
||||||
|
recountGrouping(firstAdded, till);
|
||||||
|
}
|
||||||
|
|
||||||
if (isChannel()) {
|
if (isChannel()) {
|
||||||
asChannelHistory()->checkJoinedMessage();
|
asChannelHistory()->checkJoinedMessage();
|
||||||
asChannelHistory()->checkMaxReadMessageDate();
|
asChannelHistory()->checkMaxReadMessageDate();
|
||||||
|
@ -1655,6 +1668,9 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto firstAdded = (HistoryItem*)nullptr;
|
||||||
|
auto lastAdded = (HistoryItem*)nullptr;
|
||||||
|
|
||||||
Assert(!isBuildingFrontBlock());
|
Assert(!isBuildingFrontBlock());
|
||||||
if (!slice.isEmpty()) {
|
if (!slice.isEmpty()) {
|
||||||
auto logged = QStringList();
|
auto logged = QStringList();
|
||||||
|
@ -1665,12 +1681,14 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||||
auto maxAdded = -1;
|
auto maxAdded = -1;
|
||||||
|
|
||||||
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
|
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
|
||||||
auto atLeastOneAdded = false;
|
|
||||||
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
|
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
|
||||||
--i;
|
--i;
|
||||||
auto adding = createItem(*i, false, true);
|
const auto adding = createItem(*i, false, true);
|
||||||
if (!adding) continue;
|
if (!adding) continue;
|
||||||
|
|
||||||
|
if (!firstAdded) firstAdded = adding;
|
||||||
|
lastAdded = adding;
|
||||||
|
|
||||||
if (minAdded < 0 || minAdded > adding->id) {
|
if (minAdded < 0 || minAdded > adding->id) {
|
||||||
minAdded = adding->id;
|
minAdded = adding->id;
|
||||||
}
|
}
|
||||||
|
@ -1679,7 +1697,6 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||||
}
|
}
|
||||||
|
|
||||||
addItemToBlock(adding);
|
addItemToBlock(adding);
|
||||||
atLeastOneAdded = true;
|
|
||||||
if (auto types = adding->sharedMediaTypes()) {
|
if (auto types = adding->sharedMediaTypes()) {
|
||||||
for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
|
for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
|
||||||
auto type = static_cast<Storage::SharedMediaType>(i);
|
auto type = static_cast<Storage::SharedMediaType>(i);
|
||||||
|
@ -1696,7 +1713,7 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||||
logged.push_back(QString::number(maxAdded));
|
logged.push_back(QString::number(maxAdded));
|
||||||
CrashReports::SetAnnotation("new_minmaxwas_minmaxadd", logged.join(";"));
|
CrashReports::SetAnnotation("new_minmaxwas_minmaxadd", logged.join(";"));
|
||||||
|
|
||||||
if (!atLeastOneAdded) {
|
if (!firstAdded) {
|
||||||
newLoaded = true;
|
newLoaded = true;
|
||||||
setLastMessage(lastAvailableMessage());
|
setLastMessage(lastAvailableMessage());
|
||||||
}
|
}
|
||||||
|
@ -1709,6 +1726,11 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
|
||||||
checkAddAllToUnreadMentions();
|
checkAddAllToUnreadMentions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (firstAdded) {
|
||||||
|
const auto [from, till] = recountGroupingFromTill(firstAdded);
|
||||||
|
recountGrouping(from, lastAdded);
|
||||||
|
}
|
||||||
|
|
||||||
if (isChannel()) asChannelHistory()->checkJoinedMessage();
|
if (isChannel()) asChannelHistory()->checkJoinedMessage();
|
||||||
checkLastMsg();
|
checkLastMsg();
|
||||||
}
|
}
|
||||||
|
@ -2007,7 +2029,7 @@ void History::destroyUnreadBar() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex) {
|
not_null<HistoryItem*> History::addNewInTheMiddle(not_null<HistoryItem*> newItem, int32 blockIndex, int32 itemIndex) {
|
||||||
Expects(blockIndex >= 0);
|
Expects(blockIndex >= 0);
|
||||||
Expects(blockIndex < blocks.size());
|
Expects(blockIndex < blocks.size());
|
||||||
Expects(itemIndex >= 0);
|
Expects(itemIndex >= 0);
|
||||||
|
@ -2029,9 +2051,126 @@ HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex,
|
||||||
newItem->nextItemChanged();
|
newItem->nextItemChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto [groupFrom, groupTill] = recountGroupingFromTill(newItem);
|
||||||
|
if (groupFrom != groupTill || groupFrom->groupId()) {
|
||||||
|
recountGrouping(groupFrom, groupTill);
|
||||||
|
}
|
||||||
|
|
||||||
return newItem;
|
return newItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HistoryItem *History::findNextItem(not_null<HistoryItem*> item) const {
|
||||||
|
Expects(!item->detached());
|
||||||
|
|
||||||
|
const auto nextBlockIndex = item->block()->indexInHistory() + 1;
|
||||||
|
const auto nextItemIndex = item->indexInBlock() + 1;
|
||||||
|
if (nextItemIndex < int(item->block()->items.size())) {
|
||||||
|
return item->block()->items[nextItemIndex];
|
||||||
|
} else if (nextBlockIndex < int(blocks.size())) {
|
||||||
|
return blocks[nextBlockIndex]->items.front();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryItem *History::findPreviousItem(not_null<HistoryItem*> item) const {
|
||||||
|
Expects(!item->detached());
|
||||||
|
|
||||||
|
const auto blockIndex = item->block()->indexInHistory();
|
||||||
|
const auto itemIndex = item->indexInBlock();
|
||||||
|
if (itemIndex > 0) {
|
||||||
|
return item->block()->items[itemIndex - 1];
|
||||||
|
} else if (blockIndex > 0) {
|
||||||
|
return blocks[blockIndex - 1]->items.back();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<HistoryItem*> History::findGroupFirst(
|
||||||
|
not_null<HistoryItem*> item) const {
|
||||||
|
const auto group = item->Get<HistoryMessageGroup>();
|
||||||
|
Assert(group != nullptr);
|
||||||
|
Assert(group->leader != nullptr);
|
||||||
|
|
||||||
|
const auto leaderGroup = (group->leader == item)
|
||||||
|
? group
|
||||||
|
: group->leader->Get<HistoryMessageGroup>();
|
||||||
|
Assert(leaderGroup != nullptr);
|
||||||
|
|
||||||
|
return leaderGroup->others.empty()
|
||||||
|
? group->leader
|
||||||
|
: leaderGroup->others.front().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<HistoryItem*> History::findGroupLast(
|
||||||
|
not_null<HistoryItem*> item) const {
|
||||||
|
const auto group = item->Get<HistoryMessageGroup>();
|
||||||
|
Assert(group != nullptr);
|
||||||
|
|
||||||
|
return group->leader;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto History::recountGroupingFromTill(not_null<HistoryItem*> item)
|
||||||
|
-> std::pair<not_null<HistoryItem*>, not_null<HistoryItem*>> {
|
||||||
|
const auto recountFromItem = [&] {
|
||||||
|
if (const auto prev = findPreviousItem(item)) {
|
||||||
|
if (prev->groupId()) {
|
||||||
|
return findGroupFirst(prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}();
|
||||||
|
if (recountFromItem == item && !item->groupId()) {
|
||||||
|
return { item, item };
|
||||||
|
}
|
||||||
|
const auto recountTillItem = [&] {
|
||||||
|
if (const auto next = findNextItem(item)) {
|
||||||
|
if (next->groupId()) {
|
||||||
|
return findGroupLast(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}();
|
||||||
|
return { recountFromItem, recountTillItem };
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::recountGrouping(
|
||||||
|
not_null<HistoryItem*> from,
|
||||||
|
not_null<HistoryItem*> till) {
|
||||||
|
Expects(!from->detached());
|
||||||
|
Expects(!till->detached());
|
||||||
|
|
||||||
|
from->validateGroupId();
|
||||||
|
auto others = std::vector<not_null<HistoryItem*>>();
|
||||||
|
auto currentGroupId = from->groupId();
|
||||||
|
auto prev = from;
|
||||||
|
while (prev != till) {
|
||||||
|
auto item = findNextItem(prev);
|
||||||
|
item->validateGroupId();
|
||||||
|
const auto groupId = item->groupId();
|
||||||
|
if (currentGroupId) {
|
||||||
|
if (groupId == currentGroupId) {
|
||||||
|
others.push_back(prev);
|
||||||
|
} else {
|
||||||
|
for (const auto other : others) {
|
||||||
|
other->makeGroupMember(prev);
|
||||||
|
}
|
||||||
|
prev->makeGroupLeader(base::take(others));
|
||||||
|
currentGroupId = groupId;
|
||||||
|
}
|
||||||
|
} else if (groupId) {
|
||||||
|
currentGroupId = groupId;
|
||||||
|
}
|
||||||
|
prev = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentGroupId) {
|
||||||
|
for (const auto other : others) {
|
||||||
|
other->makeGroupMember(prev);
|
||||||
|
}
|
||||||
|
till->makeGroupLeader(base::take(others));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void History::startBuildingFrontBlock(int expectedItemsCount) {
|
void History::startBuildingFrontBlock(int expectedItemsCount) {
|
||||||
Assert(!isBuildingFrontBlock());
|
Assert(!isBuildingFrontBlock());
|
||||||
Assert(expectedItemsCount > 0);
|
Assert(expectedItemsCount > 0);
|
||||||
|
@ -2471,7 +2610,7 @@ void History::setPinnedIndex(int pinnedIndex) {
|
||||||
void History::changeMsgId(MsgId oldId, MsgId newId) {
|
void History::changeMsgId(MsgId oldId, MsgId newId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void History::removeBlock(HistoryBlock *block) {
|
void History::removeBlock(not_null<HistoryBlock*> block) {
|
||||||
Expects(block->items.empty());
|
Expects(block->items.empty());
|
||||||
|
|
||||||
if (_buildingFrontBlock && block == _buildingFrontBlock->block) {
|
if (_buildingFrontBlock && block == _buildingFrontBlock->block) {
|
||||||
|
@ -2522,9 +2661,21 @@ void HistoryBlock::clear(bool leaveItems) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryBlock::removeItem(HistoryItem *item) {
|
void HistoryBlock::removeItem(not_null<HistoryItem*> item) {
|
||||||
Expects(item->block() == this);
|
Expects(item->block() == this);
|
||||||
|
|
||||||
|
auto [groupFrom, groupTill] = _history->recountGroupingFromTill(item);
|
||||||
|
const auto groupHistory = _history;
|
||||||
|
const auto needGroupRecount = (groupFrom != groupTill);
|
||||||
|
if (needGroupRecount) {
|
||||||
|
if (groupFrom == item) {
|
||||||
|
groupFrom = groupHistory->findNextItem(groupFrom);
|
||||||
|
}
|
||||||
|
if (groupTill == item) {
|
||||||
|
groupTill = groupHistory->findPreviousItem(groupTill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto blockIndex = indexInHistory();
|
auto blockIndex = indexInHistory();
|
||||||
auto itemIndex = item->indexInBlock();
|
auto itemIndex = item->indexInBlock();
|
||||||
if (_history->showFrom == item) {
|
if (_history->showFrom == item) {
|
||||||
|
@ -2558,4 +2709,8 @@ void HistoryBlock::removeItem(HistoryItem *item) {
|
||||||
if (items.empty()) {
|
if (items.empty()) {
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (needGroupRecount) {
|
||||||
|
groupHistory->recountGrouping(groupFrom, groupTill);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,6 +136,7 @@ enum HistoryMediaType {
|
||||||
MediaTypeVoiceFile,
|
MediaTypeVoiceFile,
|
||||||
MediaTypeGame,
|
MediaTypeGame,
|
||||||
MediaTypeInvoice,
|
MediaTypeInvoice,
|
||||||
|
MediaTypeGrouped,
|
||||||
|
|
||||||
MediaTypeCount
|
MediaTypeCount
|
||||||
};
|
};
|
||||||
|
@ -217,13 +218,13 @@ public:
|
||||||
|
|
||||||
virtual ~History();
|
virtual ~History();
|
||||||
|
|
||||||
HistoryItem *addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true);
|
|
||||||
HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type);
|
HistoryItem *addNewMessage(const MTPMessage &msg, NewMessageType type);
|
||||||
HistoryItem *addToHistory(const MTPMessage &msg);
|
HistoryItem *addToHistory(const MTPMessage &msg);
|
||||||
HistoryItem *addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item);
|
not_null<HistoryItem*> addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true);
|
||||||
HistoryItem *addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
|
not_null<HistoryItem*> addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item);
|
||||||
HistoryItem *addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
|
not_null<HistoryItem*> addNewDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
|
||||||
HistoryItem *addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
|
not_null<HistoryItem*> addNewPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
|
||||||
|
not_null<HistoryItem*> addNewGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
|
||||||
|
|
||||||
// Used only internally and for channel admin log.
|
// Used only internally and for channel admin log.
|
||||||
HistoryItem *createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem);
|
HistoryItem *createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem);
|
||||||
|
@ -475,17 +476,17 @@ protected:
|
||||||
// this method just removes a block from the blocks list
|
// this method just removes a block from the blocks list
|
||||||
// when the last item from this block was detached and
|
// when the last item from this block was detached and
|
||||||
// calls the required previousItemChanged()
|
// calls the required previousItemChanged()
|
||||||
void removeBlock(HistoryBlock *block);
|
void removeBlock(not_null<HistoryBlock*> block);
|
||||||
|
|
||||||
void clearBlocks(bool leaveItems);
|
void clearBlocks(bool leaveItems);
|
||||||
|
|
||||||
HistoryItem *createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg);
|
not_null<HistoryItem*> createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg);
|
||||||
HistoryItem *createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
|
not_null<HistoryItem*> createItemDocument(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup);
|
||||||
HistoryItem *createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
|
not_null<HistoryItem*> createItemPhoto(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup);
|
||||||
HistoryItem *createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
|
not_null<HistoryItem*> createItemGame(MsgId id, MTPDmessage::Flags flags, UserId viaBotId, MsgId replyTo, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup);
|
||||||
|
|
||||||
HistoryItem *addNewItem(HistoryItem *adding, bool newMsg);
|
not_null<HistoryItem*> addNewItem(not_null<HistoryItem*> adding, bool newMsg);
|
||||||
HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex);
|
not_null<HistoryItem*> addNewInTheMiddle(not_null<HistoryItem*> newItem, int32 blockIndex, int32 itemIndex);
|
||||||
|
|
||||||
// All this methods add a new item to the first or last block
|
// All this methods add a new item to the first or last block
|
||||||
// depending on if we are in isBuildingFronBlock() state.
|
// depending on if we are in isBuildingFronBlock() state.
|
||||||
|
@ -493,7 +494,7 @@ protected:
|
||||||
|
|
||||||
// Adds the item to the back or front block, depending on
|
// Adds the item to the back or front block, depending on
|
||||||
// isBuildingFrontBlock(), creating the block if necessary.
|
// isBuildingFrontBlock(), creating the block if necessary.
|
||||||
void addItemToBlock(HistoryItem *item);
|
void addItemToBlock(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
// Usually all new items are added to the last block.
|
// Usually all new items are added to the last block.
|
||||||
// Only when we scroll up and add a new slice to the
|
// Only when we scroll up and add a new slice to the
|
||||||
|
@ -517,6 +518,18 @@ private:
|
||||||
|
|
||||||
void clearSendAction(not_null<UserData*> from);
|
void clearSendAction(not_null<UserData*> from);
|
||||||
|
|
||||||
|
HistoryItem *findPreviousItem(not_null<HistoryItem*> item) const;
|
||||||
|
HistoryItem *findNextItem(not_null<HistoryItem*> item) const;
|
||||||
|
not_null<HistoryItem*> findGroupFirst(
|
||||||
|
not_null<HistoryItem*> item) const;
|
||||||
|
not_null<HistoryItem*> findGroupLast(
|
||||||
|
not_null<HistoryItem*> item) const;
|
||||||
|
auto recountGroupingFromTill(not_null<HistoryItem*> item)
|
||||||
|
-> std::pair<not_null<HistoryItem*>, not_null<HistoryItem*>>;
|
||||||
|
void recountGrouping(
|
||||||
|
not_null<HistoryItem*> from,
|
||||||
|
not_null<HistoryItem*> till);
|
||||||
|
|
||||||
enum class Flag {
|
enum class Flag {
|
||||||
f_has_pending_resized_items = (1 << 0),
|
f_has_pending_resized_items = (1 << 0),
|
||||||
f_pending_resize = (1 << 1),
|
f_pending_resize = (1 << 1),
|
||||||
|
@ -624,7 +637,7 @@ public:
|
||||||
~HistoryBlock() {
|
~HistoryBlock() {
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
void removeItem(HistoryItem *item);
|
void removeItem(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
int resizeGetHeight(int newWidth, bool resizeAllItems);
|
int resizeGetHeight(int newWidth, bool resizeAllItems);
|
||||||
int y() const {
|
int y() const {
|
||||||
|
|
|
@ -461,3 +461,9 @@ historyFastShareIcon: icon {{ "fast_share", msgServiceFg, point(4px, 3px)}};
|
||||||
historyGoToOriginalIcon: icon {{ "title_back-flip_horizontal", msgServiceFg, point(8px, 7px) }};
|
historyGoToOriginalIcon: icon {{ "title_back-flip_horizontal", msgServiceFg, point(8px, 7px) }};
|
||||||
|
|
||||||
historySavedFont: font(semibold 14px);
|
historySavedFont: font(semibold 14px);
|
||||||
|
|
||||||
|
historyGroupWidthMax: maxMediaSize;
|
||||||
|
historyGroupWidthMin: minPhotoSize;
|
||||||
|
historyGroupSkip: 4px;
|
||||||
|
historyGroupRadialSize: 44px;
|
||||||
|
historyGroupRadialLine: 3px;
|
||||||
|
|
|
@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "history/history_service_layout.h"
|
#include "history/history_service_layout.h"
|
||||||
#include "history/history_media_types.h"
|
#include "history/history_media_types.h"
|
||||||
|
#include "history/history_media_grouped.h"
|
||||||
#include "history/history_message.h"
|
#include "history/history_message.h"
|
||||||
#include "media/media_clip_reader.h"
|
#include "media/media_clip_reader.h"
|
||||||
#include "styles/style_dialogs.h"
|
#include "styles/style_dialogs.h"
|
||||||
|
@ -563,7 +564,8 @@ HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default;
|
||||||
|
|
||||||
HistoryMediaPtr::HistoryMediaPtr() = default;
|
HistoryMediaPtr::HistoryMediaPtr() = default;
|
||||||
|
|
||||||
HistoryMediaPtr::HistoryMediaPtr(std::unique_ptr<HistoryMedia> pointer) : _pointer(std::move(pointer)) {
|
HistoryMediaPtr::HistoryMediaPtr(std::unique_ptr<HistoryMedia> pointer)
|
||||||
|
: _pointer(std::move(pointer)) {
|
||||||
if (_pointer) {
|
if (_pointer) {
|
||||||
_pointer->attachToParent();
|
_pointer->attachToParent();
|
||||||
}
|
}
|
||||||
|
@ -768,6 +770,11 @@ void HistoryItem::detach() {
|
||||||
void HistoryItem::detachFast() {
|
void HistoryItem::detachFast() {
|
||||||
_block = nullptr;
|
_block = nullptr;
|
||||||
_indexInBlock = -1;
|
_indexInBlock = -1;
|
||||||
|
|
||||||
|
validateGroupId();
|
||||||
|
if (groupId()) {
|
||||||
|
makeGroupLeader({});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const {
|
Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const {
|
||||||
|
@ -1116,6 +1123,88 @@ void HistoryItem::setUnreadBarFreezed() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HistoryItem::groupIdValidityChanged() {
|
||||||
|
if (Has<HistoryMessageGroup>()) {
|
||||||
|
if (_media && _media->canBeGrouped()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RemoveComponents(HistoryMessageGroup::Bit());
|
||||||
|
setPendingInitDimensions();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryItem::makeGroupMember(not_null<HistoryItem*> leader) {
|
||||||
|
Expects(leader != this);
|
||||||
|
|
||||||
|
const auto group = Get<HistoryMessageGroup>();
|
||||||
|
Assert(group != nullptr);
|
||||||
|
if (group->leader == this) {
|
||||||
|
if (auto single = _media ? _media->takeLastFromGroup() : nullptr) {
|
||||||
|
_media = std::move(single);
|
||||||
|
}
|
||||||
|
_flags |= MTPDmessage_ClientFlag::f_hidden_by_group;
|
||||||
|
setPendingInitDimensions();
|
||||||
|
|
||||||
|
group->leader = leader;
|
||||||
|
base::take(group->others);
|
||||||
|
} else if (group->leader != leader) {
|
||||||
|
group->leader = leader;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ensures(isHiddenByGroup());
|
||||||
|
Ensures(group->others.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryItem::makeGroupLeader(
|
||||||
|
std::vector<not_null<HistoryItem*>> &&others) {
|
||||||
|
const auto group = Get<HistoryMessageGroup>();
|
||||||
|
Assert(group != nullptr);
|
||||||
|
|
||||||
|
if (group->leader != this) {
|
||||||
|
group->leader = this;
|
||||||
|
_flags &= ~MTPDmessage_ClientFlag::f_hidden_by_group;
|
||||||
|
setPendingInitDimensions();
|
||||||
|
}
|
||||||
|
group->others = std::move(others);
|
||||||
|
if (!_media || !_media->applyGroup(group->others)) {
|
||||||
|
resetGroupMedia(group->others);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ensures(!isHiddenByGroup());
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryItem::resetGroupMedia(
|
||||||
|
const std::vector<not_null<HistoryItem*>> &others) {
|
||||||
|
if (!others.empty()) {
|
||||||
|
_media = std::make_unique<HistoryGroupedMedia>(this, others);
|
||||||
|
} else if (_media) {
|
||||||
|
_media = _media->takeLastFromGroup();
|
||||||
|
}
|
||||||
|
setPendingInitDimensions();
|
||||||
|
}
|
||||||
|
|
||||||
|
int HistoryItem::marginTop() const {
|
||||||
|
auto result = 0;
|
||||||
|
if (!isHiddenByGroup()) {
|
||||||
|
if (isAttachedToPrevious()) {
|
||||||
|
result += st::msgMarginTopAttached;
|
||||||
|
} else {
|
||||||
|
result += st::msgMargin.top();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += displayedDateHeight();
|
||||||
|
if (const auto unreadbar = Get<HistoryMessageUnreadBar>()) {
|
||||||
|
result += unreadbar->height();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HistoryItem::marginBottom() const {
|
||||||
|
return isHiddenByGroup() ? 0 : st::msgMargin.bottom();
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryItem::clipCallback(Media::Clip::Notification notification) {
|
void HistoryItem::clipCallback(Media::Clip::Notification notification) {
|
||||||
using namespace Media::Clip;
|
using namespace Media::Clip;
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
|
|
||||||
#include "base/runtime_composer.h"
|
#include "base/runtime_composer.h"
|
||||||
#include "base/flags.h"
|
#include "base/flags.h"
|
||||||
|
#include "base/value_ordering.h"
|
||||||
|
|
||||||
namespace base {
|
namespace base {
|
||||||
template <typename Enum>
|
template <typename Enum>
|
||||||
|
@ -435,6 +436,34 @@ struct HistoryMessageUnreadBar : public RuntimeComponent<HistoryMessageUnreadBar
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MessageGroupId {
|
||||||
|
using Underlying = uint64;
|
||||||
|
|
||||||
|
enum Type : Underlying {
|
||||||
|
None = 0,
|
||||||
|
} value;
|
||||||
|
|
||||||
|
MessageGroupId(Type value = None) : value(value) {
|
||||||
|
}
|
||||||
|
static MessageGroupId FromRaw(Underlying value) {
|
||||||
|
return static_cast<Type>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return value != None;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend inline Type value_ordering_helper(MessageGroupId value) {
|
||||||
|
return value.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
struct HistoryMessageGroup : public RuntimeComponent<HistoryMessageGroup> {
|
||||||
|
MessageGroupId groupId = MessageGroupId::None;
|
||||||
|
HistoryItem *leader = nullptr;
|
||||||
|
std::vector<not_null<HistoryItem*>> others;
|
||||||
|
};
|
||||||
|
|
||||||
class HistoryWebPage;
|
class HistoryWebPage;
|
||||||
|
|
||||||
// Special type of Component for the channel actions log.
|
// Special type of Component for the channel actions log.
|
||||||
|
@ -899,22 +928,8 @@ public:
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
int marginTop() const {
|
int marginTop() const;
|
||||||
int result = 0;
|
int marginBottom() const;
|
||||||
if (isAttachedToPrevious()) {
|
|
||||||
result += st::msgMarginTopAttached;
|
|
||||||
} else {
|
|
||||||
result += st::msgMargin.top();
|
|
||||||
}
|
|
||||||
result += displayedDateHeight();
|
|
||||||
if (auto unreadbar = Get<HistoryMessageUnreadBar>()) {
|
|
||||||
result += unreadbar->height();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
int marginBottom() const {
|
|
||||||
return st::msgMargin.bottom();
|
|
||||||
}
|
|
||||||
bool isAttachedToPrevious() const {
|
bool isAttachedToPrevious() const {
|
||||||
return _flags & MTPDmessage_ClientFlag::f_attach_to_previous;
|
return _flags & MTPDmessage_ClientFlag::f_attach_to_previous;
|
||||||
}
|
}
|
||||||
|
@ -932,6 +947,23 @@ public:
|
||||||
bool isEmpty() const {
|
bool isEmpty() const {
|
||||||
return _text.isEmpty() && !_media && !Has<HistoryMessageLogEntryOriginal>();
|
return _text.isEmpty() && !_media && !Has<HistoryMessageLogEntryOriginal>();
|
||||||
}
|
}
|
||||||
|
bool isHiddenByGroup() const {
|
||||||
|
return _flags & MTPDmessage_ClientFlag::f_hidden_by_group;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageGroupId groupId() const {
|
||||||
|
if (const auto group = Get<HistoryMessageGroup>()) {
|
||||||
|
return group->groupId;
|
||||||
|
}
|
||||||
|
return MessageGroupId::None;
|
||||||
|
}
|
||||||
|
bool groupIdValidityChanged();
|
||||||
|
void validateGroupId() {
|
||||||
|
// Just ignore the result.
|
||||||
|
groupIdValidityChanged();
|
||||||
|
}
|
||||||
|
void makeGroupMember(not_null<HistoryItem*> leader);
|
||||||
|
void makeGroupLeader(std::vector<not_null<HistoryItem*>> &&others);
|
||||||
|
|
||||||
int width() const {
|
int width() const {
|
||||||
return _width;
|
return _width;
|
||||||
|
@ -1070,6 +1102,8 @@ protected:
|
||||||
HistoryMediaPtr _media;
|
HistoryMediaPtr _media;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void resetGroupMedia(const std::vector<not_null<HistoryItem*>> &others);
|
||||||
|
|
||||||
int _y = 0;
|
int _y = 0;
|
||||||
int _width = 0;
|
int _width = 0;
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,9 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool isDisplayed() const {
|
virtual bool isDisplayed() const {
|
||||||
return true;
|
return !_parent->isHiddenByGroup();
|
||||||
|
}
|
||||||
|
virtual void updateNeedBubbleState() {
|
||||||
}
|
}
|
||||||
virtual bool isAboveMessage() const {
|
virtual bool isAboveMessage() const {
|
||||||
return false;
|
return false;
|
||||||
|
@ -132,7 +134,8 @@ public:
|
||||||
virtual bool uploading() const {
|
virtual bool uploading() const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
virtual std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const = 0;
|
virtual std::unique_ptr<HistoryMedia> clone(
|
||||||
|
not_null<HistoryItem*> newParent) const = 0;
|
||||||
|
|
||||||
virtual DocumentData *getDocument() {
|
virtual DocumentData *getDocument() {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -155,10 +158,40 @@ public:
|
||||||
|
|
||||||
virtual void attachToParent() {
|
virtual void attachToParent() {
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void detachFromParent() {
|
virtual void detachFromParent() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual bool canBeGrouped() const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
virtual QSize sizeForGrouping() const {
|
||||||
|
Unexpected("Grouping method call.");
|
||||||
|
}
|
||||||
|
virtual void drawGrouped(
|
||||||
|
Painter &p,
|
||||||
|
const QRect &clip,
|
||||||
|
TextSelection selection,
|
||||||
|
TimeMs ms,
|
||||||
|
const QRect &geometry,
|
||||||
|
RectParts corners,
|
||||||
|
not_null<uint64*> cacheKey,
|
||||||
|
not_null<QPixmap*> cache) const {
|
||||||
|
Unexpected("Grouping method call.");
|
||||||
|
}
|
||||||
|
virtual HistoryTextState getStateGrouped(
|
||||||
|
const QRect &geometry,
|
||||||
|
QPoint point,
|
||||||
|
HistoryStateRequest request) const {
|
||||||
|
Unexpected("Grouping method call.");
|
||||||
|
}
|
||||||
|
virtual std::unique_ptr<HistoryMedia> takeLastFromGroup() {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
virtual bool applyGroup(
|
||||||
|
const std::vector<not_null<HistoryItem*>> &others) {
|
||||||
|
return others.empty();
|
||||||
|
}
|
||||||
|
|
||||||
virtual void updateSentMedia(const MTPMessageMedia &media) {
|
virtual void updateSentMedia(const MTPMessageMedia &media) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,333 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||||
|
|
||||||
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
It is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
In addition, as a special exception, the copyright holders give permission
|
||||||
|
to link the code of portions of this program with the OpenSSL library.
|
||||||
|
|
||||||
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||||
|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
|
*/
|
||||||
|
#include "history/history_media_grouped.h"
|
||||||
|
|
||||||
|
#include "history/history_media_types.h"
|
||||||
|
#include "history/history_message.h"
|
||||||
|
#include "storage/storage_shared_media.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "ui/grouped_layout.h"
|
||||||
|
#include "styles/style_history.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
RectParts GetCornersFromSides(RectParts sides) {
|
||||||
|
const auto convert = [&](
|
||||||
|
RectPart side1,
|
||||||
|
RectPart side2,
|
||||||
|
RectPart corner) {
|
||||||
|
return ((sides & side1) && (sides & side2))
|
||||||
|
? corner
|
||||||
|
: RectPart::None;
|
||||||
|
};
|
||||||
|
return RectPart::None
|
||||||
|
| convert(RectPart::Top, RectPart::Left, RectPart::TopLeft)
|
||||||
|
| convert(RectPart::Top, RectPart::Right, RectPart::TopRight)
|
||||||
|
| convert(RectPart::Bottom, RectPart::Left, RectPart::BottomLeft)
|
||||||
|
| convert(RectPart::Bottom, RectPart::Right, RectPart::BottomRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HistoryGroupedMedia::Element::Element(not_null<HistoryItem*> item)
|
||||||
|
: item(item) {
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryGroupedMedia::HistoryGroupedMedia(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
const std::vector<not_null<HistoryItem*>> &others)
|
||||||
|
: HistoryMedia(parent) {
|
||||||
|
const auto result = applyGroup(others);
|
||||||
|
|
||||||
|
Ensures(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryGroupedMedia::initDimensions() {
|
||||||
|
std::vector<QSize> sizes;
|
||||||
|
sizes.reserve(_elements.size());
|
||||||
|
for (const auto &element : _elements) {
|
||||||
|
const auto &media = element.content;
|
||||||
|
media->initDimensions();
|
||||||
|
sizes.push_back(media->sizeForGrouping());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto layout = Data::LayoutMediaGroup(
|
||||||
|
sizes,
|
||||||
|
st::historyGroupWidthMax,
|
||||||
|
st::historyGroupWidthMin,
|
||||||
|
st::historyGroupSkip);
|
||||||
|
Assert(layout.size() == _elements.size());
|
||||||
|
|
||||||
|
_maxw = _minh = 0;
|
||||||
|
for (auto i = 0, count = int(layout.size()); i != count; ++i) {
|
||||||
|
const auto &item = layout[i];
|
||||||
|
accumulate_max(_maxw, item.geometry.x() + item.geometry.width());
|
||||||
|
accumulate_max(_minh, item.geometry.y() + item.geometry.height());
|
||||||
|
_elements[i].initialGeometry = item.geometry;
|
||||||
|
_elements[i].sides = item.sides;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int HistoryGroupedMedia::resizeGetHeight(int width) {
|
||||||
|
_width = width;
|
||||||
|
_height = 0;
|
||||||
|
if (_width < st::historyGroupWidthMin) {
|
||||||
|
return _height;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto initialSpacing = st::historyGroupSkip;
|
||||||
|
const auto factor = width / float64(st::historyGroupWidthMax);
|
||||||
|
const auto scale = [&](int value) {
|
||||||
|
return int(std::round(value * factor));
|
||||||
|
};
|
||||||
|
const auto spacing = scale(initialSpacing);
|
||||||
|
for (auto &element : _elements) {
|
||||||
|
const auto sides = element.sides;
|
||||||
|
const auto initialGeometry = element.initialGeometry;
|
||||||
|
const auto needRightSkip = !(sides & RectPart::Right);
|
||||||
|
const auto needBottomSkip = !(sides & RectPart::Bottom);
|
||||||
|
const auto initialLeft = initialGeometry.x();
|
||||||
|
const auto initialTop = initialGeometry.y();
|
||||||
|
const auto initialRight = initialLeft
|
||||||
|
+ initialGeometry.width()
|
||||||
|
+ (needRightSkip ? initialSpacing : 0);
|
||||||
|
const auto initialBottom = initialTop
|
||||||
|
+ initialGeometry.height()
|
||||||
|
+ (needBottomSkip ? initialSpacing : 0);
|
||||||
|
const auto left = scale(initialLeft);
|
||||||
|
const auto top = scale(initialTop);
|
||||||
|
const auto width = scale(initialRight)
|
||||||
|
- left
|
||||||
|
- (needRightSkip ? spacing : 0);
|
||||||
|
const auto height = scale(initialBottom)
|
||||||
|
- top
|
||||||
|
- (needBottomSkip ? spacing : 0);
|
||||||
|
element.geometry = QRect(left, top, width, height);
|
||||||
|
|
||||||
|
accumulate_max(_height, top + height);
|
||||||
|
}
|
||||||
|
return _height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryGroupedMedia::draw(
|
||||||
|
Painter &p,
|
||||||
|
const QRect &clip,
|
||||||
|
TextSelection selection,
|
||||||
|
TimeMs ms) const {
|
||||||
|
for (const auto &element : _elements) {
|
||||||
|
auto corners = GetCornersFromSides(element.sides);
|
||||||
|
if (!isBubbleTop()) {
|
||||||
|
corners &= ~(RectPart::TopLeft | RectPart::TopRight);
|
||||||
|
}
|
||||||
|
if (!isBubbleBottom() || !_caption.isEmpty()) {
|
||||||
|
corners &= ~(RectPart::BottomLeft | RectPart::BottomRight);
|
||||||
|
}
|
||||||
|
element.content->drawGrouped(
|
||||||
|
p,
|
||||||
|
clip,
|
||||||
|
selection,
|
||||||
|
ms,
|
||||||
|
element.geometry,
|
||||||
|
corners,
|
||||||
|
&element.cacheKey,
|
||||||
|
&element.cache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryTextState HistoryGroupedMedia::getState(
|
||||||
|
QPoint point,
|
||||||
|
HistoryStateRequest request) const {
|
||||||
|
for (const auto &element : _elements) {
|
||||||
|
if (element.geometry.contains(point)) {
|
||||||
|
return element.content->getStateGrouped(
|
||||||
|
element.geometry,
|
||||||
|
point,
|
||||||
|
request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return HistoryTextState();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HistoryGroupedMedia::toggleSelectionByHandlerClick(
|
||||||
|
const ClickHandlerPtr &p) const {
|
||||||
|
for (const auto &element : _elements) {
|
||||||
|
if (element.content->toggleSelectionByHandlerClick(p)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HistoryGroupedMedia::dragItemByHandler(const ClickHandlerPtr &p) const {
|
||||||
|
for (const auto &element : _elements) {
|
||||||
|
if (element.content->dragItemByHandler(p)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextSelection HistoryGroupedMedia::adjustSelection(
|
||||||
|
TextSelection selection,
|
||||||
|
TextSelectType type) const {
|
||||||
|
return _caption.adjustSelection(selection, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextWithEntities HistoryGroupedMedia::selectedText(
|
||||||
|
TextSelection selection) const {
|
||||||
|
return WithCaptionSelectedText(
|
||||||
|
lang(lng_in_dlg_album),
|
||||||
|
_caption,
|
||||||
|
selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryGroupedMedia::clickHandlerActiveChanged(
|
||||||
|
const ClickHandlerPtr &p,
|
||||||
|
bool active) {
|
||||||
|
for (const auto &element : _elements) {
|
||||||
|
element.content->clickHandlerActiveChanged(p, active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryGroupedMedia::clickHandlerPressedChanged(
|
||||||
|
const ClickHandlerPtr &p,
|
||||||
|
bool pressed) {
|
||||||
|
for (const auto &element : _elements) {
|
||||||
|
element.content->clickHandlerPressedChanged(p, pressed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryGroupedMedia::attachToParent() {
|
||||||
|
for (const auto &element : _elements) {
|
||||||
|
element.content->attachToParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryGroupedMedia::detachFromParent() {
|
||||||
|
for (const auto &element : _elements) {
|
||||||
|
if (element.content) {
|
||||||
|
element.content->detachFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<HistoryMedia> HistoryGroupedMedia::takeLastFromGroup() {
|
||||||
|
return std::move(_elements.back().content);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HistoryGroupedMedia::applyGroup(
|
||||||
|
const std::vector<not_null<HistoryItem*>> &others) {
|
||||||
|
if (others.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto pushElement = [&](not_null<HistoryItem*> item) {
|
||||||
|
const auto media = item->getMedia();
|
||||||
|
Assert(media != nullptr && media->canBeGrouped());
|
||||||
|
|
||||||
|
_elements.push_back(Element(item));
|
||||||
|
_elements.back().content = item->getMedia()->clone(_parent);
|
||||||
|
};
|
||||||
|
if (_elements.empty()) {
|
||||||
|
pushElement(_parent);
|
||||||
|
} else if (validateGroupElements(others)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're updating other elements, so we just need to preserve the main.
|
||||||
|
auto mainElement = std::move(_elements.back());
|
||||||
|
_elements.erase(_elements.begin(), _elements.end());
|
||||||
|
_elements.reserve(others.size() + 1);
|
||||||
|
for (const auto item : others) {
|
||||||
|
pushElement(item);
|
||||||
|
}
|
||||||
|
_elements.push_back(std::move(mainElement));
|
||||||
|
_parent->setPendingInitDimensions();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HistoryGroupedMedia::validateGroupElements(
|
||||||
|
const std::vector<not_null<HistoryItem*>> &others) const {
|
||||||
|
if (_elements.size() != others.size() + 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto i = 0, count = int(others.size()); i != count; ++i) {
|
||||||
|
if (_elements[i].item != others[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<HistoryMedia*> HistoryGroupedMedia::main() const {
|
||||||
|
Expects(!_elements.empty());
|
||||||
|
|
||||||
|
return _elements.back().content.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HistoryGroupedMedia::hasReplyPreview() const {
|
||||||
|
return main()->hasReplyPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImagePtr HistoryGroupedMedia::replyPreview() {
|
||||||
|
return main()->replyPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::SharedMediaTypesMask HistoryGroupedMedia::sharedMediaTypes() const {
|
||||||
|
return main()->sharedMediaTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryGroupedMedia::updateNeedBubbleState() {
|
||||||
|
auto captionText = [&] {
|
||||||
|
for (const auto &element : _elements) {
|
||||||
|
auto result = element.content->getCaption();
|
||||||
|
if (!result.text.isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TextWithEntities();
|
||||||
|
}();
|
||||||
|
_caption.setText(
|
||||||
|
st::messageTextStyle,
|
||||||
|
captionText.text + _parent->skipBlock(),
|
||||||
|
itemTextNoMonoOptions(_parent));
|
||||||
|
_needBubble = computeNeedBubble();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HistoryGroupedMedia::needsBubble() const {
|
||||||
|
return _needBubble;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HistoryGroupedMedia::computeNeedBubble() const {
|
||||||
|
if (!_caption.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (const auto &element : _elements) {
|
||||||
|
if (const auto message = element.item->toHistoryMessage()) {
|
||||||
|
if (message->viaBot()
|
||||||
|
|| message->Has<HistoryMessageForwarded>()
|
||||||
|
|| message->Has<HistoryMessageReply>()
|
||||||
|
|| message->displayFromName()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||||
|
|
||||||
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
It is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
In addition, as a special exception, the copyright holders give permission
|
||||||
|
to link the code of portions of this program with the OpenSSL library.
|
||||||
|
|
||||||
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||||
|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "history/history_media.h"
|
||||||
|
#include "data/data_document.h"
|
||||||
|
#include "data/data_photo.h"
|
||||||
|
|
||||||
|
class HistoryGroupedMedia : public HistoryMedia {
|
||||||
|
public:
|
||||||
|
HistoryGroupedMedia(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
const std::vector<not_null<HistoryItem*>> &others);
|
||||||
|
|
||||||
|
HistoryMediaType type() const override {
|
||||||
|
return MediaTypeGrouped;
|
||||||
|
}
|
||||||
|
std::unique_ptr<HistoryMedia> clone(
|
||||||
|
not_null<HistoryItem*> newParent) const override {
|
||||||
|
Unexpected("Clone HistoryGroupedMedia.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void initDimensions() override;
|
||||||
|
int resizeGetHeight(int width) override;
|
||||||
|
|
||||||
|
void draw(
|
||||||
|
Painter &p,
|
||||||
|
const QRect &clip,
|
||||||
|
TextSelection selection,
|
||||||
|
TimeMs ms) const override;
|
||||||
|
HistoryTextState getState(
|
||||||
|
QPoint point,
|
||||||
|
HistoryStateRequest request) const override;
|
||||||
|
|
||||||
|
bool toggleSelectionByHandlerClick(
|
||||||
|
const ClickHandlerPtr &p) const override;
|
||||||
|
bool dragItemByHandler(const ClickHandlerPtr &p) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] TextSelection adjustSelection(
|
||||||
|
TextSelection selection,
|
||||||
|
TextSelectType type) const override;
|
||||||
|
uint16 fullSelectionLength() const override {
|
||||||
|
return _caption.length();
|
||||||
|
}
|
||||||
|
bool hasTextForCopy() const override {
|
||||||
|
return !_caption.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextWithEntities selectedText(TextSelection selection) const override;
|
||||||
|
|
||||||
|
void clickHandlerActiveChanged(
|
||||||
|
const ClickHandlerPtr &p,
|
||||||
|
bool active) override;
|
||||||
|
void clickHandlerPressedChanged(
|
||||||
|
const ClickHandlerPtr &p,
|
||||||
|
bool pressed) override;
|
||||||
|
|
||||||
|
void attachToParent() override;
|
||||||
|
void detachFromParent() override;
|
||||||
|
std::unique_ptr<HistoryMedia> takeLastFromGroup() override;
|
||||||
|
bool applyGroup(
|
||||||
|
const std::vector<not_null<HistoryItem*>> &others) override;
|
||||||
|
|
||||||
|
bool hasReplyPreview() const override;
|
||||||
|
ImagePtr replyPreview() override;
|
||||||
|
|
||||||
|
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
|
||||||
|
bool canBeGrouped() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool skipBubbleTail() const override {
|
||||||
|
return isBubbleBottom() && _caption.isEmpty();
|
||||||
|
}
|
||||||
|
void updateNeedBubbleState() override;
|
||||||
|
bool needsBubble() const override;
|
||||||
|
bool customInfoLayout() const override {
|
||||||
|
return _caption.isEmpty();
|
||||||
|
}
|
||||||
|
bool allowsFastShare() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Element {
|
||||||
|
Element(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
|
not_null<HistoryItem*> item;
|
||||||
|
std::unique_ptr<HistoryMedia> content;
|
||||||
|
|
||||||
|
RectParts sides = RectPart::None;
|
||||||
|
QRect initialGeometry;
|
||||||
|
QRect geometry;
|
||||||
|
mutable uint64 cacheKey = 0;
|
||||||
|
mutable QPixmap cache;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
bool computeNeedBubble() const;
|
||||||
|
not_null<HistoryMedia*> main() const;
|
||||||
|
bool validateGroupElements(
|
||||||
|
const std::vector<not_null<HistoryItem*>> &others) const;
|
||||||
|
|
||||||
|
Text _caption;
|
||||||
|
std::vector<Element> _elements;
|
||||||
|
bool _needBubble = false;
|
||||||
|
|
||||||
|
};
|
|
@ -95,14 +95,6 @@ bool needReSetInlineResultDocument(const MTPMessageMedia &media, DocumentData *e
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void HistoryInitMedia() {
|
|
||||||
initTextOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
int32 documentMaxStatusWidth(DocumentData *document) {
|
int32 documentMaxStatusWidth(DocumentData *document) {
|
||||||
int32 result = st::normalFont->width(formatDownloadText(document->size, document->size));
|
int32 result = st::normalFont->width(formatDownloadText(document->size, document->size));
|
||||||
if (const auto song = document->song()) {
|
if (const auto song = document->song()) {
|
||||||
|
@ -125,7 +117,43 @@ int32 gifMaxStatusWidth(DocumentData *document) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities captionedSelectedText(const QString &attachType, const Text &caption, TextSelection selection) {
|
QSize CountPixSizeForSize(QSize original, QSize geometry) {
|
||||||
|
const auto width = geometry.width();
|
||||||
|
const auto height = geometry.height();
|
||||||
|
auto tw = original.width();
|
||||||
|
auto th = original.height();
|
||||||
|
if (tw * height > th * width) {
|
||||||
|
if (tw * height < 2 * th * width) {
|
||||||
|
tw = (height * tw) / th;
|
||||||
|
th = height;
|
||||||
|
} else if (tw < width) {
|
||||||
|
th = (width * th) / tw;
|
||||||
|
tw = width;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (th * width < 2 * tw * height) {
|
||||||
|
th = (width * th) / tw;
|
||||||
|
tw = width;
|
||||||
|
} else if (tw > 0 && th < height) {
|
||||||
|
tw = (height * tw) / th;
|
||||||
|
th = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tw < 1) tw = 1;
|
||||||
|
if (th < 1) th = 1;
|
||||||
|
return { tw, th };
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void HistoryInitMedia() {
|
||||||
|
initTextOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextWithEntities WithCaptionSelectedText(
|
||||||
|
const QString &attachType,
|
||||||
|
const Text &caption,
|
||||||
|
TextSelection selection) {
|
||||||
if (selection != FullSelection) {
|
if (selection != FullSelection) {
|
||||||
return caption.originalTextWithEntities(selection, ExpandLinksAll);
|
return caption.originalTextWithEntities(selection, ExpandLinksAll);
|
||||||
}
|
}
|
||||||
|
@ -143,7 +171,9 @@ TextWithEntities captionedSelectedText(const QString &attachType, const Text &ca
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString captionedNotificationText(const QString &attachType, const Text &caption) {
|
QString WithCaptionNotificationText(
|
||||||
|
const QString &attachType,
|
||||||
|
const Text &caption) {
|
||||||
if (caption.isEmpty()) {
|
if (caption.isEmpty()) {
|
||||||
return attachType;
|
return attachType;
|
||||||
}
|
}
|
||||||
|
@ -153,7 +183,9 @@ QString captionedNotificationText(const QString &attachType, const Text &caption
|
||||||
return lng_dialogs_text_media(lt_media_part, attachTypeWrapped, lt_caption, captionText);
|
return lng_dialogs_text_media(lt_media_part, attachTypeWrapped, lt_caption, captionText);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString captionedInDialogsText(const QString &attachType, const Text &caption) {
|
QString WithCaptionDialogsText(
|
||||||
|
const QString &attachType,
|
||||||
|
const Text &caption) {
|
||||||
if (caption.isEmpty()) {
|
if (caption.isEmpty()) {
|
||||||
return textcmdLink(1, TextUtilities::Clean(attachType));
|
return textcmdLink(1, TextUtilities::Clean(attachType));
|
||||||
}
|
}
|
||||||
|
@ -163,8 +195,6 @@ QString captionedInDialogsText(const QString &attachType, const Text &caption) {
|
||||||
return lng_dialogs_text_media(lt_media_part, attachTypeWrapped, lt_caption, captionText);
|
return lng_dialogs_text_media(lt_media_part, attachTypeWrapped, lt_caption, captionText);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
||||||
if (p == _savel || p == _cancell) {
|
if (p == _savel || p == _cancell) {
|
||||||
if (active && !dataLoaded()) {
|
if (active && !dataLoaded()) {
|
||||||
|
@ -184,7 +214,10 @@ void HistoryFileMedia::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool
|
||||||
Auth().data().requestItemRepaint(_parent);
|
Auth().data().requestItemRepaint(_parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryFileMedia::setLinks(ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell) {
|
void HistoryFileMedia::setLinks(
|
||||||
|
ClickHandlerPtr &&openl,
|
||||||
|
ClickHandlerPtr &&savel,
|
||||||
|
ClickHandlerPtr &&cancell) {
|
||||||
_openl = std::move(openl);
|
_openl = std::move(openl);
|
||||||
_savel = std::move(savel);
|
_savel = std::move(savel);
|
||||||
_cancell = std::move(cancell);
|
_cancell = std::move(cancell);
|
||||||
|
@ -232,33 +265,59 @@ void HistoryFileMedia::checkAnimationFinished() const {
|
||||||
|
|
||||||
HistoryFileMedia::~HistoryFileMedia() = default;
|
HistoryFileMedia::~HistoryFileMedia() = default;
|
||||||
|
|
||||||
HistoryPhoto::HistoryPhoto(not_null<HistoryItem*> parent, not_null<PhotoData*> photo, const QString &caption) : HistoryFileMedia(parent)
|
HistoryPhoto::HistoryPhoto(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<PhotoData*> photo,
|
||||||
|
const QString &caption)
|
||||||
|
: HistoryFileMedia(parent)
|
||||||
, _data(photo)
|
, _data(photo)
|
||||||
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
|
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
|
||||||
setLinks(MakeShared<PhotoOpenClickHandler>(_data), MakeShared<PhotoSaveClickHandler>(_data), MakeShared<PhotoCancelClickHandler>(_data));
|
setLinks(
|
||||||
|
MakeShared<PhotoOpenClickHandler>(_data),
|
||||||
|
MakeShared<PhotoSaveClickHandler>(_data),
|
||||||
|
MakeShared<PhotoCancelClickHandler>(_data));
|
||||||
if (!caption.isEmpty()) {
|
if (!caption.isEmpty()) {
|
||||||
_caption.setText(st::messageTextStyle, caption + _parent->skipBlock(), itemTextNoMonoOptions(_parent));
|
_caption.setText(st::messageTextStyle, caption + _parent->skipBlock(), itemTextNoMonoOptions(_parent));
|
||||||
}
|
}
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryPhoto::HistoryPhoto(not_null<HistoryItem*> parent, not_null<PeerData*> chat, not_null<PhotoData*> photo, int32 width) : HistoryFileMedia(parent)
|
HistoryPhoto::HistoryPhoto(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<PeerData*> chat,
|
||||||
|
not_null<PhotoData*> photo,
|
||||||
|
int width)
|
||||||
|
: HistoryFileMedia(parent)
|
||||||
, _data(photo) {
|
, _data(photo) {
|
||||||
setLinks(MakeShared<PhotoOpenClickHandler>(_data, chat), MakeShared<PhotoSaveClickHandler>(_data, chat), MakeShared<PhotoCancelClickHandler>(_data, chat));
|
setLinks(
|
||||||
|
MakeShared<PhotoOpenClickHandler>(_data, chat),
|
||||||
|
MakeShared<PhotoSaveClickHandler>(_data, chat),
|
||||||
|
MakeShared<PhotoCancelClickHandler>(_data, chat));
|
||||||
|
|
||||||
_width = width;
|
_width = width;
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryPhoto::HistoryPhoto(not_null<HistoryItem*> parent, not_null<PeerData*> chat, const MTPDphoto &photo, int32 width) : HistoryPhoto(parent, chat, App::feedPhoto(photo), width) {
|
HistoryPhoto::HistoryPhoto(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<PeerData*> chat,
|
||||||
|
const MTPDphoto &photo,
|
||||||
|
int width)
|
||||||
|
: HistoryPhoto(parent, chat, App::feedPhoto(photo), width) {
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryPhoto::HistoryPhoto(not_null<HistoryItem*> parent, const HistoryPhoto &other) : HistoryFileMedia(parent)
|
HistoryPhoto::HistoryPhoto(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
const HistoryPhoto &other)
|
||||||
|
: HistoryFileMedia(parent)
|
||||||
, _data(other._data)
|
, _data(other._data)
|
||||||
, _pixw(other._pixw)
|
, _pixw(other._pixw)
|
||||||
, _pixh(other._pixh)
|
, _pixh(other._pixh)
|
||||||
, _caption(other._caption) {
|
, _caption(other._caption) {
|
||||||
setLinks(MakeShared<PhotoOpenClickHandler>(_data), MakeShared<PhotoSaveClickHandler>(_data), MakeShared<PhotoCancelClickHandler>(_data));
|
setLinks(
|
||||||
|
MakeShared<PhotoOpenClickHandler>(_data),
|
||||||
|
MakeShared<PhotoSaveClickHandler>(_data),
|
||||||
|
MakeShared<PhotoCancelClickHandler>(_data));
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
@ -378,7 +437,6 @@ void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, Tim
|
||||||
bool radial = isRadialAnimation(ms);
|
bool radial = isRadialAnimation(ms);
|
||||||
|
|
||||||
auto rthumb = rtlrect(skipx, skipy, width, height, _width);
|
auto rthumb = rtlrect(skipx, skipy, width, height, _width);
|
||||||
QPixmap pix;
|
|
||||||
if (_parent->toHistoryMessage()) {
|
if (_parent->toHistoryMessage()) {
|
||||||
if (bubble) {
|
if (bubble) {
|
||||||
skipx = st::mediaPadding.left();
|
skipx = st::mediaPadding.left();
|
||||||
|
@ -400,21 +458,17 @@ void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, Tim
|
||||||
auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
|
auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
|
||||||
auto roundCorners = inWebPage ? ImageRoundCorner::All : ((isBubbleTop() ? (ImageRoundCorner::TopLeft | ImageRoundCorner::TopRight) : ImageRoundCorner::None)
|
auto roundCorners = inWebPage ? ImageRoundCorner::All : ((isBubbleTop() ? (ImageRoundCorner::TopLeft | ImageRoundCorner::TopRight) : ImageRoundCorner::None)
|
||||||
| ((isBubbleBottom() && _caption.isEmpty()) ? (ImageRoundCorner::BottomLeft | ImageRoundCorner::BottomRight) : ImageRoundCorner::None));
|
| ((isBubbleBottom() && _caption.isEmpty()) ? (ImageRoundCorner::BottomLeft | ImageRoundCorner::BottomRight) : ImageRoundCorner::None));
|
||||||
if (loaded) {
|
const auto pix = loaded
|
||||||
pix = _data->full->pixSingle(_pixw, _pixh, width, height, roundRadius, roundCorners);
|
? _data->full->pixSingle(_pixw, _pixh, width, height, roundRadius, roundCorners)
|
||||||
} else {
|
: _data->thumb->pixBlurredSingle(_pixw, _pixh, width, height, roundRadius, roundCorners);
|
||||||
pix = _data->thumb->pixBlurredSingle(_pixw, _pixh, width, height, roundRadius, roundCorners);
|
|
||||||
}
|
|
||||||
p.drawPixmap(rthumb.topLeft(), pix);
|
p.drawPixmap(rthumb.topLeft(), pix);
|
||||||
if (selected) {
|
if (selected) {
|
||||||
App::complexOverlayRect(p, rthumb, roundRadius, roundCorners);
|
App::complexOverlayRect(p, rthumb, roundRadius, roundCorners);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (loaded) {
|
const auto pix = loaded
|
||||||
pix = _data->full->pixCircled(_pixw, _pixh);
|
? _data->full->pixCircled(_pixw, _pixh)
|
||||||
} else {
|
: _data->thumb->pixBlurredCircled(_pixw, _pixh);
|
||||||
pix = _data->thumb->pixBlurredCircled(_pixw, _pixh);
|
|
||||||
}
|
|
||||||
p.drawPixmap(rthumb.topLeft(), pix);
|
p.drawPixmap(rthumb.topLeft(), pix);
|
||||||
}
|
}
|
||||||
if (radial || (!loaded && !_data->loading())) {
|
if (radial || (!loaded && !_data->loading())) {
|
||||||
|
@ -534,6 +588,163 @@ HistoryTextState HistoryPhoto::getState(QPoint point, HistoryStateRequest reques
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSize HistoryPhoto::sizeForGrouping() const {
|
||||||
|
const auto width = convertScale(_data->full->width());
|
||||||
|
const auto height = convertScale(_data->full->height());
|
||||||
|
return { std::max(width, 1), std::max(height, 1) };
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryPhoto::drawGrouped(
|
||||||
|
Painter &p,
|
||||||
|
const QRect &clip,
|
||||||
|
TextSelection selection,
|
||||||
|
TimeMs ms,
|
||||||
|
const QRect &geometry,
|
||||||
|
RectParts corners,
|
||||||
|
not_null<uint64*> cacheKey,
|
||||||
|
not_null<QPixmap*> cache) const {
|
||||||
|
_data->automaticLoad(_parent);
|
||||||
|
|
||||||
|
validateGroupedCache(geometry, corners, cacheKey, cache);
|
||||||
|
|
||||||
|
const auto selected = (selection == FullSelection);
|
||||||
|
const auto loaded = _data->loaded();
|
||||||
|
const auto displayLoading = _data->displayLoading();
|
||||||
|
const auto bubble = _parent->hasBubble();
|
||||||
|
|
||||||
|
if (displayLoading) {
|
||||||
|
ensureAnimation();
|
||||||
|
if (!_animation->radial.animating()) {
|
||||||
|
_animation->radial.start(_data->progress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto radial = isRadialAnimation(ms);
|
||||||
|
|
||||||
|
if (!bubble) {
|
||||||
|
// App::roundShadow(p, 0, 0, width, height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
|
||||||
|
}
|
||||||
|
p.drawPixmap(geometry.topLeft(), *cache);
|
||||||
|
if (selected) {
|
||||||
|
const auto roundRadius = ImageRoundRadius::Large;
|
||||||
|
const auto roundCorners = ImageRoundCorner::None
|
||||||
|
| ((corners & RectPart::TopLeft) ? ImageRoundCorner::TopLeft : ImageRoundCorner::None)
|
||||||
|
| ((corners & RectPart::TopRight) ? ImageRoundCorner::TopLeft : ImageRoundCorner::None)
|
||||||
|
| ((corners & RectPart::BottomLeft) ? ImageRoundCorner::TopLeft : ImageRoundCorner::None)
|
||||||
|
| ((corners & RectPart::BottomRight) ? ImageRoundCorner::TopLeft : ImageRoundCorner::None);
|
||||||
|
App::complexOverlayRect(p, geometry, roundRadius, roundCorners);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (radial || (!loaded && !_data->loading())) {
|
||||||
|
const auto radialOpacity = (radial && loaded && !_data->uploading())
|
||||||
|
? _animation->radial.opacity()
|
||||||
|
: 1.;
|
||||||
|
const auto radialSize = st::historyGroupRadialSize;
|
||||||
|
const auto inner = QRect(
|
||||||
|
geometry.x() + (geometry.width() - radialSize) / 2,
|
||||||
|
geometry.y() + (geometry.height() - radialSize) / 2,
|
||||||
|
radialSize,
|
||||||
|
radialSize);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
if (selected) {
|
||||||
|
p.setBrush(st::msgDateImgBgSelected);
|
||||||
|
} else if (isThumbAnimation(ms)) {
|
||||||
|
auto over = _animation->a_thumbOver.current();
|
||||||
|
p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over));
|
||||||
|
} else {
|
||||||
|
auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
|
||||||
|
p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.setOpacity(radialOpacity * p.opacity());
|
||||||
|
|
||||||
|
{
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
p.drawEllipse(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.setOpacity(radialOpacity);
|
||||||
|
auto icon = ([radial, this, selected]() -> const style::icon*{
|
||||||
|
if (radial || _data->loading()) {
|
||||||
|
auto delayed = _data->full->toDelayedStorageImage();
|
||||||
|
if (!delayed || !delayed->location().isNull()) {
|
||||||
|
return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
|
||||||
|
})();
|
||||||
|
if (icon) {
|
||||||
|
icon->paintInCenter(p, inner);
|
||||||
|
}
|
||||||
|
p.setOpacity(1);
|
||||||
|
if (radial) {
|
||||||
|
const auto line = st::historyGroupRadialLine;
|
||||||
|
const auto rinner = inner.marginsRemoved({ line, line, line, line });
|
||||||
|
const auto color = selected
|
||||||
|
? st::historyFileThumbRadialFgSelected
|
||||||
|
: st::historyFileThumbRadialFg;
|
||||||
|
_animation->radial.draw(p, rinner, line, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryTextState HistoryPhoto::getStateGrouped(
|
||||||
|
const QRect &geometry,
|
||||||
|
QPoint point,
|
||||||
|
HistoryStateRequest request) const {
|
||||||
|
if (!geometry.contains(point)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto delayed = _data->full->toDelayedStorageImage();
|
||||||
|
return _data->uploading()
|
||||||
|
? _cancell
|
||||||
|
: _data->loaded()
|
||||||
|
? _openl
|
||||||
|
: _data->loading()
|
||||||
|
? ((!delayed || !delayed->location().isNull())
|
||||||
|
? _cancell
|
||||||
|
: ClickHandlerPtr())
|
||||||
|
: _savel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryPhoto::validateGroupedCache(
|
||||||
|
const QRect &geometry,
|
||||||
|
RectParts corners,
|
||||||
|
not_null<uint64*> cacheKey,
|
||||||
|
not_null<QPixmap*> cache) const {
|
||||||
|
using Option = Images::Option;
|
||||||
|
const auto loaded = _data->loaded();
|
||||||
|
const auto loadLevel = loaded ? 2 : _data->thumb->loaded() ? 1 : 0;
|
||||||
|
const auto width = geometry.width();
|
||||||
|
const auto height = geometry.height();
|
||||||
|
const auto options = Option::Smooth
|
||||||
|
| Option::RoundedLarge
|
||||||
|
| (loaded ? Option::None : Option::Blurred)
|
||||||
|
| ((corners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None)
|
||||||
|
| ((corners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None)
|
||||||
|
| ((corners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None)
|
||||||
|
| ((corners & RectPart::BottomRight) ? Option::RoundedBottomRight : Option::None);
|
||||||
|
const auto key = (uint64(width) << 48)
|
||||||
|
| (uint64(height) << 32)
|
||||||
|
| (uint64(options) << 16)
|
||||||
|
| (uint64(loadLevel));
|
||||||
|
if (*cacheKey == key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto originalWidth = convertScale(_data->full->width());
|
||||||
|
const auto originalHeight = convertScale(_data->full->height());
|
||||||
|
const auto pixSize = CountPixSizeForSize(
|
||||||
|
{ originalWidth, originalHeight },
|
||||||
|
{ width, height });
|
||||||
|
const auto pixWidth = pixSize.width();
|
||||||
|
const auto pixHeight = pixSize.height();
|
||||||
|
const auto &image = loaded ? _data->full : _data->thumb;
|
||||||
|
|
||||||
|
*cacheKey = key;
|
||||||
|
*cache = image->pixNoCache(pixWidth, pixHeight, options, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryPhoto::updateSentMedia(const MTPMessageMedia &media) {
|
void HistoryPhoto::updateSentMedia(const MTPMessageMedia &media) {
|
||||||
if (media.type() == mtpc_messageMediaPhoto) {
|
if (media.type() == mtpc_messageMediaPhoto) {
|
||||||
auto &mediaPhoto = media.c_messageMediaPhoto();
|
auto &mediaPhoto = media.c_messageMediaPhoto();
|
||||||
|
@ -614,15 +825,18 @@ void HistoryPhoto::detachFromParent() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HistoryPhoto::notificationText() const {
|
QString HistoryPhoto::notificationText() const {
|
||||||
return captionedNotificationText(lang(lng_in_dlg_photo), _caption);
|
return WithCaptionNotificationText(lang(lng_in_dlg_photo), _caption);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HistoryPhoto::inDialogsText() const {
|
QString HistoryPhoto::inDialogsText() const {
|
||||||
return captionedInDialogsText(lang(lng_in_dlg_photo), _caption);
|
return WithCaptionDialogsText(lang(lng_in_dlg_photo), _caption);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities HistoryPhoto::selectedText(TextSelection selection) const {
|
TextWithEntities HistoryPhoto::selectedText(TextSelection selection) const {
|
||||||
return captionedSelectedText(lang(lng_in_dlg_photo), _caption, selection);
|
return WithCaptionSelectedText(
|
||||||
|
lang(lng_in_dlg_photo),
|
||||||
|
_caption,
|
||||||
|
selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryPhoto::needsBubble() const {
|
bool HistoryPhoto::needsBubble() const {
|
||||||
|
@ -649,7 +863,11 @@ ImagePtr HistoryPhoto::replyPreview() {
|
||||||
return _data->makeReplyPreview();
|
return _data->makeReplyPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryVideo::HistoryVideo(not_null<HistoryItem*> parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent)
|
HistoryVideo::HistoryVideo(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<DocumentData*> document,
|
||||||
|
const QString &caption)
|
||||||
|
: HistoryFileMedia(parent)
|
||||||
, _data(document)
|
, _data(document)
|
||||||
, _thumbw(1)
|
, _thumbw(1)
|
||||||
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
|
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
|
||||||
|
@ -664,7 +882,10 @@ HistoryVideo::HistoryVideo(not_null<HistoryItem*> parent, DocumentData *document
|
||||||
_data->thumb->load();
|
_data->thumb->load();
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryVideo::HistoryVideo(not_null<HistoryItem*> parent, const HistoryVideo &other) : HistoryFileMedia(parent)
|
HistoryVideo::HistoryVideo(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
const HistoryVideo &other)
|
||||||
|
: HistoryFileMedia(parent)
|
||||||
, _data(other._data)
|
, _data(other._data)
|
||||||
, _thumbw(other._thumbw)
|
, _thumbw(other._thumbw)
|
||||||
, _caption(other._caption) {
|
, _caption(other._caption) {
|
||||||
|
@ -864,10 +1085,11 @@ void HistoryVideo::draw(Painter &p, const QRect &r, TextSelection selection, Tim
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryTextState HistoryVideo::getState(QPoint point, HistoryStateRequest request) const {
|
HistoryTextState HistoryVideo::getState(QPoint point, HistoryStateRequest request) const {
|
||||||
|
if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
HistoryTextState result;
|
HistoryTextState result;
|
||||||
|
|
||||||
if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result;
|
|
||||||
|
|
||||||
bool loaded = _data->loaded();
|
bool loaded = _data->loaded();
|
||||||
|
|
||||||
int32 skipx = 0, skipy = 0, width = _width, height = _height;
|
int32 skipx = 0, skipy = 0, width = _width, height = _height;
|
||||||
|
@ -914,20 +1136,176 @@ HistoryTextState HistoryVideo::getState(QPoint point, HistoryStateRequest reques
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSize HistoryVideo::sizeForGrouping() const {
|
||||||
|
const auto width = convertScale(_data->thumb->width());
|
||||||
|
const auto height = convertScale(_data->thumb->height());
|
||||||
|
return { std::max(width, 1), std::max(height, 1) };
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryVideo::drawGrouped(
|
||||||
|
Painter &p,
|
||||||
|
const QRect &clip,
|
||||||
|
TextSelection selection,
|
||||||
|
TimeMs ms,
|
||||||
|
const QRect &geometry,
|
||||||
|
RectParts corners,
|
||||||
|
not_null<uint64*> cacheKey,
|
||||||
|
not_null<QPixmap*> cache) const {
|
||||||
|
_data->automaticLoad(_parent);
|
||||||
|
|
||||||
|
validateGroupedCache(geometry, corners, cacheKey, cache);
|
||||||
|
|
||||||
|
const auto selected = (selection == FullSelection);
|
||||||
|
const auto loaded = _data->loaded();
|
||||||
|
const auto displayLoading = _data->displayLoading();
|
||||||
|
const auto bubble = _parent->hasBubble();
|
||||||
|
|
||||||
|
if (displayLoading) {
|
||||||
|
ensureAnimation();
|
||||||
|
if (!_animation->radial.animating()) {
|
||||||
|
_animation->radial.start(_data->progress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto radial = isRadialAnimation(ms);
|
||||||
|
|
||||||
|
if (!bubble) {
|
||||||
|
// App::roundShadow(p, 0, 0, width, height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
|
||||||
|
}
|
||||||
|
p.drawPixmap(geometry.topLeft(), *cache);
|
||||||
|
if (selected) {
|
||||||
|
const auto roundRadius = ImageRoundRadius::Large;
|
||||||
|
const auto roundCorners = ImageRoundCorner::None
|
||||||
|
| ((corners & RectPart::TopLeft) ? ImageRoundCorner::TopLeft : ImageRoundCorner::None)
|
||||||
|
| ((corners & RectPart::TopRight) ? ImageRoundCorner::TopLeft : ImageRoundCorner::None)
|
||||||
|
| ((corners & RectPart::BottomLeft) ? ImageRoundCorner::TopLeft : ImageRoundCorner::None)
|
||||||
|
| ((corners & RectPart::BottomRight) ? ImageRoundCorner::TopLeft : ImageRoundCorner::None);
|
||||||
|
App::complexOverlayRect(p, geometry, roundRadius, roundCorners);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto radialOpacity = (radial && loaded && !_data->uploading())
|
||||||
|
? _animation->radial.opacity()
|
||||||
|
: 1.;
|
||||||
|
const auto radialSize = st::historyGroupRadialSize;
|
||||||
|
const auto inner = QRect(
|
||||||
|
geometry.x() + (geometry.width() - radialSize) / 2,
|
||||||
|
geometry.y() + (geometry.height() - radialSize) / 2,
|
||||||
|
radialSize,
|
||||||
|
radialSize);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
if (selected) {
|
||||||
|
p.setBrush(st::msgDateImgBgSelected);
|
||||||
|
} else if (isThumbAnimation(ms)) {
|
||||||
|
auto over = _animation->a_thumbOver.current();
|
||||||
|
p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over));
|
||||||
|
} else {
|
||||||
|
auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
|
||||||
|
p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.setOpacity(radialOpacity * p.opacity());
|
||||||
|
|
||||||
|
{
|
||||||
|
PainterHighQualityEnabler hq(p);
|
||||||
|
p.drawEllipse(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.setOpacity(radialOpacity);
|
||||||
|
auto icon = ([this, radial, selected, loaded]() -> const style::icon * {
|
||||||
|
if (loaded && !radial) {
|
||||||
|
return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
|
||||||
|
} else if (radial || _data->loading()) {
|
||||||
|
if (_parent->id > 0 || _data->uploading()) {
|
||||||
|
return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
|
||||||
|
})();
|
||||||
|
if (icon) {
|
||||||
|
icon->paintInCenter(p, inner);
|
||||||
|
}
|
||||||
|
p.setOpacity(1);
|
||||||
|
if (radial) {
|
||||||
|
const auto line = st::historyGroupRadialLine;
|
||||||
|
const auto rinner = inner.marginsRemoved({ line, line, line, line });
|
||||||
|
const auto color = selected
|
||||||
|
? st::historyFileThumbRadialFgSelected
|
||||||
|
: st::historyFileThumbRadialFg;
|
||||||
|
_animation->radial.draw(p, rinner, line, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryTextState HistoryVideo::getStateGrouped(
|
||||||
|
const QRect &geometry,
|
||||||
|
QPoint point,
|
||||||
|
HistoryStateRequest request) const {
|
||||||
|
if (!geometry.contains(point)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return _data->uploading()
|
||||||
|
? _cancell
|
||||||
|
: _data->loaded()
|
||||||
|
? _openl
|
||||||
|
: _data->loading()
|
||||||
|
? _cancell
|
||||||
|
: _savel;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryVideo::validateGroupedCache(
|
||||||
|
const QRect &geometry,
|
||||||
|
RectParts corners,
|
||||||
|
not_null<uint64*> cacheKey,
|
||||||
|
not_null<QPixmap*> cache) const {
|
||||||
|
using Option = Images::Option;
|
||||||
|
const auto loaded = _data->thumb->loaded();
|
||||||
|
const auto loadLevel = loaded ? 1 : 0;
|
||||||
|
const auto width = geometry.width();
|
||||||
|
const auto height = geometry.height();
|
||||||
|
const auto options = Option::Smooth
|
||||||
|
| Option::RoundedLarge
|
||||||
|
| Option::Blurred
|
||||||
|
| ((corners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None)
|
||||||
|
| ((corners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None)
|
||||||
|
| ((corners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None)
|
||||||
|
| ((corners & RectPart::BottomRight) ? Option::RoundedBottomRight : Option::None);
|
||||||
|
const auto key = (uint64(width) << 48)
|
||||||
|
| (uint64(height) << 32)
|
||||||
|
| (uint64(options) << 16)
|
||||||
|
| (uint64(loadLevel));
|
||||||
|
if (*cacheKey == key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto originalWidth = convertScale(_data->thumb->width());
|
||||||
|
const auto originalHeight = convertScale(_data->thumb->height());
|
||||||
|
const auto pixSize = CountPixSizeForSize(
|
||||||
|
{ originalWidth, originalHeight },
|
||||||
|
{ width, height });
|
||||||
|
const auto pixWidth = pixSize.width();
|
||||||
|
const auto pixHeight = pixSize.height();
|
||||||
|
const auto &image = _data->thumb;
|
||||||
|
|
||||||
|
*cacheKey = key;
|
||||||
|
*cache = image->pixNoCache(pixWidth, pixHeight, options, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryVideo::setStatusSize(int32 newSize) const {
|
void HistoryVideo::setStatusSize(int32 newSize) const {
|
||||||
HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration(), 0);
|
HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HistoryVideo::notificationText() const {
|
QString HistoryVideo::notificationText() const {
|
||||||
return captionedNotificationText(lang(lng_in_dlg_video), _caption);
|
return WithCaptionNotificationText(lang(lng_in_dlg_video), _caption);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HistoryVideo::inDialogsText() const {
|
QString HistoryVideo::inDialogsText() const {
|
||||||
return captionedInDialogsText(lang(lng_in_dlg_video), _caption);
|
return WithCaptionDialogsText(lang(lng_in_dlg_video), _caption);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities HistoryVideo::selectedText(TextSelection selection) const {
|
TextWithEntities HistoryVideo::selectedText(TextSelection selection) const {
|
||||||
return captionedSelectedText(lang(lng_in_dlg_video), _caption, selection);
|
return WithCaptionSelectedText(
|
||||||
|
lang(lng_in_dlg_video),
|
||||||
|
_caption,
|
||||||
|
selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryVideo::needsBubble() const {
|
bool HistoryVideo::needsBubble() const {
|
||||||
|
@ -1036,7 +1414,11 @@ void HistoryDocumentVoice::stopSeeking() {
|
||||||
Media::Player::instance()->stopSeeking(AudioMsgId::Type::Voice);
|
Media::Player::instance()->stopSeeking(AudioMsgId::Type::Voice);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryDocument::HistoryDocument(not_null<HistoryItem*> parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent)
|
HistoryDocument::HistoryDocument(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<DocumentData*> document,
|
||||||
|
const QString &caption)
|
||||||
|
: HistoryFileMedia(parent)
|
||||||
, _data(document) {
|
, _data(document) {
|
||||||
createComponents(!caption.isEmpty());
|
createComponents(!caption.isEmpty());
|
||||||
if (auto named = Get<HistoryDocumentNamed>()) {
|
if (auto named = Get<HistoryDocumentNamed>()) {
|
||||||
|
@ -1056,7 +1438,6 @@ HistoryDocument::HistoryDocument(
|
||||||
not_null<HistoryItem*> parent,
|
not_null<HistoryItem*> parent,
|
||||||
const HistoryDocument &other)
|
const HistoryDocument &other)
|
||||||
: HistoryFileMedia(parent)
|
: HistoryFileMedia(parent)
|
||||||
, RuntimeComposer()
|
|
||||||
, _data(other._data) {
|
, _data(other._data) {
|
||||||
auto captioned = other.Get<HistoryDocumentCaptioned>();
|
auto captioned = other.Get<HistoryDocumentCaptioned>();
|
||||||
createComponents(captioned != 0);
|
createComponents(captioned != 0);
|
||||||
|
@ -1537,7 +1918,9 @@ void HistoryDocument::updatePressed(QPoint point) {
|
||||||
QString HistoryDocument::notificationText() const {
|
QString HistoryDocument::notificationText() const {
|
||||||
QString result;
|
QString result;
|
||||||
buildStringRepresentation([&result](const QString &type, const QString &fileName, const Text &caption) {
|
buildStringRepresentation([&result](const QString &type, const QString &fileName, const Text &caption) {
|
||||||
result = captionedNotificationText(fileName.isEmpty() ? type : fileName, caption);
|
result = WithCaptionNotificationText(
|
||||||
|
fileName.isEmpty() ? type : fileName,
|
||||||
|
caption);
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1545,7 +1928,9 @@ QString HistoryDocument::notificationText() const {
|
||||||
QString HistoryDocument::inDialogsText() const {
|
QString HistoryDocument::inDialogsText() const {
|
||||||
QString result;
|
QString result;
|
||||||
buildStringRepresentation([&result](const QString &type, const QString &fileName, const Text &caption) {
|
buildStringRepresentation([&result](const QString &type, const QString &fileName, const Text &caption) {
|
||||||
result = captionedInDialogsText(fileName.isEmpty() ? type : fileName, caption);
|
result = WithCaptionDialogsText(
|
||||||
|
fileName.isEmpty() ? type : fileName,
|
||||||
|
caption);
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1557,7 +1942,7 @@ TextWithEntities HistoryDocument::selectedText(TextSelection selection) const {
|
||||||
if (!fileName.isEmpty()) {
|
if (!fileName.isEmpty()) {
|
||||||
fullType.append(qstr(" : ")).append(fileName);
|
fullType.append(qstr(" : ")).append(fileName);
|
||||||
}
|
}
|
||||||
result = captionedSelectedText(fullType, caption, selection);
|
result = WithCaptionSelectedText(fullType, caption, selection);
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1771,7 +2156,11 @@ ImagePtr HistoryDocument::replyPreview() {
|
||||||
return _data->makeReplyPreview();
|
return _data->makeReplyPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryGif::HistoryGif(not_null<HistoryItem*> parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent)
|
HistoryGif::HistoryGif(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<DocumentData*> document,
|
||||||
|
const QString &caption)
|
||||||
|
: HistoryFileMedia(parent)
|
||||||
, _data(document)
|
, _data(document)
|
||||||
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
|
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
|
||||||
setDocumentLinks(_data, true);
|
setDocumentLinks(_data, true);
|
||||||
|
@ -1785,7 +2174,10 @@ HistoryGif::HistoryGif(not_null<HistoryItem*> parent, DocumentData *document, co
|
||||||
_data->thumb->load();
|
_data->thumb->load();
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryGif::HistoryGif(not_null<HistoryItem*> parent, const HistoryGif &other) : HistoryFileMedia(parent)
|
HistoryGif::HistoryGif(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
const HistoryGif &other)
|
||||||
|
: HistoryFileMedia(parent)
|
||||||
, _data(other._data)
|
, _data(other._data)
|
||||||
, _thumbw(other._thumbw)
|
, _thumbw(other._thumbw)
|
||||||
, _thumbh(other._thumbh)
|
, _thumbh(other._thumbh)
|
||||||
|
@ -2365,15 +2757,15 @@ HistoryTextState HistoryGif::getState(QPoint point, HistoryStateRequest request)
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HistoryGif::notificationText() const {
|
QString HistoryGif::notificationText() const {
|
||||||
return captionedNotificationText(mediaTypeString(), _caption);
|
return WithCaptionNotificationText(mediaTypeString(), _caption);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HistoryGif::inDialogsText() const {
|
QString HistoryGif::inDialogsText() const {
|
||||||
return captionedInDialogsText(mediaTypeString(), _caption);
|
return WithCaptionDialogsText(mediaTypeString(), _caption);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities HistoryGif::selectedText(TextSelection selection) const {
|
TextWithEntities HistoryGif::selectedText(TextSelection selection) const {
|
||||||
return captionedSelectedText(mediaTypeString(), _caption, selection);
|
return WithCaptionSelectedText(mediaTypeString(), _caption, selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryGif::needsBubble() const {
|
bool HistoryGif::needsBubble() const {
|
||||||
|
@ -2600,7 +2992,10 @@ bool HistoryGif::dataLoaded() const {
|
||||||
return (!_parent || _parent->id > 0) ? _data->loaded() : false;
|
return (!_parent || _parent->id > 0) ? _data->loaded() : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
HistorySticker::HistorySticker(not_null<HistoryItem*> parent, DocumentData *document) : HistoryMedia(parent)
|
HistorySticker::HistorySticker(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<DocumentData*> document)
|
||||||
|
: HistoryMedia(parent)
|
||||||
, _data(document)
|
, _data(document)
|
||||||
, _emoji(_data->sticker()->alt) {
|
, _emoji(_data->sticker()->alt) {
|
||||||
_data->thumb->load();
|
_data->thumb->load();
|
||||||
|
@ -3769,13 +4164,19 @@ int HistoryWebPage::bottomInfoPadding() const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryGame::HistoryGame(not_null<HistoryItem*> parent, GameData *data) : HistoryMedia(parent)
|
HistoryGame::HistoryGame(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<GameData*> data)
|
||||||
|
: HistoryMedia(parent)
|
||||||
, _data(data)
|
, _data(data)
|
||||||
, _title(st::msgMinWidth - st::webPageLeft)
|
, _title(st::msgMinWidth - st::webPageLeft)
|
||||||
, _description(st::msgMinWidth - st::webPageLeft) {
|
, _description(st::msgMinWidth - st::webPageLeft) {
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryGame::HistoryGame(not_null<HistoryItem*> parent, const HistoryGame &other) : HistoryMedia(parent)
|
HistoryGame::HistoryGame(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
const HistoryGame &other)
|
||||||
|
: HistoryMedia(parent)
|
||||||
, _data(other._data)
|
, _data(other._data)
|
||||||
, _attach(other._attach ? other._attach->clone(parent) : nullptr)
|
, _attach(other._attach ? other._attach->clone(parent) : nullptr)
|
||||||
, _title(other._title)
|
, _title(other._title)
|
||||||
|
@ -4799,11 +5200,11 @@ TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSele
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HistoryLocation::notificationText() const {
|
QString HistoryLocation::notificationText() const {
|
||||||
return captionedNotificationText(lang(lng_maps_point), _title);
|
return WithCaptionNotificationText(lang(lng_maps_point), _title);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HistoryLocation::inDialogsText() const {
|
QString HistoryLocation::inDialogsText() const {
|
||||||
return captionedInDialogsText(lang(lng_maps_point), _title);
|
return WithCaptionDialogsText(lang(lng_maps_point), _title);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities HistoryLocation::selectedText(TextSelection selection) const {
|
TextWithEntities HistoryLocation::selectedText(TextSelection selection) const {
|
||||||
|
|
|
@ -38,6 +38,16 @@ class EmptyUserpic;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
void HistoryInitMedia();
|
void HistoryInitMedia();
|
||||||
|
TextWithEntities WithCaptionSelectedText(
|
||||||
|
const QString &attachType,
|
||||||
|
const Text &caption,
|
||||||
|
TextSelection selection);
|
||||||
|
QString WithCaptionNotificationText(
|
||||||
|
const QString &attachType,
|
||||||
|
const Text &caption);
|
||||||
|
QString WithCaptionDialogsText(
|
||||||
|
const QString &attachType,
|
||||||
|
const Text &caption);
|
||||||
|
|
||||||
class HistoryFileMedia : public HistoryMedia {
|
class HistoryFileMedia : public HistoryMedia {
|
||||||
public:
|
public:
|
||||||
|
@ -129,23 +139,35 @@ protected:
|
||||||
|
|
||||||
class HistoryPhoto : public HistoryFileMedia {
|
class HistoryPhoto : public HistoryFileMedia {
|
||||||
public:
|
public:
|
||||||
HistoryPhoto(not_null<HistoryItem*> parent, not_null<PhotoData*> photo, const QString &caption);
|
HistoryPhoto(
|
||||||
HistoryPhoto(not_null<HistoryItem*> parent, not_null<PeerData*> chat, not_null<PhotoData*> photo, int width);
|
not_null<HistoryItem*> parent,
|
||||||
HistoryPhoto(not_null<HistoryItem*> parent, not_null<PeerData*> chat, const MTPDphoto &photo, int width);
|
not_null<PhotoData*> photo,
|
||||||
|
const QString &caption);
|
||||||
|
HistoryPhoto(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<PeerData*> chat,
|
||||||
|
not_null<PhotoData*> photo,
|
||||||
|
int width);
|
||||||
|
HistoryPhoto(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<PeerData*> chat,
|
||||||
|
const MTPDphoto &photo,
|
||||||
|
int width);
|
||||||
HistoryPhoto(not_null<HistoryItem*> parent, const HistoryPhoto &other);
|
HistoryPhoto(not_null<HistoryItem*> parent, const HistoryPhoto &other);
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
HistoryMediaType type() const override {
|
HistoryMediaType type() const override {
|
||||||
return MediaTypePhoto;
|
return MediaTypePhoto;
|
||||||
}
|
}
|
||||||
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
|
std::unique_ptr<HistoryMedia> clone(
|
||||||
|
not_null<HistoryItem*> newParent) const override {
|
||||||
return std::make_unique<HistoryPhoto>(newParent, *this);
|
return std::make_unique<HistoryPhoto>(newParent, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void initDimensions() override;
|
void initDimensions() override;
|
||||||
int resizeGetHeight(int width) override;
|
int resizeGetHeight(int width) override;
|
||||||
|
|
||||||
void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
|
void draw(Painter &p, const QRect &clip, TextSelection selection, TimeMs ms) const override;
|
||||||
HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
|
HistoryTextState getState(QPoint point, HistoryStateRequest request) const override;
|
||||||
|
|
||||||
[[nodiscard]] TextSelection adjustSelection(
|
[[nodiscard]] TextSelection adjustSelection(
|
||||||
|
@ -166,10 +188,28 @@ public:
|
||||||
|
|
||||||
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
|
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
|
||||||
|
|
||||||
PhotoData *photo() const {
|
not_null<PhotoData*> photo() const {
|
||||||
return _data;
|
return _data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool canBeGrouped() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
QSize sizeForGrouping() const override;
|
||||||
|
void drawGrouped(
|
||||||
|
Painter &p,
|
||||||
|
const QRect &clip,
|
||||||
|
TextSelection selection,
|
||||||
|
TimeMs ms,
|
||||||
|
const QRect &geometry,
|
||||||
|
RectParts corners,
|
||||||
|
not_null<uint64*> cacheKey,
|
||||||
|
not_null<QPixmap*> cache) const override;
|
||||||
|
HistoryTextState getStateGrouped(
|
||||||
|
const QRect &geometry,
|
||||||
|
QPoint point,
|
||||||
|
HistoryStateRequest request) const override;
|
||||||
|
|
||||||
void updateSentMedia(const MTPMessageMedia &media) override;
|
void updateSentMedia(const MTPMessageMedia &media) override;
|
||||||
bool needReSetInlineResultMedia(const MTPMessageMedia &media) override;
|
bool needReSetInlineResultMedia(const MTPMessageMedia &media) override;
|
||||||
|
|
||||||
|
@ -210,6 +250,12 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void validateGroupedCache(
|
||||||
|
const QRect &geometry,
|
||||||
|
RectParts corners,
|
||||||
|
not_null<uint64*> cacheKey,
|
||||||
|
not_null<QPixmap*> cache) const;
|
||||||
|
|
||||||
not_null<PhotoData*> _data;
|
not_null<PhotoData*> _data;
|
||||||
int16 _pixw = 1;
|
int16 _pixw = 1;
|
||||||
int16 _pixh = 1;
|
int16 _pixh = 1;
|
||||||
|
@ -219,12 +265,17 @@ private:
|
||||||
|
|
||||||
class HistoryVideo : public HistoryFileMedia {
|
class HistoryVideo : public HistoryFileMedia {
|
||||||
public:
|
public:
|
||||||
HistoryVideo(not_null<HistoryItem*> parent, DocumentData *document, const QString &caption);
|
HistoryVideo(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<DocumentData*> document,
|
||||||
|
const QString &caption);
|
||||||
HistoryVideo(not_null<HistoryItem*> parent, const HistoryVideo &other);
|
HistoryVideo(not_null<HistoryItem*> parent, const HistoryVideo &other);
|
||||||
|
|
||||||
HistoryMediaType type() const override {
|
HistoryMediaType type() const override {
|
||||||
return MediaTypeVideo;
|
return MediaTypeVideo;
|
||||||
}
|
}
|
||||||
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
|
std::unique_ptr<HistoryMedia> clone(
|
||||||
|
not_null<HistoryItem*> newParent) const override {
|
||||||
return std::make_unique<HistoryVideo>(newParent, *this);
|
return std::make_unique<HistoryVideo>(newParent, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,6 +307,24 @@ public:
|
||||||
return _data;
|
return _data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool canBeGrouped() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
QSize sizeForGrouping() const override;
|
||||||
|
void drawGrouped(
|
||||||
|
Painter &p,
|
||||||
|
const QRect &clip,
|
||||||
|
TextSelection selection,
|
||||||
|
TimeMs ms,
|
||||||
|
const QRect &geometry,
|
||||||
|
RectParts corners,
|
||||||
|
not_null<uint64*> cacheKey,
|
||||||
|
not_null<QPixmap*> cache) const override;
|
||||||
|
HistoryTextState getStateGrouped(
|
||||||
|
const QRect &geometry,
|
||||||
|
QPoint point,
|
||||||
|
HistoryStateRequest request) const override;
|
||||||
|
|
||||||
bool uploading() const override {
|
bool uploading() const override {
|
||||||
return _data->uploading();
|
return _data->uploading();
|
||||||
}
|
}
|
||||||
|
@ -297,13 +366,18 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void validateGroupedCache(
|
||||||
|
const QRect &geometry,
|
||||||
|
RectParts corners,
|
||||||
|
not_null<uint64*> cacheKey,
|
||||||
|
not_null<QPixmap*> cache) const;
|
||||||
|
void setStatusSize(int32 newSize) const;
|
||||||
|
void updateStatusText() const;
|
||||||
|
|
||||||
not_null<DocumentData*> _data;
|
not_null<DocumentData*> _data;
|
||||||
int32 _thumbw;
|
int32 _thumbw;
|
||||||
Text _caption;
|
Text _caption;
|
||||||
|
|
||||||
void setStatusSize(int32 newSize) const;
|
|
||||||
void updateStatusText() const;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed> {
|
struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed> {
|
||||||
|
@ -370,8 +444,14 @@ private:
|
||||||
|
|
||||||
class HistoryDocument : public HistoryFileMedia, public RuntimeComposer {
|
class HistoryDocument : public HistoryFileMedia, public RuntimeComposer {
|
||||||
public:
|
public:
|
||||||
HistoryDocument(not_null<HistoryItem*> parent, DocumentData *document, const QString &caption);
|
HistoryDocument(
|
||||||
HistoryDocument(not_null<HistoryItem*> parent, const HistoryDocument &other);
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<DocumentData*> document,
|
||||||
|
const QString &caption);
|
||||||
|
HistoryDocument(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
const HistoryDocument &other);
|
||||||
|
|
||||||
HistoryMediaType type() const override {
|
HistoryMediaType type() const override {
|
||||||
return _data->isVoiceMessage()
|
return _data->isVoiceMessage()
|
||||||
? MediaTypeVoiceFile
|
? MediaTypeVoiceFile
|
||||||
|
@ -379,7 +459,8 @@ public:
|
||||||
? MediaTypeMusicFile
|
? MediaTypeMusicFile
|
||||||
: MediaTypeFile);
|
: MediaTypeFile);
|
||||||
}
|
}
|
||||||
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
|
std::unique_ptr<HistoryMedia> clone(
|
||||||
|
not_null<HistoryItem*> newParent) const override {
|
||||||
return std::make_unique<HistoryDocument>(newParent, *this);
|
return std::make_unique<HistoryDocument>(newParent, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,12 +569,17 @@ private:
|
||||||
|
|
||||||
class HistoryGif : public HistoryFileMedia {
|
class HistoryGif : public HistoryFileMedia {
|
||||||
public:
|
public:
|
||||||
HistoryGif(not_null<HistoryItem*> parent, DocumentData *document, const QString &caption);
|
HistoryGif(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<DocumentData*> document,
|
||||||
|
const QString &caption);
|
||||||
HistoryGif(not_null<HistoryItem*> parent, const HistoryGif &other);
|
HistoryGif(not_null<HistoryItem*> parent, const HistoryGif &other);
|
||||||
|
|
||||||
HistoryMediaType type() const override {
|
HistoryMediaType type() const override {
|
||||||
return MediaTypeGif;
|
return MediaTypeGif;
|
||||||
}
|
}
|
||||||
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
|
std::unique_ptr<HistoryMedia> clone(
|
||||||
|
not_null<HistoryItem*> newParent) const override {
|
||||||
return std::make_unique<HistoryGif>(newParent, *this);
|
return std::make_unique<HistoryGif>(newParent, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -602,11 +688,15 @@ private:
|
||||||
|
|
||||||
class HistorySticker : public HistoryMedia {
|
class HistorySticker : public HistoryMedia {
|
||||||
public:
|
public:
|
||||||
HistorySticker(not_null<HistoryItem*> parent, DocumentData *document);
|
HistorySticker(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<DocumentData*> document);
|
||||||
|
|
||||||
HistoryMediaType type() const override {
|
HistoryMediaType type() const override {
|
||||||
return MediaTypeSticker;
|
return MediaTypeSticker;
|
||||||
}
|
}
|
||||||
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
|
std::unique_ptr<HistoryMedia> clone(
|
||||||
|
not_null<HistoryItem*> newParent) const override {
|
||||||
return std::make_unique<HistorySticker>(newParent, _data);
|
return std::make_unique<HistorySticker>(newParent, _data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -671,11 +761,18 @@ private:
|
||||||
|
|
||||||
class HistoryContact : public HistoryMedia {
|
class HistoryContact : public HistoryMedia {
|
||||||
public:
|
public:
|
||||||
HistoryContact(not_null<HistoryItem*> parent, int32 userId, const QString &first, const QString &last, const QString &phone);
|
HistoryContact(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
int32 userId,
|
||||||
|
const QString &first,
|
||||||
|
const QString &last,
|
||||||
|
const QString &phone);
|
||||||
|
|
||||||
HistoryMediaType type() const override {
|
HistoryMediaType type() const override {
|
||||||
return MediaTypeContact;
|
return MediaTypeContact;
|
||||||
}
|
}
|
||||||
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
|
std::unique_ptr<HistoryMedia> clone(
|
||||||
|
not_null<HistoryItem*> newParent) const override {
|
||||||
return std::make_unique<HistoryContact>(newParent, _userId, _fname, _lname, _phone);
|
return std::make_unique<HistoryContact>(newParent, _userId, _fname, _lname, _phone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -735,11 +832,15 @@ private:
|
||||||
|
|
||||||
class HistoryCall : public HistoryMedia {
|
class HistoryCall : public HistoryMedia {
|
||||||
public:
|
public:
|
||||||
HistoryCall(not_null<HistoryItem*> parent, const MTPDmessageActionPhoneCall &call);
|
HistoryCall(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
const MTPDmessageActionPhoneCall &call);
|
||||||
|
|
||||||
HistoryMediaType type() const override {
|
HistoryMediaType type() const override {
|
||||||
return MediaTypeCall;
|
return MediaTypeCall;
|
||||||
}
|
}
|
||||||
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
|
std::unique_ptr<HistoryMedia> clone(
|
||||||
|
not_null<HistoryItem*> newParent) const override {
|
||||||
Unexpected("Clone HistoryCall.");
|
Unexpected("Clone HistoryCall.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -790,12 +891,18 @@ private:
|
||||||
|
|
||||||
class HistoryWebPage : public HistoryMedia {
|
class HistoryWebPage : public HistoryMedia {
|
||||||
public:
|
public:
|
||||||
HistoryWebPage(not_null<HistoryItem*> parent, not_null<WebPageData*> data);
|
HistoryWebPage(
|
||||||
HistoryWebPage(not_null<HistoryItem*> parent, const HistoryWebPage &other);
|
not_null<HistoryItem*> parent,
|
||||||
|
not_null<WebPageData*> data);
|
||||||
|
HistoryWebPage(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
const HistoryWebPage &other);
|
||||||
|
|
||||||
HistoryMediaType type() const override {
|
HistoryMediaType type() const override {
|
||||||
return MediaTypeWebPage;
|
return MediaTypeWebPage;
|
||||||
}
|
}
|
||||||
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
|
std::unique_ptr<HistoryMedia> clone(
|
||||||
|
not_null<HistoryItem*> newParent) const override {
|
||||||
return std::make_unique<HistoryWebPage>(newParent, *this);
|
return std::make_unique<HistoryWebPage>(newParent, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -898,12 +1005,14 @@ private:
|
||||||
|
|
||||||
class HistoryGame : public HistoryMedia {
|
class HistoryGame : public HistoryMedia {
|
||||||
public:
|
public:
|
||||||
HistoryGame(not_null<HistoryItem*> parent, GameData *data);
|
HistoryGame(not_null<HistoryItem*> parent, not_null<GameData*> data);
|
||||||
HistoryGame(not_null<HistoryItem*> parent, const HistoryGame &other);
|
HistoryGame(not_null<HistoryItem*> parent, const HistoryGame &other);
|
||||||
|
|
||||||
HistoryMediaType type() const override {
|
HistoryMediaType type() const override {
|
||||||
return MediaTypeGame;
|
return MediaTypeGame;
|
||||||
}
|
}
|
||||||
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
|
std::unique_ptr<HistoryMedia> clone(
|
||||||
|
not_null<HistoryItem*> newParent) const override {
|
||||||
return std::make_unique<HistoryGame>(newParent, *this);
|
return std::make_unique<HistoryGame>(newParent, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -962,7 +1071,7 @@ public:
|
||||||
}
|
}
|
||||||
ImagePtr replyPreview() override;
|
ImagePtr replyPreview() override;
|
||||||
|
|
||||||
GameData *game() {
|
not_null<GameData*> game() {
|
||||||
return _data;
|
return _data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -993,7 +1102,7 @@ private:
|
||||||
QMargins inBubblePadding() const;
|
QMargins inBubblePadding() const;
|
||||||
int bottomInfoPadding() const;
|
int bottomInfoPadding() const;
|
||||||
|
|
||||||
GameData *_data;
|
not_null<GameData*> _data;
|
||||||
ClickHandlerPtr _openl;
|
ClickHandlerPtr _openl;
|
||||||
std::unique_ptr<HistoryMedia> _attach;
|
std::unique_ptr<HistoryMedia> _attach;
|
||||||
|
|
||||||
|
@ -1007,12 +1116,18 @@ private:
|
||||||
|
|
||||||
class HistoryInvoice : public HistoryMedia {
|
class HistoryInvoice : public HistoryMedia {
|
||||||
public:
|
public:
|
||||||
HistoryInvoice(not_null<HistoryItem*> parent, const MTPDmessageMediaInvoice &data);
|
HistoryInvoice(
|
||||||
HistoryInvoice(not_null<HistoryItem*> parent, const HistoryInvoice &other);
|
not_null<HistoryItem*> parent,
|
||||||
|
const MTPDmessageMediaInvoice &data);
|
||||||
|
HistoryInvoice(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
const HistoryInvoice &other);
|
||||||
|
|
||||||
HistoryMediaType type() const override {
|
HistoryMediaType type() const override {
|
||||||
return MediaTypeInvoice;
|
return MediaTypeInvoice;
|
||||||
}
|
}
|
||||||
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
|
std::unique_ptr<HistoryMedia> clone(
|
||||||
|
not_null<HistoryItem*> newParent) const override {
|
||||||
return std::make_unique<HistoryInvoice>(newParent, *this);
|
return std::make_unique<HistoryInvoice>(newParent, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1103,12 +1218,20 @@ struct LocationData;
|
||||||
|
|
||||||
class HistoryLocation : public HistoryMedia {
|
class HistoryLocation : public HistoryMedia {
|
||||||
public:
|
public:
|
||||||
HistoryLocation(not_null<HistoryItem*> parent, const LocationCoords &coords, const QString &title = QString(), const QString &description = QString());
|
HistoryLocation(
|
||||||
HistoryLocation(not_null<HistoryItem*> parent, const HistoryLocation &other);
|
not_null<HistoryItem*> parent,
|
||||||
|
const LocationCoords &coords,
|
||||||
|
const QString &title = QString(),
|
||||||
|
const QString &description = QString());
|
||||||
|
HistoryLocation(
|
||||||
|
not_null<HistoryItem*> parent,
|
||||||
|
const HistoryLocation &other);
|
||||||
|
|
||||||
HistoryMediaType type() const override {
|
HistoryMediaType type() const override {
|
||||||
return MediaTypeLocation;
|
return MediaTypeLocation;
|
||||||
}
|
}
|
||||||
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
|
std::unique_ptr<HistoryMedia> clone(
|
||||||
|
not_null<HistoryItem*> newParent) const override {
|
||||||
return std::make_unique<HistoryLocation>(newParent, *this);
|
return std::make_unique<HistoryLocation>(newParent, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -630,7 +630,9 @@ int HistoryMessage::KeyboardStyle::minButtonWidth(HistoryMessageReplyMarkup::But
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryMessage::HistoryMessage(not_null<History*> history, const MTPDmessage &msg)
|
HistoryMessage::HistoryMessage(
|
||||||
|
not_null<History*> history,
|
||||||
|
const MTPDmessage &msg)
|
||||||
: HistoryItem(history, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) {
|
: HistoryItem(history, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) {
|
||||||
CreateConfig config;
|
CreateConfig config;
|
||||||
|
|
||||||
|
@ -655,6 +657,9 @@ HistoryMessage::HistoryMessage(not_null<History*> history, const MTPDmessage &ms
|
||||||
if (msg.has_reply_markup()) config.mtpMarkup = &msg.vreply_markup;
|
if (msg.has_reply_markup()) config.mtpMarkup = &msg.vreply_markup;
|
||||||
if (msg.has_edit_date()) config.editDate = ::date(msg.vedit_date);
|
if (msg.has_edit_date()) config.editDate = ::date(msg.vedit_date);
|
||||||
if (msg.has_post_author()) config.author = qs(msg.vpost_author);
|
if (msg.has_post_author()) config.author = qs(msg.vpost_author);
|
||||||
|
if (msg.has_grouped_id()) {
|
||||||
|
config.groupId = MessageGroupId::FromRaw(msg.vgrouped_id.v);
|
||||||
|
}
|
||||||
|
|
||||||
createComponents(config);
|
createComponents(config);
|
||||||
|
|
||||||
|
@ -665,7 +670,9 @@ HistoryMessage::HistoryMessage(not_null<History*> history, const MTPDmessage &ms
|
||||||
setText({ text, entities });
|
setText({ text, entities });
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryMessage::HistoryMessage(not_null<History*> history, const MTPDmessageService &msg)
|
HistoryMessage::HistoryMessage(
|
||||||
|
not_null<History*> history,
|
||||||
|
const MTPDmessageService &msg)
|
||||||
: HistoryItem(history, msg.vid.v, mtpCastFlags(msg.vflags.v), ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) {
|
: HistoryItem(history, msg.vid.v, mtpCastFlags(msg.vflags.v), ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) {
|
||||||
CreateConfig config;
|
CreateConfig config;
|
||||||
|
|
||||||
|
@ -755,22 +762,53 @@ HistoryMessage::HistoryMessage(
|
||||||
setText(fwd->originalText());
|
setText(fwd->originalText());
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryMessage::HistoryMessage(not_null<History*> history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, const TextWithEntities &textWithEntities)
|
HistoryMessage::HistoryMessage(
|
||||||
|
not_null<History*> history,
|
||||||
|
MsgId id,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
MsgId replyTo,
|
||||||
|
UserId viaBotId,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
const TextWithEntities &textWithEntities)
|
||||||
: HistoryItem(history, id, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
|
: HistoryItem(history, id, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
|
||||||
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, MTPnullMarkup);
|
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, MTPnullMarkup);
|
||||||
|
|
||||||
setText(textWithEntities);
|
setText(textWithEntities);
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryMessage::HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup)
|
HistoryMessage::HistoryMessage(
|
||||||
|
not_null<History*> history,
|
||||||
|
MsgId msgId,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
MsgId replyTo,
|
||||||
|
UserId viaBotId,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
not_null<DocumentData*> document,
|
||||||
|
const QString &caption,
|
||||||
|
const MTPReplyMarkup &markup)
|
||||||
: HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
|
: HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
|
||||||
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
|
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
|
||||||
|
|
||||||
initMediaFromDocument(doc, caption);
|
initMediaFromDocument(document, caption);
|
||||||
setText(TextWithEntities());
|
setText(TextWithEntities());
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryMessage::HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup)
|
HistoryMessage::HistoryMessage(
|
||||||
|
not_null<History*> history,
|
||||||
|
MsgId msgId,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
MsgId replyTo,
|
||||||
|
UserId viaBotId,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
not_null<PhotoData*> photo,
|
||||||
|
const QString &caption,
|
||||||
|
const MTPReplyMarkup &markup)
|
||||||
: HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
|
: HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
|
||||||
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
|
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
|
||||||
|
|
||||||
|
@ -778,7 +816,17 @@ HistoryMessage::HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmess
|
||||||
setText(TextWithEntities());
|
setText(TextWithEntities());
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryMessage::HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup)
|
HistoryMessage::HistoryMessage(
|
||||||
|
not_null<History*> history,
|
||||||
|
MsgId msgId,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
MsgId replyTo,
|
||||||
|
UserId viaBotId,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
not_null<GameData*> game,
|
||||||
|
const MTPReplyMarkup &markup)
|
||||||
: HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
|
: HistoryItem(history, msgId, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
|
||||||
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
|
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
|
||||||
|
|
||||||
|
@ -786,7 +834,12 @@ HistoryMessage::HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmess
|
||||||
setText(TextWithEntities());
|
setText(TextWithEntities());
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryMessage::createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, const QString &postAuthor, const MTPReplyMarkup &markup) {
|
void HistoryMessage::createComponentsHelper(
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
MsgId replyTo,
|
||||||
|
UserId viaBotId,
|
||||||
|
const QString &postAuthor,
|
||||||
|
const MTPReplyMarkup &markup) {
|
||||||
CreateConfig config;
|
CreateConfig config;
|
||||||
|
|
||||||
if (flags & MTPDmessage::Flag::f_via_bot_id) config.viaBotId = viaBotId;
|
if (flags & MTPDmessage::Flag::f_via_bot_id) config.viaBotId = viaBotId;
|
||||||
|
@ -818,6 +871,7 @@ void HistoryMessage::updateMediaInBubbleState() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_media->updateNeedBubbleState();
|
||||||
if (!drawBubble()) {
|
if (!drawBubble()) {
|
||||||
_media->setInBubbleState(MediaInBubbleState::None);
|
_media->setInBubbleState(MediaInBubbleState::None);
|
||||||
return;
|
return;
|
||||||
|
@ -960,10 +1014,13 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
|
||||||
} else if (config.inlineMarkup) {
|
} else if (config.inlineMarkup) {
|
||||||
mask |= HistoryMessageReplyMarkup::Bit();
|
mask |= HistoryMessageReplyMarkup::Bit();
|
||||||
}
|
}
|
||||||
|
if (config.groupId) {
|
||||||
|
mask |= HistoryMessageGroup::Bit();
|
||||||
|
}
|
||||||
|
|
||||||
UpdateComponents(mask);
|
UpdateComponents(mask);
|
||||||
|
|
||||||
if (auto reply = Get<HistoryMessageReply>()) {
|
if (const auto reply = Get<HistoryMessageReply>()) {
|
||||||
reply->replyToMsgId = config.replyTo;
|
reply->replyToMsgId = config.replyTo;
|
||||||
if (!reply->updateData(this)) {
|
if (!reply->updateData(this)) {
|
||||||
Auth().api().requestMessageData(
|
Auth().api().requestMessageData(
|
||||||
|
@ -972,21 +1029,21 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
|
||||||
HistoryDependentItemCallback(fullId()));
|
HistoryDependentItemCallback(fullId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (auto via = Get<HistoryMessageVia>()) {
|
if (const auto via = Get<HistoryMessageVia>()) {
|
||||||
via->create(config.viaBotId);
|
via->create(config.viaBotId);
|
||||||
}
|
}
|
||||||
if (auto views = Get<HistoryMessageViews>()) {
|
if (const auto views = Get<HistoryMessageViews>()) {
|
||||||
views->_views = config.viewsCount;
|
views->_views = config.viewsCount;
|
||||||
}
|
}
|
||||||
if (auto edited = Get<HistoryMessageEdited>()) {
|
if (const auto edited = Get<HistoryMessageEdited>()) {
|
||||||
edited->create(config.editDate, date.toString(cTimeFormat()));
|
edited->create(config.editDate, date.toString(cTimeFormat()));
|
||||||
if (auto msgsigned = Get<HistoryMessageSigned>()) {
|
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
|
||||||
msgsigned->create(config.author, edited->_edited.originalText());
|
msgsigned->create(config.author, edited->_edited.originalText());
|
||||||
}
|
}
|
||||||
} else if (auto msgsigned = Get<HistoryMessageSigned>()) {
|
} else if (const auto msgsigned = Get<HistoryMessageSigned>()) {
|
||||||
msgsigned->create(config.author, date.toString(cTimeFormat()));
|
msgsigned->create(config.author, date.toString(cTimeFormat()));
|
||||||
}
|
}
|
||||||
if (auto forwarded = Get<HistoryMessageForwarded>()) {
|
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
||||||
forwarded->_originalDate = config.originalDate;
|
forwarded->_originalDate = config.originalDate;
|
||||||
forwarded->_originalSender = App::peer(config.senderOriginal);
|
forwarded->_originalSender = App::peer(config.senderOriginal);
|
||||||
forwarded->_originalId = config.originalId;
|
forwarded->_originalId = config.originalId;
|
||||||
|
@ -994,7 +1051,7 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
|
||||||
forwarded->_savedFromPeer = App::peerLoaded(config.savedFromPeer);
|
forwarded->_savedFromPeer = App::peerLoaded(config.savedFromPeer);
|
||||||
forwarded->_savedFromMsgId = config.savedFromMsgId;
|
forwarded->_savedFromMsgId = config.savedFromMsgId;
|
||||||
}
|
}
|
||||||
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
|
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
|
||||||
if (config.mtpMarkup) {
|
if (config.mtpMarkup) {
|
||||||
markup->create(*config.mtpMarkup);
|
markup->create(*config.mtpMarkup);
|
||||||
} else if (config.inlineMarkup) {
|
} else if (config.inlineMarkup) {
|
||||||
|
@ -1004,6 +1061,10 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
|
||||||
_flags |= MTPDmessage_ClientFlag::f_has_switch_inline_button;
|
_flags |= MTPDmessage_ClientFlag::f_has_switch_inline_button;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (const auto group = Get<HistoryMessageGroup>()) {
|
||||||
|
group->groupId = config.groupId;
|
||||||
|
group->leader = this;
|
||||||
|
}
|
||||||
initTime();
|
initTime();
|
||||||
_fromNameVersion = displayFrom()->nameVersion;
|
_fromNameVersion = displayFrom()->nameVersion;
|
||||||
}
|
}
|
||||||
|
@ -1240,14 +1301,16 @@ void HistoryMessage::initDimensions() {
|
||||||
} else if (_media) {
|
} else if (_media) {
|
||||||
_media->initDimensions();
|
_media->initDimensions();
|
||||||
_maxw = _media->maxWidth();
|
_maxw = _media->maxWidth();
|
||||||
_minh = _media->minHeight();
|
_minh = _media->isDisplayed() ? _media->minHeight() : 0;
|
||||||
} else {
|
} else {
|
||||||
_maxw = st::msgMinWidth;
|
_maxw = st::msgMinWidth;
|
||||||
_minh = 0;
|
_minh = 0;
|
||||||
}
|
}
|
||||||
if (auto markup = inlineReplyMarkup()) {
|
if (const auto markup = inlineReplyMarkup()) {
|
||||||
if (!markup->inlineKeyboard) {
|
if (!markup->inlineKeyboard) {
|
||||||
markup->inlineKeyboard = std::make_unique<ReplyKeyboard>(this, std::make_unique<KeyboardStyle>(st::msgBotKbButton));
|
markup->inlineKeyboard = std::make_unique<ReplyKeyboard>(
|
||||||
|
this,
|
||||||
|
std::make_unique<KeyboardStyle>(st::msgBotKbButton));
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we have a text bubble we can resize it to fit the keyboard
|
// if we have a text bubble we can resize it to fit the keyboard
|
||||||
|
@ -1259,7 +1322,9 @@ void HistoryMessage::initDimensions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryMessage::drawBubble() const {
|
bool HistoryMessage::drawBubble() const {
|
||||||
if (Has<HistoryMessageLogEntryOriginal>()) {
|
if (isHiddenByGroup()) {
|
||||||
|
return false;
|
||||||
|
} else if (Has<HistoryMessageLogEntryOriginal>()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return _media ? (!emptyText() || _media->needsBubble()) : !isEmpty();
|
return _media ? (!emptyText() || _media->needsBubble()) : !isEmpty();
|
||||||
|
@ -1397,7 +1462,9 @@ bool HistoryMessage::displayForwardedFrom() const {
|
||||||
|
|
||||||
void HistoryMessage::updateMedia(const MTPMessageMedia *media) {
|
void HistoryMessage::updateMedia(const MTPMessageMedia *media) {
|
||||||
auto setMediaAllowed = [](HistoryMediaType type) {
|
auto setMediaAllowed = [](HistoryMediaType type) {
|
||||||
return (type == MediaTypeWebPage || type == MediaTypeGame || type == MediaTypeLocation);
|
return (type == MediaTypeWebPage)
|
||||||
|
|| (type == MediaTypeGame)
|
||||||
|
|| (type == MediaTypeLocation);
|
||||||
};
|
};
|
||||||
if (_flags & MTPDmessage_ClientFlag::f_from_inline_bot) {
|
if (_flags & MTPDmessage_ClientFlag::f_from_inline_bot) {
|
||||||
bool needReSet = true;
|
bool needReSet = true;
|
||||||
|
@ -1804,7 +1871,9 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM
|
||||||
auto entry = Get<HistoryMessageLogEntryOriginal>();
|
auto entry = Get<HistoryMessageLogEntryOriginal>();
|
||||||
auto mediaDisplayed = _media && _media->isDisplayed();
|
auto mediaDisplayed = _media && _media->isDisplayed();
|
||||||
|
|
||||||
auto skipTail = isAttachedToNext() || (_media && _media->skipBubbleTail()) || (keyboard != nullptr);
|
auto skipTail = isAttachedToNext()
|
||||||
|
|| (_media && _media->skipBubbleTail())
|
||||||
|
|| (keyboard != nullptr);
|
||||||
auto displayTail = skipTail ? RectPart::None : (outbg && !Adaptive::ChatWide()) ? RectPart::Right : RectPart::Left;
|
auto displayTail = skipTail ? RectPart::None : (outbg && !Adaptive::ChatWide()) ? RectPart::Right : RectPart::Left;
|
||||||
HistoryLayout::paintBubble(p, g, width(), selected, outbg, displayTail);
|
HistoryLayout::paintBubble(p, g, width(), selected, outbg, displayTail);
|
||||||
|
|
||||||
|
@ -1872,7 +1941,7 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM
|
||||||
const auto fastShareTop = g.top() + g.height() - fastShareSkip - st::historyFastShareSize;
|
const auto fastShareTop = g.top() + g.height() - fastShareSkip - st::historyFastShareSize;
|
||||||
drawRightAction(p, fastShareLeft, fastShareTop, width());
|
drawRightAction(p, fastShareLeft, fastShareTop, width());
|
||||||
}
|
}
|
||||||
} else if (_media) {
|
} else if (_media && _media->isDisplayed()) {
|
||||||
p.translate(g.topLeft());
|
p.translate(g.topLeft());
|
||||||
_media->draw(p, clip.translated(-g.topLeft()), skipTextSelection(selection), ms);
|
_media->draw(p, clip.translated(-g.topLeft()), skipTextSelection(selection), ms);
|
||||||
p.translate(-g.topLeft());
|
p.translate(-g.topLeft());
|
||||||
|
@ -1880,7 +1949,7 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM
|
||||||
|
|
||||||
p.restoreTextPalette();
|
p.restoreTextPalette();
|
||||||
|
|
||||||
auto reply = Get<HistoryMessageReply>();
|
const auto reply = Get<HistoryMessageReply>();
|
||||||
if (reply && reply->isNameUpdated()) {
|
if (reply && reply->isNameUpdated()) {
|
||||||
const_cast<HistoryMessage*>(this)->setPendingInitDimensions();
|
const_cast<HistoryMessage*>(this)->setPendingInitDimensions();
|
||||||
}
|
}
|
||||||
|
@ -2006,16 +2075,16 @@ void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int HistoryMessage::resizeContentGetHeight() {
|
int HistoryMessage::resizeContentGetHeight() {
|
||||||
int result = performResizeGetHeight();
|
const auto result = performResizeGetHeight();
|
||||||
|
|
||||||
auto keyboard = inlineReplyKeyboard();
|
const auto keyboard = inlineReplyKeyboard();
|
||||||
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
|
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
|
||||||
int oldTop = markup->oldTop;
|
const auto oldTop = markup->oldTop;
|
||||||
if (oldTop >= 0) {
|
if (oldTop >= 0) {
|
||||||
markup->oldTop = -1;
|
markup->oldTop = -1;
|
||||||
if (keyboard) {
|
if (keyboard) {
|
||||||
int h = st::msgBotKbButton.margin + keyboard->naturalHeight();
|
const auto height = st::msgBotKbButton.margin + keyboard->naturalHeight();
|
||||||
int keyboardTop = _height - h + st::msgBotKbButton.margin - marginBottom();
|
const auto keyboardTop = _height - height + st::msgBotKbButton.margin - marginBottom();
|
||||||
if (keyboardTop != oldTop) {
|
if (keyboardTop != oldTop) {
|
||||||
Notify::inlineKeyboardMoved(this, oldTop, keyboardTop);
|
Notify::inlineKeyboardMoved(this, oldTop, keyboardTop);
|
||||||
}
|
}
|
||||||
|
@ -2027,7 +2096,9 @@ int HistoryMessage::resizeContentGetHeight() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int HistoryMessage::performResizeGetHeight() {
|
int HistoryMessage::performResizeGetHeight() {
|
||||||
if (width() < st::msgMinWidth) return _height;
|
if (width() < st::msgMinWidth) {
|
||||||
|
return _height;
|
||||||
|
}
|
||||||
|
|
||||||
auto contentWidth = width() - (st::msgMargin.left() + st::msgMargin.right());
|
auto contentWidth = width() - (st::msgMargin.left() + st::msgMargin.right());
|
||||||
if (history()->peer->isSelf() && !hasOutLayout()) {
|
if (history()->peer->isSelf() && !hasOutLayout()) {
|
||||||
|
@ -2111,14 +2182,14 @@ int HistoryMessage::performResizeGetHeight() {
|
||||||
reply->resize(countGeometry().width() - st::msgPadding.left() - st::msgPadding.right());
|
reply->resize(countGeometry().width() - st::msgPadding.left() - st::msgPadding.right());
|
||||||
_height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
|
_height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
|
||||||
}
|
}
|
||||||
} else if (_media) {
|
} else if (_media && _media->isDisplayed()) {
|
||||||
_height = _media->resizeGetHeight(contentWidth);
|
_height = _media->resizeGetHeight(contentWidth);
|
||||||
} else {
|
} else {
|
||||||
_height = 0;
|
_height = 0;
|
||||||
}
|
}
|
||||||
if (auto keyboard = inlineReplyKeyboard()) {
|
if (const auto keyboard = inlineReplyKeyboard()) {
|
||||||
auto g = countGeometry();
|
const auto g = countGeometry();
|
||||||
auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
|
const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
|
||||||
_height += keyboardHeight;
|
_height += keyboardHeight;
|
||||||
keyboard->resize(g.width(), keyboardHeight - st::msgBotKbButton.margin);
|
keyboard->resize(g.width(), keyboardHeight - st::msgBotKbButton.margin);
|
||||||
}
|
}
|
||||||
|
@ -2128,7 +2199,7 @@ int HistoryMessage::performResizeGetHeight() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HistoryMessage::hasPoint(QPoint point) const {
|
bool HistoryMessage::hasPoint(QPoint point) const {
|
||||||
auto g = countGeometry();
|
const auto g = countGeometry();
|
||||||
if (g.width() < 1) {
|
if (g.width() < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2248,7 +2319,7 @@ HistoryTextState HistoryMessage::getState(QPoint point, HistoryStateRequest requ
|
||||||
result.link = rightActionLink();
|
result.link = rightActionLink();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (_media) {
|
} else if (_media && _media->isDisplayed()) {
|
||||||
result = _media->getState(point - g.topLeft(), request);
|
result = _media->getState(point - g.topLeft(), request);
|
||||||
result.symbol += _text.length();
|
result.symbol += _text.length();
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,26 +30,119 @@ void FastShareMessage(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
class HistoryMessage : public HistoryItem, private HistoryItemInstantiated<HistoryMessage> {
|
class HistoryMessage : public HistoryItem, private HistoryItemInstantiated<HistoryMessage> {
|
||||||
public:
|
public:
|
||||||
static not_null<HistoryMessage*> create(not_null<History*> history, const MTPDmessage &msg) {
|
static not_null<HistoryMessage*> create(
|
||||||
|
not_null<History*> history,
|
||||||
|
const MTPDmessage &msg) {
|
||||||
return _create(history, msg);
|
return _create(history, msg);
|
||||||
}
|
}
|
||||||
static not_null<HistoryMessage*> create(not_null<History*> history, const MTPDmessageService &msg) {
|
static not_null<HistoryMessage*> create(
|
||||||
|
not_null<History*> history,
|
||||||
|
const MTPDmessageService &msg) {
|
||||||
return _create(history, msg);
|
return _create(history, msg);
|
||||||
}
|
}
|
||||||
static not_null<HistoryMessage*> create(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, not_null<HistoryMessage*> fwd) {
|
static not_null<HistoryMessage*> create(
|
||||||
|
not_null<History*> history,
|
||||||
|
MsgId msgId,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
not_null<HistoryMessage*> fwd) {
|
||||||
return _create(history, msgId, flags, date, from, postAuthor, fwd);
|
return _create(history, msgId, flags, date, from, postAuthor, fwd);
|
||||||
}
|
}
|
||||||
static not_null<HistoryMessage*> create(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, const TextWithEntities &textWithEntities) {
|
static not_null<HistoryMessage*> create(
|
||||||
return _create(history, msgId, flags, replyTo, viaBotId, date, from, postAuthor, textWithEntities);
|
not_null<History*> history,
|
||||||
|
MsgId msgId,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
MsgId replyTo,
|
||||||
|
UserId viaBotId,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
const TextWithEntities &textWithEntities) {
|
||||||
|
return _create(
|
||||||
|
history,
|
||||||
|
msgId,
|
||||||
|
flags,
|
||||||
|
replyTo,
|
||||||
|
viaBotId,
|
||||||
|
date,
|
||||||
|
from,
|
||||||
|
postAuthor,
|
||||||
|
textWithEntities);
|
||||||
}
|
}
|
||||||
static not_null<HistoryMessage*> create(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
static not_null<HistoryMessage*> create(
|
||||||
return _create(history, msgId, flags, replyTo, viaBotId, date, from, postAuthor, doc, caption, markup);
|
not_null<History*> history,
|
||||||
|
MsgId msgId,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
MsgId replyTo,
|
||||||
|
UserId viaBotId,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
not_null<DocumentData*> document,
|
||||||
|
const QString &caption,
|
||||||
|
const MTPReplyMarkup &markup) {
|
||||||
|
return _create(
|
||||||
|
history,
|
||||||
|
msgId,
|
||||||
|
flags,
|
||||||
|
replyTo,
|
||||||
|
viaBotId,
|
||||||
|
date,
|
||||||
|
from,
|
||||||
|
postAuthor,
|
||||||
|
document,
|
||||||
|
caption,
|
||||||
|
markup);
|
||||||
}
|
}
|
||||||
static not_null<HistoryMessage*> create(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
static not_null<HistoryMessage*> create(
|
||||||
return _create(history, msgId, flags, replyTo, viaBotId, date, from, postAuthor, photo, caption, markup);
|
not_null<History*> history,
|
||||||
|
MsgId msgId,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
MsgId replyTo,
|
||||||
|
UserId viaBotId,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
not_null<PhotoData*> photo,
|
||||||
|
const QString &caption,
|
||||||
|
const MTPReplyMarkup &markup) {
|
||||||
|
return _create(
|
||||||
|
history,
|
||||||
|
msgId,
|
||||||
|
flags,
|
||||||
|
replyTo,
|
||||||
|
viaBotId,
|
||||||
|
date,
|
||||||
|
from,
|
||||||
|
postAuthor,
|
||||||
|
photo,
|
||||||
|
caption,
|
||||||
|
markup);
|
||||||
}
|
}
|
||||||
static not_null<HistoryMessage*> create(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup) {
|
static not_null<HistoryMessage*> create(
|
||||||
return _create(history, msgId, flags, replyTo, viaBotId, date, from, postAuthor, game, markup);
|
not_null<History*> history,
|
||||||
|
MsgId msgId,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
MsgId replyTo,
|
||||||
|
UserId viaBotId,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
not_null<GameData*> game,
|
||||||
|
const MTPReplyMarkup &markup) {
|
||||||
|
return _create(
|
||||||
|
history,
|
||||||
|
msgId,
|
||||||
|
flags,
|
||||||
|
replyTo,
|
||||||
|
viaBotId,
|
||||||
|
date,
|
||||||
|
from,
|
||||||
|
postAuthor,
|
||||||
|
game,
|
||||||
|
markup);
|
||||||
}
|
}
|
||||||
|
|
||||||
void initTime();
|
void initTime();
|
||||||
|
@ -156,13 +249,65 @@ public:
|
||||||
~HistoryMessage();
|
~HistoryMessage();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HistoryMessage(not_null<History*> history, const MTPDmessage &msg);
|
HistoryMessage(
|
||||||
HistoryMessage(not_null<History*> history, const MTPDmessageService &msg);
|
not_null<History*> history,
|
||||||
HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, not_null<HistoryMessage*> fwd); // local forwarded
|
const MTPDmessage &msg);
|
||||||
HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, const TextWithEntities &textWithEntities); // local message
|
HistoryMessage(
|
||||||
HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); // local document
|
not_null<History*> history,
|
||||||
HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); // local photo
|
const MTPDmessageService &msg);
|
||||||
HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, QDateTime date, UserId from, const QString &postAuthor, GameData *game, const MTPReplyMarkup &markup); // local game
|
HistoryMessage(
|
||||||
|
not_null<History*> history,
|
||||||
|
MsgId msgId,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
not_null<HistoryMessage*> fwd); // local forwarded
|
||||||
|
HistoryMessage(
|
||||||
|
not_null<History*> history,
|
||||||
|
MsgId msgId,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
MsgId replyTo,
|
||||||
|
UserId viaBotId,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
const TextWithEntities &textWithEntities); // local message
|
||||||
|
HistoryMessage(
|
||||||
|
not_null<History*> history,
|
||||||
|
MsgId msgId,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
MsgId replyTo,
|
||||||
|
UserId viaBotId,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
not_null<DocumentData*> document,
|
||||||
|
const QString &caption,
|
||||||
|
const MTPReplyMarkup &markup); // local document
|
||||||
|
HistoryMessage(
|
||||||
|
not_null<History*> history,
|
||||||
|
MsgId msgId,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
MsgId replyTo,
|
||||||
|
UserId viaBotId,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
not_null<PhotoData*> photo,
|
||||||
|
const QString &caption,
|
||||||
|
const MTPReplyMarkup &markup); // local photo
|
||||||
|
HistoryMessage(
|
||||||
|
not_null<History*> history,
|
||||||
|
MsgId msgId,
|
||||||
|
MTPDmessage::Flags flags,
|
||||||
|
MsgId replyTo,
|
||||||
|
UserId viaBotId,
|
||||||
|
QDateTime date,
|
||||||
|
UserId from,
|
||||||
|
const QString &postAuthor,
|
||||||
|
not_null<GameData*> game,
|
||||||
|
const MTPReplyMarkup &markup); // local game
|
||||||
friend class HistoryItemInstantiated<HistoryMessage>;
|
friend class HistoryItemInstantiated<HistoryMessage>;
|
||||||
|
|
||||||
void setEmptyText();
|
void setEmptyText();
|
||||||
|
@ -214,6 +359,7 @@ private:
|
||||||
QString authorOriginal;
|
QString authorOriginal;
|
||||||
QDateTime originalDate;
|
QDateTime originalDate;
|
||||||
QDateTime editDate;
|
QDateTime editDate;
|
||||||
|
MessageGroupId groupId = MessageGroupId::None;
|
||||||
|
|
||||||
// For messages created from MTP structs.
|
// For messages created from MTP structs.
|
||||||
const MTPReplyMarkup *mtpMarkup = nullptr;
|
const MTPReplyMarkup *mtpMarkup = nullptr;
|
||||||
|
|
|
@ -4521,7 +4521,9 @@ void HistoryWidget::onThumbDocumentUploaded(
|
||||||
|
|
||||||
void HistoryWidget::onPhotoProgress(const FullMsgId &newId) {
|
void HistoryWidget::onPhotoProgress(const FullMsgId &newId) {
|
||||||
if (const auto item = App::histItemById(newId)) {
|
if (const auto item = App::histItemById(newId)) {
|
||||||
const auto photo = (item->getMedia() && item->getMedia()->type() == MediaTypePhoto) ? static_cast<HistoryPhoto*>(item->getMedia())->photo() : nullptr;
|
const auto photo = (item->getMedia() && item->getMedia()->type() == MediaTypePhoto)
|
||||||
|
? static_cast<HistoryPhoto*>(item->getMedia())->photo().get()
|
||||||
|
: nullptr;
|
||||||
updateSendAction(item->history(), SendAction::Type::UploadPhoto, 0);
|
updateSendAction(item->history(), SendAction::Type::UploadPhoto, 0);
|
||||||
Auth().data().requestItemRepaint(item);
|
Auth().data().requestItemRepaint(item);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3680,32 +3680,27 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha
|
||||||
// feed messages and groups, copy from App::feedMsgs
|
// feed messages and groups, copy from App::feedMsgs
|
||||||
auto h = App::history(channel->id);
|
auto h = App::history(channel->id);
|
||||||
auto &vmsgs = d.vnew_messages.v;
|
auto &vmsgs = d.vnew_messages.v;
|
||||||
QMap<uint64, int> msgsIds;
|
auto indices = base::flat_map<uint64, int>();
|
||||||
for (int i = 0, l = vmsgs.size(); i < l; ++i) {
|
for (auto i = 0, l = vmsgs.size(); i != l; ++i) {
|
||||||
auto &msg = vmsgs[i];
|
const auto &msg = vmsgs[i];
|
||||||
switch (msg.type()) {
|
if (msg.type() == mtpc_message) {
|
||||||
case mtpc_message: {
|
const auto &data = msg.c_message();
|
||||||
const auto &d(msg.c_message());
|
if (App::checkEntitiesAndViewsUpdate(data)) { // new message, index my forwarded messages to links _overview, already in blocks
|
||||||
if (App::checkEntitiesAndViewsUpdate(d)) { // new message, index my forwarded messages to links _overview, already in blocks
|
|
||||||
LOG(("Skipping message, because it is already in blocks!"));
|
LOG(("Skipping message, because it is already in blocks!"));
|
||||||
} else {
|
continue;
|
||||||
msgsIds.insert((uint64(uint32(d.vid.v)) << 32) | uint64(i), i + 1);
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case mtpc_messageEmpty: msgsIds.insert((uint64(uint32(msg.c_messageEmpty().vid.v)) << 32) | uint64(i), i + 1); break;
|
|
||||||
case mtpc_messageService: msgsIds.insert((uint64(uint32(msg.c_messageService().vid.v)) << 32) | uint64(i), i + 1); break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for_const (auto msgIndex, msgsIds) {
|
const auto msgId = idFromMessage(msg);
|
||||||
if (msgIndex > 0) { // add message
|
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
|
||||||
auto &msg = vmsgs.at(msgIndex - 1);
|
}
|
||||||
|
for (const auto [position, index] : indices) {
|
||||||
|
const auto &msg = vmsgs[index];
|
||||||
if (channel->id != peerFromMessage(msg)) {
|
if (channel->id != peerFromMessage(msg)) {
|
||||||
LOG(("API Error: message with invalid peer returned in channelDifference, channelId: %1, peer: %2").arg(peerToChannel(channel->id)).arg(peerFromMessage(msg)));
|
LOG(("API Error: message with invalid peer returned in channelDifference, channelId: %1, peer: %2").arg(peerToChannel(channel->id)).arg(peerFromMessage(msg)));
|
||||||
continue; // wtf
|
continue; // wtf
|
||||||
}
|
}
|
||||||
h->addNewMessage(msg, NewMessageUnread);
|
h->addNewMessage(msg, NewMessageUnread);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
feedUpdateVector(d.vother_updates, true);
|
feedUpdateVector(d.vother_updates, true);
|
||||||
_handlingChannelDifference = false;
|
_handlingChannelDifference = false;
|
||||||
|
|
|
@ -81,8 +81,11 @@ enum class MTPDmessage_ClientFlag : uint32 {
|
||||||
// message has an admin badge in supergroup
|
// message has an admin badge in supergroup
|
||||||
f_has_admin_badge = (1U << 20),
|
f_has_admin_badge = (1U << 20),
|
||||||
|
|
||||||
|
// message is not displayed because it is part of a group
|
||||||
|
f_hidden_by_group = (1U << 19),
|
||||||
|
|
||||||
// update this when adding new client side flags
|
// update this when adding new client side flags
|
||||||
MIN_FIELD = (1U << 20),
|
MIN_FIELD = (1U << 19),
|
||||||
};
|
};
|
||||||
DEFINE_MTP_CLIENT_FLAGS(MTPDmessage)
|
DEFINE_MTP_CLIENT_FLAGS(MTPDmessage)
|
||||||
|
|
||||||
|
|
|
@ -1201,7 +1201,9 @@ Link::Link(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_page = (media && media->type() == MediaTypeWebPage) ? static_cast<HistoryWebPage*>(media)->webpage().get() : nullptr;
|
_page = (media && media->type() == MediaTypeWebPage)
|
||||||
|
? static_cast<HistoryWebPage*>(media)->webpage().get()
|
||||||
|
: nullptr;
|
||||||
if (_page) {
|
if (_page) {
|
||||||
mainUrl = _page->url;
|
mainUrl = _page->url;
|
||||||
if (_page->document) {
|
if (_page->document) {
|
||||||
|
|
|
@ -0,0 +1,589 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||||
|
|
||||||
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
It is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
In addition, as a special exception, the copyright holders give permission
|
||||||
|
to link the code of portions of this program with the OpenSSL library.
|
||||||
|
|
||||||
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||||
|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
|
*/
|
||||||
|
#include "ui/grouped_layout.h"
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
int Round(float64 value) {
|
||||||
|
return int(std::round(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
class Layouter {
|
||||||
|
public:
|
||||||
|
Layouter(
|
||||||
|
const std::vector<QSize> &sizes,
|
||||||
|
int maxWidth,
|
||||||
|
int minWidth,
|
||||||
|
int spacing);
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> layout() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::vector<float64> CountRatios(const std::vector<QSize> &sizes);
|
||||||
|
static std::string CountProportions(const std::vector<float64> &ratios);
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> layoutTwo() const;
|
||||||
|
std::vector<GroupMediaLayout> layoutThree() const;
|
||||||
|
std::vector<GroupMediaLayout> layoutFour() const;
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> layoutOne() const;
|
||||||
|
std::vector<GroupMediaLayout> layoutTwoTopBottom() const;
|
||||||
|
std::vector<GroupMediaLayout> layoutTwoLeftRightEqual() const;
|
||||||
|
std::vector<GroupMediaLayout> layoutTwoLeftRight() const;
|
||||||
|
std::vector<GroupMediaLayout> layoutThreeLeftAndOther() const;
|
||||||
|
std::vector<GroupMediaLayout> layoutThreeTopAndOther() const;
|
||||||
|
std::vector<GroupMediaLayout> layoutFourLeftAndOther() const;
|
||||||
|
std::vector<GroupMediaLayout> layoutFourTopAndOther() const;
|
||||||
|
|
||||||
|
const std::vector<QSize> &_sizes;
|
||||||
|
const std::vector<float64> _ratios;
|
||||||
|
const std::string _proportions;
|
||||||
|
const int _count = 0;
|
||||||
|
const int _maxWidth = 0;
|
||||||
|
const int _maxHeight = 0;
|
||||||
|
const int _minWidth = 0;
|
||||||
|
const int _spacing = 0;
|
||||||
|
const float64 _averageRatio = 1.;
|
||||||
|
const float64 _maxSizeRatio = 1.;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class ComplexLayouter {
|
||||||
|
public:
|
||||||
|
ComplexLayouter(
|
||||||
|
const std::vector<float64> &ratios,
|
||||||
|
float64 averageRatio,
|
||||||
|
int maxWidth,
|
||||||
|
int minWidth,
|
||||||
|
int spacing);
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> layout() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Attempt {
|
||||||
|
std::vector<int> lineCounts;
|
||||||
|
std::vector<float64> heights;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<float64> CropRatios(
|
||||||
|
const std::vector<float64> &ratios,
|
||||||
|
float64 averageRatio);
|
||||||
|
|
||||||
|
const std::vector<float64> _ratios;
|
||||||
|
const int _count = 0;
|
||||||
|
const int _maxWidth = 0;
|
||||||
|
const int _maxHeight = 0;
|
||||||
|
const int _minWidth = 0;
|
||||||
|
const int _spacing = 0;
|
||||||
|
const float64 _averageRatio = 1.;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Layouter::Layouter(
|
||||||
|
const std::vector<QSize> &sizes,
|
||||||
|
int maxWidth,
|
||||||
|
int minWidth,
|
||||||
|
int spacing)
|
||||||
|
: _sizes(sizes)
|
||||||
|
, _ratios(CountRatios(_sizes))
|
||||||
|
, _proportions(CountProportions(_ratios))
|
||||||
|
, _count(int(_ratios.size()))
|
||||||
|
// All apps currently use square max size first.
|
||||||
|
// In complex case they use maxWidth * 4 / 3 as maxHeight.
|
||||||
|
, _maxWidth(maxWidth)
|
||||||
|
, _maxHeight(maxWidth)
|
||||||
|
, _minWidth(minWidth)
|
||||||
|
, _spacing(spacing)
|
||||||
|
, _averageRatio(ranges::accumulate(_ratios, 0.) / _count)
|
||||||
|
, _maxSizeRatio(_maxWidth / float64(_maxHeight)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<float64> Layouter::CountRatios(const std::vector<QSize> &sizes) {
|
||||||
|
return ranges::view::all(
|
||||||
|
sizes
|
||||||
|
) | ranges::view::transform([](const QSize &size) {
|
||||||
|
return size.width() / float64(size.height());
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Layouter::CountProportions(const std::vector<float64> &ratios) {
|
||||||
|
return ranges::view::all(
|
||||||
|
ratios
|
||||||
|
) | ranges::view::transform([](float64 ratio) {
|
||||||
|
return (ratio > 1.2) ? 'w' : (ratio < 0.8) ? 'n' : 'q';
|
||||||
|
}) | ranges::to_<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> Layouter::layout() const {
|
||||||
|
if (!_count) {
|
||||||
|
return {};
|
||||||
|
} else if (_count == 1) {
|
||||||
|
return layoutOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace rpl::mappers;
|
||||||
|
if (_count >= 5 || ranges::find_if(_ratios, _1 > 2) != _ratios.end()) {
|
||||||
|
return ComplexLayouter(
|
||||||
|
_ratios,
|
||||||
|
_averageRatio,
|
||||||
|
_maxWidth,
|
||||||
|
_minWidth,
|
||||||
|
_spacing).layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_count == 2) {
|
||||||
|
return layoutTwo();
|
||||||
|
} else if (_count == 3) {
|
||||||
|
return layoutThree();
|
||||||
|
}
|
||||||
|
return layoutFour();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> Layouter::layoutTwo() const {
|
||||||
|
Expects(_count == 2);
|
||||||
|
|
||||||
|
if ((_proportions == "ww")
|
||||||
|
&& (_averageRatio > 1.4 * _maxSizeRatio)
|
||||||
|
&& (_ratios[1] - _ratios[0] < 0.2)) {
|
||||||
|
return layoutTwoTopBottom();
|
||||||
|
} else if (_proportions == "ww" || _proportions == "qq") {
|
||||||
|
return layoutTwoLeftRightEqual();
|
||||||
|
}
|
||||||
|
return layoutTwoLeftRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> Layouter::layoutThree() const {
|
||||||
|
Expects(_count == 3);
|
||||||
|
|
||||||
|
auto result = std::vector<GroupMediaLayout>(_count);
|
||||||
|
if (_proportions[0] == 'n') {
|
||||||
|
return layoutThreeLeftAndOther();
|
||||||
|
}
|
||||||
|
return layoutThreeTopAndOther();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> Layouter::layoutFour() const {
|
||||||
|
Expects(_count == 4);
|
||||||
|
|
||||||
|
auto result = std::vector<GroupMediaLayout>(_count);
|
||||||
|
if (_proportions[0] == 'w') {
|
||||||
|
return layoutFourTopAndOther();
|
||||||
|
}
|
||||||
|
return layoutFourLeftAndOther();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> Layouter::layoutOne() const {
|
||||||
|
Expects(_count == 1);
|
||||||
|
|
||||||
|
const auto width = _maxWidth;
|
||||||
|
const auto height = (_sizes[0].height() * width) / _sizes[0].width();
|
||||||
|
|
||||||
|
return {
|
||||||
|
{
|
||||||
|
QRect(0, 0, width, height),
|
||||||
|
RectPart::Left | RectPart::Top | RectPart::Right | RectPart::Bottom
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> Layouter::layoutTwoTopBottom() const {
|
||||||
|
Expects(_count == 2);
|
||||||
|
|
||||||
|
const auto width = _maxWidth;
|
||||||
|
const auto height = Round(std::min(
|
||||||
|
width / _ratios[0],
|
||||||
|
std::min(
|
||||||
|
width / _ratios[1],
|
||||||
|
(_maxHeight - _spacing) / 2.)));
|
||||||
|
|
||||||
|
return {
|
||||||
|
{
|
||||||
|
QRect(0, 0, width, height),
|
||||||
|
RectPart::Left | RectPart::Top | RectPart::Right
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QRect(0, height + _spacing, width, height),
|
||||||
|
RectPart::Left | RectPart::Bottom | RectPart::Right
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> Layouter::layoutTwoLeftRightEqual() const {
|
||||||
|
Expects(_count == 2);
|
||||||
|
|
||||||
|
const auto width = (_maxWidth - _spacing) / 2;
|
||||||
|
const auto height = Round(std::min(
|
||||||
|
width / _ratios[0],
|
||||||
|
std::min(width / _ratios[1], _maxHeight * 1.)));
|
||||||
|
|
||||||
|
return {
|
||||||
|
{
|
||||||
|
QRect(0, 0, width, height),
|
||||||
|
RectPart::Top | RectPart::Left | RectPart::Bottom
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QRect(width + _spacing, 0, width, height),
|
||||||
|
RectPart::Top | RectPart::Right | RectPart::Bottom
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> Layouter::layoutTwoLeftRight() const {
|
||||||
|
Expects(_count == 2);
|
||||||
|
|
||||||
|
const auto minimalWidth = Round(_minWidth * 1.5);
|
||||||
|
const auto secondWidth = std::min(
|
||||||
|
Round(std::max(
|
||||||
|
0.4 * (_maxWidth - _spacing),
|
||||||
|
(_maxWidth - _spacing) / _ratios[0]
|
||||||
|
/ (1. / _ratios[0] + 1. / _ratios[1]))),
|
||||||
|
_maxWidth - _spacing - minimalWidth);
|
||||||
|
const auto firstWidth = _maxWidth
|
||||||
|
- secondWidth
|
||||||
|
- _spacing;
|
||||||
|
const auto height = std::min(
|
||||||
|
_maxHeight,
|
||||||
|
Round(std::min(
|
||||||
|
firstWidth / _ratios[0],
|
||||||
|
secondWidth / _ratios[1])));
|
||||||
|
|
||||||
|
return {
|
||||||
|
{
|
||||||
|
QRect(0, 0, firstWidth, height),
|
||||||
|
RectPart::Top | RectPart::Left | RectPart::Bottom
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QRect(firstWidth + _spacing, 0, secondWidth, height),
|
||||||
|
RectPart::Top | RectPart::Right | RectPart::Bottom
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> Layouter::layoutThreeLeftAndOther() const {
|
||||||
|
Expects(_count == 3);
|
||||||
|
|
||||||
|
const auto firstHeight = _maxHeight;
|
||||||
|
const auto thirdHeight = Round(std::min(
|
||||||
|
(_maxHeight - _spacing) / 2.,
|
||||||
|
(_ratios[1] * (_maxWidth - _spacing)
|
||||||
|
/ (_ratios[2] + _ratios[1]))));
|
||||||
|
const auto secondHeight = firstHeight
|
||||||
|
- thirdHeight
|
||||||
|
- _spacing;
|
||||||
|
const auto rightWidth = std::max(
|
||||||
|
_minWidth,
|
||||||
|
Round(std::min(
|
||||||
|
(_maxWidth - _spacing) / 2.,
|
||||||
|
std::min(
|
||||||
|
thirdHeight * _ratios[2],
|
||||||
|
secondHeight * _ratios[1]))));
|
||||||
|
const auto leftWidth = std::min(
|
||||||
|
Round(firstHeight * _ratios[0]),
|
||||||
|
_maxWidth - _spacing - rightWidth);
|
||||||
|
|
||||||
|
return {
|
||||||
|
{
|
||||||
|
QRect(0, 0, leftWidth, firstHeight),
|
||||||
|
RectPart::Top | RectPart::Left | RectPart::Bottom
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QRect(leftWidth + _spacing, 0, rightWidth, secondHeight),
|
||||||
|
RectPart::Top | RectPart::Right
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QRect(leftWidth + _spacing, secondHeight + _spacing, rightWidth, thirdHeight),
|
||||||
|
RectPart::Bottom | RectPart::Right
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> Layouter::layoutThreeTopAndOther() const {
|
||||||
|
Expects(_count == 3);
|
||||||
|
|
||||||
|
const auto firstWidth = _maxWidth;
|
||||||
|
const auto firstHeight = Round(std::min(
|
||||||
|
firstWidth / _ratios[0],
|
||||||
|
(_maxHeight - _spacing) * 0.66));
|
||||||
|
const auto secondWidth = (_maxWidth - _spacing) / 2;
|
||||||
|
const auto secondHeight = std::min(
|
||||||
|
_maxHeight - firstHeight - _spacing,
|
||||||
|
Round(std::min(
|
||||||
|
secondWidth / _ratios[1],
|
||||||
|
secondWidth / _ratios[2])));
|
||||||
|
const auto thirdWidth = firstWidth - secondWidth - _spacing;
|
||||||
|
|
||||||
|
return {
|
||||||
|
{
|
||||||
|
QRect(0, 0, firstWidth, firstHeight),
|
||||||
|
RectPart::Left | RectPart::Top | RectPart::Right
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QRect(0, firstHeight + _spacing, secondWidth, secondHeight),
|
||||||
|
RectPart::Bottom | RectPart::Left
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QRect(secondWidth + _spacing, firstHeight + _spacing, thirdWidth, secondHeight),
|
||||||
|
RectPart::Bottom | RectPart::Right
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> Layouter::layoutFourTopAndOther() const {
|
||||||
|
Expects(_count == 4);
|
||||||
|
|
||||||
|
const auto w = _maxWidth;
|
||||||
|
const auto h0 = Round(std::min(
|
||||||
|
w / _ratios[0],
|
||||||
|
(_maxHeight - _spacing) * 0.66));
|
||||||
|
const auto h = Round(
|
||||||
|
(_maxWidth - 2 * _spacing)
|
||||||
|
/ (_ratios[1] + _ratios[2] + _ratios[3]));
|
||||||
|
const auto w0 = std::max(
|
||||||
|
_minWidth,
|
||||||
|
Round(std::min(
|
||||||
|
(_maxWidth - 2 * _spacing) * 0.4,
|
||||||
|
h * _ratios[1])));
|
||||||
|
const auto w2 = Round(std::max(
|
||||||
|
std::max(
|
||||||
|
_minWidth * 1.,
|
||||||
|
(_maxWidth - 2 * _spacing) * 0.33),
|
||||||
|
h * _ratios[3]));
|
||||||
|
const auto w1 = w - w0 - w2 - 2 * _spacing;
|
||||||
|
const auto h1 = std::min(
|
||||||
|
_maxHeight - h0 - _spacing,
|
||||||
|
h);
|
||||||
|
|
||||||
|
return {
|
||||||
|
{
|
||||||
|
QRect(0, 0, w, h0),
|
||||||
|
RectPart::Left | RectPart::Top | RectPart::Right
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QRect(0, h0 + _spacing, w0, h1),
|
||||||
|
RectPart::Bottom | RectPart::Left
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QRect(w0 + _spacing, h0 + _spacing, w1, h1),
|
||||||
|
RectPart::Bottom,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QRect(w0 + _spacing + w1 + _spacing, h0 + _spacing, w2, h1),
|
||||||
|
RectPart::Right | RectPart::BottomLeft
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> Layouter::layoutFourLeftAndOther() const {
|
||||||
|
Expects(_count == 4);
|
||||||
|
|
||||||
|
const auto h = _maxHeight;
|
||||||
|
const auto w0 = Round(std::min(
|
||||||
|
h * _ratios[0],
|
||||||
|
(_maxWidth - _spacing) * 0.6));
|
||||||
|
|
||||||
|
const auto w = Round(
|
||||||
|
(_maxHeight - 2 * _spacing)
|
||||||
|
/ (1. / _ratios[1] + 1. / _ratios[2] + 1. / _ratios[3])
|
||||||
|
);
|
||||||
|
const auto h0 = Round(w / _ratios[1]);
|
||||||
|
const auto h1 = Round(w / _ratios[2]);
|
||||||
|
const auto h2 = h - h0 - h1 - 2 * _spacing;
|
||||||
|
const auto w1 = std::max(
|
||||||
|
_minWidth,
|
||||||
|
std::min(_maxWidth - w0 - _spacing, w));
|
||||||
|
|
||||||
|
return {
|
||||||
|
{
|
||||||
|
QRect(0, 0, w0, h),
|
||||||
|
RectPart::Top | RectPart::Left | RectPart::Bottom
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QRect(w0 + _spacing, 0, w1, h0),
|
||||||
|
RectPart::Top | RectPart::Right
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QRect(w0 + _spacing, h0 + _spacing, w1, h1),
|
||||||
|
RectPart::Right
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QRect(w0 + _spacing, h0 + h1 + 2 * _spacing, w1, h2),
|
||||||
|
RectPart::Bottom | RectPart::Right
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ComplexLayouter::ComplexLayouter(
|
||||||
|
const std::vector<float64> &ratios,
|
||||||
|
float64 averageRatio,
|
||||||
|
int maxWidth,
|
||||||
|
int minWidth,
|
||||||
|
int spacing)
|
||||||
|
: _ratios(CropRatios(ratios, averageRatio))
|
||||||
|
, _count(int(_ratios.size()))
|
||||||
|
// All apps currently use square max size first.
|
||||||
|
// In complex case they use maxWidth * 4 / 3 as maxHeight.
|
||||||
|
, _maxWidth(maxWidth)
|
||||||
|
, _maxHeight(maxWidth * 4 / 3)
|
||||||
|
, _minWidth(minWidth)
|
||||||
|
, _spacing(spacing)
|
||||||
|
, _averageRatio(averageRatio) {
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<float64> ComplexLayouter::CropRatios(
|
||||||
|
const std::vector<float64> &ratios,
|
||||||
|
float64 averageRatio) {
|
||||||
|
return ranges::view::all(
|
||||||
|
ratios
|
||||||
|
) | ranges::view::transform([&](float64 ratio) {
|
||||||
|
return (averageRatio > 1.1)
|
||||||
|
? snap(ratio, 1., 1.7)
|
||||||
|
: snap(ratio, 0.66667, 1.);
|
||||||
|
}) | ranges::to_vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> ComplexLayouter::layout() const {
|
||||||
|
Expects(_count > 1);
|
||||||
|
|
||||||
|
auto result = std::vector<GroupMediaLayout>(_count);
|
||||||
|
|
||||||
|
auto attempts = std::vector<Attempt>();
|
||||||
|
const auto multiHeight = [&](int offset, int count) {
|
||||||
|
const auto ratios = gsl::make_span(_ratios).subspan(offset, count);
|
||||||
|
const auto sum = ranges::accumulate(ratios, 0.);
|
||||||
|
return (_maxWidth - (count - 1) * _spacing) / sum;
|
||||||
|
};
|
||||||
|
const auto pushAttempt = [&](std::vector<int> lineCounts) {
|
||||||
|
auto heights = std::vector<float64>();
|
||||||
|
heights.reserve(lineCounts.size());
|
||||||
|
auto offset = 0;
|
||||||
|
for (auto count : lineCounts) {
|
||||||
|
heights.push_back(multiHeight(offset, count));
|
||||||
|
offset += count;
|
||||||
|
}
|
||||||
|
attempts.push_back({ std::move(lineCounts), std::move(heights) });
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto first = 1; first != _count; ++first) {
|
||||||
|
const auto second = _count - first;
|
||||||
|
if (first > 3 || second > 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pushAttempt({ first, second });
|
||||||
|
}
|
||||||
|
for (auto first = 1; first != _count - 1; ++first) {
|
||||||
|
for (auto second = 1; second != _count - first; ++second) {
|
||||||
|
const auto third = _count - first - second;
|
||||||
|
if ((first > 3)
|
||||||
|
|| (second > ((_averageRatio < 0.85) ? 4 : 3))
|
||||||
|
|| (third > 3)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pushAttempt({ first, second, third });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto first = 1; first != _count - 1; ++first) {
|
||||||
|
for (auto second = 1; second != _count - first; ++second) {
|
||||||
|
for (auto third = 1; third != _count - first - second; ++third) {
|
||||||
|
const auto fourth = _count - first - second - third;
|
||||||
|
if (first > 3 || second > 3 || third > 3 || fourth > 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pushAttempt({ first, second, third, fourth });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto optimalAttempt = (const Attempt*)nullptr;
|
||||||
|
auto optimalDiff = 0.;
|
||||||
|
for (const auto &attempt : attempts) {
|
||||||
|
const auto &heights = attempt.heights;
|
||||||
|
const auto &counts = attempt.lineCounts;
|
||||||
|
const auto lineCount = int(counts.size());
|
||||||
|
const auto totalHeight = ranges::accumulate(heights, 0.)
|
||||||
|
+ _spacing * (lineCount - 1);
|
||||||
|
const auto minLineHeight = ranges::min(heights);
|
||||||
|
const auto maxLineHeight = ranges::max(heights);
|
||||||
|
const auto bad1 = (minLineHeight < _minWidth) ? 1.5 : 1.;
|
||||||
|
const auto bad2 = [&] {
|
||||||
|
for (auto line = 1; line != lineCount; ++line) {
|
||||||
|
if (counts[line - 1] > counts[line]) {
|
||||||
|
return 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1.;
|
||||||
|
}();
|
||||||
|
const auto diff = std::abs(totalHeight - _maxHeight) * bad1 * bad2;
|
||||||
|
if (!optimalAttempt || diff < optimalDiff) {
|
||||||
|
optimalAttempt = &attempt;
|
||||||
|
optimalDiff = diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert(optimalAttempt != nullptr);
|
||||||
|
|
||||||
|
const auto &optimalCounts = optimalAttempt->lineCounts;
|
||||||
|
const auto &optimalHeights = optimalAttempt->heights;
|
||||||
|
const auto rowCount = int(optimalCounts.size());
|
||||||
|
|
||||||
|
auto index = 0;
|
||||||
|
auto y = 0.;
|
||||||
|
for (auto row = 0; row != rowCount; ++row) {
|
||||||
|
const auto colCount = optimalCounts[row];
|
||||||
|
const auto lineHeight = optimalHeights[row];
|
||||||
|
const auto height = Round(lineHeight);
|
||||||
|
|
||||||
|
auto x = 0;
|
||||||
|
for (auto col = 0; col != colCount; ++col) {
|
||||||
|
const auto sides = RectPart::None
|
||||||
|
| (row == 0 ? RectPart::Top : RectPart::None)
|
||||||
|
| (row == rowCount - 1 ? RectPart::Bottom : RectPart::None)
|
||||||
|
| (col == 0 ? RectPart::Left : RectPart::None)
|
||||||
|
| (col == colCount - 1 ? RectPart::Right : RectPart::None);
|
||||||
|
|
||||||
|
const auto ratio = _ratios[index];
|
||||||
|
const auto width = (col == colCount - 1)
|
||||||
|
? (_maxWidth - x)
|
||||||
|
: Round(ratio * lineHeight);
|
||||||
|
result[index] = {
|
||||||
|
QRect(x, y, width, height),
|
||||||
|
sides
|
||||||
|
};
|
||||||
|
|
||||||
|
x += width + _spacing;
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
y += height + _spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> LayoutMediaGroup(
|
||||||
|
const std::vector<QSize> &sizes,
|
||||||
|
int maxWidth,
|
||||||
|
int minWidth,
|
||||||
|
int spacing) {
|
||||||
|
return Layouter(sizes, maxWidth, minWidth, spacing).layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Data
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||||
|
|
||||||
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
It is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
In addition, as a special exception, the copyright holders give permission
|
||||||
|
to link the code of portions of this program with the OpenSSL library.
|
||||||
|
|
||||||
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||||
|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
|
||||||
|
struct GroupMediaLayout {
|
||||||
|
QRect geometry;
|
||||||
|
RectParts sides = RectPart::None;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<GroupMediaLayout> LayoutMediaGroup(
|
||||||
|
const std::vector<QSize> &sizes,
|
||||||
|
int maxWidth,
|
||||||
|
int minWidth,
|
||||||
|
int spacing);
|
||||||
|
|
||||||
|
} // namespace Data
|
|
@ -219,6 +219,8 @@
|
||||||
<(src_loc)/history/history_location_manager.h
|
<(src_loc)/history/history_location_manager.h
|
||||||
<(src_loc)/history/history_media.h
|
<(src_loc)/history/history_media.h
|
||||||
<(src_loc)/history/history_media.cpp
|
<(src_loc)/history/history_media.cpp
|
||||||
|
<(src_loc)/history/history_media_grouped.h
|
||||||
|
<(src_loc)/history/history_media_grouped.cpp
|
||||||
<(src_loc)/history/history_media_types.cpp
|
<(src_loc)/history/history_media_types.cpp
|
||||||
<(src_loc)/history/history_media_types.h
|
<(src_loc)/history/history_media_types.h
|
||||||
<(src_loc)/history/history_message.cpp
|
<(src_loc)/history/history_message.cpp
|
||||||
|
@ -607,6 +609,8 @@
|
||||||
<(src_loc)/ui/empty_userpic.cpp
|
<(src_loc)/ui/empty_userpic.cpp
|
||||||
<(src_loc)/ui/empty_userpic.h
|
<(src_loc)/ui/empty_userpic.h
|
||||||
<(src_loc)/ui/focus_persister.h
|
<(src_loc)/ui/focus_persister.h
|
||||||
|
<(src_loc)/ui/grouped_layout.cpp
|
||||||
|
<(src_loc)/ui/grouped_layout.h
|
||||||
<(src_loc)/ui/images.cpp
|
<(src_loc)/ui/images.cpp
|
||||||
<(src_loc)/ui/images.h
|
<(src_loc)/ui/images.h
|
||||||
<(src_loc)/ui/resize_area.h
|
<(src_loc)/ui/resize_area.h
|
||||||
|
|
Loading…
Reference in New Issue