From 9a1798f043acd6daca3d9df140017b5a44203c6e Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 28 Dec 2015 00:37:48 +0300 Subject: [PATCH] saved gifs done --- Telegram/Resources/lang.strings | 4 + Telegram/Resources/style.txt | 9 +- Telegram/SourceFiles/apiwrap.cpp | 2 +- Telegram/SourceFiles/app.cpp | 227 ++++--- Telegram/SourceFiles/app.h | 51 +- Telegram/SourceFiles/art/sprite.png | Bin 180346 -> 181448 bytes Telegram/SourceFiles/art/sprite_200x.png | Bin 242948 -> 245405 bytes Telegram/SourceFiles/boxes/connectionbox.cpp | 58 +- Telegram/SourceFiles/boxes/connectionbox.h | 2 +- Telegram/SourceFiles/boxes/stickersetbox.cpp | 2 - Telegram/SourceFiles/config.h | 1 + Telegram/SourceFiles/dropdown.cpp | 637 ++++++++++++++++--- Telegram/SourceFiles/dropdown.h | 60 +- Telegram/SourceFiles/facades.cpp | 30 +- Telegram/SourceFiles/facades.h | 14 +- Telegram/SourceFiles/gui/animation.cpp | 201 ++++-- Telegram/SourceFiles/gui/animation.h | 198 ++++-- Telegram/SourceFiles/gui/images.cpp | 7 + Telegram/SourceFiles/gui/images.h | 13 + Telegram/SourceFiles/gui/twidget.h | 3 + Telegram/SourceFiles/history.cpp | 106 +-- Telegram/SourceFiles/history.h | 19 + Telegram/SourceFiles/historywidget.cpp | 249 ++++++-- Telegram/SourceFiles/historywidget.h | 21 +- Telegram/SourceFiles/layerwidget.cpp | 73 ++- Telegram/SourceFiles/layerwidget.h | 6 + Telegram/SourceFiles/layout.cpp | 244 ++++++- Telegram/SourceFiles/layout.h | 59 ++ Telegram/SourceFiles/localstorage.cpp | 178 +++++- Telegram/SourceFiles/localstorage.h | 5 + Telegram/SourceFiles/mainwidget.cpp | 54 +- Telegram/SourceFiles/mainwidget.h | 8 +- Telegram/SourceFiles/mediaview.cpp | 4 +- Telegram/SourceFiles/mediaview.h | 2 +- Telegram/SourceFiles/mtproto/mtp.cpp | 3 + Telegram/SourceFiles/mtproto/mtpDC.cpp | 1 + Telegram/SourceFiles/mtproto/mtpScheme.cpp | 62 +- Telegram/SourceFiles/mtproto/mtpScheme.h | 218 ++++++- Telegram/SourceFiles/mtproto/scheme.tl | 9 +- Telegram/SourceFiles/overviewwidget.cpp | 62 +- Telegram/SourceFiles/overviewwidget.h | 10 +- Telegram/SourceFiles/profilewidget.cpp | 4 +- Telegram/SourceFiles/settings.cpp | 9 +- Telegram/SourceFiles/settings.h | 11 +- Telegram/SourceFiles/structs.cpp | 30 +- Telegram/SourceFiles/structs.h | 5 + Telegram/SourceFiles/types.h | 69 ++ Telegram/SourceFiles/window.cpp | 6 +- Telegram/SourceFiles/window.h | 2 +- 49 files changed, 2456 insertions(+), 592 deletions(-) diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings index 9695dc1d3..eb01491f9 100644 --- a/Telegram/Resources/lang.strings +++ b/Telegram/Resources/lang.strings @@ -582,6 +582,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_media_auto_gif" = "Automatic GIF download"; "lng_media_auto_private_chats" = "Private chats"; "lng_media_auto_groups" = "Groups and channels"; +"lng_media_auto_play" = "Autoplay"; "lng_emoji_category0" = "Frequently used"; "lng_emoji_category1" = "People"; @@ -596,6 +597,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_switch_stickers_gifs" = "GIFs & Stickers"; "lng_switch_emoji" = "Emoji"; +"lng_saved_gifs" = "Saved GIFs"; + "lng_box_remove" = "Remove"; "lng_custom_stickers" = "Custom stickers"; @@ -724,6 +727,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org "lng_context_delete_file" = "Delete File"; "lng_context_close_file" = "Close File"; "lng_context_copy_text" = "Copy Text"; +"lng_context_save_gif" = "Save GIF"; "lng_context_to_msg" = "Go To Message"; "lng_context_reply_msg" = "Reply"; "lng_context_forward_msg" = "Forward Message"; diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index 7e31296dd..ad646bfed 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -1865,6 +1865,8 @@ emojiObjectsActive: sprite(308px, 264px, 21px, 22px); emojiSymbolsOver: sprite(84px, 196px, 21px, 22px); emojiSymbolsActive: sprite(287px, 286px, 21px, 22px); stickersSettings: sprite(140px, 124px, 21px, 22px); +savedGifsOver: sprite(329px, 286px, 21px, 22px); +savedGifsActive: sprite(350px, 286px, 21px, 22px); emojiPanCategories: #f7f7f7; @@ -1989,6 +1991,11 @@ stickerPreviewDuration: 150; stickerPreviewBg: #FFFFFFB0; stickerPreviewMin: 0.1; +savedGifsLeft: 15px; +savedGifsSkip: 3px; +savedGifHeight: 96px; +savedGifMinWidth: 64px; + verifiedCheckProfile: sprite(285px, 235px, 18px, 18px); verifiedCheckProfilePos: point(7px, 6px); verifiedCheck: sprite(285px, 221px, 14px, 14px); @@ -2020,7 +2027,7 @@ botKbScroll: flatScroll(solidScroll) { width: 10px; } -minPhotoSize: 104px; +minPhotoSize: 100px; maxMediaSize: 420px; maxStickerSize: 256px; maxGifSize: 320px; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 8ce702b2a..46b95ce1c 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -915,5 +915,5 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs } ApiWrap::~ApiWrap() { - App::deinitMedia(false); + App::clearHistories(); } diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 9c4ac09af..cf5372e4e 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -45,21 +45,14 @@ namespace { typedef QMap UpdatedPeers; UpdatedPeers updatedPeers; - typedef QHash PhotosData; PhotosData photosData; - - typedef QHash VideosData; VideosData videosData; - - typedef QHash AudiosData; AudiosData audiosData; + DocumentsData documentsData; typedef QHash ImageLinksData; ImageLinksData imageLinksData; - typedef QHash DocumentsData; - DocumentsData documentsData; - typedef QHash WebPagesData; WebPagesData webPagesData; @@ -918,11 +911,41 @@ namespace App { existing->setViewsCount(m.has_views() ? m.vviews.v : -1); - return !existing->detached(); + if (!existing->detached()) { + App::checkSavedGif(existing); + return true; + } + + return false; } return false; } + void addSavedGif(DocumentData *doc) { + SavedGifs &saved(cRefSavedGifs()); + int32 index = saved.indexOf(doc); + if (index) { + if (index > 0) saved.remove(index); + saved.push_front(doc); + if (saved.size() > cSavedGifsLimit()) saved.pop_back(); + Local::writeSavedGifs(); + + if (App::main()) emit App::main()->savedGifsUpdated(); + } + } + + void checkSavedGif(HistoryItem *item) { + if (!item->toHistoryForwarded() && item->out()) { + if (HistoryMedia *media = item->getMedia()) { + if (DocumentData *doc = media->getDocument()) { + if (doc->type == AnimatedDocument && doc->mime.toLower() == qstr("video/mp4")) { + addSavedGif(doc); + } + } + } + } + } + void feedMsgs(const QVector &msgs, NewMessageType type) { QMap msgsIds; for (int32 i = 0, l = msgs.size(); i < l; ++i) { @@ -1417,9 +1440,9 @@ namespace App { } PhotoData *photo(const PhotoId &photo) { - PhotosData::const_iterator i = photosData.constFind(photo); - if (i == photosData.cend()) { - i = photosData.insert(photo, new PhotoData(photo)); + PhotosData::const_iterator i = ::photosData.constFind(photo); + if (i == ::photosData.cend()) { + i = ::photosData.insert(photo, new PhotoData(photo)); } return i.value(); } @@ -1427,9 +1450,9 @@ namespace App { PhotoData *photoSet(const PhotoId &photo, PhotoData *convert, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full) { if (convert) { if (convert->id != photo) { - PhotosData::iterator i = photosData.find(convert->id); - if (i != photosData.cend() && i.value() == convert) { - photosData.erase(i); + PhotosData::iterator i = ::photosData.find(convert->id); + if (i != ::photosData.cend() && i.value() == convert) { + ::photosData.erase(i); } convert->id = photo; delete convert->uploadingData; @@ -1443,16 +1466,16 @@ namespace App { convert->full = full; } } - PhotosData::const_iterator i = photosData.constFind(photo); + PhotosData::const_iterator i = ::photosData.constFind(photo); PhotoData *result; LastPhotosMap::iterator inLastIter = lastPhotosMap.end(); - if (i == photosData.cend()) { + if (i == ::photosData.cend()) { if (convert) { result = convert; } else { result = new PhotoData(photo, access, date, thumb, medium, full); } - photosData.insert(photo, result); + ::photosData.insert(photo, result); } else { result = i.value(); if (result != convert && !result->date && date) { @@ -1479,9 +1502,9 @@ namespace App { } VideoData *video(const VideoId &video) { - VideosData::const_iterator i = videosData.constFind(video); - if (i == videosData.cend()) { - i = videosData.insert(video, new VideoData(video)); + VideosData::const_iterator i = ::videosData.constFind(video); + if (i == ::videosData.cend()) { + i = ::videosData.insert(video, new VideoData(video)); } return i.value(); } @@ -1489,9 +1512,9 @@ namespace App { VideoData *videoSet(const VideoId &video, VideoData *convert, const uint64 &access, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size) { if (convert) { if (convert->id != video) { - VideosData::iterator i = videosData.find(convert->id); - if (i != videosData.cend() && i.value() == convert) { - videosData.erase(i); + VideosData::iterator i = ::videosData.find(convert->id); + if (i != ::videosData.cend() && i.value() == convert) { + ::videosData.erase(i); } convert->id = video; convert->status = FileReady; @@ -1507,15 +1530,15 @@ namespace App { convert->size = size; } } - VideosData::const_iterator i = videosData.constFind(video); + VideosData::const_iterator i = ::videosData.constFind(video); VideoData *result; - if (i == videosData.cend()) { + if (i == ::videosData.cend()) { if (convert) { result = convert; } else { result = new VideoData(video, access, date, duration, w, h, thumb, dc, size); } - videosData.insert(video, result); + ::videosData.insert(video, result); } else { result = i.value(); if (result != convert && !result->date && date) { @@ -1533,9 +1556,9 @@ namespace App { } AudioData *audio(const AudioId &audio) { - AudiosData::const_iterator i = audiosData.constFind(audio); - if (i == audiosData.cend()) { - i = audiosData.insert(audio, new AudioData(audio)); + AudiosData::const_iterator i = ::audiosData.constFind(audio); + if (i == ::audiosData.cend()) { + i = ::audiosData.insert(audio, new AudioData(audio)); } return i.value(); } @@ -1543,9 +1566,9 @@ namespace App { AudioData *audioSet(const AudioId &audio, AudioData *convert, const uint64 &access, int32 date, const QString &mime, int32 duration, int32 dc, int32 size) { if (convert) { if (convert->id != audio) { - AudiosData::iterator i = audiosData.find(convert->id); - if (i != audiosData.cend() && i.value() == convert) { - audiosData.erase(i); + AudiosData::iterator i = ::audiosData.find(convert->id); + if (i != ::audiosData.cend() && i.value() == convert) { + ::audiosData.erase(i); } convert->id = audio; convert->status = FileReady; @@ -1559,15 +1582,15 @@ namespace App { convert->size = size; } } - AudiosData::const_iterator i = audiosData.constFind(audio); + AudiosData::const_iterator i = ::audiosData.constFind(audio); AudioData *result; - if (i == audiosData.cend()) { + if (i == ::audiosData.cend()) { if (convert) { result = convert; } else { result = new AudioData(audio, access, date, mime, duration, dc, size); } - audiosData.insert(audio, result); + ::audiosData.insert(audio, result); } else { result = i.value(); if (result != convert && !result->date && date) { @@ -1583,9 +1606,9 @@ namespace App { } DocumentData *document(const DocumentId &document) { - DocumentsData::const_iterator i = documentsData.constFind(document); - if (i == documentsData.cend()) { - i = documentsData.insert(document, new DocumentData(document)); + DocumentsData::const_iterator i = ::documentsData.constFind(document); + if (i == ::documentsData.cend()) { + i = ::documentsData.insert(document, new DocumentData(document)); } return i.value(); } @@ -1594,9 +1617,9 @@ namespace App { bool sentSticker = false; if (convert) { if (convert->id != document) { - DocumentsData::iterator i = documentsData.find(convert->id); - if (i != documentsData.cend() && i.value() == convert) { - documentsData.erase(i); + DocumentsData::iterator i = ::documentsData.find(convert->id); + if (i != ::documentsData.cend() && i.value() == convert) { + ::documentsData.erase(i); } convert->id = document; convert->status = FileReady; @@ -1636,9 +1659,9 @@ namespace App { Local::writeFileLocation(mediaKey(DocumentFileLocation, convert->dc, convert->id), loc); } } - DocumentsData::const_iterator i = documentsData.constFind(document); + DocumentsData::const_iterator i = ::documentsData.constFind(document); DocumentData *result; - if (i == documentsData.cend()) { + if (i == ::documentsData.cend()) { if (convert) { result = convert; } else { @@ -1646,7 +1669,7 @@ namespace App { result->recountIsImage(); if (result->sticker()) result->sticker()->loc = thumbLocation; } - documentsData.insert(document, result); + ::documentsData.insert(document, result); } else { result = i.value(); if (result != convert) { @@ -1776,16 +1799,16 @@ namespace App { void forgetMedia() { lastPhotos.clear(); lastPhotosMap.clear(); - for (PhotosData::const_iterator i = photosData.cbegin(), e = photosData.cend(); i != e; ++i) { + for (PhotosData::const_iterator i = ::photosData.cbegin(), e = ::photosData.cend(); i != e; ++i) { i.value()->forget(); } - for (VideosData::const_iterator i = videosData.cbegin(), e = videosData.cend(); i != e; ++i) { + for (VideosData::const_iterator i = ::videosData.cbegin(), e = ::videosData.cend(); i != e; ++i) { i.value()->forget(); } - for (AudiosData::const_iterator i = audiosData.cbegin(), e = audiosData.cend(); i != e; ++i) { + for (AudiosData::const_iterator i = ::audiosData.cbegin(), e = ::audiosData.cend(); i != e; ++i) { i.value()->forget(); } - for (DocumentsData::const_iterator i = documentsData.cbegin(), e = documentsData.cend(); i != e; ++i) { + for (DocumentsData::const_iterator i = ::documentsData.cbegin(), e = ::documentsData.cend(); i != e; ++i) { i.value()->forget(); } for (ImageLinksData::const_iterator i = imageLinksData.cbegin(), e = imageLinksData.cend(); i != e; ++i) { @@ -1933,32 +1956,33 @@ namespace App { delete *i; } peersData.clear(); - for (PhotosData::const_iterator i = photosData.cbegin(), e = photosData.cend(); i != e; ++i) { + for (PhotosData::const_iterator i = ::photosData.cbegin(), e = ::photosData.cend(); i != e; ++i) { delete *i; } - photosData.clear(); - for (VideosData::const_iterator i = videosData.cbegin(), e = videosData.cend(); i != e; ++i) { + ::photosData.clear(); + for (VideosData::const_iterator i = ::videosData.cbegin(), e = ::videosData.cend(); i != e; ++i) { delete *i; } - videosData.clear(); - for (AudiosData::const_iterator i = audiosData.cbegin(), e = audiosData.cend(); i != e; ++i) { + ::videosData.clear(); + for (AudiosData::const_iterator i = ::audiosData.cbegin(), e = ::audiosData.cend(); i != e; ++i) { delete *i; } - audiosData.clear(); - for (DocumentsData::const_iterator i = documentsData.cbegin(), e = documentsData.cend(); i != e; ++i) { + ::audiosData.clear(); + for (DocumentsData::const_iterator i = ::documentsData.cbegin(), e = ::documentsData.cend(); i != e; ++i) { delete *i; } - documentsData.clear(); + ::documentsData.clear(); for (WebPagesData::const_iterator i = webPagesData.cbegin(), e = webPagesData.cend(); i != e; ++i) { delete *i; } webPagesData.clear(); if (api()) api()->clearWebPageRequests(); cSetRecentStickers(RecentStickerPack()); - cSetStickersHash(0); cSetStickerSets(StickerSets()); cSetStickerSetsOrder(StickerSetsOrder()); cSetLastStickersUpdate(0); + cSetSavedGifs(SavedGifs()); + cSetLastSavedGifsUpdate(0); cSetReportSpamStatuses(ReportSpamStatuses()); ::photoItems.clear(); ::videoItems.clear(); @@ -2057,7 +2081,6 @@ namespace App { } void initMedia() { - deinitMedia(false); audioInit(); if (!::monofont) { @@ -2119,48 +2142,47 @@ namespace App { prepareCorners(MessageInSelectedCorners, st::msgRadius, st::msgInBgSelected, &st::msgInShadowSelected); prepareCorners(MessageOutCorners, st::msgRadius, st::msgOutBg, &st::msgOutShadow); prepareCorners(MessageOutSelectedCorners, st::msgRadius, st::msgOutBgSelected, &st::msgOutShadowSelected); - } - void deinitMedia(bool completely) { + void clearHistories() { textlnkOver(TextLinkPtr()); textlnkDown(TextLinkPtr()); histories().clear(); - - if (completely) { - audioFinish(); - - delete ::sprite; - ::sprite = 0; - delete ::emoji; - ::emoji = 0; - delete ::emojiLarge; - ::emojiLarge = 0; - for (int32 j = 0; j < 4; ++j) { - for (int32 i = 0; i < RoundCornersCount; ++i) { - delete ::corners[i].p[j]; ::corners[i].p[j] = 0; - } - delete ::cornersMask[j]; ::cornersMask[j] = 0; - } - for (CornersMap::const_iterator i = ::cornersMap.cbegin(), e = ::cornersMap.cend(); i != e; ++i) { - for (int32 j = 0; j < 4; ++j) { - delete i->p[j]; - } - } - ::cornersMap.clear(); - mainEmojiMap.clear(); - otherEmojiMap.clear(); - - clearAllImages(); - } else { - clearStorageImages(); - cSetServerBackgrounds(WallPapers()); - } + + clearStorageImages(); + cSetServerBackgrounds(WallPapers()); serviceImageCacheSize = imageCacheSize(); } + void deinitMedia() { + audioFinish(); + + delete ::sprite; + ::sprite = 0; + delete ::emoji; + ::emoji = 0; + delete ::emojiLarge; + ::emojiLarge = 0; + for (int32 j = 0; j < 4; ++j) { + for (int32 i = 0; i < RoundCornersCount; ++i) { + delete ::corners[i].p[j]; ::corners[i].p[j] = 0; + } + delete ::cornersMask[j]; ::cornersMask[j] = 0; + } + for (CornersMap::const_iterator i = ::cornersMap.cbegin(), e = ::cornersMap.cend(); i != e; ++i) { + for (int32 j = 0; j < 4; ++j) { + delete i->p[j]; + } + } + ::cornersMap.clear(); + mainEmojiMap.clear(); + otherEmojiMap.clear(); + + clearAllImages(); + } + void hoveredItem(HistoryItem *item) { ::hoveredItem = item; } @@ -2362,6 +2384,10 @@ namespace App { return ::photoItems; } + const PhotosData &photosData() { + return ::photosData; + } + void regVideoItem(VideoData *data, HistoryItem *item) { ::videoItems[data].insert(item, NullType()); } @@ -2374,6 +2400,10 @@ namespace App { return ::videoItems; } + const VideosData &videosData() { + return ::videosData; + } + void regAudioItem(AudioData *data, HistoryItem *item) { ::audioItems[data].insert(item, NullType()); } @@ -2386,6 +2416,10 @@ namespace App { return ::audioItems; } + const AudiosData &audiosData() { + return ::audiosData; + } + void regDocumentItem(DocumentData *data, HistoryItem *item) { ::documentItems[data].insert(item, NullType()); } @@ -2398,6 +2432,10 @@ namespace App { return ::documentItems; } + const DocumentsData &documentsData() { + return ::documentsData; + } + void regWebPageItem(WebPageData *data, HistoryItem *item) { ::webPageItems[data].insert(item, NullType()); } @@ -2436,9 +2474,10 @@ namespace App { void stopGifItems() { if (!::gifItems.isEmpty()) { - if (HistoryItem *playing = ::gifItems.begin().value()) { - if (playing->getMedia()) { - playing->getMedia()->stopInline(playing); + GifItems gifs = ::gifItems; + for (GifItems::const_iterator i = gifs.cbegin(), e = gifs.cend(); i != e; ++i) { + if (HistoryMedia *media = i.value()->getMedia()) { + media->stopInline(i.value()); } } } diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 5bebaf9d0..02146bd58 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -35,13 +35,19 @@ class FileUploader; #include "layout.h" typedef QMap HistoryItemsMap; -typedef QMap PhotoItems; -typedef QMap VideoItems; -typedef QMap AudioItems; -typedef QMap DocumentItems; -typedef QMap WebPageItems; -typedef QMap SharedContactItems; -typedef QMap GifItems; +typedef QHash PhotoItems; +typedef QHash VideoItems; +typedef QHash AudioItems; +typedef QHash DocumentItems; +typedef QHash WebPageItems; +typedef QHash SharedContactItems; +typedef QHash GifItems; + +typedef QHash PhotosData; +typedef QHash VideosData; +typedef QHash AudiosData; +typedef QHash DocumentsData; + struct ReplyMarkup { ReplyMarkup(int32 flags = 0) : flags(flags) { } @@ -79,6 +85,8 @@ namespace App { void feedChatAdmins(const MTPDupdateChatAdmins &d, bool emitPeerUpdated = true); void feedParticipantAdmin(const MTPDupdateChatParticipantAdmin &d, bool emitPeerUpdated = true); bool checkEntitiesAndViewsUpdate(const MTPDmessage &m); // returns true if item found and it is not detached + void addSavedGif(DocumentData *doc); + void checkSavedGif(HistoryItem *item); void feedMsgs(const QVector &msgs, NewMessageType type); void feedMsgs(const MTPVector &msgs, NewMessageType type); void feedInboxRead(const PeerId &peer, MsgId upTo); @@ -184,8 +192,10 @@ namespace App { const QPixmap &emojiLarge(); const QPixmap &emojiSingle(EmojiPtr emoji, int32 fontHeight); + void clearHistories(); + void initMedia(); - void deinitMedia(bool completely = true); + void deinitMedia(); void playSound(); void checkImageCacheSize(); @@ -202,18 +212,22 @@ namespace App { void regPhotoItem(PhotoData *data, HistoryItem *item); void unregPhotoItem(PhotoData *data, HistoryItem *item); const PhotoItems &photoItems(); + const PhotosData &photosData(); void regVideoItem(VideoData *data, HistoryItem *item); void unregVideoItem(VideoData *data, HistoryItem *item); const VideoItems &videoItems(); + const VideosData &videosData(); void regAudioItem(AudioData *data, HistoryItem *item); void unregAudioItem(AudioData*data, HistoryItem *item); const AudioItems &audioItems(); + const AudiosData &audiosData(); void regDocumentItem(DocumentData *data, HistoryItem *item); void unregDocumentItem(DocumentData *data, HistoryItem *item); const DocumentItems &documentItems(); + const DocumentsData &documentsData(); void regWebPageItem(WebPageData *data, HistoryItem *item); void unregWebPageItem(WebPageData *data, HistoryItem *item); @@ -276,25 +290,4 @@ namespace App { }; -inline int32 stickersCountHash(bool checkOfficial = false) { - uint32 acc = 0; - bool foundOfficial = false, foundBad = false;; - const StickerSets &sets(cStickerSets()); - const StickerSetsOrder &order(cStickerSetsOrder()); - for (StickerSetsOrder::const_iterator i = order.cbegin(), e = order.cend(); i != e; ++i) { - StickerSets::const_iterator j = sets.constFind(*i); - if (j != sets.cend()) { - if (j->id == 0) { - foundBad = true; - } else if (j->flags & MTPDstickerSet::flag_official) { - foundOfficial = true; - } - if (!(j->flags & MTPDstickerSet::flag_disabled)) { - acc = (acc * 20261) + j->hash; - } - } - } - return (!checkOfficial || (!foundBad && foundOfficial)) ? int32(acc & 0x7FFFFFFF) : 0; -} - #include "facades.h" diff --git a/Telegram/SourceFiles/art/sprite.png b/Telegram/SourceFiles/art/sprite.png index 79a8694540f1a0ab7b59fa5e7128dc07bf647cd9..7bf606124a9388749971807454343cb0c8442397 100644 GIT binary patch delta 16209 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 KB`U>?0{;&LyLWv6 delta 15098 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

