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:
John Preston 2016-09-29 00:31:43 +03:00
commit 903795d0e5
63 changed files with 11739 additions and 9283 deletions

View File

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

View File

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

View File

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

View File

@ -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();
}
}
}

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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();
}

View File

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

View File

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

View File

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

View File

@ -87,7 +87,7 @@ void ReportBox::onChange() {
_reasonOtherText.destroy();
updateMaxHeight();
}
if (App::wnd()) App::wnd()->setInnerFocus();
_reasonOtherText->setFocus();
}
void ReportBox::doSetInnerFocus() {

View File

@ -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 &&copyCallback, SubmitCallback &&submitCallback) : ItemListBox(st::boxScroll)
ShareBox::ShareBox(CopyCallback &&copyCallback, 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

View File

@ -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 &&copyCallback, SubmitCallback &&submitCallback);
using FilterCallback = base::lambda_unique<bool(PeerData*)>;
ShareBox(CopyCallback &&copyCallback, 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

View File

@ -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);
}
}

View File

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

View File

@ -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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
}
}

View File

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

View File

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

View File

@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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");
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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()) {

View File

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

View File

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