Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de16a66a4a | ||
|
|
b0f191515a | ||
|
|
89ccaccb88 | ||
|
|
1f070da202 | ||
|
|
963e969d2a | ||
|
|
4734700ac5 | ||
|
|
d9da2edd7c | ||
|
|
6d48ca850e | ||
|
|
3e7ac7eb26 | ||
|
|
520a644150 | ||
|
|
3a56b7cabd | ||
|
|
efa72578cd | ||
|
|
b6087ce7ce | ||
|
|
537400d8b2 | ||
|
|
4c9931ab02 |
@@ -936,6 +936,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_stickers_group_from_featured" = "Choose from trending stickers";
|
||||
|
||||
"lng_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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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; };
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
454
Telegram/SourceFiles/history/history_media_grouped.cpp
Normal file
454
Telegram/SourceFiles/history/history_media_grouped.cpp
Normal 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;
|
||||
}
|
||||
145
Telegram/SourceFiles/history/history_media_grouped.h
Normal file
145
Telegram/SourceFiles/history/history_media_grouped.h
Normal 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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
589
Telegram/SourceFiles/ui/grouped_layout.cpp
Normal file
589
Telegram/SourceFiles/ui/grouped_layout.cpp
Normal 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
|
||||
36
Telegram/SourceFiles/ui/grouped_layout.h
Normal file
36
Telegram/SourceFiles/ui/grouped_layout.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user