Compare commits

..

15 Commits

Author SHA1 Message Date
John Preston
de16a66a4a Alpha version 1.2.2: Fix build for Xcode. 2017-12-16 21:09:37 +04:00
John Preston
b0f191515a Alpha version 1.2.2.
- Grouped photos and videos are displayed as albums.
2017-12-16 20:52:41 +04:00
John Preston
89ccaccb88 Display right edited badge in group with caption. 2017-12-16 20:50:43 +04:00
John Preston
1f070da202 Recount grouping after leader caption edit. 2017-12-16 20:50:43 +04:00
John Preston
963e969d2a Fix selected messages copy with grouping. 2017-12-16 20:50:43 +04:00
John Preston
4734700ac5 Improve opening history with one loaded message.
When we add just one last item, like we do while loading dialogs,
we want to remove a single added grouped media, otherwise it will
jump once we open the message history (first we show only that
media, then we load the rest of the group and show the group).

That way when we open the message history we show nothing until a
whole history part is loaded, it certainly will contain the group.
2017-12-16 20:50:43 +04:00
John Preston
d9da2edd7c Improve grouped media display. 2017-12-16 20:50:43 +04:00
John Preston
6d48ca850e Correct reply/forward/delete for grouped items. 2017-12-16 20:50:43 +04:00
John Preston
3e7ac7eb26 Use first media caption for group caption. 2017-12-16 20:50:43 +04:00
John Preston
520a644150 Fix drag by date of grouped media. 2017-12-16 20:50:43 +04:00
John Preston
3a56b7cabd Forward grouped items. Fast share grouped items. 2017-12-16 20:50:43 +04:00
John Preston
efa72578cd Fix grouped media display in MediaView. 2017-12-16 20:50:43 +04:00
John Preston
b6087ce7ce Select/forward/delete group of messages. 2017-12-16 20:50:42 +04:00
John Preston
537400d8b2 Enable distinct selecting of grouped media. 2017-12-16 20:50:42 +04:00
John Preston
4c9931ab02 Support grouped media rendering. 2017-12-16 20:50:42 +04:00
47 changed files with 3884 additions and 786 deletions

View File