?sC;b*9rY_RAC*d)@$cA3_kB0 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 delta 34317 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 + } + } + changed = true; + } + int32 autoDownloadAudio = (_audioPrivate.checked() ? 0 : dbiadNoPrivate) | (_audioGroups.checked() ? 0 : dbiadNoGroups); + if (cAutoDownloadAudio() != autoDownloadAudio) { + bool enabledPrivate = ((cAutoDownloadAudio() & dbiadNoPrivate) && !(autoDownloadAudio & dbiadNoPrivate)); + bool enabledGroups = ((cAutoDownloadAudio() & dbiadNoGroups) && !(autoDownloadAudio & dbiadNoGroups)); + cSetAutoDownloadAudio(autoDownloadAudio); + if (enabledPrivate || enabledGroups) { + const AudiosData &data(App::audiosData()); + for (AudiosData::const_iterator i = data.cbegin(), e = data.cend(); i != e; ++i) { + i.value()->automaticLoadSettingsChanged(); + } + } + changed = true; + } + int32 autoDownloadGif = (_gifPrivate.checked() ? 0 : dbiadNoPrivate) | (_gifGroups.checked() ? 0 : dbiadNoGroups); + if (cAutoDownloadGif() != autoDownloadGif) { + bool enabledPrivate = ((cAutoDownloadGif() & dbiadNoPrivate) && !(autoDownloadGif & dbiadNoPrivate)); + bool enabledGroups = ((cAutoDownloadGif() & dbiadNoGroups) && !(autoDownloadGif & dbiadNoGroups)); + cSetAutoDownloadGif(autoDownloadGif); + if (enabledPrivate || enabledGroups) { + const DocumentsData &data(App::documentsData()); + for (DocumentsData::const_iterator i = data.cbegin(), e = data.cend(); i != e; ++i) { + i.value()->automaticLoadSettingsChanged(); + } + } + changed = true; + } + if (cAutoPlayGif() != _gifPlay.checked()) { + cSetAutoPlayGif(_gifPlay.checked()); + if (!cAutoPlayGif()) { + App::stopGifItems(); + } + changed = true; + } + if (changed) Local::writeUserSettings(); onClose(); } diff --git a/Telegram/SourceFiles/boxes/connectionbox.h b/Telegram/SourceFiles/boxes/connectionbox.h index bd54f178d..79a43f378 100644 --- a/Telegram/SourceFiles/boxes/connectionbox.h +++ b/Telegram/SourceFiles/boxes/connectionbox.h @@ -77,7 +77,7 @@ private: Checkbox _photoPrivate, _photoGroups; Checkbox _audioPrivate, _audioGroups; - Checkbox _gifPrivate, _gifGroups; + Checkbox _gifPrivate, _gifGroups, _gifPlay; int32 _sectionHeight; diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index d3f235ff8..b47ee602f 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -112,7 +112,6 @@ void StickerSetInner::installDone(const MTPBool &result) { sets.erase(custom); } } - cSetStickersHash(stickersCountHash()); Local::writeStickers(); emit installed(_setId); Ui::hideLayer(); @@ -892,7 +891,6 @@ void StickersBox::onSave() { } } - cSetStickersHash(stickersCountHash()); Local::writeStickers(); if (writeRecent) Local::writeUserSettings(); emit App::main()->stickersUpdated(); diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index cdfc578e9..11212bdf5 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -130,6 +130,7 @@ enum { EmojiPanRowsPerPage = 6, StickerPanPerRow = 5, StickerPanRowsPerPage = 4, + SavedGifsMaxPerRow = 4, StickersUpdateTimeout = 3600000, // update not more than once in an hour SearchPeopleLimit = 5, diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 07e408455..c424fcdb2 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -28,6 +28,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org #include "window.h" #include "apiwrap.h" +#include "mainwidget.h" #include "boxes/confirmbox.h" #include "boxes/stickersetbox.h" @@ -732,9 +733,6 @@ EmojiPanInner::EmojiPanInner() : TWidget() _hovers[i] = QVector(_counts[i], 0); } - _saveConfigTimer.setSingleShot(true); - connect(&_saveConfigTimer, SIGNAL(timeout()), this, SLOT(onSaveConfig())); - _showPickerTimer.setSingleShot(true); connect(&_showPickerTimer, SIGNAL(timeout()), this, SLOT(onShowPicker())); connect(&_picker, SIGNAL(emojiSelected(EmojiPtr)), this, SLOT(onColorSelected(EmojiPtr))); @@ -937,15 +935,11 @@ void EmojiPanInner::selectEmoji(EmojiPtr emoji) { qSwap(*i, *(i - 1)); } } - _saveConfigTimer.start(SaveRecentEmojisTimeout); + emit saveConfigDelayed(SaveRecentEmojisTimeout); emit selected(emoji); } -void EmojiPanInner::onSaveConfig() { - Local::writeUserSettings(); -} - void EmojiPanInner::onShowPicker() { int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift; if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) { @@ -1219,6 +1213,7 @@ void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) { StickerPanInner::StickerPanInner() : TWidget() , _a_selected(animation(this, &StickerPanInner::step_selected)) , _top(0) +, _showingGifs(cShowingSavedGifs()) , _selected(-1) , _pressedSel(-1) , _settings(this, lang(lng_stickers_you_have)) @@ -1234,8 +1229,6 @@ StickerPanInner::StickerPanInner() : TWidget() _previewTimer.setSingleShot(true); connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); - - refreshStickers(); } void StickerPanInner::setMaxHeight(int32 h) { @@ -1253,11 +1246,15 @@ void StickerPanInner::setScrollTop(int top) { int StickerPanInner::countHeight() { int result = 0, minLastH = _maxHeight - st::rbEmoji.height - st::stickerPanPadding; - for (int i = 0; i < _sets.size(); ++i) { - int cnt = _sets.at(i).pack.size(), rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0); - int h = st::emojiPanHeader + rows * st::stickerPanSize.height(); - if (i == _sets.size() - 1 && h < minLastH) h = minLastH; - result += h; + if (_showingGifs) { + result = st::emojiPanHeader + _gifRows.count() * (st::savedGifHeight + st::savedGifsSkip) - st::savedGifsSkip; + } else { + for (int i = 0; i < _sets.size(); ++i) { + int cnt = _sets.at(i).pack.size(), rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0); + int h = st::emojiPanHeader + rows * st::stickerPanSize.height(); + if (i == _sets.size() - 1 && h < minLastH) h = minLastH; + result += h; + } } return qMax(minLastH, result) + st::stickerPanPadding; } @@ -1285,8 +1282,39 @@ void StickerPanInner::paintEvent(QPaintEvent *e) { if (r != rect()) { p.setClipRect(r); } - p.fillRect(r, st::white->b); + p.fillRect(r, st::white); + if (_showingGifs) { + paintSavedGifs(p, r); + } else { + paintStickers(p, r); + } +} + +void StickerPanInner::paintSavedGifs(Painter &p, const QRect &r) { + uint64 ms = getms(); + + int32 fromrow = floorclamp(r.y() - st::emojiPanHeader, st::savedGifHeight + st::savedGifsSkip, 0, _gifRows.size()); + int32 torow = ceilclamp(r.y() + r.height() - st::emojiPanHeader - st::savedGifsSkip, st::savedGifHeight + st::savedGifsSkip, 0, _gifRows.size()); + int32 fromx = rtl() ? (width() - r.x() - r.width()) : r.x(), tox = rtl() ? (width() - r.x()) : (r.x() + r.width()); + for (int32 row = fromrow; row < torow; ++row) { + const GifRow &gifRow(_gifRows.at(row)); + int32 left = st::savedGifsLeft, top = st::emojiPanHeader + row * (st::savedGifHeight + st::savedGifsSkip); + for (int32 col = 0, cols = gifRow.size(); col < cols; ++col) { + if (left >= tox) break; + + int32 w = gifRow.at(col)->width(); + if (left + w > fromx) { + p.translate(left, top); + gifRow.at(col)->paint(p, _previewShown, ms); + p.translate(-left, -top); + } + left += w + st::savedGifsSkip; + } + } +} + +void StickerPanInner::paintStickers(Painter &p, const QRect &r) { int32 fromcol = floorclamp(r.x() - st::stickerPanPadding, st::stickerPanSize.width(), 0, StickerPanPerRow); int32 tocol = ceilclamp(r.x() + r.width() - st::stickerPanPadding, st::stickerPanSize.width(), 0, StickerPanPerRow); if (rtl()) { @@ -1382,6 +1410,35 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { } if (_selected < 0 || _selected != pressed) return; + if (_showingGifs) { + int32 row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; + bool del = (col >= SavedGifsMaxPerRow); + if (del) col -= SavedGifsMaxPerRow; + if (row < _gifRows.size() && col < _gifRows.at(row).size()) { + DocumentData *doc = _gifRows.at(row).at(col)->document(); + if (del) { + int32 index = cSavedGifs().indexOf(doc); + if (index >= 0) { + cRefSavedGifs().remove(index); + Local::writeSavedGifs(); + if (App::main()) emit App::main()->savedGifsUpdated(); + + MTP::send(MTPmessages_SaveGif(MTP_inputDocument(MTP_long(doc->id), MTP_long(doc->access)), MTP_bool(true))); + } else { + refreshSavedGifs(); + } + } else { + if (doc->loaded()) { + emit selected(doc); + } else if (doc->loading()) { + doc->cancel(); + } else { + DocumentOpenLink::doOpen(doc, ActionOnLoadNone); + } + } + } + return; + } if (_selected >= MatrixRowShift * _sets.size()) { return; } @@ -1416,7 +1473,7 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { } } if (refresh) { - refreshRecent(); + refreshRecentStickers(); updateSelected(); update(); } @@ -1448,6 +1505,20 @@ void StickerPanInner::enterFromChildEvent(QEvent *e) { void StickerPanInner::clearSelection(bool fast) { _lastMousePos = mapToGlobal(QPoint(-10, -10)); if (fast) { + if (_showingGifs) { + if (_selected >= 0) { + int32 srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; + bool sdel = (scol >= SavedGifsMaxPerRow); + if (sdel) scol -= SavedGifsMaxPerRow; + if (srow < _gifRows.size() && scol < _gifRows.at(srow).size()) { + _gifRows.at(srow).at(scol)->notify_over(false); + if (sdel) _gifRows.at(srow).at(scol)->notify_deleteOver(false); + } + setCursor(style::cur_default); + } + _selected = _pressedSel = -1; + return; + } for (Animations::const_iterator i = _animations.cbegin(); i != _animations.cend(); ++i) { int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift; _sets[tab].hovers[sel] = 0; @@ -1476,28 +1547,156 @@ void StickerPanInner::clearSelection(bool fast) { } } +void StickerPanInner::hideFinish() { + clearSavedGifs(); + for (GifLayouts::const_iterator i = _gifLayouts.cbegin(), e = _gifLayouts.cend(); i != e; ++i) { + i.value()->document()->forget(); + } +} + void StickerPanInner::refreshStickers() { clearSelection(true); const StickerSets &sets(cStickerSets()); _sets.clear(); _sets.reserve(sets.size() + 1); - refreshRecent(false); + refreshRecentStickers(false); for (StickerSetsOrder::const_iterator i = cStickerSetsOrder().cbegin(), e = cStickerSetsOrder().cend(); i != e; ++i) { appendSet(*i); } - int32 h = countHeight(); - if (h != height()) resize(width(), h); + if (!_showingGifs) { + int32 h = countHeight(); + if (h != height()) resize(width(), h); + + _settings.setVisible(_sets.isEmpty()); + } else { + _settings.hide(); + } - _settings.setVisible(_sets.isEmpty()); emit refreshIcons(); updateSelected(); } +void StickerPanInner::refreshSavedGifs() { + clearSelection(true); + + clearSavedGifs(); + if (_showingGifs) { + const SavedGifs &saved(cSavedGifs()); + if (saved.isEmpty()) { + showStickerSet(RecentStickerSetId); + return; + } else { + _gifRows.reserve(saved.size()); + GifRow row; + row.reserve(SavedGifsMaxPerRow); + int32 maxWidth = width() - st::savedGifsLeft, sumWidth = 0, widths[SavedGifsMaxPerRow] = { 0 }; + for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { + DocumentData *doc = *i; + int32 w = doc->dimensions.width(), h = doc->dimensions.height(); + if ((w <= 0 || h <= 0) && !doc->thumb->isNull()) { + w = doc->thumb->width(); + h = doc->thumb->height(); + } + if (w <= 0 || h <= 0) continue; + + w = w * st::savedGifHeight / h; + widths[row.size()] = w; + + w = qMax(w, int32(st::savedGifMinWidth)); + sumWidth += w; + row.push_back(layoutPrepare(doc, (_gifRows.size() * MatrixRowShift) + row.size(), w)); + + if (row.size() >= SavedGifsMaxPerRow || sumWidth >= maxWidth - (row.size() - 1) * st::savedGifsSkip) { + _gifRows.push_back(layoutGifRow(row, widths, sumWidth)); + row.clear(); + row.reserve(SavedGifsMaxPerRow); + sumWidth = 0; + memset(widths, 0, sizeof(widths)); + } + } + if (!row.isEmpty()) { + _gifRows.push_back(row); + } + } + deleteUnusedLayouts(); + + int32 h = countHeight(); + if (h != height()) resize(width(), h); + + update(); + } + + emit refreshIcons(); + + updateSelected(); +} + +void StickerPanInner::clearSavedGifs() { + for (GifRows::const_iterator i = _gifRows.cbegin(), e = _gifRows.cend(); i != e; ++i) { + for (GifRow::const_iterator j = i->cbegin(), end = i->cend(); j != end; ++j) { + (*j)->setPosition(-1, 0); + } + } + _gifRows.clear(); +} + +void StickerPanInner::deleteUnusedLayouts() { + if (_gifRows.isEmpty()) { // delete all + for (GifLayouts::const_iterator i = _gifLayouts.cbegin(), e = _gifLayouts.cend(); i != e; ++i) { + delete i.value(); + } + _gifLayouts.clear(); + } else { + for (GifLayouts::iterator i = _gifLayouts.begin(); i != _gifLayouts.cend();) { + if (i.value()->position() < 0) { + delete i.value(); + i = _gifLayouts.erase(i); + } else { + ++i; + } + } + } +} + +LayoutSavedGif *StickerPanInner::layoutPrepare(DocumentData *doc, int32 position, int32 width) { + GifLayouts::const_iterator i = _gifLayouts.constFind(doc); + if (i == _gifLayouts.cend()) { + i = _gifLayouts.insert(doc, new LayoutSavedGif(doc)); + } + i.value()->setPosition(position, width); + return i.value(); +} + +const StickerPanInner::GifRow &StickerPanInner::layoutGifRow(const GifRow &row, int32 *widths, int32 sumWidth) { + int32 count = row.size(); + t_assert(count <= SavedGifsMaxPerRow); + + int32 availw = width() - st::savedGifsLeft - st::savedGifsSkip * (count - 1); + if (sumWidth != availw) { + for (int32 i = 0; i < count; ++i) { + int32 w = widths[i] * availw / sumWidth; + int32 actualw = qMax(w, int32(st::savedGifMinWidth)); + row.at(i)->setWidth(actualw); + + availw -= actualw; + sumWidth -= qMax(widths[i], int32(st::savedGifMinWidth)); + } + } + return row; +} + void StickerPanInner::preloadImages() { + if (_showingGifs) { + for (int32 row = 0, rows = _gifRows.size(); row < rows; ++row) { + for (int32 col = 0, cols = _gifRows.at(row).size(); col < cols; ++col) { + _gifRows.at(row).at(col)->preload(); + } + } + } uint64 ms = getms(); for (int32 i = 0, l = _sets.size(), k = 0; i < l; ++i) { for (int32 j = 0, n = _sets.at(i).pack.size(); j < n; ++j) { @@ -1518,6 +1717,8 @@ void StickerPanInner::preloadImages() { } uint64 StickerPanInner::currentSet(int yOffset) const { + if (_showingGifs) return NoneStickerSetId; + int y, ytill = 0; for (int i = 0, l = _sets.size(); i < l; ++i) { int cnt = _sets.at(i).pack.size(); @@ -1530,6 +1731,33 @@ uint64 StickerPanInner::currentSet(int yOffset) const { return _sets.isEmpty() ? RecentStickerSetId : _sets.back().id; } +void StickerPanInner::ui_repaintSavedGif(const LayoutSavedGif *layout) { + if (!_showingGifs) return; + + int32 position = layout->position(); + int32 row = position / MatrixRowShift, col = position % MatrixRowShift; + t_assert((row < _gifRows.size()) && (col < _gifRows.at(row).size())); + + const GifRow &gifRow(_gifRows.at(row)); + int32 left = st::savedGifsLeft, top = st::emojiPanHeader + row * (st::savedGifHeight + st::savedGifsSkip); + for (int32 i = 0; i < col; ++i) left += gifRow.at(i)->width() + st::savedGifsSkip; + + rtlupdate(left, top, gifRow.at(col)->width(), st::savedGifHeight); +} + +bool StickerPanInner::ui_isSavedGifVisible(const LayoutSavedGif *layout) { + int32 position = layout->position(); + int32 row = position / MatrixRowShift, col = position % MatrixRowShift; + t_assert((row < _gifRows.size()) && (col < _gifRows.at(row).size())); + + int32 top = st::emojiPanHeader + row * (st::savedGifHeight + st::savedGifsSkip); + return (top < _top + _maxHeight) && (top + st::savedGifHeight > _top); +} + +bool StickerPanInner::ui_isGifBeingChosen() { + return _showingGifs; +} + void StickerPanInner::appendSet(uint64 setId) { const StickerSets &sets(cStickerSets()); StickerSets::const_iterator it = sets.constFind(setId); @@ -1543,7 +1771,15 @@ void StickerPanInner::appendSet(uint64 setId) { _sets.push_back(DisplayedSet(it->id, it->flags, it->title, pack.size() + 1, pack)); } -void StickerPanInner::refreshRecent(bool performResize) { +void StickerPanInner::refreshRecent() { + if (_showingGifs) { + refreshSavedGifs(); + } else { + refreshRecentStickers(); + } +} + +void StickerPanInner::refreshRecentStickers(bool performResize) { _custom.clear(); clearSelection(true); StickerSets::const_iterator customIt = cStickerSets().constFind(CustomStickerSetId); @@ -1580,7 +1816,7 @@ void StickerPanInner::refreshRecent(bool performResize) { } } - if (performResize) { + if (performResize && !_showingGifs) { int32 h = countHeight(); if (h != height()) { resize(width(), h); @@ -1591,14 +1827,15 @@ void StickerPanInner::refreshRecent(bool performResize) { } } -void StickerPanInner::fillIcons(QVector &icons) { +void StickerPanInner::fillIcons(QList &icons) { icons.clear(); - if (_sets.isEmpty()) return; + icons.reserve(_sets.size() + 1); + if (!cSavedGifs().isEmpty()) icons.push_back(StickerIcon(NoneStickerSetId)); - icons.reserve(_sets.size()); + if (_sets.isEmpty()) return; int32 i = 0; if (_sets.at(0).id == RecentStickerSetId) ++i; - if (i > 0) icons.push_back(StickerIcon()); + if (i > 0) icons.push_back(StickerIcon(RecentStickerSetId)); for (int32 l = _sets.size(); i < l; ++i) { DocumentData *s = _sets.at(i).pack.at(0); int32 availw = st::rbEmoji.width - 2 * st::stickerIconPadding, availh = st::rbEmoji.height - 2 * st::stickerIconPadding; @@ -1622,6 +1859,13 @@ void StickerPanInner::fillPanels(QVector &panels) { panels.at(i)->deleteLater(); } panels.clear(); + + if (_showingGifs) { + panels.push_back(new EmojiPanel(parentWidget(), lang(lng_saved_gifs), NoneStickerSetId, true, 0)); + panels.back()->show(); + return; + } + if (_sets.isEmpty()) return; int y = 0; @@ -1637,8 +1881,9 @@ void StickerPanInner::fillPanels(QVector &panels) { } } - void StickerPanInner::refreshPanels(QVector &panels) { + if (_showingGifs) return; + if (panels.size() != _sets.size()) return fillPanels(panels); int32 y = 0; @@ -1656,6 +1901,64 @@ void StickerPanInner::updateSelected() { int32 selIndex = -1; QPoint p(mapFromGlobal(_lastMousePos)); + if (_showingGifs) { + int sx = (rtl() ? width() - p.x() : p.x()) - st::savedGifsLeft, sy = p.y() - st::emojiPanHeader; + int32 row = sy / int32(st::savedGifHeight + st::savedGifsSkip), col = 0, sel = -1; + bool del = false; + if (sx >= 0 && row >= 0 && row < _gifRows.size() && sy < (row + 1) * st::savedGifHeight) { + const GifRow &gifRow(_gifRows.at(row)); + for (int32 left = 0, cols = gifRow.size(); col < cols; ++col) { + int32 width = gifRow.at(col)->width(); + if (sx >= left && sx < left + width) { + del = (sx >= left + width - st::stickerPanDelete.pxWidth()) && (sy < row * st::savedGifHeight + st::stickerPanDelete.pxHeight()); + break; + } + left += width + st::savedGifsSkip; + } + if (col < gifRow.size()) { + sel = row * MatrixRowShift + col + (del ? SavedGifsMaxPerRow : 0); + } else { + row = col = -1; + } + } else { + row = col = -1; + } + if (_selected != sel) { + int32 srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; + int32 scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; + bool sdel = (scol >= SavedGifsMaxPerRow); + if (sdel) scol -= SavedGifsMaxPerRow; + if (srow != row || scol != col) { + if (srow >= 0 && srow < _gifRows.size()) { + if (scol >= 0 && scol < _gifRows.at(srow).size()) { + _gifRows.at(srow).at(scol)->notify_over(false); + if (sdel) _gifRows.at(srow).at(scol)->notify_deleteOver(false); + } + } + if (row >= 0 && row < _gifRows.size()) { + if (col >= 0 && col < _gifRows.at(row).size()) { + _gifRows.at(row).at(col)->notify_over(true); + if (del) _gifRows.at(row).at(col)->notify_deleteOver(true); + } + } + } else if (sdel != del) { + if (sdel) _gifRows.at(srow).at(scol)->notify_deleteOver(false); + if (del) _gifRows.at(row).at(col)->notify_deleteOver(true); + } + if ((_selected >= 0 && sel < 0) || (_selected < 0 && sel >= 0)) { + setCursor(sel >= 0 ? style::cur_pointer : style::cur_default); + } + _selected = sel; + if (_pressedSel >= 0 && _selected >= 0 && _pressedSel != _selected) { + _pressedSel = _selected; + if (row >= 0 && col >= 0) { + Ui::showStickerPreview(_gifRows.at(row).at(col)->document()); + } + } + } + return; + } + int y, ytill = 0, sx = (rtl() ? width() - p.x() : p.x()) - st::stickerPanPadding; for (int c = 0, l = _sets.size(); c < l; ++c) { const DisplayedSet &set(_sets.at(c)); @@ -1742,7 +2045,15 @@ void StickerPanInner::onSettings() { } void StickerPanInner::onPreview() { - if (_pressedSel >= 0 && _pressedSel < MatrixRowShift * _sets.size()) { + if (_pressedSel < 0) return; + if (_showingGifs) { + int32 row = _pressedSel / MatrixRowShift, col = _pressedSel % MatrixRowShift; + if (col >= SavedGifsMaxPerRow) col -= SavedGifsMaxPerRow; + if (row < _gifRows.size() && col < _gifRows.at(row).size() && _gifRows.at(row).at(col)->document()->loaded()) { + Ui::showStickerPreview(_gifRows.at(row).at(col)->document()); + _previewShown = true; + } + } else if (_pressedSel < MatrixRowShift * _sets.size()) { int tab = (_pressedSel / MatrixRowShift), sel = _pressedSel % MatrixRowShift; if (sel < _sets.at(tab).pack.size()) { Ui::showStickerPreview(_sets.at(tab).pack.at(sel)); @@ -1765,13 +2076,40 @@ void StickerPanInner::step_selected(uint64 ms, bool timer) { } toUpdate += stickerRect(tab, sel); } - if (timer)rtlupdate(toUpdate.boundingRect()); + if (timer) rtlupdate(toUpdate.boundingRect()); if (_animations.isEmpty()) _a_selected.stop(); } void StickerPanInner::showStickerSet(uint64 setId) { clearSelection(true); + if (setId == NoneStickerSetId) { + if (_gifRows.isEmpty() && !cSavedGifs().isEmpty()) { + refreshSavedGifs(); + } + bool wasNotShowingGifs = !_showingGifs; + if (wasNotShowingGifs) { + _showingGifs = true; + cSetShowingSavedGifs(true); + emit saveConfigDelayed(SaveRecentEmojisTimeout); + } + refreshSavedGifs(); + emit scrollToY(0); + emit scrollUpdated(); + return; + } + + if (_showingGifs) { + _showingGifs = false; + cSetShowingSavedGifs(false); + emit saveConfigDelayed(SaveRecentEmojisTimeout); + + Notify::clipStopperHidden(ClipStopperSavedGifsPanel); + + refreshRecentStickers(true); + emit refreshIcons(); + } + int32 y = 0; for (int c = 0; c < _sets.size(); ++c) { if (_sets.at(c).id == setId) break; @@ -1780,14 +2118,19 @@ void StickerPanInner::showStickerSet(uint64 setId) { } emit scrollToY(y); + emit scrollUpdated(); _lastMousePos = QCursor::pos(); update(); } -EmojiPanel::EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY) : TWidget(parent), -_wantedY(wantedY), _setId(setId), _special(special), _deleteVisible(false), _delete(special ? 0 : new IconedButton(this, st::notifyClose)) { // NoneStickerSetId if in emoji +EmojiPanel::EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY) : TWidget(parent) +, _wantedY(wantedY) +, _setId(setId) +, _special(special) +, _deleteVisible(false) +, _delete(special ? 0 : new IconedButton(this, st::notifyClose)) { // NoneStickerSetId if in emoji resize(st::emojiPanWidth, st::emojiPanHeader); setMouseTracking(true); setFocusPolicy(Qt::NoFocus); @@ -1815,7 +2158,7 @@ void EmojiPanel::updateText() { availw -= st::notifyClose.icon.pxWidth() + st::emojiPanHeaderLeft; } } else { - QString switchText = lang((_setId != NoneStickerSetId) ? lng_switch_emoji : lng_switch_stickers); + QString switchText = lang((_setId != NoneStickerSetId) ? lng_switch_emoji : (cSavedGifs().isEmpty() ? lng_switch_stickers : lng_switch_stickers_gifs)); availw -= st::emojiSwitchSkip + st::emojiPanHeaderFont->width(switchText); } _text = st::emojiPanHeaderFont->elided(_fullText, availw); @@ -1847,11 +2190,17 @@ void EmojiPanel::paintEvent(QPaintEvent *e) { p.drawTextLeft(st::emojiPanHeaderLeft, st::emojiPanHeaderTop, width(), _text); } -EmojiSwitchButton::EmojiSwitchButton(QWidget *parent, bool toStickers) : Button(parent), -_toStickers(toStickers), _text(lang(_toStickers ? lng_switch_stickers : lng_switch_emoji)), -_textWidth(st::emojiPanHeaderFont->width(_text)) { - int32 w = st::emojiSwitchSkip + _textWidth + (st::emojiSwitchSkip - st::emojiSwitchImgSkip); +EmojiSwitchButton::EmojiSwitchButton(QWidget *parent, bool toStickers) : Button(parent) +, _toStickers(toStickers) { setCursor(style::cur_pointer); + updateText(); +} + +void EmojiSwitchButton::updateText() { + _text = lang(_toStickers ? (cSavedGifs().isEmpty() ? lng_switch_stickers : lng_switch_stickers_gifs) : lng_switch_emoji); + _textWidth = st::emojiPanHeaderFont->width(_text); + + int32 w = st::emojiSwitchSkip + _textWidth + (st::emojiSwitchSkip - st::emojiSwitchImgSkip); resize(w, st::emojiPanHeader); } @@ -1947,6 +2296,7 @@ EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent) connect(&e_inner, SIGNAL(disableScroll(bool)), &e_scroll, SLOT(disableScroll(bool))); connect(&s_inner, SIGNAL(scrollToY(int)), &s_scroll, SLOT(scrollToY(int))); + connect(&s_inner, SIGNAL(scrollUpdated()), this, SLOT(onScroll())); connect(&e_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); connect(&s_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); @@ -1964,6 +2314,11 @@ EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent) connect(&e_inner, SIGNAL(needRefreshPanels()), this, SLOT(onRefreshPanels())); connect(&s_inner, SIGNAL(needRefreshPanels()), this, SLOT(onRefreshPanels())); + _saveConfigTimer.setSingleShot(true); + connect(&_saveConfigTimer, SIGNAL(timeout()), this, SLOT(onSaveConfig())); + connect(&e_inner, SIGNAL(saveConfigDelayed(int32)), this, SLOT(onSaveConfigDelayed(int32))); + connect(&s_inner, SIGNAL(saveConfigDelayed(int32)), this, SLOT(onSaveConfigDelayed(int32))); + if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); } @@ -2020,6 +2375,14 @@ void EmojiPan::onWndActiveChanged() { } } +void EmojiPan::onSaveConfig() { + Local::writeUserSettings(); +} + +void EmojiPan::onSaveConfigDelayed(int32 delay) { + _saveConfigTimer.start(delay); +} + void EmojiPan::paintEvent(QPaintEvent *e) { Painter p(this); @@ -2040,24 +2403,20 @@ void EmojiPan::paintEvent(QPaintEvent *e) { p.drawSpriteLeft(_iconsLeft + 7 * st::rbEmoji.width + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::stickersSettings); if (!_icons.isEmpty()) { - int32 x = _iconsLeft, i = 0, selxrel = _iconSelX.current(), selx = x + selxrel - _iconsX.current(); - if (!_icons.at(i).sticker) { - if (selxrel > 0) { - if (_iconHovers.at(i) < 1) { - p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::rbEmojiRecent.imageRect); - } - if (_iconHovers.at(i) > 0) { - p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::rbEmojiRecent.overImageRect); - } + int32 x = _iconsLeft, i = 0, selxrel = _iconsLeft + _iconSelX.current(), selx = selxrel - _iconsX.current(); + for (int32 l = _icons.size(); i < l && !_icons.at(i).sticker; ++i) { + bool gifs = (_icons.at(i).setId == NoneStickerSetId); + if (selxrel != x) { + p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), gifs ? st::savedGifsOver : st::rbEmojiRecent.imageRect); } - if (selxrel < st::rbEmoji.width) { - p.setOpacity(1 - (selxrel / st::rbEmoji.width)); - p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::rbEmojiRecent.chkImageRect); + if (selxrel < x + st::rbEmoji.width && selxrel > x - st::rbEmoji.width) { + p.setOpacity(1 - (qAbs(selxrel - x) / st::rbEmoji.width)); + p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), gifs ? st::savedGifsActive : st::rbEmojiRecent.chkImageRect); p.setOpacity(1); } x += st::rbEmoji.width; - ++i; } + int32 skip = i; QRect clip(x, _iconsTop, _iconsLeft + 7 * st::rbEmoji.width - x, st::rbEmoji.height); if (rtl()) clip.moveLeft(width() - x - clip.width()); @@ -2065,28 +2424,23 @@ void EmojiPan::paintEvent(QPaintEvent *e) { i += _iconsX.current() / int(st::rbEmoji.width); x -= _iconsX.current() % int(st::rbEmoji.width); - for (int32 l = qMin(_icons.size(), i + 7 + (_icons.at(0).sticker ? 1 : 0)); i < l; ++i) { + for (int32 l = qMin(_icons.size(), i + 8 - skip); i < l; ++i) { const StickerIcon &s(_icons.at(i)); s.sticker->thumb->load(); QPixmap pix(s.sticker->thumb->pix(s.pixw, s.pixh)); - //if (_iconSel == i) { - // p.setOpacity(1); - //} else { - // p.setOpacity(1. * _iconHovers.at(i) + st::stickerIconOpacity * (1 - _iconHovers.at(i))); - //} + p.drawPixmapLeft(x + (st::rbEmoji.width - s.pixw) / 2, _iconsTop + (st::rbEmoji.height - s.pixh) / 2, width(), pix); x += st::rbEmoji.width; - p.setOpacity(1); } if (rtl()) selx = width() - selx - st::rbEmoji.width; - p.setOpacity(_icons.at(0).sticker ? 1. : qMax(1., selx / st::rbEmoji.width)); - p.fillRect(selx, _iconsTop + st::rbEmoji.height - st::stickerIconPadding, st::rbEmoji.width, st::stickerIconSel, st::stickerIconSelColor->b); + p.setOpacity(skip ? qMax(1., selx / (skip * st::rbEmoji.width)) : 1.); + p.fillRect(selx, _iconsTop + st::rbEmoji.height - st::stickerIconPadding, st::rbEmoji.width, st::stickerIconSel, st::stickerIconSelColor); float64 o_left = snap(float64(_iconsX.current()) / st::stickerIconLeft.pxWidth(), 0., 1.); if (o_left > 0) { p.setOpacity(o_left); - p.drawSpriteLeft(QRect(_iconsLeft + (_icons.at(0).sticker ? 0 : st::rbEmoji.width), _iconsTop, st::stickerIconLeft.pxWidth(), st::rbEmoji.height), width(), st::stickerIconLeft); + p.drawSpriteLeft(QRect(_iconsLeft + skip * st::rbEmoji.width, _iconsTop, st::stickerIconLeft.pxWidth(), st::rbEmoji.height), width(), st::stickerIconLeft); } float64 o_right = snap(float64(_iconsMax - _iconsX.current()) / st::stickerIconRight.pxWidth(), 0., 1.); if (o_right > 0) { @@ -2181,7 +2535,12 @@ void EmojiPan::mouseMoveEvent(QMouseEvent *e) { _iconsMousePos = e ? e->globalPos() : QCursor::pos(); updateSelected(); - if (!_iconsDragging && !_icons.isEmpty() && _iconDown >= (_icons.at(0).sticker ? 0 : 1)) { + int32 skip = 0; + for (int32 i = 0, l = _icons.size(); i < l; ++i) { + if (_icons.at(i).sticker) break; + ++skip; + } + if (!_iconsDragging && !_icons.isEmpty() && _iconDown >= skip) { if ((_iconsMousePos - _iconsMouseDown).manhattanLength() >= QApplication::startDragDistance()) { _iconsDragging = true; } @@ -2226,24 +2585,31 @@ void EmojiPan::mouseReleaseEvent(QMouseEvent *e) { bool EmojiPan::event(QEvent *e) { if (e->type() == QEvent::TouchBegin) { - int a = 0; - } else if (e->type() == QEvent::Wheel && !_icons.isEmpty() && _iconOver >= (_icons.at(0).sticker ? 0 : 1) && _iconOver < _icons.size() && _iconDown < 0) { - QWheelEvent *ev = static_cast(e); - bool hor = (ev->angleDelta().x() != 0 || ev->orientation() == Qt::Horizontal); - bool ver = (ev->angleDelta().y() != 0 || ev->orientation() == Qt::Vertical); - if (hor) _horizontal = true; - int32 newX = _iconsX.current(); - if (/*_horizontal && */hor) { - newX = snap(newX - (rtl() ? -1 : 1) * (ev->pixelDelta().x() ? ev->pixelDelta().x() : ev->angleDelta().x()), 0, _iconsMax); - } else if (/*!_horizontal && */ver) { - newX = snap(newX - (ev->pixelDelta().y() ? ev->pixelDelta().y() : ev->angleDelta().y()), 0, _iconsMax); + + } else if (e->type() == QEvent::Wheel) { + int32 skip = 0; + for (int32 i = 0, l = _icons.size(); i < l; ++i) { + if (_icons.at(i).sticker) break; + ++skip; } - if (newX != _iconsX.current()) { - _iconsX = anim::ivalue(newX, newX); - _iconsStartAnim = 0; - if (_iconAnimations.isEmpty()) _a_icons.stop(); - updateSelected(); - updateIcons(); + if (!_icons.isEmpty() && _iconOver >= skip && _iconOver < _icons.size() && _iconDown < 0) { + QWheelEvent *ev = static_cast(e); + bool hor = (ev->angleDelta().x() != 0 || ev->orientation() == Qt::Horizontal); + bool ver = (ev->angleDelta().y() != 0 || ev->orientation() == Qt::Vertical); + if (hor) _horizontal = true; + int32 newX = _iconsX.current(); + if (/*_horizontal && */hor) { + newX = snap(newX - (rtl() ? -1 : 1) * (ev->pixelDelta().x() ? ev->pixelDelta().x() : ev->angleDelta().x()), 0, _iconsMax); + } else if (/*!_horizontal && */ver) { + newX = snap(newX - (ev->pixelDelta().y() ? ev->pixelDelta().y() : ev->angleDelta().y()), 0, _iconsMax); + } + if (newX != _iconsX.current()) { + _iconsX = anim::ivalue(newX, newX); + _iconsStartAnim = 0; + if (_iconAnimations.isEmpty()) _a_icons.stop(); + updateSelected(); + updateIcons(); + } } } return TWidget::event(e); @@ -2266,6 +2632,15 @@ void EmojiPan::refreshStickers() { } } +void EmojiPan::refreshSavedGifs() { + e_switch.updateText(); + e_switch.moveToRight(0, 0, st::emojiPanWidth); + s_inner.refreshSavedGifs(); + if (!_stickersShown) { + s_inner.preloadImages(); + } +} + void EmojiPan::onRefreshIcons() { _iconOver = -1; _iconHovers.clear(); @@ -2284,6 +2659,7 @@ void EmojiPan::onRefreshIcons() { } updatePanelsPositions(s_panels, s_scroll.scrollTop()); updateSelected(); + if (_stickersShown) validateSelectedIcon(); updateIcons(); } @@ -2316,16 +2692,18 @@ void EmojiPan::updateSelected() { newOver = _icons.size(); } else if (!_icons.isEmpty()) { if (y >= _iconsTop && y < _iconsTop + st::rbEmoji.height && x >= 0 && x < 7 * st::rbEmoji.width && x < _icons.size() * st::rbEmoji.width) { - if (!_icons.at(0).sticker) { + int32 skip = 0; + for (int32 i = 0, l = _icons.size(); i < l; ++i) { if (x < st::rbEmoji.width) { - newOver = 0; - } else { - x -= st::rbEmoji.width; + newOver = i; + break; } + x -= st::rbEmoji.width; + ++skip; } if (newOver < 0) { x += _iconsX.current(); - newOver = qFloor(x / st::rbEmoji.width) + (_icons.at(0).sticker ? 0 : 1); + newOver = qFloor(x / st::rbEmoji.width) + skip; } } } @@ -2459,6 +2837,7 @@ void EmojiPan::hideStart() { void EmojiPan::hideFinish() { hide(); e_inner.hideFinish(); + s_inner.hideFinish(); _cache = _toCache = _fromCache = QPixmap(); _a_slide.stop(); _horizontal = false; @@ -2478,6 +2857,8 @@ void EmojiPan::hideFinish() { _a_icons.stop(); _iconHovers = _icons.isEmpty() ? QVector() : QVector(_icons.size(), 0); _iconAnimations.clear(); + + Notify::clipStopperHidden(ClipStopperSavedGifsPanel); } void EmojiPan::showStart() { @@ -2543,6 +2924,26 @@ void EmojiPan::stickersInstalled(uint64 setId) { showStart(); } +void EmojiPan::ui_repaintSavedGif(const LayoutSavedGif *layout) { + if (_stickersShown && !isHidden()) { + s_inner.ui_repaintSavedGif(layout); + } +} + +bool EmojiPan::ui_isSavedGifVisible(const LayoutSavedGif *layout) { + if (_stickersShown && !isHidden()) { + return s_inner.ui_isSavedGifVisible(layout); + } + return false; +} + +bool EmojiPan::ui_isGifBeingChosen() { + if (_stickersShown && !isHidden()) { + return s_inner.ui_isGifBeingChosen(); + } + return false; +} + void EmojiPan::showAll() { if (_stickersShown) { s_scroll.show(); @@ -2636,38 +3037,60 @@ void EmojiPan::onScroll() { st = s_scroll.scrollTop(); if (_stickersShown) { updatePanelsPositions(s_panels, st); - - uint64 setId = s_inner.currentSet(st); - int32 newSel = 0; - for (int32 i = 0, l = _icons.size(); i < l; ++i) { - if (_icons.at(i).setId == setId) { - newSel = i; - break; - } - } - if (newSel != _iconSel) { - _iconSel = newSel; - _iconSelX.start(newSel * st::rbEmoji.width); - _iconsX.start(snap((2 * newSel - 7 - ((_icons.isEmpty() || _icons.at(0).sticker) ? 0 : 1)) * int(st::rbEmoji.width) / 2, 0, _iconsMax)); - _iconsStartAnim = getms(); - _a_icons.start(); - updateSelected(); - updateIcons(); - } + validateSelectedIcon(true); } s_inner.setScrollTop(st); } +void EmojiPan::validateSelectedIcon(bool animated) { + uint64 setId = s_inner.currentSet(s_scroll.scrollTop()); + int32 newSel = 0; + for (int32 i = 0, l = _icons.size(); i < l; ++i) { + if (_icons.at(i).setId == setId) { + newSel = i; + break; + } + } + if (newSel != _iconSel) { + _iconSel = newSel; + int32 skip = 0; + for (int32 i = 0, l = _icons.size(); i < l; ++i) { + if (_icons.at(i).sticker) break; + ++skip; + } + if (animated) { + _iconSelX.start(newSel * st::rbEmoji.width); + } else { + _iconSelX = anim::ivalue(newSel * st::rbEmoji.width, newSel * st::rbEmoji.width); + } + _iconsX.start(snap((2 * newSel - 7 - skip) * int(st::rbEmoji.width) / 2, 0, _iconsMax)); + _iconsStartAnim = getms(); + _a_icons.start(); + updateSelected(); + updateIcons(); + } +} + void EmojiPan::onSwitch() { QPixmap cache = _cache; _fromCache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); _stickersShown = !_stickersShown; + if (!_stickersShown) { + Notify::clipStopperHidden(ClipStopperSavedGifsPanel); + } + if (cShowingSavedGifs() && cSavedGifs().isEmpty()) { + s_inner.showStickerSet(DefaultStickerSetId); + } else if (!cShowingSavedGifs() && !cSavedGifs().isEmpty() && cStickerSets().isEmpty()) { + s_inner.showStickerSet(NoneStickerSetId); + } _iconOver = -1; _iconHovers = _icons.isEmpty() ? QVector() : QVector(_icons.size(), 0); _iconAnimations.clear(); _a_icons.stop(); + validateSelectedIcon(); + _cache = QPixmap(); showAll(); _toCache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); @@ -2721,7 +3144,6 @@ void EmojiPan::onRemoveSetSure() { cRefStickerSets().erase(it); int32 removeIndex = cStickerSetsOrder().indexOf(_removingSetId); if (removeIndex >= 0) cRefStickerSetsOrder().removeAt(removeIndex); - cSetStickersHash(stickersCountHash()); refreshStickers(); Local::writeStickers(); if (writeRecent) Local::writeUserSettings(); @@ -3337,5 +3759,16 @@ bool MentionsDropdown::eventFilter(QObject *obj, QEvent *e) { return QWidget::eventFilter(obj, e); } +void MentionsDropdown::ui_repaintSavedGif(const LayoutSavedGif *layout) { +} + +bool MentionsDropdown::ui_isSavedGifVisible(const LayoutSavedGif *layout) { + return false; +} + +bool MentionsDropdown::ui_isGifBeingChosen() { + return false; +} + MentionsDropdown::~MentionsDropdown() { } diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h index 4b5ef4741..fc69d249d 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/dropdown.h @@ -258,7 +258,6 @@ public: public slots: void updateSelected(); - void onSaveConfig(); void onShowPicker(); void onPickerHidden(); @@ -276,6 +275,7 @@ signals: void disableScroll(bool dis); void needRefreshPanels(); + void saveConfigDelayed(int32 delay); private: @@ -300,14 +300,12 @@ private: int32 _selected, _pressedSel, _pickerSel; QPoint _lastMousePos; - QTimer _saveConfigTimer; - EmojiColorPicker _picker; QTimer _showPickerTimer; }; struct StickerIcon { - StickerIcon() : setId(RecentStickerSetId), sticker(0), pixw(0), pixh(0) { + StickerIcon(uint64 setId) : setId(setId), sticker(0), pixw(0), pixh(0) { } StickerIcon(uint64 setId, DocumentData *sticker, int32 pixw, int32 pixh) : setId(setId), sticker(sticker), pixw(pixw), pixh(pixh) { } @@ -335,14 +333,17 @@ public: void step_selected(uint64 ms, bool timer); + void hideFinish(); void showStickerSet(uint64 setId); void clearSelection(bool fast = false); void refreshStickers(); - void refreshRecent(bool resize = true); + void refreshRecentStickers(bool resize = true); + void refreshSavedGifs(); + void refreshRecent(); - void fillIcons(QVector &icons); + void fillIcons(QList &icons); void fillPanels(QVector &panels); void refreshPanels(QVector &panels); @@ -351,6 +352,15 @@ public: uint64 currentSet(int yOffset) const; + void ui_repaintSavedGif(const LayoutSavedGif *layout); + bool ui_isSavedGifVisible(const LayoutSavedGif *layout); + bool ui_isGifBeingChosen(); + + ~StickerPanInner() { + clearSavedGifs(); + deleteUnusedLayouts(); + } + public slots: void updateSelected(); @@ -367,11 +377,17 @@ signals: void switchToEmoji(); void scrollToY(int y); + void scrollUpdated(); void disableScroll(bool dis); void needRefreshPanels(); + void saveConfigDelayed(int32 delay); + private: + void paintSavedGifs(Painter &p, const QRect &r); + void paintStickers(Painter &p, const QRect &r); + int32 _maxHeight; void appendSet(uint64 setId); @@ -398,6 +414,19 @@ private: QList _sets; QList _custom; + bool _showingGifs; + + typedef QList GifRow; + typedef QList GifRows; + GifRows _gifRows; + void clearSavedGifs(); + void deleteUnusedLayouts(); + + typedef QMap GifLayouts; + GifLayouts _gifLayouts; + LayoutSavedGif *layoutPrepare(DocumentData *doc, int32 position, int32 width); + const GifRow &layoutGifRow(const GifRow &row, int32 *widths, int32 sumWidth); + int32 _selected, _pressedSel; QPoint _lastMousePos; @@ -452,6 +481,7 @@ public: EmojiSwitchButton(QWidget *parent, bool toStickers); // otherwise toEmoji void paintEvent(QPaintEvent *e); + void updateText(); protected: @@ -504,9 +534,14 @@ public: ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } + void ui_repaintSavedGif(const LayoutSavedGif *layout); + bool ui_isSavedGifVisible(const LayoutSavedGif *layout); + bool ui_isGifBeingChosen(); + public slots: void refreshStickers(); + void refreshSavedGifs(); void hideStart(); void hideFinish(); @@ -525,6 +560,9 @@ public slots: void onRefreshIcons(); void onRefreshPanels(); + void onSaveConfig(); + void onSaveConfigDelayed(int32 delay); + signals: void emojiSelected(EmojiPtr emoji); @@ -533,6 +571,8 @@ signals: private: + void validateSelectedIcon(bool animated = false); + int32 _maxHeight; bool _horizontal; @@ -561,7 +601,7 @@ private: BoxShadow _shadow; FlatRadiobutton _recent, _people, _nature, _food, _activity, _travel, _objects, _symbols; - QVector _icons; + QList _icons; QVector _iconHovers; int32 _iconOver, _iconSel, _iconDown; bool _iconsDragging; @@ -591,6 +631,8 @@ private: uint64 _removingSetId; + QTimer _saveConfigTimer; + }; typedef QList MentionRows; @@ -679,6 +721,10 @@ public: return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); } + void ui_repaintSavedGif(const LayoutSavedGif *layout); + bool ui_isSavedGifVisible(const LayoutSavedGif *layout); + bool ui_isGifBeingChosen(); + ~MentionsDropdown(); signals: diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index cc37c0c81..511dbadb0 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -102,20 +102,36 @@ namespace Ui { return false; } - void clipRedraw(ClipReader *reader) { + bool isGifBeingChosen() { + if (MainWidget *m = App::main()) return m->ui_isGifBeingChosen(); + return false; + } + + void clipRepaint(ClipReader *reader) { const GifItems &items(App::gifItems()); GifItems::const_iterator it = items.constFind(reader); if (it != items.cend()) { if (reader->currentDisplayed()) { return; } - Ui::redrawHistoryItem(it.value()); + Ui::repaintHistoryItem(it.value()); } - if (Window *w = App::wnd()) w->ui_clipRedraw(reader); + if (Window *w = App::wnd()) w->ui_clipRepaint(reader); } - void redrawHistoryItem(const HistoryItem *item) { - if (MainWidget *m = App::main()) m->ui_redrawHistoryItem(item); + void repaintHistoryItem(const HistoryItem *item) { + if (!item) return; + if (MainWidget *m = App::main()) m->ui_repaintHistoryItem(item); + } + + void repaintSavedGif(const LayoutSavedGif *layout) { + if (!layout) return; + if (MainWidget *m = App::main()) m->ui_repaintSavedGif(layout); + } + + bool isSavedGifVisible(const LayoutSavedGif *layout) { + if (MainWidget *m = App::main()) return m->ui_isSavedGifVisible(layout); + return false; } void showPeerHistory(const PeerId &peer, MsgId msgId, bool back) { @@ -148,8 +164,8 @@ namespace Notify { if (MainWidget *m = App::main()) m->notify_migrateUpdated(peer); } - void mediaViewHidden() { - if (MainWidget *m = App::main()) m->notify_mediaViewHidden(); + void clipStopperHidden(ClipStopperType type) { + if (MainWidget *m = App::main()) m->notify_clipStopperHidden(type); } void clipReinit(ClipReader *reader) { diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 00f0355cc..ca254fb26 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -46,10 +46,13 @@ namespace Ui { // openssl doesn't allow me to use UI :( void hideLayer(bool fast = false); bool isLayerShown(); bool isMediaViewShown(); + bool isGifBeingChosen(); - void clipRedraw(ClipReader *reader); + void clipRepaint(ClipReader *reader); - void redrawHistoryItem(const HistoryItem *item); + void repaintHistoryItem(const HistoryItem *item); + void repaintSavedGif(const LayoutSavedGif *layout); + bool isSavedGifVisible(const LayoutSavedGif *reader); void showPeerHistory(const PeerId &peer, MsgId msgId, bool back = false); inline void showPeerHistory(const PeerData *peer, MsgId msgId, bool back = false) { @@ -68,6 +71,11 @@ namespace Ui { // openssl doesn't allow me to use UI :( }; +enum ClipStopperType { + ClipStopperMediaview, + ClipStopperSavedGifsPanel, +}; + namespace Notify { void userIsBotChanged(UserData *user); @@ -76,7 +84,7 @@ namespace Notify { void migrateUpdated(PeerData *peer); - void mediaViewHidden(); + void clipStopperHidden(ClipStopperType type); void clipReinit(ClipReader *reader); diff --git a/Telegram/SourceFiles/gui/animation.cpp b/Telegram/SourceFiles/gui/animation.cpp index 1295534ad..59830a2af 100644 --- a/Telegram/SourceFiles/gui/animation.cpp +++ b/Telegram/SourceFiles/gui/animation.cpp @@ -118,34 +118,104 @@ void Animation::stop() { _manager->stop(this); } -void AnimationManager::clipReinit(ClipReader *reader) { +AnimationManager::AnimationManager() : _timer(this), _iterating(false) { + _timer.setSingleShot(false); + connect(&_timer, SIGNAL(timeout()), this, SLOT(timeout())); +} + +void AnimationManager::start(Animation *obj) { + if (_iterating) { + _starting.insert(obj, NullType()); + if (!_stopping.isEmpty()) { + _stopping.remove(obj); + } + } else { + if (_objects.isEmpty()) { + _timer.start(AnimationTimerDelta); + } + _objects.insert(obj, NullType()); + } +} + +void AnimationManager::stop(Animation *obj) { + if (_iterating) { + _stopping.insert(obj, NullType()); + if (!_starting.isEmpty()) { + _starting.insert(obj, NullType()); + } + } else { + AnimatingObjects::iterator i = _objects.find(obj); + if (i != _objects.cend()) { + _objects.erase(i); + if (_objects.isEmpty()) { + _timer.stop(); + } + } + } +} + +void AnimationManager::timeout() { + _iterating = true; + uint64 ms = getms(); + for (AnimatingObjects::const_iterator i = _objects.begin(), e = _objects.end(); i != e; ++i) { + i.key()->step(ms, true); + } + _iterating = false; + + if (!_starting.isEmpty()) { + for (AnimatingObjects::iterator i = _starting.begin(), e = _starting.end(); i != e; ++i) { + _objects.insert(i.key(), NullType()); + } + _starting.clear(); + } + if (!_stopping.isEmpty()) { + for (AnimatingObjects::iterator i = _stopping.begin(), e = _stopping.end(); i != e; ++i) { + _objects.remove(i.key()); + } + _stopping.clear(); + } + if (!_objects.size()) { + _timer.stop(); + } +} + +void AnimationManager::clipReinit(ClipReader *reader, qint32 threadIndex) { + ClipReader::callback(reader, threadIndex, ClipReaderReinit); Notify::clipReinit(reader); } -void AnimationManager::clipRedraw(ClipReader *reader) { - Ui::clipRedraw(reader); +void AnimationManager::clipRepaint(ClipReader *reader, qint32 threadIndex) { + ClipReader::callback(reader, threadIndex, ClipReaderRepaint); + Ui::clipRepaint(reader); } -QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, QImage &cache, bool smooth) { +QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, QImage &cache, bool hasAlpha) { bool badSize = (original.width() != request.framew) || (original.height() != request.frameh); bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh); - if (badSize || needOuter || request.rounded) { + if (badSize || needOuter || hasAlpha || request.rounded) { int32 factor(request.factor); bool fill = false; if (cache.width() != request.outerw || cache.height() != request.outerh) { cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied); - if (request.framew < request.outerw || request.frameh < request.outerh || original.hasAlphaChannel()) { + if (request.framew < request.outerw || request.frameh < request.outerh || hasAlpha) { fill = true; } cache.setDevicePixelRatio(factor); } { Painter p(&cache); - if (fill) p.fillRect(0, 0, cache.width() / factor, cache.height() / factor, st::black); - if (smooth && badSize) p.setRenderHint(QPainter::SmoothPixmapTransform); - QRect to((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor), request.framew / factor, request.frameh / factor); - QRect from(0, 0, original.width(), original.height()); - p.drawImage(to, original, from, Qt::ColorOnly); + if (fill) { + p.fillRect(0, 0, cache.width() / factor, cache.height() / factor, st::black); + } + QPoint position((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor)); + if (badSize) { + p.setRenderHint(QPainter::SmoothPixmapTransform); + QRect to(position, QSize(request.framew / factor, request.frameh / factor)); + QRect from(0, 0, original.width(), original.height()); + p.drawImage(to, original, from, Qt::ColorOnly); + } else { + p.drawImage(position, original); + } } if (request.rounded) { imageRound(cache); @@ -155,7 +225,9 @@ QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, Q return QPixmap::fromImage(original, Qt::ColorOnly); } -ClipReader::ClipReader(const FileLocation &location, const QByteArray &data) : _state(ClipReading) +ClipReader::ClipReader(const FileLocation &location, const QByteArray &data, Callback *cb) +: _cb(cb) +, _state(ClipReading) , _width(0) , _height(0) , _currentDisplayed(1) @@ -182,6 +254,13 @@ ClipReader::ClipReader(const FileLocation &location, const QByteArray &data) : _ _clipManagers.at(_threadIndex)->append(this, location, data); } +void ClipReader::callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification) { + // check if reader is not deleted already + if (_clipManagers.size() > threadIndex && _clipManagers.at(threadIndex)->carries(reader)) { + if (reader->_cb) reader->_cb->call(notification); + } +} + void ClipReader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded) { if (_clipManagers.size() <= _threadIndex) error(); if (_state == ClipError) return; @@ -270,6 +349,8 @@ void ClipReader::error() { ClipReader::~ClipReader() { stop(); + delete _cb; + setBadPointer(_cb); } class ClipReaderImplementation { @@ -277,7 +358,7 @@ public: ClipReaderImplementation(FileLocation *location, QByteArray *data) : _location(location), _data(data), _device(0) { } - virtual bool readNextFrame(QImage &to) = 0; + virtual bool readNextFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0; virtual int32 nextFrameDelay() = 0; virtual bool start() = 0; virtual ~ClipReaderImplementation() { @@ -312,7 +393,7 @@ public: , _frameDelay(0) { } - bool readNextFrame(QImage &to) { + bool readNextFrame(QImage &to, bool &hasAlpha, const QSize &size) { if (_reader) _frameDelay = _reader->nextImageDelay(); if (_framesLeft < 1 && !jumpToStart()) { return false; @@ -324,19 +405,24 @@ public: } --_framesLeft; - int32 w = frame.width(), h = frame.height(); - if (to.width() == w && to.height() == h && to.format() == frame.format()) { - if (to.byteCount() != frame.byteCount()) { - int bpl = qMin(to.bytesPerLine(), frame.bytesPerLine()); - for (int i = 0; i < h; ++i) { - memcpy(to.scanLine(i), frame.constScanLine(i), bpl); + if (size.isEmpty() || size == frame.size()) { + int32 w = frame.width(), h = frame.height(); + if (to.width() == w && to.height() == h && to.format() == frame.format()) { + if (to.byteCount() != frame.byteCount()) { + int bpl = qMin(to.bytesPerLine(), frame.bytesPerLine()); + for (int i = 0; i < h; ++i) { + memcpy(to.scanLine(i), frame.constScanLine(i), bpl); + } + } else { + memcpy(to.bits(), frame.constBits(), frame.byteCount()); } } else { - memcpy(to.bits(), frame.constBits(), frame.byteCount()); + to = frame.copy(); } } else { - to = frame.copy(); + to = frame.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } + hasAlpha = frame.hasAlphaChannel(); return true; } @@ -409,7 +495,7 @@ public: _avpkt.size = 0; } - bool readNextFrame(QImage &to) { + bool readNextFrame(QImage &to, bool &hasAlpha, const QSize &size) { int res; while (true) { if (_avpkt.size > 0) { // previous packet not finished @@ -472,25 +558,31 @@ public: } } - if (to.isNull() || to.width() != _width || to.height() != _height) { - to = QImage(_width, _height, QImage::Format_ARGB32); + QSize toSize(size.isEmpty() ? QSize(_width, _height) : size); + if (to.isNull() || to.size() != toSize) { + to = QImage(toSize, QImage::Format_ARGB32); } - if (_frame->width == _width && _frame->height == _height && (_frame->format == AV_PIX_FMT_BGRA || (_frame->format == -1 && _codecContext->pix_fmt == AV_PIX_FMT_BGRA))) { + if (_frame->width == toSize.width() && _frame->height == toSize.height() && (_frame->format == AV_PIX_FMT_BGRA || (_frame->format == -1 && _codecContext->pix_fmt == AV_PIX_FMT_BGRA))) { int32 sbpl = _frame->linesize[0], dbpl = to.bytesPerLine(), bpl = qMin(sbpl, dbpl); uchar *s = _frame->data[0], *d = to.bits(); for (int32 i = 0, l = _frame->height; i < l; ++i) { memcpy(d + i * dbpl, s + i * sbpl, bpl); } + + hasAlpha = true; } else { - if (_frame->width != _width || _frame->height != _height || (_frame->format != -1 && _frame->format != _codecContext->pix_fmt) || !_swsContext) { - _swsContext = sws_getCachedContext(_swsContext, _frame->width, _frame->height, AVPixelFormat(_frame->format), _width, _height, AV_PIX_FMT_BGRA, 0, 0, 0, 0); + if ((_swsSize != toSize) || (_frame->format != -1 && _frame->format != _codecContext->pix_fmt) || !_swsContext) { + _swsSize = toSize; + _swsContext = sws_getCachedContext(_swsContext, _frame->width, _frame->height, AVPixelFormat(_frame->format), toSize.width(), toSize.height(), AV_PIX_FMT_BGRA, 0, 0, 0, 0); } uint8_t * toData[1] = { to.bits() }; int toLinesize[1] = { to.bytesPerLine() }; - if ((res = sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize)) != _height) { - LOG(("Gif Error: Unable to sws_scale to good size %1, hieght %2, should be %3").arg(logData()).arg(res).arg(_height)); + if ((res = sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize)) != _swsSize.height()) { + LOG(("Gif Error: Unable to sws_scale to good size %1, height %2, should be %3").arg(logData()).arg(res).arg(_swsSize.height())); return false; } + + hasAlpha = false; } int64 duration = av_frame_get_pkt_duration(_frame); @@ -634,6 +726,7 @@ private: int32 _width, _height; SwsContext *_swsContext; + QSize _swsSize; int64 _frameMs; int32 _nextFrameDelay, _currentFrameDelay; @@ -665,6 +758,10 @@ public: , _location(_data.isEmpty() ? new FileLocation(location) : 0) , _accessed(false) , _implementation(0) + , _currentHasAlpha(true) + , _nextHasAlpha(true) + , _width(0) + , _height(0) , _previousMs(0) , _currentMs(0) , _nextUpdateMs(0) @@ -682,9 +779,11 @@ public: return error(); } if (_currentOriginal.isNull()) { - if (!_implementation->readNextFrame(_currentOriginal)) { + if (!_implementation->readNextFrame(_currentOriginal, _currentHasAlpha, QSize())) { return error(); } + _width = _currentOriginal.width(); + _height = _currentOriginal.height(); return ClipProcessReinit; } return ClipProcessWait; @@ -702,7 +801,7 @@ public: _previousMs = _currentMs; _currentMs = ms; - _current = _prepareFrame(_request, _currentOriginal, _currentCache, true); + _current = _prepareFrame(_request, _currentOriginal, _currentCache, _currentHasAlpha); if (!prepareNextFrame()) { return error(); @@ -710,7 +809,7 @@ public: return ClipProcessStarted; } else if (!_paused && ms >= _nextUpdateMs) { swapBuffers(); - return ClipProcessRedraw; + return ClipProcessRepaint; } return ClipProcessWait; } @@ -722,7 +821,7 @@ public: if (ms >= _nextUpdateMs) { // we are late swapBuffers(ms); // keep up - return ClipProcessRedraw; + return ClipProcessRepaint; } return ClipProcessWait; } @@ -738,16 +837,17 @@ public: qSwap(_currentOriginal, _nextOriginal); qSwap(_current, _next); qSwap(_currentCache, _nextCache); + qSwap(_currentHasAlpha, _nextHasAlpha); } bool prepareNextFrame() { - if (!_implementation->readNextFrame(_nextOriginal)) { + if (!_implementation->readNextFrame(_nextOriginal, _nextHasAlpha, QSize(_request.framew, _request.frameh))) { return false; } _nextUpdateMs = _currentMs + nextFrameDelay(); _nextOriginal.setDevicePixelRatio(_request.factor); _next = QPixmap(); - _next = _prepareFrame(_request, _nextOriginal, _nextCache, true); + _next = _prepareFrame(_request, _nextOriginal, _nextCache, _nextHasAlpha); return true; } @@ -809,6 +909,8 @@ private: ClipFrameRequest _request; QPixmap _current, _next; QImage _currentOriginal, _nextOriginal, _currentCache, _nextCache; + bool _currentHasAlpha, _nextHasAlpha; + int32 _width, _height; uint64 _previousMs, _currentMs, _nextUpdateMs; @@ -828,8 +930,8 @@ ClipReadManager::ClipReadManager(QThread *thread) : _processingInThread(0), _nee _timer.moveToThread(thread); connect(&_timer, SIGNAL(timeout()), this, SLOT(process())); - connect(this, SIGNAL(reinit(ClipReader*)), _manager, SLOT(clipReinit(ClipReader*))); - connect(this, SIGNAL(redraw(ClipReader*)), _manager, SLOT(clipRedraw(ClipReader*))); + connect(this, SIGNAL(reinit(ClipReader*,qint32)), _manager, SLOT(clipReinit(ClipReader*,qint32))); + connect(this, SIGNAL(repaint(ClipReader*,qint32)), _manager, SLOT(clipRepaint(ClipReader*,qint32))); } void ClipReadManager::append(ClipReader *reader, const FileLocation &location, const QByteArray &data) { @@ -854,13 +956,18 @@ void ClipReadManager::stop(ClipReader *reader) { emit processDelayed(); } +bool ClipReadManager::carries(ClipReader *reader) const { + QMutexLocker lock(&_readerPointersMutex); + return _readerPointers.contains(reader); +} + bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) { QMutexLocker lock(&_readerPointersMutex); ReaderPointers::iterator it = _readerPointers.find(reader->_interface); if (result == ClipProcessError) { if (it != _readerPointers.cend()) { it.key()->error(); - emit reinit(it.key()); + emit reinit(it.key(), it.key()->threadIndex()); _readerPointers.erase(it); it = _readerPointers.end(); @@ -871,9 +978,9 @@ bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcess } if (result == ClipProcessStarted) { - _loadLevel.fetchAndAddRelease(reader->_currentOriginal.width() * reader->_currentOriginal.height() - AverageGifSize); + _loadLevel.fetchAndAddRelease(reader->_width * reader->_height - AverageGifSize); } - if (!reader->_paused && (result == ClipProcessRedraw || result == ClipProcessWait)) { + if (!reader->_paused && (result == ClipProcessRepaint || result == ClipProcessWait)) { if (it.key()->_lastDisplayMs.get() + WaitBeforeGifPause < qMax(reader->_previousMs, ms)) { reader->_paused = true; it.key()->_paused.set(true); @@ -885,14 +992,14 @@ bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcess } } } - if (result == ClipProcessReinit || result == ClipProcessRedraw || result == ClipProcessStarted) { + if (result == ClipProcessReinit || result == ClipProcessRepaint || result == ClipProcessStarted) { it.key()->_current = reader->_current; it.key()->_currentOriginal = reader->_currentOriginal; it.key()->_currentDisplayed.set(false); if (result == ClipProcessReinit) { - emit reinit(it.key()); - } else if (result == ClipProcessRedraw) { - emit redraw(it.key()); + emit reinit(it.key(), it.key()->threadIndex()); + } else if (result == ClipProcessRepaint) { + emit repaint(it.key(), it.key()->threadIndex()); } } return true; @@ -900,7 +1007,7 @@ bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcess ClipReadManager::ResultHandleState ClipReadManager::handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) { if (!handleProcessResult(reader, result, ms)) { - _loadLevel.fetchAndAddRelease(-1 * (reader->_currentOriginal.isNull() ? AverageGifSize : reader->_currentOriginal.width() * reader->_currentOriginal.height())); + _loadLevel.fetchAndAddRelease(-1 * (reader->_currentOriginal.isNull() ? AverageGifSize : reader->_width * reader->_height)); delete reader; return ResultHandleRemove; } @@ -910,7 +1017,7 @@ ClipReadManager::ResultHandleState ClipReadManager::handleResult(ClipReaderPriva return ResultHandleStop; } - if (result == ClipProcessRedraw) { + if (result == ClipProcessRepaint) { return handleResult(reader, reader->finishProcess(ms), ms); } diff --git a/Telegram/SourceFiles/gui/animation.h b/Telegram/SourceFiles/gui/animation.h index 59f7f246d..487389845 100644 --- a/Telegram/SourceFiles/gui/animation.h +++ b/Telegram/SourceFiles/gui/animation.h @@ -72,6 +72,8 @@ namespace anim { _delta = 0; } + typedef float64 Type; + private: float64 _cur, _from, _delta; @@ -110,6 +112,8 @@ namespace anim { _delta = 0; } + typedef int32 Type; + private: int32 _cur; @@ -179,6 +183,8 @@ namespace anim { _delta_r = _delta_g = _delta_b = _delta_a = 0; } + typedef QColor Type; + private: QColor _cur; @@ -338,87 +344,129 @@ AnimationCallbacks *animation(Param param, Type *obj, typename AnimationCallback return new AnimationCallbacksAbsoluteWithParam(param, obj, method); } +template +class SimpleAnimation { +public: + + typedef Function Callbacks; + + SimpleAnimation() : _data(0) { + } + + bool animating(uint64 ms) { + if (_data && _data->_a.animating()) { + _data->_a.step(ms); + return _data && _data->_a.animating(); + } + return false; + } + + bool isNull() { + return !_data; + } + + typename AnimType::Type current() { + return _data ? _data->a.current() : AnimType::Type(); + } + + typename AnimType::Type current(uint64 ms, const typename AnimType::Type &def) { + return animating(ms) ? current() : def; + } + + void setup(const typename AnimType::Type &from, Callbacks *update) { + if (!_data) { + _data = new Data(from, update, animation(this, &SimpleAnimation::step)); + } else { + delete update; + _data->a = AnimType(from, from); + } + } + + void start(const typename AnimType::Type &to, float64 duration, anim::transition transition = anim::linear) { + if (_data) { + _data->a.start(to); + _data->_a.start(); + _data->duration = duration; + _data->transition = transition; + } + } + + ~SimpleAnimation() { + delete _data; + setBadPointer(_data); + } + +private: + typedef struct _Data { + _Data(const typename AnimType::Type &from, Callbacks *update, AnimationCallbacks *acb) + : a(from, from) + , _a(acb) + , update(update) + , duration(0) + , transition(anim::linear) { + } + ~_Data() { + delete update; + setBadPointer(update); + } + AnimType a; + Animation _a; + Callbacks *update; + float64 duration; + anim::transition transition; + } Data; + Data *_data; + + void step(float64 ms, bool timer) { + float64 dt = (ms >= _data->duration) ? 1 : (ms / _data->duration); + if (dt >= 1) { + _data->a.finish(); + _data->_a.stop(); + } else { + _data->a.update(dt, _data->transition); + } + if (timer) { + _data->update->call(); + } + if (!_data->_a.animating()) { + delete _data; + _data = 0; + } + } + +}; + +typedef SimpleAnimation FloatAnimation; +typedef SimpleAnimation IntAnimation; +typedef SimpleAnimation ColorAnimation; + +#define EnsureAnimation(animation, from, callback) if ((animation).isNull()) { (animation).setup((from), (callback)); } + class ClipReader; class AnimationManager : public QObject { Q_OBJECT public: + AnimationManager(); - AnimationManager() : _timer(this), _iterating(false) { - _timer.setSingleShot(false); - connect(&_timer, SIGNAL(timeout()), this, SLOT(timeout())); - } - - void start(Animation *obj) { - if (_iterating) { - _starting.insert(obj, NullType()); - if (!_stopping.isEmpty()) { - _stopping.remove(obj); - } - } else { - if (_objects.isEmpty()) { - _timer.start(AnimationTimerDelta); - } - _objects.insert(obj, NullType()); - } - } - - void stop(Animation *obj) { - if (_iterating) { - _stopping.insert(obj, NullType()); - if (!_starting.isEmpty()) { - _starting.insert(obj, NullType()); - } - } else { - AnimatingObjects::iterator i = _objects.find(obj); - if (i != _objects.cend()) { - _objects.erase(i); - if (_objects.isEmpty()) { - _timer.stop(); - } - } - } - } + void start(Animation *obj); + void stop(Animation *obj); public slots: + void timeout(); - void timeout() { - _iterating = true; - uint64 ms = getms(); - for (AnimatingObjects::const_iterator i = _objects.begin(), e = _objects.end(); i != e; ++i) { - i.key()->step(ms, true); - } - _iterating = false; - - if (!_starting.isEmpty()) { - for (AnimatingObjects::iterator i = _starting.begin(), e = _starting.end(); i != e; ++i) { - _objects.insert(i.key(), NullType()); - } - _starting.clear(); - } - if (!_stopping.isEmpty()) { - for (AnimatingObjects::iterator i = _stopping.begin(), e = _stopping.end(); i != e; ++i) { - _objects.remove(i.key()); - } - _stopping.clear(); - } - if (!_objects.size()) { - _timer.stop(); - } - } - - void clipReinit(ClipReader *reader); - void clipRedraw(ClipReader *reader); + void clipReinit(ClipReader *reader, qint32 threadIndex); + void clipRepaint(ClipReader *reader, qint32 threadIndex); private: - typedef QMap AnimatingObjects; AnimatingObjects _objects, _starting, _stopping; QTimer _timer; bool _iterating; }; + class FileLocation; enum ClipState { @@ -460,11 +508,19 @@ private: }; +enum ClipReaderNotification { + ClipReaderReinit, + ClipReaderRepaint, +}; + class ClipReaderPrivate; class ClipReader { public: - ClipReader(const FileLocation &location, const QByteArray &data); + typedef Function1 Callback; + + ClipReader(const FileLocation &location, const QByteArray &data, Callback *cb = 0); + static void callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification); // reader can be deleted void setAutoplay() { _autoplay = true; @@ -484,6 +540,9 @@ public: bool paused() const { return _paused.get(); } + int32 threadIndex() const { + return _threadIndex; + } int32 width() const; int32 height() const; @@ -501,6 +560,8 @@ public: private: + Callback *_cb; + ClipState _state; ClipFrameRequest _request; @@ -527,7 +588,7 @@ enum ClipProcessResult { ClipProcessError, ClipProcessStarted, ClipProcessReinit, - ClipProcessRedraw, + ClipProcessRepaint, ClipProcessWait, }; @@ -544,14 +605,15 @@ public: void start(ClipReader *reader); void update(ClipReader *reader); void stop(ClipReader *reader); + bool carries(ClipReader *reader) const; ~ClipReadManager(); signals: void processDelayed(); - void reinit(ClipReader *reader); - void redraw(ClipReader *reader); + void reinit(ClipReader *reader, qint32 threadIndex); + void repaint(ClipReader *reader, qint32 threadIndex); public slots: @@ -565,7 +627,7 @@ private: QAtomicInt _loadLevel; typedef QMap ReaderPointers; ReaderPointers _readerPointers; - QMutex _readerPointersMutex; + mutable QMutex _readerPointersMutex; bool handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms); diff --git a/Telegram/SourceFiles/gui/images.cpp b/Telegram/SourceFiles/gui/images.cpp index d4505d1ab..034aef09f 100644 --- a/Telegram/SourceFiles/gui/images.cpp +++ b/Telegram/SourceFiles/gui/images.cpp @@ -46,6 +46,8 @@ namespace { static const uint64 RoundedCacheSkip = 0x4000000000000000LLU; } +StorageImageLocation StorageImageLocation::Null; + bool Image::isNull() const { return (this == blank()); } @@ -688,6 +690,11 @@ void StorageImage::automaticLoad(const HistoryItem *item) { } } +void StorageImage::automaticLoadSettingsChanged() { + if (loaded() || _loader != CancelledFileLoader) return; + _loader = 0; +} + void StorageImage::load(bool loadFirst, bool prior) { if (loaded()) return; diff --git a/Telegram/SourceFiles/gui/images.h b/Telegram/SourceFiles/gui/images.h index 4f154eb27..a8f2ed1d8 100644 --- a/Telegram/SourceFiles/gui/images.h +++ b/Telegram/SourceFiles/gui/images.h @@ -89,6 +89,8 @@ public: return _secret; } + static StorageImageLocation Null; + private: uint64 _widthheight; uint64 _dclocal; @@ -118,6 +120,8 @@ public: virtual void automaticLoad(const HistoryItem *item) { // auto load photo } + virtual void automaticLoadSettingsChanged() { + } virtual bool loaded() const { return true; @@ -164,6 +168,10 @@ public: virtual void loadEvenCancelled(bool loadFirst = false, bool prior = true) { } + virtual const StorageImageLocation &location() const { + return StorageImageLocation::Null; + } + bool isNull() const; void forget() const; @@ -226,6 +234,7 @@ public: int32 height() const; void automaticLoad(const HistoryItem *item); // auto load photo + void automaticLoadSettingsChanged(); bool loaded() const; bool loading() const { @@ -241,6 +250,10 @@ public: void load(bool loadFirst = false, bool prior = true); void loadEvenCancelled(bool loadFirst = false, bool prior = true); + virtual const StorageImageLocation &location() const { + return _location; + } + ~StorageImage(); protected: diff --git a/Telegram/SourceFiles/gui/twidget.h b/Telegram/SourceFiles/gui/twidget.h index 411762e1b..d01504c2b 100644 --- a/Telegram/SourceFiles/gui/twidget.h +++ b/Telegram/SourceFiles/gui/twidget.h @@ -148,6 +148,9 @@ QRect myrtlrect(const QRect &r) { \ void rtlupdate(const QRect &r) { \ update(myrtlrect(r)); \ } \ +void rtlupdate(int x, int y, int w, int h) { \ + update(myrtlrect(x, y, w, h)); \ +} \ protected: \ void enterEvent(QEvent *e) { \ TWidget *p(tparent()); \ diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 31f29d65d..c01d7f6f8 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -884,7 +884,14 @@ HistoryItem *ChannelHistory::addNewToBlocks(const MTPMessage &msg, NewMessageTyp } else { to = blocks.back(); } - return addNewItem(to, newBlock, createItem(to, msg, (type == NewMessageUnread)), (type == NewMessageUnread)); + HistoryItem *item = createItem((type == NewMessageLast) ? 0 : to, msg, (type == NewMessageUnread)); + if (type == NewMessageLast) { + if (!item->detached()) { + return item; + } + item->attach(to); + } + return addNewItem(to, newBlock, item, (type == NewMessageUnread)); } void ChannelHistory::addNewToOther(HistoryItem *item, NewMessageType type) { @@ -1331,14 +1338,17 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo } result->attach(block); } - if (result) { - if (msg.type() == mtpc_message) { - result->updateMedia(msg.c_message().has_media() ? (&msg.c_message().vmedia) : 0, (block ? false : true)); + if (msg.type() == mtpc_message) { + result->updateMedia(msg.c_message().has_media() ? (&msg.c_message().vmedia) : 0, (block ? false : true)); + if (applyServiceAction) { + App::checkSavedGif(result); } - return result; } + return result; } + bool hasNotForwardedDocument = false; + switch (msg.type()) { case mtpc_messageEmpty: result = new HistoryServiceMsg(this, block, msg.c_messageEmpty().vid.v, date(), lang(lng_message_empty)); @@ -1387,7 +1397,7 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo break; case mtpc_messageMediaDocument: switch (m.vmedia.c_messageMediaDocument().vdocument.type()) { - case mtpc_document: break; + case mtpc_document: hasNotForwardedDocument = true; break; case mtpc_documentEmpty: badMedia = 2; break; default: badMedia = 1; break; } @@ -1406,9 +1416,11 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo } if (badMedia) { result = new HistoryServiceMsg(this, block, m.vid.v, date(m.vdate), lang((badMedia == 2) ? lng_message_empty : lng_media_unsupported), m.vflags.v, 0, m.has_from_id() ? m.vfrom_id.v : 0); + hasNotForwardedDocument = false; } else { if ((m.has_fwd_date() && m.vfwd_date.v > 0) || (m.has_fwd_from_id() && peerFromMTP(m.vfwd_from_id) != 0)) { result = new HistoryForwarded(this, block, m); + hasNotForwardedDocument = false; } else if (m.has_reply_to_msg_id() && m.vreply_to_msg_id.v > 0) { result = new HistoryReply(this, block, m); } else { @@ -1543,6 +1555,10 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPMessage &msg, boo } break; } + if (applyServiceAction) { + App::checkSavedGif(result); + } + return regItem(result); } @@ -3122,21 +3138,22 @@ void HistoryFileMedia::step_thumbOver(const HistoryItem *parent, float64 ms, boo _animation->a_thumbOver.finish(); _animation->_a_thumbOver.stop(); checkAnimationFinished(); - } else { + } else if (!timer) { _animation->a_thumbOver.update(dt, anim::linear); } if (timer) { - Ui::redrawHistoryItem(parent); + Ui::repaintHistoryItem(parent); } } void HistoryFileMedia::step_radial(const HistoryItem *parent, uint64 ms, bool timer) { - _animation->radial.update(dataProgress(), dataFinished(), ms); - if (!_animation->radial.animating()) { - checkAnimationFinished(); - } if (timer) { - Ui::redrawHistoryItem(parent); + Ui::repaintHistoryItem(parent); + } else { + _animation->radial.update(dataProgress(), dataFinished(), ms); + if (!_animation->radial.animating()) { + checkAnimationFinished(); + } } } @@ -3344,8 +3361,7 @@ void HistoryPhoto::draw(Painter &p, const HistoryItem *parent, const QRect &r, b p.setPen(Qt::NoPen); if (selected) { p.setBrush(st::msgDateImgBgSelected); - } else if (_animation && _animation->_a_thumbOver.animating()) { - _animation->_a_thumbOver.step(ms); + } else if (isThumbAnimation(ms)) { float64 over = _animation->a_thumbOver.current(); p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); p.setBrush(st::black); @@ -3636,8 +3652,7 @@ void HistoryVideo::draw(Painter &p, const HistoryItem *parent, const QRect &r, b p.setPen(Qt::NoPen); if (selected) { p.setBrush(st::msgDateImgBgSelected); - } else if (_animation && _animation->_a_thumbOver.animating()) { - _animation->_a_thumbOver.step(ms); + } else if (isThumbAnimation(ms)) { float64 over = _animation->a_thumbOver.current(); p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); p.setBrush(st::black); @@ -3835,8 +3850,7 @@ void HistoryAudio::draw(Painter &p, const HistoryItem *parent, const QRect &r, b p.setPen(Qt::NoPen); if (selected) { p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected); - } else if (_animation && _animation->_a_thumbOver.animating()) { - _animation->_a_thumbOver.step(ms); + } else if (isThumbAnimation(ms)) { float64 over = _animation->a_thumbOver.current(); p.setBrush(style::interpolate(outbg ? st::msgFileOutBg : st::msgFileInBg, outbg ? st::msgFileOutBgOver : st::msgFileInBgOver, over)); } else { @@ -4079,8 +4093,7 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r p.setPen(Qt::NoPen); if (selected) { p.setBrush(st::msgDateImgBgSelected); - } else if (_animation && _animation->_a_thumbOver.animating()) { - _animation->_a_thumbOver.step(ms); + } else if (isThumbAnimation(ms)) { float64 over = _animation->a_thumbOver.current(); p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); p.setBrush(st::black); @@ -4128,7 +4141,7 @@ void HistoryDocument::draw(Painter &p, const HistoryItem *parent, const QRect &r p.setPen(Qt::NoPen); if (selected) { p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected); - } else if (_animation && _animation->_a_thumbOver.animating()) { + } else if (isThumbAnimation(ms)) { float64 over = _animation->a_thumbOver.current(); p.setBrush(style::interpolate(outbg ? st::msgFileOutBg : st::msgFileInBg, outbg ? st::msgFileOutBgOver : st::msgFileInBgOver, over)); } else { @@ -4432,7 +4445,7 @@ void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, boo _data->automaticLoad(parent); bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); - if (loaded && !gif() && _gif != BadClipReader) { + if (loaded && !gif() && _gif != BadClipReader && cAutoPlayGif()) { const_cast(this)->playInline(const_cast(parent)); if (gif()) _gif->setAutoplay(); } @@ -4467,7 +4480,7 @@ void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, boo QRect rthumb(rtlrect(skipx, skipy, width, height, _width)); if (animating) { - p.drawPixmap(rthumb.topLeft(), _gif->current(_thumbw, _thumbh, width, height, Ui::isMediaViewShown() ? 0 : ms)); + p.drawPixmap(rthumb.topLeft(), _gif->current(_thumbw, _thumbh, width, height, (Ui::isLayerShown() || Ui::isMediaViewShown() || Ui::isGifBeingChosen()) ? 0 : ms)); } else { p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_thumbw, _thumbh, width, height)); } @@ -4475,14 +4488,13 @@ void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, boo App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); } - if (radial || (!_gif && !loaded && !_data->loading()) || (_gif == BadClipReader)) { + if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == BadClipReader)) { float64 radialOpacity = (radial && loaded && !_data->uploading()) ? _animation->radial.opacity() : 1; QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); p.setPen(Qt::NoPen); if (selected) { p.setBrush(st::msgDateImgBgSelected); - } else if (_animation && _animation->_a_thumbOver.animating()) { - _animation->_a_thumbOver.step(ms); + } else if (isThumbAnimation(ms)) { float64 over = _animation->a_thumbOver.current(); p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); p.setBrush(st::black); @@ -4544,7 +4556,7 @@ void HistoryGif::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { if (_data->uploading()) { lnk = _cancell; - } else if (!gif()) { + } else if (!gif() || !cAutoPlayGif()) { lnk = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel); } if (parent->getMedia() == this) { @@ -4611,6 +4623,9 @@ bool HistoryGif::playInline(HistoryItem *parent) { if (gif()) { stopInline(parent); } else { + if (!cAutoPlayGif()) { + App::stopGifItems(); + } _gif = new ClipReader(_data->location(), _data->data()); App::regGifItem(_gif, parent); } @@ -5135,7 +5150,8 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) { _maxw += st::msgPadding.left() + st::webPageLeft + st::msgPadding.right(); _minh += st::msgPadding.bottom(); if (_asArticle) { - _minh += st::msgDateFont->height; + _minh = resize(_maxw, parent); // hack +// _minh += st::msgDateFont->height; } } @@ -5411,7 +5427,12 @@ const QString HistoryWebPage::inHistoryText() const { } ImagePtr HistoryWebPage::replyPreview() { - return _data->photo ? _data->photo->makeReplyPreview() : (_data->doc ? _data->doc->makeReplyPreview() : ImagePtr()); + return _attach ? _attach->replyPreview() : (_data->photo ? _data->photo->makeReplyPreview() : ImagePtr()); +} + +HistoryWebPage::~HistoryWebPage() { + delete _attach; + setBadPointer(_attach); } namespace { @@ -5999,12 +6020,6 @@ int32 HistoryMessage::plainMaxWidth() const { void HistoryMessage::initDimensions() { if (drawBubble()) { - _maxw = plainMaxWidth(); - if (_text.isEmpty()) { - _minh = 0; - } else { - _minh = st::msgPadding.top() + _text.minHeight() + st::msgPadding.bottom(); - } if (_media) { _media->initDimensions(this); if (_media->isDisplayed()) { @@ -6013,15 +6028,24 @@ void HistoryMessage::initDimensions() { _textWidth = 0; _textHeight = 0; } - int32 maxw = _media->maxWidth(); - if (maxw > _maxw) _maxw = maxw; - _minh += _media->minHeight(); } else if (!_text.hasSkipBlock()) { _text.setSkipBlock(skipBlockWidth(), skipBlockHeight()); _textWidth = 0; _textHeight = 0; } } + + _maxw = plainMaxWidth(); + if (_text.isEmpty()) { + _minh = 0; + } else { + _minh = st::msgPadding.top() + _text.minHeight() + st::msgPadding.bottom(); + } + if (_media && _media->isDisplayed()) { + int32 maxw = _media->maxWidth(); + if (maxw > _maxw) _maxw = maxw; + _minh += _media->minHeight(); + } } else { _media->initDimensions(this); _maxw = _media->maxWidth(); @@ -6211,7 +6235,7 @@ void HistoryMessage::setViewsCount(int32 count) { _viewsText = (_views >= 0) ? formatViewsCount(_views) : QString(); _viewsWidth = _viewsText.isEmpty() ? 0 : st::msgDateFont->width(_viewsText); if (was == _viewsWidth) { - Ui::redrawHistoryItem(this); + Ui::repaintHistoryItem(this); } else { if (_text.hasSkipBlock()) { _text.setSkipBlock(HistoryMessage::skipBlockWidth(), HistoryMessage::skipBlockHeight()); @@ -6227,7 +6251,7 @@ void HistoryMessage::setId(MsgId newId) { bool wasPositive = (id > 0), positive = (newId > 0); HistoryItem::setId(newId); if (wasPositive == positive) { - Ui::redrawHistoryItem(this); + Ui::repaintHistoryItem(this); } else { if (_text.hasSkipBlock()) { _text.setSkipBlock(HistoryMessage::skipBlockWidth(), HistoryMessage::skipBlockHeight()); diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 1db540e4f..6342883df 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -1235,6 +1235,12 @@ protected: _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; @@ -1812,6 +1818,12 @@ public: return false; } + HistoryMedia *attach() const { + return _attach; + } + + ~HistoryWebPage(); + private: WebPageData *_data; TextLinkPtr _openl; @@ -2175,6 +2187,13 @@ public: return _text.adjustSelection(from, to, type); } + void linkOver(const TextLinkPtr &lnk) { + if (_media) _media->linkOver(this, lnk); + } + void linkOut(const TextLinkPtr &lnk) { + if (_media) _media->linkOut(this, lnk); + } + void drawInDialog(Painter &p, const QRect &r, bool act, const HistoryItem *&cacheFor, Text &cache) const; QString notificationText() const; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 81e2436a8..3e0b70b5c 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -38,7 +38,7 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html -HistoryInner::HistoryInner(HistoryWidget *historyWidget, ScrollArea *scroll, History *history) : QWidget(0) +HistoryInner::HistoryInner(HistoryWidget *historyWidget, ScrollArea *scroll, History *history) : TWidget(0) , _peer(history->peer) , _migrated(history->peer->migrateFrom() ? App::history(history->peer->migrateFrom()->id) : 0) , _history(history) @@ -116,7 +116,7 @@ void HistoryInner::messagesReceivedDown(PeerData *peer, const QVectordetached() || !_history) return; int32 msgy = itemTop(item); if (msgy >= 0) { @@ -475,16 +475,16 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt if (button != Qt::LeftButton) return; if (App::pressedItem() != App::hoveredItem()) { - redrawItem(App::pressedItem()); + repaintItem(App::pressedItem()); App::pressedItem(App::hoveredItem()); - redrawItem(App::pressedItem()); + repaintItem(App::pressedItem()); } if (textlnkDown() != textlnkOver()) { - redrawItem(App::pressedLinkItem()); + repaintItem(App::pressedLinkItem()); textlnkDown(textlnkOver()); App::pressedLinkItem(App::hoveredLinkItem()); - redrawItem(App::pressedLinkItem()); - redrawItem(App::pressedItem()); + repaintItem(App::pressedLinkItem()); + repaintItem(App::pressedItem()); } _dragAction = NoDrag; @@ -512,7 +512,7 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt uint32 selStatus = (symbol << 16) | symbol; if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { if (!_selected.isEmpty()) { - redrawItem(_selected.cbegin().key()); + repaintItem(_selected.cbegin().key()); _selected.clear(); } _selected.insert(_dragItem, selStatus); @@ -553,12 +553,12 @@ void HistoryInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton butt uint32 selStatus = (_dragSymbol << 16) | _dragSymbol; if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { if (!_selected.isEmpty()) { - redrawItem(_selected.cbegin().key()); + repaintItem(_selected.cbegin().key()); _selected.clear(); } _selected.insert(_dragItem, selStatus); _dragAction = Selecting; - redrawItem(_dragItem); + repaintItem(_dragItem); } else { _dragAction = PrepareSelect; } @@ -717,7 +717,7 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but } } if (textlnkDown()) { - redrawItem(App::pressedLinkItem()); + repaintItem(App::pressedLinkItem()); textlnkDown(TextLinkPtr()); App::pressedLinkItem(0); if (!textlnkOver() && _cursor != style::cur_default) { @@ -726,7 +726,7 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but } } if (App::pressedItem()) { - redrawItem(App::pressedItem()); + repaintItem(App::pressedItem()); App::pressedItem(0); } @@ -750,16 +750,16 @@ void HistoryInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton but } else { _selected.erase(i); } - redrawItem(_dragItem); + repaintItem(_dragItem); } else if (_dragAction == PrepareDrag && !_dragWasInactive && button != Qt::RightButton) { SelectedItems::iterator i = _selected.find(_dragItem); if (i != _selected.cend() && i.value() == FullSelection) { _selected.erase(i); - redrawItem(_dragItem); + repaintItem(_dragItem); } else if (i == _selected.cend() && !_dragItem->serviceMsg() && _dragItem->id > 0 && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection) { if (_selected.size() < MaxSelectedItems) { _selected.insert(_dragItem, FullSelection); - redrawItem(_dragItem); + repaintItem(_dragItem); } } else { _selected.clear(); @@ -804,7 +804,7 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) { _dragAction = Selecting; uint32 selStatus = (symbol << 16) | symbol; if (!_selected.isEmpty()) { - redrawItem(_selected.cbegin().key()); + repaintItem(_selected.cbegin().key()); _selected.clear(); } _selected.insert(_dragItem, selStatus); @@ -928,15 +928,35 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } if (item && !isUponSelected && !_contextMenuLnk) { if (HistoryMedia *media = (msg ? msg->getMedia() : 0)) { + if (media->type() == MediaTypeWebPage && static_cast(media)->attach()) { + media = static_cast(media)->attach(); + } if (media->type() == MediaTypeSticker) { DocumentData *doc = media->getDocument(); if (doc && doc->sticker() && doc->sticker()->set.type() != mtpc_inputStickerSetEmpty) { _menu->addAction(lang(doc->sticker()->setInstalled() ? lng_context_pack_info : lng_context_pack_add), _widget, SLOT(onStickerPackInfo())); } + + _menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextFile()))->setEnabled(true); + } else if (media->type() == MediaTypeGif) { + DocumentData *doc = media->getDocument(); + if (doc) { + if (doc->loading()) { + _menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true); + } else { + if (doc->mime.toLower() == qstr("video/mp4") && doc->type == AnimatedDocument) { + _menu->addAction(lang(lng_context_save_gif), this, SLOT(saveContextGif()))->setEnabled(true); + } + if (!doc->already(true).isEmpty()) { + _menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true); + } + _menu->addAction(lang(lng_context_save_file), this, SLOT(saveContextFile()))->setEnabled(true); + } + } } } QString contextMenuText = item->selectedText(FullSelection); - if (!contextMenuText.isEmpty() && (!msg || !msg->getMedia() || msg->getMedia()->type() != MediaTypeSticker)) { + if (!contextMenuText.isEmpty() && (!msg || !msg->getMedia() || _dragCursorState == HistoryInTextCursorState)) { _menu->addAction(lang(lng_context_copy_text), this, SLOT(copyContextText()))->setEnabled(true); } } @@ -1047,23 +1067,36 @@ void HistoryInner::copyContextImage() { } void HistoryInner::cancelContextDownload() { - VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); - AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); - DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); - if (lnkVideo) { + if (VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data())) { lnkVideo->video()->cancel(); - } else if (lnkAudio) { + } else if (AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data())) { lnkAudio->audio()->cancel(); - } else if (lnkDocument) { + } else if (DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data())) { lnkDocument->document()->cancel(); + } else if (HistoryItem *item = App::contextItem()) { + if (HistoryMedia *media = item->getMedia()) { + if (DocumentData *doc = media->getDocument()) { + doc->cancel(); + } + } } } void HistoryInner::showContextInFolder() { - VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); - AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); - DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); - QString already = lnkVideo ? lnkVideo->video()->already(true) : (lnkAudio ? lnkAudio->audio()->already(true) : (lnkDocument ? lnkDocument->document()->already(true) : QString())); + QString already; + if (VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data())) { + already = lnkVideo->video()->already(true); + } else if (AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data())) { + already = lnkAudio->audio()->already(true); + } else if (DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data())) { + already = lnkDocument->document()->already(true); + } else if (HistoryItem *item = App::contextItem()) { + if (HistoryMedia *media = item->getMedia()) { + if (DocumentData *doc = media->getDocument()) { + already = doc->already(true); + } + } + } if (!already.isEmpty()) psShowInFolder(already); } @@ -1080,12 +1113,29 @@ void HistoryInner::openContextFile() { } void HistoryInner::saveContextFile() { - VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data()); - AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data()); - DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data()); - if (lnkVideo) VideoSaveLink::doSave(lnkVideo->video(), true); - if (lnkAudio) AudioSaveLink::doSave(lnkAudio->audio(), true); - if (lnkDocument) DocumentSaveLink::doSave(lnkDocument->document(), true); + if (VideoLink *lnkVideo = dynamic_cast(_contextMenuLnk.data())) { + VideoSaveLink::doSave(lnkVideo->video(), true); + } else if (AudioLink *lnkAudio = dynamic_cast(_contextMenuLnk.data())) { + AudioSaveLink::doSave(lnkAudio->audio(), true); + } else if (DocumentLink *lnkDocument = dynamic_cast(_contextMenuLnk.data())) { + DocumentSaveLink::doSave(lnkDocument->document(), true); + } else if (HistoryItem *item = App::contextItem()) { + if (HistoryMedia *media = item->getMedia()) { + if (DocumentData *doc = media->getDocument()) { + DocumentSaveLink::doSave(doc, true); + } + } + } +} + +void HistoryInner::saveContextGif() { + if (HistoryItem *item = App::contextItem()) { + if (HistoryMedia *media = item->getMedia()) { + if (DocumentData *doc = media->getDocument()) { + _widget->saveGif(doc); + } + } + } } void HistoryInner::copyContextText() { @@ -1345,13 +1395,13 @@ void HistoryInner::enterEvent(QEvent *e) { void HistoryInner::leaveEvent(QEvent *e) { if (HistoryItem *item = App::hoveredItem()) { - redrawItem(item); + repaintItem(item); App::hoveredItem(0); } if (textlnkOver()) { if (HistoryItem *item = App::hoveredLinkItem()) { item->linkOut(textlnkOver()); - redrawItem(item); + repaintItem(item); App::hoveredLinkItem(0); } textlnkOver(TextLinkPtr()); @@ -1524,12 +1574,12 @@ void HistoryInner::onUpdateSelected() { m = mapMouseToItem(point, item); if (item->hasPoint(m.x(), m.y())) { if (App::hoveredItem() != item) { - redrawItem(App::hoveredItem()); + repaintItem(App::hoveredItem()); App::hoveredItem(item); - redrawItem(App::hoveredItem()); + repaintItem(App::hoveredItem()); } } else if (App::hoveredItem()) { - redrawItem(App::hoveredItem()); + repaintItem(App::hoveredItem()); App::hoveredItem(0); } } @@ -1557,7 +1607,7 @@ void HistoryInner::onUpdateSelected() { if (textlnkOver()) { if (HistoryItem *item = App::hoveredLinkItem()) { item->linkOut(textlnkOver()); - redrawItem(item); + repaintItem(item); } else { update(_botDescRect); } @@ -1568,7 +1618,7 @@ void HistoryInner::onUpdateSelected() { if (textlnkOver()) { if (HistoryItem *item = App::hoveredLinkItem()) { item->linkOver(textlnkOver()); - redrawItem(item); + repaintItem(item); } else { update(_botDescRect); } @@ -1610,7 +1660,7 @@ void HistoryInner::onUpdateSelected() { uint32 selState = _dragItem->adjustSelection(qMin(second, _dragSymbol), qMax(second, _dragSymbol), _dragSelType); if (_selected[_dragItem] != selState) { _selected[_dragItem] = selState; - Ui::redrawHistoryItem(_dragItem); + repaintItem(_dragItem); } if (!_wasSelectedText && (selState == FullSelection || (selState & 0xFFFF) != ((selState >> 16) & 0xFFFF))) { _wasSelectedText = true; @@ -2561,6 +2611,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) , _replyForwardPressed(false) , _replyReturn(0) , _stickersUpdateRequest(0) +, _savedGifsUpdateRequest(0) , _peer(0) , _clearPeer(0) , _channel(NoChannel) @@ -2744,7 +2795,11 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) void HistoryWidget::start() { connect(App::main(), SIGNAL(stickersUpdated()), &_emojiPan, SLOT(refreshStickers())); + connect(App::main(), SIGNAL(savedGifsUpdated()), &_emojiPan, SLOT(refreshSavedGifs())); + updateRecentStickers(); + if (App::main()) emit App::main()->savedGifsUpdated(); + connect(App::api(), SIGNAL(fullPeerUpdated(PeerData*)), this, SLOT(onFullPeerUpdated(PeerData*))); } @@ -2948,11 +3003,16 @@ void HistoryWidget::onRecordUpdate(qint16 level, qint32 samples) { } void HistoryWidget::updateStickers() { - if (cLastStickersUpdate() && getms(true) < cLastStickersUpdate() + StickersUpdateTimeout) return; - if (_stickersUpdateRequest) return; - - cSetStickersHash(stickersCountHash(true)); - _stickersUpdateRequest = MTP::send(MTPmessages_GetAllStickers(MTP_int(cStickersHash())), rpcDone(&HistoryWidget::stickersGot), rpcFail(&HistoryWidget::stickersFailed)); + if (!cLastStickersUpdate() || getms(true) >= cLastStickersUpdate() + StickersUpdateTimeout) { + if (!_stickersUpdateRequest) { + _stickersUpdateRequest = MTP::send(MTPmessages_GetAllStickers(MTP_int(Local::countStickersHash(true))), rpcDone(&HistoryWidget::stickersGot), rpcFail(&HistoryWidget::stickersFailed)); + } + } + if (!cLastSavedGifsUpdate() || getms(true) >= cLastSavedGifsUpdate() + StickersUpdateTimeout) { + if (!_savedGifsUpdateRequest) { + _savedGifsUpdateRequest = MTP::send(MTPmessages_GetSavedGifs(MTP_int(Local::countSavedGifsHash())), rpcDone(&HistoryWidget::savedGifsGot), rpcFail(&HistoryWidget::savedGifsFailed)); + } + } } void HistoryWidget::notify_botCommandsChanged(UserData *user) { @@ -2993,7 +3053,7 @@ void HistoryWidget::notify_migrateUpdated(PeerData *peer) { } } -void HistoryWidget::notify_mediaViewHidden() { +void HistoryWidget::notify_clipStopperHidden(ClipStopperType type) { if (_list) _list->update(); } @@ -3062,10 +3122,8 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { } } - int32 countedHash = stickersCountHash(); - cSetStickersHash(countedHash); - if (countedHash != d.vhash.v) { - LOG(("API Error: received stickers hash %1 while counted hash is %2").arg(d.vhash.v).arg(countedHash)); + if (Local::countStickersHash() != d.vhash.v) { + LOG(("API Error: received stickers hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countStickersHash())); } if (!setsToRequest.isEmpty() && App::api()) { @@ -3091,6 +3149,61 @@ bool HistoryWidget::stickersFailed(const RPCError &error) { return true; } +void HistoryWidget::savedGifsGot(const MTPmessages_SavedGifs &gifs) { + cSetLastSavedGifsUpdate(getms(true)); + _savedGifsUpdateRequest = 0; + + if (gifs.type() != mtpc_messages_savedGifs) return; + const MTPDmessages_savedGifs &d(gifs.c_messages_savedGifs()); + + const QVector &d_gifs(d.vgifs.c_vector().v); + + SavedGifs &saved(cRefSavedGifs()); + saved.clear(); + + saved.reserve(d_gifs.size()); + for (int32 i = 0, l = d_gifs.size(); i != l; ++i) { + DocumentData *doc = App::feedDocument(d_gifs.at(i)); + if (!doc || !doc->isAnimation()) { + LOG(("API Error: bad document returned in HistoryWidget::savedGifsGot!")); + continue; + } + + saved.push_back(doc); + } + if (Local::countSavedGifsHash() != d.vhash.v) { + LOG(("API Error: received saved gifs hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countSavedGifsHash())); + } + + Local::writeSavedGifs(); + + if (App::main()) emit App::main()->savedGifsUpdated(); +} + +void HistoryWidget::saveGif(DocumentData *doc) { + if (doc->mime.toLower() == qstr("video/mp4") && doc->type == AnimatedDocument) { + if (cSavedGifs().indexOf(doc) != 0) { + MTP::send(MTPmessages_SaveGif(MTP_inputDocument(MTP_long(doc->id), MTP_long(doc->access)), MTP_bool(false)), rpcDone(&HistoryWidget::saveGifDone, doc)); + } + } +} + +void HistoryWidget::saveGifDone(DocumentData *doc, const MTPBool &result) { + if (mtpIsTrue(result)) { + App::addSavedGif(doc); + } +} + +bool HistoryWidget::savedGifsFailed(const RPCError &error) { + if (mtpIsFlood(error)) return false; + + LOG(("App Fail: Failed to get saved gifs!")); + + cSetLastSavedGifsUpdate(getms(true)); + _savedGifsUpdateRequest = 0; + return true; +} + void HistoryWidget::clearReplyReturns() { _replyReturns.clear(); _replyReturn = 0; @@ -3232,7 +3345,9 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re } } -// App::stopGifItems(); + if (!cAutoPlayGif()) { + App::stopGifItems(); + } clearReplyReturns(); clearAllLoadRequests(); @@ -5448,7 +5563,7 @@ void HistoryWidget::onPhotoProgress(const FullMsgId &newId) { if (!item->fromChannel()) { updateSendAction(item->history(), SendActionUploadPhoto, 0); } - Ui::redrawHistoryItem(item); + Ui::repaintHistoryItem(item); } } @@ -5460,7 +5575,7 @@ void HistoryWidget::onDocumentProgress(const FullMsgId &newId) { if (!item->fromChannel()) { updateSendAction(item->history(), SendActionUploadFile, doc ? doc->uploadOffset : 0); } - Ui::redrawHistoryItem(item); + Ui::repaintHistoryItem(item); } } @@ -5471,7 +5586,7 @@ void HistoryWidget::onAudioProgress(const FullMsgId &newId) { if (!item->fromChannel()) { updateSendAction(item->history(), SendActionUploadAudio, audio ? audio->uploadOffset : 0); } - Ui::redrawHistoryItem(item); + Ui::repaintHistoryItem(item); } } @@ -5482,7 +5597,7 @@ void HistoryWidget::onPhotoFailed(const FullMsgId &newId) { if (!item->fromChannel()) { updateSendAction(item->history(), SendActionUploadPhoto, -1); } -// Ui::redrawHistoryItem(item); +// Ui::repaintHistoryItem(item); } } @@ -5493,7 +5608,7 @@ void HistoryWidget::onDocumentFailed(const FullMsgId &newId) { if (!item->fromChannel()) { updateSendAction(item->history(), SendActionUploadFile, -1); } - Ui::redrawHistoryItem(item); + Ui::repaintHistoryItem(item); } } @@ -5504,7 +5619,7 @@ void HistoryWidget::onAudioFailed(const FullMsgId &newId) { if (!item->fromChannel()) { updateSendAction(item->history(), SendActionUploadAudio, -1); } - Ui::redrawHistoryItem(item); + Ui::repaintHistoryItem(item); } } @@ -5602,12 +5717,24 @@ bool HistoryWidget::isItemVisible(HistoryItem *item) { return true; } -void HistoryWidget::ui_redrawHistoryItem(const HistoryItem *item) { +void HistoryWidget::ui_repaintHistoryItem(const HistoryItem *item) { if (_peer && _list && (item->history() == _history || (_migrated && item->history() == _migrated))) { - _list->redrawItem(item); + _list->repaintItem(item); } } +void HistoryWidget::ui_repaintSavedGif(const LayoutSavedGif *layout) { + _emojiPan.ui_repaintSavedGif(layout); +} + +bool HistoryWidget::ui_isSavedGifVisible(const LayoutSavedGif *layout) { + return _emojiPan.ui_isSavedGifVisible(layout) || _attachMention.ui_isSavedGifVisible(layout); +} + +bool HistoryWidget::ui_isGifBeingChosen() { + return _emojiPan.ui_isGifBeingChosen() || _attachMention.ui_isGifBeingChosen(); +} + void HistoryWidget::notify_historyItemLayoutChanged(const HistoryItem *item) { if (_peer && _list && (item == App::mousedItem() || item == App::hoveredItem() || item == App::hoveredLinkItem())) { _list->onUpdateSelected(); @@ -6527,7 +6654,7 @@ void HistoryWidget::onAnimActiveStep() { if (getms() - _animActiveStart > st::activeFadeInDuration + st::activeFadeOutDuration) { stopAnimActive(); } else { - Ui::redrawHistoryItem(item); + Ui::repaintHistoryItem(item); } } diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index 3a09b2528..f8a1d472b 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -33,7 +33,7 @@ enum DragState { }; class HistoryWidget; -class HistoryInner : public QWidget { +class HistoryInner : public TWidget { Q_OBJECT public: @@ -69,7 +69,7 @@ public: int32 recountHeight(const HistoryItem *resizedItem); void updateSize(); - void redrawItem(const HistoryItem *item); + void repaintItem(const HistoryItem *item); bool canCopySelected() const; bool canDeleteSelected() const; @@ -114,6 +114,7 @@ public slots: void showContextInFolder(); void openContextFile(); void saveContextFile(); + void saveContextGif(); void copyContextText(); void copySelectedText(); @@ -541,6 +542,8 @@ public: void updateNotifySettings(); + void saveGif(DocumentData *doc); + bool contentOverlapped(const QRect &globalRect); void grabStart() { @@ -556,13 +559,16 @@ public: bool isItemVisible(HistoryItem *item); - void ui_redrawHistoryItem(const HistoryItem *item); + void ui_repaintHistoryItem(const HistoryItem *item); + void ui_repaintSavedGif(const LayoutSavedGif *gif); + bool ui_isSavedGifVisible(const LayoutSavedGif *layout); + bool ui_isGifBeingChosen(); void notify_historyItemLayoutChanged(const HistoryItem *item); void notify_botCommandsChanged(UserData *user); void notify_userIsBotChanged(UserData *user); void notify_migrateUpdated(PeerData *peer); - void notify_mediaViewHidden(); + void notify_clipStopperHidden(ClipStopperType type); void notify_historyItemResized(const HistoryItem *item, bool scrollToIt); ~HistoryWidget(); @@ -701,6 +707,8 @@ private: void addMessagesToFront(PeerData *peer, const QVector &messages, const QVector *collapsed); void addMessagesToBack(PeerData *peer, const QVector &messages, const QVector *collapsed); + void saveGifDone(DocumentData *doc, const MTPBool &result); + void reportSpamDone(PeerData *peer, const MTPBool &result, mtpRequestId request); bool reportSpamFail(const RPCError &error, mtpRequestId request); @@ -713,10 +721,13 @@ private: void countHistoryShowFrom(); + mtpRequestId _stickersUpdateRequest; void stickersGot(const MTPmessages_AllStickers &stickers); bool stickersFailed(const RPCError &error); - mtpRequestId _stickersUpdateRequest; + mtpRequestId _savedGifsUpdateRequest; + void savedGifsGot(const MTPmessages_SavedGifs &gifs); + bool savedGifsFailed(const RPCError &error); void writeDraft(MsgId *replyTo = 0, const QString *text = 0, const MessageCursor *cursor = 0, bool *previewCancelled = 0); void setFieldText(const QString &text); diff --git a/Telegram/SourceFiles/layerwidget.cpp b/Telegram/SourceFiles/layerwidget.cpp index e12b70e02..63b796942 100644 --- a/Telegram/SourceFiles/layerwidget.cpp +++ b/Telegram/SourceFiles/layerwidget.cpp @@ -195,6 +195,7 @@ StickerPreviewWidget::StickerPreviewWidget(QWidget *parent) : TWidget(parent) , a_shown(0, 0) , _a_shown(animation(this, &StickerPreviewWidget::step_shown)) , _doc(0) +, _gif(0) , _cacheStatus(CacheNotLoaded) { setAttribute(Qt::WA_TransparentForMouseEvents); } @@ -232,7 +233,7 @@ void StickerPreviewWidget::step_shown(float64 ms, bool timer) { } void StickerPreviewWidget::showPreview(DocumentData *sticker) { - if (sticker && !sticker->sticker()) sticker = 0; + if (sticker && !sticker->isAnimation() && !sticker->sticker()) sticker = 0; if (sticker) { _cache = QPixmap(); if (isHidden() || _a_shown.animating()) { @@ -245,10 +246,17 @@ void StickerPreviewWidget::showPreview(DocumentData *sticker) { } else if (isHidden()) { return; } else { + if (_gif) _cache = currentImage(); a_shown.start(0); _a_shown.start(); } _doc = sticker; + if (_gif) { + if (gif()) { + delete _gif; + } + _gif = 0; + } _cacheStatus = CacheNotLoaded; } @@ -259,7 +267,10 @@ void StickerPreviewWidget::hidePreview() { QSize StickerPreviewWidget::currentDimensions() const { if (!_doc) return QSize(_cache.width() / cIntRetinaFactor(), _cache.height() / cIntRetinaFactor()); - QSize result(qMax(_doc->dimensions.width(), 1), qMax(_doc->dimensions.height(), 1)); + QSize result(qMax(convertScale(_doc->dimensions.width()), 1), qMax(convertScale(_doc->dimensions.height()), 1)); + if (gif() && _gif->ready()) { + result = QSize(qMax(convertScale(_gif->width()), 1), qMax(convertScale(_gif->height()), 1)); + } if (result.width() > st::maxStickerSize) { result.setHeight(qMax(qRound((st::maxStickerSize * result.height()) / result.width()), 1)); result.setWidth(st::maxStickerSize); @@ -272,22 +283,64 @@ QSize StickerPreviewWidget::currentDimensions() const { } QPixmap StickerPreviewWidget::currentImage() const { - if (_doc && _cacheStatus != CacheLoaded) { - _doc->checkSticker(); - if (_doc->sticker()->img->isNull()) { - if (_cacheStatus != CacheThumbLoaded) { + if (_doc) { + if (_doc->sticker()) { + if (_cacheStatus != CacheLoaded) { + _doc->checkSticker(); + if (_doc->sticker()->img->isNull()) { + if (_cacheStatus != CacheThumbLoaded && _doc->thumb->loaded()) { + QSize s = currentDimensions(); + _cache = _doc->thumb->pixBlurred(s.width(), s.height()); + _cacheStatus = CacheThumbLoaded; + } + } else { + QSize s = currentDimensions(); + _cache = _doc->sticker()->img->pix(s.width(), s.height()); + _cacheStatus = CacheLoaded; + } + } + } else { + _doc->automaticLoad(0); + if (_doc->loaded()) { + if (!_gif && _gif != BadClipReader) { + StickerPreviewWidget *that = const_cast(this); + that->_gif = new ClipReader(_doc->location(), _doc->data(), func(that, &StickerPreviewWidget::clipCallback)); + if (gif()) _gif->setAutoplay(); + } + } + if (gif() && _gif->started()) { + QSize s = currentDimensions(); + return _gif->current(s.width(), s.height(), s.width(), s.height(), getms()); + } + if (_cacheStatus != CacheThumbLoaded && _doc->thumb->loaded()) { QSize s = currentDimensions(); _cache = _doc->thumb->pixBlurred(s.width(), s.height()); _cacheStatus = CacheThumbLoaded; } - } else { - QSize s = currentDimensions(); - _cache = _doc->sticker()->img->pix(s.width(), s.height()); - _cacheStatus = CacheLoaded; } } return _cache; } +void StickerPreviewWidget::clipCallback(ClipReaderNotification notification) { + switch (notification) { + case ClipReaderReinit: { + if (gif() && _gif->state() == ClipError) { + delete _gif; + _gif = BadClipReader; + } + + if (gif() && _gif->ready() && !_gif->started()) { + QSize s = currentDimensions(); + _gif->start(s.width(), s.height(), s.width(), s.height(), false); + } + + update(); + } break; + + case ClipReaderRepaint: update(); break; + } +} + StickerPreviewWidget::~StickerPreviewWidget() { } diff --git a/Telegram/SourceFiles/layerwidget.h b/Telegram/SourceFiles/layerwidget.h index 133a4072e..151597ede 100644 --- a/Telegram/SourceFiles/layerwidget.h +++ b/Telegram/SourceFiles/layerwidget.h @@ -131,6 +131,12 @@ private: anim::fvalue a_shown; Animation _a_shown; DocumentData *_doc; + ClipReader *_gif; + bool gif() const { + return (!_gif || _gif == BadClipReader) ? false : true; + } + + void clipCallback(ClipReaderNotification notification); enum CacheStatus { CacheNotLoaded, diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp index 5e5b64173..310242993 100644 --- a/Telegram/SourceFiles/layout.cpp +++ b/Telegram/SourceFiles/layout.cpp @@ -227,21 +227,22 @@ void LayoutRadialProgressItem::step_iconOver(float64 ms, bool timer) { if (dt >= 1) { a_iconOver.finish(); _a_iconOver.stop(); - } else { + } else if (!timer) { a_iconOver.update(dt, anim::linear); } if (timer && iconAnimated()) { - Ui::redrawHistoryItem(_parent); + Ui::repaintHistoryItem(_parent); } } void LayoutRadialProgressItem::step_radial(uint64 ms, bool timer) { - _radial->update(dataProgress(), dataFinished(), ms); - if (!_radial->animating()) { - checkRadialFinished(); - } if (timer) { - Ui::redrawHistoryItem(_parent); + Ui::repaintHistoryItem(_parent); + } else { + _radial->update(dataProgress(), dataFinished(), ms); + if (!_radial->animating()) { + checkRadialFinished(); + } } } @@ -590,6 +591,7 @@ void LayoutOverviewAudio::paint(Painter &p, const QRect &clip, uint32 selection, if (selected) { p.setBrush(st::msgFileInBgSelected); } else if (_a_iconOver.animating()) { + _a_iconOver.step(context->ms); float64 over = a_iconOver.current(); p.setBrush(style::interpolate(st::msgFileInBg, st::msgFileInBgOver, over)); } else { @@ -787,6 +789,7 @@ void LayoutOverviewDocument::paint(Painter &p, const QRect &clip, uint32 selecti if (selected) { p.setBrush(st::msgFileInBgSelected); } else if (_a_iconOver.animating()) { + _a_iconOver.step(context->ms); float64 over = a_iconOver.current(); p.setBrush(style::interpolate(st::msgFileInBg, st::msgFileInBgOver, over)); } else { @@ -1280,3 +1283,230 @@ LayoutOverviewLink::Link::Link(const QString &url, const QString &text) , width(st::normalFont->width(text)) , lnk(linkFromUrl(url)) { } + +LayoutSavedGif::LayoutSavedGif(DocumentData *data) +: _data(data) +, _position(0) +, _width(st::savedGifMinWidth) +, _state(0) +, _gif(0) +, _animation(0) { +} + +void LayoutSavedGif::setPosition(int32 position, int32 width) { + _position = position; + _width = width; + if (_position < 0) { + if (gif()) delete _gif; + _gif = 0; + } +} + +void LayoutSavedGif::setWidth(int32 width) { + _width = width; +} + +int32 LayoutSavedGif::position() const { + return _position; +} + +int32 LayoutSavedGif::width() const { + return _width; +} + +void LayoutSavedGif::notify_over(bool over) { + if (!_data->loaded()) { + ensureAnimation(); + if (over == !(_state & StateOver)) { + EnsureAnimation(_animation->_a_over, (_state & StateOver) ? 1 : 0, (func(this, &LayoutSavedGif::update))); + _animation->_a_over.start(over ? 1 : 0, st::stickersRowDuration); + } + } + if (over) { + _state |= StateOver; + } else { + _state &= ~StateOver; + } +} + +void LayoutSavedGif::notify_deleteOver(bool over) { + if (over == !(_state & StateDeleteOver)) { + EnsureAnimation(_a_deleteOver, (_state & StateDeleteOver) ? 1 : 0, func(this, &LayoutSavedGif::update)); + if (over) { + _state |= StateDeleteOver; + } else { + _state &= ~StateDeleteOver; + } + _a_deleteOver.start((_state & StateDeleteOver) ? 1 : 0, st::stickersRowDuration); + } +} + +void LayoutSavedGif::paint(Painter &p, bool paused, uint64 ms) const { + _data->automaticLoad(0); + + bool loaded = _data->loaded(), displayLoading = _data->displayLoading(); + if (loaded && !gif() && _gif != BadClipReader) { + LayoutSavedGif *that = const_cast(this); + that->_gif = new ClipReader(_data->location(), _data->data(), func(that, &LayoutSavedGif::clipCallback)); + if (gif()) _gif->setAutoplay(); + } + + bool animating = (gif() && _gif->started()); + if (displayLoading) { + ensureAnimation(); + if (!_animation->radial.animating()) { + _animation->radial.start(_data->progress()); + } + } + bool radial = isRadialAnimation(ms); + + int32 height = st::savedGifHeight; + QSize frame = countFrameSize(); + + QRect r(0, 0, _width, height); + if (animating) { + if (!_thumb.isNull()) const_cast(this)->_thumb = QPixmap(); + p.drawPixmap(r.topLeft(), _gif->current(frame.width(), frame.height(), _width, height, paused ? 0 : ms)); + } else { + if (!_data->thumb->isNull()) { + if (_data->thumb->loaded()) { + if (_thumb.width() != _width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) { + const_cast(this)->_thumb = _data->thumb->pixNoCache(frame.width(), frame.height(), true, false, false, _width, height); + } + } else { + _data->thumb->load(); + } + } + p.drawPixmap(r.topLeft(), _thumb); + } + + if (radial || (!_gif && !loaded && !_data->loading()) || (_gif == BadClipReader)) { + float64 radialOpacity = (radial && loaded && !_data->uploading()) ? _animation->radial.opacity() : 1; + if (_animation && _animation->_a_over.animating(ms)) { + float64 over = _animation->_a_over.current(); + p.setOpacity((st::msgDateImgBg->c.alphaF() * (1 - over)) + (st::msgDateImgBgOver->c.alphaF() * over)); + p.fillRect(r, st::black); + } else { + p.fillRect(r, (_state & StateOver) ? st::msgDateImgBgOver : st::msgDateImgBg); + } + p.setOpacity(radialOpacity * p.opacity()); + + p.setOpacity(radialOpacity); + style::sprite icon; + if (_data->loaded() && !radial) { + icon = st::msgFileInPlay; + } else if (radial || _data->loading()) { + icon = st::msgFileInCancel; + } else { + icon = st::msgFileInDownload; + } + QRect inner((_width - st::msgFileSize) / 2, (height - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize); + p.drawSpriteCenter(inner, icon); + if (radial) { + p.setOpacity(1); + QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine))); + _animation->radial.draw(p, rinner, st::msgFileRadialLine, st::msgInBg); + } + } + + if (_state & StateOver) { + float64 deleteOver = _a_deleteOver.current(ms, (_state & StateDeleteOver) ? 1 : 0); + QPoint deletePos = QPoint(_width - st::stickerPanDelete.pxWidth(), 0); + p.setOpacity(deleteOver + (1 - deleteOver) * st::stickerPanDeleteOpacity); + p.drawSpriteLeft(deletePos, _width, st::stickerPanDelete); + p.setOpacity(1); + } +} + +QSize LayoutSavedGif::countFrameSize() const { + bool animating = (gif() && _gif->ready()); + int32 framew = animating ? _gif->width() : _data->thumb->width(), frameh = animating ? _gif->height() : _data->thumb->height(), height = st::savedGifHeight; + if (framew * height > frameh * _width) { + if (framew < st::maxStickerSize || frameh > height) { + if (frameh > height || (framew * height / frameh) <= st::maxStickerSize) { + framew = framew * height / frameh; + frameh = height; + } else { + frameh = int32(frameh * st::maxStickerSize) / framew; + framew = st::maxStickerSize; + } + } + } else { + if (frameh < st::maxStickerSize || framew > _width) { + if (framew > _width || (frameh * _width / framew) <= st::maxStickerSize) { + frameh = frameh * _width / framew; + framew = _width; + } else { + framew = int32(framew * st::maxStickerSize) / frameh; + frameh = st::maxStickerSize; + } + } + } + return QSize(framew, frameh); +} + +void LayoutSavedGif::preload() { + _data->thumb->load(); +} + +LayoutSavedGif::~LayoutSavedGif() { + delete _animation; + setBadPointer(_animation); +} + +void LayoutSavedGif::ensureAnimation() const { + if (!_animation) { + _animation = new AnimationData(animation(const_cast(this), &LayoutSavedGif::step_radial)); + } +} + +bool LayoutSavedGif::isRadialAnimation(uint64 ms) const { + if (!_animation || !_animation->radial.animating()) return false; + + _animation->radial.step(ms); + return _animation && _animation->radial.animating(); +} + +void LayoutSavedGif::step_radial(uint64 ms, bool timer) { + if (timer) { + update(); + } else { + _animation->radial.update(_data->progress(), !_data->loading() || _data->loaded(), ms); + if (!_animation->radial.animating() && _data->loaded()) { + delete _animation; + _animation = 0; + } + } +} + +void LayoutSavedGif::clipCallback(ClipReaderNotification notification) { + switch (notification) { + case ClipReaderReinit: { + if (gif()) { + if (_gif->state() == ClipError) { + delete _gif; + _gif = BadClipReader; + _data->forget(); + } else if (_gif->ready() && !_gif->started()) { + int32 height = st::savedGifHeight; + QSize frame = countFrameSize(); + _gif->start(frame.width(), frame.height(), _width, height, false); + } else if (_gif->paused() && !Ui::isSavedGifVisible(this)) { + delete _gif; + _gif = 0; + _data->forget(); + } + } + + update(); + } break; + + case ClipReaderRepaint: update(); break; + } +} + +void LayoutSavedGif::update() { + if (_position >= 0) { + Ui::repaintSavedGif(this); + } +} diff --git a/Telegram/SourceFiles/layout.h b/Telegram/SourceFiles/layout.h index f606c6cea..ab0c37536 100644 --- a/Telegram/SourceFiles/layout.h +++ b/Telegram/SourceFiles/layout.h @@ -470,3 +470,62 @@ private: QVector _links; }; + +class LayoutSavedGif { +public: + LayoutSavedGif(DocumentData *data); + + void paint(Painter &p, bool paused, uint64 ms) const; + void preload(); + DocumentData *document() const { + return _data; + } + + void setPosition(int32 position, int32 width); + void setWidth(int32 width); + int32 position() const; + int32 width() const; + + void notify_over(bool over); + void notify_deleteOver(bool over); + + ~LayoutSavedGif(); + +private: + DocumentData *_data; + int32 _position; // < 0 means removed from layout + int32 _width; + QSize countFrameSize() const; + + enum StateFlags { + StateOver = 0x01, + StateDeleteOver = 0x02, + }; + int32 _state; + + ClipReader *_gif; + bool gif() const { + return (!_gif || _gif == BadClipReader) ? false : true; + } + QPixmap _thumb; + + void ensureAnimation() const; + bool isRadialAnimation(uint64 ms) const; + void step_radial(uint64 ms, bool timer); + + void clipCallback(ClipReaderNotification notification); + void update(); + + struct AnimationData { + AnimationData(AnimationCallbacks *radialCallbacks) + : over(false) + , radial(radialCallbacks) { + } + bool over; + FloatAnimation _a_over; + RadialAnimation radial; + }; + mutable AnimationData *_animation; + mutable FloatAnimation _a_deleteOver; + +}; diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 589d24893..8d2d80968 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -552,6 +552,7 @@ namespace { lskStickers = 0x0b, // no data lskSavedPeers = 0x0c, // no data lskReportSpamStatuses = 0x0d, // no data + lskSavedGifs = 0x0e, }; typedef QMap DraftsMap; @@ -568,7 +569,7 @@ namespace { FileLocationAliases _fileLocationAliases; FileKey _locationsKey = 0, _reportSpamStatusesKey = 0; - FileKey _recentStickersKeyOld = 0, _stickersKey = 0; + FileKey _recentStickersKeyOld = 0, _stickersKey = 0, _savedGifsKey; FileKey _backgroundKey = 0; bool _backgroundWasRead = false; @@ -797,6 +798,14 @@ namespace { cSetMaxGroupCount(maxSize); } break; + case dbiSavedGifsLimit: { + qint32 limit; + stream >> limit; + if (!_checkStreamStatus(stream)) return false; + + cSetSavedGifsLimit(limit); + } break; + case dbiMaxMegaGroupCount: { qint32 maxSize; stream >> maxSize; @@ -873,6 +882,14 @@ namespace { cSetAutoDownloadGif(gif); } break; + case dbiAutoPlay: { + qint32 gif; + stream >> gif; + if (!_checkStreamStatus(stream)) return false; + + cSetAutoPlayGif(gif == 1); + } break; + case dbiIncludeMuted: { qint32 v; stream >> v; @@ -881,6 +898,14 @@ namespace { cSetIncludeMuted(v == 1); } break; + case dbiShowingSavedGifs: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetShowingSavedGifs(v == 1); + } break; + case dbiDesktopNotify: { qint32 v; stream >> v; @@ -1438,7 +1463,7 @@ namespace { _writeMap(WriteMapFast); } - uint32 size = 14 * (sizeof(quint32) + sizeof(qint32)); + uint32 size = 16 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + _stringSize(cAskDownloadPath() ? QString() : cDownloadPath()) + _bytearraySize(cAskDownloadPath() ? QByteArray() : cDownloadPathBookmark()); size += sizeof(quint32) + sizeof(qint32) + (cRecentEmojisPreload().isEmpty() ? cGetRecentEmojis().size() : cRecentEmojisPreload().size()) * (sizeof(uint64) + sizeof(ushort)); size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64)); @@ -1454,6 +1479,7 @@ namespace { data.stream << quint32(dbiDefaultAttach) << qint32(cDefaultAttach()); data.stream << quint32(dbiSoundNotify) << qint32(cSoundNotify()); data.stream << quint32(dbiIncludeMuted) << qint32(cIncludeMuted()); + data.stream << quint32(dbiShowingSavedGifs) << qint32(cShowingSavedGifs()); data.stream << quint32(dbiDesktopNotify) << qint32(cDesktopNotify()); data.stream << quint32(dbiNotifyView) << qint32(cNotifyView()); data.stream << quint32(dbiWindowsNotifications) << qint32(cWindowsNotifications()); @@ -1463,6 +1489,7 @@ namespace { data.stream << quint32(dbiDialogLastPath) << cDialogLastPath(); data.stream << quint32(dbiSongVolume) << qint32(qRound(cSongVolume() * 1e6)); data.stream << quint32(dbiAutoDownload) << qint32(cAutoDownloadPhoto()) << qint32(cAutoDownloadAudio()) << qint32(cAutoDownloadGif()); + data.stream << quint32(dbiAutoPlay) << qint32(cAutoPlayGif() ? 1 : 0); { RecentEmojisPreload v(cRecentEmojisPreload()); @@ -1608,7 +1635,9 @@ namespace { DraftsNotReadMap draftsNotReadMap; StorageMap imagesMap, stickerImagesMap, audiosMap; qint64 storageImagesSize = 0, storageStickersSize = 0, storageAudiosSize = 0; - quint64 locationsKey = 0, reportSpamStatusesKey = 0, recentStickersKeyOld = 0, stickersKey = 0, backgroundKey = 0, userSettingsKey = 0, recentHashtagsKey = 0, savedPeersKey = 0; + quint64 locationsKey = 0, reportSpamStatusesKey = 0; + quint64 recentStickersKeyOld = 0, stickersKey = 0, savedGifsKey = 0; + quint64 backgroundKey = 0, userSettingsKey = 0, recentHashtagsKey = 0, savedPeersKey = 0; while (!map.stream.atEnd()) { quint32 keyType; map.stream >> keyType; @@ -1691,6 +1720,9 @@ namespace { case lskStickers: { map.stream >> stickersKey; } break; + case lskSavedGifs: { + map.stream >> savedGifsKey; + } break; case lskSavedPeers: { map.stream >> savedPeersKey; } break; @@ -1718,6 +1750,7 @@ namespace { _reportSpamStatusesKey = reportSpamStatusesKey; _recentStickersKeyOld = recentStickersKeyOld; _stickersKey = stickersKey; + _savedGifsKey = savedGifsKey; _savedPeersKey = savedPeersKey; _backgroundKey = backgroundKey; _userSettingsKey = userSettingsKey; @@ -1790,6 +1823,7 @@ namespace { if (_reportSpamStatusesKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_recentStickersKeyOld) mapSize += sizeof(quint32) + sizeof(quint64); if (_stickersKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_savedGifsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_savedPeersKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_backgroundKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_userSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64); @@ -1837,6 +1871,9 @@ namespace { if (_stickersKey) { mapData.stream << quint32(lskStickers) << quint64(_stickersKey); } + if (_savedGifsKey) { + mapData.stream << quint32(lskSavedGifs) << quint64(_savedGifsKey); + } if (_savedPeersKey) { mapData.stream << quint32(lskSavedPeers) << quint64(_savedPeersKey); } @@ -2040,7 +2077,7 @@ namespace Local { cSetDcOptions(dcOpts); } - quint32 size = 11 * (sizeof(quint32) + sizeof(qint32)); + quint32 size = 12 * (sizeof(quint32) + sizeof(qint32)); for (mtpDcOptions::const_iterator i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) { size += sizeof(quint32) + sizeof(quint32) + sizeof(quint32); size += sizeof(quint32) + _stringSize(QString::fromUtf8(i->ip.data(), i->ip.size())); @@ -2058,6 +2095,7 @@ namespace Local { EncryptedDescriptor data(size); data.stream << quint32(dbiMaxGroupCount) << qint32(cMaxGroupCount()); data.stream << quint32(dbiMaxMegaGroupCount) << qint32(cMaxMegaGroupCount()); + data.stream << quint32(dbiSavedGifsLimit) << qint32(cSavedGifsLimit()); data.stream << quint32(dbiAutoStart) << qint32(cAutoStart()); data.stream << quint32(dbiStartMinimized) << qint32(cStartMinimized()); data.stream << quint32(dbiSendToMenu) << qint32(cSendToMenu()); @@ -2111,7 +2149,9 @@ namespace Local { _stickerImagesMap.clear(); _audiosMap.clear(); _storageImagesSize = _storageStickersSize = _storageAudiosSize = 0; - _locationsKey = _reportSpamStatusesKey = _recentStickersKeyOld = _stickersKey = _backgroundKey = _userSettingsKey = _recentHashtagsKey = _savedPeersKey = 0; + _locationsKey = _reportSpamStatusesKey = 0; + _recentStickersKeyOld = _stickersKey = _savedGifsKey = 0; + _backgroundKey = _userSettingsKey = _recentHashtagsKey = _savedPeersKey = 0; _oldMapVersion = _oldSettingsVersion = 0; _mapChanged = true; _writeMap(WriteMapNow); @@ -2667,7 +2707,7 @@ namespace Local { _writeMap(); } else { int32 setsCount = 0; - QByteArray hashToWrite = (qsl("%d:") + QString::number(cStickersHash())).toUtf8(); + QByteArray hashToWrite; quint32 size = sizeof(quint32) + _bytearraySize(hashToWrite); for (StickerSets::const_iterator i = sets.cbegin(); i != sets.cend(); ++i) { bool notLoaded = (i->flags & MTPDstickerSet_flag_NOT_LOADED); @@ -2729,8 +2769,6 @@ namespace Local { RecentStickerPack &recent(cRefRecentStickers()); recent.clear(); - cSetStickersHash(0); - StickerSet &def(sets.insert(DefaultStickerSetId, StickerSet(DefaultStickerSetId, 0, lang(lng_stickers_default_set), QString(), 0, 0, MTPDstickerSet::flag_official)).value()); StickerSet &custom(sets.insert(CustomStickerSetId, StickerSet(CustomStickerSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, 0)).value()); @@ -2806,9 +2844,8 @@ namespace Local { quint32 cnt; QByteArray hash; - stickers.stream >> cnt >> hash; - if (stickers.version < 8019) { - hash.clear(); // bad data in old caches + stickers.stream >> cnt >> hash; // ignore hash, it is counted + if (stickers.version < 8019) { // bad data in old caches cnt += 2; // try to read at least something } for (uint32 i = 0; i < cnt; ++i) { @@ -2886,11 +2923,124 @@ namespace Local { ++set.count; } } + } - if (hash.startsWith(qsl("%d:").toUtf8())) { - cSetStickersHash(QString::fromUtf8(hash.mid(3)).toInt()); + int32 countStickersHash(bool checkOfficial) { + uint32 acc = 0; + bool foundOfficial = false, foundBad = false;; + const StickerSets &sets(cStickerSets()); + const StickerSetsOrder &order(cStickerSetsOrder()); + for (StickerSetsOrder::const_iterator i = order.cbegin(), e = order.cend(); i != e; ++i) { + StickerSets::const_iterator j = sets.constFind(*i); + if (j != sets.cend()) { + if (j->id == 0) { + foundBad = true; + } else if (j->flags & MTPDstickerSet::flag_official) { + foundOfficial = true; + } + if (!(j->flags & MTPDstickerSet::flag_disabled)) { + acc = (acc * 20261) + j->hash; + } + } + } + return (!checkOfficial || (!foundBad && foundOfficial)) ? int32(acc & 0x7FFFFFFF) : 0; + } + + int32 countSavedGifsHash() { + uint32 acc = 0; + const SavedGifs &saved(cSavedGifs()); + for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { + uint64 docId = (*i)->id; + + acc = (acc * 20261) + uint32(docId >> 32); + acc = (acc * 20261) + uint32(docId & 0xFFFFFFFF); + } + return int32(acc & 0x7FFFFFFF); + } + + void writeSavedGifs() { + if (!_working()) return; + + const SavedGifs &saved(cSavedGifs()); + if (saved.isEmpty()) { + if (_savedGifsKey) { + clearKey(_savedGifsKey); + _savedGifsKey = 0; + _mapChanged = true; + } + _writeMap(); } else { - cSetStickersHash(0); + quint32 size = sizeof(quint32); // count + for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { + DocumentData *doc = *i; + + // id + access + date + namelen + name + mimelen + mime + dc + size + width + height + type + size += sizeof(quint64) + sizeof(quint64) + sizeof(qint32) + _stringSize(doc->name) + _stringSize(doc->mime) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32); + + // thumb + size += _storageImageLocationSize(); + } + + if (!_savedGifsKey) { + _savedGifsKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); + } + EncryptedDescriptor data(size); + data.stream << quint32(saved.size()); + for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { + DocumentData *doc = *i; + + data.stream << quint64(doc->id) << quint64(doc->access) << qint32(doc->date) << doc->name << doc->mime << qint32(doc->dc) << qint32(doc->size) << qint32(doc->dimensions.width()) << qint32(doc->dimensions.height()) << qint32(doc->type); + _writeStorageImageLocation(data.stream, doc->thumb->location()); + } + FileWriteDescriptor file(_savedGifsKey); + file.writeEncrypted(data); + } + } + + void readSavedGifs() { + if (!_savedGifsKey) return; + + FileReadDescriptor gifs; + if (!readEncryptedFile(gifs, _savedGifsKey)) { + clearKey(_savedGifsKey); + _savedGifsKey = 0; + _writeMap(); + return; + } + + SavedGifs &saved(cRefSavedGifs()); + saved.clear(); + + quint32 cnt; + gifs.stream >> cnt; + saved.reserve(cnt); + QMap read; + for (uint32 i = 0; i < cnt; ++i) { + quint64 id, access; + QString name, mime; + qint32 date, dc, size, width, height, type; + gifs.stream >> id >> access >> date >> name >> mime >> dc >> size >> width >> height >> type; + + StorageImageLocation thumb(_readStorageImageLocation(gifs)); + + if (read.contains(id)) continue; + read.insert(id, NullType()); + + QVector attributes; + if (!name.isEmpty()) attributes.push_back(MTP_documentAttributeFilename(MTP_string(name))); + if (type == AnimatedDocument) { + attributes.push_back(MTP_documentAttributeAnimated()); + } + if (width > 0 && height > 0) { + attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height))); + } + + DocumentData *doc = App::documentSet(id, 0, access, date, attributes, mime, thumb.isNull() ? ImagePtr() : ImagePtr(thumb), dc, size, thumb); + if (!doc->isAnimation()) continue; + + saved.push_back(doc); } } diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index eafc6066d..76c193ebe 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -142,6 +142,11 @@ namespace Local { void writeStickers(); void readStickers(); + int32 countStickersHash(bool checkOfficial = false); + + void writeSavedGifs(); + void readSavedGifs(); + int32 countSavedGifsHash(); void writeBackground(int32 id, const QImage &img); bool readBackground(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 418e39498..10998c2ab 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -742,7 +742,7 @@ QPixmap MainWidget::grabTopBar() { } void MainWidget::ui_showStickerPreview(DocumentData *sticker) { - if (!sticker || !sticker->sticker()) return; + if (!sticker || ((!sticker->isAnimation() || !sticker->loaded()) && !sticker->sticker())) return; if (!_stickerPreview) { _stickerPreview = new StickerPreviewWidget(this); resizeEvent(0); @@ -773,7 +773,7 @@ void MainWidget::notify_userIsContactChanged(UserData *user, bool fromThisApp) { if (i != items.cend()) { for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { j.key()->initDimensions(); - Ui::redrawHistoryItem(j.key()); + Ui::repaintHistoryItem(j.key()); } } @@ -786,18 +786,28 @@ void MainWidget::notify_migrateUpdated(PeerData *peer) { history.notify_migrateUpdated(peer); } -void MainWidget::notify_mediaViewHidden() { - history.notify_mediaViewHidden(); +void MainWidget::notify_clipStopperHidden(ClipStopperType type) { + history.notify_clipStopperHidden(type); } -void MainWidget::ui_redrawHistoryItem(const HistoryItem *item) { - if (!item) return; - - history.ui_redrawHistoryItem(item); +void MainWidget::ui_repaintHistoryItem(const HistoryItem *item) { + history.ui_repaintHistoryItem(item); if (!item->history()->dialogs.isEmpty() && item->history()->lastMsg == item) { dialogs.dlgUpdated(item->history()->dialogs[0]); } - if (overview) overview->ui_redrawHistoryItem(item); + if (overview) overview->ui_repaintHistoryItem(item); +} + +void MainWidget::ui_repaintSavedGif(const LayoutSavedGif *layout) { + history.ui_repaintSavedGif(layout); +} + +bool MainWidget::ui_isSavedGifVisible(const LayoutSavedGif *layout) { + return history.ui_isSavedGifVisible(layout); +} + +bool MainWidget::ui_isGifBeingChosen() { + return history.ui_isGifBeingChosen(); } void MainWidget::notify_historyItemLayoutChanged(const HistoryItem *item) { @@ -814,7 +824,7 @@ void MainWidget::notify_historyItemResized(const HistoryItem *item, bool scrollT history.resizeEvent(0); } } - if (item) Ui::redrawHistoryItem(item); + if (item) Ui::repaintHistoryItem(item); } void MainWidget::noHider(HistoryHider *destroyed) { @@ -1638,7 +1648,7 @@ void MainWidget::videoLoadProgress(mtpFileLoader *loader) { VideoItems::const_iterator i = items.constFind(video); if (i != items.cend()) { for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { - Ui::redrawHistoryItem(j.key()); + Ui::repaintHistoryItem(j.key()); } } } @@ -1694,7 +1704,7 @@ void MainWidget::audioLoadProgress(mtpFileLoader *loader) { AudioItems::const_iterator i = items.constFind(audio); if (i != items.cend()) { for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { - Ui::redrawHistoryItem(j.key()); + Ui::repaintHistoryItem(j.key()); } } } @@ -1729,7 +1739,7 @@ void MainWidget::audioPlayProgress(const AudioMsgId &audioId) { } if (HistoryItem *item = App::histItemById(audioId.msgId)) { - Ui::redrawHistoryItem(item); + Ui::repaintHistoryItem(item); } } @@ -1790,7 +1800,7 @@ void MainWidget::documentPlayProgress(const SongMsgId &songId) { } if (HistoryItem *item = App::histItemById(songId.msgId)) { - Ui::redrawHistoryItem(item); + Ui::repaintHistoryItem(item); } } @@ -1828,7 +1838,7 @@ void MainWidget::documentLoadProgress(mtpFileLoader *loader) { DocumentItems::const_iterator i = items.constFind(document); if (i != items.cend()) { for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { - Ui::redrawHistoryItem(j.key()); + Ui::repaintHistoryItem(j.key()); } } App::wnd()->documentUpdated(document); @@ -3415,6 +3425,7 @@ void MainWidget::start(const MTPUser &user) { _started = true; App::wnd()->sendServiceHistoryRequest(); Local::readStickers(); + Local::readSavedGifs(); history.start(); } @@ -4155,7 +4166,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { msgRow->history()->unregTyping(App::self()); } App::historyRegItem(msgRow); - Ui::redrawHistoryItem(msgRow); + Ui::repaintHistoryItem(msgRow); } } App::historyUnregRandom(d.vrandom_id.v); @@ -4176,7 +4187,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { if (HistoryItem *item = App::histItemById(NoChannel, v.at(i).v)) { if (item->isMediaUnread()) { item->markMediaRead(); - Ui::redrawHistoryItem(item); + Ui::repaintHistoryItem(item); if (item->out() && item->history()->peer->isUser()) { item->history()->peer->asUser()->madeAction(); } @@ -4580,7 +4591,6 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { sets.erase(custom); } } - cSetStickersHash(stickersCountHash()); Local::writeStickers(); emit stickersUpdated(); } @@ -4600,11 +4610,9 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } if (result.size() != cStickerSetsOrder().size() || result.size() != order.size()) { cSetLastStickersUpdate(0); - cSetStickersHash(0); App::main()->updateStickers(); } else { cSetStickerSetsOrder(result); - cSetStickersHash(stickersCountHash()); Local::writeStickers(); emit stickersUpdated(); } @@ -4612,7 +4620,11 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { case mtpc_updateStickerSets: { cSetLastStickersUpdate(0); - cSetStickersHash(0); + App::main()->updateStickers(); + } break; + + case mtpc_updateSavedGifs: { + cSetLastSavedGifsUpdate(0); App::main()->updateStickers(); } break; diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index a4ef15298..d72e81078 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -409,14 +409,17 @@ public: void ui_showStickerPreview(DocumentData *sticker); void ui_hideStickerPreview(); - void ui_redrawHistoryItem(const HistoryItem *item); + void ui_repaintHistoryItem(const HistoryItem *item); + void ui_repaintSavedGif(const LayoutSavedGif *layout); + bool ui_isSavedGifVisible(const LayoutSavedGif *layout); + bool ui_isGifBeingChosen(); void ui_showPeerHistory(quint64 peer, qint32 msgId, bool back); void notify_botCommandsChanged(UserData *bot); void notify_userIsBotChanged(UserData *bot); void notify_userIsContactChanged(UserData *user, bool fromThisApp); void notify_migrateUpdated(PeerData *peer); - void notify_mediaViewHidden(); + void notify_clipStopperHidden(ClipStopperType type); void notify_historyItemResized(const HistoryItem *row, bool scrollToIt); void notify_historyItemLayoutChanged(const HistoryItem *item); @@ -430,6 +433,7 @@ signals: void dialogRowReplaced(DialogRow *oldRow, DialogRow *newRow); void dialogsUpdated(); void stickersUpdated(); + void savedGifsUpdated(); public slots: diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index d1b31bede..8522d3a7c 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -603,7 +603,7 @@ void MediaView::onDocClick() { } } -void MediaView::ui_clipRedraw(ClipReader *reader) { +void MediaView::ui_clipRepaint(ClipReader *reader) { if (reader == _gif) { update(_x, _y, _w, _h); } @@ -1960,7 +1960,7 @@ void MediaView::hide() { QWidget::hide(); stopGif(); - Notify::mediaViewHidden(); + Notify::clipStopperHidden(ClipStopperMediaview); } void MediaView::onMenuDestroy(QObject *obj) { diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index 498fd5427..d949c80a3 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -71,7 +71,7 @@ public: void activateControls(); void onDocClick(); - void ui_clipRedraw(ClipReader *reader); + void ui_clipRepaint(ClipReader *reader); void notify_clipReinit(ClipReader *reader); diff --git a/Telegram/SourceFiles/mtproto/mtp.cpp b/Telegram/SourceFiles/mtproto/mtp.cpp index ebc6b3853..6b9b26cb0 100644 --- a/Telegram/SourceFiles/mtproto/mtp.cpp +++ b/Telegram/SourceFiles/mtproto/mtp.cpp @@ -154,6 +154,9 @@ namespace { bool onErrorDefault(mtpRequestId requestId, const RPCError &error) { const QString &err(error.type()); int32 code = error.code(); + if (!mtpIsFlood(error)) { + int breakpoint = 0; + } bool badGuestDC = (code == 400) && (err == qsl("FILE_ID_INVALID")); QRegularExpressionMatch m; if ((m = QRegularExpression("^(FILE|PHONE|NETWORK|USER)_MIGRATE_(\\d+)$").match(err)).hasMatch()) { diff --git a/Telegram/SourceFiles/mtproto/mtpDC.cpp b/Telegram/SourceFiles/mtproto/mtpDC.cpp index 700bd3caa..ee5a079ad 100644 --- a/Telegram/SourceFiles/mtproto/mtpDC.cpp +++ b/Telegram/SourceFiles/mtproto/mtpDC.cpp @@ -154,6 +154,7 @@ namespace { mtpUpdateDcOptions(data.vdc_options.c_vector().v); cSetMaxGroupCount(data.vchat_size_max.v); cSetMaxMegaGroupCount(data.vmegagroup_size_max.v); + cSetSavedGifsLimit(data.vsaved_gifs_limit.v); configLoadedOnce = true; Local::writeSettings(); diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.cpp b/Telegram/SourceFiles/mtproto/mtpScheme.cpp index 0bdddfbd6..4fa0d5eb3 100644 --- a/Telegram/SourceFiles/mtproto/mtpScheme.cpp +++ b/Telegram/SourceFiles/mtproto/mtpScheme.cpp @@ -2466,6 +2466,10 @@ void _serialize_inputMessagesFilterUrl(MTPStringLogger &to, int32 stage, int32 l to.add("{ inputMessagesFilterUrl }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } +void _serialize_inputMessagesFilterGif(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + to.add("{ inputMessagesFilterGif }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + void _serialize_updateNewMessage(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -3039,6 +3043,10 @@ void _serialize_updateStickerSets(MTPStringLogger &to, int32 stage, int32 lev, T to.add("{ updateStickerSets }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } +void _serialize_updateSavedGifs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + to.add("{ updateSavedGifs }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + void _serialize_updates_state(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -3336,7 +3344,8 @@ void _serialize_config(MTPStringLogger &to, int32 stage, int32 lev, Types &types case 14: to.add(" chat_big_size: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 15: to.add(" push_chat_period_ms: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 16: to.add(" push_chat_limit: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 17: to.add(" disabled_features: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 17: to.add(" saved_gifs_limit: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 18: to.add(" disabled_features: "); ++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; } } @@ -5107,6 +5116,24 @@ void _serialize_messages_foundGifs(MTPStringLogger &to, int32 stage, int32 lev, } } +void _serialize_messages_savedGifsNotModified(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + to.add("{ messages_savedGifsNotModified }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + +void _serialize_messages_savedGifs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_savedGifs"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" gifs: "); ++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 flag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -5621,6 +5648,20 @@ void _serialize_messages_reorderStickerSets(MTPStringLogger &to, int32 stage, in } } +void _serialize_messages_saveGif(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_saveGif"); + 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(" unsave: "); ++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_upload_saveFilePart(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -7098,6 +7139,19 @@ void _serialize_messages_searchGifs(MTPStringLogger &to, int32 stage, int32 lev, } } +void _serialize_messages_getSavedGifs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_getSavedGifs"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int); 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 flag) { to.add("{ updates_getState }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } @@ -7546,6 +7600,7 @@ namespace { _serializers.insert(mtpc_inputMessagesFilterAudio, _serialize_inputMessagesFilterAudio); _serializers.insert(mtpc_inputMessagesFilterAudioDocuments, _serialize_inputMessagesFilterAudioDocuments); _serializers.insert(mtpc_inputMessagesFilterUrl, _serialize_inputMessagesFilterUrl); + _serializers.insert(mtpc_inputMessagesFilterGif, _serialize_inputMessagesFilterGif); _serializers.insert(mtpc_updateNewMessage, _serialize_updateNewMessage); _serializers.insert(mtpc_updateMessageID, _serialize_updateMessageID); _serializers.insert(mtpc_updateDeleteMessages, _serialize_updateDeleteMessages); @@ -7586,6 +7641,7 @@ namespace { _serializers.insert(mtpc_updateNewStickerSet, _serialize_updateNewStickerSet); _serializers.insert(mtpc_updateStickerSetsOrder, _serialize_updateStickerSetsOrder); _serializers.insert(mtpc_updateStickerSets, _serialize_updateStickerSets); + _serializers.insert(mtpc_updateSavedGifs, _serialize_updateSavedGifs); _serializers.insert(mtpc_updates_state, _serialize_updates_state); _serializers.insert(mtpc_updates_differenceEmpty, _serialize_updates_differenceEmpty); _serializers.insert(mtpc_updates_difference, _serialize_updates_difference); @@ -7754,6 +7810,8 @@ namespace { _serializers.insert(mtpc_help_termsOfService, _serialize_help_termsOfService); _serializers.insert(mtpc_foundGif, _serialize_foundGif); _serializers.insert(mtpc_messages_foundGifs, _serialize_messages_foundGifs); + _serializers.insert(mtpc_messages_savedGifsNotModified, _serialize_messages_savedGifsNotModified); + _serializers.insert(mtpc_messages_savedGifs, _serialize_messages_savedGifs); _serializers.insert(mtpc_req_pq, _serialize_req_pq); _serializers.insert(mtpc_req_DH_params, _serialize_req_DH_params); @@ -7794,6 +7852,7 @@ namespace { _serializers.insert(mtpc_messages_uninstallStickerSet, _serialize_messages_uninstallStickerSet); _serializers.insert(mtpc_messages_editChatAdmin, _serialize_messages_editChatAdmin); _serializers.insert(mtpc_messages_reorderStickerSets, _serialize_messages_reorderStickerSets); + _serializers.insert(mtpc_messages_saveGif, _serialize_messages_saveGif); _serializers.insert(mtpc_upload_saveFilePart, _serialize_upload_saveFilePart); _serializers.insert(mtpc_upload_saveBigFilePart, _serialize_upload_saveBigFilePart); _serializers.insert(mtpc_help_saveAppLog, _serialize_help_saveAppLog); @@ -7902,6 +7961,7 @@ namespace { _serializers.insert(mtpc_messages_getStickerSet, _serialize_messages_getStickerSet); _serializers.insert(mtpc_messages_getDocumentByHash, _serialize_messages_getDocumentByHash); _serializers.insert(mtpc_messages_searchGifs, _serialize_messages_searchGifs); + _serializers.insert(mtpc_messages_getSavedGifs, _serialize_messages_getSavedGifs); _serializers.insert(mtpc_updates_getState, _serialize_updates_getState); _serializers.insert(mtpc_updates_getDifference, _serialize_updates_getDifference); _serializers.insert(mtpc_updates_getChannelDifference, _serialize_updates_getChannelDifference); diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.h b/Telegram/SourceFiles/mtproto/mtpScheme.h index cf53b5277..2e3909c79 100644 --- a/Telegram/SourceFiles/mtproto/mtpScheme.h +++ b/Telegram/SourceFiles/mtproto/mtpScheme.h @@ -240,6 +240,7 @@ enum { mtpc_inputMessagesFilterAudio = 0xcfc87522, mtpc_inputMessagesFilterAudioDocuments = 0x5afbf764, mtpc_inputMessagesFilterUrl = 0x7ef0dd87, + mtpc_inputMessagesFilterGif = 0xffc86587, mtpc_updateNewMessage = 0x1f2b0afd, mtpc_updateMessageID = 0x4e90bfd6, mtpc_updateDeleteMessages = 0xa20db0e5, @@ -280,6 +281,7 @@ enum { mtpc_updateNewStickerSet = 0x688a30aa, mtpc_updateStickerSetsOrder = 0xf0dfb451, mtpc_updateStickerSets = 0x43ae3dec, + mtpc_updateSavedGifs = 0x9375341e, mtpc_updates_state = 0xa56c2a3e, mtpc_updates_differenceEmpty = 0x5d75a138, mtpc_updates_difference = 0xf49ca0, @@ -296,7 +298,7 @@ enum { mtpc_photos_photo = 0x20212ca8, mtpc_upload_file = 0x96a18d5, mtpc_dcOption = 0x5d8c6cc, - mtpc_config = 0x6cb6e65e, + mtpc_config = 0x6bbc5f8, mtpc_nearestDc = 0x8e1a1775, mtpc_help_appUpdate = 0x8987f311, mtpc_help_noAppUpdate = 0xc45a6536, @@ -448,6 +450,8 @@ enum { mtpc_help_termsOfService = 0xf1ee3e90, mtpc_foundGif = 0xd579cccb, mtpc_messages_foundGifs = 0x450a1c0a, + mtpc_messages_savedGifsNotModified = 0xe8025ca2, + mtpc_messages_savedGifs = 0x2e0709a5, mtpc_invokeAfterMsg = 0xcb9f372d, mtpc_invokeAfterMsgs = 0x3dc4b4f0, mtpc_initConnection = 0x69796de9, @@ -559,6 +563,8 @@ enum { mtpc_messages_reorderStickerSets = 0x9fcfbc30, mtpc_messages_getDocumentByHash = 0x338e2464, mtpc_messages_searchGifs = 0xbf9a776b, + mtpc_messages_getSavedGifs = 0x83bf3d52, + mtpc_messages_saveGif = 0x327a30cb, mtpc_updates_getState = 0xedd4882a, mtpc_updates_getDifference = 0xa041495, mtpc_updates_getChannelDifference = 0xbb32d7c0, @@ -1229,6 +1235,9 @@ class MTPDfoundGif; class MTPmessages_foundGifs; class MTPDmessages_foundGifs; +class MTPmessages_savedGifs; +class MTPDmessages_savedGifs; + // Boxed types definitions typedef MTPBoxed MTPResPQ; @@ -1390,6 +1399,7 @@ typedef MTPBoxed MTPchannels_ChannelParticipant; typedef MTPBoxed MTPhelp_TermsOfService; typedef MTPBoxed MTPFoundGif; typedef MTPBoxed MTPmessages_FoundGifs; +typedef MTPBoxed MTPmessages_SavedGifs; // Type classes definitions @@ -5198,6 +5208,7 @@ private: friend MTPmessagesFilter MTP_inputMessagesFilterAudio(); friend MTPmessagesFilter MTP_inputMessagesFilterAudioDocuments(); friend MTPmessagesFilter MTP_inputMessagesFilterUrl(); + friend MTPmessagesFilter MTP_inputMessagesFilterGif(); mtpTypeId _type; }; @@ -5768,6 +5779,7 @@ private: friend MTPupdate MTP_updateNewStickerSet(const MTPmessages_StickerSet &_stickerset); friend MTPupdate MTP_updateStickerSetsOrder(const MTPVector &_order); friend MTPupdate MTP_updateStickerSets(); + friend MTPupdate MTP_updateSavedGifs(); mtpTypeId _type; }; @@ -6148,7 +6160,7 @@ public: private: explicit MTPconfig(MTPDconfig *_data); - friend MTPconfig MTP_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, const MTPVector &_disabled_features); + friend MTPconfig MTP_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, const MTPVector &_disabled_features); }; typedef MTPBoxed MTPConfig; @@ -9013,6 +9025,44 @@ private: }; typedef MTPBoxed MTPmessages_FoundGifs; +class MTPmessages_savedGifs : private mtpDataOwner { +public: + MTPmessages_savedGifs() : mtpDataOwner(0), _type(0) { + } + MTPmessages_savedGifs(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) { + read(from, end, cons); + } + + MTPDmessages_savedGifs &_messages_savedGifs() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_messages_savedGifs) throw mtpErrorWrongTypeId(_type, mtpc_messages_savedGifs); + split(); + return *(MTPDmessages_savedGifs*)data; + } + const MTPDmessages_savedGifs &c_messages_savedGifs() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_messages_savedGifs) throw mtpErrorWrongTypeId(_type, mtpc_messages_savedGifs); + return *(const MTPDmessages_savedGifs*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPmessages_savedGifs(mtpTypeId type); + explicit MTPmessages_savedGifs(MTPDmessages_savedGifs *_data); + + friend MTPmessages_savedGifs MTP_messages_savedGifsNotModified(); + friend MTPmessages_savedGifs MTP_messages_savedGifs(MTPint _hash, const MTPVector &_gifs); + + mtpTypeId _type; +}; +typedef MTPBoxed MTPmessages_SavedGifs; + // Type constructors with data class MTPDresPQ : public mtpDataImpl { @@ -11635,7 +11685,7 @@ class MTPDconfig : public mtpDataImpl { public: MTPDconfig() { } - MTPDconfig(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, const MTPVector &_disabled_features) : vdate(_date), vexpires(_expires), vtest_mode(_test_mode), vthis_dc(_this_dc), vdc_options(_dc_options), vchat_size_max(_chat_size_max), vmegagroup_size_max(_megagroup_size_max), vforwarded_count_max(_forwarded_count_max), vonline_update_period_ms(_online_update_period_ms), voffline_blur_timeout_ms(_offline_blur_timeout_ms), voffline_idle_timeout_ms(_offline_idle_timeout_ms), vonline_cloud_timeout_ms(_online_cloud_timeout_ms), vnotify_cloud_delay_ms(_notify_cloud_delay_ms), vnotify_default_delay_ms(_notify_default_delay_ms), vchat_big_size(_chat_big_size), vpush_chat_period_ms(_push_chat_period_ms), vpush_chat_limit(_push_chat_limit), vdisabled_features(_disabled_features) { + MTPDconfig(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, const MTPVector &_disabled_features) : vdate(_date), vexpires(_expires), vtest_mode(_test_mode), vthis_dc(_this_dc), vdc_options(_dc_options), vchat_size_max(_chat_size_max), vmegagroup_size_max(_megagroup_size_max), vforwarded_count_max(_forwarded_count_max), vonline_update_period_ms(_online_update_period_ms), voffline_blur_timeout_ms(_offline_blur_timeout_ms), voffline_idle_timeout_ms(_offline_idle_timeout_ms), vonline_cloud_timeout_ms(_online_cloud_timeout_ms), vnotify_cloud_delay_ms(_notify_cloud_delay_ms), vnotify_default_delay_ms(_notify_default_delay_ms), vchat_big_size(_chat_big_size), vpush_chat_period_ms(_push_chat_period_ms), vpush_chat_limit(_push_chat_limit), vsaved_gifs_limit(_saved_gifs_limit), vdisabled_features(_disabled_features) { } MTPint vdate; @@ -11655,6 +11705,7 @@ public: MTPint vchat_big_size; MTPint vpush_chat_period_ms; MTPint vpush_chat_limit; + MTPint vsaved_gifs_limit; MTPVector vdisabled_features; }; @@ -13048,6 +13099,17 @@ public: MTPVector vresults; }; +class MTPDmessages_savedGifs : public mtpDataImpl { +public: + MTPDmessages_savedGifs() { + } + MTPDmessages_savedGifs(MTPint _hash, const MTPVector &_gifs) : vhash(_hash), vgifs(_gifs) { + } + + MTPint vhash; + MTPVector vgifs; +}; + // RPC methods class MTPreq_pq { // RPC method 'req_pq' @@ -18085,6 +18147,87 @@ public: } }; +class MTPmessages_getSavedGifs { // RPC method 'messages.getSavedGifs' +public: + MTPint vhash; + + MTPmessages_getSavedGifs() { + } + MTPmessages_getSavedGifs(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getSavedGifs) { + read(from, end, cons); + } + MTPmessages_getSavedGifs(MTPint _hash) : vhash(_hash) { + } + + uint32 innerLength() const { + return vhash.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_getSavedGifs; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getSavedGifs) { + vhash.read(from, end); + } + void write(mtpBuffer &to) const { + vhash.write(to); + } + + typedef MTPmessages_SavedGifs ResponseType; +}; +class MTPmessages_GetSavedGifs : public MTPBoxed { +public: + MTPmessages_GetSavedGifs() { + } + MTPmessages_GetSavedGifs(const MTPmessages_getSavedGifs &v) : MTPBoxed(v) { + } + MTPmessages_GetSavedGifs(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_GetSavedGifs(MTPint _hash) : MTPBoxed(MTPmessages_getSavedGifs(_hash)) { + } +}; + +class MTPmessages_saveGif { // RPC method 'messages.saveGif' +public: + MTPInputDocument vid; + MTPBool vunsave; + + MTPmessages_saveGif() { + } + MTPmessages_saveGif(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_saveGif) { + read(from, end, cons); + } + MTPmessages_saveGif(const MTPInputDocument &_id, MTPBool _unsave) : vid(_id), vunsave(_unsave) { + } + + uint32 innerLength() const { + return vid.innerLength() + vunsave.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_saveGif; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_saveGif) { + vid.read(from, end); + vunsave.read(from, end); + } + void write(mtpBuffer &to) const { + vid.write(to); + vunsave.write(to); + } + + typedef MTPBool ResponseType; +}; +class MTPmessages_SaveGif : public MTPBoxed { +public: + MTPmessages_SaveGif() { + } + MTPmessages_SaveGif(const MTPmessages_saveGif &v) : MTPBoxed(v) { + } + MTPmessages_SaveGif(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_SaveGif(const MTPInputDocument &_id, MTPBool _unsave) : MTPBoxed(MTPmessages_saveGif(_id, _unsave)) { + } +}; + class MTPupdates_getState { // RPC method 'updates.getState' public: MTPupdates_getState() { @@ -24951,6 +25094,7 @@ inline void MTPmessagesFilter::read(const mtpPrime *&from, const mtpPrime *end, case mtpc_inputMessagesFilterAudio: _type = cons; break; case mtpc_inputMessagesFilterAudioDocuments: _type = cons; break; case mtpc_inputMessagesFilterUrl: _type = cons; break; + case mtpc_inputMessagesFilterGif: _type = cons; break; default: throw mtpErrorUnexpected(cons, "MTPmessagesFilter"); } } @@ -24969,6 +25113,7 @@ inline MTPmessagesFilter::MTPmessagesFilter(mtpTypeId type) : _type(type) { case mtpc_inputMessagesFilterAudio: break; case mtpc_inputMessagesFilterAudioDocuments: break; case mtpc_inputMessagesFilterUrl: break; + case mtpc_inputMessagesFilterGif: break; default: throw mtpErrorBadTypeId(type, "MTPmessagesFilter"); } } @@ -24999,6 +25144,9 @@ inline MTPmessagesFilter MTP_inputMessagesFilterAudioDocuments() { inline MTPmessagesFilter MTP_inputMessagesFilterUrl() { return MTPmessagesFilter(mtpc_inputMessagesFilterUrl); } +inline MTPmessagesFilter MTP_inputMessagesFilterGif() { + return MTPmessagesFilter(mtpc_inputMessagesFilterGif); +} inline uint32 MTPupdate::innerLength() const { switch (_type) { @@ -25426,6 +25574,7 @@ inline void MTPupdate::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeI v.vorder.read(from, end); } break; case mtpc_updateStickerSets: _type = cons; break; + case mtpc_updateSavedGifs: _type = cons; break; default: throw mtpErrorUnexpected(cons, "MTPupdate"); } } @@ -25693,6 +25842,7 @@ inline MTPupdate::MTPupdate(mtpTypeId type) : mtpDataOwner(0), _type(type) { case mtpc_updateNewStickerSet: setData(new MTPDupdateNewStickerSet()); break; case mtpc_updateStickerSetsOrder: setData(new MTPDupdateStickerSetsOrder()); break; case mtpc_updateStickerSets: break; + case mtpc_updateSavedGifs: break; default: throw mtpErrorBadTypeId(type, "MTPupdate"); } } @@ -25894,6 +26044,9 @@ inline MTPupdate MTP_updateStickerSetsOrder(const MTPVector &_order) { inline MTPupdate MTP_updateStickerSets() { return MTPupdate(mtpc_updateStickerSets); } +inline MTPupdate MTP_updateSavedGifs() { + return MTPupdate(mtpc_updateSavedGifs); +} inline MTPupdates_state::MTPupdates_state() : mtpDataOwner(new MTPDupdates_state()) { } @@ -26419,7 +26572,7 @@ inline MTPconfig::MTPconfig() : mtpDataOwner(new MTPDconfig()) { inline uint32 MTPconfig::innerLength() const { const MTPDconfig &v(c_config()); - return v.vdate.innerLength() + v.vexpires.innerLength() + v.vtest_mode.innerLength() + v.vthis_dc.innerLength() + v.vdc_options.innerLength() + v.vchat_size_max.innerLength() + v.vmegagroup_size_max.innerLength() + v.vforwarded_count_max.innerLength() + v.vonline_update_period_ms.innerLength() + v.voffline_blur_timeout_ms.innerLength() + v.voffline_idle_timeout_ms.innerLength() + v.vonline_cloud_timeout_ms.innerLength() + v.vnotify_cloud_delay_ms.innerLength() + v.vnotify_default_delay_ms.innerLength() + v.vchat_big_size.innerLength() + v.vpush_chat_period_ms.innerLength() + v.vpush_chat_limit.innerLength() + v.vdisabled_features.innerLength(); + return v.vdate.innerLength() + v.vexpires.innerLength() + v.vtest_mode.innerLength() + v.vthis_dc.innerLength() + v.vdc_options.innerLength() + v.vchat_size_max.innerLength() + v.vmegagroup_size_max.innerLength() + v.vforwarded_count_max.innerLength() + v.vonline_update_period_ms.innerLength() + v.voffline_blur_timeout_ms.innerLength() + v.voffline_idle_timeout_ms.innerLength() + v.vonline_cloud_timeout_ms.innerLength() + v.vnotify_cloud_delay_ms.innerLength() + v.vnotify_default_delay_ms.innerLength() + v.vchat_big_size.innerLength() + v.vpush_chat_period_ms.innerLength() + v.vpush_chat_limit.innerLength() + v.vsaved_gifs_limit.innerLength() + v.vdisabled_features.innerLength(); } inline mtpTypeId MTPconfig::type() const { return mtpc_config; @@ -26446,6 +26599,7 @@ inline void MTPconfig::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeI v.vchat_big_size.read(from, end); v.vpush_chat_period_ms.read(from, end); v.vpush_chat_limit.read(from, end); + v.vsaved_gifs_limit.read(from, end); v.vdisabled_features.read(from, end); } inline void MTPconfig::write(mtpBuffer &to) const { @@ -26467,12 +26621,13 @@ inline void MTPconfig::write(mtpBuffer &to) const { v.vchat_big_size.write(to); v.vpush_chat_period_ms.write(to); v.vpush_chat_limit.write(to); + v.vsaved_gifs_limit.write(to); v.vdisabled_features.write(to); } inline MTPconfig::MTPconfig(MTPDconfig *_data) : mtpDataOwner(_data) { } -inline MTPconfig MTP_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, const MTPVector &_disabled_features) { - return MTPconfig(new MTPDconfig(_date, _expires, _test_mode, _this_dc, _dc_options, _chat_size_max, _megagroup_size_max, _forwarded_count_max, _online_update_period_ms, _offline_blur_timeout_ms, _offline_idle_timeout_ms, _online_cloud_timeout_ms, _notify_cloud_delay_ms, _notify_default_delay_ms, _chat_big_size, _push_chat_period_ms, _push_chat_limit, _disabled_features)); +inline MTPconfig MTP_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, const MTPVector &_disabled_features) { + return MTPconfig(new MTPDconfig(_date, _expires, _test_mode, _this_dc, _dc_options, _chat_size_max, _megagroup_size_max, _forwarded_count_max, _online_update_period_ms, _offline_blur_timeout_ms, _offline_idle_timeout_ms, _online_cloud_timeout_ms, _notify_cloud_delay_ms, _notify_default_delay_ms, _chat_big_size, _push_chat_period_ms, _push_chat_limit, _saved_gifs_limit, _disabled_features)); } inline MTPnearestDc::MTPnearestDc() : mtpDataOwner(new MTPDnearestDc()) { @@ -30193,6 +30348,57 @@ inline MTPmessages_foundGifs MTP_messages_foundGifs(MTPint _next_offset, const M return MTPmessages_foundGifs(new MTPDmessages_foundGifs(_next_offset, _results)); } +inline uint32 MTPmessages_savedGifs::innerLength() const { + switch (_type) { + case mtpc_messages_savedGifs: { + const MTPDmessages_savedGifs &v(c_messages_savedGifs()); + return v.vhash.innerLength() + v.vgifs.innerLength(); + } + } + return 0; +} +inline mtpTypeId MTPmessages_savedGifs::type() const { + if (!_type) throw mtpErrorUninitialized(); + return _type; +} +inline void MTPmessages_savedGifs::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != _type) setData(0); + switch (cons) { + case mtpc_messages_savedGifsNotModified: _type = cons; break; + case mtpc_messages_savedGifs: _type = cons; { + if (!data) setData(new MTPDmessages_savedGifs()); + MTPDmessages_savedGifs &v(_messages_savedGifs()); + v.vhash.read(from, end); + v.vgifs.read(from, end); + } break; + default: throw mtpErrorUnexpected(cons, "MTPmessages_savedGifs"); + } +} +inline void MTPmessages_savedGifs::write(mtpBuffer &to) const { + switch (_type) { + case mtpc_messages_savedGifs: { + const MTPDmessages_savedGifs &v(c_messages_savedGifs()); + v.vhash.write(to); + v.vgifs.write(to); + } break; + } +} +inline MTPmessages_savedGifs::MTPmessages_savedGifs(mtpTypeId type) : mtpDataOwner(0), _type(type) { + switch (type) { + case mtpc_messages_savedGifsNotModified: break; + case mtpc_messages_savedGifs: setData(new MTPDmessages_savedGifs()); break; + default: throw mtpErrorBadTypeId(type, "MTPmessages_savedGifs"); + } +} +inline MTPmessages_savedGifs::MTPmessages_savedGifs(MTPDmessages_savedGifs *_data) : mtpDataOwner(_data), _type(mtpc_messages_savedGifs) { +} +inline MTPmessages_savedGifs MTP_messages_savedGifsNotModified() { + return MTPmessages_savedGifs(mtpc_messages_savedGifsNotModified); +} +inline MTPmessages_savedGifs MTP_messages_savedGifs(MTPint _hash, const MTPVector &_gifs) { + return MTPmessages_savedGifs(new MTPDmessages_savedGifs(_hash, _gifs)); +} + // Human-readable text serialization #if (defined _DEBUG || defined _WITH_DEBUG) diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index 441edfd1e..90ae62dd7 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -357,6 +357,7 @@ inputMessagesFilterDocument#9eddf188 = MessagesFilter; inputMessagesFilterAudio#cfc87522 = MessagesFilter; inputMessagesFilterAudioDocuments#5afbf764 = MessagesFilter; inputMessagesFilterUrl#7ef0dd87 = MessagesFilter; +inputMessagesFilterGif#ffc86587 = MessagesFilter; updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update; updateMessageID#4e90bfd6 id:int random_id:long = Update; @@ -398,6 +399,7 @@ updateChatParticipantAdmin#b6901959 chat_id:int user_id:int is_admin:Bool versio updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update; updateStickerSetsOrder#f0dfb451 order:Vector = Update; updateStickerSets#43ae3dec = Update; +updateSavedGifs#9375341e = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -422,7 +424,7 @@ upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File; dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true id:int ip_address:string port:int = DcOption; -config#6cb6e65e date:int expires:int test_mode:Bool this_dc:int dc_options:Vector chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int disabled_features:Vector = Config; +config#6bbc5f8 date:int expires:int test_mode:Bool this_dc:int dc_options:Vector chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int disabled_features:Vector = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; @@ -641,6 +643,9 @@ foundGif#d579cccb webpage:WebPage = FoundGif; messages.foundGifs#450a1c0a next_offset:int results:Vector = messages.FoundGifs; +messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs; +messages.savedGifs#2e0709a5 hash:int gifs:Vector = messages.SavedGifs; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -759,6 +764,8 @@ messages.searchGlobal#9e3cacb0 q:string offset_date:int offset_peer:InputPeer of messages.reorderStickerSets#9fcfbc30 order:Vector = Bool; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; messages.searchGifs#bf9a776b q:string offset:int = messages.FoundGifs; +messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs; +messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#a041495 pts:int date:int qts:int = updates.Difference; diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 7189372f9..d2889f7c6 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -345,7 +345,7 @@ void OverviewInner::moveToNextItem(MsgId &msgId, int32 &index, MsgId upTo, int32 } } -void OverviewInner::redrawItem(MsgId itemId, int32 itemIndex) { +void OverviewInner::repaintItem(MsgId itemId, int32 itemIndex) { fixItemIndex(itemIndex, itemId); if (itemIndex >= 0) { if (_type == OverviewPhotos || _type == OverviewVideos) { @@ -476,10 +476,10 @@ void OverviewInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton but if (button != Qt::LeftButton) return; if (textlnkDown() != textlnkOver()) { - redrawItem(App::pressedLinkItem()); + repaintItem(App::pressedLinkItem()); textlnkDown(textlnkOver()); App::pressedLinkItem(App::hoveredLinkItem()); - redrawItem(App::pressedLinkItem()); + repaintItem(App::pressedLinkItem()); } _dragAction = NoDrag; @@ -508,12 +508,12 @@ void OverviewInner::dragActionStart(const QPoint &screenPos, Qt::MouseButton but uint32 selStatus = (_dragSymbol << 16) | _dragSymbol; if (selStatus != FullSelection && (_selected.isEmpty() || _selected.cbegin().value() != FullSelection)) { if (!_selected.isEmpty()) { - redrawItem(_selected.cbegin().key(), -1); + repaintItem(_selected.cbegin().key(), -1); _selected.clear(); } _selected.insert(_dragItem, selStatus); _dragAction = Selecting; - redrawItem(_dragItem, _dragItemIndex); + repaintItem(_dragItem, _dragItemIndex); _overview->updateTopBarSelection(); } else { _dragAction = PrepareSelect; @@ -552,7 +552,7 @@ void OverviewInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton bu } } if (textlnkDown()) { - redrawItem(App::pressedLinkItem()); + repaintItem(App::pressedLinkItem()); textlnkDown(TextLinkPtr()); App::pressedLinkItem(0); if (!textlnkOver() && _cursor != style::cur_default) { @@ -577,16 +577,16 @@ void OverviewInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton bu } else { _selected.erase(i); } - redrawItem(_dragItem, _dragItemIndex); + repaintItem(_dragItem, _dragItemIndex); } else if (_dragAction == PrepareDrag && !needClick && !_dragWasInactive && button != Qt::RightButton) { SelectedItems::iterator i = _selected.find(_dragItem); if (i != _selected.cend() && i.value() == FullSelection) { _selected.erase(i); - redrawItem(_dragItem, _dragItemIndex); + repaintItem(_dragItem, _dragItemIndex); } else if (i == _selected.cend() && itemMsgId(_dragItem) > 0 && !_selected.isEmpty() && _selected.cbegin().value() == FullSelection) { if (_selected.size() < MaxSelectedItems) { _selected.insert(_dragItem, FullSelection); - redrawItem(_dragItem, _dragItemIndex); + repaintItem(_dragItem, _dragItemIndex); } } else { _selected.clear(); @@ -994,7 +994,7 @@ void OverviewInner::onUpdateSelected() { fixItemIndex(itemIndex, itemId); if (itemIndex >= 0) { _items.at(itemIndex)->linkOut(textlnkOver()); - redrawItem(itemId, itemIndex); + repaintItem(itemId, itemIndex); } } } @@ -1004,7 +1004,7 @@ void OverviewInner::onUpdateSelected() { if (textlnkOver()) { if (item && index >= 0) { _items.at(index)->linkOver(textlnkOver()); - redrawItem(complexMsgId(item), index); + repaintItem(complexMsgId(item), index); } } } else { @@ -1012,8 +1012,8 @@ void OverviewInner::onUpdateSelected() { } if (_mousedItem != oldMousedItem) { lnkChanged = true; - if (oldMousedItem) redrawItem(oldMousedItem, oldMousedItemIndex); - if (item) redrawItem(item); + if (oldMousedItem) repaintItem(oldMousedItem, oldMousedItemIndex); + if (item) repaintItem(item); QToolTip::hideText(); } if (_cursorState == HistoryInDateCursorState && cursorState != HistoryInDateCursorState) { @@ -1199,11 +1199,11 @@ void OverviewInner::enterEvent(QEvent *e) { void OverviewInner::leaveEvent(QEvent *e) { if (_selectedMsgId) { - redrawItem(_selectedMsgId, -1); + repaintItem(_selectedMsgId, -1); _selectedMsgId = 0; } if (textlnkOver()) { - redrawItem(App::hoveredLinkItem()); + repaintItem(App::hoveredLinkItem()); textlnkOver(TextLinkPtr()); App::hoveredLinkItem(0); if (!textlnkDown() && _cursor != style::cur_default) { @@ -1223,8 +1223,8 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (_menu) { _menu->deleteLater(); _menu = 0; - redrawItem(App::contextItem()); - if (_selectedMsgId) redrawItem(_selectedMsgId, -1); + repaintItem(App::contextItem()); + if (_selectedMsgId) repaintItem(_selectedMsgId, -1); } if (e->reason() == QContextMenuEvent::Mouse) { dragActionUpdate(e->globalPos()); @@ -1301,8 +1301,8 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } App::contextItem(App::hoveredLinkItem()); - redrawItem(App::contextItem()); - if (_selectedMsgId) redrawItem(_selectedMsgId, -1); + repaintItem(App::contextItem()); + if (_selectedMsgId) repaintItem(_selectedMsgId, -1); } else if (!ignoreMousedItem && App::mousedItem() && App::mousedItem()->channelId() == itemChannel(_mousedItem) && App::mousedItem()->id == itemMsgId(_mousedItem)) { _menu = new PopupMenu(); if ((_contextMenuLnk && dynamic_cast(_contextMenuLnk.data()))) { @@ -1340,8 +1340,8 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } } App::contextItem(App::mousedItem()); - redrawItem(App::contextItem()); - if (_selectedMsgId) redrawItem(_selectedMsgId, -1); + repaintItem(App::contextItem()); + if (_selectedMsgId) repaintItem(_selectedMsgId, -1); } if (_menu) { connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*))); @@ -1627,8 +1627,8 @@ void OverviewInner::onMenuDestroy(QObject *obj) { if (_menu == obj) { _menu = 0; dragActionUpdate(QCursor::pos()); - redrawItem(App::contextItem()); - if (_selectedMsgId) redrawItem(_selectedMsgId, -1); + repaintItem(App::contextItem()); + if (_selectedMsgId) repaintItem(_selectedMsgId, -1); } } @@ -1719,7 +1719,7 @@ void OverviewInner::mediaOverviewUpdated() { allGood = false; } HistoryItem *item = App::histItemById(itemChannel(msgid), itemMsgId(msgid)); - LayoutMediaItem *layout = getItemLayout(item); + LayoutMediaItem *layout = layoutPrepare(item); if (!layout) continue; setLayoutItem(index, layout, 0); @@ -1765,13 +1765,13 @@ void OverviewInner::mediaOverviewUpdated() { allGood = false; } HistoryItem *item = App::histItemById(itemChannel(msgid), itemMsgId(msgid)); - LayoutMediaItem *layout = getItemLayout(item); + LayoutMediaItem *layout = layoutPrepare(item); if (!layout) continue; if (withDates) { QDate date = item->date.date(); if (!index || (index > 0 && (dateEveryMonth ? (date.month() != prevDate.month() || date.year() != prevDate.year()) : (date != prevDate)))) { - top += setLayoutItem(index, getDateLayout(date, dateEveryMonth), top); + top += setLayoutItem(index, layoutPrepare(date, dateEveryMonth), top); ++index; prevDate = date; } @@ -1851,7 +1851,7 @@ void OverviewInner::itemRemoved(HistoryItem *item) { update(); } -void OverviewInner::redrawItem(const HistoryItem *msg) { +void OverviewInner::repaintItem(const HistoryItem *msg) { if (!msg) return; History *history = (msg->history() == _history) ? _history : (msg->history() == _migrated ? _migrated : 0); @@ -1921,7 +1921,7 @@ void OverviewInner::recountMargins() { } } -LayoutMediaItem *OverviewInner::getItemLayout(HistoryItem *item) { +LayoutMediaItem *OverviewInner::layoutPrepare(HistoryItem *item) { if (!item) return 0; LayoutItems::const_iterator i = _layoutItems.cend(); @@ -1963,7 +1963,7 @@ LayoutMediaItem *OverviewInner::getItemLayout(HistoryItem *item) { return (i == _layoutItems.cend()) ? 0 : i.value(); } -LayoutItem *OverviewInner::getDateLayout(const QDate &date, bool month) { +LayoutItem *OverviewInner::layoutPrepare(const QDate &date, bool month) { int32 key = date.year() * 100 + date.month(); if (!month) key = key * 100 + date.day(); LayoutDates::const_iterator i = _layoutDates.constFind(key); @@ -2288,9 +2288,9 @@ void OverviewWidget::changingMsgId(HistoryItem *row, MsgId newId) { } } -void OverviewWidget::ui_redrawHistoryItem(const HistoryItem *item) { +void OverviewWidget::ui_repaintHistoryItem(const HistoryItem *item) { if (peer() == item->history()->peer || migratePeer() == item->history()->peer) { - _inner.redrawItem(item); + _inner.repaintItem(item); } } diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index b2a0ef107..62ea9d915 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -69,7 +69,7 @@ public: void mediaOverviewUpdated(); void changingMsgId(HistoryItem *row, MsgId newId); - void redrawItem(const HistoryItem *msg); + void repaintItem(const HistoryItem *msg); void itemRemoved(HistoryItem *item); void getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const; @@ -124,7 +124,7 @@ private: void updateDragSelection(MsgId dragSelFrom, int32 dragSelFromIndex, MsgId dragSelTo, int32 dragSelToIndex, bool dragSelecting); - void redrawItem(MsgId itemId, int32 itemIndex); + void repaintItem(MsgId itemId, int32 itemIndex); void touchResetSpeed(); void touchUpdateSpeed(); @@ -157,8 +157,8 @@ private: LayoutItems _layoutItems; typedef QMap LayoutDates; LayoutDates _layoutDates; - LayoutMediaItem *getItemLayout(HistoryItem *item); - LayoutItem *getDateLayout(const QDate &date, bool month); + LayoutMediaItem *layoutPrepare(HistoryItem *item); + LayoutItem *layoutPrepare(const QDate &date, bool month); int32 setLayoutItem(int32 index, LayoutItem *item, int32 top); FlatInput _search; @@ -299,7 +299,7 @@ public: resizeEvent(0); } - void ui_redrawHistoryItem(const HistoryItem *item); + void ui_repaintHistoryItem(const HistoryItem *item); void notify_historyItemLayoutChanged(const HistoryItem *item); diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index 14eb6d2b9..c17b1c0e1 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -1832,7 +1832,9 @@ int32 ProfileWidget::lastScrollTop() const { void ProfileWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back, int32 lastScrollTop) { if (App::app()) App::app()->mtpPause(); -// App::stopGifItems(); + if (!cAutoPlayGif()) { + App::stopGifItems(); + } (back ? _cacheOver : _cacheUnder) = bgAnimCache; (back ? _cacheTopBarOver : _cacheTopBarUnder) = bgAnimTopBarCache; diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index a3f59cff3..6fa99541b 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -108,15 +108,17 @@ RecentEmojiPack gRecentEmojis; RecentEmojisPreload gRecentEmojisPreload; EmojiColorVariants gEmojiVariants; -int32 gStickersHash = 0; - RecentStickerPreload gRecentStickersPreload; RecentStickerPack gRecentStickers; StickerSets gStickerSets; StickerSetsOrder gStickerSetsOrder; - uint64 gLastStickersUpdate = 0; +SavedGifs gSavedGifs; +uint64 gLastSavedGifsUpdate = 0; +bool gShowingSavedGifs = false; +int32 gSavedGifsLimit = 100; + RecentHashtagPack gRecentWriteHashtags, gRecentSearchHashtags; bool gPasswordRecovered = false; @@ -174,6 +176,7 @@ ReportSpamStatuses gReportSpamStatuses; int32 gAutoDownloadPhoto = 0; // all auto download int32 gAutoDownloadAudio = 0; int32 gAutoDownloadGif = 0; +bool gAutoPlayGif = true; void settingsParseArgs(int argc, char *argv[]) { #ifdef Q_OS_MAC diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index 6fc51ac85..a5aac46f1 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -197,7 +197,6 @@ RecentEmojiPack &cGetRecentEmojis(); class DocumentData; typedef QVector StickerPack; -DeclareSetting(int32, StickersHash); typedef QList > RecentStickerPackOld; typedef QVector > RecentStickerPreload; @@ -207,8 +206,6 @@ DeclareRefSetting(RecentStickerPack, RecentStickers); RecentStickerPack &cGetRecentStickers(); -DeclareSetting(uint64, LastStickersUpdate); - static const uint64 DefaultStickerSetId = 0; // for backward compatibility static const uint64 CustomStickerSetId = 0xFFFFFFFFFFFFFFFFULL, RecentStickerSetId = 0xFFFFFFFFFFFFFFFEULL; static const uint64 NoneStickerSetId = 0xFFFFFFFFFFFFFFFDULL; // for emoji/stickers panel @@ -224,6 +221,13 @@ typedef QMap StickerSets; DeclareRefSetting(StickerSets, StickerSets); typedef QList StickerSetsOrder; DeclareRefSetting(StickerSetsOrder, StickerSetsOrder); +DeclareSetting(uint64, LastStickersUpdate); + +typedef QVector SavedGifs; +DeclareRefSetting(SavedGifs, SavedGifs); +DeclareSetting(uint64, LastSavedGifsUpdate); +DeclareSetting(bool, ShowingSavedGifs); +DeclareSetting(int32, SavedGifsLimit); typedef QList > RecentHashtagPack; DeclareSetting(RecentHashtagPack, RecentWriteHashtags); @@ -335,5 +339,6 @@ enum DBIAutoDownloadFlags { DeclareSetting(int32, AutoDownloadPhoto); DeclareSetting(int32, AutoDownloadAudio); DeclareSetting(int32, AutoDownloadGif); +DeclareSetting(bool, AutoPlayGif); void settingsParseArgs(int argc, char *argv[]); diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index fc22d4f2d..e25e9fa31 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -578,6 +578,10 @@ void PhotoData::automaticLoad(const HistoryItem *item) { full->automaticLoad(item); } +void PhotoData::automaticLoadSettingsChanged() { + full->automaticLoadSettingsChanged(); +} + void PhotoData::download() { full->loadEvenCancelled(); notifyLayoutChanged(); @@ -1158,6 +1162,11 @@ void AudioData::automaticLoad(const HistoryItem *item) { } } +void AudioData::automaticLoadSettingsChanged() { + if (loaded() || status != FileReady || !saveToCache() || _loader != CancelledFileLoader) return; + _loader = 0; +} + void AudioData::performActionOnLoad() { if (_actionOnLoad == ActionOnLoadNone) return; @@ -1332,6 +1341,8 @@ void DocumentOpenLink::doOpen(DocumentData *data, ActionOnLoad action) { HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : 0); + MTP::send(MTPmessages_SendMedia(MTP_int(0), item->history()->peer->input, MTP_int(0), MTP_inputMediaDocument(MTP_inputDocument(MTP_long(data->id), MTP_long(data->access))), MTP_long(MTP::nonce()), MTPnullMarkup)); + bool playMusic = data->song() && audioPlayer() && item; bool playAnimation = data->isAnimation() && item && item->getMedia(); const FileLocation &location(data->location(true)); @@ -1556,18 +1567,27 @@ void DocumentData::automaticLoad(const HistoryItem *item) { if (saveToCache() && _loader != CancelledFileLoader) { if (type == StickerDocument) { save(QString(), _actionOnLoad, _actionOnLoadMsgId); - } else if (isAnimation() && item) { + } else if (isAnimation()) { bool loadFromCloud = false; - if (item->history()->peer->isUser()) { - loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate); - } else { - loadFromCloud = !(cAutoDownloadGif() & dbiadNoGroups); + if (item) { + if (item->history()->peer->isUser()) { + loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate); + } else { + loadFromCloud = !(cAutoDownloadGif() & dbiadNoGroups); + } + } else { // if load at least anywhere + loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate) || !(cAutoDownloadGif() & dbiadNoGroups); } save(QString(), _actionOnLoad, _actionOnLoadMsgId, loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); } } } +void DocumentData::automaticLoadSettingsChanged() { + if (loaded() || status != FileReady || !isAnimation() || !saveToCache() || _loader != CancelledFileLoader) return; + _loader = 0; +} + void DocumentData::performActionOnLoad() { if (_actionOnLoad == ActionOnLoadNone) return; diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index ba4f1a533..0efbce5d8 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -733,6 +733,7 @@ public: PhotoData(const PhotoId &id, const uint64 &access = 0, int32 date = 0, const ImagePtr &thumb = ImagePtr(), const ImagePtr &medium = ImagePtr(), const ImagePtr &full = ImagePtr()); void automaticLoad(const HistoryItem *item); + void automaticLoadSettingsChanged(); void download(); bool loaded() const; @@ -823,6 +824,8 @@ public: void automaticLoad(const HistoryItem *item) { } + void automaticLoadSettingsChanged() { + } bool loaded(bool check = false) const; bool loading() const; @@ -919,6 +922,7 @@ public: AudioData(const AudioId &id, const uint64 &access = 0, int32 date = 0, const QString &mime = QString(), int32 duration = 0, int32 dc = 0, int32 size = 0); void automaticLoad(const HistoryItem *item); // auto load voice message + void automaticLoadSettingsChanged(); bool loaded(bool check = false) const; bool loading() const; @@ -1079,6 +1083,7 @@ public: void setattributes(const QVector &attributes); void automaticLoad(const HistoryItem *item); // auto load sticker or video + void automaticLoadSettingsChanged(); bool loaded(bool check = false) const; bool loading() const; diff --git a/Telegram/SourceFiles/types.h b/Telegram/SourceFiles/types.h index 5e533830c..53579ab76 100644 --- a/Telegram/SourceFiles/types.h +++ b/Telegram/SourceFiles/types.h @@ -294,6 +294,9 @@ enum DataBlockId { dbiMaxMegaGroupCount = 0x32, dbiDownloadPath = 0x33, dbiAutoDownload = 0x34, + dbiSavedGifsLimit = 0x35, + dbiShowingSavedGifs = 0x36, + dbiAutoPlay = 0x37, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, @@ -469,3 +472,69 @@ static int32 FullArcLength = 360 * 16; static int32 QuarterArcLength = (FullArcLength / 4); static int32 MinArcLength = (FullArcLength / 360); static int32 AlmostFullArcLength = (FullArcLength - MinArcLength); + +template +class Function { +public: + + virtual ReturnType call() = 0; + virtual ~Function() { + } + +}; + +template +class ObjectFunction : public Function { +public: + typedef ReturnType (Object::*Method)(); + + ObjectFunction(Object *obj, Method method) : _obj(obj), _method(method) { + } + + virtual ReturnType call() { + return (_obj->*_method)(); + } + +private: + Object *_obj; + Method _method; + +}; + +template +Function *func(Object *obj, typename ReturnType (Object::*method)()) { + return new ObjectFunction(obj, method); +} + +template +class Function1 { +public: + + virtual ReturnType call(ArgumentType1 arg1) = 0; + virtual ~Function1() { + } + +}; + +template +class ObjectFunction1 : public Function1 { +public: + typedef ReturnType (Object::*Method)(ArgumentType1); + + ObjectFunction1(Object *obj, Method method) : _obj(obj), _method(method) { + } + + virtual ReturnType call(ArgumentType1 arg1) { + return (_obj->*_method)(arg1); + } + +private: + Object *_obj; + Method _method; + +}; + +template +Function1 *func(Object *obj, ReturnType (Object::*method)(ArgumentType1)) { + return new ObjectFunction1(obj, method); +} diff --git a/Telegram/SourceFiles/window.cpp b/Telegram/SourceFiles/window.cpp index b75fdc693..bbe853042 100644 --- a/Telegram/SourceFiles/window.cpp +++ b/Telegram/SourceFiles/window.cpp @@ -647,8 +647,6 @@ void Window::sendServiceHistoryRequest() { } void Window::setupMain(bool anim, const MTPUser *self) { - Local::readStickers(); - QPixmap bg = anim ? grabInner() : QPixmap(); clearWidgets(); main = new MainWidget(this); @@ -797,9 +795,9 @@ void Window::showDocument(DocumentData *doc, HistoryItem *item) { _mediaView->setFocus(); } -void Window::ui_clipRedraw(ClipReader *reader) { +void Window::ui_clipRepaint(ClipReader *reader) { if (_mediaView && !_mediaView->isHidden()) { - _mediaView->ui_clipRedraw(reader); + _mediaView->ui_clipRepaint(reader); } } diff --git a/Telegram/SourceFiles/window.h b/Telegram/SourceFiles/window.h index 04e99717e..86ac00dbf 100644 --- a/Telegram/SourceFiles/window.h +++ b/Telegram/SourceFiles/window.h @@ -237,7 +237,7 @@ public: return contentOverlapped(QRect(w->mapToGlobal(r.boundingRect().topLeft()), r.boundingRect().size())); } - void ui_clipRedraw(ClipReader *reader); + void ui_clipRepaint(ClipReader *reader); void ui_showLayer(LayeredWidget *box, ShowLayerOptions options); bool ui_isLayerShown(); bool ui_isMediaViewShown();