Merge branch 'game_media' into player
Conflicts: Telegram/SourceFiles/application.cpp Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp Telegram/SourceFiles/mainwidget.cpp Telegram/SourceFiles/mainwidget.h Telegram/SourceFiles/structs.h
This commit is contained in:
commit
903795d0e5
|
@ -47,6 +47,20 @@ index 14e4fd1..c31c62b 100644
|
|||
{ 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 14, 9, 11, 11 },
|
||||
{ 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 14, 9, 11, 11 },
|
||||
{ 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 14, 9, 11, 11 },
|
||||
diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp
|
||||
index b0ef2a2..7d5f7bc 100644
|
||||
--- a/src/gui/kernel/qhighdpiscaling.cpp
|
||||
+++ b/src/gui/kernel/qhighdpiscaling.cpp
|
||||
@@ -51,6 +51,9 @@ static const char screenFactorsEnvVar[] = "QT_SCREEN_SCALE_FACTORS";
|
||||
|
||||
static inline qreal initialGlobalScaleFactor()
|
||||
{
|
||||
+ // Disable environment variable dpi scaling changing.
|
||||
+ // It is not supported by Telegram Desktop :(
|
||||
+ return 1.;
|
||||
|
||||
qreal result = 1;
|
||||
if (qEnvironmentVariableIsSet(scaleFactorEnvVar)) {
|
||||
diff --git a/src/gui/kernel/qplatformdialoghelper.h b/src/gui/kernel/qplatformdialoghelper.h
|
||||
index 5b2f4ec..346a26f 100644
|
||||
--- a/src/gui/kernel/qplatformdialoghelper.h
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 177 KiB |
Binary file not shown.
Before Width: | Height: | Size: 241 KiB After Width: | Height: | Size: 241 KiB |
|
@ -1234,7 +1234,7 @@ introErrLabelTextStyle: textStyle(defaultTextStyle) {
|
|||
|
||||
mediaPadding: margins(0px, 0px, 0px, 0px);//1px, 1px, 1px, 1px);//2px, 2px, 2px, 2px);
|
||||
mediaCaptionSkip: 5px;
|
||||
mediaHeaderSkip: 5px;
|
||||
mediaInBubbleSkip: 5px;
|
||||
mediaThumbSize: 48px;
|
||||
mediaNameTop: 3px;
|
||||
mediaDetailsShift: 3px;
|
||||
|
@ -2287,7 +2287,6 @@ webPageLeft: 10px;
|
|||
webPageBar: 2px;
|
||||
webPageTitleFont: semiboldFont;
|
||||
webPageDescriptionFont: normalFont;
|
||||
webPagePhotoSkip: 5px;
|
||||
webPagePhotoSize: 100px;
|
||||
webPagePhotoDelta: 8px;
|
||||
|
||||
|
|
|
@ -580,6 +580,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_action_pinned_media_contact" = "a contact information";
|
||||
"lng_action_pinned_media_location" = "a location mark";
|
||||
"lng_action_pinned_media_sticker" = "a sticker";
|
||||
"lng_action_pinned_media_emoji_sticker" = "a {emoji} sticker";
|
||||
"lng_action_pinned_media_game" = "a game «{game}»";
|
||||
"lng_action_game_score" = "{from} scored {count:#|#|#} in {game}";
|
||||
|
||||
"lng_profile_migrate_reached" = "{count:_not_used_|# member|# members} limit reached";
|
||||
|
@ -774,6 +776,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_bot_groups_not_found" = "No groups found";
|
||||
"lng_bot_sure_invite" = "Add the bot to «{group}»?";
|
||||
"lng_bot_already_in_group" = "The bot is already a member of the group.";
|
||||
"lng_bot_choose_chat" = "Choose Chat";
|
||||
"lng_bot_no_chats" = "You have no chats";
|
||||
"lng_bot_chats_not_found" = "No chats found";
|
||||
"lng_bot_sure_share_game" = "Share the game with {user}?";
|
||||
"lng_bot_sure_share_game_group" = "Share the game with «{group}»?";
|
||||
|
||||
"lng_typing" = "typing";
|
||||
"lng_user_typing" = "{user} is typing";
|
||||
|
@ -808,6 +815,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_duration_played" = "{played} / {duration}";
|
||||
"lng_date_and_duration" = "{date}, {duration}";
|
||||
"lng_choose_images" = "Choose images";
|
||||
"lng_game_tag" = "Game";
|
||||
|
||||
"lng_context_view_profile" = "View profile";
|
||||
"lng_context_view_group" = "View group info";
|
||||
|
|
|
@ -1046,26 +1046,25 @@ void ApiWrap::clearWebPageRequests() {
|
|||
|
||||
void ApiWrap::resolveWebPages() {
|
||||
MessageIds ids; // temp_req_id = -1
|
||||
typedef QPair<int32, MessageIds> IndexAndMessageIds;
|
||||
typedef QMap<ChannelData*, IndexAndMessageIds> MessageIdsByChannel;
|
||||
using IndexAndMessageIds = QPair<int32, MessageIds>;
|
||||
using MessageIdsByChannel = QMap<ChannelData*, IndexAndMessageIds>;
|
||||
MessageIdsByChannel idsByChannel; // temp_req_id = -index - 2
|
||||
|
||||
const WebPageItems &items(App::webPageItems());
|
||||
auto &items = App::webPageItems();
|
||||
ids.reserve(_webPagesPending.size());
|
||||
int32 t = unixtime(), m = INT_MAX;
|
||||
for (WebPagesPending::iterator i = _webPagesPending.begin(); i != _webPagesPending.cend(); ++i) {
|
||||
for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend(); ++i) {
|
||||
if (i.value() > 0) continue;
|
||||
if (i.key()->pendingTill <= t) {
|
||||
WebPageItems::const_iterator j = items.constFind(i.key());
|
||||
auto j = items.constFind(i.key());
|
||||
if (j != items.cend() && !j.value().isEmpty()) {
|
||||
for (HistoryItemsMap::const_iterator it = j.value().cbegin(); it != j.value().cend(); ++it) {
|
||||
HistoryItem *item = j.value().begin().key();
|
||||
for_const (auto item, j.value()) {
|
||||
if (item->id > 0) {
|
||||
if (item->channelId() == NoChannel) {
|
||||
ids.push_back(MTP_int(item->id));
|
||||
i.value() = -1;
|
||||
} else {
|
||||
ChannelData *channel = item->history()->peer->asChannel();
|
||||
auto channel = item->history()->peer->asChannel();
|
||||
MessageIdsByChannel::iterator channelMap = idsByChannel.find(channel);
|
||||
if (channelMap == idsByChannel.cend()) {
|
||||
channelMap = idsByChannel.insert(channel, IndexAndMessageIds(idsByChannel.size(), MessageIds(1, MTP_int(item->id))));
|
||||
|
@ -1083,20 +1082,20 @@ void ApiWrap::resolveWebPages() {
|
|||
}
|
||||
}
|
||||
|
||||
mtpRequestId req = ids.isEmpty() ? 0 : MTP::send(MTPmessages_GetMessages(MTP_vector<MTPint>(ids)), rpcDone(&ApiWrap::gotWebPages, (ChannelData*)0), RPCFailHandlerPtr(), 0, 5);
|
||||
typedef QVector<mtpRequestId> RequestIds;
|
||||
mtpRequestId req = ids.isEmpty() ? 0 : MTP::send(MTPmessages_GetMessages(MTP_vector<MTPint>(ids)), rpcDone(&ApiWrap::gotWebPages, (ChannelData*)nullptr), RPCFailHandlerPtr(), 0, 5);
|
||||
using RequestIds = QVector<mtpRequestId>;
|
||||
RequestIds reqsByIndex(idsByChannel.size(), 0);
|
||||
for (MessageIdsByChannel::const_iterator i = idsByChannel.cbegin(), e = idsByChannel.cend(); i != e; ++i) {
|
||||
for (auto i = idsByChannel.cbegin(), e = idsByChannel.cend(); i != e; ++i) {
|
||||
reqsByIndex[i.value().first] = MTP::send(MTPchannels_GetMessages(i.key()->inputChannel, MTP_vector<MTPint>(i.value().second)), rpcDone(&ApiWrap::gotWebPages, i.key()), RPCFailHandlerPtr(), 0, 5);
|
||||
}
|
||||
if (req || !reqsByIndex.isEmpty()) {
|
||||
for (WebPagesPending::iterator i = _webPagesPending.begin(); i != _webPagesPending.cend(); ++i) {
|
||||
if (i.value() > 0) continue;
|
||||
if (i.value() < 0) {
|
||||
if (i.value() == -1) {
|
||||
i.value() = req;
|
||||
for (auto &requestId : _webPagesPending) {
|
||||
if (requestId > 0) continue;
|
||||
if (requestId < 0) {
|
||||
if (requestId == -1) {
|
||||
requestId = req;
|
||||
} else {
|
||||
i.value() = reqsByIndex[-i.value() - 2];
|
||||
requestId = reqsByIndex[-requestId - 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1115,21 +1114,21 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs
|
|||
const QVector<MTPMessage> *v = 0;
|
||||
switch (msgs.type()) {
|
||||
case mtpc_messages_messages: {
|
||||
const auto &d(msgs.c_messages_messages());
|
||||
auto &d = msgs.c_messages_messages();
|
||||
App::feedUsers(d.vusers);
|
||||
App::feedChats(d.vchats);
|
||||
v = &d.vmessages.c_vector().v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_messagesSlice: {
|
||||
const auto &d(msgs.c_messages_messagesSlice());
|
||||
auto &d = msgs.c_messages_messagesSlice();
|
||||
App::feedUsers(d.vusers);
|
||||
App::feedChats(d.vchats);
|
||||
v = &d.vmessages.c_vector().v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_channelMessages: {
|
||||
auto &d(msgs.c_messages_channelMessages());
|
||||
auto &d = msgs.c_messages_channelMessages();
|
||||
if (channel) {
|
||||
channel->ptsReceived(d.vpts.v);
|
||||
} else {
|
||||
|
@ -1152,21 +1151,21 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs
|
|||
}
|
||||
}
|
||||
|
||||
for (QMap<uint64, int32>::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) {
|
||||
if (HistoryItem *item = App::histories().addNewMessage(v->at(i.value()), NewMessageExisting)) {
|
||||
for_const (auto msgId, msgsIds) {
|
||||
if (auto item = App::histories().addNewMessage(v->at(msgId), NewMessageExisting)) {
|
||||
item->setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
const WebPageItems &items(App::webPageItems());
|
||||
for (WebPagesPending::iterator i = _webPagesPending.begin(); i != _webPagesPending.cend();) {
|
||||
auto &items = App::webPageItems();
|
||||
for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) {
|
||||
if (i.value() == req) {
|
||||
if (i.key()->pendingTill > 0) {
|
||||
i.key()->pendingTill = -1;
|
||||
WebPageItems::const_iterator j = items.constFind(i.key());
|
||||
auto j = items.constFind(i.key());
|
||||
if (j != items.cend()) {
|
||||
for (HistoryItemsMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) {
|
||||
k.key()->setPendingInitDimensions();
|
||||
for_const (auto item, j.value()) {
|
||||
item->setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "lang.h"
|
||||
#include "data/data_abstract_structure.h"
|
||||
#include "history/history_service_layout.h"
|
||||
#include "history/history_location_manager.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "application.h"
|
||||
|
@ -44,47 +46,51 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
namespace {
|
||||
App::LaunchState _launchState = App::Launched;
|
||||
|
||||
UserData *self = 0;
|
||||
UserData *self = nullptr;
|
||||
|
||||
typedef QHash<PeerId, PeerData*> PeersData;
|
||||
using PeersData = QHash<PeerId, PeerData*>;
|
||||
PeersData peersData;
|
||||
|
||||
typedef QMap<PeerData*, bool> MutedPeers;
|
||||
using MutedPeers = QMap<PeerData*, bool>;
|
||||
MutedPeers mutedPeers;
|
||||
|
||||
typedef QMap<PeerData*, bool> UpdatedPeers;
|
||||
using UpdatedPeers = QMap<PeerData*, bool>;
|
||||
UpdatedPeers updatedPeers;
|
||||
|
||||
PhotosData photosData;
|
||||
DocumentsData documentsData;
|
||||
|
||||
typedef QHash<LocationCoords, LocationData*> LocationsData;
|
||||
using LocationsData = QHash<LocationCoords, LocationData*>;
|
||||
LocationsData locationsData;
|
||||
|
||||
typedef QHash<WebPageId, WebPageData*> WebPagesData;
|
||||
using WebPagesData = QHash<WebPageId, WebPageData*>;
|
||||
WebPagesData webPagesData;
|
||||
|
||||
using GamesData = QHash<GameId, GameData*>;
|
||||
GamesData gamesData;
|
||||
|
||||
PhotoItems photoItems;
|
||||
DocumentItems documentItems;
|
||||
WebPageItems webPageItems;
|
||||
GameItems gameItems;
|
||||
SharedContactItems sharedContactItems;
|
||||
GifItems gifItems;
|
||||
|
||||
typedef OrderedSet<HistoryItem*> DependentItemsSet;
|
||||
typedef QMap<HistoryItem*, DependentItemsSet> DependentItems;
|
||||
using DependentItemsSet = OrderedSet<HistoryItem*>;
|
||||
using DependentItems = QMap<HistoryItem*, DependentItemsSet>;
|
||||
DependentItems dependentItems;
|
||||
|
||||
Histories histories;
|
||||
|
||||
typedef QHash<MsgId, HistoryItem*> MsgsData;
|
||||
using MsgsData = QHash<MsgId, HistoryItem*>;
|
||||
MsgsData msgsData;
|
||||
typedef QMap<ChannelId, MsgsData> ChannelMsgsData;
|
||||
using ChannelMsgsData = QMap<ChannelId, MsgsData>;
|
||||
ChannelMsgsData channelMsgsData;
|
||||
|
||||
typedef QMap<uint64, FullMsgId> RandomData;
|
||||
using RandomData = QMap<uint64, FullMsgId>;
|
||||
RandomData randomData;
|
||||
|
||||
typedef QMap<uint64, QPair<PeerId, QString> > SentData;
|
||||
using SentData = QMap<uint64, QPair<PeerId, QString>>;
|
||||
SentData sentData;
|
||||
|
||||
HistoryItem *hoveredItem = nullptr,
|
||||
|
@ -94,7 +100,7 @@ namespace {
|
|||
*contextItem = nullptr,
|
||||
*mousedItem = nullptr;
|
||||
|
||||
QPixmap *emoji = 0, *emojiLarge = 0;
|
||||
QPixmap *emoji = nullptr, *emojiLarge = nullptr;
|
||||
style::font monofont;
|
||||
|
||||
struct CornersPixmaps {
|
||||
|
@ -104,19 +110,19 @@ namespace {
|
|||
QPixmap *p[4];
|
||||
};
|
||||
CornersPixmaps corners[RoundCornersCount];
|
||||
typedef QMap<uint32, CornersPixmaps> CornersMap;
|
||||
using CornersMap = QMap<uint32, CornersPixmaps>;
|
||||
CornersMap cornersMap;
|
||||
QImage *cornersMaskLarge[4] = { 0 }, *cornersMaskSmall[4] = { 0 };
|
||||
QImage *cornersMaskLarge[4] = { nullptr }, *cornersMaskSmall[4] = { nullptr };
|
||||
|
||||
typedef QMap<uint64, QPixmap> EmojiMap;
|
||||
using EmojiMap = QMap<uint64, QPixmap>;
|
||||
EmojiMap mainEmojiMap;
|
||||
QMap<int32, EmojiMap> otherEmojiMap;
|
||||
|
||||
int32 serviceImageCacheSize = 0;
|
||||
|
||||
typedef QLinkedList<PhotoData*> LastPhotosList;
|
||||
using LastPhotosList = QLinkedList<PhotoData*>;
|
||||
LastPhotosList lastPhotos;
|
||||
typedef QHash<PhotoData*, LastPhotosList::iterator> LastPhotosMap;
|
||||
using LastPhotosMap = QHash<PhotoData*, LastPhotosList::iterator>;
|
||||
LastPhotosMap lastPhotosMap;
|
||||
|
||||
style::color _msgServiceBg;
|
||||
|
@ -1471,7 +1477,7 @@ namespace {
|
|||
DocumentData *feedDocument(const MTPdocument &document, const QPixmap &thumb) {
|
||||
switch (document.type()) {
|
||||
case mtpc_document: {
|
||||
const auto &d(document.c_document());
|
||||
auto &d = document.c_document();
|
||||
return App::documentSet(d.vid.v, 0, d.vaccess_hash.v, d.vversion.v, d.vdate.v, d.vattributes.c_vector().v, qs(d.vmime_type), ImagePtr(thumb, "JPG"), d.vdc_id.v, d.vsize.v, StorageImageLocation());
|
||||
} break;
|
||||
case mtpc_documentEmpty: return App::document(document.c_documentEmpty().vid.v);
|
||||
|
@ -1513,7 +1519,11 @@ namespace {
|
|||
} break;
|
||||
case mtpc_webPagePending: return App::feedWebPage(webpage.c_webPagePending());
|
||||
}
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GameData *feedGame(const MTPDgame &game, GameData *convert) {
|
||||
return App::gameSet(game.vid.v, convert, game.vaccess_hash.v, qs(game.vshort_name), qs(game.vtitle), qs(game.vdescription), App::feedPhoto(game.vphoto), game.has_document() ? App::feedDocument(game.vdocument) : nullptr);
|
||||
}
|
||||
|
||||
UserData *curUser() {
|
||||
|
@ -1753,8 +1763,8 @@ namespace {
|
|||
auto &items = App::documentItems();
|
||||
auto i = items.constFind(result);
|
||||
if (i != items.cend()) {
|
||||
for (auto j = i->cbegin(), e = i->cend(); j != e; ++j) {
|
||||
j.key()->setPendingInitDimensions();
|
||||
for_const (auto item, i.value()) {
|
||||
item->setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1762,7 +1772,7 @@ namespace {
|
|||
}
|
||||
|
||||
WebPageData *webPage(const WebPageId &webPage) {
|
||||
WebPagesData::const_iterator i = webPagesData.constFind(webPage);
|
||||
auto i = webPagesData.constFind(webPage);
|
||||
if (i == webPagesData.cend()) {
|
||||
i = webPagesData.insert(webPage, new WebPageData(webPage));
|
||||
}
|
||||
|
@ -1772,7 +1782,7 @@ namespace {
|
|||
WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const QString &description, PhotoData *photo, DocumentData *document, int32 duration, const QString &author, int32 pendingTill) {
|
||||
if (convert) {
|
||||
if (convert->id != webPage) {
|
||||
WebPagesData::iterator i = webPagesData.find(convert->id);
|
||||
auto i = webPagesData.find(convert->id);
|
||||
if (i != webPagesData.cend() && i.value() == convert) {
|
||||
webPagesData.erase(i);
|
||||
}
|
||||
|
@ -1780,21 +1790,21 @@ namespace {
|
|||
}
|
||||
if ((convert->url.isEmpty() && !url.isEmpty()) || (convert->pendingTill && convert->pendingTill != pendingTill && pendingTill >= -1)) {
|
||||
convert->type = toWebPageType(type);
|
||||
convert->url = url;
|
||||
convert->displayUrl = displayUrl;
|
||||
convert->siteName = siteName;
|
||||
convert->title = title;
|
||||
convert->description = description;
|
||||
convert->url = textClean(url);
|
||||
convert->displayUrl = textClean(displayUrl);
|
||||
convert->siteName = textClean(siteName);
|
||||
convert->title = textOneLine(textClean(title));
|
||||
convert->description = textClean(description);
|
||||
convert->photo = photo;
|
||||
convert->document = document;
|
||||
convert->duration = duration;
|
||||
convert->author = author;
|
||||
convert->author = textClean(author);
|
||||
if (convert->pendingTill > 0 && pendingTill <= 0 && api()) api()->clearWebPageRequest(convert);
|
||||
convert->pendingTill = pendingTill;
|
||||
if (App::main()) App::main()->webPageUpdated(convert);
|
||||
}
|
||||
}
|
||||
WebPagesData::const_iterator i = webPagesData.constFind(webPage);
|
||||
auto i = webPagesData.constFind(webPage);
|
||||
WebPageData *result;
|
||||
if (i == webPagesData.cend()) {
|
||||
if (convert) {
|
||||
|
@ -1811,15 +1821,15 @@ namespace {
|
|||
if (result != convert) {
|
||||
if ((result->url.isEmpty() && !url.isEmpty()) || (result->pendingTill && result->pendingTill != pendingTill && pendingTill >= -1)) {
|
||||
result->type = toWebPageType(type);
|
||||
result->url = url;
|
||||
result->displayUrl = displayUrl;
|
||||
result->siteName = siteName;
|
||||
result->title = title;
|
||||
result->description = description;
|
||||
result->url = textClean(url);
|
||||
result->displayUrl = textClean(displayUrl);
|
||||
result->siteName = textClean(siteName);
|
||||
result->title = textOneLine(textClean(title));
|
||||
result->description = textClean(description);
|
||||
result->photo = photo;
|
||||
result->document = document;
|
||||
result->duration = duration;
|
||||
result->author = author;
|
||||
result->author = textClean(author);
|
||||
if (result->pendingTill > 0 && pendingTill <= 0 && api()) api()->clearWebPageRequest(result);
|
||||
result->pendingTill = pendingTill;
|
||||
if (App::main()) App::main()->webPageUpdated(result);
|
||||
|
@ -1829,8 +1839,62 @@ namespace {
|
|||
return result;
|
||||
}
|
||||
|
||||
GameData *game(const GameId &game) {
|
||||
auto i = gamesData.constFind(game);
|
||||
if (i == gamesData.cend()) {
|
||||
i = gamesData.insert(game, new GameData(game));
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
GameData *gameSet(const GameId &game, GameData *convert, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *document) {
|
||||
if (convert) {
|
||||
if (convert->id != game) {
|
||||
auto i = gamesData.find(convert->id);
|
||||
if (i != gamesData.cend() && i.value() == convert) {
|
||||
gamesData.erase(i);
|
||||
}
|
||||
convert->id = game;
|
||||
convert->accessHash = 0;
|
||||
}
|
||||
if (!convert->accessHash && accessHash) {
|
||||
convert->accessHash = accessHash;
|
||||
convert->shortName = textClean(shortName);
|
||||
convert->title = textOneLine(textClean(title));
|
||||
convert->description = textClean(description);
|
||||
convert->photo = photo;
|
||||
convert->document = document;
|
||||
if (App::main()) App::main()->gameUpdated(convert);
|
||||
}
|
||||
}
|
||||
auto i = gamesData.constFind(game);
|
||||
GameData *result;
|
||||
if (i == gamesData.cend()) {
|
||||
if (convert) {
|
||||
result = convert;
|
||||
} else {
|
||||
result = new GameData(game, accessHash, shortName, title, description, photo, document);
|
||||
}
|
||||
gamesData.insert(game, result);
|
||||
} else {
|
||||
result = i.value();
|
||||
if (result != convert) {
|
||||
if (!result->accessHash && accessHash) {
|
||||
result->accessHash = accessHash;
|
||||
result->shortName = textClean(shortName);
|
||||
result->title = textOneLine(textClean(title));
|
||||
result->description = textClean(description);
|
||||
result->photo = photo;
|
||||
result->document = document;
|
||||
if (App::main()) App::main()->gameUpdated(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
LocationData *location(const LocationCoords &coords) {
|
||||
LocationsData::const_iterator i = locationsData.constFind(coords);
|
||||
auto i = locationsData.constFind(coords);
|
||||
if (i == locationsData.cend()) {
|
||||
i = locationsData.insert(coords, new LocationData(coords));
|
||||
}
|
||||
|
@ -1840,14 +1904,14 @@ namespace {
|
|||
void forgetMedia() {
|
||||
lastPhotos.clear();
|
||||
lastPhotosMap.clear();
|
||||
for (PhotosData::const_iterator i = ::photosData.cbegin(), e = ::photosData.cend(); i != e; ++i) {
|
||||
i.value()->forget();
|
||||
for_const (auto photo, ::photosData) {
|
||||
photo->forget();
|
||||
}
|
||||
for (DocumentsData::const_iterator i = ::documentsData.cbegin(), e = ::documentsData.cend(); i != e; ++i) {
|
||||
i.value()->forget();
|
||||
for_const (auto document, ::documentsData) {
|
||||
document->forget();
|
||||
}
|
||||
for (LocationsData::const_iterator i = ::locationsData.cbegin(), e = ::locationsData.cend(); i != e; ++i) {
|
||||
i.value()->thumb->forget();
|
||||
for_const (auto location, ::locationsData) {
|
||||
location->thumb->forget();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2040,6 +2104,7 @@ namespace {
|
|||
::photoItems.clear();
|
||||
::documentItems.clear();
|
||||
::webPageItems.clear();
|
||||
::gameItems.clear();
|
||||
::sharedContactItems.clear();
|
||||
::gifItems.clear();
|
||||
lastPhotos.clear();
|
||||
|
@ -2459,7 +2524,7 @@ namespace {
|
|||
}
|
||||
|
||||
void regPhotoItem(PhotoData *data, HistoryItem *item) {
|
||||
::photoItems[data].insert(item, NullType());
|
||||
::photoItems[data].insert(item);
|
||||
}
|
||||
|
||||
void unregPhotoItem(PhotoData *data, HistoryItem *item) {
|
||||
|
@ -2475,7 +2540,7 @@ namespace {
|
|||
}
|
||||
|
||||
void regDocumentItem(DocumentData *data, HistoryItem *item) {
|
||||
::documentItems[data].insert(item, NullType());
|
||||
::documentItems[data].insert(item);
|
||||
}
|
||||
|
||||
void unregDocumentItem(DocumentData *data, HistoryItem *item) {
|
||||
|
@ -2491,7 +2556,7 @@ namespace {
|
|||
}
|
||||
|
||||
void regWebPageItem(WebPageData *data, HistoryItem *item) {
|
||||
::webPageItems[data].insert(item, NullType());
|
||||
::webPageItems[data].insert(item);
|
||||
}
|
||||
|
||||
void unregWebPageItem(WebPageData *data, HistoryItem *item) {
|
||||
|
@ -2502,10 +2567,22 @@ namespace {
|
|||
return ::webPageItems;
|
||||
}
|
||||
|
||||
void regGameItem(GameData *data, HistoryItem *item) {
|
||||
::gameItems[data].insert(item);
|
||||
}
|
||||
|
||||
void unregGameItem(GameData *data, HistoryItem *item) {
|
||||
::gameItems[data].remove(item);
|
||||
}
|
||||
|
||||
const GameItems &gameItems() {
|
||||
return ::gameItems;
|
||||
}
|
||||
|
||||
void regSharedContactItem(int32 userId, HistoryItem *item) {
|
||||
auto user = App::userLoaded(userId);
|
||||
auto canShareThisContact = user ? user->canShareThisContact() : false;
|
||||
::sharedContactItems[userId].insert(item, NullType());
|
||||
::sharedContactItems[userId].insert(item);
|
||||
if (canShareThisContact != (user ? user->canShareThisContact() : false)) {
|
||||
Notify::peerUpdatedDelayed(user, Notify::PeerUpdate::Flag::UserCanShareContact);
|
||||
}
|
||||
|
@ -2544,11 +2621,12 @@ namespace {
|
|||
}
|
||||
|
||||
QString phoneFromSharedContact(int32 userId) {
|
||||
SharedContactItems::const_iterator i = ::sharedContactItems.constFind(userId);
|
||||
auto i = ::sharedContactItems.constFind(userId);
|
||||
if (i != ::sharedContactItems.cend() && !i->isEmpty()) {
|
||||
HistoryMedia *media = i->cbegin().key()->getMedia();
|
||||
if (media && media->type() == MediaTypeContact) {
|
||||
return static_cast<HistoryContact*>(media)->phone();
|
||||
if (auto media = (*i->cbegin())->getMedia()) {
|
||||
if (media->type() == MediaTypeContact) {
|
||||
return static_cast<HistoryContact*>(media)->phone();
|
||||
}
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
|
|
|
@ -21,6 +21,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#pragma once
|
||||
|
||||
#include "core/basic_types.h"
|
||||
#include "history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_media.h"
|
||||
#include "history/history_message.h"
|
||||
#include "layout.h"
|
||||
|
||||
class AppClass;
|
||||
class MainWindow;
|
||||
|
@ -28,18 +33,19 @@ class MainWidget;
|
|||
class ApiWrap;
|
||||
class FileUploader;
|
||||
|
||||
#include "history.h"
|
||||
#include "layout.h"
|
||||
using HistoryItemsMap = OrderedSet<HistoryItem*>;
|
||||
using PhotoItems = QHash<PhotoData*, HistoryItemsMap>;
|
||||
using DocumentItems = QHash<DocumentData*, HistoryItemsMap>;
|
||||
using WebPageItems = QHash<WebPageData*, HistoryItemsMap>;
|
||||
using GameItems = QHash<GameData*, HistoryItemsMap>;
|
||||
using SharedContactItems = QHash<int32, HistoryItemsMap>;
|
||||
using GifItems = QHash<Media::Clip::Reader*, HistoryItem*>;
|
||||
|
||||
typedef QMap<HistoryItem*, NullType> HistoryItemsMap;
|
||||
typedef QHash<PhotoData*, HistoryItemsMap> PhotoItems;
|
||||
typedef QHash<DocumentData*, HistoryItemsMap> DocumentItems;
|
||||
typedef QHash<WebPageData*, HistoryItemsMap> WebPageItems;
|
||||
typedef QHash<int32, HistoryItemsMap> SharedContactItems;
|
||||
typedef QHash<Media::Clip::Reader*, HistoryItem*> GifItems;
|
||||
using PhotosData = QHash<PhotoId, PhotoData*>;
|
||||
using DocumentsData = QHash<DocumentId, DocumentData*>;
|
||||
|
||||
typedef QHash<PhotoId, PhotoData*> PhotosData;
|
||||
typedef QHash<DocumentId, DocumentData*> DocumentsData;
|
||||
struct LocationCoords;
|
||||
struct LocationData;
|
||||
|
||||
namespace App {
|
||||
AppClass *app();
|
||||
|
@ -90,14 +96,15 @@ namespace App {
|
|||
StorageImageLocation imageLocation(const MTPPhotoSize &size);
|
||||
|
||||
PhotoData *feedPhoto(const MTPPhoto &photo, const PreparedPhotoThumbs &thumbs);
|
||||
PhotoData *feedPhoto(const MTPPhoto &photo, PhotoData *convert = 0);
|
||||
PhotoData *feedPhoto(const MTPDphoto &photo, PhotoData *convert = 0);
|
||||
PhotoData *feedPhoto(const MTPPhoto &photo, PhotoData *convert = nullptr);
|
||||
PhotoData *feedPhoto(const MTPDphoto &photo, PhotoData *convert = nullptr);
|
||||
DocumentData *feedDocument(const MTPdocument &document, const QPixmap &thumb);
|
||||
DocumentData *feedDocument(const MTPdocument &document, DocumentData *convert = 0);
|
||||
DocumentData *feedDocument(const MTPDdocument &document, DocumentData *convert = 0);
|
||||
WebPageData *feedWebPage(const MTPDwebPage &webpage, WebPageData *convert = 0);
|
||||
WebPageData *feedWebPage(const MTPDwebPagePending &webpage, WebPageData *convert = 0);
|
||||
DocumentData *feedDocument(const MTPdocument &document, DocumentData *convert = nullptr);
|
||||
DocumentData *feedDocument(const MTPDdocument &document, DocumentData *convert = nullptr);
|
||||
WebPageData *feedWebPage(const MTPDwebPage &webpage, WebPageData *convert = nullptr);
|
||||
WebPageData *feedWebPage(const MTPDwebPagePending &webpage, WebPageData *convert = nullptr);
|
||||
WebPageData *feedWebPage(const MTPWebPage &webpage);
|
||||
GameData *feedGame(const MTPDgame &game, GameData *convert = nullptr);
|
||||
|
||||
PeerData *peer(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded);
|
||||
inline UserData *user(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded) {
|
||||
|
@ -148,7 +155,9 @@ namespace App {
|
|||
DocumentData *document(const DocumentId &document);
|
||||
DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 version, int32 date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation);
|
||||
WebPageData *webPage(const WebPageId &webPage);
|
||||
WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const QString &description, PhotoData *photo, DocumentData *doc, int32 duration, const QString &author, int32 pendingTill);
|
||||
WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const QString &description, PhotoData *photo, DocumentData *doc, int32 duration, const QString &author, int32 pendingTill);
|
||||
GameData *game(const GameId &game);
|
||||
GameData *gameSet(const GameId &game, GameData *convert, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *doc);
|
||||
LocationData *location(const LocationCoords &coords);
|
||||
void forgetMedia();
|
||||
|
||||
|
@ -247,6 +256,10 @@ namespace App {
|
|||
void unregWebPageItem(WebPageData *data, HistoryItem *item);
|
||||
const WebPageItems &webPageItems();
|
||||
|
||||
void regGameItem(GameData *data, HistoryItem *item);
|
||||
void unregGameItem(GameData *data, HistoryItem *item);
|
||||
const GameItems &gameItems();
|
||||
|
||||
void regSharedContactItem(int32 userId, HistoryItem *item);
|
||||
void unregSharedContactItem(int32 userId, HistoryItem *item);
|
||||
const SharedContactItems &sharedContactItems();
|
||||
|
|
|
@ -33,9 +33,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "autoupdater.h"
|
||||
#include "core/observer.h"
|
||||
#include "observer_peer.h"
|
||||
#include "core/observer.h"
|
||||
#include "window/chat_background.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "history/history_location_manager.h"
|
||||
|
||||
namespace {
|
||||
void mtpStateChanged(int32 dc, int32 state) {
|
||||
|
@ -651,9 +651,17 @@ namespace Sandbox {
|
|||
cSetScreenScale(dbisTwo);
|
||||
}
|
||||
|
||||
if (application()->devicePixelRatio() > 1) {
|
||||
auto devicePixelRatio = application()->devicePixelRatio();
|
||||
if (devicePixelRatio > 1.) {
|
||||
if ((cPlatform() != dbipMac && cPlatform() != dbipMacOld) || (devicePixelRatio != 2.)) {
|
||||
LOG(("Found non-trivial Device Pixel Ratio: %1").arg(devicePixelRatio));
|
||||
LOG(("Environmental variables: QT_DEVICE_PIXEL_RATIO='%1'").arg(QString::fromLatin1(qgetenv("QT_DEVICE_PIXEL_RATIO"))));
|
||||
LOG(("Environmental variables: QT_SCALE_FACTOR='%1'").arg(QString::fromLatin1(qgetenv("QT_SCALE_FACTOR"))));
|
||||
LOG(("Environmental variables: QT_AUTO_SCREEN_SCALE_FACTOR='%1'").arg(QString::fromLatin1(qgetenv("QT_AUTO_SCREEN_SCALE_FACTOR"))));
|
||||
LOG(("Environmental variables: QT_SCREEN_SCALE_FACTORS='%1'").arg(QString::fromLatin1(qgetenv("QT_SCREEN_SCALE_FACTORS"))));
|
||||
}
|
||||
cSetRetina(true);
|
||||
cSetRetinaFactor(application()->devicePixelRatio());
|
||||
cSetRetinaFactor(devicePixelRatio);
|
||||
cSetIntRetinaFactor(int32(cRetinaFactor()));
|
||||
cSetConfigScale(dbisOne);
|
||||
cSetRealScale(dbisOne);
|
||||
|
@ -746,7 +754,7 @@ AppClass::AppClass() : QObject()
|
|||
|
||||
Shortcuts::start();
|
||||
|
||||
initImageLinkManager();
|
||||
initLocationManager();
|
||||
App::initMedia();
|
||||
|
||||
Local::ReadMapState state = Local::readMap(QByteArray());
|
||||
|
@ -1097,7 +1105,7 @@ AppClass::~AppClass() {
|
|||
|
||||
stopWebLoadManager();
|
||||
App::deinitMedia();
|
||||
deinitImageLinkManager();
|
||||
deinitLocationManager();
|
||||
|
||||
MTP::finish();
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "connectionbox.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "history/history_location_manager.h"
|
||||
|
||||
ConnectionBox::ConnectionBox() : AbstractBox(st::boxWidth)
|
||||
, _hostInput(this, st::connectionHostInputField, lang(lng_connection_host_ph), Global::ConnectionProxy().host)
|
||||
|
@ -207,7 +208,7 @@ void ConnectionBox::onSave() {
|
|||
Global::RefConnectionTypeChanged().notify();
|
||||
|
||||
MTP::restart();
|
||||
reinitImageLinkManager();
|
||||
reinitLocationManager();
|
||||
reinitWebLoadManager();
|
||||
onClose();
|
||||
}
|
||||
|
|
|
@ -86,6 +86,17 @@ ContactsInner::ContactsInner(ChatData *chat, MembersFilter membersFilter) : TWid
|
|||
init();
|
||||
}
|
||||
|
||||
template <typename FilterCallback>
|
||||
void ContactsInner::addDialogsToList(FilterCallback callback) {
|
||||
auto v = App::main()->dialogsList();
|
||||
for_const (auto row, *v) {
|
||||
auto peer = row->history()->peer;
|
||||
if (callback(peer)) {
|
||||
_contacts->addToEnd(row->history());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContactsInner::ContactsInner(UserData *bot) : TWidget()
|
||||
, _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom())
|
||||
, _bot(bot)
|
||||
|
@ -93,14 +104,25 @@ ContactsInner::ContactsInner(UserData *bot) : TWidget()
|
|||
, _customList(std_::make_unique<Dialogs::IndexedList>(Dialogs::SortMode::Add))
|
||||
, _contacts(_customList.get())
|
||||
, _addContactLnk(this, lang(lng_add_contact_button)) {
|
||||
auto v = App::main()->dialogsList();
|
||||
for_const (auto row, *v) {
|
||||
auto peer = row->history()->peer;
|
||||
if (peer->isChat() && peer->asChat()->canEdit()) {
|
||||
_contacts->addToEnd(row->history());
|
||||
} else if (peer->isMegagroup() && (peer->asChannel()->amCreator() || peer->asChannel()->amEditor())) {
|
||||
_contacts->addToEnd(row->history());
|
||||
}
|
||||
if (sharingBotGame()) {
|
||||
addDialogsToList([](PeerData *peer) {
|
||||
if (peer->canWrite()) {
|
||||
if (auto channel = peer->asChannel()) {
|
||||
return !channel->isBroadcast();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
addDialogsToList([](PeerData *peer) {
|
||||
if (peer->isChat() && peer->asChat()->canEdit()) {
|
||||
return true;
|
||||
} else if (peer->isMegagroup() && (peer->asChannel()->amCreator() || peer->asChannel()->amEditor())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
init();
|
||||
}
|
||||
|
@ -166,8 +188,22 @@ void ContactsInner::onPeerNameChanged(PeerData *peer, const PeerData::Names &old
|
|||
}
|
||||
|
||||
void ContactsInner::onAddBot() {
|
||||
if (_bot->botInfo && !_bot->botInfo->startGroupToken.isEmpty()) {
|
||||
MTP::send(MTPmessages_StartBot(_bot->inputUser, _addToPeer->input, MTP_long(rand_value<uint64>()), MTP_string(_bot->botInfo->startGroupToken)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, _bot));
|
||||
if (auto &info = _bot->botInfo) {
|
||||
if (!info->shareGameShortName.isEmpty()) {
|
||||
MTPmessages_SendMedia::Flags sendFlags = 0;
|
||||
|
||||
auto history = App::historyLoaded(_addToPeer);
|
||||
auto afterRequestId = history ? history->sendRequestId : 0;
|
||||
auto randomId = rand_value<uint64>();
|
||||
auto requestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), _addToPeer->input, MTP_int(0), MTP_inputMediaGame(MTP_inputGameShortName(_bot->inputUser, MTP_string(info->shareGameShortName))), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, afterRequestId);
|
||||
if (history) {
|
||||
history->sendRequestId = requestId;
|
||||
}
|
||||
} else if (!info->startGroupToken.isEmpty()) {
|
||||
MTP::send(MTPmessages_StartBot(_bot->inputUser, _addToPeer->input, MTP_long(rand_value<uint64>()), MTP_string(info->startGroupToken)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, _bot));
|
||||
} else {
|
||||
App::main()->addParticipants(_addToPeer, QVector<UserData*>(1, _bot));
|
||||
}
|
||||
} else {
|
||||
App::main()->addParticipants(_addToPeer, QVector<UserData*>(1, _bot));
|
||||
}
|
||||
|
@ -511,7 +547,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
|
|||
QString text;
|
||||
int32 skip = 0;
|
||||
if (bot()) {
|
||||
text = lang(cDialogsReceived() ? lng_bot_no_groups : lng_contacts_loading);
|
||||
text = lang((cDialogsReceived() && !_searching) ? (sharingBotGame() ? lng_bot_no_chats : lng_bot_no_groups) : lng_contacts_loading);
|
||||
} else if (_chat && _membersFilter == MembersFilterAdmins) {
|
||||
text = lang(lng_contacts_loading);
|
||||
p.fillRect(0, 0, width(), _newItemHeight - st::contactsPadding.bottom() - st::lineWidth, st::contactsAboutBg);
|
||||
|
@ -538,7 +574,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
|
|||
p.setPen(st::noContactsColor->p);
|
||||
QString text;
|
||||
if (bot()) {
|
||||
text = lang(cDialogsReceived() ? lng_bot_groups_not_found : lng_contacts_loading);
|
||||
text = lang((cDialogsReceived() && !_searching) ? (sharingBotGame() ? lng_bot_chats_not_found : lng_bot_groups_not_found) : lng_contacts_loading);
|
||||
} else if (_chat && _membersFilter == MembersFilterAdmins) {
|
||||
text = lang(_chat->participants.isEmpty() ? lng_contacts_loading : lng_contacts_not_found);
|
||||
} else {
|
||||
|
@ -702,11 +738,22 @@ void ContactsInner::chooseParticipant() {
|
|||
connect(_addAdminBox, SIGNAL(confirmed()), this, SLOT(onAddAdmin()));
|
||||
connect(_addAdminBox, SIGNAL(destroyed(QObject*)), this, SLOT(onNoAddAdminBox(QObject*)));
|
||||
Ui::showLayer(_addAdminBox, KeepOtherLayers);
|
||||
} else if (sharingBotGame()) {
|
||||
_addToPeer = peer;
|
||||
auto confirmText = [peer] {
|
||||
if (peer->isUser()) {
|
||||
return lng_bot_sure_share_game(lt_user, App::peerName(peer));
|
||||
}
|
||||
return lng_bot_sure_share_game_group(lt_group, peer->name);
|
||||
};
|
||||
auto box = std_::make_unique<ConfirmBox>(confirmText());
|
||||
connect(box.get(), SIGNAL(confirmed()), this, SLOT(onAddBot()));
|
||||
Ui::showLayer(box.release(), KeepOtherLayers);
|
||||
} else if (bot() && (peer->isChat() || peer->isMegagroup())) {
|
||||
_addToPeer = peer;
|
||||
ConfirmBox *box = new ConfirmBox(lng_bot_sure_invite(lt_group, peer->name));
|
||||
connect(box, SIGNAL(confirmed()), this, SLOT(onAddBot()));
|
||||
Ui::showLayer(box, KeepOtherLayers);
|
||||
auto box = std_::make_unique<ConfirmBox>(lng_bot_sure_invite(lt_group, peer->name));
|
||||
connect(box.get(), SIGNAL(confirmed()), this, SLOT(onAddBot()));
|
||||
Ui::showLayer(box.release(), KeepOtherLayers);
|
||||
} else {
|
||||
Ui::hideSettingsAndLayer(true);
|
||||
App::main()->choosePeer(peer->id, ShowAtUnreadMsgId);
|
||||
|
@ -899,7 +946,7 @@ void ContactsInner::updateFilter(QString filter) {
|
|||
_mouseSel = false;
|
||||
refresh();
|
||||
|
||||
if (!bot() && (!_chat || _membersFilter != MembersFilterAdmins)) {
|
||||
if ((!bot() || sharingBotGame()) && (!_chat || _membersFilter != MembersFilterAdmins)) {
|
||||
_searching = true;
|
||||
emit searchByUsername();
|
||||
}
|
||||
|
@ -966,6 +1013,15 @@ void ContactsInner::peopleReceived(const QString &query, const QVector<MTPPeer>
|
|||
} else {
|
||||
continue; // skip
|
||||
}
|
||||
} else if (sharingBotGame()) {
|
||||
if (!p->canWrite()) {
|
||||
continue;
|
||||
}
|
||||
if (auto channel = p->asChannel()) {
|
||||
if (channel->isBroadcast()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContactData *d = new ContactData();
|
||||
|
@ -1032,6 +1088,10 @@ UserData *ContactsInner::bot() const {
|
|||
return _bot;
|
||||
}
|
||||
|
||||
bool ContactsInner::sharingBotGame() const {
|
||||
return (_bot && _bot->botInfo) ? !_bot->botInfo->shareGameShortName.isEmpty() : false;
|
||||
}
|
||||
|
||||
CreatingGroupType ContactsInner::creating() const {
|
||||
return _creating;
|
||||
}
|
||||
|
@ -1040,8 +1100,11 @@ ContactsInner::~ContactsInner() {
|
|||
for (ContactsData::iterator i = _contactsData.begin(), e = _contactsData.end(); i != e; ++i) {
|
||||
delete *i;
|
||||
}
|
||||
if (_bot || (_chat && _membersFilter == MembersFilterAdmins)) {
|
||||
if (_bot && _bot->botInfo) _bot->botInfo->startGroupToken = QString();
|
||||
if (_bot) {
|
||||
if (auto &info = _bot->botInfo) {
|
||||
info->startGroupToken = QString();
|
||||
info->shareGameShortName = QString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1499,6 +1562,8 @@ void ContactsBox::paintEvent(QPaintEvent *e) {
|
|||
QString title(lang(addingAdmin ? lng_channel_add_admin : lng_profile_add_participant));
|
||||
QString additional((addingAdmin || (_inner.channel() && !_inner.channel()->isMegagroup())) ? QString() : QString("%1 / %2").arg(_inner.selectedCount()).arg(Global::MegagroupSizeMax()));
|
||||
paintTitle(p, title, additional);
|
||||
} else if (_inner.sharingBotGame()) {
|
||||
paintTitle(p, lang(lng_bot_choose_chat));
|
||||
} else if (_inner.bot()) {
|
||||
paintTitle(p, lang(lng_bot_choose_group));
|
||||
} else {
|
||||
|
|
|
@ -78,6 +78,8 @@ public:
|
|||
UserData *bot() const;
|
||||
CreatingGroupType creating() const;
|
||||
|
||||
bool sharingBotGame() const;
|
||||
|
||||
int32 selectedCount() const;
|
||||
bool hasAlreadyMembersInChannel() const {
|
||||
return !_already.isEmpty();
|
||||
|
@ -121,6 +123,9 @@ private:
|
|||
void addAdminDone(const MTPUpdates &result, mtpRequestId req);
|
||||
bool addAdminFail(const RPCError &error, mtpRequestId req);
|
||||
|
||||
template <typename FilterCallback>
|
||||
void addDialogsToList(FilterCallback callback);
|
||||
|
||||
int32 _rowHeight;
|
||||
int _newItemHeight = 0;
|
||||
bool _newItemSel = false;
|
||||
|
|
|
@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "localstorage.h"
|
||||
#include "mainwidget.h"
|
||||
#include "photosendbox.h"
|
||||
#include "history/history_media_types.h"
|
||||
|
||||
PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxWideWidth)
|
||||
, _file(file)
|
||||
|
|
|
@ -87,7 +87,7 @@ void ReportBox::onChange() {
|
|||
_reasonOtherText.destroy();
|
||||
updateMaxHeight();
|
||||
}
|
||||
if (App::wnd()) App::wnd()->setInnerFocus();
|
||||
_reasonOtherText->setFocus();
|
||||
}
|
||||
|
||||
void ReportBox::doSetInnerFocus() {
|
||||
|
|
|
@ -32,11 +32,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "boxes/confirmbox.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "history/history_media_types.h"
|
||||
|
||||
ShareBox::ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback) : ItemListBox(st::boxScroll)
|
||||
ShareBox::ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback) : ItemListBox(st::boxScroll)
|
||||
, _copyCallback(std_::move(copyCallback))
|
||||
, _submitCallback(std_::move(submitCallback))
|
||||
, _inner(this)
|
||||
, _inner(this, std_::move(filterCallback))
|
||||
, _filter(this, st::boxSearchField, lang(lng_participant_filter))
|
||||
, _filterCancel(this, st::boxSearchCancel)
|
||||
, _copy(this, lang(lng_share_copy_link), st::defaultBoxButton)
|
||||
|
@ -241,7 +242,8 @@ void ShareBox::onScroll() {
|
|||
|
||||
namespace internal {
|
||||
|
||||
ShareInner::ShareInner(QWidget *parent) : ScrolledWidget(parent)
|
||||
ShareInner::ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallback) : ScrolledWidget(parent)
|
||||
, _filterCallback(std_::move(filterCallback))
|
||||
, _chatsIndexed(std_::make_unique<Dialogs::IndexedList>(Dialogs::SortMode::Add)) {
|
||||
_rowsTop = st::shareRowsTop;
|
||||
_rowHeight = st::shareRowHeight;
|
||||
|
@ -250,7 +252,7 @@ ShareInner::ShareInner(QWidget *parent) : ScrolledWidget(parent)
|
|||
auto dialogs = App::main()->dialogsList();
|
||||
for_const (auto row, dialogs->all()) {
|
||||
auto history = row->history();
|
||||
if (history->peer->canWrite()) {
|
||||
if (_filterCallback(history->peer)) {
|
||||
_chatsIndexed->addToEnd(history);
|
||||
}
|
||||
}
|
||||
|
@ -863,7 +865,7 @@ void ShareInner::peopleReceived(const QString &query, const QVector<MTPPeer> &pe
|
|||
}
|
||||
if (j == already) {
|
||||
auto *peer = App::peer(peerId);
|
||||
if (!peer || !peer->canWrite()) continue;
|
||||
if (!peer || !_filterCallback(peer)) continue;
|
||||
|
||||
auto chat = new Chat(peer);
|
||||
updateChatName(chat, peer);
|
||||
|
@ -958,24 +960,15 @@ void shareGameScoreFromItem(HistoryItem *item) {
|
|||
if (auto main = App::main()) {
|
||||
if (auto item = App::histItemById(data->msgId)) {
|
||||
if (auto bot = item->getMessageBot()) {
|
||||
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
for (int i = 0, rowsCount = markup->rows.size(); i != rowsCount; ++i) {
|
||||
auto &row = markup->rows[i];
|
||||
for (int j = 0, buttonsCount = row.size(); j != buttonsCount; ++j) {
|
||||
auto &button = row[j];
|
||||
if (button.type == HistoryMessageReplyMarkup::Button::Type::Game) {
|
||||
auto strData = QString::fromUtf8(button.data);
|
||||
auto parts = strData.split(',');
|
||||
t_assert(parts.size() > 1);
|
||||
if (auto media = item->getMedia()) {
|
||||
if (media->type() == MediaTypeGame) {
|
||||
auto shortName = static_cast<HistoryGame*>(media)->game()->shortName;
|
||||
|
||||
QApplication::clipboard()->setText(qsl("https://telegram.me/") + bot->username + qsl("?start=") + parts[1]);
|
||||
QApplication::clipboard()->setText(qsl("https://telegram.me/") + bot->username + qsl("?game=") + shortName);
|
||||
|
||||
Ui::Toast::Config toast;
|
||||
toast.text = lang(lng_share_game_link_copied);
|
||||
Ui::Toast::Show(App::wnd(), toast);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Ui::Toast::Config toast;
|
||||
toast.text = lang(lng_share_game_link_copied);
|
||||
Ui::Toast::Show(App::wnd(), toast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1015,7 +1008,16 @@ void shareGameScoreFromItem(HistoryItem *item) {
|
|||
}
|
||||
}
|
||||
};
|
||||
Ui::showLayer(new ShareBox(std_::move(copyCallback), std_::move(submitCallback)));
|
||||
auto filterCallback = [](PeerData *peer) {
|
||||
if (peer->canWrite()) {
|
||||
if (auto channel = peer->asChannel()) {
|
||||
return !channel->isBroadcast();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
Ui::showLayer(new ShareBox(std_::move(copyCallback), std_::move(submitCallback), std_::move(filterCallback)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -47,7 +47,8 @@ class ShareBox : public ItemListBox, public RPCSender {
|
|||
public:
|
||||
using CopyCallback = base::lambda_unique<void()>;
|
||||
using SubmitCallback = base::lambda_unique<void(const QVector<PeerData*> &)>;
|
||||
ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback);
|
||||
using FilterCallback = base::lambda_unique<bool(PeerData*)>;
|
||||
ShareBox(CopyCallback &©Callback, SubmitCallback &&submitCallback, FilterCallback &&filterCallback);
|
||||
|
||||
private slots:
|
||||
void onFilterUpdate();
|
||||
|
@ -112,7 +113,7 @@ class ShareInner : public ScrolledWidget, public RPCSender, private base::Subscr
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ShareInner(QWidget *parent);
|
||||
ShareInner(QWidget *parent, ShareBox::FilterCallback &&filterCallback);
|
||||
|
||||
QVector<PeerData*> selected() const;
|
||||
bool hasSelected() const;
|
||||
|
@ -197,6 +198,7 @@ private:
|
|||
int _active = -1;
|
||||
int _upon = -1;
|
||||
|
||||
ShareBox::FilterCallback _filterCallback;
|
||||
std_::unique_ptr<Dialogs::IndexedList> _chatsIndexed;
|
||||
QString _filter;
|
||||
using FilteredDialogs = QVector<Dialogs::Row*>;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,869 @@
|
|||
/*
|
||||
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-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
#include "history/history_item.h"
|
||||
|
||||
#include "lang.h"
|
||||
#include "mainwidget.h"
|
||||
#include "history/history_service_layout.h"
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "fileuploader.h"
|
||||
|
||||
ReplyMarkupClickHandler::ReplyMarkupClickHandler(const HistoryItem *item, int row, int col)
|
||||
: _itemId(item->fullId())
|
||||
, _row(row)
|
||||
, _col(col) {
|
||||
}
|
||||
|
||||
// Copy to clipboard support.
|
||||
void ReplyMarkupClickHandler::copyToClipboard() const {
|
||||
if (auto button = getButton()) {
|
||||
if (button->type == HistoryMessageReplyMarkup::Button::Type::Url) {
|
||||
auto url = QString::fromUtf8(button->data);
|
||||
if (!url.isEmpty()) {
|
||||
QApplication::clipboard()->setText(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString ReplyMarkupClickHandler::copyToClipboardContextItemText() const {
|
||||
if (auto button = getButton()) {
|
||||
if (button->type == HistoryMessageReplyMarkup::Button::Type::Url) {
|
||||
return lang(lng_context_copy_link);
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
// Finds the corresponding button in the items markup struct.
|
||||
// If the button is not found it returns nullptr.
|
||||
// Note: it is possible that we will point to the different button
|
||||
// than the one was used when constructing the handler, but not a big deal.
|
||||
const HistoryMessageReplyMarkup::Button *ReplyMarkupClickHandler::getButton() const {
|
||||
if (auto item = App::histItemById(_itemId)) {
|
||||
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
if (_row < markup->rows.size()) {
|
||||
auto &row = markup->rows.at(_row);
|
||||
if (_col < row.size()) {
|
||||
return &row.at(_col);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ReplyMarkupClickHandler::onClickImpl() const {
|
||||
if (auto item = App::histItemById(_itemId)) {
|
||||
App::activateBotCommand(item, _row, _col);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the full text of the corresponding button.
|
||||
QString ReplyMarkupClickHandler::buttonText() const {
|
||||
if (auto button = getButton()) {
|
||||
return button->text;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
ReplyKeyboard::ReplyKeyboard(const HistoryItem *item, StylePtr &&s)
|
||||
: _item(item)
|
||||
, _a_selected(animation(this, &ReplyKeyboard::step_selected))
|
||||
, _st(std_::forward<StylePtr>(s)) {
|
||||
if (auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||
_rows.reserve(markup->rows.size());
|
||||
for (int i = 0, l = markup->rows.size(); i != l; ++i) {
|
||||
auto &row = markup->rows.at(i);
|
||||
int s = row.size();
|
||||
ButtonRow newRow(s, Button());
|
||||
for (int j = 0; j != s; ++j) {
|
||||
auto &button = newRow[j];
|
||||
auto str = row.at(j).text;
|
||||
button.type = row.at(j).type;
|
||||
button.link = MakeShared<ReplyMarkupClickHandler>(item, i, j);
|
||||
button.text.setText(_st->textFont(), textOneLine(str), _textPlainOptions);
|
||||
button.characters = str.isEmpty() ? 1 : str.size();
|
||||
}
|
||||
_rows.push_back(newRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReplyKeyboard::updateMessageId() {
|
||||
auto msgId = _item->fullId();
|
||||
for_const (auto &row, _rows) {
|
||||
for_const (auto &button, row) {
|
||||
button.link->setMessageId(msgId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ReplyKeyboard::resize(int width, int height) {
|
||||
_width = width;
|
||||
|
||||
auto markup = _item->Get<HistoryMessageReplyMarkup>();
|
||||
float64 y = 0, buttonHeight = _rows.isEmpty() ? _st->buttonHeight() : (float64(height + _st->buttonSkip()) / _rows.size());
|
||||
for (auto &row : _rows) {
|
||||
int s = row.size();
|
||||
|
||||
int widthForButtons = _width - ((s - 1) * _st->buttonSkip());
|
||||
int widthForText = widthForButtons;
|
||||
int widthOfText = 0;
|
||||
int maxMinButtonWidth = 0;
|
||||
for_const (auto &button, row) {
|
||||
widthOfText += qMax(button.text.maxWidth(), 1);
|
||||
int minButtonWidth = _st->minButtonWidth(button.type);
|
||||
widthForText -= minButtonWidth;
|
||||
accumulate_max(maxMinButtonWidth, minButtonWidth);
|
||||
}
|
||||
bool exact = (widthForText == widthOfText);
|
||||
bool enough = (widthForButtons - s * maxMinButtonWidth) >= widthOfText;
|
||||
|
||||
float64 x = 0;
|
||||
for (Button &button : row) {
|
||||
int buttonw = qMax(button.text.maxWidth(), 1);
|
||||
float64 textw = buttonw, minw = _st->minButtonWidth(button.type);
|
||||
float64 w = textw;
|
||||
if (exact) {
|
||||
w += minw;
|
||||
} else if (enough) {
|
||||
w = (widthForButtons / float64(s));
|
||||
textw = w - minw;
|
||||
} else {
|
||||
textw = (widthForText / float64(s));
|
||||
w = minw + textw;
|
||||
accumulate_max(w, 2 * float64(_st->buttonPadding()));
|
||||
}
|
||||
|
||||
int rectx = static_cast<int>(std::floor(x));
|
||||
int rectw = static_cast<int>(std::floor(x + w)) - rectx;
|
||||
button.rect = QRect(rectx, qRound(y), rectw, qRound(buttonHeight - _st->buttonSkip()));
|
||||
if (rtl()) button.rect.setX(_width - button.rect.x() - button.rect.width());
|
||||
x += w + _st->buttonSkip();
|
||||
|
||||
button.link->setFullDisplayed(textw >= buttonw);
|
||||
}
|
||||
y += buttonHeight;
|
||||
}
|
||||
}
|
||||
|
||||
bool ReplyKeyboard::isEnoughSpace(int width, const style::botKeyboardButton &st) const {
|
||||
for_const (auto &row, _rows) {
|
||||
int s = row.size();
|
||||
int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding);
|
||||
for_const (auto &button, row) {
|
||||
widthLeft -= qMax(button.text.maxWidth(), 1);
|
||||
if (widthLeft < 0) {
|
||||
if (row.size() > 3) {
|
||||
return false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReplyKeyboard::setStyle(StylePtr &&st) {
|
||||
_st = std_::move(st);
|
||||
}
|
||||
|
||||
int ReplyKeyboard::naturalWidth() const {
|
||||
auto result = 0;
|
||||
for_const (auto &row, _rows) {
|
||||
auto maxMinButtonWidth = 0;
|
||||
for_const (auto &button, row) {
|
||||
accumulate_max(maxMinButtonWidth, _st->minButtonWidth(button.type));
|
||||
}
|
||||
auto rowMaxButtonWidth = 0;
|
||||
for_const (auto &button, row) {
|
||||
accumulate_max(rowMaxButtonWidth, qMax(button.text.maxWidth(), 1) + maxMinButtonWidth);
|
||||
}
|
||||
|
||||
auto rowSize = row.size();
|
||||
accumulate_max(result, rowSize * rowMaxButtonWidth + (rowSize - 1) * _st->buttonSkip());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int ReplyKeyboard::naturalHeight() const {
|
||||
return (_rows.size() - 1) * _st->buttonSkip() + _rows.size() * _st->buttonHeight();
|
||||
}
|
||||
|
||||
void ReplyKeyboard::paint(Painter &p, const QRect &clip) const {
|
||||
t_assert(_st != nullptr);
|
||||
t_assert(_width > 0);
|
||||
|
||||
_st->startPaint(p);
|
||||
for_const (auto &row, _rows) {
|
||||
for_const (auto &button, row) {
|
||||
QRect rect(button.rect);
|
||||
if (rect.y() >= clip.y() + clip.height()) return;
|
||||
if (rect.y() + rect.height() < clip.y()) continue;
|
||||
|
||||
// just ignore the buttons that didn't layout well
|
||||
if (rect.x() + rect.width() > _width) break;
|
||||
|
||||
_st->paintButton(p, button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClickHandlerPtr ReplyKeyboard::getState(int x, int y) const {
|
||||
t_assert(_width > 0);
|
||||
|
||||
for_const (auto &row, _rows) {
|
||||
for_const (auto &button, row) {
|
||||
QRect rect(button.rect);
|
||||
|
||||
// just ignore the buttons that didn't layout well
|
||||
if (rect.x() + rect.width() > _width) break;
|
||||
|
||||
if (rect.contains(x, y)) {
|
||||
return button.link;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ClickHandlerPtr();
|
||||
}
|
||||
|
||||
void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
||||
if (!p) return;
|
||||
|
||||
bool startAnimation = false;
|
||||
for (int i = 0, rows = _rows.size(); i != rows; ++i) {
|
||||
auto &row = _rows.at(i);
|
||||
for (int j = 0, cols = row.size(); j != cols; ++j) {
|
||||
if (row.at(j).link == p) {
|
||||
bool startAnimation = _animations.isEmpty();
|
||||
|
||||
int indexForAnimation = i * MatrixRowShift + j + 1;
|
||||
if (!active) {
|
||||
indexForAnimation = -indexForAnimation;
|
||||
}
|
||||
|
||||
_animations.remove(-indexForAnimation);
|
||||
if (!_animations.contains(indexForAnimation)) {
|
||||
_animations.insert(indexForAnimation, getms());
|
||||
}
|
||||
|
||||
if (startAnimation && !_a_selected.animating()) {
|
||||
_a_selected.start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReplyKeyboard::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
|
||||
_st->repaint(_item);
|
||||
}
|
||||
|
||||
void ReplyKeyboard::step_selected(uint64 ms, bool timer) {
|
||||
for (Animations::iterator i = _animations.begin(); i != _animations.end();) {
|
||||
int index = qAbs(i.key()) - 1, row = (index / MatrixRowShift), col = index % MatrixRowShift;
|
||||
float64 dt = float64(ms - i.value()) / st::botKbDuration;
|
||||
if (dt >= 1) {
|
||||
_rows[row][col].howMuchOver = (i.key() > 0) ? 1 : 0;
|
||||
i = _animations.erase(i);
|
||||
} else {
|
||||
_rows[row][col].howMuchOver = (i.key() > 0) ? dt : (1 - dt);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (timer) _st->repaint(_item);
|
||||
if (_animations.isEmpty()) {
|
||||
_a_selected.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void ReplyKeyboard::clearSelection() {
|
||||
for (auto i = _animations.cbegin(), e = _animations.cend(); i != e; ++i) {
|
||||
int index = qAbs(i.key()) - 1, row = (index / MatrixRowShift), col = index % MatrixRowShift;
|
||||
_rows[row][col].howMuchOver = 0;
|
||||
}
|
||||
_animations.clear();
|
||||
_a_selected.stop();
|
||||
}
|
||||
|
||||
void ReplyKeyboard::Style::paintButton(Painter &p, const ReplyKeyboard::Button &button) const {
|
||||
const QRect &rect = button.rect;
|
||||
bool pressed = ClickHandler::showAsPressed(button.link);
|
||||
|
||||
paintButtonBg(p, rect, pressed, button.howMuchOver);
|
||||
paintButtonIcon(p, rect, button.type);
|
||||
if (button.type == HistoryMessageReplyMarkup::Button::Type::Callback
|
||||
|| button.type == HistoryMessageReplyMarkup::Button::Type::Game) {
|
||||
if (auto data = button.link->getButton()) {
|
||||
if (data->requestId) {
|
||||
paintButtonLoading(p, rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int tx = rect.x(), tw = rect.width();
|
||||
if (tw >= st::botKbFont->elidew + _st->padding * 2) {
|
||||
tx += _st->padding;
|
||||
tw -= _st->padding * 2;
|
||||
} else if (tw > st::botKbFont->elidew) {
|
||||
tx += (tw - st::botKbFont->elidew) / 2;
|
||||
tw = st::botKbFont->elidew;
|
||||
}
|
||||
int textTop = rect.y() + (pressed ? _st->downTextTop : _st->textTop);
|
||||
button.text.drawElided(p, tx, textTop + ((rect.height() - _st->height) / 2), tw, 1, style::al_top);
|
||||
}
|
||||
|
||||
void HistoryMessageReplyMarkup::createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v) {
|
||||
if (v.isEmpty()) {
|
||||
rows.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
rows.reserve(v.size());
|
||||
for_const (auto &row, v) {
|
||||
switch (row.type()) {
|
||||
case mtpc_keyboardButtonRow: {
|
||||
auto &r = row.c_keyboardButtonRow();
|
||||
auto &b = r.vbuttons.c_vector().v;
|
||||
if (!b.isEmpty()) {
|
||||
ButtonRow buttonRow;
|
||||
buttonRow.reserve(b.size());
|
||||
for_const (const auto &button, b) {
|
||||
switch (button.type()) {
|
||||
case mtpc_keyboardButton: {
|
||||
buttonRow.push_back({ Button::Type::Default, qs(button.c_keyboardButton().vtext), QByteArray(), 0 });
|
||||
} break;
|
||||
case mtpc_keyboardButtonCallback: {
|
||||
auto &buttonData = button.c_keyboardButtonCallback();
|
||||
buttonRow.push_back({ Button::Type::Callback, qs(buttonData.vtext), qba(buttonData.vdata), 0 });
|
||||
} break;
|
||||
case mtpc_keyboardButtonRequestGeoLocation: {
|
||||
buttonRow.push_back({ Button::Type::RequestLocation, qs(button.c_keyboardButtonRequestGeoLocation().vtext), QByteArray(), 0 });
|
||||
} break;
|
||||
case mtpc_keyboardButtonRequestPhone: {
|
||||
buttonRow.push_back({ Button::Type::RequestPhone, qs(button.c_keyboardButtonRequestPhone().vtext), QByteArray(), 0 });
|
||||
} break;
|
||||
case mtpc_keyboardButtonUrl: {
|
||||
auto &buttonData = button.c_keyboardButtonUrl();
|
||||
buttonRow.push_back({ Button::Type::Url, qs(buttonData.vtext), qba(buttonData.vurl), 0 });
|
||||
} break;
|
||||
case mtpc_keyboardButtonSwitchInline: {
|
||||
auto &buttonData = button.c_keyboardButtonSwitchInline();
|
||||
auto buttonType = buttonData.is_same_peer() ? Button::Type::SwitchInlineSame : Button::Type::SwitchInline;
|
||||
buttonRow.push_back({ buttonType, qs(buttonData.vtext), qba(buttonData.vquery), 0 });
|
||||
if (buttonType == Button::Type::SwitchInline) {
|
||||
// Optimization flag.
|
||||
// Fast check on all new messages if there is a switch button to auto-click it.
|
||||
flags |= MTPDreplyKeyboardMarkup_ClientFlag::f_has_switch_inline_button;
|
||||
}
|
||||
} break;
|
||||
case mtpc_keyboardButtonGame: {
|
||||
auto &buttonData = button.c_keyboardButtonGame();
|
||||
buttonRow.push_back({ Button::Type::Game, qs(buttonData.vtext), QByteArray(), 0 });
|
||||
} break;
|
||||
}
|
||||
}
|
||||
if (!buttonRow.isEmpty()) rows.push_back(buttonRow);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryMessageReplyMarkup::create(const MTPReplyMarkup &markup) {
|
||||
flags = 0;
|
||||
rows.clear();
|
||||
inlineKeyboard = nullptr;
|
||||
|
||||
switch (markup.type()) {
|
||||
case mtpc_replyKeyboardMarkup: {
|
||||
const auto &d(markup.c_replyKeyboardMarkup());
|
||||
flags = d.vflags.v;
|
||||
|
||||
createFromButtonRows(d.vrows.c_vector().v);
|
||||
} break;
|
||||
|
||||
case mtpc_replyInlineMarkup: {
|
||||
const auto &d(markup.c_replyInlineMarkup());
|
||||
flags = MTPDreplyKeyboardMarkup::Flags(0) | MTPDreplyKeyboardMarkup_ClientFlag::f_inline;
|
||||
|
||||
createFromButtonRows(d.vrows.c_vector().v);
|
||||
} break;
|
||||
|
||||
case mtpc_replyKeyboardHide: {
|
||||
const auto &d(markup.c_replyKeyboardHide());
|
||||
flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_zero;
|
||||
} break;
|
||||
|
||||
case mtpc_replyKeyboardForceReply: {
|
||||
const auto &d(markup.c_replyKeyboardForceReply());
|
||||
flags = mtpCastFlags(d.vflags) | MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryMessageUnreadBar::init(int count) {
|
||||
if (_freezed) return;
|
||||
_text = lng_unread_bar(lt_count, count);
|
||||
_width = st::semiboldFont->width(_text);
|
||||
}
|
||||
|
||||
int HistoryMessageUnreadBar::height() {
|
||||
return st::unreadBarHeight + st::unreadBarMargin;
|
||||
}
|
||||
|
||||
int HistoryMessageUnreadBar::marginTop() {
|
||||
return st::lineWidth + st::unreadBarMargin;
|
||||
}
|
||||
|
||||
void HistoryMessageUnreadBar::paint(Painter &p, int y, int w) const {
|
||||
p.fillRect(0, y + marginTop(), w, height() - marginTop() - st::lineWidth, st::unreadBarBG);
|
||||
p.fillRect(0, y + height() - st::lineWidth, w, st::lineWidth, st::unreadBarBorder);
|
||||
p.setFont(st::unreadBarFont);
|
||||
p.setPen(st::unreadBarColor);
|
||||
|
||||
int left = st::msgServiceMargin.left();
|
||||
int maxwidth = w;
|
||||
if (Adaptive::Wide()) {
|
||||
maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()));
|
||||
}
|
||||
w = maxwidth;
|
||||
|
||||
p.drawText((w - _width) / 2, y + marginTop() + (st::unreadBarHeight - 2 * st::lineWidth - st::unreadBarFont->height) / 2 + st::unreadBarFont->ascent, _text);
|
||||
}
|
||||
|
||||
void HistoryMessageDate::init(const QDateTime &date) {
|
||||
_text = langDayOfMonthFull(date.date());
|
||||
_width = st::msgServiceFont->width(_text);
|
||||
}
|
||||
|
||||
int HistoryMessageDate::height() const {
|
||||
return st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom() + st::msgServiceMargin.bottom();
|
||||
}
|
||||
|
||||
void HistoryMessageDate::paint(Painter &p, int y, int w) const {
|
||||
HistoryLayout::ServiceMessagePainter::paintDate(p, _text, _width, y, w);
|
||||
}
|
||||
|
||||
void HistoryMediaPtr::reset(HistoryMedia *p) {
|
||||
if (_p) {
|
||||
_p->detachFromParent();
|
||||
delete _p;
|
||||
}
|
||||
_p = p;
|
||||
if (_p) {
|
||||
_p->attachToParent();
|
||||
}
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
TextSelection unshiftSelection(TextSelection selection, const Text &byText) {
|
||||
if (selection == FullSelection) {
|
||||
return selection;
|
||||
}
|
||||
return ::unshiftSelection(selection, byText);
|
||||
}
|
||||
|
||||
TextSelection shiftSelection(TextSelection selection, const Text &byText) {
|
||||
if (selection == FullSelection) {
|
||||
return selection;
|
||||
}
|
||||
return ::shiftSelection(selection, byText);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
HistoryItem::HistoryItem(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime msgDate, int32 from) : HistoryElement()
|
||||
, y(0)
|
||||
, id(msgId)
|
||||
, date(msgDate)
|
||||
, _from(from ? App::user(from) : history->peer)
|
||||
, _history(history)
|
||||
, _flags(flags | MTPDmessage_ClientFlag::f_pending_init_dimensions | MTPDmessage_ClientFlag::f_pending_resize)
|
||||
, _authorNameVersion(author()->nameVersion) {
|
||||
}
|
||||
|
||||
void HistoryItem::finishCreate() {
|
||||
App::historyRegItem(this);
|
||||
}
|
||||
|
||||
void HistoryItem::finishEdition(int oldKeyboardTop) {
|
||||
setPendingInitDimensions();
|
||||
if (App::main()) {
|
||||
App::main()->dlgUpdated(history(), id);
|
||||
}
|
||||
|
||||
// invalidate cache for drawInDialog
|
||||
if (history()->textCachedFor == this) {
|
||||
history()->textCachedFor = nullptr;
|
||||
}
|
||||
|
||||
if (oldKeyboardTop >= 0) {
|
||||
if (auto keyboard = Get<HistoryMessageReplyMarkup>()) {
|
||||
keyboard->oldTop = oldKeyboardTop;
|
||||
}
|
||||
}
|
||||
|
||||
App::historyUpdateDependent(this);
|
||||
}
|
||||
|
||||
void HistoryItem::finishEditionToEmpty() {
|
||||
recountDisplayDate();
|
||||
finishEdition(-1);
|
||||
|
||||
_history->removeNotification(this);
|
||||
if (history()->isChannel()) {
|
||||
if (history()->peer->isMegagroup() && history()->peer->asChannel()->mgInfo->pinnedMsgId == id) {
|
||||
history()->peer->asChannel()->mgInfo->pinnedMsgId = 0;
|
||||
}
|
||||
}
|
||||
if (history()->lastKeyboardId == id) {
|
||||
history()->clearLastKeyboard();
|
||||
}
|
||||
if ((!out() || isPost()) && unread() && history()->unreadCount() > 0) {
|
||||
history()->setUnreadCount(history()->unreadCount() - 1);
|
||||
}
|
||||
|
||||
if (auto next = nextItem()) {
|
||||
next->previousItemChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
||||
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
|
||||
if (markup->inlineKeyboard) {
|
||||
markup->inlineKeyboard->clickHandlerActiveChanged(p, active);
|
||||
}
|
||||
}
|
||||
App::hoveredLinkItem(active ? this : nullptr);
|
||||
Ui::repaintHistoryItem(this);
|
||||
}
|
||||
|
||||
void HistoryItem::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
|
||||
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
|
||||
if (markup->inlineKeyboard) {
|
||||
markup->inlineKeyboard->clickHandlerPressedChanged(p, pressed);
|
||||
}
|
||||
}
|
||||
App::pressedLinkItem(pressed ? this : nullptr);
|
||||
Ui::repaintHistoryItem(this);
|
||||
}
|
||||
|
||||
void HistoryItem::destroy() {
|
||||
// All this must be done for all items manually in History::clear(false)!
|
||||
eraseFromOverview();
|
||||
|
||||
bool wasAtBottom = history()->loadedAtBottom();
|
||||
_history->removeNotification(this);
|
||||
detach();
|
||||
if (history()->isChannel()) {
|
||||
if (history()->peer->isMegagroup() && history()->peer->asChannel()->mgInfo->pinnedMsgId == id) {
|
||||
history()->peer->asChannel()->mgInfo->pinnedMsgId = 0;
|
||||
}
|
||||
}
|
||||
if (history()->lastMsg == this) {
|
||||
history()->fixLastMessage(wasAtBottom);
|
||||
}
|
||||
if (history()->lastKeyboardId == id) {
|
||||
history()->clearLastKeyboard();
|
||||
}
|
||||
if ((!out() || isPost()) && unread() && history()->unreadCount() > 0) {
|
||||
history()->setUnreadCount(history()->unreadCount() - 1);
|
||||
}
|
||||
Global::RefPendingRepaintItems().remove(this);
|
||||
delete this;
|
||||
}
|
||||
|
||||
void HistoryItem::detach() {
|
||||
if (detached()) return;
|
||||
|
||||
if (_history->isChannel()) {
|
||||
_history->asChannelHistory()->messageDetached(this);
|
||||
}
|
||||
_block->removeItem(this);
|
||||
App::historyItemDetached(this);
|
||||
|
||||
_history->setPendingResize();
|
||||
}
|
||||
|
||||
void HistoryItem::detachFast() {
|
||||
_block = nullptr;
|
||||
_indexInBlock = -1;
|
||||
}
|
||||
|
||||
void HistoryItem::previousItemChanged() {
|
||||
recountDisplayDate();
|
||||
recountAttachToPrevious();
|
||||
}
|
||||
|
||||
void HistoryItem::recountAttachToPrevious() {
|
||||
bool attach = false;
|
||||
if (!isPost() && !Has<HistoryMessageDate>() && !Has<HistoryMessageUnreadBar>()) {
|
||||
if (auto previos = previousItem()) {
|
||||
attach = !previos->isPost()
|
||||
&& !previos->serviceMsg()
|
||||
&& !previos->isEmpty()
|
||||
&& previos->from() == from()
|
||||
&& (qAbs(previos->date.secsTo(date)) < AttachMessageToPreviousSecondsDelta);
|
||||
}
|
||||
}
|
||||
if (attach && !(_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) {
|
||||
_flags |= MTPDmessage_ClientFlag::f_attach_to_previous;
|
||||
setPendingInitDimensions();
|
||||
} else if (!attach && (_flags & MTPDmessage_ClientFlag::f_attach_to_previous)) {
|
||||
_flags &= ~MTPDmessage_ClientFlag::f_attach_to_previous;
|
||||
setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::setId(MsgId newId) {
|
||||
history()->changeMsgId(id, newId);
|
||||
id = newId;
|
||||
|
||||
// We don't need to call Notify::replyMarkupUpdated(this) and update keyboard
|
||||
// in history widget, because it can't exist for an outgoing message.
|
||||
// Only inline keyboards can be in outgoing messages.
|
||||
if (auto markup = inlineReplyMarkup()) {
|
||||
if (markup->inlineKeyboard) {
|
||||
markup->inlineKeyboard->updateMessageId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HistoryItem::canEdit(const QDateTime &cur) const {
|
||||
auto messageToMyself = (peerToUser(_history->peer->id) == MTP::authedId());
|
||||
auto messageTooOld = messageToMyself ? false : (date.secsTo(cur) >= Global::EditTimeLimit());
|
||||
if (id < 0 || messageTooOld) return false;
|
||||
|
||||
if (auto msg = toHistoryMessage()) {
|
||||
if (msg->Has<HistoryMessageVia>() || msg->Has<HistoryMessageForwarded>()) return false;
|
||||
|
||||
if (auto media = msg->getMedia()) {
|
||||
auto type = media->type();
|
||||
if (type != MediaTypePhoto &&
|
||||
type != MediaTypeVideo &&
|
||||
type != MediaTypeFile &&
|
||||
type != MediaTypeGif &&
|
||||
type != MediaTypeMusicFile &&
|
||||
type != MediaTypeVoiceFile &&
|
||||
type != MediaTypeWebPage) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isPost()) {
|
||||
auto channel = _history->peer->asChannel();
|
||||
return (channel->amCreator() || (channel->amEditor() && out()));
|
||||
}
|
||||
return out() || messageToMyself;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HistoryItem::unread() const {
|
||||
// Messages from myself are always read.
|
||||
if (history()->peer->isSelf()) return false;
|
||||
|
||||
if (out()) {
|
||||
// Outgoing messages in converted chats are always read.
|
||||
if (history()->peer->migrateTo()) return false;
|
||||
|
||||
if (id > 0) {
|
||||
if (id < history()->outboxReadBefore) return false;
|
||||
if (auto user = history()->peer->asUser()) {
|
||||
if (user->botInfo) return false;
|
||||
} else if (auto channel = history()->peer->asChannel()) {
|
||||
if (!channel->isMegagroup()) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (id > 0) {
|
||||
if (id < history()->inboxReadBefore) return false;
|
||||
return true;
|
||||
}
|
||||
return (_flags & MTPDmessage_ClientFlag::f_clientside_unread);
|
||||
}
|
||||
|
||||
void HistoryItem::destroyUnreadBar() {
|
||||
if (Has<HistoryMessageUnreadBar>()) {
|
||||
RemoveComponents(HistoryMessageUnreadBar::Bit());
|
||||
setPendingInitDimensions();
|
||||
if (_history->unreadBar == this) {
|
||||
_history->unreadBar = nullptr;
|
||||
}
|
||||
|
||||
recountAttachToPrevious();
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::setUnreadBarCount(int count) {
|
||||
if (count > 0) {
|
||||
HistoryMessageUnreadBar *bar;
|
||||
if (!Has<HistoryMessageUnreadBar>()) {
|
||||
AddComponents(HistoryMessageUnreadBar::Bit());
|
||||
setPendingInitDimensions();
|
||||
|
||||
recountAttachToPrevious();
|
||||
|
||||
bar = Get<HistoryMessageUnreadBar>();
|
||||
} else {
|
||||
bar = Get<HistoryMessageUnreadBar>();
|
||||
if (bar->_freezed) {
|
||||
return;
|
||||
}
|
||||
Global::RefPendingRepaintItems().insert(this);
|
||||
}
|
||||
bar->init(count);
|
||||
} else {
|
||||
destroyUnreadBar();
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::setUnreadBarFreezed() {
|
||||
if (auto bar = Get<HistoryMessageUnreadBar>()) {
|
||||
bar->_freezed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::clipCallback(Media::Clip::Notification notification) {
|
||||
using namespace Media::Clip;
|
||||
|
||||
HistoryMedia *media = getMedia();
|
||||
if (!media) return;
|
||||
|
||||
Reader *reader = media ? media->getClipReader() : 0;
|
||||
if (!reader) return;
|
||||
|
||||
switch (notification) {
|
||||
case NotificationReinit: {
|
||||
bool stopped = false;
|
||||
if (reader->autoPausedGif()) {
|
||||
if (MainWidget *m = App::main()) {
|
||||
if (!m->isItemVisible(this)) { // stop animation if it is not visible
|
||||
media->stopInline();
|
||||
if (DocumentData *document = media->getDocument()) { // forget data from memory
|
||||
document->forget();
|
||||
}
|
||||
stopped = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!stopped) {
|
||||
setPendingInitDimensions();
|
||||
Notify::historyItemLayoutChanged(this);
|
||||
}
|
||||
} break;
|
||||
|
||||
case NotificationRepaint: {
|
||||
if (!reader->currentDisplayed()) {
|
||||
Ui::repaintHistoryItem(this);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryItem::recountDisplayDate() {
|
||||
bool displayingDate = ([this]() {
|
||||
if (isEmpty()) return false;
|
||||
|
||||
if (auto previous = previousItem()) {
|
||||
return previous->isEmpty() || (previous->date.date() != date.date());
|
||||
}
|
||||
return true;
|
||||
})();
|
||||
|
||||
if (displayingDate && !Has<HistoryMessageDate>()) {
|
||||
AddComponents(HistoryMessageDate::Bit());
|
||||
Get<HistoryMessageDate>()->init(date);
|
||||
setPendingInitDimensions();
|
||||
} else if (!displayingDate && Has<HistoryMessageDate>()) {
|
||||
RemoveComponents(HistoryMessageDate::Bit());
|
||||
setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
QString HistoryItem::notificationText() const {
|
||||
auto getText = [this]() {
|
||||
if (emptyText()) {
|
||||
return _media ? _media->notificationText() : QString();
|
||||
}
|
||||
return _text.originalText();
|
||||
};
|
||||
|
||||
auto result = getText();
|
||||
if (result.size() > 0xFF) result = result.mid(0, 0xFF) + qsl("...");
|
||||
return result;
|
||||
}
|
||||
|
||||
QString HistoryItem::inDialogsText() const {
|
||||
auto getText = [this]() {
|
||||
if (emptyText()) {
|
||||
return _media ? _media->inDialogsText() : QString();
|
||||
}
|
||||
return textClean(_text.originalText());
|
||||
};
|
||||
auto plainText = getText();
|
||||
if ((!_history->peer->isUser() || out()) && !isPost() && !isEmpty()) {
|
||||
auto fromText = author()->isSelf() ? lang(lng_from_you) : author()->shortName();
|
||||
auto fromWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, textClean(fromText)));
|
||||
return lng_dialogs_text_with_from(lt_from_part, fromWrapped, lt_message, plainText);
|
||||
}
|
||||
return plainText;
|
||||
}
|
||||
|
||||
void HistoryItem::drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const {
|
||||
if (cacheFor != this) {
|
||||
cacheFor = this;
|
||||
cache.setText(st::dialogsTextFont, inDialogsText(), _textDlgOptions);
|
||||
}
|
||||
if (r.width()) {
|
||||
textstyleSet(&(act ? st::dialogsTextStyleActive : st::dialogsTextStyle));
|
||||
p.setFont(st::dialogsTextFont);
|
||||
p.setPen(act ? st::dialogsTextFgActive : st::dialogsTextFg);
|
||||
cache.drawElided(p, r.left(), r.top(), r.width(), r.height() / st::dialogsTextFont->height);
|
||||
textstyleRestore();
|
||||
}
|
||||
}
|
||||
|
||||
HistoryItem::~HistoryItem() {
|
||||
App::historyUnregItem(this);
|
||||
if (id < 0 && App::uploader()) {
|
||||
App::uploader()->cancel(fullId());
|
||||
}
|
||||
}
|
||||
|
||||
void GoToMessageClickHandler::onClickImpl() const {
|
||||
if (App::main()) {
|
||||
HistoryItem *current = App::mousedItem();
|
||||
if (current && current->history()->peer->id == peer()) {
|
||||
App::main()->pushReplyReturn(current);
|
||||
}
|
||||
Ui::showPeerHistory(peer(), msgid(), Ui::ShowWay::Forward);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,975 @@
|
|||
/*
|
||||
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-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class HistoryElement {
|
||||
public:
|
||||
HistoryElement() = default;
|
||||
HistoryElement(const HistoryElement &other) = delete;
|
||||
HistoryElement &operator=(const HistoryElement &other) = delete;
|
||||
|
||||
int maxWidth() const {
|
||||
return _maxw;
|
||||
}
|
||||
int minHeight() const {
|
||||
return _minh;
|
||||
}
|
||||
int height() const {
|
||||
return _height;
|
||||
}
|
||||
|
||||
virtual ~HistoryElement() = default;
|
||||
|
||||
protected:
|
||||
mutable int _maxw = 0;
|
||||
mutable int _minh = 0;
|
||||
mutable int _height = 0;
|
||||
|
||||
};
|
||||
|
||||
class HistoryMessage;
|
||||
|
||||
enum HistoryCursorState {
|
||||
HistoryDefaultCursorState,
|
||||
HistoryInTextCursorState,
|
||||
HistoryInDateCursorState,
|
||||
HistoryInForwardedCursorState,
|
||||
};
|
||||
|
||||
struct HistoryTextState {
|
||||
HistoryTextState() = default;
|
||||
HistoryTextState(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;
|
||||
}
|
||||
HistoryCursorState cursor = HistoryDefaultCursorState;
|
||||
ClickHandlerPtr link;
|
||||
bool afterSymbol = false;
|
||||
uint16 symbol = 0;
|
||||
};
|
||||
|
||||
struct HistoryStateRequest {
|
||||
Text::StateRequest::Flags flags = Text::StateRequest::Flag::LookupLink;
|
||||
Text::StateRequest forText() const {
|
||||
Text::StateRequest result;
|
||||
result.flags = flags;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
enum InfoDisplayType {
|
||||
InfoDisplayDefault,
|
||||
InfoDisplayOverImage,
|
||||
InfoDisplayOverBackground,
|
||||
};
|
||||
|
||||
enum HistoryItemType {
|
||||
HistoryItemMsg = 0,
|
||||
HistoryItemJoined
|
||||
};
|
||||
|
||||
struct HistoryMessageVia : public BaseComponent<HistoryMessageVia> {
|
||||
void create(int32 userId);
|
||||
void resize(int32 availw) const;
|
||||
|
||||
UserData *_bot = nullptr;
|
||||
mutable QString _text;
|
||||
mutable int _width = 0;
|
||||
mutable int _maxWidth = 0;
|
||||
ClickHandlerPtr _lnk;
|
||||
};
|
||||
|
||||
struct HistoryMessageViews : public BaseComponent<HistoryMessageViews> {
|
||||
QString _viewsText;
|
||||
int _views = 0;
|
||||
int _viewsWidth = 0;
|
||||
};
|
||||
|
||||
struct HistoryMessageSigned : public BaseComponent<HistoryMessageSigned> {
|
||||
void create(UserData *from, const QDateTime &date);
|
||||
int maxWidth() const;
|
||||
|
||||
Text _signature;
|
||||
};
|
||||
|
||||
struct HistoryMessageEdited : public BaseComponent<HistoryMessageEdited> {
|
||||
void create(const QDateTime &editDate, const QDateTime &date);
|
||||
int maxWidth() const;
|
||||
|
||||
QDateTime _editDate;
|
||||
Text _edited;
|
||||
};
|
||||
|
||||
struct HistoryMessageForwarded : public BaseComponent<HistoryMessageForwarded> {
|
||||
void create(const HistoryMessageVia *via) const;
|
||||
|
||||
PeerData *_authorOriginal = nullptr;
|
||||
PeerData *_fromOriginal = nullptr;
|
||||
MsgId _originalId = 0;
|
||||
mutable Text _text = { 1 };
|
||||
};
|
||||
|
||||
struct HistoryMessageReply : public BaseComponent<HistoryMessageReply> {
|
||||
HistoryMessageReply &operator=(HistoryMessageReply &&other) {
|
||||
replyToMsgId = other.replyToMsgId;
|
||||
std::swap(replyToMsg, other.replyToMsg);
|
||||
replyToLnk = std_::move(other.replyToLnk);
|
||||
replyToName = std_::move(other.replyToName);
|
||||
replyToText = std_::move(other.replyToText);
|
||||
replyToVersion = other.replyToVersion;
|
||||
_maxReplyWidth = other._maxReplyWidth;
|
||||
_replyToVia = std_::move(other._replyToVia);
|
||||
return *this;
|
||||
}
|
||||
~HistoryMessageReply() {
|
||||
// clearData() should be called by holder
|
||||
t_assert(replyToMsg == nullptr);
|
||||
t_assert(_replyToVia == nullptr);
|
||||
}
|
||||
|
||||
bool updateData(HistoryMessage *holder, bool force = false);
|
||||
void clearData(HistoryMessage *holder); // must be called before destructor
|
||||
|
||||
bool isNameUpdated() const;
|
||||
void updateName() const;
|
||||
void resize(int width) const;
|
||||
void itemRemoved(HistoryMessage *holder, HistoryItem *removed);
|
||||
|
||||
enum PaintFlag {
|
||||
PaintInBubble = 0x01,
|
||||
PaintSelected = 0x02,
|
||||
};
|
||||
Q_DECLARE_FLAGS(PaintFlags, PaintFlag);
|
||||
void paint(Painter &p, const HistoryItem *holder, int x, int y, int w, PaintFlags flags) const;
|
||||
|
||||
MsgId replyToId() const {
|
||||
return replyToMsgId;
|
||||
}
|
||||
int replyToWidth() const {
|
||||
return _maxReplyWidth;
|
||||
}
|
||||
ClickHandlerPtr replyToLink() const {
|
||||
return replyToLnk;
|
||||
}
|
||||
|
||||
MsgId replyToMsgId = 0;
|
||||
HistoryItem *replyToMsg = nullptr;
|
||||
ClickHandlerPtr replyToLnk;
|
||||
mutable Text replyToName, replyToText;
|
||||
mutable int replyToVersion = 0;
|
||||
mutable int _maxReplyWidth = 0;
|
||||
std_::unique_ptr<HistoryMessageVia> _replyToVia;
|
||||
int toWidth = 0;
|
||||
};
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(HistoryMessageReply::PaintFlags);
|
||||
|
||||
class ReplyKeyboard;
|
||||
struct HistoryMessageReplyMarkup : public BaseComponent<HistoryMessageReplyMarkup> {
|
||||
HistoryMessageReplyMarkup() = default;
|
||||
HistoryMessageReplyMarkup(MTPDreplyKeyboardMarkup::Flags f) : flags(f) {
|
||||
}
|
||||
|
||||
void create(const MTPReplyMarkup &markup);
|
||||
|
||||
struct Button {
|
||||
enum class Type {
|
||||
Default,
|
||||
Url,
|
||||
Callback,
|
||||
RequestPhone,
|
||||
RequestLocation,
|
||||
SwitchInline,
|
||||
SwitchInlineSame,
|
||||
Game,
|
||||
};
|
||||
Type type;
|
||||
QString text;
|
||||
QByteArray data;
|
||||
mutable mtpRequestId requestId;
|
||||
};
|
||||
using ButtonRow = QVector<Button>;
|
||||
using ButtonRows = QVector<ButtonRow>;
|
||||
|
||||
ButtonRows rows;
|
||||
MTPDreplyKeyboardMarkup::Flags flags = 0;
|
||||
|
||||
std_::unique_ptr<ReplyKeyboard> inlineKeyboard;
|
||||
|
||||
// If >= 0 it holds the y coord of the inlineKeyboard before the last edition.
|
||||
int oldTop = -1;
|
||||
|
||||
private:
|
||||
void createFromButtonRows(const QVector<MTPKeyboardButtonRow> &v);
|
||||
|
||||
};
|
||||
|
||||
class ReplyMarkupClickHandler : public LeftButtonClickHandler {
|
||||
public:
|
||||
ReplyMarkupClickHandler(const HistoryItem *item, int row, int col);
|
||||
|
||||
QString tooltip() const override {
|
||||
return _fullDisplayed ? QString() : buttonText();
|
||||
}
|
||||
|
||||
void setFullDisplayed(bool full) {
|
||||
_fullDisplayed = full;
|
||||
}
|
||||
|
||||
// Copy to clipboard support.
|
||||
void copyToClipboard() const override;
|
||||
QString copyToClipboardContextItemText() const override;
|
||||
|
||||
// Finds the corresponding button in the items markup struct.
|
||||
// If the button is not found it returns nullptr.
|
||||
// Note: it is possible that we will point to the different button
|
||||
// than the one was used when constructing the handler, but not a big deal.
|
||||
const HistoryMessageReplyMarkup::Button *getButton() const;
|
||||
|
||||
// We hold only FullMsgId, not HistoryItem*, because all click handlers
|
||||
// are activated async and the item may be already destroyed.
|
||||
void setMessageId(const FullMsgId &msgId) {
|
||||
_itemId = msgId;
|
||||
}
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
private:
|
||||
FullMsgId _itemId;
|
||||
int _row, _col;
|
||||
bool _fullDisplayed = true;
|
||||
|
||||
// Returns the full text of the corresponding button.
|
||||
QString buttonText() const;
|
||||
|
||||
};
|
||||
|
||||
class ReplyKeyboard {
|
||||
private:
|
||||
struct Button;
|
||||
|
||||
public:
|
||||
class Style {
|
||||
public:
|
||||
Style(const style::botKeyboardButton &st) : _st(&st) {
|
||||
}
|
||||
|
||||
virtual void startPaint(Painter &p) const = 0;
|
||||
virtual style::font textFont() const = 0;
|
||||
|
||||
int buttonSkip() const {
|
||||
return _st->margin;
|
||||
}
|
||||
int buttonPadding() const {
|
||||
return _st->padding;
|
||||
}
|
||||
int buttonHeight() const {
|
||||
return _st->height;
|
||||
}
|
||||
|
||||
virtual void repaint(const HistoryItem *item) const = 0;
|
||||
virtual ~Style() {
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void paintButtonBg(Painter &p, const QRect &rect, bool pressed, float64 howMuchOver) const = 0;
|
||||
virtual void paintButtonIcon(Painter &p, const QRect &rect, HistoryMessageReplyMarkup::Button::Type type) const = 0;
|
||||
virtual void paintButtonLoading(Painter &p, const QRect &rect) const = 0;
|
||||
virtual int minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const = 0;
|
||||
|
||||
private:
|
||||
const style::botKeyboardButton *_st;
|
||||
|
||||
void paintButton(Painter &p, const ReplyKeyboard::Button &button) const;
|
||||
friend class ReplyKeyboard;
|
||||
|
||||
};
|
||||
typedef std_::unique_ptr<Style> StylePtr;
|
||||
|
||||
ReplyKeyboard(const HistoryItem *item, StylePtr &&s);
|
||||
ReplyKeyboard(const ReplyKeyboard &other) = delete;
|
||||
ReplyKeyboard &operator=(const ReplyKeyboard &other) = delete;
|
||||
|
||||
bool isEnoughSpace(int width, const style::botKeyboardButton &st) const;
|
||||
void setStyle(StylePtr &&s);
|
||||
void resize(int width, int height);
|
||||
|
||||
// what width and height will best fit this keyboard
|
||||
int naturalWidth() const;
|
||||
int naturalHeight() const;
|
||||
|
||||
void paint(Painter &p, const QRect &clip) const;
|
||||
ClickHandlerPtr getState(int x, int y) const;
|
||||
|
||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active);
|
||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed);
|
||||
|
||||
void clearSelection();
|
||||
void updateMessageId();
|
||||
|
||||
private:
|
||||
const HistoryItem *_item;
|
||||
int _width = 0;
|
||||
|
||||
friend class Style;
|
||||
using ReplyMarkupClickHandlerPtr = QSharedPointer<ReplyMarkupClickHandler>;
|
||||
struct Button {
|
||||
Text text = { 1 };
|
||||
QRect rect;
|
||||
int characters = 0;
|
||||
float64 howMuchOver = 0.;
|
||||
HistoryMessageReplyMarkup::Button::Type type;
|
||||
ReplyMarkupClickHandlerPtr link;
|
||||
};
|
||||
using ButtonRow = QVector<Button>;
|
||||
using ButtonRows = QVector<ButtonRow>;
|
||||
ButtonRows _rows;
|
||||
|
||||
using Animations = QMap<int, uint64>;
|
||||
Animations _animations;
|
||||
Animation _a_selected;
|
||||
void step_selected(uint64 ms, bool timer);
|
||||
|
||||
StylePtr _st;
|
||||
};
|
||||
|
||||
// any HistoryItem can have this Interface for
|
||||
// displaying the day mark above the message
|
||||
struct HistoryMessageDate : public BaseComponent<HistoryMessageDate> {
|
||||
void init(const QDateTime &date);
|
||||
|
||||
int height() const;
|
||||
void paint(Painter &p, int y, int w) const;
|
||||
|
||||
QString _text;
|
||||
int _width = 0;
|
||||
};
|
||||
|
||||
// any HistoryItem can have this Interface for
|
||||
// displaying the unread messages bar above the message
|
||||
struct HistoryMessageUnreadBar : public BaseComponent<HistoryMessageUnreadBar> {
|
||||
void init(int count);
|
||||
|
||||
static int height();
|
||||
static int marginTop();
|
||||
|
||||
void paint(Painter &p, int y, int w) const;
|
||||
|
||||
QString _text;
|
||||
int _width = 0;
|
||||
|
||||
// if unread bar is freezed the new messages do not
|
||||
// increment the counter displayed by this bar
|
||||
//
|
||||
// it happens when we've opened the conversation and
|
||||
// we've seen the bar and new messages are marked as read
|
||||
// as soon as they are added to the chat history
|
||||
bool _freezed = false;
|
||||
};
|
||||
|
||||
// HistoryMedia has a special owning smart pointer
|
||||
// which regs/unregs this media to the holding HistoryItem
|
||||
class HistoryMedia;
|
||||
class HistoryMediaPtr {
|
||||
public:
|
||||
HistoryMediaPtr() = default;
|
||||
HistoryMediaPtr(const HistoryMediaPtr &other) = delete;
|
||||
HistoryMediaPtr &operator=(const HistoryMediaPtr &other) = delete;
|
||||
HistoryMedia *data() const {
|
||||
return _p;
|
||||
}
|
||||
void reset(HistoryMedia *p = nullptr);
|
||||
void clear() {
|
||||
reset();
|
||||
}
|
||||
bool isNull() const {
|
||||
return data() == nullptr;
|
||||
}
|
||||
|
||||
HistoryMedia *operator->() const {
|
||||
return data();
|
||||
}
|
||||
HistoryMedia &operator*() const {
|
||||
t_assert(!isNull());
|
||||
return *data();
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return !isNull();
|
||||
}
|
||||
~HistoryMediaPtr() {
|
||||
clear();
|
||||
}
|
||||
|
||||
private:
|
||||
HistoryMedia *_p = nullptr;
|
||||
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
TextSelection unshiftSelection(TextSelection selection, const Text &byText);
|
||||
TextSelection shiftSelection(TextSelection selection, const Text &byText);
|
||||
|
||||
} // namespace internal
|
||||
|
||||
class HistoryItem : public HistoryElement, public Composer, public ClickHandlerHost {
|
||||
public:
|
||||
int resizeGetHeight(int width) {
|
||||
if (_flags & MTPDmessage_ClientFlag::f_pending_init_dimensions) {
|
||||
_flags &= ~MTPDmessage_ClientFlag::f_pending_init_dimensions;
|
||||
initDimensions();
|
||||
}
|
||||
if (_flags & MTPDmessage_ClientFlag::f_pending_resize) {
|
||||
_flags &= ~MTPDmessage_ClientFlag::f_pending_resize;
|
||||
}
|
||||
return resizeGetHeight_(width);
|
||||
}
|
||||
virtual void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const = 0;
|
||||
|
||||
virtual void dependencyItemRemoved(HistoryItem *dependency) {
|
||||
}
|
||||
virtual bool updateDependencyItem() {
|
||||
return true;
|
||||
}
|
||||
virtual MsgId dependencyMsgId() const {
|
||||
return 0;
|
||||
}
|
||||
virtual bool notificationReady() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
UserData *viaBot() const {
|
||||
if (const HistoryMessageVia *via = Get<HistoryMessageVia>()) {
|
||||
return via->_bot;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
UserData *getMessageBot() const {
|
||||
if (auto bot = viaBot()) {
|
||||
return bot;
|
||||
}
|
||||
auto bot = from()->asUser();
|
||||
if (!bot) {
|
||||
bot = history()->peer->asUser();
|
||||
}
|
||||
return (bot && bot->botInfo) ? bot : nullptr;
|
||||
};
|
||||
|
||||
History *history() const {
|
||||
return _history;
|
||||
}
|
||||
PeerData *from() const {
|
||||
return _from;
|
||||
}
|
||||
HistoryBlock *block() {
|
||||
return _block;
|
||||
}
|
||||
const HistoryBlock *block() const {
|
||||
return _block;
|
||||
}
|
||||
void destroy();
|
||||
void detach();
|
||||
void detachFast();
|
||||
bool detached() const {
|
||||
return !_block;
|
||||
}
|
||||
void attachToBlock(HistoryBlock *block, int index) {
|
||||
t_assert(_block == nullptr);
|
||||
t_assert(_indexInBlock < 0);
|
||||
t_assert(block != nullptr);
|
||||
t_assert(index >= 0);
|
||||
|
||||
_block = block;
|
||||
_indexInBlock = index;
|
||||
if (pendingResize()) {
|
||||
_history->setHasPendingResizedItems();
|
||||
}
|
||||
}
|
||||
void setIndexInBlock(int index) {
|
||||
t_assert(_block != nullptr);
|
||||
t_assert(index >= 0);
|
||||
|
||||
_indexInBlock = index;
|
||||
}
|
||||
int indexInBlock() const {
|
||||
if (_indexInBlock >= 0) {
|
||||
t_assert(_block != nullptr);
|
||||
t_assert(_block->items.at(_indexInBlock) == this);
|
||||
} else if (_block != nullptr) {
|
||||
t_assert(_indexInBlock >= 0);
|
||||
t_assert(_block->items.at(_indexInBlock) == this);
|
||||
}
|
||||
return _indexInBlock;
|
||||
}
|
||||
bool out() const {
|
||||
return _flags & MTPDmessage::Flag::f_out;
|
||||
}
|
||||
bool unread() const;
|
||||
bool mentionsMe() const {
|
||||
return _flags & MTPDmessage::Flag::f_mentioned;
|
||||
}
|
||||
bool isMediaUnread() const {
|
||||
return (_flags & MTPDmessage::Flag::f_media_unread) && (channelId() == NoChannel);
|
||||
}
|
||||
void markMediaRead() {
|
||||
_flags &= ~MTPDmessage::Flag::f_media_unread;
|
||||
}
|
||||
bool definesReplyKeyboard() const {
|
||||
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
|
||||
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// optimization: don't create markup component for the case
|
||||
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
|
||||
return (_flags & MTPDmessage::Flag::f_reply_markup);
|
||||
}
|
||||
MTPDreplyKeyboardMarkup::Flags replyKeyboardFlags() const {
|
||||
t_assert(definesReplyKeyboard());
|
||||
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
|
||||
return markup->flags;
|
||||
}
|
||||
|
||||
// optimization: don't create markup component for the case
|
||||
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
|
||||
return qFlags(MTPDreplyKeyboardMarkup_ClientFlag::f_zero);
|
||||
}
|
||||
bool hasSwitchInlineButton() const {
|
||||
return _flags & MTPDmessage_ClientFlag::f_has_switch_inline_button;
|
||||
}
|
||||
bool hasTextLinks() const {
|
||||
return _flags & MTPDmessage_ClientFlag::f_has_text_links;
|
||||
}
|
||||
bool isGroupMigrate() const {
|
||||
return _flags & MTPDmessage_ClientFlag::f_is_group_migrate;
|
||||
}
|
||||
bool hasViews() const {
|
||||
return _flags & MTPDmessage::Flag::f_views;
|
||||
}
|
||||
bool isPost() const {
|
||||
return _flags & MTPDmessage::Flag::f_post;
|
||||
}
|
||||
bool indexInOverview() const {
|
||||
return (id > 0) && (!history()->isChannel() || history()->isMegagroup() || isPost());
|
||||
}
|
||||
bool isSilent() const {
|
||||
return _flags & MTPDmessage::Flag::f_silent;
|
||||
}
|
||||
bool hasOutLayout() const {
|
||||
return out() && !isPost();
|
||||
}
|
||||
virtual int32 viewsCount() const {
|
||||
return hasViews() ? 1 : -1;
|
||||
}
|
||||
|
||||
virtual bool needCheck() const {
|
||||
return out() || (id < 0 && history()->peer->isSelf());
|
||||
}
|
||||
virtual bool hasPoint(int x, int y) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0;
|
||||
|
||||
virtual TextSelection adjustSelection(TextSelection selection, TextSelectType type) const {
|
||||
return selection;
|
||||
}
|
||||
|
||||
// ClickHandlerHost interface
|
||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
|
||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
||||
|
||||
virtual HistoryItemType type() const {
|
||||
return HistoryItemMsg;
|
||||
}
|
||||
virtual bool serviceMsg() const {
|
||||
return false;
|
||||
}
|
||||
virtual void applyEdition(const MTPDmessage &message) {
|
||||
}
|
||||
virtual void applyEdition(const MTPDmessageService &message) {
|
||||
}
|
||||
virtual void updateMedia(const MTPMessageMedia *media) {
|
||||
}
|
||||
virtual int32 addToOverview(AddToOverviewMethod method) {
|
||||
return 0;
|
||||
}
|
||||
virtual void eraseFromOverview() {
|
||||
}
|
||||
virtual bool hasBubble() const {
|
||||
return false;
|
||||
}
|
||||
virtual void previousItemChanged();
|
||||
|
||||
virtual TextWithEntities selectedText(TextSelection selection) const {
|
||||
return { qsl("[-]"), EntitiesInText() };
|
||||
}
|
||||
|
||||
virtual QString notificationHeader() const {
|
||||
return QString();
|
||||
}
|
||||
virtual QString notificationText() const;
|
||||
|
||||
// Returns text with link-start and link-end commands for service-color highlighting.
|
||||
// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
|
||||
virtual QString inDialogsText() const;
|
||||
virtual QString inReplyText() const {
|
||||
return notificationText();
|
||||
}
|
||||
virtual TextWithEntities originalText() const {
|
||||
return { QString(), EntitiesInText() };
|
||||
}
|
||||
|
||||
virtual void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const {
|
||||
}
|
||||
virtual void setViewsCount(int32 count) {
|
||||
}
|
||||
virtual void setId(MsgId newId);
|
||||
void drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const;
|
||||
|
||||
bool emptyText() const {
|
||||
return _text.isEmpty();
|
||||
}
|
||||
|
||||
bool canDelete() const {
|
||||
ChannelData *channel = _history->peer->asChannel();
|
||||
if (!channel) return !(_flags & MTPDmessage_ClientFlag::f_is_group_migrate);
|
||||
|
||||
if (id == 1) return false;
|
||||
if (channel->amCreator()) return true;
|
||||
if (isPost()) {
|
||||
if (channel->amEditor() && out()) return true;
|
||||
return false;
|
||||
}
|
||||
return (channel->amEditor() || channel->amModerator() || out());
|
||||
}
|
||||
|
||||
bool canPin() const {
|
||||
return id > 0 && _history->peer->isMegagroup() && (_history->peer->asChannel()->amEditor() || _history->peer->asChannel()->amCreator()) && toHistoryMessage();
|
||||
}
|
||||
|
||||
bool canEdit(const QDateTime &cur) const;
|
||||
|
||||
bool suggestBanReportDeleteAll() const {
|
||||
ChannelData *channel = history()->peer->asChannel();
|
||||
if (!channel || (!channel->amEditor() && !channel->amCreator())) return false;
|
||||
return !isPost() && !out() && from()->isUser() && toHistoryMessage();
|
||||
}
|
||||
|
||||
bool hasDirectLink() const {
|
||||
return id > 0 && _history->peer->isChannel() && _history->peer->asChannel()->isPublic() && !_history->peer->isMegagroup();
|
||||
}
|
||||
QString directLink() const {
|
||||
return hasDirectLink() ? qsl("https://telegram.me/") + _history->peer->asChannel()->username + '/' + QString::number(id) : QString();
|
||||
}
|
||||
|
||||
int32 y;
|
||||
MsgId id;
|
||||
QDateTime date;
|
||||
|
||||
ChannelId channelId() const {
|
||||
return _history->channelId();
|
||||
}
|
||||
FullMsgId fullId() const {
|
||||
return FullMsgId(channelId(), id);
|
||||
}
|
||||
|
||||
HistoryMedia *getMedia() const {
|
||||
return _media.data();
|
||||
}
|
||||
virtual void setText(const TextWithEntities &textWithEntities) {
|
||||
}
|
||||
virtual bool textHasLinks() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual int infoWidth() const {
|
||||
return 0;
|
||||
}
|
||||
virtual int timeLeft() const {
|
||||
return 0;
|
||||
}
|
||||
virtual int timeWidth() const {
|
||||
return 0;
|
||||
}
|
||||
virtual bool pointInTime(int32 right, int32 bottom, int x, int y, InfoDisplayType type) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 skipBlockWidth() const {
|
||||
return st::msgDateSpace + infoWidth() - st::msgDateDelta.x();
|
||||
}
|
||||
int32 skipBlockHeight() const {
|
||||
return st::msgDateFont->height - st::msgDateDelta.y();
|
||||
}
|
||||
QString skipBlock() const {
|
||||
return textcmdSkipBlock(skipBlockWidth(), skipBlockHeight());
|
||||
}
|
||||
|
||||
virtual HistoryMessage *toHistoryMessage() { // dynamic_cast optimize
|
||||
return nullptr;
|
||||
}
|
||||
virtual const HistoryMessage *toHistoryMessage() const { // dynamic_cast optimize
|
||||
return nullptr;
|
||||
}
|
||||
MsgId replyToId() const {
|
||||
if (auto reply = Get<HistoryMessageReply>()) {
|
||||
return reply->replyToId();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool hasFromName() const {
|
||||
return (!out() || isPost()) && !history()->peer->isUser();
|
||||
}
|
||||
PeerData *author() const {
|
||||
return isPost() ? history()->peer : _from;
|
||||
}
|
||||
|
||||
PeerData *fromOriginal() const {
|
||||
if (const HistoryMessageForwarded *fwd = Get<HistoryMessageForwarded>()) {
|
||||
return fwd->_fromOriginal;
|
||||
}
|
||||
return from();
|
||||
}
|
||||
PeerData *authorOriginal() const {
|
||||
if (const HistoryMessageForwarded *fwd = Get<HistoryMessageForwarded>()) {
|
||||
return fwd->_authorOriginal;
|
||||
}
|
||||
return author();
|
||||
}
|
||||
|
||||
// count > 0 - creates the unread bar if necessary and
|
||||
// sets unread messages count if bar is not freezed yet
|
||||
// count <= 0 - destroys the unread bar
|
||||
void setUnreadBarCount(int count);
|
||||
void destroyUnreadBar();
|
||||
|
||||
// marks the unread bar as freezed so that unread
|
||||
// messages count will not change for this bar
|
||||
// when the new messages arrive in this chat history
|
||||
void setUnreadBarFreezed();
|
||||
|
||||
bool pendingResize() const {
|
||||
return _flags & MTPDmessage_ClientFlag::f_pending_resize;
|
||||
}
|
||||
void setPendingResize() {
|
||||
_flags |= MTPDmessage_ClientFlag::f_pending_resize;
|
||||
if (!detached()) {
|
||||
_history->setHasPendingResizedItems();
|
||||
}
|
||||
}
|
||||
bool pendingInitDimensions() const {
|
||||
return _flags & MTPDmessage_ClientFlag::f_pending_init_dimensions;
|
||||
}
|
||||
void setPendingInitDimensions() {
|
||||
_flags |= MTPDmessage_ClientFlag::f_pending_init_dimensions;
|
||||
setPendingResize();
|
||||
}
|
||||
|
||||
int displayedDateHeight() const {
|
||||
if (auto date = Get<HistoryMessageDate>()) {
|
||||
return date->height();
|
||||
}
|
||||
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();
|
||||
}
|
||||
bool isAttachedToPrevious() const {
|
||||
return _flags & MTPDmessage_ClientFlag::f_attach_to_previous;
|
||||
}
|
||||
bool displayDate() const {
|
||||
return Has<HistoryMessageDate>();
|
||||
}
|
||||
|
||||
bool isInOneDayWithPrevious() const {
|
||||
return !isEmpty() && !displayDate();
|
||||
}
|
||||
|
||||
bool isEmpty() const {
|
||||
return _text.isEmpty() && !_media;
|
||||
}
|
||||
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
|
||||
virtual ~HistoryItem();
|
||||
|
||||
protected:
|
||||
HistoryItem(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime msgDate, int32 from);
|
||||
|
||||
// to completely create history item we need to call
|
||||
// a virtual method, it can not be done from constructor
|
||||
virtual void finishCreate();
|
||||
|
||||
// called from resizeGetHeight() when MTPDmessage_ClientFlag::f_pending_init_dimensions is set
|
||||
virtual void initDimensions() = 0;
|
||||
|
||||
virtual int resizeGetHeight_(int width) = 0;
|
||||
|
||||
void finishEdition(int oldKeyboardTop);
|
||||
void finishEditionToEmpty();
|
||||
|
||||
PeerData *_from;
|
||||
History *_history;
|
||||
HistoryBlock *_block = nullptr;
|
||||
int _indexInBlock = -1;
|
||||
MTPDmessage::Flags _flags;
|
||||
|
||||
mutable int32 _authorNameVersion;
|
||||
|
||||
HistoryItem *previousItem() const {
|
||||
if (_block && _indexInBlock >= 0) {
|
||||
if (_indexInBlock > 0) {
|
||||
return _block->items.at(_indexInBlock - 1);
|
||||
}
|
||||
if (auto previous = _block->previousBlock()) {
|
||||
t_assert(!previous->items.isEmpty());
|
||||
return previous->items.back();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
HistoryItem *nextItem() const {
|
||||
if (_block && _indexInBlock >= 0) {
|
||||
if (_indexInBlock + 1 < _block->items.size()) {
|
||||
return _block->items.at(_indexInBlock + 1);
|
||||
}
|
||||
if (auto next = _block->nextBlock()) {
|
||||
t_assert(!next->items.isEmpty());
|
||||
return next->items.front();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// this should be used only in previousItemChanged()
|
||||
// to add required bits to the Composer mask
|
||||
// after that always use Has<HistoryMessageDate>()
|
||||
void recountDisplayDate();
|
||||
|
||||
// this should be used only in previousItemChanged() or when
|
||||
// HistoryMessageDate or HistoryMessageUnreadBar bit is changed in the Composer mask
|
||||
// then the result should be cached in a client side flag MTPDmessage_ClientFlag::f_attach_to_previous
|
||||
void recountAttachToPrevious();
|
||||
|
||||
const HistoryMessageReplyMarkup *inlineReplyMarkup() const {
|
||||
if (auto markup = Get<HistoryMessageReplyMarkup>()) {
|
||||
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
|
||||
return markup;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
const ReplyKeyboard *inlineReplyKeyboard() const {
|
||||
if (auto markup = inlineReplyMarkup()) {
|
||||
return markup->inlineKeyboard.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
HistoryMessageReplyMarkup *inlineReplyMarkup() {
|
||||
return const_cast<HistoryMessageReplyMarkup*>(static_cast<const HistoryItem*>(this)->inlineReplyMarkup());
|
||||
}
|
||||
ReplyKeyboard *inlineReplyKeyboard() {
|
||||
return const_cast<ReplyKeyboard*>(static_cast<const HistoryItem*>(this)->inlineReplyKeyboard());
|
||||
}
|
||||
|
||||
TextSelection toMediaSelection(TextSelection selection) const {
|
||||
return internal::unshiftSelection(selection, _text);
|
||||
}
|
||||
TextSelection fromMediaSelection(TextSelection selection) const {
|
||||
return internal::shiftSelection(selection, _text);
|
||||
}
|
||||
|
||||
Text _text = { int(st::msgMinWidth) };
|
||||
int _textWidth = -1;
|
||||
int _textHeight = 0;
|
||||
|
||||
HistoryMediaPtr _media;
|
||||
|
||||
};
|
||||
|
||||
// make all the constructors in HistoryItem children protected
|
||||
// and wrapped with a static create() call with the same args
|
||||
// so that history item can not be created directly, without
|
||||
// calling a virtual finishCreate() method
|
||||
template <typename T>
|
||||
class HistoryItemInstantiated {
|
||||
public:
|
||||
template <typename ... Args>
|
||||
static T *_create(Args ... args) {
|
||||
T *result = new T(args ...);
|
||||
result->finishCreate();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
class MessageClickHandler : public LeftButtonClickHandler {
|
||||
public:
|
||||
MessageClickHandler(PeerId peer, MsgId msgid) : _peer(peer), _msgid(msgid) {
|
||||
}
|
||||
MessageClickHandler(HistoryItem *item) : _peer(item->history()->peer->id), _msgid(item->id) {
|
||||
}
|
||||
PeerId peer() const {
|
||||
return _peer;
|
||||
}
|
||||
MsgId msgid() const {
|
||||
return _msgid;
|
||||
}
|
||||
|
||||
private:
|
||||
PeerId _peer;
|
||||
MsgId _msgid;
|
||||
|
||||
};
|
||||
|
||||
class GoToMessageClickHandler : public MessageClickHandler {
|
||||
public:
|
||||
using MessageClickHandler::MessageClickHandler;
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
};
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
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-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
#include "history/history_location_manager.h"
|
||||
|
||||
#include "mainwidget.h"
|
||||
#include "lang.h"
|
||||
#include "pspecific.h"
|
||||
|
||||
QString LocationClickHandler::copyToClipboardContextItemText() const {
|
||||
return lang(lng_context_copy_link);
|
||||
}
|
||||
|
||||
void LocationClickHandler::onClick(Qt::MouseButton button) const {
|
||||
if (!psLaunchMaps(_coords)) {
|
||||
QDesktopServices::openUrl(_text);
|
||||
}
|
||||
}
|
||||
|
||||
void LocationClickHandler::setup() {
|
||||
QString latlon(qsl("%1,%2").arg(_coords.lat).arg(_coords.lon));
|
||||
_text = qsl("https://maps.google.com/maps?q=") + latlon + qsl("&ll=") + latlon + qsl("&z=16");
|
||||
}
|
||||
|
||||
namespace {
|
||||
LocationManager *locationManager = nullptr;
|
||||
} // namespace
|
||||
|
||||
void initLocationManager() {
|
||||
if (!locationManager) {
|
||||
locationManager = new LocationManager();
|
||||
locationManager->init();
|
||||
}
|
||||
}
|
||||
|
||||
void reinitLocationManager() {
|
||||
if (locationManager) {
|
||||
locationManager->reinit();
|
||||
}
|
||||
}
|
||||
|
||||
void deinitLocationManager() {
|
||||
if (locationManager) {
|
||||
locationManager->deinit();
|
||||
delete locationManager;
|
||||
locationManager = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void LocationManager::init() {
|
||||
if (manager) delete manager;
|
||||
manager = new QNetworkAccessManager();
|
||||
App::setProxySettings(*manager);
|
||||
|
||||
connect(manager, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), this, SLOT(onFailed(QNetworkReply*)));
|
||||
#ifndef OS_MAC_OLD
|
||||
connect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)), this, SLOT(onFailed(QNetworkReply*)));
|
||||
#endif // OS_MAC_OLD
|
||||
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onFinished(QNetworkReply*)));
|
||||
|
||||
if (black) {
|
||||
delete black->v();
|
||||
delete black;
|
||||
}
|
||||
QImage b(cIntRetinaFactor(), cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
{
|
||||
QPainter p(&b);
|
||||
p.fillRect(QRect(0, 0, cIntRetinaFactor(), cIntRetinaFactor()), st::white->b);
|
||||
}
|
||||
QPixmap p = App::pixmapFromImageInPlace(std_::move(b));
|
||||
p.setDevicePixelRatio(cRetinaFactor());
|
||||
black = new ImagePtr(p, "PNG");
|
||||
}
|
||||
|
||||
void LocationManager::reinit() {
|
||||
if (manager) App::setProxySettings(*manager);
|
||||
}
|
||||
|
||||
void LocationManager::deinit() {
|
||||
if (manager) {
|
||||
delete manager;
|
||||
manager = nullptr;
|
||||
}
|
||||
if (black) {
|
||||
delete black->v();
|
||||
delete black;
|
||||
black = nullptr;
|
||||
}
|
||||
dataLoadings.clear();
|
||||
imageLoadings.clear();
|
||||
}
|
||||
|
||||
void LocationManager::getData(LocationData *data) {
|
||||
if (!manager) {
|
||||
DEBUG_LOG(("App Error: getting image link data without manager init!"));
|
||||
return failed(data);
|
||||
}
|
||||
|
||||
int32 w = st::locationSize.width(), h = st::locationSize.height();
|
||||
int32 zoom = 13, scale = 1;
|
||||
if (cScale() == dbisTwo || cRetina()) {
|
||||
scale = 2;
|
||||
} else {
|
||||
w = convertScale(w);
|
||||
h = convertScale(h);
|
||||
}
|
||||
QString coords = qsl("%1,%2").arg(data->coords.lat).arg(data->coords.lon);
|
||||
QString url = qsl("https://maps.googleapis.com/maps/api/staticmap?center=") + coords + qsl("&zoom=%1&size=%2x%3&maptype=roadmap&scale=%4&markers=color:red|size:big|").arg(zoom).arg(w).arg(h).arg(scale) + coords + qsl("&sensor=false");
|
||||
QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url)));
|
||||
imageLoadings[reply] = data;
|
||||
}
|
||||
|
||||
void LocationManager::onFinished(QNetworkReply *reply) {
|
||||
if (!manager) return;
|
||||
if (reply->error() != QNetworkReply::NoError) return onFailed(reply);
|
||||
|
||||
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
if (statusCode.isValid()) {
|
||||
int status = statusCode.toInt();
|
||||
if (status == 301 || status == 302) {
|
||||
QString loc = reply->header(QNetworkRequest::LocationHeader).toString();
|
||||
if (!loc.isEmpty()) {
|
||||
QMap<QNetworkReply*, LocationData*>::iterator i = dataLoadings.find(reply);
|
||||
if (i != dataLoadings.cend()) {
|
||||
LocationData *d = i.value();
|
||||
if (serverRedirects.constFind(d) == serverRedirects.cend()) {
|
||||
serverRedirects.insert(d, 1);
|
||||
} else if (++serverRedirects[d] > MaxHttpRedirects) {
|
||||
DEBUG_LOG(("Network Error: Too many HTTP redirects in onFinished() for image link: %1").arg(loc));
|
||||
return onFailed(reply);
|
||||
}
|
||||
dataLoadings.erase(i);
|
||||
dataLoadings.insert(manager->get(QNetworkRequest(loc)), d);
|
||||
return;
|
||||
} else if ((i = imageLoadings.find(reply)) != imageLoadings.cend()) {
|
||||
LocationData *d = i.value();
|
||||
if (serverRedirects.constFind(d) == serverRedirects.cend()) {
|
||||
serverRedirects.insert(d, 1);
|
||||
} else if (++serverRedirects[d] > MaxHttpRedirects) {
|
||||
DEBUG_LOG(("Network Error: Too many HTTP redirects in onFinished() for image link: %1").arg(loc));
|
||||
return onFailed(reply);
|
||||
}
|
||||
imageLoadings.erase(i);
|
||||
imageLoadings.insert(manager->get(QNetworkRequest(loc)), d);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (status != 200) {
|
||||
DEBUG_LOG(("Network Error: Bad HTTP status received in onFinished() for image link: %1").arg(status));
|
||||
return onFailed(reply);
|
||||
}
|
||||
}
|
||||
|
||||
LocationData *d = 0;
|
||||
QMap<QNetworkReply*, LocationData*>::iterator i = dataLoadings.find(reply);
|
||||
if (i != dataLoadings.cend()) {
|
||||
d = i.value();
|
||||
dataLoadings.erase(i);
|
||||
|
||||
QJsonParseError e;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &e);
|
||||
if (e.error != QJsonParseError::NoError) {
|
||||
DEBUG_LOG(("JSON Error: Bad json received in onFinished() for image link"));
|
||||
return onFailed(reply);
|
||||
}
|
||||
failed(d);
|
||||
|
||||
if (App::main()) App::main()->update();
|
||||
} else {
|
||||
i = imageLoadings.find(reply);
|
||||
if (i != imageLoadings.cend()) {
|
||||
d = i.value();
|
||||
imageLoadings.erase(i);
|
||||
|
||||
QPixmap thumb;
|
||||
QByteArray format;
|
||||
QByteArray data(reply->readAll());
|
||||
{
|
||||
QBuffer buffer(&data);
|
||||
QImageReader reader(&buffer);
|
||||
#ifndef OS_MAC_OLD
|
||||
reader.setAutoTransform(true);
|
||||
#endif // OS_MAC_OLD
|
||||
thumb = QPixmap::fromImageReader(&reader, Qt::ColorOnly);
|
||||
format = reader.format();
|
||||
thumb.setDevicePixelRatio(cRetinaFactor());
|
||||
if (format.isEmpty()) format = QByteArray("JPG");
|
||||
}
|
||||
d->loading = false;
|
||||
d->thumb = thumb.isNull() ? (*black) : ImagePtr(thumb, format);
|
||||
serverRedirects.remove(d);
|
||||
if (App::main()) App::main()->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocationManager::onFailed(QNetworkReply *reply) {
|
||||
if (!manager) return;
|
||||
|
||||
LocationData *d = 0;
|
||||
QMap<QNetworkReply*, LocationData*>::iterator i = dataLoadings.find(reply);
|
||||
if (i != dataLoadings.cend()) {
|
||||
d = i.value();
|
||||
dataLoadings.erase(i);
|
||||
} else {
|
||||
i = imageLoadings.find(reply);
|
||||
if (i != imageLoadings.cend()) {
|
||||
d = i.value();
|
||||
imageLoadings.erase(i);
|
||||
}
|
||||
}
|
||||
DEBUG_LOG(("Network Error: failed to get data for image link %1,%2 error %3").arg(d ? d->coords.lat : 0).arg(d ? d->coords.lon : 0).arg(reply->errorString()));
|
||||
if (d) {
|
||||
failed(d);
|
||||
}
|
||||
}
|
||||
|
||||
void LocationManager::failed(LocationData *data) {
|
||||
data->loading = false;
|
||||
data->thumb = *black;
|
||||
serverRedirects.remove(data);
|
||||
}
|
||||
|
||||
void LocationData::load() {
|
||||
if (!thumb->isNull()) return thumb->load(false, false);
|
||||
if (loading) return;
|
||||
|
||||
loading = true;
|
||||
if (locationManager) {
|
||||
locationManager->getData(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
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-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
void initLocationManager();
|
||||
void reinitLocationManager();
|
||||
void deinitLocationManager();
|
||||
|
||||
struct LocationCoords {
|
||||
LocationCoords() : lat(0), lon(0) {
|
||||
}
|
||||
LocationCoords(float64 lat, float64 lon) : lat(lat), lon(lon) {
|
||||
}
|
||||
LocationCoords(const MTPDgeoPoint &point) : lat(point.vlat.v), lon(point.vlong.v) {
|
||||
}
|
||||
float64 lat, lon;
|
||||
};
|
||||
inline bool operator==(const LocationCoords &a, const LocationCoords &b) {
|
||||
return (a.lat == b.lat) && (a.lon == b.lon);
|
||||
}
|
||||
inline bool operator<(const LocationCoords &a, const LocationCoords &b) {
|
||||
return (a.lat < b.lat) || ((a.lat == b.lat) && (a.lon < b.lon));
|
||||
}
|
||||
inline uint qHash(const LocationCoords &t, uint seed = 0) {
|
||||
#ifndef OS_MAC_OLD
|
||||
return qHash(QtPrivate::QHashCombine().operator()(qHash(t.lat), t.lon), seed);
|
||||
#else // OS_MAC_OLD
|
||||
uint h1 = qHash(t.lat, seed);
|
||||
uint h2 = qHash(t.lon, seed);
|
||||
return ((h1 << 16) | (h1 >> 16)) ^ h2 ^ seed;
|
||||
#endif // OS_MAC_OLD
|
||||
}
|
||||
|
||||
struct LocationData {
|
||||
LocationData(const LocationCoords &coords) : coords(coords), loading(false) {
|
||||
}
|
||||
|
||||
LocationCoords coords;
|
||||
ImagePtr thumb;
|
||||
bool loading;
|
||||
|
||||
void load();
|
||||
};
|
||||
|
||||
class LocationClickHandler : public ClickHandler {
|
||||
public:
|
||||
LocationClickHandler(const LocationCoords &coords) : _coords(coords) {
|
||||
setup();
|
||||
}
|
||||
|
||||
void onClick(Qt::MouseButton button) const override;
|
||||
|
||||
QString tooltip() const override {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString dragText() const override {
|
||||
return _text;
|
||||
}
|
||||
|
||||
void copyToClipboard() const override {
|
||||
if (!_text.isEmpty()) {
|
||||
QApplication::clipboard()->setText(_text);
|
||||
}
|
||||
}
|
||||
QString copyToClipboardContextItemText() const override;
|
||||
|
||||
private:
|
||||
|
||||
void setup();
|
||||
LocationCoords _coords;
|
||||
QString _text;
|
||||
|
||||
};
|
||||
|
||||
class LocationManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
void init();
|
||||
void reinit();
|
||||
void deinit();
|
||||
|
||||
void getData(LocationData *data);
|
||||
|
||||
~LocationManager() {
|
||||
deinit();
|
||||
}
|
||||
|
||||
public slots:
|
||||
void onFinished(QNetworkReply *reply);
|
||||
void onFailed(QNetworkReply *reply);
|
||||
|
||||
private:
|
||||
void failed(LocationData *data);
|
||||
|
||||
QNetworkAccessManager *manager = nullptr;
|
||||
QMap<QNetworkReply*, LocationData*> dataLoadings, imageLoadings;
|
||||
QMap<LocationData*, int32> serverRedirects;
|
||||
ImagePtr *black = nullptr;
|
||||
|
||||
};
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
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-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
enum class MediaInBubbleState {
|
||||
None,
|
||||
Top,
|
||||
Middle,
|
||||
Bottom,
|
||||
};
|
||||
|
||||
class HistoryMedia : public HistoryElement {
|
||||
public:
|
||||
HistoryMedia(HistoryItem *parent) : _parent(parent) {
|
||||
}
|
||||
|
||||
virtual HistoryMediaType type() const = 0;
|
||||
|
||||
virtual QString notificationText() const {
|
||||
return QString();
|
||||
}
|
||||
|
||||
// Returns text with link-start and link-end commands for service-color highlighting.
|
||||
// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
|
||||
virtual QString inDialogsText() const {
|
||||
auto result = notificationText();
|
||||
return result.isEmpty() ? QString() : textcmdLink(1, textClean(result));
|
||||
}
|
||||
virtual TextWithEntities selectedText(TextSelection selection) const = 0;
|
||||
|
||||
bool hasPoint(int x, int y) const {
|
||||
return (x >= 0 && y >= 0 && x < _width && y < _height);
|
||||
}
|
||||
|
||||
virtual bool isDisplayed() const {
|
||||
return true;
|
||||
}
|
||||
virtual bool isAboveMessage() const {
|
||||
return false;
|
||||
}
|
||||
virtual bool hasTextForCopy() const {
|
||||
return false;
|
||||
}
|
||||
virtual void initDimensions() = 0;
|
||||
virtual int resizeGetHeight(int width) {
|
||||
_width = qMin(width, _maxw);
|
||||
return _height;
|
||||
}
|
||||
virtual void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const = 0;
|
||||
virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0;
|
||||
|
||||
// if we are in selecting items mode perhaps we want to
|
||||
// toggle selection instead of activating the pressed link
|
||||
virtual bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const = 0;
|
||||
|
||||
// if we press and drag on this media should we drag the item
|
||||
virtual bool dragItem() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual TextSelection adjustSelection(TextSelection selection, TextSelectType type) const {
|
||||
return selection;
|
||||
}
|
||||
virtual bool consumeMessageText(const TextWithEntities &textWithEntities) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we press and drag this link should we drag the item
|
||||
virtual bool dragItemByHandler(const ClickHandlerPtr &p) const = 0;
|
||||
|
||||
virtual void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
||||
}
|
||||
virtual void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
|
||||
}
|
||||
|
||||
virtual bool uploading() const {
|
||||
return false;
|
||||
}
|
||||
virtual HistoryMedia *clone(HistoryItem *newParent) const = 0;
|
||||
|
||||
virtual DocumentData *getDocument() {
|
||||
return nullptr;
|
||||
}
|
||||
virtual Media::Clip::Reader *getClipReader() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool playInline(/*bool autoplay = false*/) {
|
||||
return playInline(false);
|
||||
}
|
||||
virtual bool playInline(bool autoplay) {
|
||||
return false;
|
||||
}
|
||||
virtual void stopInline() {
|
||||
}
|
||||
|
||||
virtual void attachToParent() {
|
||||
}
|
||||
|
||||
virtual void detachFromParent() {
|
||||
}
|
||||
|
||||
virtual void updateSentMedia(const MTPMessageMedia &media) {
|
||||
}
|
||||
|
||||
// After sending an inline result we may want to completely recreate
|
||||
// the media (all media that was generated on client side, for example)
|
||||
virtual bool needReSetInlineResultMedia(const MTPMessageMedia &media) {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool animating() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool hasReplyPreview() const {
|
||||
return false;
|
||||
}
|
||||
virtual ImagePtr replyPreview() {
|
||||
return ImagePtr();
|
||||
}
|
||||
virtual TextWithEntities getCaption() const {
|
||||
return TextWithEntities();
|
||||
}
|
||||
virtual bool needsBubble() const = 0;
|
||||
virtual bool customInfoLayout() const = 0;
|
||||
virtual QMargins bubbleMargins() const {
|
||||
return QMargins();
|
||||
}
|
||||
virtual bool hideFromName() const {
|
||||
return false;
|
||||
}
|
||||
virtual bool hideForwardedFrom() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
int currentWidth() const {
|
||||
return _width;
|
||||
}
|
||||
|
||||
void setInBubbleState(MediaInBubbleState state) {
|
||||
_inBubbleState = state;
|
||||
}
|
||||
MediaInBubbleState inBubbleState() const {
|
||||
return _inBubbleState;
|
||||
}
|
||||
bool isBubbleTop() const {
|
||||
return (_inBubbleState == MediaInBubbleState::Top) || (_inBubbleState == MediaInBubbleState::None);
|
||||
}
|
||||
bool isBubbleBottom() const {
|
||||
return (_inBubbleState == MediaInBubbleState::Bottom) || (_inBubbleState == MediaInBubbleState::None);
|
||||
}
|
||||
|
||||
protected:
|
||||
HistoryItem *_parent;
|
||||
int _width = 0;
|
||||
MediaInBubbleState _inBubbleState = MediaInBubbleState::None;
|
||||
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,912 @@
|
|||
/*
|
||||
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-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/effects/radial_animation.h"
|
||||
|
||||
void historyInitMedia();
|
||||
|
||||
class HistoryFileMedia : public HistoryMedia {
|
||||
public:
|
||||
using HistoryMedia::HistoryMedia;
|
||||
|
||||
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
|
||||
return p == _openl || p == _savel || p == _cancell;
|
||||
}
|
||||
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
|
||||
return p == _openl || p == _savel || p == _cancell;
|
||||
}
|
||||
|
||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
|
||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
||||
|
||||
~HistoryFileMedia();
|
||||
|
||||
protected:
|
||||
ClickHandlerPtr _openl, _savel, _cancell;
|
||||
void setLinks(ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell);
|
||||
void setDocumentLinks(DocumentData *document, bool inlinegif = false) {
|
||||
ClickHandlerPtr open, save;
|
||||
if (inlinegif) {
|
||||
open.reset(new GifOpenClickHandler(document));
|
||||
} else {
|
||||
open.reset(new DocumentOpenClickHandler(document));
|
||||
}
|
||||
if (inlinegif) {
|
||||
save.reset(new GifOpenClickHandler(document));
|
||||
} else if (document->voice()) {
|
||||
save.reset(new DocumentOpenClickHandler(document));
|
||||
} else {
|
||||
save.reset(new DocumentSaveClickHandler(document));
|
||||
}
|
||||
setLinks(std_::move(open), std_::move(save), MakeShared<DocumentCancelClickHandler>(document));
|
||||
}
|
||||
|
||||
// >= 0 will contain download / upload string, _statusSize = loaded bytes
|
||||
// < 0 will contain played string, _statusSize = -(seconds + 1) played
|
||||
// 0x7FFFFFF0 will contain status for not yet downloaded file
|
||||
// 0x7FFFFFF1 will contain status for already downloaded file
|
||||
// 0x7FFFFFF2 will contain status for failed to download / upload file
|
||||
mutable int32 _statusSize;
|
||||
mutable QString _statusText;
|
||||
|
||||
// duration = -1 - no duration, duration = -2 - "GIF" duration
|
||||
void setStatusSize(int32 newSize, int32 fullSize, int32 duration, qint64 realDuration) const;
|
||||
|
||||
void step_thumbOver(float64 ms, bool timer);
|
||||
void step_radial(uint64 ms, bool timer);
|
||||
|
||||
void ensureAnimation() const;
|
||||
void checkAnimationFinished();
|
||||
|
||||
bool isRadialAnimation(uint64 ms) const {
|
||||
if (!_animation || !_animation->radial.animating()) return false;
|
||||
|
||||
_animation->radial.step(ms);
|
||||
return _animation && _animation->radial.animating();
|
||||
}
|
||||
bool isThumbAnimation(uint64 ms) const {
|
||||
if (!_animation || !_animation->_a_thumbOver.animating()) return false;
|
||||
|
||||
_animation->_a_thumbOver.step(ms);
|
||||
return _animation && _animation->_a_thumbOver.animating();
|
||||
}
|
||||
|
||||
virtual float64 dataProgress() const = 0;
|
||||
virtual bool dataFinished() const = 0;
|
||||
virtual bool dataLoaded() const = 0;
|
||||
|
||||
struct AnimationData {
|
||||
AnimationData(AnimationCallbacks &&thumbOverCallbacks, AnimationCallbacks &&radialCallbacks) : a_thumbOver(0, 0)
|
||||
, _a_thumbOver(std_::move(thumbOverCallbacks))
|
||||
, radial(std_::move(radialCallbacks)) {
|
||||
}
|
||||
anim::fvalue a_thumbOver;
|
||||
Animation _a_thumbOver;
|
||||
|
||||
Ui::RadialAnimation radial;
|
||||
};
|
||||
mutable AnimationData *_animation = nullptr;
|
||||
|
||||
};
|
||||
|
||||
class HistoryPhoto : public HistoryFileMedia {
|
||||
public:
|
||||
HistoryPhoto(HistoryItem *parent, PhotoData *photo, const QString &caption);
|
||||
HistoryPhoto(HistoryItem *parent, PeerData *chat, const MTPDphoto &photo, int width);
|
||||
HistoryPhoto(HistoryItem *parent, const HistoryPhoto &other);
|
||||
|
||||
void init();
|
||||
HistoryMediaType type() const override {
|
||||
return MediaTypePhoto;
|
||||
}
|
||||
HistoryPhoto *clone(HistoryItem *newParent) const override {
|
||||
return new HistoryPhoto(newParent, *this);
|
||||
}
|
||||
|
||||
void initDimensions() override;
|
||||
int resizeGetHeight(int width) override;
|
||||
|
||||
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
|
||||
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
|
||||
|
||||
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
|
||||
return _caption.adjustSelection(selection, type);
|
||||
}
|
||||
bool hasTextForCopy() const override {
|
||||
return !_caption.isEmpty();
|
||||
}
|
||||
|
||||
QString notificationText() const override;
|
||||
QString inDialogsText() const override;
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
|
||||
PhotoData *photo() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
void updateSentMedia(const MTPMessageMedia &media) override;
|
||||
bool needReSetInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
|
||||
void attachToParent() override;
|
||||
void detachFromParent() override;
|
||||
|
||||
bool hasReplyPreview() const override {
|
||||
return !_data->thumb->isNull();
|
||||
}
|
||||
ImagePtr replyPreview() override;
|
||||
|
||||
TextWithEntities getCaption() const override {
|
||||
return _caption.originalTextWithEntities();
|
||||
}
|
||||
bool needsBubble() const override {
|
||||
if (!_caption.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (_parent->viaBot()) {
|
||||
return true;
|
||||
}
|
||||
return (_parent->Has<HistoryMessageForwarded>() || _parent->Has<HistoryMessageReply>());
|
||||
}
|
||||
bool customInfoLayout() const override {
|
||||
return _caption.isEmpty();
|
||||
}
|
||||
bool hideFromName() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
float64 dataProgress() const override {
|
||||
return _data->progress();
|
||||
}
|
||||
bool dataFinished() const override {
|
||||
return !_data->loading() && !_data->uploading();
|
||||
}
|
||||
bool dataLoaded() const override {
|
||||
return _data->loaded();
|
||||
}
|
||||
|
||||
private:
|
||||
PhotoData *_data;
|
||||
int16 _pixw = 1;
|
||||
int16 _pixh = 1;
|
||||
Text _caption;
|
||||
|
||||
};
|
||||
|
||||
class HistoryVideo : public HistoryFileMedia {
|
||||
public:
|
||||
HistoryVideo(HistoryItem *parent, DocumentData *document, const QString &caption);
|
||||
HistoryVideo(HistoryItem *parent, const HistoryVideo &other);
|
||||
HistoryMediaType type() const override {
|
||||
return MediaTypeVideo;
|
||||
}
|
||||
HistoryVideo *clone(HistoryItem *newParent) const override {
|
||||
return new HistoryVideo(newParent, *this);
|
||||
}
|
||||
|
||||
void initDimensions() override;
|
||||
int resizeGetHeight(int width) override;
|
||||
|
||||
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
|
||||
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
|
||||
|
||||
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
|
||||
return _caption.adjustSelection(selection, type);
|
||||
}
|
||||
bool hasTextForCopy() const override {
|
||||
return !_caption.isEmpty();
|
||||
}
|
||||
|
||||
QString notificationText() const override;
|
||||
QString inDialogsText() const override;
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
|
||||
DocumentData *getDocument() override {
|
||||
return _data;
|
||||
}
|
||||
|
||||
bool uploading() const override {
|
||||
return _data->uploading();
|
||||
}
|
||||
|
||||
void attachToParent() override;
|
||||
void detachFromParent() override;
|
||||
|
||||
bool needReSetInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
|
||||
bool hasReplyPreview() const override {
|
||||
return !_data->thumb->isNull();
|
||||
}
|
||||
ImagePtr replyPreview() override;
|
||||
|
||||
TextWithEntities getCaption() const override {
|
||||
return _caption.originalTextWithEntities();
|
||||
}
|
||||
bool needsBubble() const override {
|
||||
if (!_caption.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (_parent->viaBot()) {
|
||||
return true;
|
||||
}
|
||||
return (_parent->Has<HistoryMessageForwarded>() || _parent->Has<HistoryMessageReply>());
|
||||
}
|
||||
bool customInfoLayout() const override {
|
||||
return _caption.isEmpty();
|
||||
}
|
||||
bool hideFromName() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
float64 dataProgress() const override {
|
||||
return _data->progress();
|
||||
}
|
||||
bool dataFinished() const override {
|
||||
return !_data->loading() && !_data->uploading();
|
||||
}
|
||||
bool dataLoaded() const override {
|
||||
return _data->loaded();
|
||||
}
|
||||
|
||||
private:
|
||||
DocumentData *_data;
|
||||
int32 _thumbw;
|
||||
Text _caption;
|
||||
|
||||
void setStatusSize(int32 newSize) const;
|
||||
void updateStatusText() const;
|
||||
|
||||
};
|
||||
|
||||
struct HistoryDocumentThumbed : public BaseComponent<HistoryDocumentThumbed> {
|
||||
ClickHandlerPtr _linksavel, _linkcancell;
|
||||
int _thumbw = 0;
|
||||
|
||||
mutable int _linkw = 0;
|
||||
mutable QString _link;
|
||||
};
|
||||
struct HistoryDocumentCaptioned : public BaseComponent<HistoryDocumentCaptioned> {
|
||||
Text _caption = { int(st::msgFileMinWidth) - st::msgPadding.left() - st::msgPadding.right() };
|
||||
};
|
||||
struct HistoryDocumentNamed : public BaseComponent<HistoryDocumentNamed> {
|
||||
QString _name;
|
||||
int _namew = 0;
|
||||
};
|
||||
class HistoryDocument;
|
||||
struct HistoryDocumentVoicePlayback {
|
||||
HistoryDocumentVoicePlayback(const HistoryDocument *that);
|
||||
|
||||
int32 _position;
|
||||
anim::fvalue a_progress;
|
||||
Animation _a_progress;
|
||||
};
|
||||
struct HistoryDocumentVoice : public BaseComponent<HistoryDocumentVoice> {
|
||||
HistoryDocumentVoice &operator=(HistoryDocumentVoice &&other) {
|
||||
std::swap(_playback, other._playback);
|
||||
return *this;
|
||||
}
|
||||
~HistoryDocumentVoice() {
|
||||
deleteAndMark(_playback);
|
||||
}
|
||||
void ensurePlayback(const HistoryDocument *interfaces) const;
|
||||
void checkPlaybackFinished() const;
|
||||
mutable HistoryDocumentVoicePlayback *_playback = nullptr;
|
||||
};
|
||||
|
||||
class HistoryDocument : public HistoryFileMedia, public Composer {
|
||||
public:
|
||||
HistoryDocument(HistoryItem *parent, DocumentData *document, const QString &caption);
|
||||
HistoryDocument(HistoryItem *parent, const HistoryDocument &other);
|
||||
HistoryMediaType type() const override {
|
||||
return _data->voice() ? MediaTypeVoiceFile : (_data->song() ? MediaTypeMusicFile : MediaTypeFile);
|
||||
}
|
||||
HistoryDocument *clone(HistoryItem *newParent) const override {
|
||||
return new HistoryDocument(newParent, *this);
|
||||
}
|
||||
|
||||
void initDimensions() override;
|
||||
int resizeGetHeight(int width) override;
|
||||
|
||||
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
|
||||
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
|
||||
|
||||
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
|
||||
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||
return captioned->_caption.adjustSelection(selection, type);
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
bool hasTextForCopy() const override {
|
||||
return Has<HistoryDocumentCaptioned>();
|
||||
}
|
||||
|
||||
QString notificationText() const override;
|
||||
QString inDialogsText() const override;
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
|
||||
bool uploading() const override {
|
||||
return _data->uploading();
|
||||
}
|
||||
|
||||
DocumentData *getDocument() override {
|
||||
return _data;
|
||||
}
|
||||
|
||||
void attachToParent() override;
|
||||
void detachFromParent() override;
|
||||
|
||||
void updateSentMedia(const MTPMessageMedia &media) override;
|
||||
bool needReSetInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
|
||||
bool hasReplyPreview() const override {
|
||||
return !_data->thumb->isNull();
|
||||
}
|
||||
ImagePtr replyPreview() override;
|
||||
|
||||
TextWithEntities getCaption() const override {
|
||||
if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
|
||||
return captioned->_caption.originalTextWithEntities();
|
||||
}
|
||||
return TextWithEntities();
|
||||
}
|
||||
bool needsBubble() const override {
|
||||
return true;
|
||||
}
|
||||
bool customInfoLayout() const override {
|
||||
return false;
|
||||
}
|
||||
QMargins bubbleMargins() const override {
|
||||
return Get<HistoryDocumentThumbed>() ? QMargins(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbPadding.left(), st::msgFileThumbPadding.bottom()) : st::msgPadding;
|
||||
}
|
||||
bool hideForwardedFrom() const override {
|
||||
return _data->song();
|
||||
}
|
||||
|
||||
void step_voiceProgress(float64 ms, bool timer);
|
||||
|
||||
protected:
|
||||
float64 dataProgress() const override {
|
||||
return _data->progress();
|
||||
}
|
||||
bool dataFinished() const override {
|
||||
return !_data->loading() && !_data->uploading();
|
||||
}
|
||||
bool dataLoaded() const override {
|
||||
return _data->loaded();
|
||||
}
|
||||
|
||||
private:
|
||||
void createComponents(bool caption);
|
||||
|
||||
void setStatusSize(int32 newSize, qint64 realDuration = 0) const;
|
||||
bool updateStatusText() const; // returns showPause
|
||||
|
||||
// Callback is a void(const QString &, const QString &, const Text &) functor.
|
||||
// It will be called as callback(attachType, attachFileName, attachCaption).
|
||||
template <typename Callback>
|
||||
void buildStringRepresentation(Callback callback) const;
|
||||
|
||||
DocumentData *_data;
|
||||
|
||||
};
|
||||
|
||||
class HistoryGif : public HistoryFileMedia {
|
||||
public:
|
||||
HistoryGif(HistoryItem *parent, DocumentData *document, const QString &caption);
|
||||
HistoryGif(HistoryItem *parent, const HistoryGif &other);
|
||||
HistoryMediaType type() const override {
|
||||
return MediaTypeGif;
|
||||
}
|
||||
HistoryGif *clone(HistoryItem *newParent) const override {
|
||||
return new HistoryGif(newParent, *this);
|
||||
}
|
||||
|
||||
void initDimensions() override;
|
||||
int resizeGetHeight(int width) override;
|
||||
|
||||
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
|
||||
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
|
||||
|
||||
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
|
||||
return _caption.adjustSelection(selection, type);
|
||||
}
|
||||
bool hasTextForCopy() const override {
|
||||
return !_caption.isEmpty();
|
||||
}
|
||||
|
||||
QString notificationText() const override;
|
||||
QString inDialogsText() const override;
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
|
||||
bool uploading() const override {
|
||||
return _data->uploading();
|
||||
}
|
||||
|
||||
DocumentData *getDocument() override {
|
||||
return _data;
|
||||
}
|
||||
Media::Clip::Reader *getClipReader() override {
|
||||
return _gif.get();
|
||||
}
|
||||
|
||||
bool playInline(bool autoplay) override;
|
||||
void stopInline() override;
|
||||
|
||||
void attachToParent() override;
|
||||
void detachFromParent() override;
|
||||
|
||||
void updateSentMedia(const MTPMessageMedia &media) override;
|
||||
bool needReSetInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
|
||||
bool hasReplyPreview() const override {
|
||||
return !_data->thumb->isNull();
|
||||
}
|
||||
ImagePtr replyPreview() override;
|
||||
|
||||
TextWithEntities getCaption() const override {
|
||||
return _caption.originalTextWithEntities();
|
||||
}
|
||||
bool needsBubble() const override {
|
||||
if (!_caption.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (_parent->viaBot()) {
|
||||
return true;
|
||||
}
|
||||
return (_parent->Has<HistoryMessageForwarded>() || _parent->Has<HistoryMessageReply>());
|
||||
}
|
||||
bool customInfoLayout() const override {
|
||||
return _caption.isEmpty();
|
||||
}
|
||||
bool hideFromName() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
~HistoryGif();
|
||||
|
||||
protected:
|
||||
float64 dataProgress() const override;
|
||||
bool dataFinished() const override;
|
||||
bool dataLoaded() const override;
|
||||
|
||||
private:
|
||||
DocumentData *_data;
|
||||
int32 _thumbw = 1;
|
||||
int32 _thumbh = 1;
|
||||
Text _caption;
|
||||
|
||||
Media::Clip::ReaderPointer _gif;
|
||||
|
||||
void setStatusSize(int32 newSize) const;
|
||||
void updateStatusText() const;
|
||||
|
||||
};
|
||||
|
||||
class HistorySticker : public HistoryMedia {
|
||||
public:
|
||||
HistorySticker(HistoryItem *parent, DocumentData *document);
|
||||
HistoryMediaType type() const override {
|
||||
return MediaTypeSticker;
|
||||
}
|
||||
HistorySticker *clone(HistoryItem *newParent) const override {
|
||||
return new HistorySticker(newParent, _data);
|
||||
}
|
||||
|
||||
void initDimensions() override;
|
||||
int resizeGetHeight(int width) override;
|
||||
|
||||
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
|
||||
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
|
||||
|
||||
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
|
||||
return true;
|
||||
}
|
||||
bool dragItem() const override {
|
||||
return true;
|
||||
}
|
||||
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
QString notificationText() const override;
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
|
||||
DocumentData *getDocument() override {
|
||||
return _data;
|
||||
}
|
||||
|
||||
void attachToParent() override;
|
||||
void detachFromParent() override;
|
||||
|
||||
void updateSentMedia(const MTPMessageMedia &media) override;
|
||||
bool needReSetInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
|
||||
bool needsBubble() const override {
|
||||
return false;
|
||||
}
|
||||
bool customInfoLayout() const override {
|
||||
return true;
|
||||
}
|
||||
QString emoji() const {
|
||||
return _emoji;
|
||||
}
|
||||
|
||||
private:
|
||||
int additionalWidth(const HistoryMessageVia *via, const HistoryMessageReply *reply) const;
|
||||
int additionalWidth() const {
|
||||
return additionalWidth(_parent->Get<HistoryMessageVia>(), _parent->Get<HistoryMessageReply>());
|
||||
}
|
||||
QString toString() const;
|
||||
|
||||
int16 _pixw = 1;
|
||||
int16 _pixh = 1;
|
||||
ClickHandlerPtr _packLink;
|
||||
DocumentData *_data;
|
||||
QString _emoji;
|
||||
|
||||
};
|
||||
|
||||
class HistoryContact : public HistoryMedia {
|
||||
public:
|
||||
HistoryContact(HistoryItem *parent, int32 userId, const QString &first, const QString &last, const QString &phone);
|
||||
HistoryMediaType type() const override {
|
||||
return MediaTypeContact;
|
||||
}
|
||||
HistoryContact *clone(HistoryItem *newParent) const override {
|
||||
return new HistoryContact(newParent, _userId, _fname, _lname, _phone);
|
||||
}
|
||||
|
||||
void initDimensions() override;
|
||||
|
||||
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
|
||||
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
|
||||
|
||||
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
|
||||
return true;
|
||||
}
|
||||
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
QString notificationText() const override;
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
|
||||
void attachToParent() override;
|
||||
void detachFromParent() override;
|
||||
|
||||
void updateSentMedia(const MTPMessageMedia &media) override;
|
||||
|
||||
bool needsBubble() const override {
|
||||
return true;
|
||||
}
|
||||
bool customInfoLayout() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString &fname() const {
|
||||
return _fname;
|
||||
}
|
||||
const QString &lname() const {
|
||||
return _lname;
|
||||
}
|
||||
const QString &phone() const {
|
||||
return _phone;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
int32 _userId;
|
||||
UserData *_contact;
|
||||
|
||||
int32 _phonew;
|
||||
QString _fname, _lname, _phone;
|
||||
Text _name;
|
||||
|
||||
ClickHandlerPtr _linkl;
|
||||
int32 _linkw;
|
||||
QString _link;
|
||||
};
|
||||
|
||||
class HistoryWebPage : public HistoryMedia {
|
||||
public:
|
||||
HistoryWebPage(HistoryItem *parent, WebPageData *data);
|
||||
HistoryWebPage(HistoryItem *parent, const HistoryWebPage &other);
|
||||
HistoryMediaType type() const override {
|
||||
return MediaTypeWebPage;
|
||||
}
|
||||
HistoryWebPage *clone(HistoryItem *newParent) const override {
|
||||
return new HistoryWebPage(newParent, *this);
|
||||
}
|
||||
|
||||
void initDimensions() override;
|
||||
int resizeGetHeight(int width) override;
|
||||
|
||||
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
|
||||
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
|
||||
|
||||
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;
|
||||
bool hasTextForCopy() const override {
|
||||
return false; // we do not add _title and _description in FullSelection text copy.
|
||||
}
|
||||
|
||||
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
|
||||
return _attach && _attach->toggleSelectionByHandlerClick(p);
|
||||
}
|
||||
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
|
||||
return _attach && _attach->dragItemByHandler(p);
|
||||
}
|
||||
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
|
||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
|
||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
||||
|
||||
bool isDisplayed() const override {
|
||||
return !_data->pendingTill;
|
||||
}
|
||||
DocumentData *getDocument() override {
|
||||
return _attach ? _attach->getDocument() : 0;
|
||||
}
|
||||
Media::Clip::Reader *getClipReader() override {
|
||||
return _attach ? _attach->getClipReader() : 0;
|
||||
}
|
||||
bool playInline(bool autoplay) override {
|
||||
return _attach ? _attach->playInline(autoplay) : false;
|
||||
}
|
||||
void stopInline() override {
|
||||
if (_attach) _attach->stopInline();
|
||||
}
|
||||
|
||||
void attachToParent() override;
|
||||
void detachFromParent() override;
|
||||
|
||||
bool hasReplyPreview() const override {
|
||||
return (_data->photo && !_data->photo->thumb->isNull()) || (_data->document && !_data->document->thumb->isNull());
|
||||
}
|
||||
ImagePtr replyPreview() override;
|
||||
|
||||
WebPageData *webpage() {
|
||||
return _data;
|
||||
}
|
||||
|
||||
bool needsBubble() const override {
|
||||
return true;
|
||||
}
|
||||
bool customInfoLayout() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
HistoryMedia *attach() const {
|
||||
return _attach.get();
|
||||
}
|
||||
|
||||
private:
|
||||
TextSelection toDescriptionSelection(TextSelection selection) const {
|
||||
return internal::unshiftSelection(selection, _title);
|
||||
}
|
||||
TextSelection fromDescriptionSelection(TextSelection selection) const {
|
||||
return internal::shiftSelection(selection, _title);
|
||||
}
|
||||
QMargins inBubblePadding() const;
|
||||
int bottomInfoPadding() const;
|
||||
|
||||
WebPageData *_data;
|
||||
ClickHandlerPtr _openl;
|
||||
std_::unique_ptr<HistoryMedia> _attach;
|
||||
|
||||
bool _asArticle = false;
|
||||
int32 _titleLines, _descriptionLines;
|
||||
|
||||
Text _title, _description;
|
||||
int32 _siteNameWidth = 0;
|
||||
|
||||
QString _duration;
|
||||
int32 _durationWidth = 0;
|
||||
|
||||
int16 _pixw = 0;
|
||||
int16 _pixh = 0;
|
||||
};
|
||||
|
||||
class HistoryGame : public HistoryMedia {
|
||||
public:
|
||||
HistoryGame(HistoryItem *parent, GameData *data);
|
||||
HistoryGame(HistoryItem *parent, const HistoryGame &other);
|
||||
HistoryMediaType type() const override {
|
||||
return MediaTypeGame;
|
||||
}
|
||||
HistoryGame *clone(HistoryItem *newParent) const override {
|
||||
return new HistoryGame(newParent, *this);
|
||||
}
|
||||
|
||||
void initDimensions() override;
|
||||
int resizeGetHeight(int width) override;
|
||||
|
||||
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
|
||||
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
|
||||
|
||||
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;
|
||||
bool isAboveMessage() const override {
|
||||
return true;
|
||||
}
|
||||
bool hasTextForCopy() const override {
|
||||
return false; // we do not add _title and _description in FullSelection text copy.
|
||||
}
|
||||
bool consumeMessageText(const TextWithEntities &textWithEntities) override;
|
||||
|
||||
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
|
||||
return _attach && _attach->toggleSelectionByHandlerClick(p);
|
||||
}
|
||||
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
|
||||
return _attach && _attach->dragItemByHandler(p);
|
||||
}
|
||||
|
||||
QString notificationText() const override;
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
|
||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
|
||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
||||
|
||||
DocumentData *getDocument() override {
|
||||
return _attach ? _attach->getDocument() : nullptr;
|
||||
}
|
||||
Media::Clip::Reader *getClipReader() override {
|
||||
return _attach ? _attach->getClipReader() : nullptr;
|
||||
}
|
||||
bool playInline(bool autoplay) override {
|
||||
return _attach ? _attach->playInline(autoplay) : false;
|
||||
}
|
||||
void stopInline() override {
|
||||
if (_attach) _attach->stopInline();
|
||||
}
|
||||
|
||||
void attachToParent() override;
|
||||
void detachFromParent() override;
|
||||
|
||||
bool hasReplyPreview() const override {
|
||||
return (_data->photo && !_data->photo->thumb->isNull()) || (_data->document && !_data->document->thumb->isNull());
|
||||
}
|
||||
ImagePtr replyPreview() override;
|
||||
|
||||
GameData *game() {
|
||||
return _data;
|
||||
}
|
||||
|
||||
void updateSentMedia(const MTPMessageMedia &media) override;
|
||||
bool needReSetInlineResultMedia(const MTPMessageMedia &media) override;
|
||||
|
||||
bool needsBubble() const override {
|
||||
return true;
|
||||
}
|
||||
bool customInfoLayout() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
HistoryMedia *attach() const {
|
||||
return _attach.get();
|
||||
}
|
||||
|
||||
private:
|
||||
TextSelection toDescriptionSelection(TextSelection selection) const {
|
||||
return internal::unshiftSelection(selection, _title);
|
||||
}
|
||||
TextSelection fromDescriptionSelection(TextSelection selection) const {
|
||||
return internal::shiftSelection(selection, _title);
|
||||
}
|
||||
QMargins inBubblePadding() const;
|
||||
int bottomInfoPadding() const;
|
||||
|
||||
GameData *_data;
|
||||
ClickHandlerPtr _openl;
|
||||
std_::unique_ptr<HistoryMedia> _attach;
|
||||
|
||||
int32 _titleLines, _descriptionLines;
|
||||
|
||||
Text _title, _description;
|
||||
|
||||
int _gameTagWidth = 0;
|
||||
|
||||
};
|
||||
|
||||
struct LocationCoords;
|
||||
struct LocationData;
|
||||
|
||||
class HistoryLocation : public HistoryMedia {
|
||||
public:
|
||||
HistoryLocation(HistoryItem *parent, const LocationCoords &coords, const QString &title = QString(), const QString &description = QString());
|
||||
HistoryLocation(HistoryItem *parent, const HistoryLocation &other);
|
||||
HistoryMediaType type() const override {
|
||||
return MediaTypeLocation;
|
||||
}
|
||||
HistoryLocation *clone(HistoryItem *newParent) const override {
|
||||
return new HistoryLocation(newParent, *this);
|
||||
}
|
||||
|
||||
void initDimensions() override;
|
||||
int resizeGetHeight(int32 width) override;
|
||||
|
||||
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
|
||||
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
|
||||
|
||||
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;
|
||||
bool hasTextForCopy() const override {
|
||||
return !_title.isEmpty() || !_description.isEmpty();
|
||||
}
|
||||
|
||||
bool toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const override {
|
||||
return p == _link;
|
||||
}
|
||||
bool dragItemByHandler(const ClickHandlerPtr &p) const override {
|
||||
return p == _link;
|
||||
}
|
||||
|
||||
QString notificationText() const override;
|
||||
QString inDialogsText() const override;
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
|
||||
bool needsBubble() const override {
|
||||
if (!_title.isEmpty() || !_description.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (_parent->viaBot()) {
|
||||
return true;
|
||||
}
|
||||
return (_parent->Has<HistoryMessageForwarded>() || _parent->Has<HistoryMessageReply>());
|
||||
}
|
||||
bool customInfoLayout() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
TextSelection toDescriptionSelection(TextSelection selection) const {
|
||||
return internal::unshiftSelection(selection, _title);
|
||||
}
|
||||
TextSelection fromDescriptionSelection(TextSelection selection) const {
|
||||
return internal::shiftSelection(selection, _title);
|
||||
}
|
||||
|
||||
LocationData *_data;
|
||||
Text _title, _description;
|
||||
ClickHandlerPtr _link;
|
||||
|
||||
int32 fullWidth() const;
|
||||
int32 fullHeight() const;
|
||||
|
||||
};
|
||||
|
||||
class SendMessageClickHandler : public PeerClickHandler {
|
||||
public:
|
||||
using PeerClickHandler::PeerClickHandler;
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
};
|
||||
|
||||
class AddContactClickHandler : public MessageClickHandler {
|
||||
public:
|
||||
using MessageClickHandler::MessageClickHandler;
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,392 @@
|
|||
/*
|
||||
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-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
void historyInitMessages();
|
||||
|
||||
class HistoryMessage : public HistoryItem, private HistoryItemInstantiated<HistoryMessage> {
|
||||
public:
|
||||
static HistoryMessage *create(History *history, const MTPDmessage &msg) {
|
||||
return _create(history, msg);
|
||||
}
|
||||
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd) {
|
||||
return _create(history, msgId, flags, date, from, fwd);
|
||||
}
|
||||
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities) {
|
||||
return _create(history, msgId, flags, replyTo, viaBotId, date, from, textWithEntities);
|
||||
}
|
||||
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
return _create(history, msgId, flags, replyTo, viaBotId, date, from, doc, caption, markup);
|
||||
}
|
||||
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup) {
|
||||
return _create(history, msgId, flags, replyTo, viaBotId, date, from, photo, caption, markup);
|
||||
}
|
||||
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, GameData *game, const MTPReplyMarkup &markup) {
|
||||
return _create(history, msgId, flags, replyTo, viaBotId, date, from, game, markup);
|
||||
}
|
||||
|
||||
void initTime();
|
||||
void initMedia(const MTPMessageMedia *media);
|
||||
void initMediaFromDocument(DocumentData *doc, const QString &caption);
|
||||
void fromNameUpdated(int32 width) const;
|
||||
|
||||
int32 plainMaxWidth() const;
|
||||
void countPositionAndSize(int32 &left, int32 &width) const;
|
||||
|
||||
bool drawBubble() const {
|
||||
return _media ? (!emptyText() || _media->needsBubble()) : !isEmpty();
|
||||
}
|
||||
bool hasBubble() const override {
|
||||
return drawBubble();
|
||||
}
|
||||
bool displayFromName() const {
|
||||
if (!hasFromName()) return false;
|
||||
if (isAttachedToPrevious()) return false;
|
||||
|
||||
return (!emptyText() || !_media || !_media->isDisplayed() || Has<HistoryMessageReply>() || Has<HistoryMessageForwarded>() || viaBot() || !_media->hideFromName());
|
||||
}
|
||||
bool displayEditedBadge(bool hasViaBot) const;
|
||||
bool uploading() const {
|
||||
return _media && _media->uploading();
|
||||
}
|
||||
|
||||
void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const override;
|
||||
void setViewsCount(int32 count) override;
|
||||
void setId(MsgId newId) override;
|
||||
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
|
||||
|
||||
void dependencyItemRemoved(HistoryItem *dependency) override;
|
||||
|
||||
bool hasPoint(int x, int y) const override;
|
||||
bool pointInTime(int32 right, int32 bottom, int x, int y, InfoDisplayType type) const override;
|
||||
|
||||
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
|
||||
|
||||
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;
|
||||
|
||||
// ClickHandlerHost interface
|
||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override {
|
||||
if (_media) _media->clickHandlerActiveChanged(p, active);
|
||||
HistoryItem::clickHandlerActiveChanged(p, active);
|
||||
}
|
||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override {
|
||||
if (_media) _media->clickHandlerPressedChanged(p, pressed);
|
||||
HistoryItem::clickHandlerPressedChanged(p, pressed);
|
||||
}
|
||||
|
||||
QString notificationHeader() const override;
|
||||
|
||||
void applyEdition(const MTPDmessage &message) override;
|
||||
void applyEdition(const MTPDmessageService &message) override;
|
||||
void updateMedia(const MTPMessageMedia *media) override;
|
||||
int32 addToOverview(AddToOverviewMethod method) override;
|
||||
void eraseFromOverview() override;
|
||||
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
void setText(const TextWithEntities &textWithEntities) override;
|
||||
TextWithEntities originalText() const override;
|
||||
bool textHasLinks() const override;
|
||||
|
||||
int32 infoWidth() const override {
|
||||
int32 result = _timeWidth;
|
||||
if (const HistoryMessageViews *views = Get<HistoryMessageViews>()) {
|
||||
result += st::msgDateViewsSpace + views->_viewsWidth + st::msgDateCheckSpace + st::msgViewsImg.pxWidth();
|
||||
} else if (id < 0 && history()->peer->isSelf()) {
|
||||
result += st::msgDateCheckSpace + st::msgCheckImg.pxWidth();
|
||||
}
|
||||
if (out() && !isPost()) {
|
||||
result += st::msgDateCheckSpace + st::msgCheckImg.pxWidth();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
int32 timeLeft() const override {
|
||||
int32 result = 0;
|
||||
if (const HistoryMessageViews *views = Get<HistoryMessageViews>()) {
|
||||
result += st::msgDateViewsSpace + views->_viewsWidth + st::msgDateCheckSpace + st::msgViewsImg.pxWidth();
|
||||
} else if (id < 0 && history()->peer->isSelf()) {
|
||||
result += st::msgDateCheckSpace + st::msgCheckImg.pxWidth();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
int32 timeWidth() const override {
|
||||
return _timeWidth;
|
||||
}
|
||||
|
||||
int32 viewsCount() const override {
|
||||
if (const HistoryMessageViews *views = Get<HistoryMessageViews>()) {
|
||||
return views->_views;
|
||||
}
|
||||
return HistoryItem::viewsCount();
|
||||
}
|
||||
|
||||
bool updateDependencyItem() override {
|
||||
if (auto reply = Get<HistoryMessageReply>()) {
|
||||
return reply->updateData(this, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
MsgId dependencyMsgId() const override {
|
||||
return replyToId();
|
||||
}
|
||||
|
||||
HistoryMessage *toHistoryMessage() override { // dynamic_cast optimize
|
||||
return this;
|
||||
}
|
||||
const HistoryMessage *toHistoryMessage() const override { // dynamic_cast optimize
|
||||
return this;
|
||||
}
|
||||
|
||||
// hasFromPhoto() returns true even if we don't display the photo
|
||||
// but we need to skip a place at the left side for this photo
|
||||
bool displayFromPhoto() const;
|
||||
bool hasFromPhoto() const;
|
||||
|
||||
~HistoryMessage();
|
||||
|
||||
private:
|
||||
HistoryMessage(History *history, const MTPDmessage &msg);
|
||||
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd); // local forwarded
|
||||
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities); // local message
|
||||
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); // local document
|
||||
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); // local photo
|
||||
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, GameData *game, const MTPReplyMarkup &markup); // local game
|
||||
friend class HistoryItemInstantiated<HistoryMessage>;
|
||||
|
||||
void setEmptyText();
|
||||
|
||||
void initDimensions() override;
|
||||
int resizeGetHeight_(int width) override;
|
||||
int performResizeGetHeight(int width);
|
||||
void applyEditionToEmpty();
|
||||
|
||||
bool displayForwardedFrom() const {
|
||||
if (auto fwd = Get<HistoryMessageForwarded>()) {
|
||||
return Has<HistoryMessageVia>() || !_media || !_media->isDisplayed() || fwd->_authorOriginal->isChannel() || !_media->hideForwardedFrom();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
// this method draws "via @bot" if it is not painted in forwarded info or in from name
|
||||
void paintViaBotIdInfo(Painter &p, QRect &trect, bool selected) const;
|
||||
void paintText(Painter &p, QRect &trect, TextSelection selection) const;
|
||||
|
||||
bool getStateFromName(int x, int y, QRect &trect, HistoryTextState *outResult) const;
|
||||
bool getStateForwardedInfo(int x, int y, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const;
|
||||
bool getStateReplyInfo(int x, int y, QRect &trect, HistoryTextState *outResult) const;
|
||||
bool getStateViaBotIdInfo(int x, int y, QRect &trect, HistoryTextState *outResult) const;
|
||||
bool getStateText(int x, int y, QRect &trect, HistoryTextState *outResult, const HistoryStateRequest &request) const;
|
||||
|
||||
void setMedia(const MTPMessageMedia *media);
|
||||
void setReplyMarkup(const MTPReplyMarkup *markup);
|
||||
|
||||
QString _timeText;
|
||||
int _timeWidth = 0;
|
||||
|
||||
struct CreateConfig {
|
||||
MsgId replyTo = 0;
|
||||
UserId viaBotId = 0;
|
||||
int viewsCount = -1;
|
||||
PeerId authorIdOriginal = 0;
|
||||
PeerId fromIdOriginal = 0;
|
||||
MsgId originalId = 0;
|
||||
QDateTime editDate;
|
||||
const MTPReplyMarkup *markup = nullptr;
|
||||
};
|
||||
void createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, const MTPReplyMarkup &markup);
|
||||
void createComponents(const CreateConfig &config);
|
||||
|
||||
class KeyboardStyle : public ReplyKeyboard::Style {
|
||||
public:
|
||||
using ReplyKeyboard::Style::Style;
|
||||
|
||||
void startPaint(Painter &p) const override;
|
||||
style::font textFont() const override;
|
||||
void repaint(const HistoryItem *item) const override;
|
||||
|
||||
protected:
|
||||
void paintButtonBg(Painter &p, const QRect &rect, bool down, float64 howMuchOver) const override;
|
||||
void paintButtonIcon(Painter &p, const QRect &rect, HistoryMessageReplyMarkup::Button::Type type) const override;
|
||||
void paintButtonLoading(Painter &p, const QRect &rect) const override;
|
||||
int minButtonWidth(HistoryMessageReplyMarkup::Button::Type type) const override;
|
||||
|
||||
};
|
||||
|
||||
void updateMediaInBubbleState();
|
||||
|
||||
};
|
||||
|
||||
inline MTPDmessage::Flags newMessageFlags(PeerData *p) {
|
||||
MTPDmessage::Flags result = 0;
|
||||
if (!p->isSelf()) {
|
||||
result |= MTPDmessage::Flag::f_out;
|
||||
//if (p->isChat() || (p->isUser() && !p->asUser()->botInfo)) {
|
||||
// result |= MTPDmessage::Flag::f_unread;
|
||||
//}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
struct HistoryServiceDependentData {
|
||||
MsgId msgId = 0;
|
||||
HistoryItem *msg = nullptr;
|
||||
ClickHandlerPtr lnk;
|
||||
};
|
||||
|
||||
struct HistoryServicePinned : public BaseComponent<HistoryServicePinned>, public HistoryServiceDependentData {
|
||||
};
|
||||
|
||||
struct HistoryServiceGameScore : public BaseComponent<HistoryServiceGameScore>, public HistoryServiceDependentData {
|
||||
int score = 0;
|
||||
};
|
||||
|
||||
namespace HistoryLayout {
|
||||
class ServiceMessagePainter;
|
||||
} // namespace HistoryLayout
|
||||
|
||||
class HistoryService : public HistoryItem, private HistoryItemInstantiated<HistoryService> {
|
||||
public:
|
||||
static HistoryService *create(History *history, const MTPDmessageService &msg) {
|
||||
return _create(history, msg);
|
||||
}
|
||||
static HistoryService *create(History *history, MsgId msgId, QDateTime date, const QString &msg, MTPDmessage::Flags flags = 0, int32 from = 0) {
|
||||
return _create(history, msgId, date, msg, flags, from);
|
||||
}
|
||||
|
||||
bool updateDependencyItem() override;
|
||||
MsgId dependencyMsgId() const override {
|
||||
if (auto dependent = GetDependentData()) {
|
||||
return dependent->msgId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
bool notificationReady() const override {
|
||||
if (auto dependent = GetDependentData()) {
|
||||
return (dependent->msg || !dependent->msgId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void countPositionAndSize(int32 &left, int32 &width) const;
|
||||
|
||||
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
|
||||
bool hasPoint(int x, int y) const override;
|
||||
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
|
||||
|
||||
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
|
||||
return _text.adjustSelection(selection, type);
|
||||
}
|
||||
|
||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override {
|
||||
if (_media) _media->clickHandlerActiveChanged(p, active);
|
||||
HistoryItem::clickHandlerActiveChanged(p, active);
|
||||
}
|
||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override {
|
||||
if (_media) _media->clickHandlerPressedChanged(p, pressed);
|
||||
HistoryItem::clickHandlerPressedChanged(p, pressed);
|
||||
}
|
||||
|
||||
void applyEdition(const MTPDmessageService &message) override;
|
||||
|
||||
int32 addToOverview(AddToOverviewMethod method) override;
|
||||
void eraseFromOverview() override;
|
||||
|
||||
bool needCheck() const override {
|
||||
return false;
|
||||
}
|
||||
bool serviceMsg() const override {
|
||||
return true;
|
||||
}
|
||||
TextWithEntities selectedText(TextSelection selection) const override;
|
||||
QString inDialogsText() const override;
|
||||
QString inReplyText() const override;
|
||||
|
||||
~HistoryService();
|
||||
|
||||
protected:
|
||||
friend class HistoryLayout::ServiceMessagePainter;
|
||||
|
||||
HistoryService(History *history, const MTPDmessageService &msg);
|
||||
HistoryService(History *history, MsgId msgId, QDateTime date, const QString &msg, MTPDmessage::Flags flags = 0, int32 from = 0);
|
||||
friend class HistoryItemInstantiated<HistoryService>;
|
||||
|
||||
void initDimensions() override;
|
||||
int resizeGetHeight_(int width) override;
|
||||
|
||||
using Links = QList<ClickHandlerPtr>;
|
||||
void setServiceText(const QString &text, const Links &links);
|
||||
|
||||
void removeMedia();
|
||||
|
||||
private:
|
||||
HistoryServiceDependentData *GetDependentData() {
|
||||
if (auto pinned = Get<HistoryServicePinned>()) {
|
||||
return pinned;
|
||||
} else if (auto gamescore = Get<HistoryServiceGameScore>()) {
|
||||
return gamescore;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
const HistoryServiceDependentData *GetDependentData() const {
|
||||
return const_cast<HistoryService*>(this)->GetDependentData();
|
||||
}
|
||||
bool updateDependent(bool force = false);
|
||||
bool updateDependentText();
|
||||
void clearDependency();
|
||||
|
||||
void createFromMtp(const MTPDmessageService &message);
|
||||
void setMessageByAction(const MTPmessageAction &action);
|
||||
|
||||
bool preparePinnedText(const QString &from, QString *outText, Links *outLinks);
|
||||
bool prepareGameScoreText(const QString &from, QString *outText, Links *outLinks);
|
||||
|
||||
};
|
||||
|
||||
class HistoryJoined : public HistoryService, private HistoryItemInstantiated<HistoryJoined> {
|
||||
public:
|
||||
static HistoryJoined *create(History *history, const QDateTime &date, UserData *from, MTPDmessage::Flags flags) {
|
||||
return _create(history, date, from, flags);
|
||||
}
|
||||
|
||||
HistoryItemType type() const {
|
||||
return HistoryItemJoined;
|
||||
}
|
||||
|
||||
protected:
|
||||
HistoryJoined(History *history, const QDateTime &date, UserData *from, MTPDmessage::Flags flags);
|
||||
using HistoryItemInstantiated<HistoryJoined>::_create;
|
||||
friend class HistoryItemInstantiated<HistoryJoined>;
|
||||
|
||||
};
|
||||
|
||||
class ViaInlineBotClickHandler : public LeftButtonClickHandler {
|
||||
public:
|
||||
ViaInlineBotClickHandler(UserData *bot) : _bot(bot) {
|
||||
}
|
||||
|
||||
protected:
|
||||
void onClickImpl() const override;
|
||||
|
||||
private:
|
||||
UserData *_bot;
|
||||
|
||||
};
|
|
@ -33,6 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "inline_bots/inline_bot_result.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "history/history_service_layout.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "profile/profile_members_widget.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "stickers/emoji_pan.h"
|
||||
|
@ -4506,6 +4507,7 @@ void HistoryWidget::updateControlsVisibility() {
|
|||
if (!_a_show.animating()) {
|
||||
_topShadow.setVisible(_peer ? true : false);
|
||||
}
|
||||
updateToEndVisibility();
|
||||
if (!_history || _a_show.animating()) {
|
||||
_reportSpamPanel.hide();
|
||||
_scroll.hide();
|
||||
|
@ -4536,7 +4538,6 @@ void HistoryWidget::updateControlsVisibility() {
|
|||
return;
|
||||
}
|
||||
|
||||
updateToEndVisibility();
|
||||
if (_pinnedBar) {
|
||||
_pinnedBar->cancel.show();
|
||||
_pinnedBar->shadow.show();
|
||||
|
@ -5797,16 +5798,13 @@ void HistoryWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button
|
|||
BotCallbackInfo info = { bot, msg->fullId(), row, col, (button->type == ButtonType::Game) };
|
||||
MTPmessages_GetBotCallbackAnswer::Flags flags = 0;
|
||||
QByteArray sendData;
|
||||
int32 sendGameId = 0;
|
||||
if (info.game) {
|
||||
flags = MTPmessages_GetBotCallbackAnswer::Flag::f_game_id;
|
||||
auto strData = QString::fromUtf8(button->data);
|
||||
sendGameId = strData.midRef(0, strData.indexOf(',')).toInt();
|
||||
flags = MTPmessages_GetBotCallbackAnswer::Flag::f_game;
|
||||
} else if (button->type == ButtonType::Callback) {
|
||||
flags = MTPmessages_GetBotCallbackAnswer::Flag::f_data;
|
||||
sendData = button->data;
|
||||
}
|
||||
button->requestId = MTP::send(MTPmessages_GetBotCallbackAnswer(MTP_flags(flags), _peer->input, MTP_int(msg->id), MTP_bytes(sendData), MTP_int(sendGameId)), rpcDone(&HistoryWidget::botCallbackDone, info), rpcFail(&HistoryWidget::botCallbackFail, info));
|
||||
button->requestId = MTP::send(MTPmessages_GetBotCallbackAnswer(MTP_flags(flags), _peer->input, MTP_int(msg->id), MTP_bytes(sendData)), rpcDone(&HistoryWidget::botCallbackDone, info), rpcFail(&HistoryWidget::botCallbackFail, info));
|
||||
Ui::repaintHistoryItem(msg);
|
||||
|
||||
if (_replyToId == msg->id) {
|
||||
|
@ -7382,6 +7380,8 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
|
|||
}
|
||||
|
||||
void HistoryWidget::updateToEndVisibility() {
|
||||
if (_a_show.animating()) return;
|
||||
|
||||
auto haveUnreadBelowBottom = [this](History *history) {
|
||||
if (!_list || !history || history->unreadCount() <= 0) {
|
||||
return false;
|
||||
|
@ -7392,7 +7392,7 @@ void HistoryWidget::updateToEndVisibility() {
|
|||
return (_list->itemTop(history->showFrom) >= _scroll.scrollTop() + _scroll.height());
|
||||
};
|
||||
auto isToEndVisible = [this, &haveUnreadBelowBottom]() {
|
||||
if (!_history || _a_show.animating() || _firstLoadRequest) {
|
||||
if (!_history || _firstLoadRequest) {
|
||||
return false;
|
||||
}
|
||||
if (!_history->loadedAtBottom() || _replyReturn) {
|
||||
|
@ -8138,7 +8138,7 @@ void HistoryWidget::gotPreview(QString links, const MTPMessageMedia &result, mtp
|
|||
_previewRequest = 0;
|
||||
}
|
||||
if (result.type() == mtpc_messageMediaWebPage) {
|
||||
WebPageData *data = App::feedWebPage(result.c_messageMediaWebPage().vwebpage);
|
||||
auto data = App::feedWebPage(result.c_messageMediaWebPage().vwebpage);
|
||||
_previewCache.insert(links, data->id);
|
||||
if (data->pendingTill > 0 && data->pendingTill <= unixtime()) {
|
||||
data->pendingTill = -1;
|
||||
|
@ -8147,7 +8147,7 @@ void HistoryWidget::gotPreview(QString links, const MTPMessageMedia &result, mtp
|
|||
_previewData = (data->id && data->pendingTill >= 0) ? data : 0;
|
||||
updatePreview();
|
||||
}
|
||||
if (App::main()) App::main()->webPagesUpdate();
|
||||
if (App::main()) App::main()->webPagesOrGamesUpdate();
|
||||
} else if (result.type() == mtpc_messageMediaEmpty) {
|
||||
_previewCache.insert(links, 0);
|
||||
if (links == _previewLinks && !_previewCancelled) {
|
||||
|
|
|
@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "inline_bots/inline_bot_result.h"
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "history/history_location_manager.h"
|
||||
#include "localstorage.h"
|
||||
#include "mainwidget.h"
|
||||
#include "lang.h"
|
||||
|
@ -112,8 +113,7 @@ void Gif::initDimensions() {
|
|||
void Gif::setPosition(int32 position) {
|
||||
ItemBase::setPosition(position);
|
||||
if (_position < 0) {
|
||||
if (gif()) delete _gif;
|
||||
_gif = 0;
|
||||
_gif.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,15 +133,15 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
|
|||
document->automaticLoad(nullptr);
|
||||
|
||||
bool loaded = document->loaded(), loading = document->loading(), displayLoading = document->displayLoading();
|
||||
if (loaded && !gif() && _gif != Media::Clip::BadReader) {
|
||||
Gif *that = const_cast<Gif*>(this);
|
||||
that->_gif = new Media::Clip::Reader(document->location(), document->data(), [that](Media::Clip::Notification notification) {
|
||||
if (loaded && !_gif && !_gif.isBad()) {
|
||||
auto that = const_cast<Gif*>(this);
|
||||
that->_gif = Media::Clip::MakeReader(document->location(), document->data(), [that](Media::Clip::Notification notification) {
|
||||
that->clipCallback(notification);
|
||||
});
|
||||
if (gif()) _gif->setAutoplay();
|
||||
if (_gif) _gif->setAutoplay();
|
||||
}
|
||||
|
||||
bool animating = (gif() && _gif->started());
|
||||
bool animating = (_gif && _gif->started());
|
||||
if (displayLoading) {
|
||||
ensureAnimation();
|
||||
if (!_animation->radial.animating()) {
|
||||
|
@ -166,7 +166,7 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
|
|||
}
|
||||
}
|
||||
|
||||
if (radial || (!_gif && !loaded && !loading) || (_gif == Media::Clip::BadReader)) {
|
||||
if (radial || _gif.isBad() || (!_gif && !loaded && !loading)) {
|
||||
auto radialOpacity = (radial && loaded) ? _animation->radial.opacity() : 1.;
|
||||
if (_animation && _animation->_a_over.animating(context->ms)) {
|
||||
auto over = _animation->_a_over.current();
|
||||
|
@ -248,7 +248,7 @@ void Gif::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
|
|||
}
|
||||
|
||||
QSize Gif::countFrameSize() const {
|
||||
bool animating = (gif() && _gif->ready());
|
||||
bool animating = (_gif && _gif->ready());
|
||||
int32 framew = animating ? _gif->width() : content_width(), frameh = animating ? _gif->height() : content_height(), height = st::inlineMediaHeight;
|
||||
if (framew * height > frameh * _width) {
|
||||
if (framew < st::maxStickerSize || frameh > height) {
|
||||
|
@ -274,11 +274,6 @@ QSize Gif::countFrameSize() const {
|
|||
return QSize(framew, frameh);
|
||||
}
|
||||
|
||||
Gif::~Gif() {
|
||||
if (gif()) deleteAndMark(_gif);
|
||||
deleteAndMark(_animation);
|
||||
}
|
||||
|
||||
void Gif::prepareThumb(int32 width, int32 height, const QSize &frame) const {
|
||||
if (DocumentData *document = getShownDocument()) {
|
||||
if (!document->thumb->isNull()) {
|
||||
|
@ -306,7 +301,7 @@ void Gif::prepareThumb(int32 width, int32 height, const QSize &frame) const {
|
|||
|
||||
void Gif::ensureAnimation() const {
|
||||
if (!_animation) {
|
||||
_animation = new AnimationData(animation(const_cast<Gif*>(this), &Gif::step_radial));
|
||||
_animation = std_::make_unique<AnimationData>(animation(const_cast<Gif*>(this), &Gif::step_radial));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,8 +319,7 @@ void Gif::step_radial(uint64 ms, bool timer) {
|
|||
DocumentData *document = getShownDocument();
|
||||
_animation->radial.update(document->progress(), !document->loading() || document->loaded(), ms);
|
||||
if (!_animation->radial.animating() && document->loaded()) {
|
||||
delete _animation;
|
||||
_animation = nullptr;
|
||||
_animation.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -334,18 +328,16 @@ void Gif::clipCallback(Media::Clip::Notification notification) {
|
|||
using namespace Media::Clip;
|
||||
switch (notification) {
|
||||
case NotificationReinit: {
|
||||
if (gif()) {
|
||||
if (_gif) {
|
||||
if (_gif->state() == State::Error) {
|
||||
delete _gif;
|
||||
_gif = BadReader;
|
||||
_gif.setBad();
|
||||
getShownDocument()->forget();
|
||||
} else if (_gif->ready() && !_gif->started()) {
|
||||
int32 height = st::inlineMediaHeight;
|
||||
QSize frame = countFrameSize();
|
||||
_gif->start(frame.width(), frame.height(), _width, height, ImageRoundRadius::None);
|
||||
} else if (_gif->autoPausedGif() && !Ui::isInlineItemVisible(this)) {
|
||||
delete _gif;
|
||||
_gif = nullptr;
|
||||
_gif.reset();
|
||||
getShownDocument()->forget();
|
||||
}
|
||||
}
|
||||
|
@ -354,7 +346,7 @@ void Gif::clipCallback(Media::Clip::Notification notification) {
|
|||
} break;
|
||||
|
||||
case NotificationRepaint: {
|
||||
if (gif() && !_gif->currentDisplayed()) {
|
||||
if (_gif && !_gif->currentDisplayed()) {
|
||||
update();
|
||||
}
|
||||
} break;
|
||||
|
@ -1140,6 +1132,232 @@ void Article::prepareThumb(int width, int height) const {
|
|||
}
|
||||
}
|
||||
|
||||
Game::Game(Result *result) : ItemBase(result)
|
||||
, _title(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip)
|
||||
, _description(st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft - st::inlineThumbSize - st::inlineThumbSkip) {
|
||||
countFrameSize();
|
||||
}
|
||||
|
||||
void Game::countFrameSize() {
|
||||
if (auto document = getResultDocument()) {
|
||||
if (document->isAnimation()) {
|
||||
auto documentSize = document->dimensions;
|
||||
if (documentSize.isEmpty()) {
|
||||
documentSize = QSize(st::inlineThumbSize, st::inlineThumbSize);
|
||||
}
|
||||
auto resizeByHeight1 = (documentSize.width() > documentSize.height()) && (documentSize.height() >= st::inlineThumbSize);
|
||||
auto resizeByHeight2 = (documentSize.height() >= documentSize.width()) && (documentSize.width() < st::inlineThumbSize);
|
||||
if (resizeByHeight1 || resizeByHeight2) {
|
||||
if (documentSize.height() > st::inlineThumbSize) {
|
||||
_frameSize = QSize((documentSize.width() * st::inlineThumbSize) / documentSize.height(), st::inlineThumbSize);
|
||||
}
|
||||
} else {
|
||||
if (documentSize.width() > st::inlineThumbSize) {
|
||||
_frameSize = QSize(st::inlineThumbSize, (documentSize.height() * st::inlineThumbSize) / documentSize.width());
|
||||
}
|
||||
}
|
||||
if (!_frameSize.width()) {
|
||||
_frameSize.setWidth(1);
|
||||
}
|
||||
if (!_frameSize.height()) {
|
||||
_frameSize.setHeight(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Game::initDimensions() {
|
||||
_maxw = st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft;
|
||||
int32 textWidth = _maxw - (st::inlineThumbSize + st::inlineThumbSkip);
|
||||
TextParseOptions titleOpts = { 0, _maxw, 2 * st::semiboldFont->height, Qt::LayoutDirectionAuto };
|
||||
_title.setText(st::semiboldFont, textOneLine(_result->getLayoutTitle()), titleOpts);
|
||||
int32 titleHeight = qMin(_title.countHeight(_maxw), 2 * st::semiboldFont->height);
|
||||
|
||||
int32 descriptionLines = 2;
|
||||
QString description = _result->getLayoutDescription();
|
||||
TextParseOptions descriptionOpts = { TextParseMultiline, _maxw, descriptionLines * st::normalFont->height, Qt::LayoutDirectionAuto };
|
||||
_description.setText(st::normalFont, description, descriptionOpts);
|
||||
int32 descriptionHeight = qMin(_description.countHeight(_maxw), descriptionLines * st::normalFont->height);
|
||||
|
||||
_minh = titleHeight + descriptionHeight;
|
||||
accumulate_max(_minh, st::inlineThumbSize);
|
||||
_minh += st::inlineRowMargin * 2 + st::inlineRowBorder;
|
||||
}
|
||||
|
||||
void Game::setPosition(int32 position) {
|
||||
ItemBase::setPosition(position);
|
||||
if (_position < 0) {
|
||||
_gif.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) const {
|
||||
int32 left = st::emojiPanHeaderLeft - st::inlineResultsLeft;
|
||||
|
||||
left = st::inlineThumbSize + st::inlineThumbSkip;
|
||||
auto rthumb = rtlrect(0, st::inlineRowMargin, st::inlineThumbSize, st::inlineThumbSize, _width);
|
||||
|
||||
// Gif thumb
|
||||
auto thumbDisplayed = false, radial = false;
|
||||
auto document = getResultDocument();
|
||||
auto animatedThumb = document && document->isAnimation();
|
||||
if (animatedThumb) {
|
||||
document->automaticLoad(nullptr);
|
||||
|
||||
bool loaded = document->loaded(), loading = document->loading(), displayLoading = document->displayLoading();
|
||||
if (loaded && !_gif && !_gif.isBad()) {
|
||||
auto that = const_cast<Game*>(this);
|
||||
that->_gif = Media::Clip::MakeReader(document->location(), document->data(), [that](Media::Clip::Notification notification) {
|
||||
that->clipCallback(notification);
|
||||
});
|
||||
if (_gif) _gif->setAutoplay();
|
||||
}
|
||||
|
||||
bool animating = (_gif && _gif->started());
|
||||
if (displayLoading) {
|
||||
if (!_radial) {
|
||||
_radial = std_::make_unique<Ui::RadialAnimation>(animation(const_cast<Game*>(this), &Game::step_radial));
|
||||
}
|
||||
if (!_radial->animating()) {
|
||||
_radial->start(document->progress());
|
||||
}
|
||||
}
|
||||
radial = isRadialAnimation(context->ms);
|
||||
|
||||
if (animating) {
|
||||
if (!_thumb.isNull()) _thumb = QPixmap();
|
||||
auto animationThumb = _gif->current(_frameSize.width(), _frameSize.height(), st::inlineThumbSize, st::inlineThumbSize, context->paused ? 0 : context->ms);
|
||||
p.drawPixmapLeft(rthumb.topLeft(), _width, animationThumb);
|
||||
thumbDisplayed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!thumbDisplayed) {
|
||||
prepareThumb(st::inlineThumbSize, st::inlineThumbSize);
|
||||
if (_thumb.isNull()) {
|
||||
p.fillRect(rthumb, st::overviewPhotoBg);
|
||||
} else {
|
||||
p.drawPixmapLeft(rthumb.topLeft(), _width, _thumb);
|
||||
}
|
||||
}
|
||||
|
||||
if (radial) {
|
||||
p.fillRect(rthumb, st::msgDateImgBg);
|
||||
QRect inner((st::inlineThumbSize - st::msgFileSize) / 2, (st::inlineThumbSize - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
|
||||
if (radial) {
|
||||
p.setOpacity(1);
|
||||
QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
|
||||
_radial->draw(p, rinner, st::msgFileRadialLine, st::msgInBg);
|
||||
}
|
||||
}
|
||||
|
||||
p.setPen(st::black);
|
||||
_title.drawLeftElided(p, left, st::inlineRowMargin, _width - left, _width, 2);
|
||||
int32 titleHeight = qMin(_title.countHeight(_width - left), st::semiboldFont->height * 2);
|
||||
|
||||
p.setPen(st::inlineDescriptionFg);
|
||||
int32 descriptionLines = 2;
|
||||
_description.drawLeftElided(p, left, st::inlineRowMargin + titleHeight, _width - left, _width, descriptionLines);
|
||||
|
||||
if (!context->lastRow) {
|
||||
p.fillRect(rtlrect(left, _height - st::inlineRowBorder, _width - left, st::inlineRowBorder, _width), st::inlineRowBorderFg);
|
||||
}
|
||||
}
|
||||
|
||||
void Game::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const {
|
||||
int left = st::inlineThumbSize + st::inlineThumbSkip;
|
||||
if (x >= 0 && x < left - st::inlineThumbSkip && y >= st::inlineRowMargin && y < st::inlineRowMargin + st::inlineThumbSize) {
|
||||
link = _send;
|
||||
return;
|
||||
}
|
||||
if (x >= left && x < _width && y >= 0 && y < _height) {
|
||||
link = _send;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Game::prepareThumb(int width, int height) const {
|
||||
auto thumb = ([this]() {
|
||||
if (auto photo = getResultPhoto()) {
|
||||
return photo->medium;
|
||||
} else if (auto document = getResultDocument()) {
|
||||
return document->thumb;
|
||||
}
|
||||
return ImagePtr();
|
||||
})();
|
||||
if (thumb->isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (thumb->loaded()) {
|
||||
if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) {
|
||||
int w = qMax(convertScale(thumb->width()), 1), h = qMax(convertScale(thumb->height()), 1);
|
||||
auto resizeByHeight1 = (w * height > h * width) && (h >= height);
|
||||
auto resizeByHeight2 = (h * width >= w * height) && (w < width);
|
||||
if (resizeByHeight1 || resizeByHeight2) {
|
||||
if (h > height) {
|
||||
w = w * height / h;
|
||||
h = height;
|
||||
}
|
||||
} else {
|
||||
if (w > width) {
|
||||
h = h * width / w;
|
||||
w = width;
|
||||
}
|
||||
}
|
||||
_thumb = thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), ImagePixSmooth, width, height);
|
||||
}
|
||||
} else {
|
||||
thumb->load();
|
||||
}
|
||||
}
|
||||
|
||||
bool Game::isRadialAnimation(uint64 ms) const {
|
||||
if (!_radial || !_radial->animating()) return false;
|
||||
|
||||
_radial->step(ms);
|
||||
return _radial && _radial->animating();
|
||||
}
|
||||
|
||||
void Game::step_radial(uint64 ms, bool timer) {
|
||||
if (timer) {
|
||||
update();
|
||||
} else {
|
||||
auto document = getResultDocument();
|
||||
_radial->update(document->progress(), !document->loading() || document->loaded(), ms);
|
||||
if (!_radial->animating() && document->loaded()) {
|
||||
_radial.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Game::clipCallback(Media::Clip::Notification notification) {
|
||||
using namespace Media::Clip;
|
||||
switch (notification) {
|
||||
case NotificationReinit: {
|
||||
if (_gif) {
|
||||
if (_gif->state() == State::Error) {
|
||||
_gif.setBad();
|
||||
getResultDocument()->forget();
|
||||
} else if (_gif->ready() && !_gif->started()) {
|
||||
_gif->start(_frameSize.width(), _frameSize.height(), st::inlineThumbSize, st::inlineThumbSize, ImageRoundRadius::None);
|
||||
} else if (_gif->autoPausedGif() && !Ui::isInlineItemVisible(this)) {
|
||||
_gif.reset();
|
||||
getResultDocument()->forget();
|
||||
}
|
||||
}
|
||||
|
||||
update();
|
||||
} break;
|
||||
|
||||
case NotificationRepaint: {
|
||||
if (_gif && !_gif->currentDisplayed()) {
|
||||
update();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Layout
|
||||
} // namespace InlineBots
|
||||
|
|
|
@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#pragma once
|
||||
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/text/text.h"
|
||||
|
||||
namespace InlineBots {
|
||||
|
@ -76,10 +77,7 @@ public:
|
|||
// ClickHandlerHost interface
|
||||
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
|
||||
|
||||
~Gif();
|
||||
|
||||
private:
|
||||
|
||||
QSize countFrameSize() const;
|
||||
|
||||
enum class StateFlag {
|
||||
|
@ -92,11 +90,8 @@ private:
|
|||
return ~StateFlags(flag);
|
||||
}
|
||||
|
||||
Media::Clip::Reader *_gif = nullptr;
|
||||
Media::Clip::ReaderPointer _gif;
|
||||
ClickHandlerPtr _delete;
|
||||
bool gif() const {
|
||||
return (!_gif || _gif == Media::Clip::BadReader) ? false : true;
|
||||
}
|
||||
mutable QPixmap _thumb;
|
||||
void prepareThumb(int32 width, int32 height, const QSize &frame) const;
|
||||
|
||||
|
@ -113,9 +108,9 @@ private:
|
|||
}
|
||||
bool over;
|
||||
FloatAnimation _a_over;
|
||||
RadialAnimation radial;
|
||||
Ui::RadialAnimation radial;
|
||||
};
|
||||
mutable AnimationData *_animation = nullptr;
|
||||
mutable std_::unique_ptr<AnimationData> _animation;
|
||||
mutable FloatAnimation _a_deleteOver;
|
||||
|
||||
};
|
||||
|
@ -275,7 +270,7 @@ private:
|
|||
anim::fvalue a_thumbOver;
|
||||
Animation _a_thumbOver;
|
||||
|
||||
RadialAnimation radial;
|
||||
Ui::RadialAnimation radial;
|
||||
};
|
||||
mutable std_::unique_ptr<AnimationData> _animation;
|
||||
|
||||
|
@ -338,6 +333,35 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class Game : public ItemBase {
|
||||
public:
|
||||
Game(Result *result);
|
||||
|
||||
void setPosition(int32 position) override;
|
||||
void initDimensions() override;
|
||||
|
||||
void paint(Painter &p, const QRect &clip, const PaintContext *context) const override;
|
||||
void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, int x, int y) const override;
|
||||
|
||||
private:
|
||||
void countFrameSize();
|
||||
|
||||
void prepareThumb(int32 width, int32 height) const;
|
||||
|
||||
bool isRadialAnimation(uint64 ms) const;
|
||||
void step_radial(uint64 ms, bool timer);
|
||||
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
|
||||
Media::Clip::ReaderPointer _gif;
|
||||
mutable QPixmap _thumb;
|
||||
mutable std_::unique_ptr<Ui::RadialAnimation> _radial;
|
||||
Text _title, _description;
|
||||
|
||||
QSize _frameSize;
|
||||
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace Layout
|
||||
} // namespace InlineBots
|
||||
|
|
|
@ -113,6 +113,7 @@ std_::unique_ptr<ItemBase> ItemBase::createLayout(Result *result, bool forceThum
|
|||
case Type::Article:
|
||||
case Type::Geo:
|
||||
case Type::Venue: return std_::make_unique<internal::Article>(result, forceThumb); break;
|
||||
case Type::Game: return std_::make_unique<internal::Game>(result); break;
|
||||
case Type::Contact: return std_::make_unique<internal::Contact>(result); break;
|
||||
}
|
||||
return std_::unique_ptr<ItemBase>();
|
||||
|
|
|
@ -47,6 +47,7 @@ std_::unique_ptr<Result> Result::create(uint64 queryId, const MTPBotInlineResult
|
|||
result->insert(qsl("contact"), Result::Type::Contact);
|
||||
result->insert(qsl("venue"), Result::Type::Venue);
|
||||
result->insert(qsl("geo"), Result::Type::Geo);
|
||||
result->insert(qsl("game"), Result::Type::Game);
|
||||
return result.release();
|
||||
})() };
|
||||
|
||||
|
@ -122,6 +123,9 @@ std_::unique_ptr<Result> Result::create(uint64 queryId, const MTPBotInlineResult
|
|||
if (result->_type == Type::Photo) {
|
||||
result->createPhoto();
|
||||
result->sendData.reset(new internal::SendPhoto(result->_photo, qs(r.vcaption)));
|
||||
} else if (result->_type == Type::Game) {
|
||||
result->createGame();
|
||||
result->sendData.reset(new internal::SendGame(result->_game));
|
||||
} else {
|
||||
result->createDocument();
|
||||
result->sendData.reset(new internal::SendFile(result->_document, qs(r.vcaption)));
|
||||
|
@ -313,7 +317,7 @@ void Result::createPhoto() {
|
|||
ImagePtr medium = ImagePtr(mediumsize.width(), mediumsize.height());
|
||||
|
||||
ImagePtr full = ImagePtr(_content_url, _width, _height);
|
||||
uint64 photoId = rand_value<uint64>();
|
||||
auto photoId = rand_value<PhotoId>();
|
||||
_photo = App::photoSet(photoId, 0, 0, unixtime(), _thumb, medium, full);
|
||||
_photo->thumb = _thumb;
|
||||
}
|
||||
|
@ -321,8 +325,6 @@ void Result::createPhoto() {
|
|||
void Result::createDocument() {
|
||||
if (_document) return;
|
||||
|
||||
uint64 docId = rand_value<uint64>();
|
||||
|
||||
if (!_thumb_url.isEmpty()) {
|
||||
_thumb = ImagePtr(_thumb_url, QSize(90, 90));
|
||||
}
|
||||
|
@ -352,16 +354,16 @@ void Result::createDocument() {
|
|||
attributes.push_back(MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(_duration), MTPstring(), MTPstring(), MTPbytes()));
|
||||
}
|
||||
|
||||
MTPDocument document = MTP_document(MTP_long(docId), MTP_long(0), MTP_int(unixtime()), MTP_string(mime), MTP_int(0), MTP_photoSizeEmpty(MTP_string("")), MTP_int(MTP::maindc()), MTP_int(0), MTP_vector<MTPDocumentAttribute>(attributes));
|
||||
|
||||
_document = App::feedDocument(document);
|
||||
auto documentId = rand_value<DocumentId>();
|
||||
_document = App::documentSet(documentId, nullptr, 0, 0, unixtime(), attributes, mime, _thumb, MTP::maindc(), 0, StorageImageLocation());
|
||||
_document->setContentUrl(_content_url);
|
||||
if (!_thumb->isNull()) {
|
||||
_document->thumb = _thumb;
|
||||
}
|
||||
}
|
||||
|
||||
Result::~Result() {
|
||||
void Result::createGame() {
|
||||
if (_game) return;
|
||||
|
||||
auto gameId = rand_value<GameId>();
|
||||
_game = App::gameSet(gameId, nullptr, 0, QString(), _title, _description, _photo, _document);
|
||||
}
|
||||
|
||||
} // namespace InlineBots
|
||||
|
|
|
@ -74,11 +74,10 @@ public:
|
|||
QString getLayoutTitle() const;
|
||||
QString getLayoutDescription() const;
|
||||
|
||||
~Result();
|
||||
|
||||
private:
|
||||
void createPhoto();
|
||||
void createDocument();
|
||||
void createGame();
|
||||
|
||||
enum class Type {
|
||||
Unknown,
|
||||
|
@ -92,6 +91,7 @@ private:
|
|||
Contact,
|
||||
Geo,
|
||||
Venue,
|
||||
Game,
|
||||
};
|
||||
|
||||
friend class internal::SendData;
|
||||
|
@ -112,6 +112,7 @@ private:
|
|||
|
||||
DocumentData *_document = nullptr;
|
||||
PhotoData *_photo = nullptr;
|
||||
GameData *_game = nullptr;
|
||||
|
||||
std_::unique_ptr<MTPReplyMarkup> _mtpKeyboard;
|
||||
|
||||
|
|
|
@ -90,5 +90,11 @@ UserId viaBotId, MsgId replyToId, const MTPReplyMarkup &markup) const {
|
|||
history->addNewDocument(msgId, flags, viaBotId, replyToId, date(mtpDate), fromId, _document, _caption, markup);
|
||||
}
|
||||
|
||||
void SendGame::addToHistory(const Result *owner, History *history,
|
||||
MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate,
|
||||
UserId viaBotId, MsgId replyToId, const MTPReplyMarkup &markup) const {
|
||||
history->addNewGame(msgId, flags, viaBotId, replyToId, date(mtpDate), fromId, _game, markup);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace InlineBots
|
||||
|
|
|
@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "core/basic_types.h"
|
||||
#include "structs.h"
|
||||
#include "mtproto/core_types.h"
|
||||
#include "history/history_location_manager.h"
|
||||
|
||||
namespace InlineBots {
|
||||
|
||||
|
@ -62,7 +63,6 @@ public:
|
|||
// Only SendFile and SendPhoto work by their own.
|
||||
class SendDataCommon : public SendData {
|
||||
public:
|
||||
|
||||
struct SentMTPMessageFields {
|
||||
MTPString text = MTP_string("");
|
||||
MTPVector<MTPMessageEntity> entities = MTPnullEntities;
|
||||
|
@ -99,7 +99,7 @@ private:
|
|||
// Message with geo location point media.
|
||||
class SendGeo : public SendDataCommon {
|
||||
public:
|
||||
SendGeo(const MTPDgeoPoint &point) : _location(point) {
|
||||
explicit SendGeo(const MTPDgeoPoint &point) : _location(point) {
|
||||
}
|
||||
|
||||
bool isValid() const override {
|
||||
|
@ -221,5 +221,25 @@ private:
|
|||
|
||||
};
|
||||
|
||||
// Message with game.
|
||||
class SendGame : public SendData {
|
||||
public:
|
||||
SendGame(GameData *game)
|
||||
: _game(game) {
|
||||
}
|
||||
|
||||
bool isValid() const override {
|
||||
return _game != nullptr;
|
||||
}
|
||||
|
||||
void addToHistory(const Result *owner, History *history,
|
||||
MTPDmessage::Flags flags, MsgId msgId, UserId fromId, MTPint mtpDate,
|
||||
UserId viaBotId, MsgId replyToId, const MTPReplyMarkup &markup) const override;
|
||||
|
||||
private:
|
||||
GameData *_game;
|
||||
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace InlineBots
|
||||
|
|
|
@ -567,12 +567,7 @@ void MediaPreviewWidget::fillEmojiString() {
|
|||
}
|
||||
|
||||
void MediaPreviewWidget::resetGifAndCache() {
|
||||
if (_gif) {
|
||||
if (gif()) {
|
||||
delete _gif;
|
||||
}
|
||||
_gif = nullptr;
|
||||
}
|
||||
_gif.reset();
|
||||
_cacheStatus = CacheNotLoaded;
|
||||
_cachedSize = QSize();
|
||||
}
|
||||
|
@ -592,7 +587,7 @@ QSize MediaPreviewWidget::currentDimensions() const {
|
|||
box = QSize(width() - 2 * st::boxVerticalMargin, height() - 2 * st::boxVerticalMargin);
|
||||
} else {
|
||||
result = _document->dimensions;
|
||||
if (gif() && _gif->ready()) {
|
||||
if (_gif && _gif->ready()) {
|
||||
result = QSize(_gif->width(), _gif->height());
|
||||
}
|
||||
if (_document->sticker()) {
|
||||
|
@ -636,15 +631,15 @@ QPixmap MediaPreviewWidget::currentImage() const {
|
|||
} else {
|
||||
_document->automaticLoad(nullptr);
|
||||
if (_document->loaded()) {
|
||||
if (!_gif && _gif != Media::Clip::BadReader) {
|
||||
if (!_gif && !_gif.isBad()) {
|
||||
auto that = const_cast<MediaPreviewWidget*>(this);
|
||||
that->_gif = new Media::Clip::Reader(_document->location(), _document->data(), [this, that](Media::Clip::Notification notification) {
|
||||
that->_gif = Media::Clip::MakeReader(_document->location(), _document->data(), [this, that](Media::Clip::Notification notification) {
|
||||
that->clipCallback(notification);
|
||||
});
|
||||
if (gif()) _gif->setAutoplay();
|
||||
if (_gif) _gif->setAutoplay();
|
||||
}
|
||||
}
|
||||
if (gif() && _gif->started()) {
|
||||
if (_gif && _gif->started()) {
|
||||
QSize s = currentDimensions();
|
||||
return _gif->current(s.width(), s.height(), s.width(), s.height(), getms());
|
||||
}
|
||||
|
@ -681,12 +676,11 @@ void MediaPreviewWidget::clipCallback(Media::Clip::Notification notification) {
|
|||
using namespace Media::Clip;
|
||||
switch (notification) {
|
||||
case NotificationReinit: {
|
||||
if (gif() && _gif->state() == State::Error) {
|
||||
delete _gif;
|
||||
_gif = BadReader;
|
||||
if (_gif && _gif->state() == State::Error) {
|
||||
_gif.setBad();
|
||||
}
|
||||
|
||||
if (gif() && _gif->ready() && !_gif->started()) {
|
||||
if (_gif && _gif->ready() && !_gif->started()) {
|
||||
QSize s = currentDimensions();
|
||||
_gif->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None);
|
||||
}
|
||||
|
@ -695,7 +689,7 @@ void MediaPreviewWidget::clipCallback(Media::Clip::Notification notification) {
|
|||
} break;
|
||||
|
||||
case NotificationRepaint: {
|
||||
if (gif() && !_gif->currentDisplayed()) {
|
||||
if (_gif && !_gif->currentDisplayed()) {
|
||||
update();
|
||||
}
|
||||
} break;
|
||||
|
|
|
@ -157,10 +157,7 @@ private:
|
|||
Animation _a_shown;
|
||||
DocumentData *_document = nullptr;
|
||||
PhotoData *_photo = nullptr;
|
||||
Media::Clip::Reader *_gif = nullptr;
|
||||
bool gif() const {
|
||||
return (!_gif || _gif == Media::Clip::BadReader) ? false : true;
|
||||
}
|
||||
Media::Clip::ReaderPointer _gif;
|
||||
|
||||
int _emojiSize;
|
||||
QList<EmojiPtr> _emojiList;
|
||||
|
|
|
@ -77,6 +77,10 @@ const TextParseOptions &itemTextOptions(History *h, PeerData *f) {
|
|||
return _historyTextOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &itemTextOptions(const HistoryItem *item) {
|
||||
return itemTextOptions(item->history(), item->author());
|
||||
}
|
||||
|
||||
const TextParseOptions &itemTextNoMonoOptions(History *h, PeerData *f) {
|
||||
if ((h->peer->isUser() && h->peer->asUser()->botInfo) || (f->isUser() && f->asUser()->botInfo) || (h->peer->isChat() && h->peer->asChat()->botStatus >= 0) || (h->peer->isMegagroup() && h->peer->asChannel()->mgInfo->botStatus >= 0)) {
|
||||
return _historyBotNoMonoOptions;
|
||||
|
@ -84,6 +88,10 @@ const TextParseOptions &itemTextNoMonoOptions(History *h, PeerData *f) {
|
|||
return _historyTextNoMonoOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &itemTextNoMonoOptions(const HistoryItem *item) {
|
||||
return itemTextNoMonoOptions(item->history(), item->author());
|
||||
}
|
||||
|
||||
QString formatSizeText(qint64 size) {
|
||||
if (size >= 1024 * 1024) { // more than 1 mb
|
||||
qint64 sizeTenthMb = (size * 10 / (1024 * 1024));
|
||||
|
|
|
@ -26,7 +26,9 @@ extern TextParseOptions _textNameOptions, _textDlgOptions;
|
|||
extern TextParseOptions _historyTextOptions, _historyBotOptions, _historyTextNoMonoOptions, _historyBotNoMonoOptions;
|
||||
|
||||
const TextParseOptions &itemTextOptions(History *h, PeerData *f);
|
||||
const TextParseOptions &itemTextOptions(const HistoryItem *item);
|
||||
const TextParseOptions &itemTextNoMonoOptions(History *h, PeerData *f);
|
||||
const TextParseOptions &itemTextNoMonoOptions(const HistoryItem *item);
|
||||
|
||||
enum RoundCorners {
|
||||
SmallMaskCorners = 0x00, // for images
|
||||
|
|
|
@ -2978,11 +2978,11 @@ public:
|
|||
voice->waveform[0] = -2;
|
||||
voice->wavemax = 0;
|
||||
}
|
||||
const DocumentItems &items(App::documentItems());
|
||||
DocumentItems::const_iterator i = items.constFind(_doc);
|
||||
auto &items = App::documentItems();
|
||||
auto i = items.constFind(_doc);
|
||||
if (i != items.cend()) {
|
||||
for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) {
|
||||
Ui::repaintHistoryItem(j.key());
|
||||
for_const (auto item, i.value()) {
|
||||
Ui::repaintHistoryItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,8 +109,8 @@ MainWidget::MainWidget(MainWindow *window) : TWidget(window)
|
|||
connect(&_updateMutedTimer, SIGNAL(timeout()), this, SLOT(onUpdateMuted()));
|
||||
connect(&_viewsIncrementTimer, SIGNAL(timeout()), this, SLOT(onViewsIncrement()));
|
||||
|
||||
_webPageUpdater.setSingleShot(true);
|
||||
connect(&_webPageUpdater, SIGNAL(timeout()), this, SLOT(webPagesUpdate()));
|
||||
_webPageOrGameUpdater.setSingleShot(true);
|
||||
connect(&_webPageOrGameUpdater, SIGNAL(timeout()), this, SLOT(webPagesOrGamesUpdate()));
|
||||
|
||||
subscribe(Window::chatBackground(), [this](const Window::ChatBackgroundUpdate &update) {
|
||||
using Update = Window::ChatBackgroundUpdate;
|
||||
|
@ -348,24 +348,41 @@ void MainWidget::finishForwarding(History *history, bool silent) {
|
|||
}
|
||||
|
||||
void MainWidget::webPageUpdated(WebPageData *data) {
|
||||
_webPagesUpdated.insert(data->id, true);
|
||||
_webPageUpdater.start(0);
|
||||
_webPagesUpdated.insert(data->id);
|
||||
_webPageOrGameUpdater.start(0);
|
||||
}
|
||||
|
||||
void MainWidget::webPagesUpdate() {
|
||||
if (_webPagesUpdated.isEmpty()) return;
|
||||
void MainWidget::gameUpdated(GameData *data) {
|
||||
_gamesUpdated.insert(data->id);
|
||||
_webPageOrGameUpdater.start(0);
|
||||
}
|
||||
|
||||
_webPageUpdater.stop();
|
||||
const WebPageItems &items(App::webPageItems());
|
||||
for (QMap<WebPageId, bool>::const_iterator i = _webPagesUpdated.cbegin(), e = _webPagesUpdated.cend(); i != e; ++i) {
|
||||
WebPageItems::const_iterator j = items.constFind(App::webPage(i.key()));
|
||||
if (j != items.cend()) {
|
||||
for (HistoryItemsMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) {
|
||||
k.key()->setPendingInitDimensions();
|
||||
void MainWidget::webPagesOrGamesUpdate() {
|
||||
_webPageOrGameUpdater.stop();
|
||||
if (!_webPagesUpdated.isEmpty()) {
|
||||
auto &items = App::webPageItems();
|
||||
for_const (auto webPageId, _webPagesUpdated) {
|
||||
auto j = items.constFind(App::webPage(webPageId));
|
||||
if (j != items.cend()) {
|
||||
for_const (auto item, j.value()) {
|
||||
item->setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
}
|
||||
_webPagesUpdated.clear();
|
||||
}
|
||||
if (!_gamesUpdated.isEmpty()) {
|
||||
auto &items = App::gameItems();
|
||||
for_const (auto gameId, _gamesUpdated) {
|
||||
auto j = items.constFind(App::game(gameId));
|
||||
if (j != items.cend()) {
|
||||
for_const (auto item, j.value()) {
|
||||
item->setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
}
|
||||
_gamesUpdated.clear();
|
||||
}
|
||||
_webPagesUpdated.clear();
|
||||
}
|
||||
|
||||
void MainWidget::updateMutedIn(int32 seconds) {
|
||||
|
@ -477,8 +494,8 @@ void MainWidget::notify_userIsContactChanged(UserData *user, bool fromThisApp) {
|
|||
const SharedContactItems &items(App::sharedContactItems());
|
||||
SharedContactItems::const_iterator i = items.constFind(peerToUser(user->id));
|
||||
if (i != items.cend()) {
|
||||
for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) {
|
||||
j.key()->setPendingInitDimensions();
|
||||
for_const (auto item, i.value()) {
|
||||
item->setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1158,7 +1175,7 @@ void MainWidget::sendMessage(const MessageToSend &message) {
|
|||
if (message.webPageId == CancelledWebPageId) {
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
|
||||
} else if (message.webPageId) {
|
||||
WebPageData *page = App::webPage(message.webPageId);
|
||||
auto page = App::webPage(message.webPageId);
|
||||
media = MTP_messageMediaWebPage(MTP_webPagePending(MTP_long(page->id), MTP_int(page->pendingTill)));
|
||||
flags |= MTPDmessage::Flag::f_media;
|
||||
}
|
||||
|
@ -1621,8 +1638,8 @@ void MainWidget::documentLoadProgress(DocumentData *document) {
|
|||
auto &items = App::documentItems();
|
||||
auto i = items.constFind(document);
|
||||
if (i != items.cend()) {
|
||||
for (auto j = i->cbegin(), e = i->cend(); j != e; ++j) {
|
||||
Ui::repaintHistoryItem(j.key());
|
||||
for_const (auto item, i.value()) {
|
||||
Ui::repaintHistoryItem(item);
|
||||
}
|
||||
}
|
||||
App::wnd()->documentUpdated(document);
|
||||
|
@ -1688,11 +1705,11 @@ void MainWidget::mediaMarkRead(DocumentData *data) {
|
|||
void MainWidget::mediaMarkRead(const HistoryItemsMap &items) {
|
||||
QVector<MTPint> markedIds;
|
||||
markedIds.reserve(items.size());
|
||||
for (HistoryItemsMap::const_iterator j = items.cbegin(), e = items.cend(); j != e; ++j) {
|
||||
if (!j.key()->out() && j.key()->isMediaUnread()) {
|
||||
j.key()->markMediaRead();
|
||||
if (j.key()->id > 0) {
|
||||
markedIds.push_back(MTP_int(j.key()->id));
|
||||
for_const (auto item, items) {
|
||||
if (!item->out() && item->isMediaUnread()) {
|
||||
item->markMediaRead();
|
||||
if (item->id > 0) {
|
||||
markedIds.push_back(MTP_int(item->id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3355,7 +3372,7 @@ void MainWidget::openLocalUrl(const QString &url) {
|
|||
} else if (auto usernameMatch = regex_match(qsl("^resolve/?\\?(.+)(#|$)"), command, matchOptions)) {
|
||||
auto params = url_parse_params(usernameMatch->captured(1), UrlParamNameTransform::ToLower);
|
||||
auto domain = params.value(qsl("domain"));
|
||||
if (auto domainMatch = regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), domain, matchOptions)) {
|
||||
if (regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), domain, matchOptions)) {
|
||||
auto start = qsl("start");
|
||||
auto startToken = params.value(start);
|
||||
if (startToken.isEmpty()) {
|
||||
|
@ -3370,6 +3387,11 @@ void MainWidget::openLocalUrl(const QString &url) {
|
|||
if (auto postId = postParam.toInt()) {
|
||||
post = postId;
|
||||
}
|
||||
auto gameParam = params.value(qsl("game"));
|
||||
if (!gameParam.isEmpty() && regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), gameParam, matchOptions)) {
|
||||
startToken = gameParam;
|
||||
post = ShowAtGameShareMsgId;
|
||||
}
|
||||
openPeerByName(domain, post, startToken);
|
||||
}
|
||||
} else if (auto shareGameScoreMatch = regex_match(qsl("^share_game_score/?\\?(.+)(#|$)"), command, matchOptions)) {
|
||||
|
@ -3383,7 +3405,14 @@ void MainWidget::openPeerByName(const QString &username, MsgId msgId, const QStr
|
|||
|
||||
PeerData *peer = App::peerByName(username);
|
||||
if (peer) {
|
||||
if (msgId == ShowAtProfileMsgId && !peer->isChannel()) {
|
||||
if (msgId == ShowAtGameShareMsgId) {
|
||||
if (peer->isUser() && peer->asUser()->botInfo && !startToken.isEmpty()) {
|
||||
peer->asUser()->botInfo->shareGameShortName = startToken;
|
||||
Ui::showLayer(new ContactsBox(peer->asUser()));
|
||||
} else {
|
||||
Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Forward);
|
||||
}
|
||||
} else if (msgId == ShowAtProfileMsgId && !peer->isChannel()) {
|
||||
if (peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->cantJoinGroups && !startToken.isEmpty()) {
|
||||
peer->asUser()->botInfo->startGroupToken = startToken;
|
||||
Ui::showLayer(new ContactsBox(peer->asUser()));
|
||||
|
@ -4202,8 +4231,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
}
|
||||
}
|
||||
if (needToAdd) {
|
||||
HistoryItem *item = App::histories().addNewMessage(d.vmessage, NewMessageUnread);
|
||||
if (item) {
|
||||
if (auto item = App::histories().addNewMessage(d.vmessage, NewMessageUnread)) {
|
||||
_history->peerMessagesUpdated(item->history()->peer->id);
|
||||
}
|
||||
}
|
||||
|
@ -4211,11 +4239,10 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateMessageID: {
|
||||
const auto &d(update.c_updateMessageID());
|
||||
FullMsgId msg = App::histItemByRandom(d.vrandom_id.v);
|
||||
auto &d = update.c_updateMessageID();
|
||||
auto msg = App::histItemByRandom(d.vrandom_id.v);
|
||||
if (msg.msg) {
|
||||
HistoryItem *msgRow = App::histItemById(msg);
|
||||
if (msgRow) {
|
||||
if (auto msgRow = App::histItemById(msg)) {
|
||||
if (App::histItemById(msg.channel, d.vid.v)) {
|
||||
History *h = msgRow->history();
|
||||
bool wasLast = (h->lastMsg == msgRow);
|
||||
|
@ -4241,14 +4268,14 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateReadMessagesContents: {
|
||||
const auto &d(update.c_updateReadMessagesContents());
|
||||
auto &d = update.c_updateReadMessagesContents();
|
||||
|
||||
if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// update before applying skipped
|
||||
const auto &v(d.vmessages.c_vector().v);
|
||||
auto &v = d.vmessages.c_vector().v;
|
||||
for (int32 i = 0, l = v.size(); i < l; ++i) {
|
||||
if (HistoryItem *item = App::histItemById(NoChannel, v.at(i).v)) {
|
||||
if (item->isMediaUnread()) {
|
||||
|
@ -4267,7 +4294,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateReadHistoryInbox: {
|
||||
const auto &d(update.c_updateReadHistoryInbox());
|
||||
auto &d = update.c_updateReadHistoryInbox();
|
||||
|
||||
if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
|
||||
return;
|
||||
|
@ -4280,7 +4307,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateReadHistoryOutbox: {
|
||||
const auto &d(update.c_updateReadHistoryOutbox());
|
||||
auto &d = update.c_updateReadHistoryOutbox();
|
||||
|
||||
if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
|
||||
return;
|
||||
|
@ -4298,7 +4325,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateWebPage: {
|
||||
const auto &d(update.c_updateWebPage());
|
||||
auto &d = update.c_updateWebPage();
|
||||
|
||||
if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
|
||||
return;
|
||||
|
@ -4307,13 +4334,13 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
// update before applying skipped
|
||||
App::feedWebPage(d.vwebpage);
|
||||
_history->updatePreview();
|
||||
webPagesUpdate();
|
||||
webPagesOrGamesUpdate();
|
||||
|
||||
ptsApplySkippedUpdates();
|
||||
} break;
|
||||
|
||||
case mtpc_updateDeleteMessages: {
|
||||
const auto &d(update.c_updateDeleteMessages());
|
||||
auto &d = update.c_updateDeleteMessages();
|
||||
|
||||
if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
|
||||
return;
|
||||
|
@ -4327,9 +4354,9 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateUserTyping: {
|
||||
const auto &d(update.c_updateUserTyping());
|
||||
History *history = App::historyLoaded(peerFromUser(d.vuser_id));
|
||||
UserData *user = App::userLoaded(d.vuser_id.v);
|
||||
auto &d = update.c_updateUserTyping();
|
||||
auto history = App::historyLoaded(peerFromUser(d.vuser_id));
|
||||
auto user = App::userLoaded(d.vuser_id.v);
|
||||
if (history && user) {
|
||||
auto when = requestingDifference() ? 0 : unixtime();
|
||||
App::histories().regSendAction(history, user, d.vaction, when);
|
||||
|
@ -4337,14 +4364,14 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateChatUserTyping: {
|
||||
const auto &d(update.c_updateChatUserTyping());
|
||||
auto &d = update.c_updateChatUserTyping();
|
||||
History *history = 0;
|
||||
if (PeerData *chat = App::chatLoaded(d.vchat_id.v)) {
|
||||
if (auto chat = App::chatLoaded(d.vchat_id.v)) {
|
||||
history = App::historyLoaded(chat->id);
|
||||
} else if (PeerData *channel = App::channelLoaded(d.vchat_id.v)) {
|
||||
} else if (auto channel = App::channelLoaded(d.vchat_id.v)) {
|
||||
history = App::historyLoaded(channel->id);
|
||||
}
|
||||
UserData *user = (d.vuser_id.v == MTP::authedId()) ? 0 : App::userLoaded(d.vuser_id.v);
|
||||
auto user = (d.vuser_id.v == MTP::authedId()) ? nullptr : App::userLoaded(d.vuser_id.v);
|
||||
if (history && user) {
|
||||
auto when = requestingDifference() ? 0 : unixtime();
|
||||
App::histories().regSendAction(history, user, d.vaction, when);
|
||||
|
@ -4372,9 +4399,8 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateUserStatus: {
|
||||
const auto &d(update.c_updateUserStatus());
|
||||
UserData *user = App::userLoaded(d.vuser_id.v);
|
||||
if (user) {
|
||||
auto &d = update.c_updateUserStatus();
|
||||
if (auto user = App::userLoaded(d.vuser_id.v)) {
|
||||
switch (d.vstatus.type()) {
|
||||
case mtpc_userStatusEmpty: user->onlineTill = 0; break;
|
||||
case mtpc_userStatusRecently:
|
||||
|
@ -4403,7 +4429,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateUserName: {
|
||||
auto &d(update.c_updateUserName());
|
||||
auto &d = update.c_updateUserName();
|
||||
if (auto user = App::userLoaded(d.vuser_id.v)) {
|
||||
if (user->contact <= 0) {
|
||||
user->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), user->nameOrPhone, textOneLine(qs(d.vusername)));
|
||||
|
@ -4415,9 +4441,8 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateUserPhoto: {
|
||||
const auto &d(update.c_updateUserPhoto());
|
||||
UserData *user = App::userLoaded(d.vuser_id.v);
|
||||
if (user) {
|
||||
auto &d = update.c_updateUserPhoto();
|
||||
if (auto user = App::userLoaded(d.vuser_id.v)) {
|
||||
user->setPhoto(d.vphoto);
|
||||
user->loadUserpic();
|
||||
if (mtpIsTrue(d.vprevious)) {
|
||||
|
@ -4438,9 +4463,8 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateContactRegistered: {
|
||||
const auto &d(update.c_updateContactRegistered());
|
||||
UserData *user = App::userLoaded(d.vuser_id.v);
|
||||
if (user) {
|
||||
auto &d = update.c_updateContactRegistered();
|
||||
if (auto user = App::userLoaded(d.vuser_id.v)) {
|
||||
if (App::history(user->id)->loadedAtBottom()) {
|
||||
App::history(user->id)->addNewService(clientMsgId(), date(d.vdate), lng_action_user_registered(lt_from, user->name), 0);
|
||||
}
|
||||
|
@ -4448,22 +4472,22 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateContactLink: {
|
||||
const auto &d(update.c_updateContactLink());
|
||||
auto &d = update.c_updateContactLink();
|
||||
App::feedUserLink(d.vuser_id, d.vmy_link, d.vforeign_link);
|
||||
} break;
|
||||
|
||||
case mtpc_updateNotifySettings: {
|
||||
const auto &d(update.c_updateNotifySettings());
|
||||
auto &d = update.c_updateNotifySettings();
|
||||
applyNotifySetting(d.vpeer, d.vnotify_settings);
|
||||
} break;
|
||||
|
||||
case mtpc_updateDcOptions: {
|
||||
const auto &d(update.c_updateDcOptions());
|
||||
auto &d = update.c_updateDcOptions();
|
||||
MTP::updateDcOptions(d.vdc_options.c_vector().v);
|
||||
} break;
|
||||
|
||||
case mtpc_updateUserPhone: {
|
||||
auto &d(update.c_updateUserPhone());
|
||||
auto &d = update.c_updateUserPhone();
|
||||
if (auto user = App::userLoaded(d.vuser_id.v)) {
|
||||
auto newPhone = qs(d.vphone);
|
||||
if (newPhone != user->phone()) {
|
||||
|
@ -4477,31 +4501,31 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateNewEncryptedMessage: {
|
||||
const auto &d(update.c_updateNewEncryptedMessage());
|
||||
auto &d = update.c_updateNewEncryptedMessage();
|
||||
} break;
|
||||
|
||||
case mtpc_updateEncryptedChatTyping: {
|
||||
const auto &d(update.c_updateEncryptedChatTyping());
|
||||
auto &d = update.c_updateEncryptedChatTyping();
|
||||
} break;
|
||||
|
||||
case mtpc_updateEncryption: {
|
||||
const auto &d(update.c_updateEncryption());
|
||||
auto &d = update.c_updateEncryption();
|
||||
} break;
|
||||
|
||||
case mtpc_updateEncryptedMessagesRead: {
|
||||
const auto &d(update.c_updateEncryptedMessagesRead());
|
||||
auto &d = update.c_updateEncryptedMessagesRead();
|
||||
} break;
|
||||
|
||||
case mtpc_updateUserBlocked: {
|
||||
const auto &d(update.c_updateUserBlocked());
|
||||
if (UserData *user = App::userLoaded(d.vuser_id.v)) {
|
||||
auto &d = update.c_updateUserBlocked();
|
||||
if (auto user = App::userLoaded(d.vuser_id.v)) {
|
||||
user->setBlockStatus(mtpIsTrue(d.vblocked) ? UserData::BlockStatus::Blocked : UserData::BlockStatus::NotBlocked);
|
||||
App::markPeerUpdated(user);
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updateNewAuthorization: {
|
||||
const auto &d(update.c_updateNewAuthorization());
|
||||
auto &d = update.c_updateNewAuthorization();
|
||||
QDateTime datetime = date(d.vdate);
|
||||
|
||||
QString name = App::self()->firstName;
|
||||
|
@ -4514,7 +4538,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateServiceNotification: {
|
||||
const auto &d(update.c_updateServiceNotification());
|
||||
auto &d = update.c_updateServiceNotification();
|
||||
if (mtpIsTrue(d.vpopup)) {
|
||||
Ui::showLayer(new InformBox(qs(d.vmessage)));
|
||||
} else {
|
||||
|
|
|
@ -345,6 +345,7 @@ public:
|
|||
void mediaMarkRead(const HistoryItemsMap &items);
|
||||
|
||||
void webPageUpdated(WebPageData *page);
|
||||
void gameUpdated(GameData *game);
|
||||
void updateMutedIn(int32 seconds);
|
||||
|
||||
void updateStickers();
|
||||
|
@ -419,7 +420,7 @@ signals:
|
|||
void savedGifsUpdated();
|
||||
|
||||
public slots:
|
||||
void webPagesUpdate();
|
||||
void webPagesOrGamesUpdate();
|
||||
|
||||
void documentLoadProgress(FileLoader *loader);
|
||||
void documentLoadFailed(FileLoader *loader, bool started);
|
||||
|
@ -516,8 +517,9 @@ private:
|
|||
Text _toForwardFrom, _toForwardText;
|
||||
int32 _toForwardNameVersion = 0;
|
||||
|
||||
QMap<WebPageId, bool> _webPagesUpdated;
|
||||
QTimer _webPageUpdater;
|
||||
OrderedSet<WebPageId> _webPagesUpdated;
|
||||
OrderedSet<GameId> _gamesUpdated;
|
||||
QTimer _webPageOrGameUpdater;
|
||||
|
||||
SingleTimer _updateMutedTimer;
|
||||
|
||||
|
|
|
@ -176,11 +176,14 @@ void Widget::startAnimation() {
|
|||
_cache = myGrab(this);
|
||||
}
|
||||
hideChildren();
|
||||
_a_appearance.start([this]() {
|
||||
_a_appearance.start([this] {
|
||||
update();
|
||||
if (!_a_appearance.animating(getms()) && _hiding) {
|
||||
_hiding = false;
|
||||
hidingFinished();
|
||||
|
||||
// hack, animating() call destroys lambda :(
|
||||
auto that = this;
|
||||
if (!_a_appearance.animating() && that->_hiding) {
|
||||
that->_hiding = false;
|
||||
that->hidingFinished();
|
||||
}
|
||||
}, from, to, st::defaultInnerDropdown.duration);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "media/view/media_clip_controller.h"
|
||||
#include "styles/style_mediaview.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "history/history_media_types.h"
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#pragma once
|
||||
|
||||
#include "dropdown.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
|
||||
namespace Media {
|
||||
namespace Clip {
|
||||
|
@ -237,7 +238,7 @@ private:
|
|||
LinkButton _docDownload, _docSaveAs, _docCancel;
|
||||
|
||||
QRect _photoRadialRect;
|
||||
RadialAnimation _radial;
|
||||
Ui::RadialAnimation _radial;
|
||||
|
||||
History *_migrated = nullptr;
|
||||
History *_history = nullptr; // if conversation photos or files overview
|
||||
|
|
|
@ -157,6 +157,7 @@ inputMediaVenue#2827a81a geo_point:InputGeoPoint title:string address:string pro
|
|||
inputMediaGifExternal#4843b0fd url:string q:string = InputMedia;
|
||||
inputMediaPhotoExternal#b55f4f18 url:string caption:string = InputMedia;
|
||||
inputMediaDocumentExternal#e5e9607c url:string caption:string = InputMedia;
|
||||
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
|
||||
|
||||
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
|
||||
inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
|
||||
|
@ -236,6 +237,7 @@ messageMediaUnsupported#9f84f49e = MessageMedia;
|
|||
messageMediaDocument#f3e02ea8 document:Document caption:string = MessageMedia;
|
||||
messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia;
|
||||
messageMediaVenue#7912b71f geo:GeoPoint title:string address:string provider:string venue_id:string = MessageMedia;
|
||||
messageMediaGame#fdb19008 game:Game = MessageMedia;
|
||||
|
||||
messageActionEmpty#b6aef7b0 = MessageAction;
|
||||
messageActionChatCreate#a6638b9a title:string users:Vector<int> = MessageAction;
|
||||
|
@ -250,7 +252,7 @@ messageActionChatMigrateTo#51bdb021 channel_id:int = MessageAction;
|
|||
messageActionChannelMigrateFrom#b055eaee title:string chat_id:int = MessageAction;
|
||||
messageActionPinMessage#94bd38ed = MessageAction;
|
||||
messageActionHistoryClear#9fbab604 = MessageAction;
|
||||
messageActionGameScore#3a14cfa5 game_id:int score:int = MessageAction;
|
||||
messageActionGameScore#92a72876 game_id:long score:int = MessageAction;
|
||||
|
||||
dialog#66ffba14 flags:# peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog;
|
||||
|
||||
|
@ -387,9 +389,9 @@ updateBotInlineQuery#54826690 flags:# query_id:long user_id:int query:string geo
|
|||
updateBotInlineSend#e48f964 flags:# user_id:int query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update;
|
||||
updateEditChannelMessage#1b3f4df7 message:Message pts:int pts_count:int = Update;
|
||||
updateChannelPinnedMessage#98592475 channel_id:int id:int = Update;
|
||||
updateBotCallbackQuery#4bf9a8a0 flags:# query_id:long user_id:int peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_id:flags.1?int = Update;
|
||||
updateBotCallbackQuery#e73547e1 flags:# query_id:long user_id:int peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
|
||||
updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update;
|
||||
updateInlineBotCallbackQuery#4f2f45d1 flags:# query_id:long user_id:int msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_id:flags.1?int = Update;
|
||||
updateInlineBotCallbackQuery#f9d27a5a flags:# query_id:long user_id:int msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
|
||||
updateReadChannelOutbox#25d6c9c7 channel_id:int max_id:int = Update;
|
||||
updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update;
|
||||
updateReadFeaturedStickers#571d2742 = Update;
|
||||
|
@ -572,7 +574,7 @@ keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton;
|
|||
keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton;
|
||||
keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton;
|
||||
keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton;
|
||||
keyboardButtonGame#28fc3164 text:string game_title:string game_id:int start_param:string = KeyboardButton;
|
||||
keyboardButtonGame#50f41ccf text:string = KeyboardButton;
|
||||
|
||||
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
|
||||
|
||||
|
@ -647,10 +649,12 @@ inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:strin
|
|||
inputBotInlineMessageMediaGeo#f4a59de1 flags:# geo_point:InputGeoPoint reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageMediaVenue#aaafadc8 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageMediaContact#2daf01a7 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageGame#3c00f8aa reply_markup:ReplyMarkup = InputBotInlineMessage;
|
||||
|
||||
inputBotInlineResult#2cbbe15a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb_url:flags.4?string content_url:flags.5?string content_type:flags.5?string w:flags.6?int h:flags.6?int duration:flags.7?int send_message:InputBotInlineMessage = InputBotInlineResult;
|
||||
inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult;
|
||||
inputBotInlineResultDocument#fff8fdc4 flags:# id:string type:string title:flags.1?string description:flags.2?string document:InputDocument send_message:InputBotInlineMessage = InputBotInlineResult;
|
||||
inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:InputBotInlineMessage = InputBotInlineResult;
|
||||
|
||||
botInlineMessageMediaAuto#a74b15b flags:# caption:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
|
||||
botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
|
||||
|
@ -721,6 +725,15 @@ maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords;
|
|||
inputStickeredMediaPhoto#4a992157 id:InputPhoto = InputStickeredMedia;
|
||||
inputStickeredMediaDocument#438865b id:InputDocument = InputStickeredMedia;
|
||||
|
||||
game#bdf9653b flags:# id:long access_hash:long short_name:string title:string description:string photo:Photo document:flags.0?Document = Game;
|
||||
|
||||
inputGameID#32c3e77 id:long access_hash:long = InputGame;
|
||||
inputGameShortName#c331e80a bot_id:InputUser short_name:string = InputGame;
|
||||
|
||||
highScore#58fffcd0 pos:int user_id:int score:int = HighScore;
|
||||
|
||||
messages.highScores#9a3bfd99 scores:Vector<HighScore> users:Vector<User> = messages.HighScores;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
|
@ -851,7 +864,7 @@ messages.sendInlineBotResult#b16e06fe flags:# silent:flags.5?true background:fla
|
|||
messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData;
|
||||
messages.editMessage#ce91e4ca flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Updates;
|
||||
messages.editInlineBotMessage#130c2c85 flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Bool;
|
||||
messages.getBotCallbackAnswer#6c996518 flags:# peer:InputPeer msg_id:int data:flags.0?bytes game_id:flags.1?int = messages.BotCallbackAnswer;
|
||||
messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer;
|
||||
messages.setBotCallbackAnswer#c927d44b flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string = Bool;
|
||||
messages.getPeerDialogs#2d9776b9 peers:Vector<InputPeer> = messages.PeerDialogs;
|
||||
messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector<MessageEntity> = Bool;
|
||||
|
@ -862,10 +875,12 @@ messages.getRecentStickers#5ea192c9 flags:# attached:flags.0?true hash:int = mes
|
|||
messages.saveRecentSticker#392718f8 flags:# attached:flags.0?true id:InputDocument unsave:Bool = Bool;
|
||||
messages.clearRecentStickers#8999602d flags:# attached:flags.0?true = Bool;
|
||||
messages.getArchivedStickers#57f17692 flags:# masks:flags.0?true offset_id:long limit:int = messages.ArchivedStickers;
|
||||
messages.setGameScore#dfbc7c1f flags:# edit_message:flags.0?true peer:InputPeer id:int user_id:InputUser game_id:int score:int = Updates;
|
||||
messages.setInlineGameScore#54f882f1 flags:# edit_message:flags.0?true id:InputBotInlineMessageID user_id:InputUser game_id:int score:int = Bool;
|
||||
messages.getMaskStickers#65b8c79f hash:int = messages.AllStickers;
|
||||
messages.getAttachedStickers#cc5b67cc media:InputStickeredMedia = Vector<StickerSetCovered>;
|
||||
messages.setGameScore#8ef8ecc0 flags:# edit_message:flags.0?true peer:InputPeer id:int user_id:InputUser score:int = Updates;
|
||||
messages.setInlineGameScore#15ad9f64 flags:# edit_message:flags.0?true id:InputBotInlineMessageID user_id:InputUser score:int = Bool;
|
||||
messages.getGameHighScores#e822649d peer:InputPeer id:int user_id:InputUser = messages.HighScores;
|
||||
messages.getInlineGameHighScores#f635e1b id:InputBotInlineMessageID user_id:InputUser = messages.HighScores;
|
||||
|
||||
updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#a041495 pts:int date:int qts:int = updates.Difference;
|
||||
|
@ -917,4 +932,4 @@ channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
|
|||
channels.updatePinnedMessage#a72ded52 flags:# silent:flags.0?true channel:InputChannel id:int = Updates;
|
||||
channels.getAdminedPublicChannels#8d8d82d7 = messages.Chats;
|
||||
|
||||
// LAYER 56
|
||||
// LAYER 57
|
||||
|
|
|
@ -761,6 +761,19 @@ void _serialize_inputMediaDocumentExternal(MTPStringLogger &to, int32 stage, int
|
|||
}
|
||||
}
|
||||
|
||||
void _serialize_inputMediaGame(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ inputMediaGame");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _serialize_inputChatPhotoEmpty(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
to.add("{ inputChatPhotoEmpty }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
|
||||
}
|
||||
|
@ -1528,6 +1541,19 @@ void _serialize_messageMediaVenue(MTPStringLogger &to, int32 stage, int32 lev, T
|
|||
}
|
||||
}
|
||||
|
||||
void _serialize_messageMediaGame(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ messageMediaGame");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" game: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _serialize_messageActionEmpty(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
to.add("{ messageActionEmpty }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
|
||||
}
|
||||
|
@ -1671,7 +1697,7 @@ void _serialize_messageActionGameScore(MTPStringLogger &to, int32 stage, int32 l
|
|||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" game_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 0: to.add(" game_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" score: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
|
@ -2999,7 +3025,7 @@ void _serialize_updateBotCallbackQuery(MTPStringLogger &to, int32 stage, int32 l
|
|||
case 4: to.add(" msg_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" chat_instance: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 6: to.add(" data: "); ++stages.back(); if (flag & MTPDupdateBotCallbackQuery::Flag::f_data) { types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
|
||||
case 7: to.add(" game_id: "); ++stages.back(); if (flag & MTPDupdateBotCallbackQuery::Flag::f_game_id) { types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break;
|
||||
case 7: to.add(" game_short_name: "); ++stages.back(); if (flag & MTPDupdateBotCallbackQuery::Flag::f_game_short_name) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
@ -3035,7 +3061,7 @@ void _serialize_updateInlineBotCallbackQuery(MTPStringLogger &to, int32 stage, i
|
|||
case 3: to.add(" msg_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" chat_instance: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" data: "); ++stages.back(); if (flag & MTPDupdateInlineBotCallbackQuery::Flag::f_data) { types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
|
||||
case 6: to.add(" game_id: "); ++stages.back(); if (flag & MTPDupdateInlineBotCallbackQuery::Flag::f_game_id) { types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break;
|
||||
case 6: to.add(" game_short_name: "); ++stages.back(); if (flag & MTPDupdateInlineBotCallbackQuery::Flag::f_game_short_name) { types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
@ -4627,9 +4653,6 @@ void _serialize_keyboardButtonGame(MTPStringLogger &to, int32 stage, int32 lev,
|
|||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" text: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" game_title: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" game_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" start_param: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
@ -5363,6 +5386,19 @@ void _serialize_inputBotInlineMessageMediaContact(MTPStringLogger &to, int32 sta
|
|||
}
|
||||
}
|
||||
|
||||
void _serialize_inputBotInlineMessageGame(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ inputBotInlineMessageGame");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" reply_markup: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _serialize_inputBotInlineResult(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
MTPDinputBotInlineResult::Flags flag(iflag);
|
||||
|
||||
|
@ -5427,6 +5463,21 @@ void _serialize_inputBotInlineResultDocument(MTPStringLogger &to, int32 stage, i
|
|||
}
|
||||
}
|
||||
|
||||
void _serialize_inputBotInlineResultGame(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ inputBotInlineResultGame");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" short_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" send_message: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _serialize_botInlineMessageMediaAuto(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
MTPDbotInlineMessageMediaAuto::Flags flag(iflag);
|
||||
|
||||
|
@ -5996,6 +6047,85 @@ void _serialize_inputStickeredMediaDocument(MTPStringLogger &to, int32 stage, in
|
|||
}
|
||||
}
|
||||
|
||||
void _serialize_game(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
MTPDgame::Flags flag(iflag);
|
||||
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ game");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" short_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" title: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" description: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 6: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 7: to.add(" document: "); ++stages.back(); if (flag & MTPDgame::Flag::f_document) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _serialize_inputGameID(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ inputGameID");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _serialize_inputGameShortName(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ inputGameShortName");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" bot_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" short_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _serialize_highScore(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ highScore");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" pos: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" user_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" score: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _serialize_messages_highScores(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ messages_highScores");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" scores: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" users: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _serialize_req_pq(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
|
@ -6691,8 +6821,7 @@ void _serialize_messages_setInlineGameScore(MTPStringLogger &to, int32 stage, in
|
|||
case 1: to.add(" edit_message: "); ++stages.back(); if (flag & MTPmessages_setInlineGameScore::Flag::f_edit_message) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
|
||||
case 2: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" user_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" game_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" score: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" score: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
@ -7828,8 +7957,7 @@ void _serialize_messages_setGameScore(MTPStringLogger &to, int32 stage, int32 le
|
|||
case 2: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" user_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" game_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 6: to.add(" score: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 5: to.add(" score: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
@ -8387,10 +8515,10 @@ void _serialize_messages_getBotCallbackAnswer(MTPStringLogger &to, int32 stage,
|
|||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" msg_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" data: "); ++stages.back(); if (flag & MTPmessages_getBotCallbackAnswer::Flag::f_data) { types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
|
||||
case 4: to.add(" game_id: "); ++stages.back(); if (flag & MTPmessages_getBotCallbackAnswer::Flag::f_game_id) { types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break;
|
||||
case 1: to.add(" game: "); ++stages.back(); if (flag & MTPmessages_getBotCallbackAnswer::Flag::f_game) { to.add("YES [ BY BIT 1 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break;
|
||||
case 2: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 3: to.add(" msg_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 4: to.add(" data: "); ++stages.back(); if (flag & MTPmessages_getBotCallbackAnswer::Flag::f_data) { types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
@ -8469,6 +8597,35 @@ void _serialize_messages_getAttachedStickers(MTPStringLogger &to, int32 stage, i
|
|||
}
|
||||
}
|
||||
|
||||
void _serialize_messages_getGameHighScores(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ messages_getGameHighScores");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 2: to.add(" user_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _serialize_messages_getInlineGameHighScores(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
if (stage) {
|
||||
to.add(",\n").addSpaces(lev);
|
||||
} else {
|
||||
to.add("{ messages_getInlineGameHighScores");
|
||||
to.add("\n").addSpaces(lev);
|
||||
}
|
||||
switch (stage) {
|
||||
case 0: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
case 1: to.add(" user_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
|
||||
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
|
||||
}
|
||||
}
|
||||
|
||||
void _serialize_updates_getState(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
|
||||
to.add("{ updates_getState }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
|
||||
}
|
||||
|
@ -8739,6 +8896,7 @@ namespace {
|
|||
_serializers.insert(mtpc_inputMediaGifExternal, _serialize_inputMediaGifExternal);
|
||||
_serializers.insert(mtpc_inputMediaPhotoExternal, _serialize_inputMediaPhotoExternal);
|
||||
_serializers.insert(mtpc_inputMediaDocumentExternal, _serialize_inputMediaDocumentExternal);
|
||||
_serializers.insert(mtpc_inputMediaGame, _serialize_inputMediaGame);
|
||||
_serializers.insert(mtpc_inputChatPhotoEmpty, _serialize_inputChatPhotoEmpty);
|
||||
_serializers.insert(mtpc_inputChatUploadedPhoto, _serialize_inputChatUploadedPhoto);
|
||||
_serializers.insert(mtpc_inputChatPhoto, _serialize_inputChatPhoto);
|
||||
|
@ -8800,6 +8958,7 @@ namespace {
|
|||
_serializers.insert(mtpc_messageMediaDocument, _serialize_messageMediaDocument);
|
||||
_serializers.insert(mtpc_messageMediaWebPage, _serialize_messageMediaWebPage);
|
||||
_serializers.insert(mtpc_messageMediaVenue, _serialize_messageMediaVenue);
|
||||
_serializers.insert(mtpc_messageMediaGame, _serialize_messageMediaGame);
|
||||
_serializers.insert(mtpc_messageActionEmpty, _serialize_messageActionEmpty);
|
||||
_serializers.insert(mtpc_messageActionChatCreate, _serialize_messageActionChatCreate);
|
||||
_serializers.insert(mtpc_messageActionChatEditTitle, _serialize_messageActionChatEditTitle);
|
||||
|
@ -9106,9 +9265,11 @@ namespace {
|
|||
_serializers.insert(mtpc_inputBotInlineMessageMediaGeo, _serialize_inputBotInlineMessageMediaGeo);
|
||||
_serializers.insert(mtpc_inputBotInlineMessageMediaVenue, _serialize_inputBotInlineMessageMediaVenue);
|
||||
_serializers.insert(mtpc_inputBotInlineMessageMediaContact, _serialize_inputBotInlineMessageMediaContact);
|
||||
_serializers.insert(mtpc_inputBotInlineMessageGame, _serialize_inputBotInlineMessageGame);
|
||||
_serializers.insert(mtpc_inputBotInlineResult, _serialize_inputBotInlineResult);
|
||||
_serializers.insert(mtpc_inputBotInlineResultPhoto, _serialize_inputBotInlineResultPhoto);
|
||||
_serializers.insert(mtpc_inputBotInlineResultDocument, _serialize_inputBotInlineResultDocument);
|
||||
_serializers.insert(mtpc_inputBotInlineResultGame, _serialize_inputBotInlineResultGame);
|
||||
_serializers.insert(mtpc_botInlineMessageMediaAuto, _serialize_botInlineMessageMediaAuto);
|
||||
_serializers.insert(mtpc_botInlineMessageText, _serialize_botInlineMessageText);
|
||||
_serializers.insert(mtpc_botInlineMessageMediaGeo, _serialize_botInlineMessageMediaGeo);
|
||||
|
@ -9154,6 +9315,11 @@ namespace {
|
|||
_serializers.insert(mtpc_maskCoords, _serialize_maskCoords);
|
||||
_serializers.insert(mtpc_inputStickeredMediaPhoto, _serialize_inputStickeredMediaPhoto);
|
||||
_serializers.insert(mtpc_inputStickeredMediaDocument, _serialize_inputStickeredMediaDocument);
|
||||
_serializers.insert(mtpc_game, _serialize_game);
|
||||
_serializers.insert(mtpc_inputGameID, _serialize_inputGameID);
|
||||
_serializers.insert(mtpc_inputGameShortName, _serialize_inputGameShortName);
|
||||
_serializers.insert(mtpc_highScore, _serialize_highScore);
|
||||
_serializers.insert(mtpc_messages_highScores, _serialize_messages_highScores);
|
||||
|
||||
_serializers.insert(mtpc_req_pq, _serialize_req_pq);
|
||||
_serializers.insert(mtpc_req_DH_params, _serialize_req_DH_params);
|
||||
|
@ -9330,6 +9496,8 @@ namespace {
|
|||
_serializers.insert(mtpc_messages_getRecentStickers, _serialize_messages_getRecentStickers);
|
||||
_serializers.insert(mtpc_messages_getArchivedStickers, _serialize_messages_getArchivedStickers);
|
||||
_serializers.insert(mtpc_messages_getAttachedStickers, _serialize_messages_getAttachedStickers);
|
||||
_serializers.insert(mtpc_messages_getGameHighScores, _serialize_messages_getGameHighScores);
|
||||
_serializers.insert(mtpc_messages_getInlineGameHighScores, _serialize_messages_getInlineGameHighScores);
|
||||
_serializers.insert(mtpc_updates_getState, _serialize_updates_getState);
|
||||
_serializers.insert(mtpc_updates_getDifference, _serialize_updates_getDifference);
|
||||
_serializers.insert(mtpc_updates_getChannelDifference, _serialize_updates_getChannelDifference);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -34,6 +34,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "media/media_audio.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "localstorage.h"
|
||||
#include "history/history_media_types.h"
|
||||
|
||||
namespace Overview {
|
||||
namespace Layout {
|
||||
|
@ -92,7 +93,7 @@ void RadialProgressItem::step_radial(uint64 ms, bool timer) {
|
|||
|
||||
void RadialProgressItem::ensureRadial() const {
|
||||
if (!_radial) {
|
||||
_radial = new RadialAnimation(animation(const_cast<RadialProgressItem*>(this), &RadialProgressItem::step_radial));
|
||||
_radial = new Ui::RadialAnimation(animation(const_cast<RadialProgressItem*>(this), &RadialProgressItem::step_radial));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -889,13 +890,13 @@ bool Document::updateStatusText() const {
|
|||
Link::Link(HistoryMedia *media, HistoryItem *parent) : ItemBase(parent) {
|
||||
AddComponents(Info::Bit());
|
||||
|
||||
const auto textWithEntities = _parent->originalText();
|
||||
auto textWithEntities = _parent->originalText();
|
||||
QString mainUrl;
|
||||
|
||||
auto text = textWithEntities.text;
|
||||
auto &entities = textWithEntities.entities;
|
||||
int32 from = 0, till = text.size(), lnk = entities.size();
|
||||
for_const (const auto &entity, entities) {
|
||||
for_const (auto &entity, entities) {
|
||||
auto type = entity.type();
|
||||
if (type != EntityInTextUrl && type != EntityInTextCustomUrl && type != EntityInTextEmail) {
|
||||
continue;
|
||||
|
@ -909,7 +910,7 @@ Link::Link(HistoryMedia *media, HistoryItem *parent) : ItemBase(parent) {
|
|||
}
|
||||
while (lnk > 0 && till > from) {
|
||||
--lnk;
|
||||
const auto &entity = entities.at(lnk);
|
||||
auto &entity = entities.at(lnk);
|
||||
auto type = entity.type();
|
||||
if (type != EntityInTextUrl && type != EntityInTextCustomUrl && type != EntityInTextEmail) {
|
||||
++lnk;
|
||||
|
|
|
@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "layout.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
|
||||
namespace Overview {
|
||||
namespace Layout {
|
||||
|
@ -130,7 +131,7 @@ protected:
|
|||
return false;
|
||||
}
|
||||
|
||||
mutable RadialAnimation *_radial;
|
||||
mutable Ui::RadialAnimation *_radial;
|
||||
anim::fvalue a_iconOver;
|
||||
mutable Animation _a_iconOver;
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "application.h"
|
||||
#include "playerwidget.h"
|
||||
#include "overview/overview_layout.h"
|
||||
#include "history/history_media_types.h"
|
||||
|
||||
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "mainwidget.h"
|
||||
#include "localstorage.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "history/history_media_types.h"
|
||||
|
||||
PlayerWidget::PlayerWidget(QWidget *parent) : TWidget(parent)
|
||||
, _a_state(animation(this, &PlayerWidget::step_state))
|
||||
|
|
|
@ -29,6 +29,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "lang.h"
|
||||
#include "application.h"
|
||||
#include "mainwidget.h"
|
||||
#include "history/history_location_manager.h"
|
||||
|
||||
#include "localstorage.h"
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#pragma once
|
||||
|
||||
#include "settings/settings_block_widget.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
#include "ui/filedialog.h"
|
||||
|
||||
class LinkButton;
|
||||
|
@ -61,7 +62,7 @@ private:
|
|||
ChildWidget<LinkButton> _chooseFromGallery;
|
||||
ChildWidget<LinkButton> _chooseFromFile;
|
||||
|
||||
RadialAnimation _radial;
|
||||
Ui::RadialAnimation _radial;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "lang.h"
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "observer_peer.h"
|
||||
#include "history.h"
|
||||
#include "mainwidget.h"
|
||||
#include "application.h"
|
||||
#include "fileuploader.h"
|
||||
|
@ -34,6 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "boxes/confirmbox.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "localstorage.h"
|
||||
#include "history/history_media_types.h"
|
||||
|
||||
namespace {
|
||||
int peerColorIndex(const PeerId &peer) {
|
||||
|
@ -717,11 +717,11 @@ void PhotoData::cancel() {
|
|||
}
|
||||
|
||||
void PhotoData::notifyLayoutChanged() const {
|
||||
const PhotoItems &items(App::photoItems());
|
||||
PhotoItems::const_iterator i = items.constFind(const_cast<PhotoData*>(this));
|
||||
auto &items = App::photoItems();
|
||||
auto i = items.constFind(const_cast<PhotoData*>(this));
|
||||
if (i != items.cend()) {
|
||||
for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) {
|
||||
Notify::historyItemLayoutChanged(j.key());
|
||||
for_const (auto item, i.value()) {
|
||||
Notify::historyItemLayoutChanged(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1443,12 +1443,9 @@ void DocumentData::cancel() {
|
|||
}
|
||||
|
||||
void DocumentData::notifyLayoutChanged() const {
|
||||
const DocumentItems &items(App::documentItems());
|
||||
DocumentItems::const_iterator i = items.constFind(const_cast<DocumentData*>(this));
|
||||
if (i != items.cend()) {
|
||||
for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) {
|
||||
Notify::historyItemLayoutChanged(j.key());
|
||||
}
|
||||
auto &items = App::documentItems();
|
||||
for (auto item : items.value(const_cast<DocumentData*>(this))) {
|
||||
Notify::historyItemLayoutChanged(item);
|
||||
}
|
||||
|
||||
if (auto items = InlineBots::Layout::documentItems()) {
|
||||
|
@ -1634,6 +1631,15 @@ WebPageData::WebPageData(const WebPageId &id, WebPageType type, const QString &u
|
|||
, pendingTill(pendingTill) {
|
||||
}
|
||||
|
||||
GameData::GameData(const GameId &id, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *document) : id(id)
|
||||
, accessHash(accessHash)
|
||||
, shortName(shortName)
|
||||
, title(title)
|
||||
, description(description)
|
||||
, photo(photo)
|
||||
, document(document) {
|
||||
}
|
||||
|
||||
void PeerOpenClickHandler::onClickImpl() const {
|
||||
if (App::main()) {
|
||||
if (peer() && peer()->isChannel() && App::main()->historyPeer() != peer()) {
|
||||
|
@ -1653,18 +1659,3 @@ MsgId clientMsgId() {
|
|||
Q_ASSERT(currentClientMsgId < EndClientMsgId);
|
||||
return currentClientMsgId++;
|
||||
}
|
||||
|
||||
QString LocationClickHandler::copyToClipboardContextItemText() const {
|
||||
return lang(lng_context_copy_link);
|
||||
}
|
||||
|
||||
void LocationClickHandler::onClick(Qt::MouseButton button) const {
|
||||
if (!psLaunchMaps(_coords)) {
|
||||
QDesktopServices::openUrl(_text);
|
||||
}
|
||||
}
|
||||
|
||||
void LocationClickHandler::setup() {
|
||||
QString latlon(qsl("%1,%2").arg(_coords.lat).arg(_coords.lon));
|
||||
_text = qsl("https://maps.google.com/maps?q=") + latlon + qsl("&ll=") + latlon + qsl("&z=16");
|
||||
}
|
||||
|
|
|
@ -137,11 +137,12 @@ inline TimeId dateFromMessage(const MTPmessage &msg) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
typedef uint64 PhotoId;
|
||||
typedef uint64 VideoId;
|
||||
typedef uint64 AudioId;
|
||||
typedef uint64 DocumentId;
|
||||
typedef uint64 WebPageId;
|
||||
using PhotoId = uint64;
|
||||
using VideoId = uint64;
|
||||
using AudioId = uint64;
|
||||
using DocumentId = uint64;
|
||||
using WebPageId = uint64;
|
||||
using GameId = uint64;
|
||||
static const WebPageId CancelledWebPageId = 0xFFFFFFFFFFFFFFFFULL;
|
||||
|
||||
inline bool operator==(const FullMsgId &a, const FullMsgId &b) {
|
||||
|
@ -162,6 +163,7 @@ constexpr const MsgId ShowAtTheEndMsgId = -0x40000000;
|
|||
constexpr const MsgId SwitchAtTopMsgId = -0x3FFFFFFF;
|
||||
constexpr const MsgId ShowAtProfileMsgId = -0x3FFFFFFE;
|
||||
constexpr const MsgId ShowAndStartBotMsgId = -0x3FFFFFD;
|
||||
constexpr const MsgId ShowAtGameShareMsgId = -0x3FFFFFC;
|
||||
constexpr const MsgId ServerMaxMsgId = 0x3FFFFFFF;
|
||||
constexpr const MsgId ShowAtUnreadMsgId = 0;
|
||||
|
||||
|
@ -384,7 +386,7 @@ struct BotInfo {
|
|||
QList<BotCommand> commands;
|
||||
Text text = Text{ int(st::msgMinWidth) }; // description
|
||||
|
||||
QString startToken, startGroupToken;
|
||||
QString startToken, startGroupToken, shareGameShortName;
|
||||
PeerId inlineReturnPeerId = 0;
|
||||
};
|
||||
|
||||
|
@ -1354,6 +1356,7 @@ struct WebPageData {
|
|||
WebPageData(const WebPageId &id, WebPageType type = WebPageArticle, const QString &url = QString(), const QString &displayUrl = QString(), const QString &siteName = QString(), const QString &title = QString(), const QString &description = QString(), DocumentData *doc = nullptr, PhotoData *photo = nullptr, int32 duration = 0, const QString &author = QString(), int32 pendingTill = -1);
|
||||
|
||||
void forget() {
|
||||
if (document) document->forget();
|
||||
if (photo) photo->forget();
|
||||
}
|
||||
|
||||
|
@ -1368,6 +1371,22 @@ struct WebPageData {
|
|||
|
||||
};
|
||||
|
||||
struct GameData {
|
||||
GameData(const GameId &id, const uint64 &accessHash = 0, const QString &shortName = QString(), const QString &title = QString(), const QString &description = QString(), PhotoData *photo = nullptr, DocumentData *doc = nullptr);
|
||||
|
||||
void forget() {
|
||||
if (document) document->forget();
|
||||
if (photo) photo->forget();
|
||||
}
|
||||
|
||||
GameId id;
|
||||
uint64 accessHash;
|
||||
QString shortName, title, description;
|
||||
PhotoData *photo;
|
||||
DocumentData *document;
|
||||
|
||||
};
|
||||
|
||||
QString saveFileName(const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir = QDir());
|
||||
MsgId clientMsgId();
|
||||
|
||||
|
@ -1400,70 +1419,3 @@ struct MessageCursor {
|
|||
inline bool operator==(const MessageCursor &a, const MessageCursor &b) {
|
||||
return (a.position == b.position) && (a.anchor == b.anchor) && (a.scroll == b.scroll);
|
||||
}
|
||||
|
||||
struct LocationCoords {
|
||||
LocationCoords() : lat(0), lon(0) {
|
||||
}
|
||||
LocationCoords(float64 lat, float64 lon) : lat(lat), lon(lon) {
|
||||
}
|
||||
LocationCoords(const MTPDgeoPoint &point) : lat(point.vlat.v), lon(point.vlong.v) {
|
||||
}
|
||||
float64 lat, lon;
|
||||
};
|
||||
inline bool operator==(const LocationCoords &a, const LocationCoords &b) {
|
||||
return (a.lat == b.lat) && (a.lon == b.lon);
|
||||
}
|
||||
inline bool operator<(const LocationCoords &a, const LocationCoords &b) {
|
||||
return (a.lat < b.lat) || ((a.lat == b.lat) && (a.lon < b.lon));
|
||||
}
|
||||
inline uint qHash(const LocationCoords &t, uint seed = 0) {
|
||||
#ifndef OS_MAC_OLD
|
||||
return qHash(QtPrivate::QHashCombine().operator()(qHash(t.lat), t.lon), seed);
|
||||
#else // OS_MAC_OLD
|
||||
uint h1 = qHash(t.lat, seed);
|
||||
uint h2 = qHash(t.lon, seed);
|
||||
return ((h1 << 16) | (h1 >> 16)) ^ h2 ^ seed;
|
||||
#endif // OS_MAC_OLD
|
||||
}
|
||||
|
||||
struct LocationData {
|
||||
LocationData(const LocationCoords &coords) : coords(coords), loading(false) {
|
||||
}
|
||||
|
||||
LocationCoords coords;
|
||||
ImagePtr thumb;
|
||||
bool loading;
|
||||
|
||||
void load();
|
||||
};
|
||||
|
||||
class LocationClickHandler : public ClickHandler {
|
||||
public:
|
||||
LocationClickHandler(const LocationCoords &coords) : _coords(coords) {
|
||||
setup();
|
||||
}
|
||||
|
||||
void onClick(Qt::MouseButton button) const override;
|
||||
|
||||
QString tooltip() const override {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString dragText() const override {
|
||||
return _text;
|
||||
}
|
||||
|
||||
void copyToClipboard() const override {
|
||||
if (!_text.isEmpty()) {
|
||||
QApplication::clipboard()->setText(_text);
|
||||
}
|
||||
}
|
||||
QString copyToClipboardContextItemText() const override;
|
||||
|
||||
private:
|
||||
|
||||
void setup();
|
||||
LocationCoords _coords;
|
||||
QString _text;
|
||||
|
||||
};
|
||||
|
|
|
@ -23,6 +23,26 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "media/media_clip_reader.h"
|
||||
|
||||
namespace Media {
|
||||
namespace Clip {
|
||||
|
||||
Reader *const ReaderPointer::BadPointer = SharedMemoryLocation<Reader, 0>();
|
||||
|
||||
ReaderPointer::~ReaderPointer() {
|
||||
if (valid()) {
|
||||
delete _pointer;
|
||||
}
|
||||
_pointer = nullptr;
|
||||
}
|
||||
|
||||
class Tmp;
|
||||
void f(Tmp *t) {
|
||||
delete t;
|
||||
}
|
||||
|
||||
} // namespace Clip
|
||||
} // namespace Media
|
||||
|
||||
namespace {
|
||||
|
||||
AnimationManager *_manager = nullptr;
|
||||
|
|
|
@ -29,7 +29,58 @@ namespace Media {
|
|||
namespace Clip {
|
||||
|
||||
class Reader;
|
||||
static Reader * const BadReader = SharedMemoryLocation<Reader, 0>();
|
||||
class ReaderPointer {
|
||||
public:
|
||||
ReaderPointer(std::nullptr_t = nullptr) {
|
||||
}
|
||||
explicit ReaderPointer(Reader *pointer) : _pointer(pointer) {
|
||||
}
|
||||
ReaderPointer(const ReaderPointer &other) = delete;
|
||||
ReaderPointer &operator=(const ReaderPointer &other) = delete;
|
||||
ReaderPointer(ReaderPointer &&other) : _pointer(createAndSwap(other._pointer)) {
|
||||
}
|
||||
ReaderPointer &operator=(ReaderPointer &&other) {
|
||||
swap(other);
|
||||
return *this;
|
||||
}
|
||||
void swap(ReaderPointer &other) {
|
||||
qSwap(_pointer, other._pointer);
|
||||
}
|
||||
Reader *get() const {
|
||||
return valid() ? _pointer : nullptr;
|
||||
}
|
||||
Reader *operator->() const {
|
||||
return get();
|
||||
}
|
||||
void setBad() {
|
||||
reset();
|
||||
_pointer = BadPointer;
|
||||
}
|
||||
void reset() {
|
||||
ReaderPointer temp;
|
||||
swap(temp);
|
||||
}
|
||||
bool isBad() const {
|
||||
return (_pointer == BadPointer);
|
||||
}
|
||||
bool valid() const {
|
||||
return _pointer && !isBad();
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return valid();
|
||||
}
|
||||
~ReaderPointer();
|
||||
|
||||
private:
|
||||
Reader *_pointer = nullptr;
|
||||
static Reader *const BadPointer;
|
||||
|
||||
};
|
||||
|
||||
template <typename ...Args>
|
||||
inline ReaderPointer MakeReader(Args&&... args) {
|
||||
return ReaderPointer(new Reader(std_::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
class Manager;
|
||||
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
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-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
#include "ui/effects/radial_animation.h"
|
||||
|
||||
namespace Ui {
|
||||
|
||||
RadialAnimation::RadialAnimation(AnimationCallbacks &&callbacks)
|
||||
: a_arcEnd(0, 0)
|
||||
, a_arcStart(0, FullArcLength)
|
||||
, _animation(std_::move(callbacks)) {
|
||||
}
|
||||
|
||||
void RadialAnimation::start(float64 prg) {
|
||||
_firstStart = _lastStart = _lastTime = getms();
|
||||
int32 iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength), iprgstrict = qRound(prg * AlmostFullArcLength);
|
||||
a_arcEnd = anim::ivalue(iprgstrict, iprg);
|
||||
_animation.start();
|
||||
}
|
||||
|
||||
void RadialAnimation::update(float64 prg, bool finished, uint64 ms) {
|
||||
int32 iprg = qRound(qMax(prg, 0.0001) * AlmostFullArcLength);
|
||||
if (iprg != a_arcEnd.to()) {
|
||||
a_arcEnd.start(iprg);
|
||||
_lastStart = _lastTime;
|
||||
}
|
||||
_lastTime = ms;
|
||||
|
||||
float64 dt = float64(ms - _lastStart), fulldt = float64(ms - _firstStart);
|
||||
_opacity = qMin(fulldt / st::radialDuration, 1.);
|
||||
if (!finished) {
|
||||
a_arcEnd.update(1. - (st::radialDuration / (st::radialDuration + dt)), anim::linear);
|
||||
} else if (dt >= st::radialDuration) {
|
||||
a_arcEnd.update(1, anim::linear);
|
||||
stop();
|
||||
} else {
|
||||
float64 r = dt / st::radialDuration;
|
||||
a_arcEnd.update(r, anim::linear);
|
||||
_opacity *= 1 - r;
|
||||
}
|
||||
float64 fromstart = fulldt / st::radialPeriod;
|
||||
a_arcStart.update(fromstart - std::floor(fromstart), anim::linear);
|
||||
}
|
||||
|
||||
void RadialAnimation::stop() {
|
||||
_firstStart = _lastStart = _lastTime = 0;
|
||||
a_arcEnd = anim::ivalue(0, 0);
|
||||
_animation.stop();
|
||||
}
|
||||
|
||||
void RadialAnimation::step(uint64 ms) {
|
||||
_animation.step(ms);
|
||||
}
|
||||
|
||||
void RadialAnimation::draw(Painter &p, const QRect &inner, int32 thickness, const style::color &color) {
|
||||
float64 o = p.opacity();
|
||||
p.setOpacity(o * _opacity);
|
||||
|
||||
QPen pen(color->p), was(p.pen());
|
||||
pen.setWidth(thickness);
|
||||
p.setPen(pen);
|
||||
|
||||
int32 len = MinArcLength + a_arcEnd.current();
|
||||
int32 from = QuarterArcLength - a_arcStart.current() - len;
|
||||
if (rtl()) {
|
||||
from = QuarterArcLength - (from - QuarterArcLength) - len;
|
||||
if (from < 0) from += FullArcLength;
|
||||
}
|
||||
|
||||
p.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
p.drawArc(inner, from, len);
|
||||
p.setRenderHint(QPainter::HighQualityAntialiasing, false);
|
||||
|
||||
p.setPen(was);
|
||||
p.setOpacity(o);
|
||||
}
|
||||
|
||||
} // namespace Ui
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
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-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class RadialAnimation {
|
||||
public:
|
||||
RadialAnimation(AnimationCallbacks &&callbacks);
|
||||
|
||||
float64 opacity() const {
|
||||
return _opacity;
|
||||
}
|
||||
bool animating() const {
|
||||
return _animation.animating();
|
||||
}
|
||||
|
||||
void start(float64 prg);
|
||||
void update(float64 prg, bool finished, uint64 ms);
|
||||
void stop();
|
||||
|
||||
void step(uint64 ms);
|
||||
void step() {
|
||||
step(getms());
|
||||
}
|
||||
|
||||
void draw(Painter &p, const QRect &inner, int32 thickness, const style::color &color);
|
||||
|
||||
private:
|
||||
uint64 _firstStart = 0;
|
||||
uint64 _lastStart = 0;
|
||||
uint64 _lastTime = 0;
|
||||
float64 _opacity = 0.;
|
||||
anim::ivalue a_arcEnd, a_arcStart;
|
||||
Animation _animation;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui
|
|
@ -2891,6 +2891,10 @@ TextSelection Text::adjustSelection(TextSelection selection, TextSelectType sele
|
|||
return { from, to };
|
||||
}
|
||||
|
||||
bool Text::isEmpty() const {
|
||||
return _blocks.empty() || _blocks[0]->type() == TextBlockTSkip;
|
||||
}
|
||||
|
||||
template <typename AppendPartCallback, typename ClickHandlerStartCallback, typename ClickHandlerFinishCallback, typename FlagsChangeCallback>
|
||||
void Text::enumerateText(TextSelection selection, AppendPartCallback appendPartCallback, ClickHandlerStartCallback clickHandlerStartCallback, ClickHandlerFinishCallback clickHandlerFinishCallback, FlagsChangeCallback flagsChangeCallback) const {
|
||||
if (isEmpty() || selection.empty()) {
|
||||
|
|
|
@ -173,9 +173,7 @@ public:
|
|||
return (selection.from == 0) && (selection.to >= _text.size());
|
||||
}
|
||||
|
||||
bool isEmpty() const {
|
||||
return _text.isEmpty();
|
||||
}
|
||||
bool isEmpty() const;
|
||||
bool isNull() const {
|
||||
return !_font;
|
||||
}
|
||||
|
|
|
@ -219,6 +219,15 @@
|
|||
'<(src_loc)/dialogs/dialogs_row.h',
|
||||
'<(src_loc)/history/field_autocomplete.cpp',
|
||||
'<(src_loc)/history/field_autocomplete.h',
|
||||
'<(src_loc)/history/history_item.cpp',
|
||||
'<(src_loc)/history/history_item.h',
|
||||
'<(src_loc)/history/history_location_manager.cpp',
|
||||
'<(src_loc)/history/history_location_manager.h',
|
||||
'<(src_loc)/history/history_media.h',
|
||||
'<(src_loc)/history/history_media_types.cpp',
|
||||
'<(src_loc)/history/history_media_types.h',
|
||||
'<(src_loc)/history/history_message.cpp',
|
||||
'<(src_loc)/history/history_message.h',
|
||||
'<(src_loc)/history/history_service_layout.cpp',
|
||||
'<(src_loc)/history/history_service_layout.h',
|
||||
'<(src_loc)/inline_bots/inline_bot_layout_internal.cpp',
|
||||
|
@ -409,6 +418,8 @@
|
|||
'<(src_loc)/ui/buttons/round_button.h',
|
||||
'<(src_loc)/ui/effects/fade_animation.cpp',
|
||||
'<(src_loc)/ui/effects/fade_animation.h',
|
||||
'<(src_loc)/ui/effects/radial_animation.cpp',
|
||||
'<(src_loc)/ui/effects/radial_animation.h',
|
||||
'<(src_loc)/ui/style/style_core.cpp',
|
||||
'<(src_loc)/ui/style/style_core.h',
|
||||
'<(src_loc)/ui/style/style_core_color.cpp',
|
||||
|
|
Loading…
Reference in New Issue