@@ -936,6 +936,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_stickers_group_from_featured" = "Choose from trending stickers";
"lng_in_dlg_photo" = "Photo";
"lng_in_dlg_album" = "Album";
"lng_in_dlg_video" = "Video";
"lng_in_dlg_audio_file" = "Audio file";
"lng_in_dlg_contact" = "Contact";

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="1.2.1.0" />
Version="1.2.2.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,2,1,0
PRODUCTVERSION 1,2,1,0
FILEVERSION 1,2,2,0
PRODUCTVERSION 1,2,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -52,10 +52,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "1.2.1.0"
VALUE "FileVersion", "1.2.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.2.1.0"
VALUE "ProductVersion", "1.2.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,2,1,0
PRODUCTVERSION 1,2,1,0
FILEVERSION 1,2,2,0
PRODUCTVERSION 1,2,2,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -43,10 +43,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "1.2.1.0"
VALUE "FileVersion", "1.2.2.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.2.1.0"
VALUE "ProductVersion", "1.2.2.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -120,6 +120,7 @@ void ApiWrap::addLocalChangelogs(int oldAppVersion) {
addLocalAlphaChangelog(1001024, "\xE2\x80\x94 Radically improved navigation. New side panel on the right with quick access to shared media and group members.\n\xE2\x80\x94 Pinned Messages. If you are a channel admin, pin messages to focus your subscribers\xE2\x80\x99 attention on important announcements.\n\xE2\x80\x94 Also supported clearing history in supergroups and added a host of minor improvements.");
addLocalAlphaChangelog(1001026, "\xE2\x80\x94 Admin badges in supergroup messages.\n\xE2\x80\x94 Fix crashing on launch in OS X 10.6.\n\xE2\x80\x94 Bug fixes and other minor improvements.");
addLocalAlphaChangelog(1001027, "\xE2\x80\x94 Saved Messages. Bookmark messages by forwarding them to \xE2\x80\x9C""Saved Messages\xE2\x80\x9D. Access them from the Chats list or from the side menu.");
addLocalAlphaChangelog(1002002, "\xE2\x80\x94 Grouped photos and videos are displayed as albums.");
}
if (!addedSome) {
auto text = lng_new_version_wrap(lt_version, str_const_toString(AppVersionStr), lt_changes, lang(lng_new_version_minor), lt_link, qsl("https://desktop.telegram.org/changelog")).trimmed();
@@ -1608,18 +1609,18 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs
}
if (!v) return;
QMap<uint64, int32> msgsIds; // copied from feedMsgs
for (int32 i = 0, l = v->size(); i < l; ++i) {
const auto &msg(v->at(i));
switch (msg.type()) {
case mtpc_message: msgsIds.insert((uint64(uint32(msg.c_message().vid.v)) << 32) | uint64(i), i); break;
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;
}
auto indices = base::flat_map<uint64, int>(); // copied from feedMsgs
for (auto i = 0, l = v->size(); i != l; ++i) {
const auto msgId = idFromMessage(v->at(i));
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
}
for_const (auto msgId, msgsIds) {
if (auto item = App::histories().addNewMessage(v->at(msgId), NewMessageExisting)) {
for (const auto [position, index] : indices) {
const auto item = App::histories().addNewMessage(
v->at(index),
NewMessageExisting);
if (item) {
item->setPendingInitDimensions();
}
}
@@ -2455,6 +2456,7 @@ void ApiWrap::forwardMessages(
}
auto forwardFrom = items.front()->history()->peer;
auto currentGroupId = items.front()->groupId();
auto ids = QVector<MTPint>();
auto randomIds = QVector<MTPlong>();
@@ -2462,8 +2464,12 @@ void ApiWrap::forwardMessages(
if (shared) {
++shared->requestsLeft;
}
const auto finalFlags = sendFlags
| (currentGroupId == MessageGroupId()
? MTPmessages_ForwardMessages::Flag(0)
: MTPmessages_ForwardMessages::Flag::f_grouped);
history->sendRequestId = request(MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
MTP_flags(finalFlags),
forwardFrom->input,
MTP_vector<MTPint>(ids),
MTP_vector<MTPlong>(randomIds),
@@ -2508,9 +2514,13 @@ void ApiWrap::forwardMessages(
App::historyRegRandom(randomId, newId);
}
}
if (forwardFrom != item->history()->peer) {
const auto newFrom = item->history()->peer;
const auto newGroupId = item->groupId();
if (forwardFrom != newFrom
|| currentGroupId != newGroupId) {
sendAccumulated();
forwardFrom = item->history()->peer;
forwardFrom = newFrom;
currentGroupId = newGroupId;
}
ids.push_back(MTP_int(item->id));
randomIds.push_back(MTP_long(randomId));

View File

@@ -1105,29 +1105,23 @@ namespace {
}
void feedMsgs(const QVector<MTPMessage> &msgs, NewMessageType type) {
QMap<uint64, int32> msgsIds;
for (int32 i = 0, l = msgs.size(); i < l; ++i) {
const auto &msg(msgs.at(i));
switch (msg.type()) {
case mtpc_message: {
const auto &d(msg.c_message());
bool needToAdd = true;
auto indices = base::flat_map<uint64, int>();
for (int i = 0, l = msgs.size(); i != l; ++i) {
const auto &msg = msgs[i];
if (msg.type() == mtpc_message) {
const auto &data = msg.c_message();
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!"));
needToAdd = false;
continue;
}
}
if (needToAdd) {
msgsIds.insert((uint64(uint32(d.vid.v)) << 32) | uint64(i), i);
}
} break;
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;
}
const auto msgId = idFromMessage(msg);
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
}
for (QMap<uint64, int32>::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) {
histories().addNewMessage(msgs.at(i.value()), type);
for (const auto [position, index] : indices) {
histories().addNewMessage(msgs[index], type);
}
}
@@ -2671,7 +2665,9 @@ namespace {
p.setBrush(p.textPalette().selectOverlay);
p.drawEllipse(rect);
} else {
auto overlayCorners = (radius == ImageRoundRadius::Small) ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners;
auto overlayCorners = (radius == ImageRoundRadius::Small)
? SelectedOverlaySmallCorners
: SelectedOverlayLargeCorners;
auto overlayParts = RectPart::Full | RectPart::None;
if (radius == ImageRoundRadius::Large) {
complexAdjustRect(corners, rect, overlayParts);

View File

@@ -378,6 +378,13 @@ MessageIdsList AuthSessionData::itemsToIds(
}) | ranges::to_vector;
}
MessageIdsList AuthSessionData::groupToIds(
not_null<HistoryMessageGroup*> group) const {
auto result = itemsToIds(group->others);
result.push_back(group->leader->fullId());
return result;
}
AuthSession &Auth() {
auto result = Messenger::Instance().authSession();
Assert(result != nullptr);

View File

@@ -263,6 +263,7 @@ public:
HistoryItemsList idsToItems(const MessageIdsList &ids) const;
MessageIdsList itemsToIds(const HistoryItemsList &items) const;
MessageIdsList groupToIds(not_null<HistoryMessageGroup*> group) const;
private:
struct Variables {

View File

@@ -480,7 +480,7 @@ EditCaptionBox::EditCaptionBox(QWidget*, HistoryMedia *media, FullMsgId msgId) :
case MediaTypePhoto: {
_photo = true;
auto photo = static_cast<HistoryPhoto*>(media)->photo();
auto photo = static_cast<HistoryPhoto*>(media)->getPhoto();
dimensions = QSize(photo->full->width(), photo->full->height());
image = photo->full;
} break;
@@ -492,6 +492,17 @@ EditCaptionBox::EditCaptionBox(QWidget*, HistoryMedia *media, FullMsgId msgId) :
image = doc->thumb;
} break;
case MediaTypeGrouped: {
if (const auto photo = media->getPhoto()) {
dimensions = QSize(photo->full->width(), photo->full->height());
image = photo->full;
} else if (const auto doc = media->getDocument()) {
dimensions = doc->dimensions;
image = doc->thumb;
_animated = true;
}
} break;
case MediaTypeFile:
case MediaTypeMusicFile:
case MediaTypeVoiceFile: {

View File

@@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#define BETA_VERSION_MACRO (0ULL)
constexpr int AppVersion = 1002001;
constexpr str_const AppVersionStr = "1.2.1";
constexpr bool AppAlphaVersion = false;
constexpr int AppVersion = 1002002;
constexpr str_const AppVersionStr = "1.2.2";
constexpr bool AppAlphaVersion = true;
constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO;

View File

@@ -224,7 +224,10 @@ QString documentSaveFilename(const DocumentData *data, bool forceSavingAs = fals
return saveFileName(caption, filter, prefix, name, forceSavingAs, dir);
}
void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context, ActionOnLoad action) {
void DocumentOpenClickHandler::doOpen(
not_null<DocumentData*> data,
HistoryItem *context,
ActionOnLoad action) {
if (!data->date) return;
auto msgId = context ? context->fullId() : FullMsgId();
@@ -329,9 +332,13 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
}
void DocumentOpenClickHandler::onClickImpl() const {
const auto item = App::hoveredLinkItem()
const auto item = context()
? App::histItemById(context())
: App::hoveredLinkItem()
? App::hoveredLinkItem()
: (App::contextItem() ? App::contextItem() : nullptr);
: App::contextItem()
? App::contextItem()
: nullptr;
const auto action = document()->isVoiceMessage()
? ActionOnLoadNone
: ActionOnLoadOpen;
@@ -339,13 +346,19 @@ void DocumentOpenClickHandler::onClickImpl() const {
}
void GifOpenClickHandler::onClickImpl() const {
const auto item = App::hoveredLinkItem()
const auto item = context()
? App::histItemById(context())
: App::hoveredLinkItem()
? App::hoveredLinkItem()
: (App::contextItem() ? App::contextItem() : nullptr);
: App::contextItem()
? App::contextItem()
: nullptr;
doOpen(document(), item, ActionOnLoadPlayInline);
}
void DocumentSaveClickHandler::doSave(DocumentData *data, bool forceSavingAs) {
void DocumentSaveClickHandler::doSave(
not_null<DocumentData*> data,
bool forceSavingAs) {
if (!data->date) return;
auto filepath = data->filepath(DocumentData::FilePathResolveSaveFromDataSilent, forceSavingAs);

View File

@@ -311,15 +311,22 @@ QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform);
class DocumentClickHandler : public LeftButtonClickHandler {
public:
DocumentClickHandler(DocumentData *document)
: _document(document) {
DocumentClickHandler(
not_null<DocumentData*> document,
FullMsgId context = FullMsgId())
: _document(document)
, _context(context) {
}
DocumentData *document() const {
not_null<DocumentData*> document() const {
return _document;
}
FullMsgId context() const {
return _context;
}
private:
DocumentData *_document;
not_null<DocumentData*> _document;
FullMsgId _context;
};
@@ -327,7 +334,7 @@ class DocumentSaveClickHandler : public DocumentClickHandler {
public:
using DocumentClickHandler::DocumentClickHandler;
static void doSave(
DocumentData *document,
not_null<DocumentData*> document,
bool forceSavingAs = false);
protected:
@@ -339,7 +346,7 @@ class DocumentOpenClickHandler : public DocumentClickHandler {
public:
using DocumentClickHandler::DocumentClickHandler;
static void doOpen(
DocumentData *document,
not_null<DocumentData*> document,
HistoryItem *context,
ActionOnLoad action = ActionOnLoadOpen);

View File

@@ -121,7 +121,7 @@ ImagePtr PhotoData::makeReplyPreview() {
}
void PhotoOpenClickHandler::onClickImpl() const {
Messenger::Instance().showPhoto(this, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem());
Messenger::Instance().showPhoto(this);
}
void PhotoSaveClickHandler::onClickImpl() const {
@@ -136,13 +136,9 @@ void PhotoCancelClickHandler::onClickImpl() const {
if (!data->date) return;
if (data->uploading()) {
if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) {
if (auto media = item->getMedia()) {
if (media->type() == MediaTypePhoto && static_cast<HistoryPhoto*>(media)->photo() == data) {
App::contextItem(item);
App::main()->cancelUploadLayer();
}
}
if (const auto item = App::histItemById(context())) {
App::contextItem(item);
App::main()->cancelUploadLayer();
}
} else {
data->cancel();

View File

@@ -74,8 +74,11 @@ class PhotoClickHandler : public LeftButtonClickHandler {
public:
PhotoClickHandler(
not_null<PhotoData*> photo,
FullMsgId context = FullMsgId(),
PeerData *peer = nullptr)
: _photo(photo), _peer(peer) {
: _photo(photo)
, _context(context)
, _peer(peer) {
}
not_null<PhotoData*> photo() const {
return _photo;
@@ -83,10 +86,14 @@ public:
PeerData *peer() const {
return _peer;
}
FullMsgId context() const {
return _context;
}
private:
not_null<PhotoData*> _photo;
PeerData *_peer;
FullMsgId _context;
PeerData *_peer = nullptr;
};

View File

@@ -290,9 +290,7 @@ base::optional<bool> SharedMediaWithLastSlice::IsLastIsolated(
| [](FullMsgId msgId) { return App::histItemById(msgId); }
| [](HistoryItem *item) { return item ? item->getMedia() : nullptr; }
| [](HistoryMedia *media) {
return (media && media->type() == MediaTypePhoto)
? static_cast<HistoryPhoto*>(media)->photo()
: nullptr;
return media ? media->getPhoto() : nullptr;
}
| [](PhotoData *photo) { return photo ? photo->id : 0; }
| [&](PhotoId photoId) { return *lastPeerPhotoId != photoId; };

View File

@@ -796,12 +796,7 @@ void Histories::checkSelfDestructItems() {
}
HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) {
auto msgId = MsgId(0);
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;
}
const auto msgId = idFromMessage(msg);
if (!msgId) return nullptr;
auto result = App::histItemById(channelId(), msgId);
@@ -810,7 +805,10 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
result->detach();
}
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) {
App::checkSavedGif(result);
}
@@ -1094,33 +1092,37 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
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);
}
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);
}
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);
}
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);
}
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 };
return addNewItem(HistoryService::create(this, msgId, date, message, flags), newMsg);
}
HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type) {
if (isChannel()) return asChannelHistory()->addNewChannelMessage(msg, type);
if (isChannel()) {
return asChannelHistory()->addNewChannelMessage(msg, type);
}
if (type == NewMessageExisting) return addToHistory(msg);
if (type == NewMessageExisting) {
return addToHistory(msg);
}
if (!loadedAtBottom() || peer->migrateTo()) {
HistoryItem *item = addToHistory(msg);
const auto item = addToHistory(msg);
if (item) {
setLastMessage(item);
if (type == NewMessageUnread) {
@@ -1134,32 +1136,43 @@ HistoryItem *History::addNewMessage(const MTPMessage &msg, NewMessageType type)
}
HistoryItem *History::addNewToLastBlock(const MTPMessage &msg, NewMessageType type) {
auto applyServiceAction = (type == NewMessageUnread);
auto detachExistingItem = (type != NewMessageLast);
auto item = createItem(msg, applyServiceAction, detachExistingItem);
const auto applyServiceAction = (type == NewMessageUnread);
const auto detachExistingItem = (type != NewMessageLast);
const auto item = createItem(msg, applyServiceAction, detachExistingItem);
if (!item || !item->detached()) {
return item;
}
return addNewItem(item, (type == NewMessageUnread));
const auto result = addNewItem(item, (type == NewMessageUnread));
if (type == NewMessageLast) {
// When we add just one last item, like we do while loading dialogs,
// we want to remove a single added grouped media, otherwise it will
// jump once we open the message history (first we show only that
// media, then we load the rest of the group and show the group).
//
// That way when we open the message history we show nothing until a
// whole history part is loaded, it certainly will contain the group.
removeOrphanMediaGroupPart();
}
return result;
}
HistoryItem *History::addToHistory(const MTPMessage &msg) {
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);
}
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);
}
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);
}
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);
}
@@ -1251,10 +1264,16 @@ void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) {
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());
addItemToBlock(adding);
const auto [groupFrom, groupTill] = recountGroupingFromTill(adding);
if (groupFrom != groupTill || groupFrom->groupId()) {
recountGrouping(groupFrom, groupTill);
}
setLastMessage(adding);
if (newMsg) {
newItemAdded(adding);
@@ -1434,8 +1453,7 @@ HistoryBlock *History::prepareBlockForAddingItem() {
return result;
};
void History::addItemToBlock(HistoryItem *item) {
Expects(item != nullptr);
void History::addItemToBlock(not_null<HistoryItem*> item) {
Expects(item->detached());
auto block = prepareBlockForAddingItem();
@@ -1528,6 +1546,9 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
return;
}
auto firstAdded = (HistoryItem*)nullptr;
auto lastAdded = (HistoryItem*)nullptr;
auto logged = QStringList();
logged.push_back(QString::number(minMsgId()));
logged.push_back(QString::number(maxMsgId()));
@@ -1539,9 +1560,12 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
--i;
auto adding = createItem(*i, false, true);
const auto adding = createItem(*i, false, true);
if (!adding) continue;
if (!firstAdded) firstAdded = adding;
lastAdded = adding;
if (minAdded < 0 || minAdded > adding->id) {
minAdded = adding->id;
}
@@ -1638,6 +1662,11 @@ void History::addOlderSlice(const QVector<MTPMessage> &slice) {
CrashReports::ClearAnnotation("old_minmaxwas_minmaxadd");
if (lastAdded) {
const auto [from, till] = recountGroupingFromTill(lastAdded);
recountGrouping(firstAdded, till);
}
if (isChannel()) {
asChannelHistory()->checkJoinedMessage();
asChannelHistory()->checkMaxReadMessageDate();
@@ -1655,6 +1684,9 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
}
}
auto firstAdded = (HistoryItem*)nullptr;
auto lastAdded = (HistoryItem*)nullptr;
Assert(!isBuildingFrontBlock());
if (!slice.isEmpty()) {
auto logged = QStringList();
@@ -1665,12 +1697,14 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
auto maxAdded = -1;
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
auto atLeastOneAdded = false;
for (auto i = slice.cend(), e = slice.cbegin(); i != e;) {
--i;
auto adding = createItem(*i, false, true);
const auto adding = createItem(*i, false, true);
if (!adding) continue;
if (!firstAdded) firstAdded = adding;
lastAdded = adding;
if (minAdded < 0 || minAdded > adding->id) {
minAdded = adding->id;
}
@@ -1679,7 +1713,6 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
}
addItemToBlock(adding);
atLeastOneAdded = true;
if (auto types = adding->sharedMediaTypes()) {
for (auto i = 0; i != Storage::kSharedMediaTypeCount; ++i) {
auto type = static_cast<Storage::SharedMediaType>(i);
@@ -1696,7 +1729,7 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
logged.push_back(QString::number(maxAdded));
CrashReports::SetAnnotation("new_minmaxwas_minmaxadd", logged.join(";"));
if (!atLeastOneAdded) {
if (!firstAdded) {
newLoaded = true;
setLastMessage(lastAvailableMessage());
}
@@ -1709,6 +1742,11 @@ void History::addNewerSlice(const QVector<MTPMessage> &slice) {
checkAddAllToUnreadMentions();
}
if (firstAdded) {
const auto [from, till] = recountGroupingFromTill(firstAdded);
recountGrouping(from, lastAdded);
}
if (isChannel()) asChannelHistory()->checkJoinedMessage();
checkLastMsg();
}
@@ -2007,7 +2045,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 < blocks.size());
Expects(itemIndex >= 0);
@@ -2029,9 +2067,135 @@ HistoryItem *History::addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex,
newItem->nextItemChanged();
}
const auto [groupFrom, groupTill] = recountGroupingFromTill(newItem);
if (groupFrom != groupTill || groupFrom->groupId()) {
recountGrouping(groupFrom, groupTill);
}
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;
}
void History::recountGroupingAround(not_null<HistoryItem*> item) {
Expects(item->history() == this);
if (!item->detached() && item->groupId()) {
const auto [groupFrom, groupTill] = recountGroupingFromTill(item);
recountGrouping(groupFrom, groupTill);
}
}
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) {
Assert(!isBuildingFrontBlock());
Assert(expectedItemsCount > 0);
@@ -2278,6 +2442,25 @@ bool History::isDisplayedEmpty() const {
return isEmpty() || ((blocks.size() == 1) && blocks.front()->items.size() == 1 && blocks.front()->items.front()->isEmpty());
}
bool History::hasOrphanMediaGroupPart() const {
if (loadedAtTop() || !loadedAtBottom()) {
return false;
} else if (blocks.size() != 1) {
return false;
} else if (blocks.front()->items.size() != 1) {
return false;
}
return blocks.front()->items.front()->groupId() != MessageGroupId();
}
bool History::removeOrphanMediaGroupPart() {
if (hasOrphanMediaGroupPart()) {
clear(true);
return true;
}
return false;
}
void History::clear(bool leaveItems) {
if (unreadBar) {
unreadBar = nullptr;
@@ -2471,7 +2654,7 @@ void History::setPinnedIndex(int pinnedIndex) {
void History::changeMsgId(MsgId oldId, MsgId newId) {
}
void History::removeBlock(HistoryBlock *block) {
void History::removeBlock(not_null<HistoryBlock*> block) {
Expects(block->items.empty());
if (_buildingFrontBlock && block == _buildingFrontBlock->block) {
@@ -2522,9 +2705,21 @@ void HistoryBlock::clear(bool leaveItems) {
}
}
void HistoryBlock::removeItem(HistoryItem *item) {
void HistoryBlock::removeItem(not_null<HistoryItem*> item) {
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 itemIndex = item->indexInBlock();
if (_history->showFrom == item) {
@@ -2558,4 +2753,8 @@ void HistoryBlock::removeItem(HistoryItem *item) {
if (items.empty()) {
delete this;
}
if (needGroupRecount) {
groupHistory->recountGrouping(groupFrom, groupTill);
}
}

View File

@@ -136,6 +136,7 @@ enum HistoryMediaType {
MediaTypeVoiceFile,
MediaTypeGame,
MediaTypeInvoice,
MediaTypeGrouped,
MediaTypeCount
};
@@ -209,6 +210,8 @@ public:
return blocks.empty();
}
bool isDisplayedEmpty() const;
bool hasOrphanMediaGroupPart() const;
bool removeOrphanMediaGroupPart();
void clear(bool leaveItems = false);
void clearUpTill(MsgId availableMinId);
@@ -217,13 +220,13 @@ public:
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 *addToHistory(const MTPMessage &msg);
HistoryItem *addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item);
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 *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);
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*> addNewService(MsgId msgId, QDateTime date, const QString &text, MTPDmessage::Flags flags = 0, bool newMsg = true);
not_null<HistoryItem*> addNewForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *item);
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);
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.
HistoryItem *createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem);
@@ -419,6 +422,7 @@ public:
}
HistoryItemsList validateForwardDraft();
void setForwardDraft(MessageIdsList &&items);
void recountGroupingAround(not_null<HistoryItem*> item);
// some fields below are a property of a currently displayed instance of this
// conversation history not a property of the conversation history itself
@@ -475,17 +479,17 @@ protected:
// this method just removes a block from the blocks list
// when the last item from this block was detached and
// calls the required previousItemChanged()
void removeBlock(HistoryBlock *block);
void removeBlock(not_null<HistoryBlock*> block);
void clearBlocks(bool leaveItems);
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);
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*> createItemForwarded(MsgId id, MTPDmessage::Flags flags, QDateTime date, UserId from, const QString &postAuthor, HistoryMessage *msg);
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);
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);
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);
HistoryItem *addNewInTheMiddle(HistoryItem *newItem, int32 blockIndex, int32 itemIndex);
not_null<HistoryItem*> addNewItem(not_null<HistoryItem*> adding, bool newMsg);
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
// depending on if we are in isBuildingFronBlock() state.
@@ -493,7 +497,7 @@ protected:
// Adds the item to the back or front block, depending on
// 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.
// Only when we scroll up and add a new slice to the
@@ -517,6 +521,18 @@ private:
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 {
f_has_pending_resized_items = (1 << 0),
f_pending_resize = (1 << 1),
@@ -624,7 +640,7 @@ public:
~HistoryBlock() {
clear();
}
void removeItem(HistoryItem *item);
void removeItem(not_null<HistoryItem*> item);
int resizeGetHeight(int newWidth, bool resizeAllItems);
int y() const {

View File

@@ -461,3 +461,9 @@ historyFastShareIcon: icon {{ "fast_share", msgServiceFg, point(4px, 3px)}};
historyGoToOriginalIcon: icon {{ "title_back-flip_horizontal", msgServiceFg, point(8px, 7px) }};
historySavedFont: font(semibold 14px);
historyGroupWidthMax: maxMediaSize;
historyGroupWidthMin: minPhotoSize;
historyGroupSkip: 4px;
historyGroupRadialSize: 44px;
historyGroupRadialLine: 3px;

View File

@@ -28,6 +28,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "history/history_media_types.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_controller.h"
#include "window/window_peer_menu.h"
#include "boxes/confirm_box.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/stickers.h"
#include "history/history_widget.h"
@@ -85,6 +87,43 @@ int BinarySearchBlocksOrItems(const T &list, int edge) {
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
class HistoryInner::BotAbout : public ClickHandlerHost {
public:
BotAbout(not_null<HistoryInner*> parent, not_null<BotInfo*> info);
// ClickHandlerHost interface
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
not_null<BotInfo*> info;
int width = 0;
int height = 0;
QRect rect;
private:
not_null<HistoryInner*> _parent;
};
HistoryInner::BotAbout::BotAbout(
not_null<HistoryInner*> parent,
not_null<BotInfo*> info)
: info(info)
, _parent(parent) {
}
void HistoryInner::BotAbout::clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active) {
_parent->update(rect);
}
void HistoryInner::BotAbout::clickHandlerPressedChanged(
const ClickHandlerPtr &p,
bool pressed) {
_parent->update(rect);
}
HistoryInner::HistoryInner(
not_null<HistoryWidget*> historyWidget,
not_null<Window::Controller*> controller,
@@ -363,6 +402,61 @@ void HistoryInner::enumerateDates(Method method) {
enumerateItems<EnumItemsDirection::BottomToTop>(dateCallback);
}
TextSelection HistoryInner::computeRenderSelection(
not_null<const SelectedItems*> selected,
not_null<HistoryItem*> item) const {
const auto itemSelection = [&](not_null<HistoryItem*> item) {
auto i = selected->find(item);
if (i != selected->end()) {
return i->second;
}
return TextSelection();
};
const auto group = item->Get<HistoryMessageGroup>();
if (group) {
if (group->leader != item) {
return TextSelection();
}
auto result = TextSelection();
auto allFullSelected = true;
const auto count = int(group->others.size());
for (auto i = 0; i != count; ++i) {
if (itemSelection(group->others[i]) == FullSelection) {
result = AddGroupItemSelection(result, i);
} else {
allFullSelected = false;
}
}
const auto leaderSelection = itemSelection(item);
if (leaderSelection == FullSelection) {
return allFullSelected
? FullSelection
: AddGroupItemSelection(result, count);
} else if (leaderSelection != TextSelection()) {
return leaderSelection;
}
return result;
}
return itemSelection(item);
}
TextSelection HistoryInner::itemRenderSelection(
not_null<HistoryItem*> item,
int selfromy,
int seltoy) const {
Expects(!item->detached());
const auto y = item->block()->y() + item->y();
if (y >= selfromy && y < seltoy) {
if (_dragSelecting && !item->serviceMsg() && item->id > 0) {
return FullSelection;
}
} else if (!_selected.empty()) {
return computeRenderSelection(&_selected, item);
}
return TextSelection();
}
void HistoryInner::paintEvent(QPaintEvent *e) {
if (Ui::skipPaintEvent(this, e)) {
return;
@@ -399,9 +493,6 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
adjustCurrent(clip.top());
auto selEnd = _selected.cend();
auto hasSel = !_selected.empty();
auto drawToY = clip.y() + clip.height();
auto selfromy = itemTop(_dragSelFrom);
@@ -425,18 +516,11 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
p.save();
p.translate(0, y);
if (clip.y() < y + item->height()) while (y < drawToY) {
TextSelection sel;
if (y >= selfromy && y < seltoy) {
if (_dragSelecting && !item->serviceMsg() && item->id > 0) {
sel = FullSelection;
}
} else if (hasSel) {
auto i = _selected.find(item);
if (i != selEnd) {
sel = i->second;
}
}
item->draw(p, clip.translated(0, -y), sel, ms);
const auto selection = itemRenderSelection(
item,
selfromy - mtop,
seltoy - mtop);
item->draw(p, clip.translated(0, -y), selection, ms);
if (item->hasViews()) {
App::main()->scheduleViewIncrement(item);
@@ -469,25 +553,18 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
auto iItem = (_curHistory == _history ? _curItem : 0);
auto item = block->items[iItem];
auto historyRect = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height()));
auto hclip = clip.intersected(QRect(0, hdrawtop, width(), clip.top() + clip.height()));
auto y = htop + block->y() + item->y();
p.save();
p.translate(0, y);
while (y < drawToY) {
auto h = item->height();
if (historyRect.y() < y + h && hdrawtop < y + h) {
TextSelection sel;
if (y >= selfromy && y < seltoy) {
if (_dragSelecting && !item->serviceMsg() && item->id > 0) {
sel = FullSelection;
}
} else if (hasSel) {
auto i = _selected.find(item);
if (i != selEnd) {
sel = i->second;
}
}
item->draw(p, historyRect.translated(0, -y), sel, ms);
if (hclip.y() < y + h && hdrawtop < y + h) {
const auto selection = itemRenderSelection(
item,
selfromy - htop,
seltoy - htop);
item->draw(p, hclip.translated(0, -y), selection, ms);
if (item->hasViews()) {
App::main()->scheduleViewIncrement(item);
@@ -830,7 +907,9 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but
_mouseAction = MouseAction::PrepareDrag;
} else if (!_selected.empty()) {
if (_selected.cbegin()->second == FullSelection) {
if (_selected.find(_mouseActionItem) != _selected.cend() && App::hoveredItem()) {
if (_dragStateItem
&& _selected.find(_dragStateItem) != _selected.cend()
&& App::hoveredItem()) {
_mouseAction = MouseAction::PrepareDrag; // start items drag
} else if (!_pressWasInactive) {
_mouseAction = MouseAction::PrepareSelect; // start items select
@@ -915,6 +994,7 @@ void HistoryInner::mouseActionStart(const QPoint &screenPos, Qt::MouseButton but
void HistoryInner::mouseActionCancel() {
_mouseActionItem = nullptr;
_dragStateItem = nullptr;
_mouseAction = MouseAction::None;
_dragStartPosition = QPoint(0, 0);
_dragSelFrom = _dragSelTo = nullptr;
@@ -928,7 +1008,8 @@ void HistoryInner::performDrag() {
bool uponSelected = false;
if (_mouseActionItem) {
if (!_selected.empty() && _selected.cbegin()->second == FullSelection) {
uponSelected = (_selected.find(_mouseActionItem) != _selected.cend());
uponSelected = _dragStateItem
&& (_selected.find(_dragStateItem) != _selected.cend());
} else {
HistoryStateRequest request;
request.flags |= Text::StateRequest::Flag::LookupSymbol;
@@ -986,7 +1067,7 @@ void HistoryInner::performDrag() {
forwardMimeType = qsl("application/x-td-forward-pressed");
}
}
if (auto pressedLnkItem = App::pressedLinkItem()) {
if (const auto pressedLnkItem = _mouseActionItem) {
if ((pressedMedia = pressedLnkItem->getMedia())) {
if (forwardMimeType.isEmpty() && pressedMedia->dragItemByHandler(pressedHandler)) {
forwardMimeType = qsl("application/x-td-forward-pressed-link");
@@ -1029,6 +1110,9 @@ void HistoryInner::itemRemoved(not_null<const HistoryItem*> item) {
if (_mouseActionItem == item) {
mouseActionCancel();
}
if (_dragStateItem == item) {
_dragStateItem = nullptr;
}
if (_dragSelFrom == item || _dragSelTo == item) {
_dragSelFrom = 0;
@@ -1041,15 +1125,14 @@ void HistoryInner::itemRemoved(not_null<const HistoryItem*> item) {
void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button) {
mouseActionUpdate(screenPos);
auto pressedLinkItem = App::pressedLinkItem();
auto activated = ClickHandler::unpressed();
if (_mouseAction == MouseAction::Dragging) {
activated.clear();
} else if (auto pressed = pressedLinkItem) {
} else if (_mouseActionItem) {
// if we are in selecting items mode perhaps we want to
// toggle selection instead of activating the pressed link
if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && !_selected.empty() && _selected.cbegin()->second == FullSelection && button != Qt::RightButton) {
if (auto media = pressed->getMedia()) {
if (auto media = _mouseActionItem->getMedia()) {
if (media->toggleSelectionByHandlerClick(activated)) {
activated.clear();
}
@@ -1068,29 +1151,30 @@ void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton bu
App::activateClickHandler(activated, button);
return;
}
if (_mouseAction == MouseAction::PrepareSelect && !_pressWasInactive && !_selected.empty() && _selected.cbegin()->second == FullSelection) {
auto i = _selected.find(_mouseActionItem);
if (i == _selected.cend()) {
if (!_mouseActionItem->serviceMsg()
&& IsServerMsgId(_mouseActionItem->id)
&& _selected.size() < MaxSelectedItems) {
if (!_selected.empty() && _selected.cbegin()->second != FullSelection) {
_selected.clear();
}
_selected.emplace(_mouseActionItem, FullSelection);
}
} else {
_selected.erase(i);
}
if ((_mouseAction == MouseAction::PrepareSelect)
&& !_pressWasInactive
&& !_selected.empty()
&& (_selected.cbegin()->second == FullSelection)) {
changeSelectionAsGroup(
&_selected,
_mouseActionItem,
SelectAction::Invert);
repaintItem(_mouseActionItem);
} else if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && button != Qt::RightButton) {
auto i = _selected.find(_mouseActionItem);
} else if ((_mouseAction == MouseAction::PrepareDrag)
&& !_pressWasInactive
&& _dragStateItem
&& (button != Qt::RightButton)) {
auto i = _selected.find(_dragStateItem);
if (i != _selected.cend() && i->second == FullSelection) {
_selected.erase(i);
repaintItem(_mouseActionItem);
} else if (i == _selected.cend() && !_mouseActionItem->serviceMsg() && _mouseActionItem->id > 0 && !_selected.empty() && _selected.cbegin()->second == FullSelection) {
} else if ((i == _selected.cend())
&& !_dragStateItem->serviceMsg()
&& (_dragStateItem->id > 0)
&& !_selected.empty()
&& _selected.cbegin()->second == FullSelection) {
if (_selected.size() < MaxSelectedItems) {
_selected.emplace(_mouseActionItem, FullSelection);
_selected.emplace(_dragStateItem, FullSelection);
repaintItem(_mouseActionItem);
}
} else {
@@ -1117,7 +1201,8 @@ void HistoryInner::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton bu
#if defined Q_OS_LINUX32 || defined Q_OS_LINUX64
if (!_selected.empty() && _selected.cbegin()->second != FullSelection) {
setToClipboard(_selected.cbegin()->first->selectedText(_selected.cbegin()->second), QClipboard::Selection);
const auto [item, selection] = *_selected.cbegin();
setToClipboard(item->selectedText(selection), QClipboard::Selection);
}
#endif // Q_OS_LINUX32 || Q_OS_LINUX64
}
@@ -1180,7 +1265,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
isUponSelected = -1;
if (_selected.cbegin()->second == FullSelection) {
hasSelected = 2;
if (App::hoveredItem() && _selected.find(App::hoveredItem()) != _selected.cend()) {
if (_dragStateItem && _selected.find(_dragStateItem) != _selected.cend()) {
isUponSelected = 2;
} else {
isUponSelected = -2;
@@ -1206,13 +1291,13 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_menu = new Ui::PopupMenu(nullptr);
_contextMenuLink = ClickHandler::getActive();
HistoryItem *item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem();
PhotoClickHandler *lnkPhoto = dynamic_cast<PhotoClickHandler*>(_contextMenuLink.data());
DocumentClickHandler *lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.data());
auto lnkPhoto = dynamic_cast<PhotoClickHandler*>(_contextMenuLink.data());
auto lnkDocument = dynamic_cast<DocumentClickHandler*>(_contextMenuLink.data());
auto lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideoFile() : false;
auto lnkIsVoice = lnkDocument ? lnkDocument->document()->isVoiceMessage() : false;
auto lnkIsAudio = lnkDocument ? lnkDocument->document()->isAudioFile() : false;
if (lnkPhoto || lnkDocument) {
const auto item = _dragStateItem;
if (isUponSelected > 0) {
_menu->addAction(lang((isUponSelected > 1) ? lng_context_copy_selected_items : lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
@@ -1227,6 +1312,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
auto isPinned = item->isPinned();
_menu->addAction(lang(isPinned ? lng_context_unpin_msg : lng_context_pin_msg), _widget, isPinned ? SLOT(onUnpinMessage()) : SLOT(onPinMessage()));
}
App::contextItem(item);
}
if (lnkPhoto) {
_menu->addAction(lang(lng_context_save_image), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, photo = lnkPhoto->photo()] {
@@ -1267,23 +1353,46 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}));
}
_menu->addAction(lang(lng_context_clear_selection), _widget, SLOT(onClearSelected()));
} else if (App::hoveredLinkItem()) {
} else if (item) {
if (isUponSelected != -2) {
if (App::hoveredLinkItem()->canForward()) {
_menu->addAction(lang(lng_context_forward_msg), _widget, SLOT(forwardMessage()))->setEnabled(true);
if (item->canForward()) {
_menu->addAction(lang(lng_context_forward_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
forwardItem(item);
}
}))->setEnabled(true);
}
if (App::hoveredLinkItem()->canDelete()) {
if (item->canDelete()) {
_menu->addAction(lang(lng_context_delete_msg), base::lambda_guarded(this, [this] {
_widget->confirmDeleteContextItem();
if (const auto item = App::contextItem()) {
deleteItem(item);
}
}));
}
}
if (App::hoveredLinkItem()->id > 0 && !App::hoveredLinkItem()->serviceMsg()) {
_menu->addAction(lang(lng_context_select_msg), _widget, SLOT(selectMessage()))->setEnabled(true);
if (item && item->id > 0 && !item->serviceMsg()) {
_menu->addAction(lang(lng_context_select_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
if (!item->detached()) {
changeSelection(&_selected, item, SelectAction::Select);
repaintItem(item);
_widget->updateTopBarSelection();
}
}
}))->setEnabled(true);
}
App::contextItem(App::hoveredLinkItem());
App::contextItem(item);
}
} else { // maybe cursor on some text history item?
auto item = App::hoveredItem()
? App::hoveredItem()
: App::hoveredLinkItem();
const auto group = item->getFullGroup();
if (group) {
item = group->others.empty()
? group->leader
: group->others.front().get();
}
bool canDelete = item && item->canDelete() && (item->id > 0 || !item->serviceMsg());
bool canForward = item && item->canForward();
@@ -1381,21 +1490,43 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
} else if (item && ((isUponSelected != -2 && (canForward || canDelete)) || item->id > 0)) {
if (isUponSelected != -2) {
if (canForward) {
_menu->addAction(lang(lng_context_forward_msg), _widget, SLOT(forwardMessage()))->setEnabled(true);
_menu->addAction(lang(lng_context_forward_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
forwardAsGroup(item);
}
}))->setEnabled(true);
}
if (canDelete) {
_menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), base::lambda_guarded(this, [this] {
_widget->confirmDeleteContextItem();
if (const auto item = App::contextItem()) {
deleteAsGroup(item);
}
}));
}
}
if (item->id > 0 && !item->serviceMsg()) {
_menu->addAction(lang(lng_context_select_msg), _widget, SLOT(selectMessage()))->setEnabled(true);
_menu->addAction(lang(lng_context_select_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
if (!item->detached()) {
changeSelectionAsGroup(&_selected, item, SelectAction::Select);
repaintItem(item);
_widget->updateTopBarSelection();
}
}
}))->setEnabled(true);
}
} else {
if (App::mousedItem() && !App::mousedItem()->serviceMsg() && App::mousedItem()->id > 0) {
_menu->addAction(lang(lng_context_select_msg), _widget, SLOT(selectMessage()))->setEnabled(true);
_menu->addAction(lang(lng_context_select_msg), base::lambda_guarded(this, [this] {
if (const auto item = App::contextItem()) {
if (!item->detached()) {
changeSelectionAsGroup(&_selected, item, SelectAction::Select);
repaintItem(item);
_widget->updateTopBarSelection();
}
}
}))->setEnabled(true);
item = App::mousedItem();
}
}
@@ -1513,12 +1644,13 @@ void HistoryInner::saveContextGif() {
}
void HistoryInner::copyContextText() {
auto item = App::contextItem();
const auto item = App::contextItem();
if (!item || (item->getMedia() && item->getMedia()->type() == MediaTypeSticker)) {
return;
}
setToClipboard(item->selectedText(FullSelection));
const auto group = item->getFullGroup();
const auto leader = group ? group->leader : item;
setToClipboard(leader->selectedText(FullSelection));
}
void HistoryInner::setToClipboard(const TextWithEntities &forClipboard, QClipboard::Mode mode) {
@@ -1532,46 +1664,79 @@ void HistoryInner::resizeEvent(QResizeEvent *e) {
}
TextWithEntities HistoryInner::getSelectedText() const {
SelectedItems sel = _selected;
auto selected = _selected;
if (_mouseAction == MouseAction::Selecting && _dragSelFrom && _dragSelTo) {
applyDragSelection(&sel);
applyDragSelection(&selected);
}
if (sel.empty()) {
if (selected.empty()) {
return TextWithEntities();
}
if (sel.cbegin()->second != FullSelection) {
return sel.cbegin()->first->selectedText(sel.cbegin()->second);
if (selected.cbegin()->second != FullSelection) {
const auto [item, selection] = *selected.cbegin();
return item->selectedText(selection);
}
int fullSize = 0;
QString timeFormat(qsl(", [dd.MM.yy hh:mm]\n"));
QMap<int, TextWithEntities> texts;
for (auto &selected : sel) {
auto item = selected.first;
if (item->detached()) continue;
const auto timeFormat = qsl(", [dd.MM.yy hh:mm]\n");
auto groupLeadersAdded = base::flat_set<not_null<HistoryItem*>>();
auto fullSize = 0;
auto texts = base::flat_map<std::pair<int, MsgId>, TextWithEntities>();
const auto addItem = [&](
not_null<HistoryItem*> item,
TextSelection selection) {
auto time = item->date.toString(timeFormat);
TextWithEntities part, unwrapped = item->selectedText(FullSelection);
int size = item->author()->name.size() + time.size() + unwrapped.text.size();
auto part = TextWithEntities();
auto unwrapped = item->selectedText(selection);
auto size = item->author()->name.size()
+ time.size()
+ unwrapped.text.size();
part.text.reserve(size);
int y = itemTop(item);
auto y = itemTop(item);
if (y >= 0) {
part.text.append(item->author()->name).append(time);
TextUtilities::Append(part, std::move(unwrapped));
texts.insert(y, part);
texts.emplace(std::make_pair(y, item->id), part);
fullSize += size;
}
};
for (const auto [item, selection] : selected) {
if (item->detached()) {
continue;
}
if (const auto group = item->Get<HistoryMessageGroup>()) {
if (groupLeadersAdded.contains(group->leader)) {
continue;
}
const auto leaderSelection = computeRenderSelection(
&selected,
group->leader);
if (leaderSelection == FullSelection) {
groupLeadersAdded.emplace(group->leader);
addItem(group->leader, FullSelection);
} else if (item == group->leader) {
const auto leaderFullSelection = AddGroupItemSelection(
TextSelection(),
int(group->others.size()));
addItem(item, leaderFullSelection);
} else {
addItem(item, FullSelection);
}
} else {
addItem(item, FullSelection);
}
}
TextWithEntities result;
auto result = TextWithEntities();
auto sep = qsl("\n\n");
result.text.reserve(fullSize + (texts.size() - 1) * sep.size());
for (auto i = texts.begin(), e = texts.end(); i != e; ++i) {
TextUtilities::Append(result, std::move(i.value()));
if (i + 1 != e) {
for (auto i = texts.begin(), e = texts.end(); i != e;) {
TextUtilities::Append(result, std::move(i->second));
if (++i != e) {
result.text.append(sep);
}
}
@@ -2007,10 +2172,11 @@ MessageIdsList HistoryInner::getSelectedItems() const {
return result;
}
void HistoryInner::selectItem(HistoryItem *item) {
void HistoryInner::selectItem(not_null<HistoryItem*> item) {
if (!_selected.empty() && _selected.cbegin()->second != FullSelection) {
_selected.clear();
} else if (_selected.size() == MaxSelectedItems && _selected.find(item) == _selected.cend()) {
} else if (_selected.size() == MaxSelectedItems
&& _selected.find(item) == _selected.cend()) {
return;
}
_selected.emplace(item, FullSelection);
@@ -2059,10 +2225,16 @@ void HistoryInner::onUpdateSelected() {
HistoryTextState dragState;
ClickHandlerHost *lnkhost = nullptr;
bool selectingText = (item == _mouseActionItem && item == App::hoveredItem() && !_selected.empty() && _selected.cbegin()->second != FullSelection);
auto selectingText = (item == _mouseActionItem)
&& (item == App::hoveredItem())
&& !_selected.empty()
&& (_selected.cbegin()->second != FullSelection);
if (point.y() < _historyPaddingTop) {
if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
dragState = _botAbout->info->text.getState(point - _botAbout->rect.topLeft() - QPoint(st::msgPadding.left(), st::msgPadding.top() + st::botDescSkip + st::msgNameFont->height), _botAbout->width);
dragState = HistoryTextState(nullptr, _botAbout->info->text.getState(
point - _botAbout->rect.topLeft() - QPoint(st::msgPadding.left(), st::msgPadding.top() + st::botDescSkip + st::msgNameFont->height),
_botAbout->width));
_dragStateItem = App::histItemById(dragState.itemId);
lnkhost = _botAbout.get();
}
} else if (item) {
@@ -2077,7 +2249,7 @@ void HistoryInner::onUpdateSelected() {
auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top();
auto scrollDateOpacity = _scrollDateOpacity.current(_scrollDateShown ? 1. : 0.);
enumerateDates([this, &dragState, &lnkhost, &point, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](not_null<HistoryItem*> item, int itemtop, int dateTop) {
enumerateDates([&](not_null<HistoryItem*> item, int itemtop, int dateTop) {
// stop enumeration if the date is above our point
if (dateTop + dateHeight <= point.y()) {
return false;
@@ -2116,7 +2288,10 @@ void HistoryInner::onUpdateSelected() {
} else {
static_cast<DateClickHandler*>(_scrollDateLink.data())->setDate(item->date.date());
}
dragState.link = _scrollDateLink;
dragState = HistoryTextState(
nullptr,
_scrollDateLink);
_dragStateItem = App::histItemById(dragState.itemId);
lnkhost = item;
}
}
@@ -2132,11 +2307,12 @@ void HistoryInner::onUpdateSelected() {
selectingText = false;
}
dragState = item->getState(m, request);
_dragStateItem = App::histItemById(dragState.itemId);
lnkhost = item;
if (!dragState.link && m.x() >= st::historyPhotoLeft && m.x() < st::historyPhotoLeft + st::msgPhotoSize) {
if (auto msg = item->toHistoryMessage()) {
if (msg->hasFromPhoto()) {
enumerateUserpics([&dragState, &lnkhost, &point](not_null<HistoryMessage*> message, int userpicTop) -> bool {
enumerateUserpics([&](not_null<HistoryMessage*> message, int userpicTop) -> bool {
// stop enumeration if the userpic is below our point
if (userpicTop > point.y()) {
return false;
@@ -2144,7 +2320,10 @@ void HistoryInner::onUpdateSelected() {
// stop enumeration if we've found a userpic under the cursor
if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) {
dragState.link = message->displayFrom()->openLink();
dragState = HistoryTextState(
nullptr,
message->displayFrom()->openLink());
_dragStateItem = App::histItemById(dragState.itemId);
lnkhost = message;
return false;
}
@@ -2171,7 +2350,7 @@ void HistoryInner::onUpdateSelected() {
} else if (_mouseCursorState == HistoryInTextCursorState && (_selected.empty() || _selected.cbegin()->second != FullSelection)) {
cur = style::cur_text;
} else if (_mouseCursorState == HistoryInDateCursorState) {
// cur = style::cur_cross;
//cur = style::cur_cross;
}
} else if (item) {
if (_mouseAction == MouseAction::Selecting) {
@@ -2235,7 +2414,9 @@ void HistoryInner::onUpdateSelected() {
if (ClickHandler::getPressed()) {
cur = style::cur_pointer;
} else if (_mouseAction == MouseAction::Selecting && !_selected.empty() && _selected.cbegin()->second != FullSelection) {
} else if ((_mouseAction == MouseAction::Selecting)
&& !_selected.empty()
&& (_selected.cbegin()->second != FullSelection)) {
if (!_dragSelFrom || !_dragSelTo) {
cur = style::cur_text;
}
@@ -2243,7 +2424,7 @@ void HistoryInner::onUpdateSelected() {
}
// Voice message seek support.
if (auto pressedItem = App::pressedLinkItem()) {
if (const auto pressedItem = _dragStateItem) {
if (!pressedItem->detached()) {
if (pressedItem->history() == _history || pressedItem->history() == _migrated) {
auto adjustedPoint = mapPointToItem(point, pressedItem);
@@ -2282,14 +2463,6 @@ void HistoryInner::updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dr
update();
}
void HistoryInner::BotAbout::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
_parent->update(rect);
}
void HistoryInner::BotAbout::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
_parent->update(rect);
}
int HistoryInner::historyHeight() const {
int result = 0;
if (!_history || _history->isEmpty()) {
@@ -2337,13 +2510,16 @@ int HistoryInner::itemTop(const HistoryItem *item) const { // -1 if should not b
}
void HistoryInner::notifyIsBotChanged() {
BotInfo *newinfo = (_history && _history->peer->isUser()) ? _history->peer->asUser()->botInfo.get() : nullptr;
if ((!newinfo && !_botAbout) || (newinfo && _botAbout && _botAbout->info == newinfo)) {
const auto newinfo = (_history && _history->peer->isUser())
? _history->peer->asUser()->botInfo.get()
: nullptr;
if ((!newinfo && !_botAbout)
|| (newinfo && _botAbout && _botAbout->info == newinfo)) {
return;
}
if (newinfo) {
_botAbout.reset(new BotAbout(this, newinfo));
_botAbout = std::make_unique<BotAbout>(this, newinfo);
if (newinfo && !newinfo->inited) {
Auth().api().requestFullPeer(_peer);
}
@@ -2370,25 +2546,171 @@ void HistoryInner::applyDragSelection() {
applyDragSelection(&_selected);
}
void HistoryInner::addSelectionRange(SelectedItems *toItems, int32 fromblock, int32 fromitem, int32 toblock, int32 toitem, History *h) const {
bool HistoryInner::isSelected(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
const auto i = toItems->find(item);
return (i != toItems->cend()) && (i->second == FullSelection);
}
bool HistoryInner::isSelectedAsGroup(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
if (const auto group = item->getFullGroup()) {
if (!isSelected(toItems, group->leader)) {
return false;
}
for (const auto other : group->others) {
if (!isSelected(toItems, other)) {
return false;
}
}
return true;
}
return isSelected(toItems, item);
}
bool HistoryInner::goodForSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
int &totalCount) const {
if (item->id <= 0 || item->serviceMsg()) {
return false;
}
if (toItems->find(item) == toItems->end()) {
++totalCount;
}
return true;
}
void HistoryInner::addToSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
const auto i = toItems->find(item);
if (i == toItems->cend()) {
toItems->emplace(item, FullSelection);
} else if (i->second != FullSelection) {
i->second = FullSelection;
}
}
void HistoryInner::removeFromSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const {
const auto i = toItems->find(item);
if (i != toItems->cend()) {
toItems->erase(i);
}
}
void HistoryInner::changeSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
SelectAction action) const {
if (action == SelectAction::Invert) {
action = isSelected(toItems, item)
? SelectAction::Deselect
: SelectAction::Select;
}
auto total = int(toItems->size());
const auto add = (action == SelectAction::Select);
if (add
&& goodForSelection(toItems, item, total)
&& total <= MaxSelectedItems) {
addToSelection(toItems, item);
} else {
removeFromSelection(toItems, item);
}
}
void HistoryInner::changeSelectionAsGroup(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
SelectAction action) const {
const auto group = item->getFullGroup();
if (!group) {
return changeSelection(toItems, item, action);
}
if (action == SelectAction::Invert) {
action = isSelectedAsGroup(toItems, item)
? SelectAction::Deselect
: SelectAction::Select;
}
auto total = int(toItems->size());
const auto add = (action == SelectAction::Select);
const auto adding = [&] {
if (!add || !goodForSelection(toItems, group->leader, total)) {
return false;
}
for (const auto other : group->others) {
if (!goodForSelection(toItems, other, total)) {
return false;
}
}
return (total <= MaxSelectedItems);
}();
if (adding) {
addToSelection(toItems, group->leader);
for (const auto other : group->others) {
addToSelection(toItems, other);
}
} else {
removeFromSelection(toItems, group->leader);
for (const auto other : group->others) {
removeFromSelection(toItems, other);
}
}
}
void HistoryInner::forwardItem(not_null<HistoryItem*> item) {
Window::ShowForwardMessagesBox({ 1, item->fullId() });
}
void HistoryInner::forwardAsGroup(not_null<HistoryItem*> item) {
if (const auto group = item->getFullGroup()) {
auto items = Auth().data().itemsToIds(group->others);
items.push_back(group->leader->fullId());
Window::ShowForwardMessagesBox(std::move(items));
} else {
Window::ShowForwardMessagesBox({ 1, item->fullId() });
}
}
void HistoryInner::deleteItem(not_null<HistoryItem*> item) {
if (auto message = item->toHistoryMessage()) {
if (message->uploading()) {
App::main()->cancelUploadLayer();
return;
}
}
const auto suggestModerateActions = true;
Ui::show(Box<DeleteMessagesBox>(item, suggestModerateActions));
}
void HistoryInner::deleteAsGroup(not_null<HistoryItem*> item) {
const auto group = item->getFullGroup();
if (!group || group->others.empty()) {
return deleteItem(item);
}
auto items = Auth().data().itemsToIds(group->others);
items.push_back(group->leader->fullId());
Ui::show(Box<DeleteMessagesBox>(Auth().data().groupToIds(group)));
}
void HistoryInner::addSelectionRange(
not_null<SelectedItems*> toItems,
not_null<History*> history,
int fromblock,
int fromitem,
int toblock,
int toitem) const {
if (fromblock >= 0 && fromitem >= 0 && toblock >= 0 && toitem >= 0) {
for (; fromblock <= toblock; ++fromblock) {
auto block = h->blocks[fromblock];
for (int32 cnt = (fromblock < toblock) ? block->items.size() : (toitem + 1); fromitem < cnt; ++fromitem) {
auto block = history->blocks[fromblock];
for (int cnt = (fromblock < toblock) ? block->items.size() : (toitem + 1); fromitem < cnt; ++fromitem) {
auto item = block->items[fromitem];
auto i = toItems->find(item);
if (item->id > 0 && !item->serviceMsg()) {
if (i == toItems->cend()) {
if (toItems->size() >= MaxSelectedItems) break;
toItems->emplace(item, FullSelection);
} else if (i->second != FullSelection) {
i->second = FullSelection;
}
} else {
if (i != toItems->cend()) {
toItems->erase(i);
}
}
changeSelectionAsGroup(toItems, item, SelectAction::Select);
}
if (toItems->size() >= MaxSelectedItems) break;
fromitem = 0;
@@ -2396,27 +2718,33 @@ void HistoryInner::addSelectionRange(SelectedItems *toItems, int32 fromblock, in
}
}
void HistoryInner::applyDragSelection(SelectedItems *toItems) const {
int32 selfromy = itemTop(_dragSelFrom), seltoy = itemTop(_dragSelTo);
void HistoryInner::applyDragSelection(
not_null<SelectedItems*> toItems) const {
const auto selfromy = itemTop(_dragSelFrom);
const auto seltoy = [&] {
auto result = itemTop(_dragSelTo);
return (result < 0) ? result : (result + _dragSelTo->height());
}();
if (selfromy < 0 || seltoy < 0) {
return;
}
seltoy += _dragSelTo->height();
if (!toItems->empty() && toItems->cbegin()->second != FullSelection) {
toItems->clear();
}
if (_dragSelecting) {
int32 fromblock = _dragSelFrom->block()->indexInHistory(), fromitem = _dragSelFrom->indexInBlock();
int32 toblock = _dragSelTo->block()->indexInHistory(), toitem = _dragSelTo->indexInBlock();
auto fromblock = _dragSelFrom->block()->indexInHistory();
auto fromitem = _dragSelFrom->indexInBlock();
auto toblock = _dragSelTo->block()->indexInHistory();
auto toitem = _dragSelTo->indexInBlock();
if (_migrated) {
if (_dragSelFrom->history() == _migrated) {
if (_dragSelTo->history() == _migrated) {
addSelectionRange(toItems, fromblock, fromitem, toblock, toitem, _migrated);
addSelectionRange(toItems, _migrated, fromblock, fromitem, toblock, toitem);
toblock = -1;
toitem = -1;
} else {
addSelectionRange(toItems, fromblock, fromitem, _migrated->blocks.size() - 1, _migrated->blocks.back()->items.size() - 1, _migrated);
addSelectionRange(toItems, _migrated, fromblock, fromitem, _migrated->blocks.size() - 1, _migrated->blocks.back()->items.size() - 1);
}
fromblock = 0;
fromitem = 0;
@@ -2425,20 +2753,20 @@ void HistoryInner::applyDragSelection(SelectedItems *toItems) const {
toitem = -1;
}
}
addSelectionRange(toItems, fromblock, fromitem, toblock, toitem, _history);
addSelectionRange(toItems, _history, fromblock, fromitem, toblock, toitem);
} else {
for (auto i = toItems->begin(); i != toItems->cend();) {
auto toRemove = std::vector<not_null<HistoryItem*>>();
for (auto i = toItems->begin(); i != toItems->cend(); ++i) {
auto iy = itemTop(i->first);
if (iy < 0) {
if (iy < -1) i = toItems->erase(i);
continue;
}
if (iy >= selfromy && iy < seltoy) {
i = toItems->erase(i);
} else {
++i;
if (iy < -1) {
toRemove.push_back(i->first);
} else if (iy >= 0 && iy >= selfromy && iy < seltoy) {
toRemove.push_back(i->first);
}
}
for (const auto item : toRemove) {
changeSelectionAsGroup(toItems, item, SelectAction::Deselect);
}
}
}
@@ -2446,8 +2774,9 @@ QString HistoryInner::tooltipText() const {
if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) {
if (App::hoveredItem()) {
auto dateText = App::hoveredItem()->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat));
if (auto edited = App::hoveredItem()->Get<HistoryMessageEdited>()) {
dateText += '\n' + lng_edited_date(lt_date, edited->_editDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
auto editedDate = App::hoveredItem()->displayedEditDate();
if (!editedDate.isNull()) {
dateText += '\n' + lng_edited_date(lt_date, editedDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));
}
if (auto forwarded = App::hoveredItem()->Get<HistoryMessageForwarded>()) {
dateText += '\n' + lng_forwarded_date(lt_date, forwarded->_originalDate.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)));

View File

@@ -66,7 +66,7 @@ public:
HistoryTopBarWidget::SelectedState getSelectionState() const;
void clearSelectedItems(bool onlyTextSelection = false);
MessageIdsList getSelectedItems() const;
void selectItem(HistoryItem *item);
void selectItem(not_null<HistoryItem*> item);
void updateBotInfo(bool recount = true);
@@ -133,6 +133,9 @@ private slots:
void onScrollDateHideByTimer();
private:
class BotAbout;
using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>;
enum class MouseAction {
None,
PrepareDrag,
@@ -140,6 +143,11 @@ private:
PrepareSelect,
Selecting,
};
enum class SelectAction {
Select,
Deselect,
Invert,
};
void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button);
void mouseActionUpdate(const QPoint &screenPos);
@@ -165,6 +173,13 @@ private:
HistoryItem *prevItem(HistoryItem *item);
HistoryItem *nextItem(HistoryItem *item);
void updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting);
TextSelection itemRenderSelection(
not_null<HistoryItem*> item,
int selfromy,
int seltoy) const;
TextSelection computeRenderSelection(
not_null<const SelectedItems*> selected,
not_null<HistoryItem*> item) const;
void setToClipboard(const TextWithEntities &forClipboard, QClipboard::Mode mode = QClipboard::Clipboard);
@@ -186,23 +201,6 @@ private:
// or at least we don't need to display first _history date (just skip it by height)
int _historySkipHeight = 0;
class BotAbout : public ClickHandlerHost {
public:
BotAbout(HistoryInner *parent, BotInfo *info) : info(info), _parent(parent) {
}
BotInfo *info = nullptr;
int width = 0;
int height = 0;
QRect rect;
// ClickHandlerHost interface
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
private:
HistoryInner *_parent;
};
std::unique_ptr<BotAbout> _botAbout;
HistoryWidget *_widget = nullptr;
@@ -214,11 +212,45 @@ private:
bool _firstLoading = false;
style::cursor _cursor = style::cur_default;
using SelectedItems = std::map<HistoryItem*, TextSelection, std::less<>>;
SelectedItems _selected;
void applyDragSelection();
void applyDragSelection(SelectedItems *toItems) const;
void addSelectionRange(SelectedItems *toItems, int32 fromblock, int32 fromitem, int32 toblock, int32 toitem, History *h) const;
void applyDragSelection(not_null<SelectedItems*> toItems) const;
void addSelectionRange(
not_null<SelectedItems*> toItems,
not_null<History*> history,
int fromblock,
int fromitem,
int toblock,
int toitem) const;
bool isSelected(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const;
bool isSelectedAsGroup(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const;
bool goodForSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
int &totalCount) const;
void addToSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const;
void removeFromSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item) const;
void changeSelection(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
SelectAction action) const;
void changeSelectionAsGroup(
not_null<SelectedItems*> toItems,
not_null<HistoryItem*> item,
SelectAction action) const;
void forwardItem(not_null<HistoryItem*> item);
void forwardAsGroup(not_null<HistoryItem*> item);
void deleteItem(not_null<HistoryItem*> item);
void deleteAsGroup(not_null<HistoryItem*> item);
// Does any of the shown histories has this flag set.
bool hasPendingResizedItems() const {
@@ -230,6 +262,7 @@ private:
QPoint _dragStartPosition;
QPoint _mousePosition;
HistoryItem *_mouseActionItem = nullptr;
HistoryItem *_dragStateItem = nullptr;
HistoryCursorState _mouseCursorState = HistoryDefaultCursorState;
uint16 _mouseTextSymbol = 0;
bool _pressWasInactive = false;

View File

@@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "history/history_service_layout.h"
#include "history/history_media_types.h"
#include "history/history_media_grouped.h"
#include "history/history_message.h"
#include "media/media_clip_reader.h"
#include "styles/style_dialogs.h"
@@ -45,6 +46,29 @@ constexpr int kAttachMessageToPreviousSecondsDelta = 900;
} // namespace
HistoryTextState::HistoryTextState(not_null<const HistoryItem*> item)
: itemId(item->fullId()) {
}
HistoryTextState::HistoryTextState(
not_null<const HistoryItem*> item,
const Text::StateResult &state)
: itemId(item->fullId())
, cursor(state.uponSymbol
? HistoryInTextCursorState
: HistoryDefaultCursorState)
, link(state.link)
, afterSymbol(state.afterSymbol)
, symbol(state.symbol) {
}
HistoryTextState::HistoryTextState(
not_null<const HistoryItem*> item,
ClickHandlerPtr link)
: itemId(item->fullId())
, link(link) {
}
ReplyMarkupClickHandler::ReplyMarkupClickHandler(const HistoryItem *item, int row, int col)
: _itemId(item->fullId())
, _row(row)
@@ -563,7 +587,8 @@ HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = 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) {
_pointer->attachToParent();
}
@@ -625,13 +650,15 @@ void HistoryItem::finishCreate() {
void HistoryItem::finishEdition(int oldKeyboardTop) {
setPendingInitDimensions();
if (App::main()) {
App::main()->dlgUpdated(history()->peer, id);
}
// invalidate cache for drawInDialog
if (history()->textCachedFor == this) {
history()->textCachedFor = nullptr;
invalidateChatsListEntry();
//if (groupId()) {
// history()->fixGroupAfterEdition(this);
//}
if (isHiddenByGroup()) {
// Perhaps caption was changed, we should refresh the group.
const auto group = Get<HistoryMessageGroup>();
group->leader->setPendingInitDimensions();
group->leader->invalidateChatsListEntry();
}
if (oldKeyboardTop >= 0) {
@@ -643,6 +670,17 @@ void HistoryItem::finishEdition(int oldKeyboardTop) {
App::historyUpdateDependent(this);
}
void HistoryItem::invalidateChatsListEntry() {
if (App::main()) {
App::main()->dlgUpdated(history()->peer, id);
}
// invalidate cache for drawInDialog
if (history()->textCachedFor == this) {
history()->textCachedFor = nullptr;
}
}
void HistoryItem::finishEditionToEmpty() {
recountDisplayDate();
finishEdition(-1);
@@ -768,6 +806,11 @@ void HistoryItem::detach() {
void HistoryItem::detachFast() {
_block = nullptr;
_indexInBlock = -1;
validateGroupId();
if (groupId()) {
makeGroupLeader({});
}
}
Storage::SharedMediaTypesMask HistoryItem::sharedMediaTypes() const {
@@ -1116,6 +1159,99 @@ 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);
const auto leaderChanged = (group->leader != this);
if (leaderChanged) {
_flags &= ~MTPDmessage_ClientFlag::f_hidden_by_group;
setPendingInitDimensions();
}
group->others = std::move(others);
if (!_media || !_media->applyGroup(group->others)) {
resetGroupMedia(group->others);
invalidateChatsListEntry();
}
Ensures(!isHiddenByGroup());
}
HistoryMessageGroup *HistoryItem::getFullGroup() {
if (const auto group = Get<HistoryMessageGroup>()) {
if (group->leader == this) {
return group;
}
return group->leader->Get<HistoryMessageGroup>();
}
return nullptr;
}
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) {
using namespace Media::Clip;

View File

@@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "base/runtime_composer.h"
#include "base/flags.h"
#include "base/value_ordering.h"
namespace base {
template <typename Enum>
@@ -78,25 +79,33 @@ enum HistoryCursorState {
struct HistoryTextState {
HistoryTextState() = default;
HistoryTextState(const Text::StateResult &state)
: cursor(state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState)
, link(state.link)
, afterSymbol(state.afterSymbol)
, symbol(state.symbol) {
HistoryTextState(not_null<const HistoryItem*> item);
HistoryTextState(
not_null<const HistoryItem*> item,
const Text::StateResult &state);
HistoryTextState(
not_null<const HistoryItem*> item,
ClickHandlerPtr link);
HistoryTextState(
std::nullptr_t,
const Text::StateResult &state)
: cursor(state.uponSymbol
? HistoryInTextCursorState
: HistoryDefaultCursorState)
, link(state.link)
, afterSymbol(state.afterSymbol)
, symbol(state.symbol) {
}
HistoryTextState &operator=(const Text::StateResult &state) {
cursor = state.uponSymbol ? HistoryInTextCursorState : HistoryDefaultCursorState;
link = state.link;
afterSymbol = state.afterSymbol;
symbol = state.symbol;
return *this;
}
HistoryTextState(ClickHandlerPtr link) : link(link) {
HistoryTextState(std::nullptr_t, ClickHandlerPtr link)
: link(link) {
}
FullMsgId itemId;
HistoryCursorState cursor = HistoryDefaultCursorState;
ClickHandlerPtr link;
bool afterSymbol = false;
uint16 symbol = 0;
};
struct HistoryStateRequest {
@@ -140,11 +149,11 @@ struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned> {
};
struct HistoryMessageEdited : public RuntimeComponent<HistoryMessageEdited> {
void create(const QDateTime &editDate, const QString &date);
void refresh(const QString &date, bool displayed);
int maxWidth() const;
QDateTime _editDate;
Text _edited;
QDateTime date;
Text text;
};
struct HistoryMessageForwarded : public RuntimeComponent<HistoryMessageForwarded> {
@@ -435,6 +444,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;
// Special type of Component for the channel actions log.
@@ -739,6 +776,15 @@ public:
}
virtual void setId(MsgId newId);
virtual bool displayEditedBadge() const {
return false;
}
virtual QDateTime displayedEditDate() const {
return QDateTime();
}
virtual void refreshEditedBadge() {
}
void drawInDialog(
Painter &p,
const QRect &r,
@@ -899,22 +945,8 @@ public:
}
return 0;
}
int marginTop() const {
int result = 0;
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();
}
int marginTop() const;
int marginBottom() const;
bool isAttachedToPrevious() const {
return _flags & MTPDmessage_ClientFlag::f_attach_to_previous;
}
@@ -932,6 +964,24 @@ public:
bool isEmpty() const {
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);
HistoryMessageGroup *getFullGroup();
int width() const {
return _width;
@@ -1053,6 +1103,7 @@ protected:
}
return nullptr;
}
void invalidateChatsListEntry();
[[nodiscard]] TextSelection skipTextSelection(
TextSelection selection) const {
@@ -1070,6 +1121,8 @@ protected:
HistoryMediaPtr _media;
private:
void resetGroupMedia(const std::vector<not_null<HistoryItem*>> &others);
int _y = 0;
int _width = 0;

View File

@@ -61,7 +61,9 @@ public:
}
virtual bool isDisplayed() const {
return true;
return !_parent->isHiddenByGroup();
}
virtual void updateNeedBubbleState() {
}
virtual bool isAboveMessage() const {
return false;
@@ -132,9 +134,14 @@ public:
virtual bool uploading() const {
return false;
}
virtual std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const = 0;
virtual std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const = 0;
virtual DocumentData *getDocument() {
virtual PhotoData *getPhoto() const {
return nullptr;
}
virtual DocumentData *getDocument() const {
return nullptr;
}
virtual Media::Clip::Reader *getClipReader() {
@@ -155,10 +162,40 @@ public:
virtual void attachToParent() {
}
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) {
}
@@ -190,6 +227,13 @@ public:
return false;
}
virtual bool overrideEditedDate() const {
return false;
}
virtual HistoryMessageEdited *displayedEditBadge() const {
Unexpected("displayedEditBadge() on non-grouped media.");
}
// An attach media in a web page can provide an
// additional text to be displayed below the attach.
// For example duration / progress for video messages.

View File

@@ -0,0 +1,454 @@
/*
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)
, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
const auto result = applyGroup(others);
Ensures(result);
}
void HistoryGroupedMedia::initDimensions() {
if (_caption.hasSkipBlock()) {
_caption.setSkipBlock(
_parent->skipBlockWidth(),
_parent->skipBlockHeight());
}
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;
}
if (!_caption.isEmpty()) {
auto captionw = _maxw - st::msgPadding.left() - st::msgPadding.right();
_minh += st::mediaCaptionSkip + _caption.countHeight(captionw);
if (isBubbleBottom()) {
_minh += st::msgPadding.bottom();
}
}
}
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(_maxw);
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);
}
if (!_caption.isEmpty()) {
const auto captionw = _width - st::msgPadding.left() - st::msgPadding.right();
_height += st::mediaPadding.bottom() + st::mediaCaptionSkip + _caption.countHeight(captionw);
if (isBubbleBottom()) {
_height += st::msgPadding.bottom();
}
}
return _height;
}
void HistoryGroupedMedia::draw(
Painter &p,
const QRect &clip,
TextSelection selection,
TimeMs ms) const {
for (auto i = 0, count = int(_elements.size()); i != count; ++i) {
const auto &element = _elements[i];
const auto elementSelection = (selection == FullSelection)
? FullSelection
: IsGroupItemSelection(selection, i)
? FullSelection
: TextSelection();
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,
elementSelection,
ms,
element.geometry,
corners,
&element.cacheKey,
&element.cache);
}
// date
const auto selected = (selection == FullSelection);
if (!_caption.isEmpty()) {
const auto captionw = _width - st::msgPadding.left() - st::msgPadding.right();
const auto outbg = _parent->hasOutLayout();
const auto captiony = _height
- (isBubbleBottom() ? st::msgPadding.bottom() : 0)
- _caption.countHeight(captionw);
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
_caption.draw(p, st::msgPadding.left(), captiony, captionw, style::al_left, 0, -1, selection);
} else if (_parent->getMedia() == this) {
auto fullRight = _width;
auto fullBottom = _height;
if (_parent->id < 0 || App::hoveredItem() == _parent) {
_parent->drawInfo(p, fullRight, fullBottom, _width, selected, InfoDisplayOverImage);
}
if (!_parent->hasBubble() && _parent->displayRightAction()) {
auto fastShareLeft = (fullRight + st::historyFastShareLeft);
auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
_parent->drawRightAction(p, fastShareLeft, fastShareTop, _width);
}
}
}
HistoryTextState HistoryGroupedMedia::getElementState(
QPoint point,
HistoryStateRequest request) const {
for (const auto &element : _elements) {
if (element.geometry.contains(point)) {
auto result = element.content->getStateGrouped(
element.geometry,
point,
request);
result.itemId = element.item->fullId();
return result;
}
}
return HistoryTextState(_parent);
}
HistoryTextState HistoryGroupedMedia::getState(
QPoint point,
HistoryStateRequest request) const {
auto result = getElementState(point, request);
if (!result.link && !_caption.isEmpty()) {
const auto captionw = _width - st::msgPadding.left() - st::msgPadding.right();
const auto captiony = _height
- (isBubbleBottom() ? st::msgPadding.bottom() : 0)
- _caption.countHeight(captionw);
if (QRect(st::msgPadding.left(), captiony, captionw, _height - captiony).contains(point)) {
return HistoryTextState(_parent, _caption.getState(
point - QPoint(st::msgPadding.left(), captiony),
captionw,
request.forText()));
}
} else if (_parent->getMedia() == this) {
auto fullRight = _width;
auto fullBottom = _height;
if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayOverImage)) {
result.cursor = HistoryInDateCursorState;
}
if (!_parent->hasBubble() && _parent->displayRightAction()) {
auto fastShareLeft = (fullRight + st::historyFastShareLeft);
auto fastShareTop = (fullBottom - st::historyFastShareBottom - st::historyFastShareSize);
if (QRect(fastShareLeft, fastShareTop, st::historyFastShareSize, st::historyFastShareSize).contains(point)) {
result.link = _parent->rightActionLink();
}
}
}
return result;
}
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);
}
QString HistoryGroupedMedia::notificationText() const {
return WithCaptionNotificationText(lang(lng_in_dlg_photo), _caption);
}
QString HistoryGroupedMedia::inDialogsText() const {
return WithCaptionDialogsText(lang(lng_in_dlg_album), _caption);
}
TextWithEntities HistoryGroupedMedia::selectedText(
TextSelection selection) const {
if (!IsSubGroupSelection(selection)) {
return WithCaptionSelectedText(
lang(lng_in_dlg_album),
_caption,
selection);
} else if (IsGroupItemSelection(selection, int(_elements.size()) - 1)) {
return main()->selectedText(FullSelection);
}
return TextWithEntities();
}
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);
if (pressed && element.content->dragItemByHandler(p)) {
App::pressedLinkItem(element.item);
}
}
}
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, item);
};
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();
}
TextWithEntities HistoryGroupedMedia::getCaption() const {
return main()->getCaption();
}
Storage::SharedMediaTypesMask HistoryGroupedMedia::sharedMediaTypes() const {
return main()->sharedMediaTypes();
}
HistoryMessageEdited *HistoryGroupedMedia::displayedEditBadge() const {
if (!_caption.isEmpty()) {
return _elements.front().item->Get<HistoryMessageEdited>();
}
return nullptr;
}
void HistoryGroupedMedia::updateNeedBubbleState() {
const auto getItemCaption = [](const Element &element) {
if (const auto media = element.item->getMedia()) {
return media->getCaption();
}
return element.content->getCaption();
};
const auto captionText = [&] {
auto result = getItemCaption(_elements.front());
if (result.text.isEmpty()) {
return result;
}
for (auto i = 1, count = int(_elements.size()); i != count; ++i) {
if (!getItemCaption(_elements[i]).text.isEmpty()) {
return TextWithEntities();
}
}
return result;
}();
_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;
}
if (const auto message = _parent->toHistoryMessage()) {
if (message->viaBot()
|| message->Has<HistoryMessageReply>()
|| message->displayForwardedFrom()
|| message->displayFromName()) {
return true;
}
}
return false;
}

View File

@@ -0,0 +1,145 @@
/*
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,
not_null<HistoryItem*> realParent) 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();
}
PhotoData *getPhoto() const override {
return main()->getPhoto();
}
DocumentData *getDocument() const override {
return main()->getDocument();
}
QString notificationText() const override;
QString inDialogsText() const override;
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;
TextWithEntities getCaption() const override;
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
bool overrideEditedDate() const override {
return true;
}
HistoryMessageEdited *displayedEditBadge() 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;
HistoryTextState getElementState(
QPoint point,
HistoryStateRequest request) const;
Text _caption;
std::vector<Element> _elements;
bool _needBubble = false;
};

File diff suppressed because it is too large Load Diff

View File

@@ -38,6 +38,16 @@ class EmptyUserpic;
} // namespace Ui
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 {
public:
@@ -62,21 +72,28 @@ public:
protected:
ClickHandlerPtr _openl, _savel, _cancell;
void setLinks(ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell);
void setDocumentLinks(DocumentData *document, bool inlinegif = false) {
void setDocumentLinks(
not_null<DocumentData*> document,
not_null<HistoryItem*> realParent,
bool inlinegif = false) {
ClickHandlerPtr open, save;
const auto context = realParent->fullId();
if (inlinegif) {
open = MakeShared<GifOpenClickHandler>(document);
open = MakeShared<GifOpenClickHandler>(document, context);
} else {
open = MakeShared<DocumentOpenClickHandler>(document);
open = MakeShared<DocumentOpenClickHandler>(document, context);
}
if (inlinegif) {
save = MakeShared<GifOpenClickHandler>(document);
save = MakeShared<GifOpenClickHandler>(document, context);
} else if (document->isVoiceMessage()) {
save = MakeShared<DocumentOpenClickHandler>(document);
save = MakeShared<DocumentOpenClickHandler>(document, context);
} else {
save = MakeShared<DocumentSaveClickHandler>(document);
save = MakeShared<DocumentSaveClickHandler>(document, context);
}
setLinks(std::move(open), std::move(save), MakeShared<DocumentCancelClickHandler>(document));
setLinks(
std::move(open),
std::move(save),
MakeShared<DocumentCancelClickHandler>(document, context));
}
// >= 0 will contain download / upload string, _statusSize = loaded bytes
@@ -129,23 +146,39 @@ protected:
class HistoryPhoto : public HistoryFileMedia {
public:
HistoryPhoto(not_null<HistoryItem*> parent, 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,
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,
not_null<HistoryItem*> realParent,
const HistoryPhoto &other);
void init();
HistoryMediaType type() const override {
return MediaTypePhoto;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
return std::make_unique<HistoryPhoto>(newParent, *this);
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
return std::make_unique<HistoryPhoto>(newParent, realParent, *this);
}
void initDimensions() 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;
[[nodiscard]] TextSelection adjustSelection(
@@ -166,10 +199,28 @@ public:
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
PhotoData *photo() const {
PhotoData *getPhoto() const override {
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;
bool needReSetInlineResultMedia(const MTPMessageMedia &media) override;
@@ -210,6 +261,12 @@ protected:
}
private:
void validateGroupedCache(
const QRect &geometry,
RectParts corners,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const;
not_null<PhotoData*> _data;
int16 _pixw = 1;
int16 _pixh = 1;
@@ -219,13 +276,22 @@ private:
class HistoryVideo : public HistoryFileMedia {
public:
HistoryVideo(not_null<HistoryItem*> parent, DocumentData *document, const QString &caption);
HistoryVideo(not_null<HistoryItem*> parent, const HistoryVideo &other);
HistoryVideo(
not_null<HistoryItem*> parent,
not_null<DocumentData*> document,
const QString &caption);
HistoryVideo(
not_null<HistoryItem*> parent,
not_null<HistoryItem*> realParent,
const HistoryVideo &other);
HistoryMediaType type() const override {
return MediaTypeVideo;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
return std::make_unique<HistoryVideo>(newParent, *this);
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
return std::make_unique<HistoryVideo>(newParent, realParent, *this);
}
void initDimensions() override;
@@ -252,10 +318,28 @@ public:
Storage::SharedMediaTypesMask sharedMediaTypes() const override;
DocumentData *getDocument() override {
DocumentData *getDocument() const override {
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 {
return _data->uploading();
}
@@ -297,13 +381,18 @@ protected:
}
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;
int32 _thumbw;
Text _caption;
void setStatusSize(int32 newSize) const;
void updateStatusText() const;
};
struct HistoryDocumentThumbed : public RuntimeComponent<HistoryDocumentThumbed> {
@@ -370,8 +459,14 @@ private:
class HistoryDocument : public HistoryFileMedia, public RuntimeComposer {
public:
HistoryDocument(not_null<HistoryItem*> parent, DocumentData *document, const QString &caption);
HistoryDocument(not_null<HistoryItem*> parent, const HistoryDocument &other);
HistoryDocument(
not_null<HistoryItem*> parent,
not_null<DocumentData*> document,
const QString &caption);
HistoryDocument(
not_null<HistoryItem*> parent,
const HistoryDocument &other);
HistoryMediaType type() const override {
return _data->isVoiceMessage()
? MediaTypeVoiceFile
@@ -379,7 +474,11 @@ public:
? MediaTypeMusicFile
: MediaTypeFile);
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryDocument>(newParent, *this);
}
@@ -418,7 +517,7 @@ public:
return _data->uploading();
}
DocumentData *getDocument() override {
DocumentData *getDocument() const override {
return _data;
}
@@ -488,12 +587,20 @@ private:
class HistoryGif : public HistoryFileMedia {
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);
HistoryMediaType type() const override {
return MediaTypeGif;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryGif>(newParent, *this);
}
@@ -525,7 +632,7 @@ public:
return _data->uploading();
}
DocumentData *getDocument() override {
DocumentData *getDocument() const override {
return _data;
}
Media::Clip::Reader *getClipReader() override {
@@ -602,11 +709,18 @@ private:
class HistorySticker : public HistoryMedia {
public:
HistorySticker(not_null<HistoryItem*> parent, DocumentData *document);
HistorySticker(
not_null<HistoryItem*> parent,
not_null<DocumentData*> document);
HistoryMediaType type() const override {
return MediaTypeSticker;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistorySticker>(newParent, _data);
}
@@ -629,7 +743,7 @@ public:
QString notificationText() const override;
TextWithEntities selectedText(TextSelection selection) const override;
DocumentData *getDocument() override {
DocumentData *getDocument() const override {
return _data;
}
@@ -671,12 +785,27 @@ private:
class HistoryContact : public HistoryMedia {
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 {
return MediaTypeContact;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
return std::make_unique<HistoryContact>(newParent, _userId, _fname, _lname, _phone);
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryContact>(
newParent,
_userId,
_fname,
_lname,
_phone);
}
void initDimensions() override;
@@ -735,11 +864,16 @@ private:
class HistoryCall : public HistoryMedia {
public:
HistoryCall(not_null<HistoryItem*> parent, const MTPDmessageActionPhoneCall &call);
HistoryCall(
not_null<HistoryItem*> parent,
const MTPDmessageActionPhoneCall &call);
HistoryMediaType type() const override {
return MediaTypeCall;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Unexpected("Clone HistoryCall.");
}
@@ -790,12 +924,21 @@ private:
class HistoryWebPage : public HistoryMedia {
public:
HistoryWebPage(not_null<HistoryItem*> parent, not_null<WebPageData*> data);
HistoryWebPage(not_null<HistoryItem*> parent, const HistoryWebPage &other);
HistoryWebPage(
not_null<HistoryItem*> parent,
not_null<WebPageData*> data);
HistoryWebPage(
not_null<HistoryItem*> parent,
const HistoryWebPage &other);
HistoryMediaType type() const override {
return MediaTypeWebPage;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryWebPage>(newParent, *this);
}
@@ -830,7 +973,10 @@ public:
bool isDisplayed() const override {
return !_data->pendingTill && !_parent->Has<HistoryMessageLogEntryOriginal>();
}
DocumentData *getDocument() override {
PhotoData *getPhoto() const override {
return _attach ? _attach->getPhoto() : nullptr;
}
DocumentData *getDocument() const override {
return _attach ? _attach->getDocument() : nullptr;
}
Media::Clip::Reader *getClipReader() override {
@@ -898,12 +1044,17 @@ private:
class HistoryGame : public HistoryMedia {
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);
HistoryMediaType type() const override {
return MediaTypeGame;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryGame>(newParent, *this);
}
@@ -941,7 +1092,10 @@ public:
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
DocumentData *getDocument() override {
PhotoData *getPhoto() const override {
return _attach ? _attach->getPhoto() : nullptr;
}
DocumentData *getDocument() const override {
return _attach ? _attach->getDocument() : nullptr;
}
Media::Clip::Reader *getClipReader() override {
@@ -962,7 +1116,7 @@ public:
}
ImagePtr replyPreview() override;
GameData *game() {
not_null<GameData*> game() {
return _data;
}
@@ -993,7 +1147,7 @@ private:
QMargins inBubblePadding() const;
int bottomInfoPadding() const;
GameData *_data;
not_null<GameData*> _data;
ClickHandlerPtr _openl;
std::unique_ptr<HistoryMedia> _attach;
@@ -1007,12 +1161,21 @@ private:
class HistoryInvoice : public HistoryMedia {
public:
HistoryInvoice(not_null<HistoryItem*> parent, const MTPDmessageMediaInvoice &data);
HistoryInvoice(not_null<HistoryItem*> parent, const HistoryInvoice &other);
HistoryInvoice(
not_null<HistoryItem*> parent,
const MTPDmessageMediaInvoice &data);
HistoryInvoice(
not_null<HistoryItem*> parent,
const HistoryInvoice &other);
HistoryMediaType type() const override {
return MediaTypeInvoice;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryInvoice>(newParent, *this);
}
@@ -1103,12 +1266,23 @@ struct LocationData;
class HistoryLocation : public HistoryMedia {
public:
HistoryLocation(not_null<HistoryItem*> parent, const LocationCoords &coords, const QString &title = QString(), const QString &description = QString());
HistoryLocation(not_null<HistoryItem*> parent, const HistoryLocation &other);
HistoryLocation(
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 {
return MediaTypeLocation;
}
std::unique_ptr<HistoryMedia> clone(HistoryItem *newParent) const override {
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
Expects(newParent == realParent);
return std::make_unique<HistoryLocation>(newParent, *this);
}

View File

@@ -125,6 +125,7 @@ bool HasMediaItems(const HistoryItemsList &items) {
switch (media->type()) {
case MediaTypePhoto:
case MediaTypeVideo:
case MediaTypeGrouped:
case MediaTypeFile:
case MediaTypeMusicFile:
case MediaTypeVoiceFile: return true;
@@ -181,27 +182,28 @@ bool HasInlineItems(const HistoryItemsList &items) {
void FastShareMessage(not_null<HistoryItem*> item) {
struct ShareData {
ShareData(const FullMsgId &msgId) : msgId(msgId) {
ShareData(not_null<PeerData*> peer, MessageIdsList &&ids)
: peer(peer)
, msgIds(std::move(ids)) {
}
FullMsgId msgId;
OrderedSet<mtpRequestId> requests;
not_null<PeerData*> peer;
MessageIdsList msgIds;
base::flat_set<mtpRequestId> requests;
};
auto data = MakeShared<ShareData>(item->fullId());
auto isGame = item->getMessageBot()
const auto data = MakeShared<ShareData>(item->history()->peer, [&] {
if (const auto group = item->getFullGroup()) {
return Auth().data().groupToIds(group);
}
return MessageIdsList(1, item->fullId());
}());
const auto isGame = item->getMessageBot()
&& item->getMedia()
&& (item->getMedia()->type() == MediaTypeGame);
const auto canCopyLink = item->hasDirectLink() || isGame;
auto canCopyLink = item->hasDirectLink();
if (!canCopyLink) {
if (auto bot = item->getMessageBot()) {
if (auto media = item->getMedia()) {
canCopyLink = (media->type() == MediaTypeGame);
}
}
}
auto copyCallback = [data]() {
if (auto main = App::main()) {
if (auto item = App::histItemById(data->msgId)) {
if (auto item = App::histItemById(data->msgIds[0])) {
if (item->hasDirectLink()) {
QApplication::clipboard()->setText(item->directLink());
@@ -224,12 +226,11 @@ void FastShareMessage(not_null<HistoryItem*> item) {
if (!data->requests.empty()) {
return; // Share clicked already.
}
auto item = App::histItemById(data->msgId);
if (!item || result.empty()) {
auto items = Auth().data().idsToItems(data->msgIds);
if (items.empty() || result.empty()) {
return;
}
auto items = HistoryItemsList(1, item);
auto restrictedSomewhere = false;
auto restrictedEverywhere = true;
auto firstError = QString();
@@ -262,16 +263,32 @@ void FastShareMessage(not_null<HistoryItem*> item) {
}
};
auto sendFlags = MTPmessages_ForwardMessages::Flag::f_with_my_score;
MTPVector<MTPint> msgIds = MTP_vector<MTPint>(1, MTP_int(data->msgId.msg));
auto sendFlags = MTPmessages_ForwardMessages::Flag::f_with_my_score
| MTPmessages_ForwardMessages::Flag::f_grouped;
auto msgIds = QVector<MTPint>();
msgIds.reserve(data->msgIds.size());
for (const auto fullId : data->msgIds) {
msgIds.push_back(MTP_int(fullId.msg));
}
auto generateRandom = [&] {
auto result = QVector<MTPlong>(data->msgIds.size());
for (auto &value : result) {
value = rand_value<MTPlong>();
}
return result;
};
if (auto main = App::main()) {
for (const auto peer : result) {
if (!GetErrorTextForForward(peer, items).isEmpty()) {
continue;
}
MTPVector<MTPlong> random = MTP_vector<MTPlong>(1, rand_value<MTPlong>());
auto request = MTPmessages_ForwardMessages(MTP_flags(sendFlags), item->history()->peer->input, msgIds, random, peer->input);
auto request = MTPmessages_ForwardMessages(
MTP_flags(sendFlags),
data->peer->input,
MTP_vector<MTPint>(msgIds),
MTP_vector<MTPlong>(generateRandom()),
peer->input);
auto callback = doneCallback;
auto requestId = MTP::send(request, rpcDone(std::move(callback)));
data->requests.insert(requestId);
@@ -381,13 +398,13 @@ int HistoryMessageSigned::maxWidth() const {
return _signature.maxWidth();
}
void HistoryMessageEdited::create(const QDateTime &editDate, const QString &date) {
_editDate = editDate;
_edited.setText(st::msgDateTextStyle, lang(lng_edited) + ' ' + date, _textNameOptions);
void HistoryMessageEdited::refresh(const QString &date, bool displayed) {
const auto prefix = displayed ? (lang(lng_edited) + ' ') : QString();
text.setText(st::msgDateTextStyle, prefix + date, _textNameOptions);
}
int HistoryMessageEdited::maxWidth() const {
return _edited.maxWidth();
return text.maxWidth();
}
void HistoryMessageForwarded::create(const HistoryMessageVia *via) const {
@@ -630,7 +647,9 @@ int HistoryMessage::KeyboardStyle::minButtonWidth(HistoryMessageReplyMarkup::But
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) {
CreateConfig config;
@@ -655,6 +674,9 @@ HistoryMessage::HistoryMessage(not_null<History*> history, const MTPDmessage &ms
if (msg.has_reply_markup()) config.mtpMarkup = &msg.vreply_markup;
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_grouped_id()) {
config.groupId = MessageGroupId::FromRaw(msg.vgrouped_id.v);
}
createComponents(config);
@@ -665,7 +687,9 @@ HistoryMessage::HistoryMessage(not_null<History*> history, const MTPDmessage &ms
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) {
CreateConfig config;
@@ -750,27 +774,58 @@ HistoryMessage::HistoryMessage(
return (mediaType != MediaTypeCount);
};
if (cloneMedia()) {
_media = mediaOriginal->clone(this);
_media = mediaOriginal->clone(this, this);
}
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) {
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, MTPnullMarkup);
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) {
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
initMediaFromDocument(doc, caption);
initMediaFromDocument(document, caption);
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) {
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
@@ -778,7 +833,17 @@ HistoryMessage::HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmess
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) {
createComponentsHelper(flags, replyTo, viaBotId, postAuthor, markup);
@@ -786,7 +851,12 @@ HistoryMessage::HistoryMessage(not_null<History*> history, MsgId msgId, MTPDmess
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;
if (flags & MTPDmessage::Flag::f_via_bot_id) config.viaBotId = viaBotId;
@@ -818,6 +888,7 @@ void HistoryMessage::updateMediaInBubbleState() {
return;
}
_media->updateNeedBubbleState();
if (!drawBubble()) {
_media->setInBubbleState(MediaInBubbleState::None);
return;
@@ -882,18 +953,43 @@ void HistoryMessage::applyGroupAdminChanges(
}
}
bool HistoryMessage::displayEditedBadge(bool hasViaBotOrInlineMarkup) const {
bool HistoryMessage::displayEditedBadge() const {
return !displayedEditDate().isNull();
}
QDateTime HistoryMessage::displayedEditDate() const {
auto hasViaBotId = Has<HistoryMessageVia>();
auto hasInlineMarkup = (inlineReplyMarkup() != nullptr);
return displayedEditDate(hasViaBotId || hasInlineMarkup);
}
QDateTime HistoryMessage::displayedEditDate(
bool hasViaBotOrInlineMarkup) const {
if (hasViaBotOrInlineMarkup) {
return false;
} else if (!(_flags & MTPDmessage::Flag::f_edit_date)) {
return false;
}
if (auto fromUser = from()->asUser()) {
return QDateTime();
} else if (const auto fromUser = from()->asUser()) {
if (fromUser->botInfo) {
return false;
return QDateTime();
}
}
return true;
if (const auto edited = displayedEditBadge()) {
return edited->date;
}
return QDateTime();
}
HistoryMessageEdited *HistoryMessage::displayedEditBadge() {
if (_media && _media->overrideEditedDate()) {
return _media->displayedEditBadge();
}
return Get<HistoryMessageEdited>();
}
const HistoryMessageEdited *HistoryMessage::displayedEditBadge() const {
if (_media && _media->overrideEditedDate()) {
return _media->displayedEditBadge();
}
return Get<HistoryMessageEdited>();
}
bool HistoryMessage::uploading() const {
@@ -945,7 +1041,7 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
}
return (config.inlineMarkup != nullptr);
};
if (displayEditedBadge(hasViaBot || hasInlineMarkup())) {
if (!config.editDate.isNull()) {
mask |= HistoryMessageEdited::Bit();
}
if (config.senderOriginal) {
@@ -960,10 +1056,13 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
} else if (config.inlineMarkup) {
mask |= HistoryMessageReplyMarkup::Bit();
}
if (config.groupId) {
mask |= HistoryMessageGroup::Bit();
}
UpdateComponents(mask);
if (auto reply = Get<HistoryMessageReply>()) {
if (const auto reply = Get<HistoryMessageReply>()) {
reply->replyToMsgId = config.replyTo;
if (!reply->updateData(this)) {
Auth().api().requestMessageData(
@@ -972,21 +1071,16 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
HistoryDependentItemCallback(fullId()));
}
}
if (auto via = Get<HistoryMessageVia>()) {
if (const auto via = Get<HistoryMessageVia>()) {
via->create(config.viaBotId);
}
if (auto views = Get<HistoryMessageViews>()) {
if (const auto views = Get<HistoryMessageViews>()) {
views->_views = config.viewsCount;
}
if (auto edited = Get<HistoryMessageEdited>()) {
edited->create(config.editDate, date.toString(cTimeFormat()));
if (auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->create(config.author, edited->_edited.originalText());
}
} else if (auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->create(config.author, date.toString(cTimeFormat()));
if (const auto edited = Get<HistoryMessageEdited>()) {
edited->date = config.editDate;
}
if (auto forwarded = Get<HistoryMessageForwarded>()) {
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
forwarded->_originalDate = config.originalDate;
forwarded->_originalSender = App::peer(config.senderOriginal);
forwarded->_originalId = config.originalId;
@@ -994,7 +1088,7 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
forwarded->_savedFromPeer = App::peerLoaded(config.savedFromPeer);
forwarded->_savedFromMsgId = config.savedFromMsgId;
}
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
if (config.mtpMarkup) {
markup->create(*config.mtpMarkup);
} else if (config.inlineMarkup) {
@@ -1004,7 +1098,10 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
_flags |= MTPDmessage_ClientFlag::f_has_switch_inline_button;
}
}
initTime();
if (const auto group = Get<HistoryMessageGroup>()) {
group->groupId = config.groupId;
group->leader = this;
}
_fromNameVersion = displayFrom()->nameVersion;
}
@@ -1028,18 +1125,23 @@ QString formatViewsCount(int32 views) {
}
void HistoryMessage::initTime() {
if (auto msgsigned = Get<HistoryMessageSigned>()) {
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
_timeWidth = msgsigned->maxWidth();
} else if (auto edited = Get<HistoryMessageEdited>()) {
} else if (const auto edited = displayedEditBadge()) {
_timeWidth = edited->maxWidth();
} else {
_timeText = date.toString(cTimeFormat());
_timeWidth = st::msgDateFont->width(_timeText);
}
if (auto views = Get<HistoryMessageViews>()) {
if (const auto views = Get<HistoryMessageViews>()) {
views->_viewsText = (views->_views >= 0) ? formatViewsCount(views->_views) : QString();
views->_viewsWidth = views->_viewsText.isEmpty() ? 0 : st::msgDateFont->width(views->_viewsText);
}
if (_text.hasSkipBlock()) {
_text.setSkipBlock(skipBlockWidth(), skipBlockHeight());
_textWidth = -1;
_textHeight = 0;
}
}
void HistoryMessage::initMedia(const MTPMessageMedia *media) {
@@ -1144,6 +1246,7 @@ int32 HistoryMessage::plainMaxWidth() const {
void HistoryMessage::initDimensions() {
updateMediaInBubbleState();
refreshEditedBadge();
if (drawBubble()) {
auto forwarded = Get<HistoryMessageForwarded>();
auto reply = Get<HistoryMessageReply>();
@@ -1240,14 +1343,16 @@ void HistoryMessage::initDimensions() {
} else if (_media) {
_media->initDimensions();
_maxw = _media->maxWidth();
_minh = _media->minHeight();
_minh = _media->isDisplayed() ? _media->minHeight() : 0;
} else {
_maxw = st::msgMinWidth;
_minh = 0;
}
if (auto markup = inlineReplyMarkup()) {
if (const auto markup = inlineReplyMarkup()) {
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
@@ -1259,7 +1364,9 @@ void HistoryMessage::initDimensions() {
}
bool HistoryMessage::drawBubble() const {
if (Has<HistoryMessageLogEntryOriginal>()) {
if (isHiddenByGroup()) {
return false;
} else if (Has<HistoryMessageLogEntryOriginal>()) {
return true;
}
return _media ? (!emptyText() || _media->needsBubble()) : !isEmpty();
@@ -1334,24 +1441,11 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
if (message.has_edit_date()) {
_flags |= MTPDmessage::Flag::f_edit_date;
auto hasViaBotId = Has<HistoryMessageVia>();
auto hasInlineMarkup = (inlineReplyMarkup() != nullptr);
if (displayEditedBadge(hasViaBotId || hasInlineMarkup)) {
if (!Has<HistoryMessageEdited>()) {
AddComponents(HistoryMessageEdited::Bit());
}
auto edited = Get<HistoryMessageEdited>();
edited->create(::date(message.vedit_date), date.toString(cTimeFormat()));
if (auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->create(msgsigned->_author, edited->_edited.originalText());
}
} else if (Has<HistoryMessageEdited>()) {
RemoveComponents(HistoryMessageEdited::Bit());
if (auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->create(msgsigned->_author, date.toString(cTimeFormat()));
}
if (!Has<HistoryMessageEdited>()) {
AddComponents(HistoryMessageEdited::Bit());
}
initTime();
auto edited = Get<HistoryMessageEdited>();
edited->date = ::date(message.vedit_date);
}
TextWithEntities textWithEntities = { qs(message.vmessage), EntitiesInText() };
@@ -1381,6 +1475,22 @@ void HistoryMessage::applyEditionToEmpty() {
finishEditionToEmpty();
}
void HistoryMessage::refreshEditedBadge() {
const auto edited = displayedEditBadge();
const auto editDate = displayedEditDate();
const auto dateText = date.toString(cTimeFormat());
if (edited) {
edited->refresh(dateText, !editDate.isNull());
}
if (auto msgsigned = Get<HistoryMessageSigned>()) {
const auto text = (!edited || editDate.isNull())
? dateText
: edited->text.originalText();
msgsigned->create(msgsigned->_author, text);
}
initTime();
}
bool HistoryMessage::displayForwardedFrom() const {
if (auto forwarded = Get<HistoryMessageForwarded>()) {
if (history()->peer->isSelf()) {
@@ -1397,7 +1507,9 @@ bool HistoryMessage::displayForwardedFrom() const {
void HistoryMessage::updateMedia(const MTPMessageMedia *media) {
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) {
bool needReSet = true;
@@ -1445,12 +1557,24 @@ Storage::SharedMediaTypesMask HistoryMessage::sharedMediaTypes() const {
TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
TextWithEntities logEntryOriginalResult;
auto textResult = _text.originalTextWithEntities((selection == FullSelection) ? AllTextSelection : selection, ExpandLinksAll);
const auto textSelection = (selection == FullSelection)
? AllTextSelection
: IsSubGroupSelection(selection)
? TextSelection(0, 0)
: selection;
auto textResult = _text.originalTextWithEntities(
textSelection,
ExpandLinksAll);
auto skipped = skipTextSelection(selection);
auto mediaDisplayed = (_media && _media->isDisplayed());
auto mediaResult = mediaDisplayed ? _media->selectedText(skipped) : TextWithEntities();
auto mediaResult = (mediaDisplayed || isHiddenByGroup())
? _media->selectedText(skipped)
: TextWithEntities();
if (auto entry = Get<HistoryMessageLogEntryOriginal>()) {
logEntryOriginalResult = entry->_page->selectedText(mediaDisplayed ? _media->skipSelection(skipped) : skipped);
const auto originalSelection = mediaDisplayed
? _media->skipSelection(skipped)
: skipped;
logEntryOriginalResult = entry->_page->selectedText(originalSelection);
}
auto result = textResult;
if (result.text.isEmpty()) {
@@ -1512,6 +1636,7 @@ void HistoryMessage::setMedia(const MTPMessageMedia *media) {
_textWidth = -1;
_textHeight = 0;
}
_history->recountGroupingAround(this);
}
void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
@@ -1665,10 +1790,10 @@ void HistoryMessage::drawInfo(Painter &p, int32 right, int32 bottom, int32 width
}
dateX += HistoryMessage::timeLeft();
if (auto msgsigned = Get<HistoryMessageSigned>()) {
if (const auto msgsigned = Get<HistoryMessageSigned>()) {
msgsigned->_signature.drawElided(p, dateX, dateY, _timeWidth);
} else if (auto edited = Get<HistoryMessageEdited>()) {
edited->_edited.drawElided(p, dateX, dateY, _timeWidth);
} else if (const auto edited = displayedEditBadge()) {
edited->text.drawElided(p, dateX, dateY, _timeWidth);
} else {
p.drawText(dateX, dateY + st::msgDateFont->ascent, _timeText);
}
@@ -1804,7 +1929,9 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM
auto entry = Get<HistoryMessageLogEntryOriginal>();
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;
HistoryLayout::paintBubble(p, g, width(), selected, outbg, displayTail);
@@ -1872,7 +1999,7 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM
const auto fastShareTop = g.top() + g.height() - fastShareSkip - st::historyFastShareSize;
drawRightAction(p, fastShareLeft, fastShareTop, width());
}
} else if (_media) {
} else if (_media && _media->isDisplayed()) {
p.translate(g.topLeft());
_media->draw(p, clip.translated(-g.topLeft()), skipTextSelection(selection), ms);
p.translate(-g.topLeft());
@@ -1880,7 +2007,7 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM
p.restoreTextPalette();
auto reply = Get<HistoryMessageReply>();
const auto reply = Get<HistoryMessageReply>();
if (reply && reply->isNameUpdated()) {
const_cast<HistoryMessage*>(this)->setPendingInitDimensions();
}
@@ -2006,16 +2133,16 @@ void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) {
}
int HistoryMessage::resizeContentGetHeight() {
int result = performResizeGetHeight();
const auto result = performResizeGetHeight();
auto keyboard = inlineReplyKeyboard();
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
int oldTop = markup->oldTop;
const auto keyboard = inlineReplyKeyboard();
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
const auto oldTop = markup->oldTop;
if (oldTop >= 0) {
markup->oldTop = -1;
if (keyboard) {
int h = st::msgBotKbButton.margin + keyboard->naturalHeight();
int keyboardTop = _height - h + st::msgBotKbButton.margin - marginBottom();
const auto height = st::msgBotKbButton.margin + keyboard->naturalHeight();
const auto keyboardTop = _height - height + st::msgBotKbButton.margin - marginBottom();
if (keyboardTop != oldTop) {
Notify::inlineKeyboardMoved(this, oldTop, keyboardTop);
}
@@ -2027,7 +2154,9 @@ int HistoryMessage::resizeContentGetHeight() {
}
int HistoryMessage::performResizeGetHeight() {
if (width() < st::msgMinWidth) return _height;
if (width() < st::msgMinWidth) {
return _height;
}
auto contentWidth = width() - (st::msgMargin.left() + st::msgMargin.right());
if (history()->peer->isSelf() && !hasOutLayout()) {
@@ -2111,14 +2240,14 @@ int HistoryMessage::performResizeGetHeight() {
reply->resize(countGeometry().width() - st::msgPadding.left() - st::msgPadding.right());
_height += st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
}
} else if (_media) {
} else if (_media && _media->isDisplayed()) {
_height = _media->resizeGetHeight(contentWidth);
} else {
_height = 0;
}
if (auto keyboard = inlineReplyKeyboard()) {
auto g = countGeometry();
auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
if (const auto keyboard = inlineReplyKeyboard()) {
const auto g = countGeometry();
const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
_height += keyboardHeight;
keyboard->resize(g.width(), keyboardHeight - st::msgBotKbButton.margin);
}
@@ -2128,7 +2257,7 @@ int HistoryMessage::performResizeGetHeight() {
}
bool HistoryMessage::hasPoint(QPoint point) const {
auto g = countGeometry();
const auto g = countGeometry();
if (g.width() < 1) {
return false;
}
@@ -2161,7 +2290,7 @@ bool HistoryMessage::pointInTime(int right, int bottom, QPoint point, InfoDispla
}
HistoryTextState HistoryMessage::getState(QPoint point, HistoryStateRequest request) const {
HistoryTextState result;
auto result = HistoryTextState(this);
auto g = countGeometry();
if (g.width() < 1) {
@@ -2201,7 +2330,9 @@ HistoryTextState HistoryMessage::getState(QPoint point, HistoryStateRequest requ
auto entryLeft = g.left();
auto entryTop = trect.y() + trect.height();
if (point.y() >= entryTop && point.y() < entryTop + entryHeight) {
result = entry->_page->getState(point - QPoint(entryLeft, entryTop), request);
result = entry->_page->getState(
point - QPoint(entryLeft, entryTop),
request);
result.symbol += _text.length() + (mediaDisplayed ? _media->fullSelectionLength() : 0);
}
}
@@ -2248,7 +2379,7 @@ HistoryTextState HistoryMessage::getState(QPoint point, HistoryStateRequest requ
result.link = rightActionLink();
}
}
} else if (_media) {
} else if (_media && _media->isDisplayed()) {
result = _media->getState(point - g.topLeft(), request);
result.symbol += _text.length();
}
@@ -2278,7 +2409,7 @@ ClickHandlerPtr HistoryMessage::rightActionLink() const {
Window::SectionShow::Way::Forward,
savedFromMsgId);
} else {
FastShareMessage(item->toHistoryMessage());
FastShareMessage(item);
}
}
});
@@ -2337,7 +2468,10 @@ void HistoryMessage::updatePressed(QPoint point) {
}
}
bool HistoryMessage::getStateFromName(QPoint point, QRect &trect, HistoryTextState *outResult) const {
bool HistoryMessage::getStateFromName(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult) const {
if (displayFromName()) {
if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) {
auto user = displayFrom();
@@ -2357,7 +2491,11 @@ bool HistoryMessage::getStateFromName(QPoint point, QRect &trect, HistoryTextSta
return false;
}
bool HistoryMessage::getStateForwardedInfo(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const {
bool HistoryMessage::getStateForwardedInfo(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult,
const HistoryStateRequest &request) const {
if (displayForwardedFrom()) {
auto forwarded = Get<HistoryMessageForwarded>();
auto fwdheight = ((forwarded->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
@@ -2367,7 +2505,10 @@ bool HistoryMessage::getStateForwardedInfo(QPoint point, QRect &trect, HistoryTe
if (breakEverywhere) {
textRequest.flags |= Text::StateRequest::Flag::BreakEverywhere;
}
*outResult = forwarded->_text.getState(point - trect.topLeft(), trect.width(), textRequest);
*outResult = HistoryTextState(this, forwarded->_text.getState(
point - trect.topLeft(),
trect.width(),
textRequest));
outResult->symbol = 0;
outResult->afterSymbol = false;
if (breakEverywhere) {
@@ -2382,7 +2523,10 @@ bool HistoryMessage::getStateForwardedInfo(QPoint point, QRect &trect, HistoryTe
return false;
}
bool HistoryMessage::getStateReplyInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const {
bool HistoryMessage::getStateReplyInfo(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult) const {
if (auto reply = Get<HistoryMessageReply>()) {
int32 h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
if (point.y() >= trect.top() && point.y() < trect.top() + h) {
@@ -2396,7 +2540,10 @@ bool HistoryMessage::getStateReplyInfo(QPoint point, QRect &trect, HistoryTextSt
return false;
}
bool HistoryMessage::getStateViaBotIdInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const {
bool HistoryMessage::getStateViaBotIdInfo(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult) const {
if (!displayFromName() && !Has<HistoryMessageForwarded>()) {
if (auto via = Get<HistoryMessageVia>()) {
if (QRect(trect.x(), trect.y(), via->_width, st::msgNameFont->height).contains(point)) {
@@ -2409,9 +2556,16 @@ bool HistoryMessage::getStateViaBotIdInfo(QPoint point, QRect &trect, HistoryTex
return false;
}
bool HistoryMessage::getStateText(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const {
bool HistoryMessage::getStateText(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult,
const HistoryStateRequest &request) const {
if (trect.contains(point)) {
*outResult = _text.getState(point - trect.topLeft(), trect.width(), request.forText());
*outResult = HistoryTextState(this, _text.getState(
point - trect.topLeft(),
trect.width(),
request.forText()));
return true;
}
return false;
@@ -2453,13 +2607,18 @@ TextSelection HistoryMessage::adjustSelection(TextSelection selection, TextSelec
}
void HistoryMessage::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (_media) _media->clickHandlerActiveChanged(p, active);
HistoryItem::clickHandlerActiveChanged(p, active);
if (_media) {
_media->clickHandlerActiveChanged(p, active);
}
}
void HistoryMessage::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
if (_media) _media->clickHandlerPressedChanged(p, pressed);
HistoryItem::clickHandlerPressedChanged(p, pressed);
if (_media) {
// HistoryGroupedMedia overrides HistoryItem App::pressedLinkItem().
_media->clickHandlerPressedChanged(p, pressed);
}
}
QString HistoryMessage::notificationHeader() const {

View File

@@ -30,26 +30,119 @@ void FastShareMessage(not_null<HistoryItem*> item);
class HistoryMessage : public HistoryItem, private HistoryItemInstantiated<HistoryMessage> {
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);
}
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);
}
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);
}
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) {
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,
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) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, postAuthor, doc, 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,
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) {
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,
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) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, postAuthor, game, 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,
not_null<GameData*> game,
const MTPReplyMarkup &markup) {
return _create(
history,
msgId,
flags,
replyTo,
viaBotId,
date,
from,
postAuthor,
game,
markup);
}
void initTime();
@@ -70,7 +163,7 @@ public:
if (isAttachedToPrevious()) return false;
return true;
}
bool displayEditedBadge(bool hasViaBotOrInlineMarkup) const;
bool displayForwardedFrom() const;
bool uploading() const;
bool displayRightAction() const override;
@@ -116,6 +209,9 @@ public:
TextWithEntities originalText() const override;
bool textHasLinks() const override;
bool displayEditedBadge() const override;
QDateTime displayedEditDate() const override;
int infoWidth() const override;
int timeLeft() const override;
int timeWidth() const override {
@@ -155,14 +251,69 @@ public:
~HistoryMessage();
protected:
void refreshEditedBadge() override;
private:
HistoryMessage(not_null<History*> history, const MTPDmessage &msg);
HistoryMessage(not_null<History*> history, const MTPDmessageService &msg);
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, DocumentData *doc, 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, 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, GameData *game, const MTPReplyMarkup &markup); // local game
HistoryMessage(
not_null<History*> history,
const MTPDmessage &msg);
HistoryMessage(
not_null<History*> history,
const MTPDmessageService &msg);
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>;
void setEmptyText();
@@ -175,8 +326,10 @@ private:
int resizeContentGetHeight() override;
int performResizeGetHeight();
void applyEditionToEmpty();
QDateTime displayedEditDate(bool hasViaBotOrInlineMarkup) const;
const HistoryMessageEdited *displayedEditBadge() const;
HistoryMessageEdited *displayedEditBadge();
bool displayForwardedFrom() const;
void paintFromName(Painter &p, QRect &trect, bool selected) const;
void paintForwardedInfo(Painter &p, QRect &trect, bool selected) const;
void paintReplyInfo(Painter &p, QRect &trect, bool selected) const;
@@ -184,11 +337,28 @@ private:
void paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const;
void paintText(Painter &p, QRect &trect, TextSelection selection) const;
bool getStateFromName(QPoint point, QRect &trect, HistoryTextState *outResult) const;
bool getStateForwardedInfo(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const;
bool getStateReplyInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const;
bool getStateViaBotIdInfo(QPoint point, QRect &trect, HistoryTextState *outResult) const;
bool getStateText(QPoint point, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const;
bool getStateFromName(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult) const;
bool getStateForwardedInfo(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult,
const HistoryStateRequest &request) const;
bool getStateReplyInfo(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult) const;
bool getStateViaBotIdInfo(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult) const;
bool getStateText(
QPoint point,
QRect &trect,
not_null<HistoryTextState*> outResult,
const HistoryStateRequest &request) const;
void setMedia(const MTPMessageMedia *media);
void setReplyMarkup(const MTPReplyMarkup *markup);
@@ -214,6 +384,7 @@ private:
QString authorOriginal;
QDateTime originalDate;
QDateTime editDate;
MessageGroupId groupId = MessageGroupId::None;
// For messages created from MTP structs.
const MTPReplyMarkup *mtpMarkup = nullptr;

View File

@@ -286,6 +286,9 @@ HistoryService::PreparedText HistoryService::preparePinnedText() {
switch (media ? media->type() : MediaTypeCount) {
case MediaTypePhoto: return lang(lng_action_pinned_media_photo);
case MediaTypeVideo: return lang(lng_action_pinned_media_video);
case MediaTypeGrouped: return lang(media->getPhoto()
? lng_action_pinned_media_photo
: lng_action_pinned_media_video);
case MediaTypeContact: return lang(lng_action_pinned_media_contact);
case MediaTypeFile: return lang(lng_action_pinned_media_file);
case MediaTypeGif: {
@@ -594,7 +597,7 @@ bool HistoryService::hasPoint(QPoint point) const {
}
HistoryTextState HistoryService::getState(QPoint point, HistoryStateRequest request) const {
HistoryTextState result;
auto result = HistoryTextState(this);
auto g = countGeometry();
if (g.width() < 1) {
@@ -618,7 +621,10 @@ HistoryTextState HistoryService::getState(QPoint point, HistoryStateRequest requ
if (trect.contains(point)) {
auto textRequest = request.forText();
textRequest.align = style::al_center;
result = _text.getState(point - trect.topLeft(), trect.width(), textRequest);
result = HistoryTextState(this, _text.getState(
point - trect.topLeft(),
trect.width(),
textRequest));
if (auto gamescore = Get<HistoryServiceGameScore>()) {
if (!result.link && result.cursor == HistoryInTextCursorState && g.contains(point)) {
result.link = gamescore->lnk;

View File

@@ -782,6 +782,9 @@ void HistoryWidget::scrollToAnimationCallback(FullMsgId attachToId) {
}
void HistoryWidget::enqueueMessageHighlight(not_null<HistoryItem*> item) {
if (const auto group = item->getFullGroup()) {
item = group->leader;
}
auto enqueueMessageId = [this](MsgId universalId) {
if (_highlightQueue.empty() && !_highlightTimer.isActive()) {
highlightMessage(universalId);
@@ -884,6 +887,9 @@ void HistoryWidget::clearHighlightMessages() {
}
int HistoryWidget::itemTopForHighlight(not_null<HistoryItem*> item) const {
if (const auto group = item->getFullGroup()) {
item = group->leader;
}
auto itemTop = _list->itemTop(item);
Assert(itemTop >= 0);
@@ -3735,20 +3741,6 @@ void HistoryWidget::onCmdStart() {
setFieldText({ qsl("/"), TextWithTags::Tags() }, 0, Ui::FlatTextarea::AddToUndoHistory);
}
void HistoryWidget::forwardMessage() {
auto item = App::contextItem();
if (!item || item->id < 0 || item->serviceMsg()) return;
Window::ShowForwardMessagesBox({ 1, item->fullId() });
}
void HistoryWidget::selectMessage() {
auto item = App::contextItem();
if (!item || item->id < 0 || item->serviceMsg()) return;
if (_list) _list->selectItem(item);
}
void HistoryWidget::setMembersShowAreaActive(bool active) {
if (!active) {
_membersDropdownShowTimer.stop();
@@ -4521,7 +4513,9 @@ void HistoryWidget::onThumbDocumentUploaded(
void HistoryWidget::onPhotoProgress(const FullMsgId &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()->getPhoto()
: nullptr;
updateSendAction(item->history(), SendAction::Type::UploadPhoto, 0);
Auth().data().requestItemRepaint(item);
}
@@ -6196,19 +6190,6 @@ void HistoryWidget::onForwardSelected() {
});
}
void HistoryWidget::confirmDeleteContextItem() {
auto item = App::contextItem();
if (!item) return;
if (auto message = item->toHistoryMessage()) {
if (message->uploading()) {
App::main()->cancelUploadLayer();
return;
}
}
App::main()->deleteLayer();
}
void HistoryWidget::confirmDeleteSelectedItems() {
if (!_list) return;
@@ -6218,29 +6199,6 @@ void HistoryWidget::confirmDeleteSelectedItems() {
App::main()->deleteLayer(int(selected.size()));
}
void HistoryWidget::deleteContextItem(bool forEveryone) {
Ui::hideLayer();
auto item = App::contextItem();
if (!item) {
return;
}
auto toDelete = QVector<MTPint>(1, MTP_int(item->id));
auto history = item->history();
auto wasOnServer = (item->id > 0);
auto wasLast = (history->lastMsg == item);
item->destroy();
if (!wasOnServer && wasLast && !history->lastMsg) {
App::main()->checkPeerHistory(history->peer);
}
if (wasOnServer) {
App::main()->deleteMessages(history->peer, toDelete, forEveryone);
}
}
void HistoryWidget::deleteSelectedItems(bool forEveryone) {
Ui::hideLayer();
if (!_list) return;

View File

@@ -325,9 +325,7 @@ public:
bool isItemVisible(HistoryItem *item);
void confirmDeleteContextItem();
void confirmDeleteSelectedItems();
void deleteContextItem(bool forEveryone);
void deleteSelectedItems(bool forEveryone);
// Float player interface.
@@ -414,9 +412,6 @@ public slots:
void onWindowVisibleChanged();
void forwardMessage();
void selectMessage();
void onFieldFocused();
void onFieldResize();
void onCheckFieldAutocomplete();

View File

@@ -847,10 +847,8 @@ std::unique_ptr<BaseLayout> ListWidget::createLayout(
return nullptr;
}
auto getPhoto = [&]() -> PhotoData* {
if (auto media = item->getMedia()) {
if (media->type() == MediaTypePhoto) {
return static_cast<HistoryPhoto*>(media)->photo();
}
if (const auto media = item->getMedia()) {
return media->getPhoto();
}
return nullptr;
};

View File

@@ -218,9 +218,9 @@ HistoryTextState Gif::getState(
HistoryStateRequest request) const {
if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
if (_delete && rtlpoint(point, _width).x() >= _width - st::stickerPanDeleteIconBg.width() && point.y() < st::stickerPanDeleteIconBg.height()) {
return _delete;
return { nullptr, _delete };
} else {
return _send;
return { nullptr, _send };
}
}
return {};
@@ -411,7 +411,7 @@ HistoryTextState Sticker::getState(
QPoint point,
HistoryStateRequest request) const {
if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
return _send;
return { nullptr, _send };
}
return {};
}
@@ -501,7 +501,7 @@ HistoryTextState Photo::getState(
QPoint point,
HistoryStateRequest request) const {
if (QRect(0, 0, _width, st::inlineMediaHeight).contains(point)) {
return _send;
return { nullptr, _send };
}
return {};
}
@@ -646,10 +646,10 @@ HistoryTextState Video::getState(
QPoint point,
HistoryStateRequest request) const {
if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
return _link;
return { nullptr, _link };
}
if (QRect(st::inlineThumbSize + st::inlineThumbSkip, 0, _width - st::inlineThumbSize - st::inlineThumbSkip, _height).contains(point)) {
return _send;
return { nullptr, _send };
}
return {};
}
@@ -787,11 +787,11 @@ HistoryTextState File::getState(
QPoint point,
HistoryStateRequest request) const {
if (QRect(0, st::inlineRowMargin, st::msgFileSize, st::msgFileSize).contains(point)) {
return getShownDocument()->loading() ? _cancel : _open;
return { nullptr, getShownDocument()->loading() ? _cancel : _open };
} else {
auto left = st::msgFileSize + st::inlineThumbSkip;
if (QRect(left, 0, _width - left, _height).contains(point)) {
return _send;
return { nullptr, _send };
}
}
return {};
@@ -955,7 +955,7 @@ HistoryTextState Contact::getState(
if (!QRect(0, st::inlineRowMargin, st::msgFileSize, st::inlineThumbSize).contains(point)) {
auto left = (st::msgFileSize + st::inlineThumbSkip);
if (QRect(left, 0, _width - left, _height).contains(point)) {
return _send;
return { nullptr, _send };
}
}
return {};
@@ -1090,7 +1090,7 @@ HistoryTextState Article::getState(
QPoint point,
HistoryStateRequest request) const {
if (_withThumb && QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
return _link;
return { nullptr, _link };
}
auto left = _withThumb ? (st::inlineThumbSize + st::inlineThumbSkip) : 0;
if (QRect(left, 0, _width - left, _height).contains(point)) {
@@ -1100,10 +1100,10 @@ HistoryTextState Article::getState(
auto descriptionLines = 2;
auto descriptionHeight = qMin(_description.countHeight(_width - left), st::normalFont->height * descriptionLines);
if (rtlrect(left, st::inlineRowMargin + titleHeight + descriptionHeight, _urlWidth, st::normalFont->height, _width).contains(point)) {
return _url;
return { nullptr, _url };
}
}
return _send;
return { nullptr, _send };
}
return {};
}
@@ -1275,23 +1275,23 @@ HistoryTextState Game::getState(
HistoryStateRequest request) const {
int left = st::inlineThumbSize + st::inlineThumbSkip;
if (QRect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize).contains(point)) {
return _send;
return { nullptr, _send };
}
if (QRect(left, 0, _width - left, _height).contains(point)) {
return _send;
return { nullptr, _send };
}
return {};
}
void Game::prepareThumb(int width, int height) const {
auto thumb = ([this]() {
auto thumb = [this] {
if (auto photo = getResultPhoto()) {
return photo->medium;
} else if (auto document = getResultDocument()) {
return document->thumb;
}
return ImagePtr();
})();
}();
if (thumb->isNull()) {
return;
}

View File

@@ -24,6 +24,40 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
constexpr auto FullSelection = TextSelection { 0xFFFF, 0xFFFF };
inline bool IsSubGroupSelection(TextSelection selection) {
return (selection.from == 0xFFFF) && (selection.to != 0xFFFF);
}
inline bool IsGroupItemSelection(
TextSelection selection,
int index) {
Expects(index >= 0 && index < 0x0F);
return IsSubGroupSelection(selection) && (selection.to & (1 << index));
}
[[nodiscard]] inline TextSelection AddGroupItemSelection(
TextSelection selection,
int index) {
Expects(index >= 0 && index < 0x0F);
const auto bit = uint16(1U << index);
return TextSelection(
0xFFFF,
IsSubGroupSelection(selection) ? (selection.to | bit) : bit);
}
[[nodiscard]] inline TextSelection RemoveGroupItemSelection(
TextSelection selection,
int index) {
Expects(index >= 0 && index < 0x0F);
const auto bit = uint16(1U << index);
return IsSubGroupSelection(selection)
? TextSelection(0xFFFF, selection.to & ~bit)
: selection;
}
extern TextParseOptions _textNameOptions, _textDlgOptions;
extern TextParseOptions _historyTextOptions, _historyBotOptions, _historyTextNoMonoOptions, _historyBotNoMonoOptions;

View File

@@ -610,6 +610,11 @@ bool MainWidget::setForwardDraft(PeerId peerId, ForwardWhatMessages what) {
item = App::contextItem();
} else if (what == ForwardPressedMessage) {
item = App::pressedItem();
if (const auto group = item ? item->getFullGroup() : nullptr) {
if (item->id > 0) {
return Auth().data().groupToIds(group);
}
}
} else if (what == ForwardPressedLinkMessage) {
item = App::pressedLinkItem();
}
@@ -965,9 +970,18 @@ void MainWidget::cancelUploadLayer() {
return;
}
Auth().uploader().pause(item->fullId());
Ui::show(Box<ConfirmBox>(lang(lng_selected_cancel_sure_this), lang(lng_selected_upload_stop), lang(lng_continue), base::lambda_guarded(this, [this] {
_history->deleteContextItem(false);
const auto itemId = item->fullId();
Auth().uploader().pause(itemId);
Ui::show(Box<ConfirmBox>(lang(lng_selected_cancel_sure_this), lang(lng_selected_upload_stop), lang(lng_continue), base::lambda_guarded(this, [=] {
Ui::hideLayer();
if (const auto item = App::histItemById(itemId)) {
const auto history = item->history();
const auto wasLast = (history->lastMsg == item);
item->destroy();
if (wasLast && !history->lastMsg) {
checkPeerHistory(history->peer);
}
}
Auth().uploader().unpause();
}), base::lambda_guarded(this, [] {
Auth().uploader().unpause();
@@ -3680,31 +3694,26 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha
// feed messages and groups, copy from App::feedMsgs
auto h = App::history(channel->id);
auto &vmsgs = d.vnew_messages.v;
QMap<uint64, int> msgsIds;
for (int i = 0, l = vmsgs.size(); i < l; ++i) {
auto &msg = vmsgs[i];
switch (msg.type()) {
case mtpc_message: {
const auto &d(msg.c_message());
if (App::checkEntitiesAndViewsUpdate(d)) { // new message, index my forwarded messages to links _overview, already in blocks
auto indices = base::flat_map<uint64, int>();
for (auto i = 0, l = vmsgs.size(); i != l; ++i) {
const auto &msg = vmsgs[i];
if (msg.type() == mtpc_message) {
const auto &data = msg.c_message();
if (App::checkEntitiesAndViewsUpdate(data)) { // new message, index my forwarded messages to links _overview, already in blocks
LOG(("Skipping message, because it is already in blocks!"));
} else {
msgsIds.insert((uint64(uint32(d.vid.v)) << 32) | uint64(i), i + 1);
continue;
}
} 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;
}
const auto msgId = idFromMessage(msg);
indices.emplace((uint64(uint32(msgId)) << 32) | uint64(i), i);
}
for_const (auto msgIndex, msgsIds) {
if (msgIndex > 0) { // add message
auto &msg = vmsgs.at(msgIndex - 1);
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)));
continue; // wtf
}
h->addNewMessage(msg, NewMessageUnread);
for (const auto [position, index] : indices) {
const auto &msg = vmsgs[index];
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)));
continue; // wtf
}
h->addNewMessage(msg, NewMessageUnread);
}
feedUpdateVector(d.vother_updates, true);

View File

@@ -1266,8 +1266,9 @@ void MediaView::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
_zoom = 0;
_caption = Text();
if (auto itemMsg = item ? item->toHistoryMessage() : nullptr) {
if (auto photoMsg = dynamic_cast<HistoryPhoto*>(itemMsg->getMedia())) {
if (const auto media = item ? item->getMedia() : nullptr) {
const auto caption = media->getCaption();
if (!caption.text.isEmpty()) {
auto asBot = (item->author()->isUser()
&& item->author()->asUser()->botInfo);
auto skipw = qMax(_dateNav.left() + _dateNav.width(), _headerNav.left() + _headerNav.width());
@@ -1275,7 +1276,7 @@ void MediaView::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
_caption = Text(maxw);
_caption.setMarkedText(
st::mediaviewCaptionStyle,
photoMsg->getCaption(),
caption,
itemTextOptions(item));
}
}
@@ -2254,21 +2255,16 @@ MediaView::Entity MediaView::entityForSharedMedia(int index) const {
return { base::none, nullptr };
}
auto value = (*_sharedMediaData)[index];
if (auto photo = base::get_if<not_null<PhotoData*>>(&value)) {
if (const auto photo = base::get_if<not_null<PhotoData*>>(&value)) {
// Last peer photo.
return { *photo, nullptr };
} else if (auto itemId = base::get_if<FullMsgId>(&value)) {
if (auto item = App::histItemById(*itemId)) {
if (auto media = item->getMedia()) {
switch (media->type()) {
case MediaTypePhoto: return {
static_cast<HistoryPhoto*>(item->getMedia())->photo(),
item
};
case MediaTypeFile:
case MediaTypeVideo:
case MediaTypeGif:
case MediaTypeSticker: return { media->getDocument(), item };
} else if (const auto itemId = base::get_if<FullMsgId>(&value)) {
if (const auto item = App::histItemById(*itemId)) {
if (const auto media = item->getMedia()) {
if (const auto photo = media->getPhoto()) {
return { photo, item };
} else if (const auto document = media->getDocument()) {
return { document, item };
}
}
return { base::none, item };

View File

@@ -201,9 +201,11 @@ bool Messenger::hideMediaView() {
return false;
}
void Messenger::showPhoto(not_null<const PhotoOpenClickHandler*> link, HistoryItem *item) {
return (!item && link->peer())
? showPhoto(link->photo(), link->peer())
void Messenger::showPhoto(not_null<const PhotoOpenClickHandler*> link) {
const auto item = App::histItemById(link->context());
const auto peer = link->peer();
return (!item && peer)
? showPhoto(link->photo(), peer)
: showPhoto(link->photo(), item);
}
@@ -214,7 +216,9 @@ void Messenger::showPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
_mediaView->setFocus();
}
void Messenger::showPhoto(not_null<PhotoData*> photo, PeerData *peer) {
void Messenger::showPhoto(
not_null<PhotoData*> photo,
not_null<PeerData*> peer) {
if (_mediaView->isHidden()) Ui::hideLayer(anim::type::instant);
_mediaView->showPhoto(photo, peer);
_mediaView->activateWindow();

View File

@@ -86,9 +86,9 @@ public:
// MediaView interface.
void checkMediaViewActivation();
bool hideMediaView();
void showPhoto(not_null<const PhotoOpenClickHandler*> link, HistoryItem *item = nullptr);
void showPhoto(not_null<const PhotoOpenClickHandler*> link);
void showPhoto(not_null<PhotoData*> photo, HistoryItem *item);
void showPhoto(not_null<PhotoData*> photo, PeerData *item);
void showPhoto(not_null<PhotoData*> photo, not_null<PeerData*> item);
void showDocument(not_null<DocumentData*> document, HistoryItem *item);
PeerData *ui_getPeerForMouseAction();

View File

@@ -81,8 +81,11 @@ enum class MTPDmessage_ClientFlag : uint32 {
// message has an admin badge in supergroup
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
MIN_FIELD = (1U << 20),
MIN_FIELD = (1U << 19),
};
DEFINE_MTP_CLIENT_FLAGS(MTPDmessage)

View File

@@ -283,7 +283,7 @@ Photo::Photo(
not_null<PhotoData*> photo)
: ItemBase(parent)
, _data(photo)
, _link(MakeShared<PhotoOpenClickHandler>(photo)) {
, _link(MakeShared<PhotoOpenClickHandler>(photo, parent->fullId())) {
}
void Photo::initDimensions() {
@@ -352,7 +352,7 @@ HistoryTextState Photo::getState(
QPoint point,
HistoryStateRequest request) const {
if (hasPoint(point)) {
return _link;
return { parent(), _link };
}
return {};
}
@@ -508,7 +508,12 @@ HistoryTextState Video::getState(
bool loaded = _data->loaded();
if (hasPoint(point)) {
return loaded ? _openl : (_data->loading() ? _cancell : _savel);
const auto link = loaded
? _openl
: _data->loading()
? _cancell
: _savel;
return { parent(), link };
}
return {};
}
@@ -687,13 +692,14 @@ HistoryTextState Voice::getState(
_st.songThumbSize,
_width);
if (inner.contains(point)) {
return loaded
const auto link = loaded
? _openl
: ((_data->loading() || _data->status == FileUploading)
? _cancell
: _openl);
: (_data->loading() || _data->status == FileUploading)
? _cancell
: _openl;
return { parent(), link };
}
auto result = HistoryTextState();
auto result = HistoryTextState(parent());
const auto statusmaxwidth = _width - nameleft - nameright;
const auto statusrect = rtlrect(
nameleft,
@@ -718,7 +724,7 @@ HistoryTextState Voice::getState(
st::normalFont->height,
_width);
if (namerect.contains(point) && !result.link && !_data->loading()) {
return _namel;
return { parent(), _namel };
}
return result;
}
@@ -1014,11 +1020,12 @@ HistoryTextState Document::getState(
_st.songThumbSize,
_width);
if (inner.contains(point)) {
return loaded
const auto link = loaded
? _openl
: ((_data->loading() || _data->status == FileUploading)
? _cancell
: _openl);
: (_data->loading() || _data->status == FileUploading)
? _cancell
: _openl;
return { parent(), link };
}
const auto namerect = rtlrect(
nameleft,
@@ -1027,7 +1034,7 @@ HistoryTextState Document::getState(
st::semiboldFont->height,
_width);
if (namerect.contains(point) && !_data->loading()) {
return _namel;
return { parent(), _namel };
}
} else {
const auto nameleft = _st.fileThumbSize + _st.filePadding.right();
@@ -1047,11 +1054,12 @@ HistoryTextState Document::getState(
_width);
if (rthumb.contains(point)) {
return loaded
const auto link = loaded
? _openl
: ((_data->loading() || _data->status == FileUploading)
? _cancell
: _savel);
: (_data->loading() || _data->status == FileUploading)
? _cancell
: _savel;
return { parent(), link };
}
if (_data->status != FileUploadFailed) {
@@ -1062,7 +1070,7 @@ HistoryTextState Document::getState(
st::normalFont->height,
_width);
if (daterect.contains(point)) {
return _msgl;
return { parent(), _msgl };
}
}
if (!_data->loading() && _data->isValid()) {
@@ -1073,7 +1081,7 @@ HistoryTextState Document::getState(
_height - st::linksBorder,
_width);
if (loaded && leftofnamerect.contains(point)) {
return _namel;
return { parent(), _namel };
}
const auto namerect = rtlrect(
nameleft,
@@ -1082,7 +1090,7 @@ HistoryTextState Document::getState(
st::semiboldFont->height,
_width);
if (namerect.contains(point)) {
return _namel;
return { parent(), _namel };
}
}
}
@@ -1201,7 +1209,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) {
mainUrl = _page->url;
if (_page->document) {
@@ -1210,7 +1220,9 @@ Link::Link(
if (_page->type == WebPageProfile || _page->type == WebPageVideo) {
_photol = MakeShared<UrlClickHandler>(_page->url);
} else if (_page->type == WebPagePhoto || _page->siteName == qstr("Twitter") || _page->siteName == qstr("Facebook")) {
_photol = MakeShared<PhotoOpenClickHandler>(_page->photo);
_photol = MakeShared<PhotoOpenClickHandler>(
_page->photo,
parent->fullId());
} else {
_photol = MakeShared<UrlClickHandler>(_page->url);
}
@@ -1413,7 +1425,7 @@ HistoryTextState Link::getState(
HistoryStateRequest request) const {
int32 left = st::linksPhotoSize + st::linksPhotoPadding, top = st::linksMargin.top() + st::linksBorder, w = _width - left;
if (rtlrect(0, top, st::linksPhotoSize, st::linksPhotoSize, _width).contains(point)) {
return _photol;
return { parent(), _photol };
}
if (!_title.isEmpty() && _text.isEmpty() && _links.size() == 1) {
@@ -1421,7 +1433,7 @@ HistoryTextState Link::getState(
}
if (!_title.isEmpty()) {
if (rtlrect(left, top, qMin(w, _titlew), st::semiboldFont->height, _width).contains(point)) {
return _photol;
return { parent(), _photol };
}
top += st::webPageTitleFont->height;
}
@@ -1430,7 +1442,7 @@ HistoryTextState Link::getState(
}
for (int32 i = 0, l = _links.size(); i < l; ++i) {
if (rtlrect(left, top, qMin(w, _links.at(i).width), st::normalFont->height, _width).contains(point)) {
return ClickHandlerPtr(_links[i].lnk);
return { parent(), ClickHandlerPtr(_links[i].lnk) };
}
top += st::normalFont->height;
}

View File

@@ -94,6 +94,8 @@ CoverWidget::CoverWidget(QWidget *parent, UserData *self)
}
PhotoData *CoverWidget::validatePhoto() const {
Expects(_self != nullptr);
const auto photo = _self->userpicPhotoId()
? App::photo(_self->userpicPhotoId())
: nullptr;
@@ -106,7 +108,7 @@ PhotoData *CoverWidget::validatePhoto() const {
}
void CoverWidget::showPhoto() {
if (auto photo = validatePhoto()) {
if (const auto photo = validatePhoto()) {
Messenger::Instance().showPhoto(photo, _self);
}
}

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
AppVersion 1002001
AppVersion 1002002
AppVersionStrMajor 1.2
AppVersionStrSmall 1.2.1
AppVersionStr 1.2.1
AlphaChannel 0
AppVersionStrSmall 1.2.2
AppVersionStr 1.2.2
AlphaChannel 1
BetaVersion 0

View File

@@ -219,6 +219,8 @@
<(src_loc)/history/history_location_manager.h
<(src_loc)/history/history_media.h
<(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.h
<(src_loc)/history/history_message.cpp
@@ -607,6 +609,8 @@
<(src_loc)/ui/empty_userpic.cpp
<(src_loc)/ui/empty_userpic.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.h
<(src_loc)/ui/resize_area.h

View File

@@ -1,3 +1,7 @@
1.2.2 alpha (16.12.17)
- Grouped photos and videos are displayed as albums.
1.2.1 (12.12.17)
- Bug fixes and other minor improvements.