From 92858dc7d3f1c83bb6b7b37fa6229d3b57afec7d Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 19 May 2015 18:46:45 +0300 Subject: [PATCH] sticker packs done --- Telegram/Resources/lang.strings | 12 +- Telegram/Resources/style.txt | 98 +- Telegram/Resources/style_classes.txt | 3 +- Telegram/SourceFiles/apiwrap.cpp | 1 - Telegram/SourceFiles/app.cpp | 88 +- Telegram/SourceFiles/app.h | 5 +- Telegram/SourceFiles/application.cpp | 4 +- Telegram/SourceFiles/art/sprite.png | Bin 167124 -> 168226 bytes Telegram/SourceFiles/art/sprite_200x.png | Bin 217545 -> 219885 bytes Telegram/SourceFiles/boxes/stickersetbox.cpp | 275 ++++ Telegram/SourceFiles/boxes/stickersetbox.h | 100 ++ Telegram/SourceFiles/config.h | 13 +- Telegram/SourceFiles/dialogswidget.cpp | 2 +- Telegram/SourceFiles/dropdown.cpp | 1139 ++++++++++++----- Telegram/SourceFiles/dropdown.h | 121 +- Telegram/SourceFiles/fileuploader.cpp | 2 +- Telegram/SourceFiles/gui/images.cpp | 25 +- Telegram/SourceFiles/gui/images.h | 112 +- Telegram/SourceFiles/gui/scrollarea.cpp | 30 +- Telegram/SourceFiles/gui/text.cpp | 3 + Telegram/SourceFiles/historywidget.cpp | 285 +++-- Telegram/SourceFiles/historywidget.h | 4 +- Telegram/SourceFiles/localstorage.cpp | 358 ++++-- Telegram/SourceFiles/localstorage.h | 8 +- Telegram/SourceFiles/mainwidget.cpp | 85 +- Telegram/SourceFiles/mainwidget.h | 8 + .../SourceFiles/mtproto/mtpFileLoader.cpp | 26 +- Telegram/SourceFiles/mtproto/mtpFileLoader.h | 6 +- Telegram/SourceFiles/profilewidget.cpp | 3 + Telegram/SourceFiles/settings.cpp | 24 +- Telegram/SourceFiles/settings.h | 24 +- Telegram/SourceFiles/structs.cpp | 12 +- Telegram/SourceFiles/structs.h | 7 +- Telegram/SourceFiles/types.h | 79 +- Telegram/SourceFiles/window.cpp | 5 +- Telegram/SourceFiles/window.h | 5 +- Telegram/Telegram.rc | Bin 5540 -> 5540 bytes Telegram/Telegram.vcxproj | 27 + Telegram/Telegram.vcxproj.filters | 15 + 39 files changed, 2312 insertions(+), 702 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/stickersetbox.cpp create mode 100644 Telegram/SourceFiles/boxes/stickersetbox.h diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings index 15d48f26c..eeecb83d9 100644 --- a/Telegram/Resources/lang.strings +++ b/Telegram/Resources/lang.strings @@ -428,7 +428,16 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_emoji_category5" = "Activity"; "lng_emoji_category6" = "Travel & Places"; "lng_emoji_category7" = "Objects & Symbols"; -"lng_emoji_category8" = "Stickers"; + +"lng_switch_stickers" = "Stickers"; +"lng_switch_emoji" = "Emoji"; + +"lng_custom_stickers" = "Custom stickers"; +"lng_stickers_remove_pack" = "Remove «{sticker_pack}»?"; +"lng_stickers_add_pack" = "Add {count:_not_used_|# Sticker|# Stickers}"; +"lng_stickers_share_pack" = "Share Stickers"; +"lng_stickers_not_found" = "Sticker pack not found."; +"lng_stickers_copied" = "Sticker pack link copied to clipboard."; "lng_in_dlg_photo" = "Photo"; "lng_in_dlg_video" = "Video"; @@ -481,6 +490,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_context_save_video" = "Save Video As.."; "lng_context_open_audio" = "Open Audio"; "lng_context_save_audio" = "Save Audio As.."; +"lng_context_pack_info" = "Pack Info"; "lng_context_open_file" = "Open File"; "lng_context_save_file" = "Save File As.."; "lng_context_forward_file" = "Forward File"; diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index eecbc9d64..5627a3c56 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -308,7 +308,8 @@ scrollDef: flatScroll { width: 10px; minHeight: 20px; deltax: 3px; - deltay: 3px; + deltat: 3px; + deltab: 3px; topsh: 2px; bottomsh: 2px; @@ -983,12 +984,12 @@ btnAttachPhoto: iconedButton(btnAttachDocument) { } btnAttachEmoji: iconedButton(btnAttachDocument) { overBgColor: white; - icon: sprite(311px, 221px, 20px, 20px); + icon: sprite(363px, 344px, 21px, 22px); iconPos: point(6px, 13px); - downIcon: sprite(311px, 221px, 20px, 20px); + downIcon: sprite(363px, 344px, 21px, 22px); downIconPos: point(6px, 13px); - width: 32px; + width: 33px; } replySkip: 51px; @@ -1020,7 +1021,8 @@ historyScroll: flatScroll(scrollDef) { width: 12px; deltax: 3px; - deltay: 3px; + deltat: 3px; + deltab: 3px; topsh: 0px; bottomsh: -1px; @@ -1452,19 +1454,62 @@ dpiFont2: linkFont; dpiFont3: linkFont; dpiFont4: linkFont; -emojiScroll: flatScroll(scrollDef) { - width: 5px; - deltax: 2px; - deltay: 1px; +newScroll: flatScroll(scrollDef) { + barColor: #3f729734; + bgColor: #214f751a; + barOverColor: #3f729734; + bgOverColor: #214f751a; + + deltax: 5px; + width: 14px; + deltat: 6px; + deltab: 6px; + topsh: 0px; bottomsh: 0px; + + hiding: 0; +} + +stickersMaxHeight: 340px; +stickersAddOrShare: 70px; +btnStickersAdd: flatButton(btnDefNext, btnDefBig) { + width: 180px; + height: 42px; + + textTop: 9px; + overTextTop: 9px; + downTextTop: 10px; + + font: font(17px); + overFont: font(17px); + + bgColor: #15c23c; + overBgColor: #13a835; + downBgColor: #13a835; +} +btnStickersClose: iconedButton(notifyClose) { + iconPos: point(21px, 21px); + downIconPos: point(21px, 22px); + width: 52px; + height: 48px; +} +stickersWidth: 344px; +stickersPadding: 10px; +stickersSize: size(64px, 64px); +stickersScroll: flatScroll(newScroll) { + deltab: 76px; +} + +emojiScroll: flatScroll(newScroll) { + deltat: 48px; } emojiRecent: sprite(0px, 196px, 21px, 22px); emojiRecentOver: sprite(287px, 220px, 21px, 22px); emojiRecentActive: sprite(287px, 242px, 21px, 22px); emojiPeople: sprite(21px, 196px, 21px, 22px); -emojiPeopleOver: sprite(298px, 220px, 21px, 22px); -emojiPeopleActive: sprite(298px, 242px, 21px, 22px); +emojiPeopleOver: sprite(308px, 220px, 21px, 22px); +emojiPeopleActive: sprite(308px, 242px, 21px, 22px); emojiNature: sprite(42px, 196px, 21px, 22px); emojiNatureOver: sprite(245px, 264px, 21px, 22px); emojiNatureActive: sprite(245px, 286px, 21px, 22px); @@ -1483,10 +1528,13 @@ emojiTravelActive: sprite(321px, 366px, 21px, 22px); emojiObjects: sprite(147px, 196px, 21px, 22px); emojiObjectsOver: sprite(342px, 344px, 21px, 22px); emojiObjectsActive: sprite(342px, 366px, 21px, 22px); + +emojiPanCategories: #f7f7f7; + rbEmoji: flatCheckbox { textColor: transparent; - bgColor: transparent; - disColor: transparent; + bgColor: emojiPanCategories; + disColor: emojiPanCategories; width: 36px; height: 46px; @@ -1565,27 +1613,33 @@ rbEmojiObjects: flatCheckbox(rbEmoji) { disImageRect: emojiObjects; chkDisImageRect: emojiObjectsActive; } -emojiPanPadding: margins(5px, 0px, 0px, 5px); +emojiPanPadding: 10px; emojiPanSize: size(39px, 35px); +emojiPanFullSize: size(300px, 321px); emojiPanDuration: 200; -emojiPanHover: #f0f0f0; +emojiPanHover: #f0f4f7; emojiPanRound: 2px; -emojiPanHeader: 25px; +emojiPanHeader: 42px; emojiPanHeaderFont: font(fsize semibold); emojiPanHeaderColor: #999; -emojiPanHeaderLeft: 5px; -emojiPanHeaderTop: 5px; -emojiPanHeaderBg: #fffd; +emojiPanHeaderLeft: 17px; +emojiPanHeaderTop: 12px; +emojiPanHeaderBg: #fffffff2; emojiColorsPadding: 5px; emojiColorsSep: 1px; emojiColorsSepColor: #d5d5d5; -toStickersImg: sprite(); -toEmojiImg: sprite(); +emojiSwitchSkip: 27px; +emojiSwitchImgSkip: 21px; +emojiSwitchStickers: sprite(318px, 328px, 8px, 12px); +emojiSwitchEmoji: sprite(310px, 328px, 8px, 12px); +emojiSwitchColor: #42a8db; + +stickerPanSize: size(55px, 55px); stickerPanRound: 3px; -stickerPanPadding: 2px; +stickerPanPadding: 11px; stickerPanDelete: sprite(123px, 132px, 12px, 12px); stickerPanDeleteOpacity: 0.5; diff --git a/Telegram/Resources/style_classes.txt b/Telegram/Resources/style_classes.txt index 26c39731d..f07d13cef 100644 --- a/Telegram/Resources/style_classes.txt +++ b/Telegram/Resources/style_classes.txt @@ -173,7 +173,8 @@ flatScroll { width: number; minHeight: number; deltax: number; - deltay: number; + deltat: number; + deltab: number; topsh: number; bottomsh: number; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 6d0434ab3..063d75b6b 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -34,7 +34,6 @@ ApiWrap::ApiWrap(QObject *parent) : QObject(parent) { } void ApiWrap::init() { - App::initMedia(); } void ApiWrap::itemRemoved(HistoryItem *item) { diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 81d1a6722..b3088bfd4 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -633,7 +633,7 @@ namespace App { const MTPDphotoSize &d(size.c_photoSize()); if (d.vlocation.type() == mtpc_fileLocation) { const MTPDfileLocation &l(d.vlocation.c_fileLocation()); - return ImagePtr(d.vw.v, d.vh.v, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v, d.vsize.v); + return ImagePtr(StorageImageLocation(d.vw.v, d.vh.v, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v), d.vsize.v); } } break; case mtpc_photoCachedSize: { @@ -642,17 +642,43 @@ namespace App { const MTPDfileLocation &l(d.vlocation.c_fileLocation()); const string &s(d.vbytes.c_string().v); QByteArray bytes(s.data(), s.size()); - return ImagePtr(d.vw.v, d.vh.v, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v, bytes); + return ImagePtr(StorageImageLocation(d.vw.v, d.vh.v, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v), bytes); } else if (d.vlocation.type() == mtpc_fileLocationUnavailable) { const string &s(d.vbytes.c_string().v); QByteArray bytes(s.data(), s.size()); - return ImagePtr(d.vw.v, d.vh.v, 0, 0, 0, 0, bytes); + return ImagePtr(StorageImageLocation(d.vw.v, d.vh.v, 0, 0, 0, 0), bytes); } } break; } return ImagePtr(); } + StorageImageLocation imageLocation(const MTPPhotoSize &size) { + switch (size.type()) { + case mtpc_photoSize: { + const MTPDphotoSize &d(size.c_photoSize()); + if (d.vlocation.type() == mtpc_fileLocation) { + const MTPDfileLocation &l(d.vlocation.c_fileLocation()); + return StorageImageLocation(d.vw.v, d.vh.v, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v); + } + } break; + case mtpc_photoCachedSize: { + const MTPDphotoCachedSize &d(size.c_photoCachedSize()); + if (d.vlocation.type() == mtpc_fileLocation) { + const MTPDfileLocation &l(d.vlocation.c_fileLocation()); + const string &s(d.vbytes.c_string().v); + QByteArray bytes(s.data(), s.size()); + return StorageImageLocation(d.vw.v, d.vh.v, l.vdc_id.v, l.vvolume_id.v, l.vlocal_id.v, l.vsecret.v); + } else if (d.vlocation.type() == mtpc_fileLocationUnavailable) { + const string &s(d.vbytes.c_string().v); + QByteArray bytes(s.data(), s.size()); + return StorageImageLocation(d.vw.v, d.vh.v, 0, 0, 0, 0); + } + } break; + } + return StorageImageLocation(); + } + void feedWereRead(const QVector &msgsIds) { for (QVector::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) { MsgsData::const_iterator j = msgsData.constFind(i->v); @@ -874,7 +900,7 @@ namespace App { switch (document.type()) { case mtpc_document: { const MTPDdocument &d(document.c_document()); - return App::document(d.vid.v, 0, d.vaccess_hash.v, d.vdate.v, d.vattributes.c_vector().v, qs(d.vmime_type), ImagePtr(thumb, "JPG"), d.vdc_id.v, d.vsize.v); + return App::documentSet(d.vid.v, 0, d.vaccess_hash.v, d.vdate.v, d.vattributes.c_vector().v, qs(d.vmime_type), ImagePtr(thumb, "JPG"), d.vdc_id.v, d.vsize.v, StorageImageLocation()); } break; case mtpc_documentEmpty: return App::document(document.c_documentEmpty().vid.v); } @@ -887,14 +913,14 @@ namespace App { return feedDocument(document.c_document(), convert); } break; case mtpc_documentEmpty: { - return App::document(document.c_documentEmpty().vid.v, convert); + return App::documentSet(document.c_documentEmpty().vid.v, convert, 0, 0, QVector(), QString(), ImagePtr(), 0, 0, StorageImageLocation()); } break; } return App::document(0); } DocumentData *feedDocument(const MTPDdocument &document, DocumentData *convert) { - return App::document(document.vid.v, convert, document.vaccess_hash.v, document.vdate.v, document.vattributes.c_vector().v, qs(document.vmime_type), App::image(document.vthumb), document.vdc_id.v, document.vsize.v); + return App::documentSet(document.vid.v, convert, document.vaccess_hash.v, document.vdate.v, document.vattributes.c_vector().v, qs(document.vmime_type), App::image(document.vthumb), document.vdc_id.v, document.vsize.v, App::imageLocation(document.vthumb)); } WebPageData *feedWebPage(const MTPDwebPage &webpage, WebPageData *convert) { @@ -1128,7 +1154,15 @@ namespace App { return result; } - DocumentData *document(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size) { + DocumentData *document(const DocumentId &document) { + DocumentsData::const_iterator i = documentsData.constFind(document); + if (i == documentsData.cend()) { + i = documentsData.insert(document, new DocumentData(document)); + } + return i.value(); + } + + DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation) { if (convert) { if (convert->id != document) { DocumentsData::iterator i = documentsData.find(convert->id); @@ -1146,12 +1180,28 @@ namespace App { convert->thumb = thumb; convert->dc = dc; convert->size = size; - } else if (convert->thumb->isNull() && !thumb->isNull()) { - convert->thumb = thumb; + } else { + if (!thumb->isNull() && (convert->thumb->isNull() || convert->thumb->width() < thumb->width() || convert->thumb->height() < thumb->height())) { + convert->thumb = thumb; + } + if (convert->sticker && !attributes.isEmpty() && (convert->sticker->alt.isEmpty() || convert->sticker->set.type() == mtpc_inputStickerSetEmpty)) { + for (QVector::const_iterator i = attributes.cbegin(), e = attributes.cend(); i != e; ++i) { + if (i->type() == mtpc_documentAttributeSticker) { + const MTPDdocumentAttributeSticker &d(i->c_documentAttributeSticker()); + if (d.valt.c_string().v.length() > 0) { + convert->sticker->alt = qs(d.valt); + convert->sticker->set = d.vstickerset; + } + } + } + } + } + if (convert->sticker && !convert->sticker->loc.dc && thumbLocation.dc) { + convert->sticker->loc = thumbLocation; } if (convert->location.check()) { - Local::writeFileLocation(mediaKey(mtpc_inputDocumentFileLocation, convert->dc, convert->id), convert->location); + Local::writeFileLocation(mediaKey(DocumentFileLocation, convert->dc, convert->id), convert->location); } } DocumentsData::const_iterator i = documentsData.constFind(document); @@ -1161,6 +1211,7 @@ namespace App { result = convert; } else { result = new DocumentData(document, access, date, attributes, mime, thumb, dc, size); + if (result->sticker) result->sticker->loc = thumbLocation; } documentsData.insert(document, result); } else { @@ -1175,19 +1226,23 @@ namespace App { result->dc = dc; result->size = size; } else { - if (result->thumb->isNull() && !thumb->isNull()) { + if (!thumb->isNull() && (result->thumb->isNull() || result->thumb->width() < thumb->width() || result->thumb->height() < thumb->height())) { result->thumb = thumb; } - if (result->sticker && result->sticker->alt.isEmpty()) { + if (result->sticker && !attributes.isEmpty() && (result->sticker->alt.isEmpty() || result->sticker->set.type() == mtpc_inputStickerSetEmpty)) { for (QVector::const_iterator i = attributes.cbegin(), e = attributes.cend(); i != e; ++i) { if (i->type() == mtpc_documentAttributeSticker) { const MTPDdocumentAttributeSticker &d(i->c_documentAttributeSticker()); if (d.valt.c_string().v.length() > 0) { result->sticker->alt = qs(d.valt); + result->sticker->set = d.vstickerset; } } } } + if (result->sticker && !result->sticker->loc.dc && thumbLocation.dc) { + result->sticker->loc = thumbLocation; + } } } } @@ -1464,8 +1519,9 @@ namespace App { if (api()) api()->clearWebPageRequests(); cSetRecentStickers(RecentStickerPack()); cSetStickersHash(QByteArray()); - cSetStickers(AllStickers()); cSetEmojiStickers(EmojiStickersMap()); + cSetStickerSets(StickerSets()); + cSetLastStickersUpdate(0); ::videoItems.clear(); ::audioItems.clear(); ::documentItems.clear(); @@ -1854,6 +1910,12 @@ namespace App { } } + void stickersBox(const QString &name) { + if (App::main()) { + App::main()->stickersBox(MTP_inputStickerSetShortName(MTP_string(name))); + } + } + void openLocalUrl(const QString &url) { if (App::main()) { App::main()->openLocalUrl(url); diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 2644f7026..5dc0ce6a2 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -87,6 +87,7 @@ namespace App { int32 maxMsgId(); ImagePtr image(const MTPPhotoSize &size); + StorageImageLocation imageLocation(const MTPPhotoSize &size); PhotoData *feedPhoto(const MTPPhoto &photo, const PreparedPhotoThumbs &thumbs); PhotoData *feedPhoto(const MTPPhoto &photo, PhotoData *convert = 0); @@ -117,7 +118,8 @@ namespace App { PhotoData *photo(const PhotoId &photo, PhotoData *convert = 0, const uint64 &access = 0, int32 user = 0, int32 date = 0, const ImagePtr &thumb = ImagePtr(), const ImagePtr &medium = ImagePtr(), const ImagePtr &full = ImagePtr()); VideoData *video(const VideoId &video, VideoData *convert = 0, const uint64 &access = 0, int32 user = 0, int32 date = 0, int32 duration = 0, int32 w = 0, int32 h = 0, const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0); AudioData *audio(const AudioId &audio, AudioData *convert = 0, const uint64 &access = 0, int32 user = 0, int32 date = 0, const QString &mime = QString(), int32 duration = 0, int32 dc = 0, int32 size = 0); - DocumentData *document(const DocumentId &document, DocumentData *convert = 0, const uint64 &access = 0, int32 date = 0, const QVector &attributes = QVector(), const QString &mime = QString(), const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0); + DocumentData *document(const DocumentId &document); + DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation); WebPageData *webPage(const WebPageId &webPage, WebPageData *convert = 0, const QString &type = QString(), const QString &url = QString(), const QString &displayUrl = QString(), const QString &siteName = QString(), const QString &title = QString(), const QString &description = QString(), PhotoData *photo = 0, int32 duration = 0, const QString &author = QString(), int32 pendingTill = -2); ImageLinkData *imageLink(const QString &imageLink, ImageLinkType type = InvalidImageLink, const QString &url = QString()); void forgetMedia(); @@ -200,6 +202,7 @@ namespace App { void searchByHashtag(const QString &tag); void openUserByName(const QString &username, bool toProfile = false); void joinGroupByHash(const QString &hash); + void stickersBox(const QString &name); void openLocalUrl(const QString &url); void initBackground(int32 id = DefaultChatBackground, const QImage &p = QImage(), bool nowrite = false); diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 503a54cca..f68a0a51c 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -662,8 +662,8 @@ void Application::checkMapVersion() { psRegisterCustomScheme(); if (Local::oldMapVersion()) { QString versionFeatures; - if (DevChannel && Local::oldMapVersion() < 8012) { - versionFeatures = QString::fromUtf8("\xe2\x80\x94 New emojis support added\n\xe2\x80\x94 Emojis and stickers panel improved").replace('@', qsl("@") + QChar(0x200D)); + if (DevChannel && Local::oldMapVersion() < 8014) { + versionFeatures = QString::fromUtf8("\xe2\x80\x94 Added support for sticker packs\n\xe2\x80\x94 New emoji and sticker panel").replace('@', qsl("@") + QChar(0x200D)); } else if (!DevChannel && Local::oldMapVersion() < 8013) { versionFeatures = lang(lng_new_version_text).trimmed(); } diff --git a/Telegram/SourceFiles/art/sprite.png b/Telegram/SourceFiles/art/sprite.png index 824f523892125d3c65462c6a2e49483c3a897390..efc99191c984425a9228d6be608fcf736e6b7b07 100644 GIT binary patch delta 9820 zcma)i1yohtw*RK<(1?hXh=Oo{Lx*&S5`rRKB8`;NY(9`K5jk`trF2S2Nk~d}i-1VC z)LVY{z5DL?|L=I??!h=`?|s(ZYpxl;-<)$Xl1P}9MfgSpqq$793yVNNn}K>C^UKSLphS33v8D&?HFS~~8fa|A z5`)k2j-wb{m~}HX8KW4}ad4`K0j1mJl`)@4s1#Yu&4LUzCih!7dg@YVo9jn|#htIF zr;1HN#_%zG8}|Mq%xcK|O5u2>g_i zAEynjyQY8p`vrqI(PDc_Au^dJ^|A7LdQTa&_+gBpC z`{;;;%9N?$lQL?U_1C+=Nd6?~oG^E>yt*NGczuq(%Oxf)P2xjl<|1iPQ4u^*^>I)m!D2UtedUiF*PivyZR3K-TCyT^;cu6lJ=^5J%P8wJBl`C*wh%) z>+9>SIwEd#n|DTyPUq(3=Kh}XIiAhTQ%fy;psrp5`Polb8z%Bvgyj_>&(F_ujEszy zTH)4Kqf|9R_BdY8Mx)w6uj5&_bXZEtZ)u?=AkJ_z+1k#IYK@iOb$4&i49aL|Na^1v zyNg17o62uP^-_wTMl>}w9jvCqM%O4PC`^@s`D>k)JC3jR;+wt;|2C}D+dLyUv+;Co z{3?%_OBSFVR_ys`jce4r&${8*(7MYCM?IgrpR|rdR3MFTn%nr_J;7e6>K`gqq;YU? z7$>l73nJ!G7eCYv(`JwJJsj0xW$;|fWW+GJy1dw5?M>thG^Ghz^)Q{Sd+8-ECdQ&J zd2!%fpv``IH2nhpeOLnfoPSzPyLsHrQ8IHIc#*o;?w3_#-&2eBV#E3$eF;49ZJA7s zu8ob2q)xPt)U^&NgeJy|){WsUvRi z-IcloJVC_aQOO%@gwrd>1pYPx@H z>`ByX=~kwoPbj;b-3LlaUGT}=o504_HDu)68+o19(wIPt5KhQ@ERH{gyrVz${^}na z?jqVykcP4GJQWqy!FC*nP7s(>+)51>;ujF8a`*JC(;wsH=GNJstYAI+%9{RwJXDSu z>iYWi9w8y&9uWVAb$vP(9!biL`>%ej5m4TJJ(asXTQB6Yy9Bq-V>JKhOU{$LJXv<( zoGzCydNLjy6y&?J(?%NHaB(zE+V!lQYZbSryPLfhTpsJ83x<=Kf;{%My}i$;b&3tX zs;jD=HS6jMPl;9SozXsfva!>YmlEGjoRoDv%)uY0+X zwOzX$CERHA0xr$*aCfi8lKSIw!HmAb-1y^SKNlByT3;DPEuUQDc#=aCsjxxs^Web) zcS~KUxxr)qt{XbgLo$t87is)iOcy8_sMy)X1-=JVFdd?XPDog@;0XWy`*&_$nx{=C zu(WUGM9Mtq?EDGO0CzF(j$jcTOZ50>(d4Ek9qv@&`x3l|9o0^nSm){|HMSTB? z^cc-=U5miuG%@Y|~bYs$*2%Q&j009mEg4b-S^la$oNT76| zqe(l}{Wp3=#a_URVFF^}!-|jbaAKk^IzE2<$L8jyss4uh!JwjB({9JjK-6J<0``aD z_0@s&D-Tak&$7+Srr$q5EV<2ju(l6f&m6R>B)ZNe*5Gq?^d%%Dga^{l*PjL`wuA+M z6GQNh^c$TkSXw!2AbV`>F%v5CpxAU} z^qHV&nofV*JOn{k#B614J^0MRf>XEBY7hza0xcGIbaXhVK74pc87eonxjEN(e}3<3 zIa-pz;uLNwDtf5e%7ky$WxmzrOPm|Ess6T!?egLEsyFk{3APXo&k7q5rtz>}5~LK4 z0C#$F9u;6iBn)B+Rkq{B-J>6L6rbAAi473p21q~sz zi#aCbae?-Sp`js$Qu@(@&6;^59Eg#T(Jfg|HryiLRzu@W%^jdxA2~zVaF%aRZ8>y$ zYN|3WE{-@_{Al9f?c2BW1=@w9F01{iX7&BQ4>w|sK`7)`SPpP$u~9ig->mrS?x>>a zstuztkMaF!_J))$vuIFVDn^?>A6L&H9g=E59VFy?U(UKH-uAOlwqG>@Gg0GQqLEp*ac?#9T&Iw3T_gP2MswG|>i1{GyR{SN0jj=NRs)F$sIsyW zVUrDD7WKoMVWg9sE42mkYcZi2>AV#KE77pidy*W21XP+#yNwCoe@zvT_SEH+j*bs6 z3&Ni%a3ahwW{kdROWqa*`G=rgI$-m0oZ?Ck8{CMNJx)$jsZpinu?PX;91 z+}zl>xUFq%%LFDSc;I>6zgPr|-Svfgu;0O6Z(qz>;Vv;6j)rnidG%{<4 zo=WeBNLD9fWag@$IBv~|Kz~APVSU|teAU+0*6H%xfQk2gLis1Jj@;pNlKz!JIrPJ3BK8=2t(S%Iif~w1e~3nneZ{efii7 zB|g9ffA1fK?(8^HQB!A?ms3nmP9}(X@WS3cKFT!WMTApBvxcpoea^h82;21_mtN&g z0ouMPkP+9QeN}ukH2eu4w7vw9(lsy;++yOV%`WfbBmQ&aVe}doCujSYP)Z(;y=V2d zxFCr0iyEK2euqZOWDhbiF)ie}UC~D30>350#%O722?-0UTbFqM=$@IG850E@s`2DW z(9Dbh%iX&m6Php%U^ucECB_^jp%W%6(qJRhMH_xCx2rs@dX%X_#USj+l;o>cOw?13 zwZ|MpqElkje!9PUi#nW~o<4Hgq5e;^t*a3+|ub0y+^X>l>2S6xedj83Qg z;$j5{2d?g(o_gZ6zJUQ4uhR)1f%p}JvOjr_$uVmNKW8CYSy>UEGSbqb1W+m(lEp{YnVFxz z7DQp=;Gke=NDrKc?8c4tZ%*Ot>NdJ@5$${Vj5x zdaP~}@g^cbR`K}pW1-Zrz-3`TS`HSb9ECTF^mTO!pq<0Rz&^wp8y8nc%xy&tgPFxe zFT>n$IcD3dKnpLH2qQmJnwZ<$lgpPtOMGHOc9GBOf^VEBq;{mz{`3&O<#-|AFJ@loP$N-{HBwi+@tA6@i< z8Mnx>7fT=qCn>X0f2MQsJ9jKT*n27{C}6jt>anHSl$4cqERd9UZ(zqv)wAy;39*@N8@t56|tSfeBx{&~Sn<_k%A&^8~kS)l5K9GES_>64F007aHDf_6I@y3UqD2Z8B^a1JNdYEq;&^ zdBK`mG6`x2)0tYQ@gg|eRaLI8#e~Tef$5&WFtdabET8Bt|HU^(yzgym44)bomSmKd z(YoAzp_s3}lOaw3QevP*X>V^aK&g1M$5%QXisSFa+U%v$}vdViwj^1c=UdXgwIrFJ*Sz~@X(O|aGp9WONQN>z=MT>fdRskn3!1DsEoz4c#{61?8&?v*D*0VKw`U}5>FvGxwsS_KmJp$p*#<) z;JVhJTQ*g+U}m5Py3Q6W{;aC1YXGl--^!YqF@q9#ad|nQC!RaIsED}fe2?gz%AJPB z#`-1>w}rf^QA>p=y3ij#o@+mO@+v`vMOc^y2*Ae1CM7SA1Hvw2)TXHI`HN30Xp-r& zh$vbS=kLwqDBzU{sMKz<{2r_rMdx94_;Zv1IO*-Rx4-D0q7R=LvZ;Jjp7t-PI9k@; zz2PV(WVK%fqyl55)ztd^`sL*j2oRkb1=^r1P}*NVGUwpQ@=8wbbrp2Hzh4o>OA+2K zp5)w6RW+KqQO1Y9C>;GslDY;m(~$7+fTku%V2#2LA2LU6;Kl9j=-S#^l2)*Mo2t6D zBiza$Q&o^5?ez)G8~iQf##qtW7V!6`%IfN$hP(bEo(K6%_%d3~&gJoq=~#Xq`#*@M z2{^H(rCxpYFerL_>yD?Ow^#$-rqlP8mCS|)I%Q?$oxMFw9k?~|LJ9L~~0c(P;qex>3dw*D0!S~n%|E$y|p*_P4;J9L$uF|n}$dwb3- zEG(|a5Um-_XwGByYY-C>`BAZ=2Q0ltrzt=tQ|iG31mI!7Zn$Zy&sG`BdY% zGPANU5p7I&?{;K{;=yM{L8bt@$%~8{MR!5{KnmpJoqPOS2wVE3<@{LC<*!{=p}?=h z)f;*e)9N_7e3JOIlOK{sCQgUPYc-yr@`*wLn$ih7Lh|zR+GZp!z_-7B`vwKdI2l$} zH45DZw}pLv^Q)yxL}q0%*Z=MVj8A^^rfrdRikJrljDv$?<={{qb;L)504EC4(im!K zV%9~vUphKO#i~yI@Y`gptn$a}w?Cgr8p9WdC4Wk1C+APzQf4wp^ZXk@BBsRzMHqg7 zhqtd|n-N0gorkJtdb`BM^wW%=b8hjrKfNV>Ah7S~=H z2A=5jqxS}#r0-1T+EcT&5tNv?IQg|}Dtnkusrhp`?Heo-@nsC zZ(_jDJ>`5j(!%K}DGh^B7wjpbrpS$0kVeP9eY?jYO*XgQ;GuJ#88;SpTik^-tdNWJ zCuTOtug;FQn;aY%%-k0i7ymjranHk(MIo?^jg3Lm2oDr8K_w+6vMn$S+Dw1)Fo2`v zw6uik>gu+9iBFM1Amn6ZWn&T&LO@M+|HA!AvpjR7F52Qcw~^R-as3u;6Nq{({c5|f z#rgSGTj4&FX$k%GNq~<-O%r61J;gm;T?r&6@+vAShk$1WbjQASb?so4##v0BA=AtD zxdNR8c@Gv^;DB31)JTmxtgMnQfI|3?Jr3qH3cckY>p89#eseIRqXK9gn3GYaGEKi! zf0^NNd9A;X|AEx2WV_4Bx%qu-wtgQpue_>=GwL3b3d9Q2D>IQT2cGkYiUE2 z5(~wHMBev3p)(ZwC#(ZxdK7Y_G$3c_XL&3s zFCqXql@qW%LvmW0FV7$Sox=O8AI`EpT9E#DH4d}v1Vlv1Toj>d7*MBEC}_|0>y??| zJQQ=K#B4Z6aobS8a|rMf{<$`o9rNZ51o9`*f;K7)sSiee!3TP}x1Qgh+n%0Q(H-ht zE^^s4fzC|}5jE~4QHqqG&h4tq%AOv4lO=7R1HHw?7oSgmsFyTd!k#4v(@o|3mAZ&T zP~74`Ik~ydhX(Sznhv8ZdUK>_#|FxgppnAkv?TYlf~I4{3dcJouao;BU27aO zc0C_HP0P{~nEc5@^kRN@h^8~{*IVM;Dgp`^lLB;l2f4Hg=%j!W|G#8@{T`ppNSvFQ zp7xS+cRx|8du@0fW^{XzBR3FKbG9Jy5OjoQIlkH{a~Qz9h0J=R&p_+x#qb-^QT zzR}BV4zw24)5X22-*TCc2bj`4Ac|~{DAQV$*=H7YrPI{ZgcJC!hcYBcT0M^9X-YqI z`3*WC&=1})_v=LLJz+>5sLIym&S;41?BEk^@chuA*r)6d(Qykcg{yj2x+P_6SyJt2 zT?_K11e#&(zocnX_Q%%i5nfMZ0-h1xMZ#&v$k+d_U4ggWba@A4^ zW0d4r;~?m~a5yAyNrB-^6)~o{XvB+)%h2_acL`2I5h#33bXbYa-~Z_w)d(C{#~!%Xe&m-g`Mqpp*6cW*x<>j3Zt*5PPl#ALQM z{G6;*T%9z#&sAOh!CU5%w9XnGg!`(&eHn3Gwl4_pYlt7X;r9t-rPLk{q+g3QY%_Iw z4MT}thqQtQGBo~73&5}2!R~{w$HCelhihov&TK6#{QRMk-vhB)OaB@%*W7wt3Bwy+ zXz|qBJi`}COq|N1WMhq_960gFiC))Mj1vS< ze#@Q;#7WhJRF)iBKPF*PQdmrL@dcQ>!e2Kz=XN;L^8r;5HTbOqZClEAJFa(Tly*59(= zw_X$0>RWtt8?T7`n+s;8g4Aetc9~)GQMl*1z*piPR%M<^#n+AT#Q9dsW%Uz&j(5V9 zwzq=$l<8|Zaaub&ncmzilO zC0h>_Y5MhB(H4E@v?LS%sn=4p`WGX|gUH+aogs@K);_N;J;I8H2MB+qe>kYX#9|12 zM#eK#RY=>&h4^H^wd7$m(dc#DUNh&p@ux4Ty6(&pgvFoQ#U$Z|K&kY*-t@zRR&9l~ ztsgKaGJ5VW?Wa$Q)(4H~fwk@q*wJ5#t1mt}9ocBD^*(hT-dpKun*8Ll+CG=m4>l6b zGRU#Dz6e4{1RQ%{^-5QVW5-a9h7mq@A~Dnppi1Hi-%GE(wPG(uc+>ragN6lH*vOUE z-DH3pmr5|9kbzP$Z0CbxXjS@`_qJxPH^>6AkQNEIb`8QZo#~ER_Zwz6^o@!!S)m&D za(ATO5A8)RGe49%b(}qBp#WwFyRPRF1IUpM`$zgOVS1fkq=bhiyFZJZSNHufmd@ou z_HTL*1m)4opUL!3lkH#6St8)HTh~LCLew-G%?n;T zw>UkLXiDE3AVB-NM(qu>n{=mnIdt{M4Qhk713|Z+UwWNB_Zs7sqdQ}yq?qKh{gTFy zbP&a}7U|g^>Epfp!NC*Q@!)wM$@X&enM#n|<>N6yOeq<^ zXdQ4r+Uv!74_uN7mEtvfLXt_t4tP7fe#RKzW~KSq&|HnnygHPCX>!M}e^qzw zIb0oqv2)8$4$6bhN&MQ>rRhoB77`!E#Syr^Sa83rijUK}6p4Y~SFv|XvhJ0XS370w zsbibq`JF-}Nmo;sO>GToZs$bJ+ZgB5NXYBi?h6Q3PpVmDn}%{Q9fhtgz9b@U47HjR z_Z7flIg4GRPMlXtv?R|MUH*JWeNR7pemVjv1z?n3Yi<-2h?Gn)Mz0pn5iMZ3N@AE- ze-dn-xJ4X}q}jKuNEMFiKSj&6uH9fEX5Ej@Sv0te;ZNyl@pLW-$XgnSxn#XE77tLzg5`YW$_ab>}iLnAuHad%g&VN zrje(7{VLft78n%=yijH0m4r$wll9-@Bj*++pzR6dcmj^5OKhc^$xm!IZCamOM9m{D z_S3;maj&I^{xP(lkF;6M0N&^tgKG1%>|3kHoJfl!-%?;G3B71D`qSq3OkJeQllt%B z)Mk)3j+BkjShDTSS>pAJuSnuX4_%u%g^1K48me-dt^bjL7{bRbW%L_rKtk?_T*0ek zr;&!@J6tl>b5Ba?ht^beBQPBCrNU3QX()nXufv9OL+=l=Uou}6x@laB#KQN|=haVF z8ORg{BPNOi}M}W1d9QPoG4e@>OGYqh5GrWC|nR;wlH^NFTiAAHtBELN?); zWMfTF;xD8N_E7dtZ-w>yP#<+zPhhqGw;r?NZE2~6StWXoO;U?RYd1rOORtBuF~NVU zO^#dHoX5{Qc||hI9HF4Di?yIG*c14}e*OXVcDAJxtxVo{v-AIeLd^#@p+bd$%J|+%Bj?UAoQ6IqeBX%RyaQE&I9p zM1Ij8|0UGlj)gm@I5YthnZ)(&SwUBf^ych!D*8CX0cg?vcc0KEFErBRY4v-jrATuI zANT$5L%~B6{!wK#%MhL4y)b=ibfA5adG;xv;jL!=*rMCyGuz1)gQNNR{X?~yd{qC` zSoHhdtkbQTYAH3LrrhbA7mA&1+m|di$KhP+PnzjC|1$0t^b|uYPtYVO?&#ru53lT| z1k^L=WAH^v)`M96) za$N-l@c-Vao@KDM>h(Vvyt}FBlJvZG5`7QsoSNn{7=-H)XTqd};(yhs-H4?*dG@U! zGYS4(XftiTDQ>s79jKWNknAzPw*Pj(`EsD`snFy_x~$;U2yq{odnW1LKwaOL0mkIF z%5lYMw;8(eI$eB>^u+|;?W?0`?_H%dcWGXVV0G?Z#J~t^ztUA1-ZE_%q-a8a0@{;ULJl^HbEXib2f8058OgPfE&)q&0k)Qs*U=8dNPI`QyBN3 z<+xZld1C6&d;G+)s>QL8l-a8%1(2f9s>{TXl;cn-W4AElktdMYk*jH|xhIfjW1)G^ z*BbCP^n=Aj;T3z#2dL6o_zh1MFqnS+QMZHFdcA9GXsmx*S__YxwcVd_(bd);g}c_* z!QoYnHs6yH*Nzk(C$8p1$!Mr{2=mwaadP&prkvwh*we}cY~x%vrQ^{gv?ucL3<@4! z4>%6Er}@phL>i$SQ2EGDbP@Q-;X-KbE+r0VXb+rQdtzDI`K+gmv9XV5Dys@Tp z(=gKBP5$5vAxIO{36_fRzlVe7cdw-#X00v%6e+AcdG)DZ>w)XPCNSvY@IDNsE!JY$ zW@3$wiP2^v#HxJ8dV3ZqcAY8LZo0$9?BtX=I%e|DIzV%#+r64)@-nevyV z@=4EHeXr{YagyTLovzotke5%?eD&(MFd!gcu}Ht#mH@>sD5bMNv#_)2?iO(ulbCq9 z_^$Fw&beh&MNH&M1-cr=@|VmcmKnZ2>g8yjA2o3(Oh=;`IQ)6Fkr`#c$ouW0qJnjf z5Lsy=PDZ=Ejh%NOYxlP=gpPyOzIFSWL@}Nnw`so#VaiJ$uD~AO`;dU3lan*3`z@=!sAw@hHM zyu*nilOetKWo>kpLxcQGIm1a~PIK4=ti>Bvj_@?NxVW&ihWMdp&z{|#RX6wBuh|5O zGzOz3*3{ITM^FkLncLeRBw#*dP#Wy%X{&fU%EzH!W&PzDD{ocV#mznh{or3{x5Lw3 z>Q8LvY}XF|nh`fJUKpYA$kb9%2WQj2tPDF`E){OjaMZ(c$q=*YC??kY2)aE^Kkn)2 z2^(nG1&2dRWP!9xNVBuE7k}V|&&|EA&dA76)z|rJ?^kyI_h6)~1X>^ThZ%SSnBYN_ z1@CY}jc5wC4$C%Jgi@77%{p-gOPg=&X1qO2O|u3Y&le~@TpTQoeEIT)05;X&e(c}f z{Q?hBc5rCy4Ar;~tltO7l0xa_9vT|ICl6o^0|tVTyh6iUx$ByDVpJ4%OI zR##W)Vd4%BT$9HzVto7|4^=t*PhCRCDNF5vh29rSfkYA#5=&KYvzU@5OO5XKjt9lk zN5{`c#>P%;Pf9|RZS2TveZtEFOlCNpw|QfjmZqgNr;J8Ug(IkI?GlDaR`=G{s_Jek zoVO>SmmbT(j076Yv0Gpkv5SLtJO~|@pPw&Z{$?q7)pa#SMKtlN zdj6Ms*ZteuH)4qC+c#|m^;Rs*%lJGui+g!BdXE=q8re?&SmlN(rHjs+9k2DDhmk*HR?XMwK@yLq%LhI?sG7B%of#T3 zHZkVpw2VRzFtB`1L`XXiMafVthAPUebSZDhMn#lcG(8XANI1TGrW z@2=M+>>3U`{XpK%aIcY<>_Wg8q;6Ge&klLoFY`N_i>W_f{Es9+Z$f||iRE74ADa`W@^fm4H4 z-*r0Ry>avj{c95Ua&=q4-{U`Do=70f!Qh zcysIDL)B#`aTOYqn5#Z|ZzQn`iy%APv4RI$H0mrz2h%pMvno8nhj#bb#G&DRNmZR~q zxuYIP7c+Y1b>$TKGn8b}#Hw~@vH4C%Lu2d>E-r4dzvo(CV%zy5bZ4lq!^_W4@n#_9 z@|;b->b3XT^cxBj*_9IB)!22v5Vh+nXrSyE}E|C`*^YO z@!E}tofPXI{rqmMkEuX3GI4OI?CqqOby8J^6~NYC-d9w$Fb>~wzap*A&Uc_?|!_8eTIsTj+kvc%Rh$J z2l3w_5rls6NC+II#neLEG_>G{poqWSNyudQY}M?!K%3Q)vH&LYwPR$17HhZY?6MN zb3>P_@vGUg;qP!iUR_;v78MowL&xib>G#A19M}4Cf`WqjgM)*;-`6%cyU$kJiNO&F zV0J1ygnsEHKi6Nw4f~>?>z!Zw`T3!!zj{SdsKHDR`=S3JOZ2&8V}`d{jX}K`vmw&fCU~)2nYIBO^)lig8Mwx0mY=y*_+sm@yKp zblIJ`0NDip%SGP+xWUBD%(6|E+WbAI+h`we0f>|72T81XHmabY&|_w5di&PXdYi-~ zd)RVJQ&Y3j+1a_+HGr9e!w$_Sl!T+o1%fqroW8RP6!MoG);q-h2;=AHuV&FM(U?j1 zAmy`8aDDxH?oW4jHz%O4%#XAfwY9bD<>lprQ)`&GxKxM0s{N-R8h%u1g|TQAT@-l@ zIs!4H#&I#f?_J$x$(zJV97+#Z`=rKUMYLicwc2*R?n!uf_{(TId5^}XL#VK{Nr;@B z{AvNnAJ;D+@{^Sqj>qy}*&+E5v1nb}FLwkxqWNV`i(0zu&AIoPbL&d0eJLm`++OfT z?3PMOO7iVbeP4;%#V3O4uo5eR1I~jOiRk+I(#AyMYKjUSkNUX@FS)+(6L2fF>jSq zR!Lr-k5-|--`SijfS0uB!BFRrd6OD9*RzMFB9hYxL^cK7s1YG`QWLiZWvqdzS# zn*z3OG5ua~nf}vMLRXiJnT6#6HFboK4=h(Cw^d4=KSP7rhzPY6Ar$+$T0Q>*kvM^3 zu%$6~az81*EoA^6&HdSylC!|i)*+#x!bGGX3@33z&sx<~Rbi>h=qf5I>vLo)-yEbI z>n_9|zjw&Y%*4RKvBnLB>gt4E*E!{%-o2|NSI8mIE_q$MsS1+jp1;38Re-;js% z^|H%VV%XjyV{?_jUyxT+RBQyKfn98JSVoJnkNq)&81)gdcqy9fOmHcer#0)CLZxEq zCqHyNQ>ZhH%%-H6u`VqsDanfHo1|}987GV_Fvp@VVc26Stk9{+%(#a5fq3=(x6}SV zx49E{@+am*AuWCHHC$QU`fl!JOWt0x^TBRB6cxc z!AKD2Ak6kZfr^1!okAa8R1s$$*0cbG!t^=$Z$Ys_ko;!-0lu@>M z=tv5bn%xwT3J-x?{f2rIL?<6UuRx(zGzau&&B@8hE|4<%AB;W(bgs!>Z%=7YL0hy+ z#4)`kq2InSd^qms@9yjS^vis?Q{HB5a`Fie&%rXg1@PM!pcX8p1pb|!FG>mL$c%C=@PAP#CDTd)?Tht>d zW4>gEy*a_LiHU`V=v_#%nf48p>9?-MSi@3&q!vKqPtJE|&kq0`XM1^h1=`2#WD+sN zxOb;mS`}Cobdl)h3w$_ZV$J#q@2$+tL_(bHN+~Pf7ygYa31U|Q;1^&XRV}T6f&yk< z{^~gb;pfjADv{5RkNujP#eCw9$x_MQH-Pw8oXzIh8iyQ7eSLk?N}H~>QUM`FyarmI z|Lxni>wpu&26XVr$jAUwI3FG!8b*lX!GONvu|%(kqQjgfiCj^KKO$i0XziMxg^S4< z8d8JYdq6>f&B4Jj^qceT2py1M*|oJ~7Uh3`KuVE^qb)28AGB$P5N021T{QN+->N+e&yJ zHVONf!0-Y*l@=onD6CTuB}ETq4nHa}P&hZb%cwQLV1I`zMB|R8l6rY~kOkl12y3ZWLxVv5{QXR?sqc97b(!5z+JEC|I@{N(n zHMqi7*VPRMSPbeRpJ!{dxs%&!uU?@PYlVWU(bekB*NhBUVqzl5326xl-`*7d?dq7^ zR;abr(5~@x0{U?HlSHf@pt!6RZ<6=sL(}>IJ^W986LCY#uoobtV(H#~_S%++{wgFN zO-xTupEEOP2 zyuVBw5EzIK+6arqg$ck%z(FS-kO(;i#nR^HCbyU1)y=!)goK38cUP0m#D6QkTM!fH zx_Hcp`~1;#k3>bi8|mb`;M~)FAo)p zM~Qx>Ue^f!3gzV7(?!{R8v64HTRysP;=I^#E!-yKB#25$#4~tDaD0e%Z*Oml#L&&; z?E3PU5fp|rGfiHI{H?7mY7w-dVNDKjAkWd!(HK;T3!_9Omu5P7*WBFPH%J$)LW*B# zF-B1(R28B%801GFdOErt=dvG(jPfJQ^3jw{XBzkrY9mv~{!9QGk`K^ikzl`($Zjgp zU(4rNQWkVeTczZFM>teV6;7O~8J#RVz{mH=%VVmkt^K}6OUYJkya?y{P>D>9^-41L zkB<7yV^&t{jc+fL*Fc#D&4Dgj^WR$NYKK%@QnGEqpx))>*t<%*UVx_eB!bZJZNwrW zrjYb!e0-t{vHF#rU0o37?_O%}L3fy~W7TQi7ZwM79l}oZ2!~CMVD)@yb8|Y`Na}vQ zXc(-rpI||F7$+Mer3A@V4LgBk=5jbn8q<=udPn3f5wbaZ^fP7Qiws(fpvXuXDiRWY zW)QRdf`XSrfN**}R-9+b(ORXF`5+thfKQ*4VtQ8tk%0c_ND+fXt)_p#Dcv?0t{bm8 z-(0i0)zzM@=CudlN;x_4Itfu}Z_&~tS#m1aYa0}@5OP-tnr&>T^@M86G`JLzA^RA; zR9j)BE^)AWoJ&)zm8sfI13ETA_4`f2C*#^iH@BGqU{0d@knpCXRtQ^CWR*1Y;cqeN z0R6z;@1{S<$^0#F#xa8MwmxXiSjI&em`F5!&+-4PY3p06<)gK9J)78zW4E`vD?-Q1 zdqQm6Hqa}#9{vf_;b5Zn40?BTbR=nQT{OC&op>dyReMkWC~PR1&aD9TkEVW<_WV)v zi6q&rRS}koj1ZrIv~te9K!tt*L-*oE8IMddqL+_NZ-{~p9QKmkg}jV|vY2can?&|c zCyfTL&ztWIditj>F+Td_w?0VVdKWSp@nd>=kjwkpO({iSI7bZK*$CQ_d00i1z}fe* zrHzYO>0C3TMxtZ8E6HJ;DHGgXWusl+YQ4+_Dv_Y9c`6J_z!C3+w}^+l?qIkR>C|$ck#}{*&n)4(^rDhy2Y}E7L-Ubtc220#(EboNWdH z!)XJx?aN<$)wHxG%1qh={6IHH%u!K6VXO91C^z^?=VKQqLc;fxO%7%{I;7u}5|L|P z55y|KeA27xmp848gT_L?O%%2@jv*^Ow*M1C<|y@rwBUgdgp+ zN_m`I11F4vGr2zw^bzMyw^bRFH@gpROcvq60eVX_Pg>E@(ao+-HbKLU z{qmNoma)2gt{LG$SRUyM_$NjyeIpORN0&W`fY z=?Bfd02#?#JrX{&a@5cm7yI4w79f@Lwu%ks$Dm^Z#tz(|L7*FYW93t$wY`d~Twh@% z&@B9jaR`-d2~{K4p%Z7QIQdDs!6=N_?Nb1v=omD(8=$kDX&TUT^}Mst!T((TmRC`S zwB+4x#hbDhQz^4y%K>r3fwUD)%Q3hg%QoU$Kp#^{US3cTw1grJ0omCBPAhp|4$(b+ zC`YMo`P%Fa;@pUr z6vx6gTKW%8M4Ybuw~3DoJjN9-EV)tqaUZ`+rnmm}Y&r6~t&WhI|1Z(^JJ0PTgAF^* z${9X+^l%Q;ukr38ze(`^Ms5d{dCKAKytMRL zT99h?H(kk><)0YJQT=LH-_hHJ`(R3yYR<_%xG4F$`uoMmi+dj`snTl&UO`WmS4JZu zHLnieKJ?~N7QbK{7$4g>Yynm0do{wG-P!8(rn4EV#K8Mhb$$Jx(NOL)F1Kwf!vfxY z0-6)4jn%OgUx(Z4%eoxJc<)-! zaGpyeAMgvxYnE46jI@CzKqOf^V;esPw3dVoj6Z(uKa$Efl+?}sq?X3L;1C&<=R7#pdMrHP28l9F83ZgY7HoR! zVzfAk34TM=E;KIv?dCkL;ta}jt*Nctp)zXRnn8I6q@&N9NK#V%$MPe_29s?tUAt?_ zYsxVdw-+HnL8PS8(j6_}HPC-~@V93W1d4oV&@la`PD# zx{fz!^!k~xiZMPR6glqmp<>A{sUU=9iQL>Y?^`ytG+*cRmZ~9k%TcPXlR`s~X!S%M zm$e@qCne6UPU;+ukAiV_ZDM5clNae3_j~vKzE0L(lQ%CoJG8_Zy@j*Eq!9==XB}^- zcAv5FlCJ{h0@K0%ExcVZi`0Mt{mzAv+i7(nUr_j1!}f(PW+IV-P|GOA5vQ!q2Oej# z$Zj;^I6m#LTMF^`{f|PVXDyLqv2n7Fiy7Oildm=Uj<#hl4N6$eFP*cm6Btx*yK3S;PWlNkUX=w7hY`OTHLw4;(FdW8fmSFu=~ID|x-)H{a)7Peoz-nJY-7 z<;BHFt-eEL{{F^Zwl4gla)|e#aAT+YQrXF?T|*m&mt4Gc+TLE2@2Gs%{Mm0%vevmc+54We~_26NZMZUliZ#+GJfhNn(U)LZL zdXcj$3th%~^&D+0U($a#g7ubCtX6|ioPXMEG7&P?^zn|$2NEa?gLKl(Xv8-s;OG2j z?fsI;r?1pdda^8z7BGbEo^E|E}pl^3Uw6_L;4l*m9eJHo=`kX>j}Me5zT zoM5K0Uvbl{V0m0u-yS%x(Y zG-MawQJ8vJOY{nchPfQS^j*1OoeAtDtiPLQ1@U(8AG72PO<@}QL-Q*puvek^a_Yv^v9vW54N3 zxD_N7I=7MX-@95Jih{99^~&31z%^#Hi^ZxQymLj;35h_+ocy=%(KAhu%JA73L-#9g ztL{7>)lKp=aFo%==E^q`U;91b_4s~`CTDqKc!?V@wDEJ^MNTq%m>hp``XypOsMw(_ zr&#r*k7q>u#{P5_T8;9^9Es_ecw}aZOpW*axoYpATTb6?`59&Xk*DoO8Iry?{j(f= z&&+G-O})#P6F1HP)!h3vUu2D8uTIwWC8)$nQ=4QL9uuNmwg<-<+Obhncds%g_OmwI za_R$YHTfRorRI%n=HrV5J2*LCC*MOm4Qg3{J~OQNur))UMI{>V9tZf=-&`Wx4iz`d z-A0oCk6`|v@c#Fk%>(dt{$C96FTnq|k$>le|DPqG|E7t5z4dRz@xSN!-y{FVHvhiX rfAP;hFa5(v|BR4Q0@nJobqm4D`?IObib%X*;F6b7mM)Pr{`mg@uj}l^ diff --git a/Telegram/SourceFiles/art/sprite_200x.png b/Telegram/SourceFiles/art/sprite_200x.png index dd697097939ad1af581f683fbd736d2870ff51e8..f36cff458af1613c7a16b4697e83d08d8497d9e7 100644 GIT binary patch delta 57056 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 AzyJUM delta 54766 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 +} + +void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { + _pack.clear(); + if (set.type() == mtpc_messages_stickerSet) { + const MTPDmessages_stickerSet &d(set.c_messages_stickerSet()); + const QVector &v(d.vdocuments.c_vector().v); + _pack.reserve(v.size()); + for (int32 i = 0, l = v.size(); i < l; ++i) { + DocumentData *doc = App::feedDocument(v.at(i)); + if (!doc || !doc->sticker) continue; + + _pack.push_back(doc); + } + if (d.vset.type() == mtpc_stickerSet) { + const MTPDstickerSet &s(d.vset.c_stickerSet()); + _setTitle = qs(s.vtitle); + _title = st::boxTitleFont->m.elidedText(_setTitle, Qt::ElideRight, width() - st::btnStickersClose.width - st::boxTitlePos.x()); + _setShortName = qs(s.vshort_name); + _setId = s.vid.v; + _setAccess = s.vaccess_hash.v; + } + } + + if (_pack.isEmpty() || _setShortName.isEmpty()) { + App::wnd()->showLayer(new ConfirmBox(lang(lng_stickers_not_found), true), true); + } else { + int32 rows = _pack.size() / StickerPanPerRow + ((_pack.size() % StickerPanPerRow) ? 1 : 0); + resize(st::stickersPadding + StickerPanPerRow * st::stickersSize.width(), rows * st::stickersSize.height() + st::stickersAddOrShare); + } + _loaded = true; + + emit updateButtons(); +} + +bool StickerSetInner::failedSet(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + + _loaded = true; + + App::wnd()->showLayer(new ConfirmBox(lang(lng_stickers_not_found), true), true); + + return true; +} + +void StickerSetInner::installDone(const MTPBool &result) { + StickerSets &sets(cRefStickerSets()); + sets.insert(_setId, StickerSet(_setId, _setAccess, _setTitle, _setShortName)).value().stickers = _pack; + cSetStickersHash(QByteArray()); + Local::writeStickers(); + emit installed(_setId); + App::wnd()->hideLayer(); +} + +bool StickerSetInner::installFailed(const RPCError &error) { + if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; + + App::wnd()->showLayer(new ConfirmBox(lang(lng_stickers_not_found), true), true); + + return true; +} + +void StickerSetInner::paintEvent(QPaintEvent *e) { + QRect r(e->rect()); + Painter p(this); + + if (_pack.isEmpty()) return; + + int32 rows = _pack.size() / StickerPanPerRow + ((_pack.size() % StickerPanPerRow) ? 1 : 0); + int32 from = qFloor(e->rect().top() / st::stickersSize.height()), to = qFloor(e->rect().bottom() / st::stickersSize.height()) + 1; + + for (int32 i = from; i < to; ++i) { + for (int32 j = 0; j < StickerPanPerRow; ++j) { + int32 index = i * StickerPanPerRow + j; + if (index >= _pack.size()) break; + + DocumentData *doc = _pack.at(index); + QPoint pos(st::stickerPanPadding + j * st::stickersSize.width(), i * st::stickersSize.height()); + + bool goodThumb = !doc->thumb->isNull() && ((doc->thumb->width() >= 128) || (doc->thumb->height() >= 128)); + if (goodThumb) { + doc->thumb->load(); + } else { + bool already = !doc->already().isEmpty(), hasdata = !doc->data.isEmpty(); + if (!doc->loader && doc->status != FileFailed && !already && !hasdata) { + doc->save(QString()); + } + if (doc->sticker->img->isNull() && (already || hasdata)) { + if (already) { + doc->sticker->img = ImagePtr(doc->already()); + } else { + doc->sticker->img = ImagePtr(doc->data); + } + } + } + + float64 coef = qMin((st::stickersSize.width() - st::stickerPanRound * 2) / float64(doc->dimensions.width()), (st::stickersSize.height() - st::stickerPanRound * 2) / float64(doc->dimensions.height())); + if (coef > 1) coef = 1; + int32 w = qRound(coef * doc->dimensions.width()), h = qRound(coef * doc->dimensions.height()); + if (w < 1) w = 1; + if (h < 1) h = 1; + QPoint ppos = pos + QPoint((st::stickersSize.width() - w) / 2, (st::stickersSize.height() - h) / 2); + if (goodThumb) { + p.drawPixmapLeft(ppos, width(), doc->thumb->pix(w, h)); + } else if (!doc->sticker->img->isNull()) { + p.drawPixmapLeft(ppos, width(), doc->sticker->img->pix(w, h)); + } + } + } + p.fillRect(0, _bottom - st::stickersAddOrShare, width(), st::stickersAddOrShare, st::emojiPanHeaderBg->b); +} + +void StickerSetInner::setScrollBottom(int32 bottom) { + if (bottom == _bottom) return; + + QRegion upd = QRect(0, _bottom - st::stickersAddOrShare, width(), st::stickersAddOrShare); + _bottom = bottom; + upd += QRect(0, _bottom - st::stickersAddOrShare, width(), st::stickersAddOrShare); + repaint(upd); +} + +bool StickerSetInner::loaded() const { + return _loaded && !_pack.isEmpty(); +} + +int32 StickerSetInner::notInstalled() const { + return (_loaded && (cStickerSets().constFind(_setId) == cStickerSets().cend())) ? _pack.size() : 0; +} + +QString StickerSetInner::title() const { + return _loaded ? (_pack.isEmpty() ? lang(lng_attach_failed) : _title) : lang(lng_contacts_loading); +} + +QString StickerSetInner::shortName() const { + return _setShortName; +} + +void StickerSetInner::install() { + if (_installRequest) return; + _installRequest = MTP::send(MTPmessages_InstallStickerSet(_input), rpcDone(&StickerSetInner::installDone), rpcFail(&StickerSetInner::installFailed)); +} + +StickerSetInner::~StickerSetInner() { +} + +StickerSetBox::StickerSetBox(const MTPInputStickerSet &set) : ScrollableBox(st::stickersScroll), _inner(set), +_close(this, st::btnStickersClose), +_addStickers(this, lng_stickers_add_pack(lt_count, 0), st::btnStickersAdd), +_shareStickers(this, lang(lng_stickers_share_pack), st::btnStickersAdd) { + resize(st::stickersWidth, height()); + setMaxHeight(st::stickersMaxHeight); + connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); + + init(&_inner, 0, st::boxFont->height + st::newGroupNamePadding.top() + st::newGroupNamePadding.bottom()); + + connect(&_close, SIGNAL(clicked()), this, SLOT(onClose())); + connect(&_addStickers, SIGNAL(clicked()), this, SLOT(onAddStickers())); + connect(&_shareStickers, SIGNAL(clicked()), this, SLOT(onShareStickers())); + + connect(&_inner, SIGNAL(updateButtons()), this, SLOT(onUpdateButtons())); + connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + + connect(&_inner, SIGNAL(installed(uint64)), this, SIGNAL(installed(uint64))); + + onStickersUpdated(); + + onScroll(); + + prepare(); +} + +void StickerSetBox::onStickersUpdated() { + showAll(); +} + +void StickerSetBox::onAddStickers() { + _inner.install(); +} + +void StickerSetBox::onShareStickers() { + QString url = qsl("https://telegram.me/addstickers/") + _inner.shortName(); + DEBUG_LOG(("Setting text to clipboard from stickerset box: %1").arg(url)); + QApplication::clipboard()->setText(url); + App::wnd()->showLayer(new ConfirmBox(lang(lng_stickers_copied), true), true); +} + +void StickerSetBox::onUpdateButtons() { + if (!_close.isHidden()) showAll(); +} + +void StickerSetBox::onScroll() { + _inner.setScrollBottom(_scroll.scrollTop() + _scroll.height()); +} + +void StickerSetBox::hideAll() { + ScrollableBox::hideAll(); + _close.hide(); + _addStickers.hide(); + _shareStickers.hide(); +} + +void StickerSetBox::showAll() { + ScrollableBox::showAll(); + _close.show(); + int32 cnt = _inner.notInstalled(); + if (_inner.loaded()) { + if (_inner.notInstalled()) { + _addStickers.setText(lng_stickers_add_pack(lt_count, cnt)); + _addStickers.show(); + _addStickers.raise(); + _shareStickers.hide(); + } else { + _shareStickers.show(); + _shareStickers.raise(); + _addStickers.hide(); + } + } else { + _addStickers.hide(); + _shareStickers.hide(); + } + update(); +} + +void StickerSetBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + paintTitle(p, _inner.title(), false); +} + +void StickerSetBox::resizeEvent(QResizeEvent *e) { + ScrollableBox::resizeEvent(e); + _inner.resize(width(), _inner.height()); + _close.moveToRight(0, 0, width()); + _addStickers.move((width() - _addStickers.width()) / 2, height() - (st::stickersAddOrShare + _addStickers.height()) / 2); + _shareStickers.move((width() - _shareStickers.width()) / 2, height() - (st::stickersAddOrShare + _shareStickers.height()) / 2); +} diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h new file mode 100644 index 000000000..c5c3661f8 --- /dev/null +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -0,0 +1,100 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "abstractbox.h" + +class StickerSetInner : public QWidget, public RPCSender { + Q_OBJECT + +public: + + StickerSetInner(const MTPInputStickerSet &set); + + void init(); + + void paintEvent(QPaintEvent *e); + + bool loaded() const; + int32 notInstalled() const; + QString title() const; + QString shortName() const; + + void setScrollBottom(int32 bottom); + void install(); + + ~StickerSetInner(); + +signals: + + void updateButtons(); + void installed(uint64 id); + +private: + + void gotSet(const MTPmessages_StickerSet &set); + bool failedSet(const RPCError &error); + + void installDone(const MTPBool &result); + bool installFailed(const RPCError &error); + + StickerPack _pack; + bool _loaded; + uint64 _setId, _setAccess; + QString _title, _setTitle, _setShortName; + + int32 _bottom; + MTPInputStickerSet _input; + + mtpRequestId _installRequest; +}; + +class StickerSetBox : public ScrollableBox, public RPCSender { + Q_OBJECT + +public: + + StickerSetBox(const MTPInputStickerSet &set); + + void paintEvent(QPaintEvent *e); + void resizeEvent(QResizeEvent *e); + +public slots: + + void onStickersUpdated(); + void onAddStickers(); + void onShareStickers(); + void onUpdateButtons(); + + void onScroll(); + +signals: + + void installed(uint64 id); + +protected: + + void hideAll(); + void showAll(); + +private: + + StickerSetInner _inner; + IconedButton _close; + FlatButton _addStickers, _shareStickers; +}; diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 6b9a8a14f..0c171bb84 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -17,9 +17,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org */ #pragma once -static const int32 AppVersion = 8013; -static const wchar_t *AppVersionStr = L"0.8.13"; -static const bool DevChannel = false; +static const int32 AppVersion = 8014; +static const wchar_t *AppVersionStr = L"0.8.14"; +static const bool DevChannel = true; static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)"; static const wchar_t *AppName = L"Telegram Desktop"; @@ -101,9 +101,10 @@ enum { ZoomToScreenLevel = 1024, // just constant PreloadHeightsCount = 3, // when 3 screens to scroll left make a preload request - EmojiPadPerRow = 7, - EmojiPadRowsPerPage = 6, - StickerPadPerRow = 3, + EmojiPanPerRow = 7, + EmojiPanRowsPerPage = 6, + StickerPanPerRow = 5, + StickerPanRowsPerPage = 4, StickersUpdateTimeout = 3600000, // update not more than once in an hour SearchPeopleLimit = 5, diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index c7a88e00e..9384227d3 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -1122,7 +1122,7 @@ void DialogsListWidget::saveRecentHashtags(const QString &text) { } } if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) { - Local::readRecentStickers(); + Local::readRecentHashtags(); recent = cRecentSearchHashtags(); } found = true; diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 61b324d2f..c6f46eaf2 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -26,6 +26,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "window.h" #include "apiwrap.h" +#include "boxes/confirmbox.h" + Dropdown::Dropdown(QWidget *parent, const style::dropdown &st) : TWidget(parent), _ignore(false), _selected(-1), _st(st), _width(_st.width), _hiding(false), a_opacity(0), _shadow(_st.shadow) { resetButtons(); @@ -684,8 +686,10 @@ void EmojiColorPicker::drawVariant(Painter &p, int variant) { p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojisLarge(), QRect(_variants[variant]->x * esize, _variants[variant]->y * esize, esize, esize)); } -EmojiPanInner::EmojiPanInner(QWidget *parent) : TWidget(parent), _top(0), _selected(-1), _pressedSel(-1), _pickerSel(-1), _picker(this) { - resize(EmojiPadPerRow * st::emojiPanSize.width(), countHeight()); +EmojiPanInner::EmojiPanInner(QWidget *parent) : TWidget(parent), +_top(0), _selected(-1), _pressedSel(-1), _pickerSel(-1), _picker(this), +_switcherHover(0), _stickersWidth(st::emojiPanHeaderFont->m.width(lang(lng_switch_stickers))) { + resize(st::emojiPanFullSize.width(), countHeight()); setMouseTracking(true); setFocusPolicy(Qt::NoFocus); @@ -693,12 +697,10 @@ EmojiPanInner::EmojiPanInner(QWidget *parent) : TWidget(parent), _top(0), _selec _esize = EmojiSizes[EIndex + 1]; - int sum = 0; - for (int i = 0; i < emojiTabCount; ++i) { - sum += (_counts[i] = emojiPackCount(emojiTabAtIndex(i))); + for (int32 i = 0; i < emojiTabCount; ++i) { + _counts[i] = emojiPackCount(emojiTabAtIndex(i)); _hovers[i] = QVector(_counts[i], 0); } - _count = sum; _saveConfigTimer.setSingleShot(true); connect(&_saveConfigTimer, SIGNAL(timeout()), this, SLOT(onSaveConfig())); @@ -722,16 +724,11 @@ void EmojiPanInner::setScrollTop(int top) { int EmojiPanInner::countHeight() { int result = 0; for (int i = 0; i < emojiTabCount; ++i) { - int cnt = emojiPackCount(emojiTabAtIndex(i)), rows = (cnt / EmojiPadPerRow) + ((cnt % EmojiPadPerRow) ? 1 : 0); + int cnt = emojiPackCount(emojiTabAtIndex(i)), rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0); result += st::emojiPanHeader + rows * st::emojiPanSize.height(); } - result += st::emojiPanHeader; - int32 cnt = cRecentStickers().size(), rows = (cnt / StickerPadPerRow) + ((cnt % StickerPadPerRow) ? 1 : 0); - _stickerWidth = (EmojiPadPerRow * st::emojiPanSize.width()) / float64(StickerPadPerRow); - _stickerSize = int32(_stickerWidth); - result += rows * _stickerSize; - return result; + return result + st::emojiPanPadding; } void EmojiPanInner::paintEvent(QPaintEvent *e) { @@ -742,50 +739,11 @@ void EmojiPanInner::paintEvent(QPaintEvent *e) { } p.fillRect(r, st::white->b); - float64 stickerWidth = width() / float64(StickerPadPerRow); - int32 stickerSize = int32(stickerWidth); - - int32 fullh = 0; - for (int c = 0; c < emojiTabCount; ++c) { - int32 size = _counts[c], rows = (size / EmojiPadPerRow) + ((size % EmojiPadPerRow) ? 1 : 0); - fullh += st::emojiPanHeader + (rows * st::emojiPanSize.height()); - } - int32 ssize = _stickers.size(), srows = (ssize / StickerPadPerRow) + ((ssize % StickerPadPerRow) ? 1 : 0); - fullh += st::emojiPanHeader + ssize * stickerSize; - int32 downfrom = 0, uptill = (_top * _stickers.size()) / fullh; - if (uptill >= _stickers.size()) uptill = _stickers.size(); - if (uptill > downfrom + StickerPadPerRow * 4) { - downfrom = uptill - StickerPadPerRow * 4; - } - for (int index = downfrom; index < uptill; ++index) { // preload stickers - DocumentData *sticker = _stickers[index]; - if (!sticker->sticker) continue; - - bool already = !sticker->already().isEmpty(), hasdata = !sticker->data.isEmpty(); - if (!sticker->loader && sticker->status != FileFailed && !already && !hasdata) { - sticker->save(QString()); - } - if (sticker->sticker->img->isNull() && (already || hasdata)) { - if (already) { - sticker->sticker->img = ImagePtr(sticker->already()); - } else { - sticker->sticker->img = ImagePtr(sticker->data); - } - } - - float64 coef = qMin((stickerWidth - st::stickerPanPadding * 2) / float64(sticker->dimensions.width()), (stickerSize - st::stickerPanPadding * 2) / float64(sticker->dimensions.height())); - if (coef > 1) coef = 1; - int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height()); - if (w < 1) w = 1; - if (h < 1) h = 1; - if (!sticker->sticker->img->isNull()) sticker->sticker->img->pix(w, h); - } - int32 y, tilly = 0; for (int c = 0; c < emojiTabCount; ++c) { y = tilly; int32 size = _counts[c]; - int32 rows = (size / EmojiPadPerRow) + ((size % EmojiPadPerRow) ? 1 : 0); + int32 rows = (size / EmojiPanPerRow) + ((size % EmojiPanPerRow) ? 1 : 0); tilly = y + st::emojiPanHeader + (rows * st::emojiPanSize.height()); if (r.top() >= tilly) continue; @@ -819,13 +777,13 @@ void EmojiPanInner::paintEvent(QPaintEvent *e) { int32 fromrow = (r.top() <= y) ? 0 : qMax(qFloor((r.top() - y) / st::emojiPanSize.height()), 0), torow = qMin(qCeil((r.bottom() - y) / st::emojiPanSize.height()) + 1, rows); for (int32 i = fromrow; i < torow; ++i) { - for (int32 j = 0; j < EmojiPadPerRow; ++j) { - int32 index = i * EmojiPadPerRow + j; + for (int32 j = 0; j < EmojiPanPerRow; ++j) { + int32 index = i * EmojiPanPerRow + j; if (index >= size) break; float64 hover = (!_picker.isHidden() && c * emojiTabShift + index == _pickerSel) ? 1 : _hovers[c][index]; - QPoint w(j * st::emojiPanSize.width(), y + i * st::emojiPanSize.height()); + QPoint w(st::emojiPanPadding + j * st::emojiPanSize.width(), y + i * st::emojiPanSize.height()); if (hover > 0) { p.setOpacity(hover); p.setBrush(st::emojiPanHover->b); @@ -847,82 +805,10 @@ void EmojiPanInner::paintEvent(QPaintEvent *e) { p.drawTextLeft(st::emojiPanHeaderLeft, qMin(qMax(y - int(st::emojiPanHeader), _top), tilly - int(st::emojiPanHeader)) + st::emojiPanHeaderTop, width(), lang(LangKey(lng_emoji_category0 + c))); } - y = tilly; - int32 size = _stickers.size(), rows = (size / StickerPadPerRow) + ((size % StickerPadPerRow) ? 1 : 0); - tilly = y + st::emojiPanHeader + (rows * stickerSize); - if (r.top() >= tilly) return; - - y += st::emojiPanHeader; - - if (r.bottom() <= y) { - p.setFont(st::emojiPanHeaderFont->f); - p.setPen(st::emojiPanHeaderColor->p); - p.drawTextLeft(st::emojiPanHeaderLeft, qMax(y - int(st::emojiPanHeader), _top) + st::emojiPanHeaderTop, width(), lang(LangKey(lng_emoji_category0 + emojiTabCount))); - return; - } - - int32 fromrow = (r.top() <= y) ? 0 : qMax(qFloor((r.top() - y) / stickerSize), 0), torow = qMin(qCeil((r.bottom() - y) / stickerSize) + 1, rows); - for (int32 i = fromrow; i < torow; ++i) { - for (int32 j = 0; j < StickerPadPerRow; ++j) { - int32 index = i * StickerPadPerRow + j; - if (index >= size) break; - - float64 hover = _hovers[emojiTabCount][index]; - - QPoint pos(qRound(j * stickerWidth), y + i * stickerSize); - if (hover > 0) { - p.setOpacity(hover); - p.setBrush(st::emojiPanHover->b); - p.setPen(Qt::NoPen); - QPoint tl(pos); - if (rtl()) tl.setX(width() - tl.x() - stickerSize); - p.drawRoundedRect(QRect(tl, QSize(stickerSize, stickerSize)), st::stickerPanRound, st::stickerPanRound); - p.setOpacity(1); - } - - DocumentData *sticker = _stickers[index]; - if (!sticker->sticker) continue; - bool already = !sticker->already().isEmpty(), hasdata = !sticker->data.isEmpty(); - if (!sticker->loader && sticker->status != FileFailed && !already && !hasdata) { - sticker->save(QString()); - } - if (sticker->sticker->img->isNull() && (already || hasdata)) { - if (already) { - sticker->sticker->img = ImagePtr(sticker->already()); - } else { - sticker->sticker->img = ImagePtr(sticker->data); - } - } - - float64 coef = qMin((stickerWidth - st::stickerPanPadding * 2) / float64(sticker->dimensions.width()), (stickerSize - st::stickerPanPadding * 2) / float64(sticker->dimensions.height())); - if (coef > 1) coef = 1; - int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height()); - if (w < 1) w = 1; - if (h < 1) h = 1; - QPoint ppos = pos + QPoint((stickerSize - w) / 2, (stickerSize - h) / 2); - if (sticker->sticker->img->isNull()) { - p.drawPixmapLeft(ppos, width(), sticker->thumb->pix(w, h)); - } else { - p.drawPixmapLeft(ppos, width(), sticker->sticker->img->pix(w, h)); - } - - if (hover > 0 && _isUserGen[index]) { - float64 xHover = _hovers[emojiTabCount][_stickers.size() + index]; - - QPoint xPos = pos + QPoint(stickerWidth - st::stickerPanDelete.pxWidth(), 0); - p.setOpacity(hover * (xHover + (1 - xHover) * st::stickerPanDeleteOpacity)); - p.drawPixmapLeft(xPos, width(), App::sprite(), st::stickerPanDelete); - p.setOpacity(1); - } - } - } - - if (y - int(st::emojiPanHeader) < _top) { - p.fillRect(QRect(0, qMin(_top, tilly - int(st::emojiPanHeader)), width(), st::emojiPanHeader), st::emojiPanHeaderBg->b); - } p.setFont(st::emojiPanHeaderFont->f); - p.setPen(st::emojiPanHeaderColor->p); - p.drawTextLeft(st::emojiPanHeaderLeft, qMax(y - int(st::emojiPanHeader), _top) + st::emojiPanHeaderTop, width(), lang(LangKey(lng_emoji_category0 + emojiTabCount))); + p.setPen(st::emojiSwitchColor->p); + p.drawTextRight(st::emojiSwitchSkip, _top + st::emojiPanHeaderTop, width(), lang(lng_switch_stickers), _stickersWidth); + p.drawSpriteRight(QPoint(st::emojiSwitchImgSkip - st::emojiSwitchStickers.pxWidth(), _top + (st::emojiPanHeader - st::emojiSwitchStickers.pxHeight()) / 2), width(), st::emojiSwitchStickers); } void EmojiPanInner::mousePressEvent(QMouseEvent *e) { @@ -934,7 +820,7 @@ void EmojiPanInner::mousePressEvent(QMouseEvent *e) { } _pressedSel = _selected; - if (_selected >= 0) { + if (_selected >= 0 && _selected != SwitcherSelected) { int tab = (_selected / emojiTabShift), sel = _selected % emojiTabShift; if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) { _pickerSel = _selected; @@ -973,26 +859,15 @@ void EmojiPanInner::mouseReleaseEvent(QMouseEvent *e) { } if (_selected < 0 || _selected != pressed) return; - if (_selected >= emojiTabCount * emojiTabShift) { - int sel = _selected - (emojiTabCount * emojiTabShift); - if (sel >= _stickers.size()) { - RecentStickerPack recent(cRecentStickers()); - DocumentData *sticker = _stickers.at(sel - _stickers.size()); - for (int32 i = 0, l = recent.size(); i < l; ++i) { - if (recent.at(i).first == sticker) { - recent.removeAt(i); - cSetRecentStickers(recent); - Local::writeRecentStickers(); - refreshStickers(); - updateSelected(); - break; - } - } - } else { - emit stickerSelected(_stickers[sel]); - } + if (_selected == SwitcherSelected) { + emit switchToStickers(); return; } + + if (_selected >= emojiTabCount * emojiTabShift) { + return; + } + int tab = (_selected / emojiTabShift), sel = _selected % emojiTabShift; if (sel < _emojis[tab].size()) { EmojiPtr emoji(_emojis[tab][sel]); @@ -1027,7 +902,7 @@ void EmojiPanInner::selectEmoji(EmojiPtr emoji) { } } if (i == e) { - while (recent.size() >= EmojiPadPerRow * EmojiPadRowsPerPage) recent.pop_back(); + while (recent.size() >= EmojiPanPerRow * EmojiPanRowsPerPage) recent.pop_back(); recent.push_back(qMakePair(emoji, 1)); for (i = recent.end() - 1; i != recent.begin(); --i) { if ((i - 1)->second > i->second) { @@ -1038,7 +913,7 @@ void EmojiPanInner::selectEmoji(EmojiPtr emoji) { } _saveConfigTimer.start(SaveRecentEmojisTimeout); - emit emojiSelected(emoji); + emit selected(emoji); } void EmojiPanInner::onSaveConfig() { @@ -1050,15 +925,15 @@ void EmojiPanInner::onShowPicker() { if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) { int32 y = 0; for (int c = 0; c <= tab; ++c) { - int32 size = (c == tab) ? (sel - (sel % EmojiPadPerRow)) : _counts[c], rows = (size / EmojiPadPerRow) + ((size % EmojiPadPerRow) ? 1 : 0); + int32 size = (c == tab) ? (sel - (sel % EmojiPanPerRow)) : _counts[c], rows = (size / EmojiPanPerRow) + ((size % EmojiPanPerRow) ? 1 : 0); y += st::emojiPanHeader + (rows * st::emojiPanSize.height()); } y -= _picker.height() - st::emojiPanRound; if (y < _top) { y += _picker.height() - st::emojiPanRound + st::emojiPanSize.height() - st::emojiPanRound; } - int xmax = width() - _picker.width() - st::emojiPanPadding.right(); - float64 coef = float64(sel % EmojiPadPerRow) / float64(EmojiPadPerRow - 1); + int xmax = width() - _picker.width(); + float64 coef = float64(sel % EmojiPanPerRow) / float64(EmojiPanPerRow - 1); if (rtl()) coef = 1. - coef; _picker.move(qRound(xmax * coef), y); @@ -1119,11 +994,11 @@ void EmojiPanInner::enterFromChildEvent(QEvent *e) { void EmojiPanInner::clearSelection(bool fast) { _lastMousePos = mapToGlobal(QPoint(-10, -10)); if (fast) { - for (EmojiAnimations::const_iterator i = _emojiAnimations.cbegin(); i != _emojiAnimations.cend(); ++i) { + for (Animations::const_iterator i = _animations.cbegin(); i != _animations.cend(); ++i) { int index = qAbs(i.key()) - 1, tab = (index / emojiTabShift), sel = index % emojiTabShift; - _hovers[tab][sel] = 0; + (index == SwitcherSelected ? _switcherHover : _hovers[tab][sel]) = 0; } - _emojiAnimations.clear(); + _animations.clear(); _selected = _pressedSel = -1; anim::stop(this); } else { @@ -1136,26 +1011,12 @@ DBIEmojiTab EmojiPanInner::currentTab(int yOffset) const { for (int c = 0; c < emojiTabCount; ++c) { int cnt = _counts[c]; y = ytill; - ytill = y + st::emojiPanHeader + ((cnt / EmojiPadPerRow) + ((cnt % EmojiPadPerRow) ? 1 : 0)) * st::emojiPanSize.height(); + ytill = y + st::emojiPanHeader + ((cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0)) * st::emojiPanSize.height(); if (yOffset < ytill) { return emojiTabAtIndex(c); } } - return dbietStickers; -} - -void EmojiPanInner::refreshStickers() { - clearSelection(true); - int32 cnt = cRecentStickers().size(); - _hovers[emojiTabCount] = QVector(cnt * 2, 0); - _stickers.resize(cnt); - _isUserGen.resize(cnt); - for (int32 i = 0; i < cnt; ++i) { - _stickers[i] = cRecentStickers().at(i).first; - _isUserGen[i] = (cRecentStickers().at(i).second < 0); - } - int32 h = countHeight(); - if (h != height()) resize(width(), h); + return emojiTabAtIndex(emojiTabCount - 1); } void EmojiPanInner::hideFinish() { @@ -1167,9 +1028,7 @@ void EmojiPanInner::hideFinish() { void EmojiPanInner::refreshRecent() { clearSelection(true); - _count -= _counts[0]; _counts[0] = emojiPackCount(dbietRecent); - _count += _counts[0]; if (_hovers[0].size() != _counts[0]) _hovers[0] = QVector(_counts[0], 0); _emojis[0] = emojiPack(dbietRecent); int32 h = countHeight(); @@ -1181,67 +1040,49 @@ void EmojiPanInner::updateSelected() { int32 selIndex = -1; QPoint p(mapFromGlobal(_lastMousePos)); - - int y, ytill = 0, sx = (rtl() ? width() - p.x() : p.x()); - for (int c = 0; c < emojiTabCount; ++c) { - int cnt = _counts[c]; - y = ytill; - ytill = y + st::emojiPanHeader + ((cnt / EmojiPadPerRow) + ((cnt % EmojiPadPerRow) ? 1 : 0)) * st::emojiPanSize.height(); - if (p.y() >= y && p.y() < ytill) { - y += st::emojiPanHeader; - if (p.y() >= y && sx >= 0 && sx < EmojiPadPerRow * st::emojiPanSize.width()) { - selIndex = qFloor((p.y() - y) / st::emojiPanSize.height()) * EmojiPadPerRow + qFloor(sx / st::emojiPanSize.width()); - if (selIndex >= _emojis[c].size()) { - selIndex = -1; - } else { - selIndex += c * emojiTabShift; - } - } - break; + if (p.y() < _top + st::emojiPanHeader) { + bool upon1 = rtl() && p.x() >= 0 && p.x() < st::emojiSwitchSkip + _stickersWidth + (st::emojiSwitchSkip - st::emojiSwitchImgSkip); + bool upon2 = !rtl() && p.x() < width() && p.x() >= width() - st::emojiSwitchSkip - _stickersWidth - (st::emojiSwitchSkip - st::emojiSwitchImgSkip); + if (upon1 || upon2) { + selIndex = SwitcherSelected; } - } - ytill += st::emojiPanHeader; - if (p.y() >= ytill) { - float64 stickerWidth = width() / float64(StickerPadPerRow); - int32 stickerSize = int32(stickerWidth); - if (sx >= 0 && sx < StickerPadPerRow * stickerWidth) { - selIndex = qFloor((p.y() - ytill) / stickerSize) * StickerPadPerRow + qFloor(sx / stickerWidth); - if (selIndex >= _stickers.size()) { - selIndex = -1; - } else { - int32 inx = sx - (selIndex % StickerPadPerRow) * stickerWidth, iny = p.y() - ytill - ((selIndex / StickerPadPerRow) * stickerSize); - if (inx >= stickerWidth - st::stickerPanDelete.pxWidth() && iny < st::stickerPanDelete.pxHeight()) { - selIndex = _stickers.size() + selIndex; + } else { + int y, ytill = 0, sx = (rtl() ? width() - p.x() : p.x()) - st::emojiPanPadding; + for (int c = 0; c < emojiTabCount; ++c) { + int cnt = _counts[c]; + y = ytill; + ytill = y + st::emojiPanHeader + ((cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0)) * st::emojiPanSize.height(); + if (p.y() >= y && p.y() < ytill) { + y += st::emojiPanHeader; + if (p.y() >= y && sx >= 0 && sx < EmojiPanPerRow * st::emojiPanSize.width()) { + selIndex = qFloor((p.y() - y) / st::emojiPanSize.height()) * EmojiPanPerRow + qFloor(sx / st::emojiPanSize.width()); + if (selIndex >= _emojis[c].size()) { + selIndex = -1; + } else { + selIndex += c * emojiTabShift; + } } - selIndex += emojiTabCount * emojiTabShift; + break; } } - } - + bool startanim = false; - int oldSel = _selected, xOldSel = -1, newSel = selIndex, xNewSel = -1; - if (oldSel >= emojiTabCount * emojiTabShift + _stickers.size()) { - xOldSel = oldSel; - oldSel -= _stickers.size(); - } - if (newSel >= emojiTabCount * emojiTabShift + _stickers.size()) { - xNewSel = newSel; - newSel -= _stickers.size(); - } + int oldSel = _selected, newSel = selIndex; + if (newSel != oldSel) { if (oldSel >= 0) { - _emojiAnimations.remove(oldSel + 1); - if (_emojiAnimations.find(-oldSel - 1) == _emojiAnimations.end()) { - if (_emojiAnimations.isEmpty()) startanim = true; - _emojiAnimations.insert(-oldSel - 1, getms()); + _animations.remove(oldSel + 1); + if (_animations.find(-oldSel - 1) == _animations.end()) { + if (_animations.isEmpty()) startanim = true; + _animations.insert(-oldSel - 1, getms()); } } if (newSel >= 0) { - _emojiAnimations.remove(-newSel - 1); - if (_emojiAnimations.find(newSel + 1) == _emojiAnimations.end()) { - if (_emojiAnimations.isEmpty()) startanim = true; - _emojiAnimations.insert(newSel + 1, getms()); + _animations.remove(-newSel - 1); + if (_animations.find(newSel + 1) == _animations.end()) { + if (_animations.isEmpty()) startanim = true; + _animations.insert(newSel + 1, getms()); } } setCursor((newSel >= 0) ? style::cur_pointer : style::cur_default); @@ -1253,41 +1094,26 @@ void EmojiPanInner::updateSelected() { } } } - if (xNewSel != xOldSel) { - if (xOldSel >= 0) { - _emojiAnimations.remove(xOldSel + 1); - if (_emojiAnimations.find(-xOldSel - 1) == _emojiAnimations.end()) { - if (_emojiAnimations.isEmpty()) startanim = true; - _emojiAnimations.insert(-xOldSel - 1, getms()); - } - } - if (xNewSel >= 0) { - _emojiAnimations.remove(-xNewSel - 1); - if (_emojiAnimations.find(xNewSel + 1) == _emojiAnimations.end()) { - if (_emojiAnimations.isEmpty()) startanim = true; - _emojiAnimations.insert(xNewSel + 1, getms()); - } - } - } + _selected = selIndex; if (startanim) anim::start(this); } bool EmojiPanInner::animStep(float64 ms) { uint64 now = getms(); - for (EmojiAnimations::iterator i = _emojiAnimations.begin(); i != _emojiAnimations.end();) { + for (Animations::iterator i = _animations.begin(); i != _animations.end();) { int index = qAbs(i.key()) - 1, tab = (index / emojiTabShift), sel = index % emojiTabShift; float64 dt = float64(now - i.value()) / st::emojiPanDuration; if (dt >= 1) { - _hovers[tab][sel] = (i.key() > 0) ? 1 : 0; - i = _emojiAnimations.erase(i); + (index == SwitcherSelected ? _switcherHover : _hovers[tab][sel]) = (i.key() > 0) ? 1 : 0; + i = _animations.erase(i); } else { - _hovers[tab][sel] = (i.key() > 0) ? dt : (1 - dt); + (index == SwitcherSelected ? _switcherHover : _hovers[tab][sel]) = (i.key() > 0) ? dt : (1 - dt); ++i; } } update(); - return !_emojiAnimations.isEmpty(); + return !_animations.isEmpty(); } void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) { @@ -1298,7 +1124,7 @@ void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) { int32 y = 0; for (int c = 0; c < emojiTabCount; ++c) { if (emojiTabAtIndex(c) == packIndex) break; - int rows = (_counts[c] / EmojiPadPerRow) + ((_counts[c] % EmojiPadPerRow) ? 1 : 0); + int rows = (_counts[c] / EmojiPanPerRow) + ((_counts[c] % EmojiPanPerRow) ? 1 : 0); y += st::emojiPanHeader + rows * st::emojiPanSize.height(); } @@ -1309,6 +1135,482 @@ void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) { update(); } +StickerPanInner::StickerPanInner(QWidget *parent) : TWidget(parent), +_top(0), _selected(-1), _pressedSel(-1), +_switcherHover(0), _emojiWidth(st::emojiPanHeaderFont->m.width(lang(lng_switch_emoji))) { + resize(st::emojiPanFullSize.width(), countHeight()); + setMouseTracking(true); + setFocusPolicy(Qt::NoFocus); + + connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); + + refreshStickers(); +} + +void StickerPanInner::setScrollTop(int top) { + if (top == _top) return; + + QRegion upd = QRect(0, _top, width(), st::emojiPanHeader); + _top = top; + upd += QRect(0, _top, width(), st::emojiPanHeader); + repaint(upd); + updateSelected(); +} + +int StickerPanInner::countHeight() { + int result = 0; + for (int i = 0; i < _sets.size(); ++i) { + int cnt = _sets.at(i).size(), rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0); + result += st::emojiPanHeader + rows * st::stickerPanSize.height(); + } + return result + st::stickerPanPadding; +} + +void StickerPanInner::paintEvent(QPaintEvent *e) { + Painter p(this); + QRect r = e ? e->rect() : rect(); + if (r != rect()) { + p.setClipRect(r); + } + p.fillRect(r, st::white->b); + int32 y, tilly = 0; + for (int c = 0, l = _sets.size(); c < l; ++c) { + y = tilly; + int32 size = _sets.at(c).size(); + int32 rows = (size / StickerPanPerRow) + ((size % StickerPanPerRow) ? 1 : 0); + tilly = y + st::emojiPanHeader + (rows * st::stickerPanSize.height()); + if (r.top() >= tilly) continue; + + bool special = (_setIds[c] == DefaultStickerSetId || _setIds[c] == CustomStickerSetId || _setIds[c] == RecentStickerSetId); + y += st::emojiPanHeader; + + QString title = _titles[c]; + if (r.bottom() <= y) { + p.setFont(st::emojiPanHeaderFont->f); + p.setPen(st::emojiPanHeaderColor->p); + p.drawTextLeft(st::emojiPanHeaderLeft, qMax(y - int(st::emojiPanHeader), _top) + st::emojiPanHeaderTop, width(), title); + if (!special && y >= _top + 2 * st::emojiPanHeader) { + p.setOpacity(st::stickerPanDeleteOpacity + (1 - st::stickerPanDeleteOpacity) * _hovers[c][size]); + p.drawSpriteRight(QPoint(st::emojiPanHeaderLeft, y - (st::emojiPanHeader + st::notifyClose.icon.pxHeight()) / 2), width(), st::notifyClose.icon); + p.setOpacity(1); + } + break; + } + + int32 fromrow = (r.top() <= y) ? 0 : qMax(qFloor((r.top() - y) / st::stickerPanSize.height()), 0), torow = qMin(qCeil((r.bottom() - y) / st::stickerPanSize.height()) + 1, rows); + for (int32 i = fromrow; i < torow; ++i) { + for (int32 j = 0; j < StickerPanPerRow; ++j) { + int32 index = i * StickerPanPerRow + j; + if (index >= size) break; + + float64 hover = _hovers[c][index]; + + DocumentData *sticker = _sets[c][index]; + if (!sticker->sticker) continue; + + QPoint pos(st::stickerPanPadding + j * st::stickerPanSize.width(), y + i * st::stickerPanSize.height()); + if (hover > 0) { + p.setOpacity(hover); + p.setBrush(st::emojiPanHover->b); + p.setPen(Qt::NoPen); + QPoint tl(pos); + if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width()); + p.drawRoundedRect(QRect(tl, st::stickerPanSize), st::stickerPanRound, st::stickerPanRound); + p.setOpacity(1); + } + + bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); + if (goodThumb) { + sticker->thumb->load(); + } else { + bool already = !sticker->already().isEmpty(), hasdata = !sticker->data.isEmpty(); + if (!sticker->loader && sticker->status != FileFailed && !already && !hasdata) { + sticker->save(QString()); + } + if (sticker->sticker->img->isNull() && (already || hasdata)) { + if (already) { + sticker->sticker->img = ImagePtr(sticker->already()); + } else { + sticker->sticker->img = ImagePtr(sticker->data); + } + } + } + + float64 coef = qMin((st::stickerPanSize.width() - st::stickerPanRound * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::stickerPanRound * 2) / float64(sticker->dimensions.height())); + if (coef > 1) coef = 1; + int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height()); + if (w < 1) w = 1; + if (h < 1) h = 1; + QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2); + if (goodThumb) { + p.drawPixmapLeft(ppos, width(), sticker->thumb->pix(w, h)); + } else if (!sticker->sticker->img->isNull()) { + p.drawPixmapLeft(ppos, width(), sticker->sticker->img->pix(w, h)); + } + + if (hover > 0 && _setIds[c] == CustomStickerSetId) { + float64 xHover = _hovers[c][_sets[c].size() + index]; + + QPoint xPos = pos + QPoint(st::stickerPanSize.width() - st::stickerPanDelete.pxWidth(), 0); + p.setOpacity(hover * (xHover + (1 - xHover) * st::stickerPanDeleteOpacity)); + p.drawPixmapLeft(xPos, width(), App::sprite(), st::stickerPanDelete); + p.setOpacity(1); + } + } + } + + if (y - st::emojiPanHeader < _top) { + p.fillRect(QRect(0, qMin(_top, tilly - int(st::emojiPanHeader)), width(), st::emojiPanHeader), st::emojiPanHeaderBg->b); + } else if (!special && y >= _top + 2 * st::emojiPanHeader) { + p.setOpacity(st::stickerPanDeleteOpacity + (1 - st::stickerPanDeleteOpacity) * _hovers[c][size]); + p.drawSpriteRight(QPoint(st::emojiPanHeaderLeft, y - (st::emojiPanHeader + st::notifyClose.icon.pxHeight()) / 2), width(), st::notifyClose.icon); + p.setOpacity(1); + } + + p.setFont(st::emojiPanHeaderFont->f); + p.setPen(st::emojiPanHeaderColor->p); + p.drawTextLeft(st::emojiPanHeaderLeft, qMin(qMax(y - int(st::emojiPanHeader), _top), tilly - int(st::emojiPanHeader)) + st::emojiPanHeaderTop, width(), title); + } + + p.setFont(st::emojiPanHeaderFont->f); + p.setPen(st::emojiSwitchColor->p); + p.drawTextRight(st::emojiSwitchImgSkip - st::emojiSwitchEmoji.pxWidth(), _top + st::emojiPanHeaderTop, width(), lang(lng_switch_emoji), _emojiWidth); + p.drawSpriteRight(QPoint(st::emojiSwitchSkip + _emojiWidth - st::emojiSwitchEmoji.pxWidth(), _top + (st::emojiPanHeader - st::emojiSwitchEmoji.pxHeight()) / 2), width(), st::emojiSwitchEmoji); +} + +void StickerPanInner::mousePressEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); + + _pressedSel = _selected; +} + +void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { + int32 pressed = _pressedSel; + _pressedSel = -1; + + _lastMousePos = e->globalPos(); + updateSelected(); + + if (_selected < 0 || _selected != pressed) return; + if (_selected == SwitcherSelected) { + emit switchToEmoji(); + return; + } + if (_selected >= emojiTabShift * _setIds.size()) { + return; + } + + int tab = (_selected / emojiTabShift), sel = _selected % emojiTabShift; + if (_setIds[tab] == CustomStickerSetId && sel >= _sets[tab].size() && sel < _sets[tab].size() * 2) { + clearSelection(true); + int32 refresh = 0; + DocumentData *sticker = _sets[tab].at(sel - _sets[tab].size()); + RecentStickerPack &recent(cGetRecentStickers()); + for (int32 i = 0, l = recent.size(); i < l; ++i) { + if (recent.at(i).first == sticker) { + recent.removeAt(i); + Local::writeUserSettings(); + refresh = 1; + break; + } + } + StickerSets &sets(cRefStickerSets()); + StickerSets::iterator it = sets.find(CustomStickerSetId); + if (it != sets.cend()) { + for (int32 i = 0, l = it->stickers.size(); i < l; ++i) { + if (it->stickers.at(i) == sticker) { + it->stickers.removeAt(i); + if (it->stickers.isEmpty()) { + sets.erase(it); + } + Local::writeStickers(); + refresh = 2; + break; + } + } + } + if (refresh) { + if (refresh > 1) { + refreshStickers(); + } else { + refreshRecent(); + } + updateSelected(); + update(); + } + return; + } + if (sel < _sets[tab].size()) { + emit selected(_sets[tab][sel]); + } else if (sel == _sets[tab].size()) { + emit removing(_setIds[tab]); + } +} + +void StickerPanInner::mouseMoveEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); +} + +void StickerPanInner::leaveEvent(QEvent *e) { + clearSelection(); +} + +void StickerPanInner::leaveToChildEvent(QEvent *e) { + clearSelection(); +} + +void StickerPanInner::enterFromChildEvent(QEvent *e) { + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +void StickerPanInner::clearSelection(bool fast) { + _lastMousePos = mapToGlobal(QPoint(-10, -10)); + if (fast) { + for (Animations::const_iterator i = _animations.cbegin(); i != _animations.cend(); ++i) { + int index = qAbs(i.key()) - 1, tab = (index / emojiTabShift), sel = index % emojiTabShift; + (index == SwitcherSelected ? _switcherHover : _hovers[tab][sel]) = 0; + } + _animations.clear(); + _selected = _pressedSel = -1; + anim::stop(this); + } else { + updateSelected(); + } +} + +void StickerPanInner::refreshStickers() { + clearSelection(true); + + const StickerSets &sets(cStickerSets()); + _setIds.clear(); _setIds.reserve(sets.size() + 1); + _sets.clear(); _sets.reserve(sets.size() + 1); + _hovers.clear(); _hovers.reserve(sets.size() + 1); + _titles.clear(); _titles.reserve(sets.size() + 1); + + refreshRecent(false); + + StickerSets::const_iterator it; + + it = sets.constFind(CustomStickerSetId); if (it != sets.cend()) appendSet(it); + it = sets.constFind(DefaultStickerSetId); if (it != sets.cend()) appendSet(it); + + for (it = sets.cbegin(); it != sets.cend(); ++it) { + if (it->id != CustomStickerSetId && it->id != DefaultStickerSetId) appendSet(it); + } + + int32 h = countHeight(); + if (h != height()) resize(width(), h); + + updateSelected(); +} + +void StickerPanInner::preloadImages() { + uint64 ms = getms(); + for (int32 i = 0, l = _sets.size(), k = 0; i < l; ++i) { + for (int32 j = 0, n = _sets.at(i).size(); j < n; ++j) { + if (++k > StickerPanPerRow * (StickerPanPerRow + 1)) break; + + DocumentData *sticker = _sets.at(i).at(j); + if (!sticker || !sticker->sticker) continue; + + bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); + if (goodThumb) { + sticker->thumb->load(); + } else { + bool already = !sticker->already().isEmpty(), hasdata = !sticker->data.isEmpty(); + if (!sticker->loader && sticker->status != FileFailed && !already && !hasdata) { + sticker->save(QString()); + } + //if (sticker->sticker->img->isNull() && (already || hasdata)) { + // if (already) { + // sticker->sticker->img = ImagePtr(sticker->already()); + // } else { + // sticker->sticker->img = ImagePtr(sticker->data); + // } + //} + } + } + if (k > StickerPanPerRow * (StickerPanPerRow + 1)) break; + } +} + +void StickerPanInner::appendSet(StickerSets::const_iterator it) { + if (it->stickers.isEmpty()) return; + + StickerPack pack; + pack.reserve(it->stickers.size()); + for (int32 i = 0, l = it->stickers.size(); i < l; ++i) { + pack.push_back(it->stickers.at(i)); + } + _setIds.push_back(it->id); + _sets.push_back(pack); + _hovers.push_back(QVector(it->stickers.size() + (it->id == CustomStickerSetId ? it->stickers.size() : 1), 0)); + int32 availw = width() - st::emojiPanHeaderLeft - st::emojiSwitchSkip - _emojiWidth - (st::emojiSwitchSkip - st::emojiSwitchImgSkip); + _titles.push_back(st::emojiPanHeaderFont->m.elidedText(it->title, Qt::ElideRight, availw)); +} + +void StickerPanInner::refreshRecent(bool performResize) { + clearSelection(true); + if (cGetRecentStickers().isEmpty()) { + if (!_setIds.isEmpty() && _setIds.at(0) == RecentStickerSetId) { + _setIds.pop_front(); + _sets.pop_front(); + _hovers.pop_front(); + _titles.pop_front(); + } + } else { + StickerPack recent; + recent.reserve(cGetRecentStickers().size()); + for (int32 i = 0, l = cGetRecentStickers().size(); i < l; ++i) { + recent.push_back(cGetRecentStickers().at(i).first); + } + if (_setIds.isEmpty() || _setIds.at(0) != RecentStickerSetId) { + _setIds.push_front(RecentStickerSetId); + _hovers.push_back(QVector(recent.size() + 1, 0)); + _sets.push_back(recent); + _titles.push_back(lang(lng_emoji_category0)); + } else { + _sets[0] = recent; + _hovers[0].resize(recent.size() + 1); + } + } + + if (performResize) { + int32 h = countHeight(); + if (h != height()) resize(width(), h); + + updateSelected(); + } +} + +void StickerPanInner::updateSelected() { + if (_pressedSel >= 0) return; + + int32 selIndex = -1; + QPoint p(mapFromGlobal(_lastMousePos)); + + if (p.y() < _top + st::emojiPanHeader) { + bool upon1 = rtl() && p.x() >= 0 && p.x() < st::emojiSwitchSkip + _emojiWidth + (st::emojiSwitchSkip - st::emojiSwitchImgSkip); + bool upon2 = !rtl() && p.x() < width() && p.x() >= width() - st::emojiSwitchSkip - _emojiWidth - (st::emojiSwitchSkip - st::emojiSwitchImgSkip); + if (upon1 || upon2) { + selIndex = SwitcherSelected; + } + } else { + int y, ytill = 0, sx = (rtl() ? width() - p.x() : p.x()) - st::stickerPanPadding; + for (int c = 0, l = _setIds.size(); c < l; ++c) { + int cnt = _sets[c].size(); + bool special = _setIds[c] == DefaultStickerSetId || _setIds[c] == CustomStickerSetId || _setIds[c] == RecentStickerSetId; + y = ytill; + ytill = y + st::emojiPanHeader + ((cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0)) * st::stickerPanSize.height(); + if (p.y() >= y && p.y() < ytill) { + if (!special && p.y() >= y && p.y() < y + st::emojiPanHeader && sx + st::stickerPanPadding >= width() - st::emojiPanHeaderLeft - st::notifyClose.icon.pxWidth() && sx + st::stickerPanPadding < width() - st::emojiPanHeaderLeft) { + selIndex = c * emojiTabShift + _sets[c].size(); + } else { + y += st::emojiPanHeader; + if (p.y() >= y && sx >= 0 && sx < StickerPanPerRow * st::stickerPanSize.width()) { + selIndex = qFloor((p.y() - y) / st::stickerPanSize.height()) * StickerPanPerRow + qFloor(sx / st::stickerPanSize.width()); + if (selIndex >= _sets[c].size()) { + selIndex = -1; + } else { + if (_setIds[c] == CustomStickerSetId) { + int32 inx = sx - (selIndex % StickerPanPerRow) * st::stickerPanSize.width(), iny = p.y() - y - ((selIndex / StickerPanPerRow) * st::stickerPanSize.height()); + if (inx >= st::stickerPanSize.width() - st::stickerPanDelete.pxWidth() && iny < st::stickerPanDelete.pxHeight()) { + selIndex += _sets[c].size(); + } + } + selIndex += c * emojiTabShift; + } + } + } + break; + } + } + } + + bool startanim = false; + int oldSel = _selected, oldSelTab = oldSel / emojiTabShift, xOldSel = -1, newSel = selIndex, newSelTab = newSel / emojiTabShift, xNewSel = -1; + if (oldSel >= 0 && oldSelTab < _setIds.size() && _setIds[oldSelTab] == CustomStickerSetId && oldSel >= oldSelTab * emojiTabShift + _sets[oldSelTab].size()) { + xOldSel = oldSel; + oldSel -= _sets[oldSelTab].size(); + } + if (newSel >= 0 && newSelTab < _setIds.size() && _setIds[newSelTab] == CustomStickerSetId && newSel >= newSelTab * emojiTabShift + _sets[newSelTab].size()) { + xNewSel = newSel; + newSel -= _sets[newSelTab].size(); + } + if (newSel != oldSel) { + if (oldSel >= 0) { + _animations.remove(oldSel + 1); + if (_animations.find(-oldSel - 1) == _animations.end()) { + if (_animations.isEmpty()) startanim = true; + _animations.insert(-oldSel - 1, getms()); + } + } + if (newSel >= 0) { + _animations.remove(-newSel - 1); + if (_animations.find(newSel + 1) == _animations.end()) { + if (_animations.isEmpty()) startanim = true; + _animations.insert(newSel + 1, getms()); + } + } + setCursor((newSel >= 0) ? style::cur_pointer : style::cur_default); + } + if (xNewSel != xOldSel) { + if (xOldSel >= 0) { + _animations.remove(xOldSel + 1); + if (_animations.find(-xOldSel - 1) == _animations.end()) { + if (_animations.isEmpty()) startanim = true; + _animations.insert(-xOldSel - 1, getms()); + } + } + if (xNewSel >= 0) { + _animations.remove(-xNewSel - 1); + if (_animations.find(xNewSel + 1) == _animations.end()) { + if (_animations.isEmpty()) startanim = true; + _animations.insert(xNewSel + 1, getms()); + } + } + } + _selected = selIndex; + if (startanim) anim::start(this); +} + +bool StickerPanInner::animStep(float64 ms) { + uint64 now = getms(); + for (Animations::iterator i = _animations.begin(); i != _animations.end();) { + int index = qAbs(i.key()) - 1, tab = (index / emojiTabShift), sel = index % emojiTabShift; + float64 dt = float64(now - i.value()) / st::emojiPanDuration; + if (dt >= 1) { + (index == SwitcherSelected ? _switcherHover : _hovers[tab][sel]) = (i.key() > 0) ? 1 : 0; + i = _animations.erase(i); + } else { + (index == SwitcherSelected ? _switcherHover : _hovers[tab][sel]) = (i.key() > 0) ? dt : (1 - dt); + ++i; + } + } + update(); + return !_animations.isEmpty(); +} + +void StickerPanInner::showStickerSet(uint64 setId) { + clearSelection(true); + + int32 y = 0; + for (int c = 0; c < _setIds.size(); ++c) { + if (_setIds.at(c) == setId) break; + int rows = (_sets[c].size() / StickerPanPerRow) + ((_sets[c].size() % StickerPanPerRow) ? 1 : 0); + y += st::emojiPanHeader + rows * st::stickerPanSize.height(); + } + + emit scrollToY(y); + + _lastMousePos = QCursor::pos(); + + update(); +} + EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent), _noTabUpdate(false), _hiding(false), a_opacity(0), _shadow(st::dropdownDef.shadow), _recent(this , qsl("emoji_group"), dbietRecent , QString(), true , st::rbEmojiRecent), @@ -1319,63 +1621,73 @@ _celebration(this, qsl("emoji_group"), dbietCelebration, QString(), false, st::r _activity(this , qsl("emoji_group"), dbietActivity , QString(), false, st::rbEmojiActivity), _travel(this , qsl("emoji_group"), dbietTravel , QString(), false, st::rbEmojiTravel), _objects(this , qsl("emoji_group"), dbietObjects , QString(), false, st::rbEmojiObjects), -_stickers(this , qsl("emoji_group"), dbietStickers , QString(), false, st::rbEmojiStickers), -_scroll(this, st::emojiScroll), _inner() { +_stickersShown(false), _moveStart(0), +e_scroll(this, st::emojiScroll), e_inner(), s_scroll(this, st::emojiScroll), s_inner(), _removingSetId(0) { setFocusPolicy(Qt::NoFocus); - _scroll.setFocusPolicy(Qt::NoFocus); - _scroll.viewport()->setFocusPolicy(Qt::NoFocus); + e_scroll.setFocusPolicy(Qt::NoFocus); + e_scroll.viewport()->setFocusPolicy(Qt::NoFocus); + s_scroll.setFocusPolicy(Qt::NoFocus); + s_scroll.viewport()->setFocusPolicy(Qt::NoFocus); - _scroll.resize(st::emojiPanPadding.left() + _inner.width() + st::emojiPanPadding.right(), EmojiPadRowsPerPage * st::emojiPanSize.height() + st::emojiPanHeader); - - _width = st::dropdownDef.padding.left() + st::emojiPanPadding.left() + _scroll.width() + st::emojiPanPadding.right() + st::dropdownDef.padding.right(); - _height = st::dropdownDef.padding.top() + _recent.height() + st::emojiPanPadding.top() + _scroll.height() + st::emojiPanPadding.bottom() + st::dropdownDef.padding.bottom(); + _width = st::dropdownDef.padding.left() + st::emojiPanFullSize.width() + st::dropdownDef.padding.right(); + _height = st::dropdownDef.padding.top() + st::emojiPanFullSize.height() + st::dropdownDef.padding.bottom(); resize(_width, _height); - int32 sx = st::dropdownDef.padding.left() + st::emojiPanPadding.left(), sy = st::dropdownDef.padding.top() + _recent.height() + st::emojiPanPadding.top(); - _scroll.move(rtl() ? (_width - _scroll.width() - sx) : sx, sy); - _scroll.setWidget(&_inner); + e_scroll.resize(st::emojiPanFullSize.width(), st::emojiPanFullSize.height() - _recent.height()); + s_scroll.resize(st::emojiPanFullSize.width(), st::emojiPanFullSize.height()); - _inner.setAttribute(Qt::WA_OpaquePaintEvent); - _scroll.setAutoFillBackground(true); + e_scroll.move(st::dropdownDef.padding.left(), st::dropdownDef.padding.top()); + e_scroll.setWidget(&e_inner); + s_scroll.move(st::dropdownDef.padding.left(), st::dropdownDef.padding.top()); + s_scroll.setWidget(&s_inner); - int32 left = st::dropdownDef.padding.left() + (_width - st::dropdownDef.padding.left() - st::dropdownDef.padding.right() - 9 * _recent.width()) / 2; - int32 top = st::dropdownDef.padding.top(); - _recent.moveToLeft(left , top, _width); left += _recent.width(); - _people.moveToLeft(left , top, _width); left += _people.width(); - _nature.moveToLeft(left , top, _width); left += _nature.width(); - _food.moveToLeft(left , top, _width); left += _food.width(); - _celebration.moveToLeft(left, top, _width); left += _celebration.width(); - _activity.moveToLeft(left , top, _width); left += _activity.width(); - _travel.moveToLeft(left , top, _width); left += _travel.width(); - _objects.moveToLeft(left , top, _width); left += _objects.width(); - _stickers.moveToLeft(left , top, _width); left += _stickers.width(); + e_inner.setAttribute(Qt::WA_OpaquePaintEvent); + e_scroll.setAutoFillBackground(true); + s_inner.setAttribute(Qt::WA_OpaquePaintEvent); + s_scroll.setAutoFillBackground(true); + + int32 left = st::dropdownDef.padding.left() + (st::emojiPanFullSize.width() - 8 * _recent.width()) / 2; + int32 top = st::dropdownDef.padding.top() + st::emojiPanFullSize.height() - _recent.height(); + prepareTab(left, top, _width, _recent); + prepareTab(left, top, _width, _people); + prepareTab(left, top, _width, _nature); + prepareTab(left, top, _width, _food); + prepareTab(left, top, _width, _celebration); + prepareTab(left, top, _width, _activity); + prepareTab(left, top, _width, _travel); + prepareTab(left, top, _width, _objects); _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); - connect(&_inner, SIGNAL(scrollToY(int)), &_scroll, SLOT(scrollToY(int))); - connect(&_inner, SIGNAL(disableScroll(bool)), &_scroll, SLOT(disableScroll(bool))); + connect(&e_inner, SIGNAL(scrollToY(int)), &e_scroll, SLOT(scrollToY(int))); + connect(&e_inner, SIGNAL(disableScroll(bool)), &e_scroll, SLOT(disableScroll(bool))); - connect(&_recent , SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_people , SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_nature , SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_food , SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_celebration, SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_activity , SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_travel , SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_objects , SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_stickers , SIGNAL(changed()), this, SLOT(onTabChange())); + connect(&s_inner, SIGNAL(scrollToY(int)), &s_scroll, SLOT(scrollToY(int))); - connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(&e_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(&s_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); - connect(&_inner, SIGNAL(emojiSelected(EmojiPtr)), this, SIGNAL(emojiSelected(EmojiPtr))); - connect(&_inner, SIGNAL(stickerSelected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*))); + connect(&e_inner, SIGNAL(selected(EmojiPtr)), this, SIGNAL(emojiSelected(EmojiPtr))); + connect(&s_inner, SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*))); + + connect(&e_inner, SIGNAL(switchToStickers()), this, SLOT(onSwitch())); + connect(&s_inner, SIGNAL(switchToEmoji()), this, SLOT(onSwitch())); + + connect(&s_inner, SIGNAL(removing(uint64)), this, SLOT(onRemoveSet(uint64))); if (cPlatform() == dbipMac) { connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); } } +void EmojiPan::prepareTab(int32 &left, int32 top, int32 _width, FlatRadiobutton &tab) { + tab.moveToLeft(left, top, _width); + left += tab.width(); + tab.setAttribute(Qt::WA_OpaquePaintEvent); + connect(&tab, SIGNAL(changed()), this, SLOT(onTabChange())); +} + void EmojiPan::onWndActiveChanged() { if (!App::wnd()->windowHandle()->isActive() && !isHidden()) { leaveEvent(0); @@ -1385,21 +1697,54 @@ void EmojiPan::onWndActiveChanged() { void EmojiPan::paintEvent(QPaintEvent *e) { QPainter p(this); + float64 o = 1; if (!_cache.isNull()) { - p.setOpacity(a_opacity.current()); + p.setOpacity(o = a_opacity.current()); } QRect r(st::dropdownDef.padding.left(), st::dropdownDef.padding.top(), _width - st::dropdownDef.padding.left() - st::dropdownDef.padding.right(), _height - st::dropdownDef.padding.top() - st::dropdownDef.padding.bottom()); _shadow.paint(p, r); - if (_cache.isNull()) { - p.fillRect(r.left(), r.top(), r.width(), _scroll.y() - r.top(), st::white->b); - p.fillRect(r.left(), _scroll.y(), _scroll.x() - r.left(), _scroll.height(), st::white->b); - p.fillRect(_scroll.x() + _scroll.width(), _scroll.y(), r.left() + r.width() - _scroll.x() - _scroll.width(), _scroll.height(), st::white->b); - p.fillRect(r.left(), _scroll.y() + _scroll.height(), r.width(), r.top() + r.height() - _scroll.y() - _scroll.height(), st::white->b); + if (_toCache.isNull()) { + if (_cache.isNull()) { + if (!_stickersShown) { + p.fillRect(r.left(), _recent.y(), (rtl() ? _objects.x() : _recent.x() - r.left()), _recent.height(), st::emojiPanCategories->b); + int32 x = rtl() ? (_recent.x() + _recent.width()) : (_objects.x() + _objects.width()); + p.fillRect(x, _recent.y(), r.left() + r.width() - x, _recent.height(), st::emojiPanCategories->b); + } + } else { + p.fillRect(r, st::white->b); + p.drawPixmap(r.left(), r.top(), _cache); + } } else { - p.drawPixmap(r.left(), r.top(), _cache); + p.fillRect(r, st::white->b); + p.setOpacity(o * a_fromAlpha.current()); + QRect fromDst = QRect(r.left() + a_fromCoord.current(), r.top(), _fromCache.width() / cIntRetinaFactor(), _fromCache.height() / cIntRetinaFactor()); + QRect fromSrc = QRect(0, 0, _fromCache.width(), _fromCache.height()); + if (fromDst.x() < r.left() + r.width() && fromDst.x() + fromDst.width() > r.left()) { + if (fromDst.x() < r.left()) { + fromSrc.setX((r.left() - fromDst.x()) * cIntRetinaFactor()); + fromDst.setX(r.left()); + } else if (fromDst.x() + fromDst.width() > r.left() + r.width()) { + fromSrc.setWidth((r.left() + r.width() - fromDst.x()) * cIntRetinaFactor()); + fromDst.setWidth(r.left() + r.width() - fromDst.x()); + } + p.drawPixmap(fromDst, _fromCache, fromSrc); + } + p.setOpacity(o * a_toAlpha.current()); + QRect toDst = QRect(r.left() + a_toCoord.current(), r.top(), _toCache.width() / cIntRetinaFactor(), _toCache.height() / cIntRetinaFactor()); + QRect toSrc = QRect(0, 0, _toCache.width(), _toCache.height()); + if (toDst.x() < r.left() + r.width() && toDst.x() + toDst.width() > r.left()) { + if (toDst.x() < r.left()) { + toSrc.setX((r.left() - toDst.x()) * cIntRetinaFactor()); + toDst.setX(r.left()); + } else if (toDst.x() + toDst.width() > r.left() + r.width()) { + toSrc.setWidth((r.left() + r.width() - toDst.x()) * cIntRetinaFactor()); + toDst.setWidth(r.left() + r.width() - toDst.x()); + } + p.drawPixmap(toDst, _toCache, toSrc); + } } } @@ -1409,6 +1754,7 @@ void EmojiPan::enterEvent(QEvent *e) { } void EmojiPan::leaveEvent(QEvent *e) { + if (_removingSetId) return; if (animating()) { hideStart(); } else { @@ -1440,32 +1786,62 @@ void EmojiPan::fastHide() { } void EmojiPan::refreshStickers() { - _inner.refreshStickers(); + s_inner.refreshStickers(); + if (!_stickersShown) { + s_inner.preloadImages(); + } } bool EmojiPan::animStep(float64 ms) { - float64 dt = ms / st::dropdownDef.duration; - bool res = true; - if (dt >= 1) { - a_opacity.finish(); - if (_hiding) { - hideFinish(); + bool res1 = false; + if (_moveStart) { + float64 movems = getms() - _moveStart; + float64 fullDuration = st::introSlideDelta + st::introSlideDuration, dt = ms / fullDuration; + float64 dt1 = (movems > st::introSlideDuration) ? 1 : (movems / st::introSlideDuration), dt2 = (movems > st::introSlideDelta) ? (movems - st::introSlideDelta) / (st::introSlideDuration) : 0; + if (dt2 >= 1) { + a_fromCoord.finish(); + a_fromAlpha.finish(); + a_toCoord.finish(); + a_toAlpha.finish(); + _fromCache = _toCache = QPixmap(); + _moveStart = 0; + if (_cache.isNull()) showAll(); } else { - showAll(); - _cache = QPixmap(); + a_fromCoord.update(dt1, st::introHideFunc); + a_fromAlpha.update(dt1, st::introAlphaHideFunc); + a_toCoord.update(dt2, st::introShowFunc); + a_toAlpha.update(dt2, st::introAlphaShowFunc); + res1 = true; + } + } + bool res2 = false; + if (!_cache.isNull()) { + float64 dt = ms / st::dropdownDef.duration; + if (dt >= 1) { + a_opacity.finish(); + if (_hiding) { + res1 = false; + hideFinish(); + } else { + if (_toCache.isNull()) showAll(); + _cache = QPixmap(); + } + } else { + a_opacity.update(dt, anim::linear); + res2 = true; } - res = false; - } else { - a_opacity.update(dt, anim::linear); } update(); - return res; + return res1 || res2; } void EmojiPan::hideStart() { if (_cache.isNull()) { + QPixmap from = _fromCache, to = _toCache; + _fromCache = _toCache = QPixmap(); showAll(); _cache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); + _fromCache = from; _toCache = to; } hideAll(); _hiding = true; @@ -1475,9 +1851,12 @@ void EmojiPan::hideStart() { void EmojiPan::hideFinish() { hide(); - _inner.hideFinish(); - _cache = QPixmap(); - _recent.setChecked(true); + e_inner.hideFinish(); + _cache = _toCache = _fromCache = QPixmap(); + _moveStart = 0; + + e_scroll.scrollToY(0); + s_scroll.scrollToY(0); } void EmojiPan::showStart() { @@ -1485,11 +1864,19 @@ void EmojiPan::showStart() { return; } if (isHidden()) { - _inner.refreshRecent(); + e_inner.refreshRecent(); + s_inner.refreshRecent(); + s_inner.preloadImages(); + _stickersShown = false; + _fromCache = _toCache = QPixmap(); + _moveStart = 0; } if (_cache.isNull()) { + QPixmap from = _fromCache, to = _toCache; + _fromCache = _toCache = QPixmap(); showAll(); _cache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); + _fromCache = from; _toCache = to; } hideAll(); _hiding = false; @@ -1522,17 +1909,43 @@ bool EmojiPan::eventFilter(QObject *obj, QEvent *e) { return false; } +void EmojiPan::stickersInstalled(uint64 setId) { + _stickersShown = true; + if (isHidden()) { + show(); + a_opacity = anim::fvalue(0, 1); + a_opacity.update(0, anim::linear); + _cache = _fromCache = _toCache = QPixmap(); + } + showAll(); + s_inner.showStickerSet(setId); + showStart(); +} + void EmojiPan::showAll() { - _recent.show(); - _people.show(); - _nature.show(); - _food.show(); - _celebration.show(); - _activity.show(); - _travel.show(); - _objects.show(); - _stickers.show(); - _scroll.show(); + if (_stickersShown) { + s_scroll.show(); + _recent.hide(); + _people.hide(); + _nature.hide(); + _food.hide(); + _celebration.hide(); + _activity.hide(); + _travel.hide(); + _objects.hide(); + e_scroll.hide(); + } else { + s_scroll.hide(); + _recent.show(); + _people.show(); + _nature.show(); + _food.show(); + _celebration.show(); + _activity.show(); + _travel.show(); + _objects.show(); + e_scroll.show(); + } } void EmojiPan::hideAll() { @@ -1544,9 +1957,10 @@ void EmojiPan::hideAll() { _activity.hide(); _travel.hide(); _objects.hide(); - _stickers.hide(); - _scroll.hide(); - _inner.clearSelection(true); + e_scroll.hide(); + s_scroll.hide(); + e_inner.clearSelection(true); + s_inner.clearSelection(true); } void EmojiPan::onTabChange() { @@ -1559,13 +1973,12 @@ void EmojiPan::onTabChange() { else if (_activity.checked()) newTab = dbietActivity; else if (_travel.checked()) newTab = dbietTravel; else if (_objects.checked()) newTab = dbietObjects; - else if (_stickers.checked()) newTab = dbietStickers; - _inner.showEmojiPack(newTab); + e_inner.showEmojiPack(newTab); } void EmojiPan::onScroll() { - int top = _scroll.scrollTop(); - DBIEmojiTab tab = _inner.currentTab(top); + int top = e_scroll.scrollTop(); + DBIEmojiTab tab = e_inner.currentTab(top); FlatRadiobutton *check = 0; switch (tab) { case dbietRecent : check = &_recent ; break; @@ -1576,14 +1989,82 @@ void EmojiPan::onScroll() { case dbietActivity : check = &_activity ; break; case dbietTravel : check = &_travel ; break; case dbietObjects : check = &_objects ; break; - case dbietStickers : check = &_stickers ; break; } if (check && !check->checked()) { _noTabUpdate = true; check->setChecked(true); _noTabUpdate = false; } - _inner.setScrollTop(top); + e_inner.setScrollTop(top); + s_inner.setScrollTop(s_scroll.scrollTop()); +} + +void EmojiPan::onSwitch() { + QPixmap cache = _cache; + _fromCache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); + _stickersShown = !_stickersShown; + + _cache = QPixmap(); + showAll(); + _toCache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); + _cache = cache; + + hideAll(); + _moveStart = getms(); + + a_toCoord = _stickersShown ? anim::ivalue(st::emojiPanFullSize.width(), 0) : anim::ivalue(-st::emojiPanFullSize.width(), 0); + a_toAlpha = anim::fvalue(0, 1); + a_fromCoord = _stickersShown ? anim::ivalue(0, -st::emojiPanFullSize.width()) : anim::ivalue(0, st::emojiPanFullSize.width()); + a_fromAlpha = anim::fvalue(1, 0); + + if (!animating()) anim::start(this); + update(); +} + +void EmojiPan::onRemoveSet(uint64 setId) { + StickerSets::const_iterator it = cStickerSets().constFind(setId); + if (it != cStickerSets().cend() && setId != CustomStickerSetId && setId != DefaultStickerSetId && setId != RecentStickerSetId) { + _removingSetId = it->id; + ConfirmBox *box = new ConfirmBox(lng_stickers_remove_pack(lt_sticker_pack, it->title)); + connect(box, SIGNAL(confirmed()), this, SLOT(onRemoveSetSure())); + connect(box, SIGNAL(destroyed(QObject*)), this, SLOT(onDelayedHide())); + App::wnd()->showLayer(box); + } +} + +void EmojiPan::onRemoveSetSure() { + App::wnd()->hideLayer(); + StickerSets::iterator it = cRefStickerSets().find(_removingSetId); + if (it != cRefStickerSets().cend() && _removingSetId != CustomStickerSetId && _removingSetId != DefaultStickerSetId && _removingSetId != RecentStickerSetId) { + if (it->id && it->access) { + MTP::send(MTPmessages_UninstallStickerSet(MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)))); + } else if (!it->shortName.isEmpty()) { + MTP::send(MTPmessages_UninstallStickerSet(MTP_inputStickerSetShortName(MTP_string(it->shortName)))); + } + bool writeRecent = false; + RecentStickerPack &recent(cGetRecentStickers()); + for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) { + if (it->stickers.indexOf(i->first) >= 0) { + i = recent.erase(i); + writeRecent = true; + } else { + ++i; + } + } + cRefStickerSets().erase(it); + cSetStickersHash(QByteArray()); + refreshStickers(); + Local::writeStickers(); + if (writeRecent) Local::writeUserSettings(); + } + _removingSetId = 0; +} + +void EmojiPan::onDelayedHide() { + if (!rect().contains(mapFromGlobal(QCursor::pos()))) { + _hideTimer.start(3000); + } + _removingSetId = 0; } MentionsInner::MentionsInner(MentionsDropdown *parent, MentionRows *rows, HashtagRows *hrows) : _parent(parent), _rows(rows), _hrows(hrows), _sel(-1), _mouseSel(false), _overDelete(false) { diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h index 3b1f78371..f95e45871 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/dropdown.h @@ -193,6 +193,8 @@ private: }; +static const int32 SwitcherSelected = (INT_MAX / 2); + class EmojiPanInner : public TWidget, public Animated { Q_OBJECT @@ -218,7 +220,6 @@ public: DBIEmojiTab currentTab(int yOffset) const; - void refreshStickers(); void refreshRecent(); void setScrollTop(int top); @@ -234,30 +235,27 @@ public slots: signals: - void emojiSelected(EmojiPtr emoji); - void stickerSelected(DocumentData *sticker); + void selected(EmojiPtr emoji); + + void switchToStickers(); void scrollToY(int y); void disableScroll(bool dis); private: - int countHeight(); + int32 countHeight(); void selectEmoji(EmojiPtr emoji); - typedef QMap EmojiAnimations; // index - showing, -index - hiding - EmojiAnimations _emojiAnimations; + typedef QMap Animations; // index - showing, -index - hiding + Animations _animations; - int _top; - int _counts[emojiTabCount], _count; + int32 _top, _counts[emojiTabCount]; - StickerPack _stickers; - QVector _isUserGen; QVector _emojis[emojiTabCount]; - QVector _hovers[emojiTabCount + 1]; // + stickers hovers and stickers-x hovers + QVector _hovers[emojiTabCount]; - float64 _stickerWidth; - int32 _esize, _stickerSize; + int32 _esize; int32 _selected, _pressedSel, _pickerSel; QPoint _lastMousePos; @@ -267,6 +265,74 @@ private: EmojiColorPicker _picker; QTimer _showPickerTimer; + float64 _switcherHover; + int32 _stickersWidth; +}; + +class StickerPanInner : public TWidget, public Animated { + Q_OBJECT + +public: + + StickerPanInner(QWidget *parent = 0); + + void paintEvent(QPaintEvent *e); + + void mousePressEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + void mouseMoveEvent(QMouseEvent *e); + void leaveEvent(QEvent *e); + void leaveToChildEvent(QEvent *e); + void enterFromChildEvent(QEvent *e); + + bool animStep(float64 ms); + + void showStickerSet(uint64 setId); + + void clearSelection(bool fast = false); + + void refreshStickers(); + void refreshRecent(bool resize = true); + + void setScrollTop(int top); + void preloadImages(); + +public slots: + + void updateSelected(); + +signals: + + void selected(DocumentData *sticker); + void removing(uint64 setId); + + void switchToEmoji(); + + void scrollToY(int y); + void disableScroll(bool dis); + +private: + + void appendSet(StickerSets::const_iterator it); + + int32 countHeight(); + void selectEmoji(EmojiPtr emoji); + + typedef QMap Animations; // index - showing, -index - hiding + Animations _animations; + + int32 _top; + + QList _titles; + QList _setIds; + QList _sets; + QList > _hovers; + + int32 _selected, _pressedSel; + QPoint _lastMousePos; + + float64 _switcherHover; + int32 _emojiWidth; }; class EmojiPan : public TWidget, public Animated { @@ -291,11 +357,12 @@ public: bool animStep(float64 ms); bool eventFilter(QObject *obj, QEvent *e); - - void refreshStickers(); + void stickersInstalled(uint64 setId); public slots: + void refreshStickers(); + void hideStart(); void hideFinish(); @@ -304,6 +371,11 @@ public slots: void onTabChange(); void onScroll(); + void onSwitch(); + + void onRemoveSet(uint64 setId); + void onRemoveSetSure(); + void onDelayedHide(); signals: @@ -313,6 +385,8 @@ signals: private: + void prepareTab(int32 &left, int32 top, int32 _width, FlatRadiobutton &tab); + void showAll(); void hideAll(); @@ -328,11 +402,20 @@ private: BoxShadow _shadow; - FlatRadiobutton _recent, _people, _nature, _food, _celebration, _activity, _travel, _objects, _stickers; + FlatRadiobutton _recent, _people, _nature, _food, _celebration, _activity, _travel, _objects; - int32 _emojiPack; - ScrollArea _scroll; - EmojiPanInner _inner; + bool _stickersShown; + QPixmap _fromCache, _toCache; + anim::ivalue a_fromCoord, a_toCoord; + anim::fvalue a_fromAlpha, a_toAlpha; + uint64 _moveStart; + + ScrollArea e_scroll; + EmojiPanInner e_inner; + ScrollArea s_scroll; + StickerPanInner s_inner; + + uint64 _removingSetId; }; diff --git a/Telegram/SourceFiles/fileuploader.cpp b/Telegram/SourceFiles/fileuploader.cpp index dbb5d3880..b6ea26373 100644 --- a/Telegram/SourceFiles/fileuploader.cpp +++ b/Telegram/SourceFiles/fileuploader.cpp @@ -38,7 +38,7 @@ void FileUploader::uploadMedia(MsgId msgId, const ReadyLocalMedia &media) { } document->status = FileUploading; if (!media.file.isEmpty()) { - document->location = FileLocation(mtpc_storage_filePartial, media.file); + document->location = FileLocation(StorageFilePartial, media.file); } } queue.insert(msgId, File(media)); diff --git a/Telegram/SourceFiles/gui/images.cpp b/Telegram/SourceFiles/gui/images.cpp index 7f2bb79ef..00415df95 100644 --- a/Telegram/SourceFiles/gui/images.cpp +++ b/Telegram/SourceFiles/gui/images.cpp @@ -19,6 +19,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "gui/images.h" #include "mainwidget.h" +#include "localstorage.h" namespace { typedef QMap LocalImages; @@ -43,7 +44,7 @@ ImagePtr::ImagePtr() : Parent(blank()) { } ImagePtr::ImagePtr(int32 width, int32 height, const MTPFileLocation &location, ImagePtr def) : - Parent((location.type() == mtpc_fileLocation) ? (Image*)(getImage(width, height, location.c_fileLocation().vdc_id.v, location.c_fileLocation().vvolume_id.v, location.c_fileLocation().vlocal_id.v, location.c_fileLocation().vsecret.v)) : def.v()) { + Parent((location.type() == mtpc_fileLocation) ? (Image*)(getImage(StorageImageLocation(width, height, location.c_fileLocation()))) : def.v()) { } const QPixmap &Image::pix(int32 w, int32 h) const { @@ -517,11 +518,14 @@ int64 imageCacheSize() { return globalAquiredSize; } -StorageImage::StorageImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size) : w(width), h(height), loader(new mtpFileLoader(dc, volume, local, secret, size)) { +StorageImage::StorageImage(const StorageImageLocation &location, int32 size) : w(location.width), h(location.height), loader(new mtpFileLoader(location.dc, location.volume, location.local, location.secret, size)) { } -StorageImage::StorageImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, QByteArray &bytes) : w(width), h(height), loader(0) { +StorageImage::StorageImage(const StorageImageLocation &location, QByteArray &bytes) : w(location.width), h(location.height), loader(0) { setData(bytes); + if (location.dc) { + Local::writeImage(storageKey(location.dc, location.volume, location.local), StorageImageSaved(mtpToStorageType(mtpc_storage_filePartial), bytes)); + } } const QPixmap &StorageImage::pixData() const { @@ -608,24 +612,27 @@ bool StorageImage::loaded() const { return check(); } -StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size) { - StorageKey key(storageKey(dc, volume, local)); +StorageImage *getImage(const StorageImageLocation &location, int32 size) { + StorageKey key(storageKey(location.dc, location.volume, location.local)); StorageImages::const_iterator i = storageImages.constFind(key); if (i == storageImages.cend()) { - i = storageImages.insert(key, new StorageImage(width, height, dc, volume, local, secret, size)); + i = storageImages.insert(key, new StorageImage(location, size)); } return i.value(); } -StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, const QByteArray &bytes) { - StorageKey key(storageKey(dc, volume, local)); +StorageImage *getImage(const StorageImageLocation &location, const QByteArray &bytes) { + StorageKey key(storageKey(location.dc, location.volume, location.local)); StorageImages::const_iterator i = storageImages.constFind(key); if (i == storageImages.cend()) { QByteArray bytesArr(bytes); - i = storageImages.insert(key, new StorageImage(width, height, dc, volume, local, secret, bytesArr)); + i = storageImages.insert(key, new StorageImage(location, bytesArr)); } else if (!i.value()->loaded()) { QByteArray bytesArr(bytes); i.value()->setData(bytesArr); + if (location.dc) { + Local::writeImage(storageKey(location.dc, location.volume, location.local), StorageImageSaved(mtpToStorageType(mtpc_storage_filePartial), bytes)); + } } return i.value(); } diff --git a/Telegram/SourceFiles/gui/images.h b/Telegram/SourceFiles/gui/images.h index eca31ac29..6d4a437aa 100644 --- a/Telegram/SourceFiles/gui/images.h +++ b/Telegram/SourceFiles/gui/images.h @@ -21,6 +21,20 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org QImage imageBlur(QImage img); +struct StorageImageLocation { + StorageImageLocation() : width(0), height(0), dc(0), volume(0), local(0), secret(0) { + } + StorageImageLocation(int32 width, int32 height, int32 dc, const uint64 &volume, int32 local, const uint64 &secret) : width(width), height(height), dc(dc), volume(volume), local(local), secret(secret) { + } + StorageImageLocation(int32 width, int32 height, const MTPDfileLocation &location) : width(width), height(height), dc(location.vdc_id.v), volume(location.vvolume_id.v), local(location.vlocal_id.v), secret(location.vsecret.v) { + } + int32 width, height; + int32 dc; + uint64 volume; + int32 local; + uint64 secret; +}; + class Image { public: @@ -123,25 +137,66 @@ typedef QPair StorageKey; inline uint64 storageMix32To64(int32 a, int32 b) { return (uint64(*reinterpret_cast(&a)) << 32) | uint64(*reinterpret_cast(&b)); } -inline StorageKey storageKey(int32 dc, const int64 &volume, int32 local) { +inline StorageKey storageKey(int32 dc, const uint64 &volume, int32 local) { return StorageKey(storageMix32To64(dc, local), volume); } inline StorageKey storageKey(const MTPDfileLocation &location) { return storageKey(location.vdc_id.v, location.vvolume_id.v, location.vlocal_id.v); } +enum StorageFileType { + StorageFileUnknown = 0xaa963b05, // mtpc_storage_fileUnknown + StorageFileJpeg = 0x7efe0e, // mtpc_storage_fileJpeg + StorageFileGif = 0xcae1aadf, // mtpc_storage_fileGif + StorageFilePng = 0xa4f63c0, // mtpc_storage_filePng + StorageFilePdf = 0xae1e508d, // mtpc_storage_filePdf + StorageFileMp3 = 0x528a0677, // mtpc_storage_fileMp3 + StorageFileMov = 0x4b09ebbc, // mtpc_storage_fileMov + StorageFilePartial = 0x40bc6f52, // mtpc_storage_filePartial + StorageFileMp4 = 0xb3cea0e4, // mtpc_storage_fileMp4 + StorageFileWebp = 0x1081464c, // mtpc_storage_fileWebp +}; +inline StorageFileType mtpToStorageType(mtpTypeId type) { + switch (type) { + case mtpc_storage_fileJpeg: return StorageFileJpeg; + case mtpc_storage_fileGif: return StorageFileGif; + case mtpc_storage_filePng: return StorageFilePng; + case mtpc_storage_filePdf: return StorageFilePdf; + case mtpc_storage_fileMp3: return StorageFileMp3; + case mtpc_storage_fileMov: return StorageFileMov; + case mtpc_storage_filePartial: return StorageFilePartial; + case mtpc_storage_fileMp4: return StorageFileMp4; + case mtpc_storage_fileWebp: return StorageFileWebp; + case mtpc_storage_fileUnknown: + default: return StorageFileUnknown; + } +} +inline mtpTypeId mtpFromStorageType(StorageFileType type) { + switch (type) { + case StorageFileGif: return mtpc_storage_fileGif; + case StorageFilePng: return mtpc_storage_filePng; + case StorageFilePdf: return mtpc_storage_filePdf; + case StorageFileMp3: return mtpc_storage_fileMp3; + case StorageFileMov: return mtpc_storage_fileMov; + case StorageFilePartial: return mtpc_storage_filePartial; + case StorageFileMp4: return mtpc_storage_fileMp4; + case StorageFileWebp: return mtpc_storage_fileWebp; + case StorageFileUnknown: + default: return mtpc_storage_fileUnknown; + } +} struct StorageImageSaved { - StorageImageSaved() : type(mtpc_storage_fileUnknown) { + StorageImageSaved() : type(StorageFileUnknown) { } - StorageImageSaved(mtpTypeId type, const QByteArray &data) : type(type), data(data) { + StorageImageSaved(StorageFileType type, const QByteArray &data) : type(type), data(data) { } - mtpTypeId type; + StorageFileType type; QByteArray data; }; class StorageImage : public Image { public: - StorageImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size = 0); - StorageImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, QByteArray &bytes); + StorageImage(const StorageImageLocation &location, int32 size = 0); + StorageImage(const StorageImageLocation &location, QByteArray &bytes); int32 width() const; int32 height() const; @@ -188,8 +243,8 @@ private: mutable mtpFileLoader *loader; }; -StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size = 0); -StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, const QByteArray &bytes); +StorageImage *getImage(const StorageImageLocation &location, int32 size = 0); +StorageImage *getImage(const StorageImageLocation &location, const QByteArray &bytes); Image *getImage(int32 width, int32 height, const MTPFileLocation &location); class ImagePtr : public ManagedPtr { @@ -201,9 +256,9 @@ public: } ImagePtr(const QPixmap &pixmap, QByteArray format) : Parent(getImage(pixmap, format)) { } - ImagePtr(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size = 0) : Parent(getImage(width, height, dc, volume, local, secret, size)) { + ImagePtr(const StorageImageLocation &location, int32 size = 0) : Parent(getImage(location, size)) { } - ImagePtr(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, const QByteArray &bytes) : Parent(getImage(width, height, dc, volume, local, secret, bytes)) { + ImagePtr(const StorageImageLocation &location, const QByteArray &bytes) : Parent(getImage(location, bytes)) { } ImagePtr(int32 width, int32 height, const MTPFileLocation &location, ImagePtr def = ImagePtr()); }; @@ -213,16 +268,16 @@ void clearAllImages(); int64 imageCacheSize(); struct FileLocation { - FileLocation(mtpTypeId type, const QString &name, const QDateTime &modified, qint32 size) : type(type), name(name), modified(modified), size(size) { + FileLocation(StorageFileType type, const QString &name, const QDateTime &modified, qint32 size) : type(type), name(name), modified(modified), size(size) { } - FileLocation(mtpTypeId type, const QString &name) : type(type), name(name) { + FileLocation(StorageFileType type, const QString &name) : type(type), name(name) { QFileInfo f(name); if (f.exists()) { qint64 s = f.size(); if (s > INT_MAX) { this->name = QString(); size = 0; - type = mtpc_storage_fileUnknown; + type = StorageFileUnknown; } else { modified = f.lastModified(); size = qint32(s); @@ -230,7 +285,7 @@ struct FileLocation { } else { this->name = QString(); size = 0; - type = mtpc_storage_fileUnknown; + type = StorageFileUnknown; } } FileLocation() : size(0) { @@ -245,7 +300,7 @@ struct FileLocation { return (f.lastModified() == modified) && (qint32(s) == size); } - mtpTypeId type; + StorageFileType type; QString name; QDateTime modified; qint32 size; @@ -257,11 +312,34 @@ inline bool operator!=(const FileLocation &a, const FileLocation &b) { return !(a == b); } +enum LocationType { + UnknownFileLocation = 0, + DocumentFileLocation = 0x4e45abe9, // mtpc_inputDocumentFileLocation + AudioFileLocation = 0x74dc404d, // mtpc_inputAudioFileLocation + VideoFileLocation = 0x3d0364ec, // mtpc_inputVideoFileLocation +}; +inline LocationType mtpToLocationType(mtpTypeId type) { + switch (type) { + case mtpc_inputDocumentFileLocation: return DocumentFileLocation; + case mtpc_inputAudioFileLocation: return AudioFileLocation; + case mtpc_inputVideoFileLocation: return VideoFileLocation; + default: return UnknownFileLocation; + } +} +inline mtpTypeId mtpFromLocationType(LocationType type) { + switch (type) { + case DocumentFileLocation: return mtpc_inputDocumentFileLocation; + case AudioFileLocation: return mtpc_inputAudioFileLocation; + case VideoFileLocation: return mtpc_inputVideoFileLocation; + case UnknownFileLocation: + default: return 0; + } +} typedef QPair MediaKey; -inline uint64 mediaMix32To64(mtpTypeId a, int32 b) { +inline uint64 mediaMix32To64(int32 a, int32 b) { return (uint64(*reinterpret_cast(&a)) << 32) | uint64(*reinterpret_cast(&b)); } -inline MediaKey mediaKey(mtpTypeId type, int32 dc, const int64 &id) { +inline MediaKey mediaKey(LocationType type, int32 dc, const uint64 &id) { return MediaKey(mediaMix32To64(type, dc), id); } inline StorageKey mediaKey(const MTPDfileLocation &location) { diff --git a/Telegram/SourceFiles/gui/scrollarea.cpp b/Telegram/SourceFiles/gui/scrollarea.cpp index b02b7a965..3ee94159d 100644 --- a/Telegram/SourceFiles/gui/scrollarea.cpp +++ b/Telegram/SourceFiles/gui/scrollarea.cpp @@ -54,7 +54,7 @@ ScrollBar::ScrollBar(ScrollArea *parent, bool vert, const style::flatScroll *st) } void ScrollBar::recountSize() { - setGeometry(_vertical ? QRect(rtl() ? 0 : (_area->width() - _st->width), 0, _st->width, _area->height()) : QRect(0, _area->height() - _st->width, _area->width(), _st->width)); + setGeometry(_vertical ? QRect(rtl() ? 0 : (_area->width() - _st->width), _st->deltat, _st->width, _area->height() - _st->deltat - _st->deltab) : QRect(_st->deltat, _area->height() - _st->width, _area->width() - _st->deltat - _st->deltab, _st->width)); } void ScrollBar::updateBar(bool force) { @@ -65,7 +65,7 @@ void ScrollBar::updateBar(bool force) { _area->rangeChanged(oldMax, newMax, _vertical); } if (_vertical) { - int sh = _area->scrollHeight(), rh = height() - 2 * _st->deltay, h = sh ? int32((rh * int64(_area->height())) / sh) : 0; + int sh = _area->scrollHeight(), rh = height(), h = sh ? int32((rh * int64(_area->height())) / sh) : 0; if (h >= rh || !_area->scrollTopMax() || rh < _st->minHeight) { if (!isHidden()) hide(); bool newTopSh = (_st->topsh < 0), newBottomSh = (_st->bottomsh < 0); @@ -78,9 +78,9 @@ void ScrollBar::updateBar(bool force) { int stm = _area->scrollTopMax(), y = stm ? int32(((rh - h) * int64(_area->scrollTop())) / stm) : 0; if (y > rh - h) y = rh - h; - newBar = QRect(_st->deltax, y + _st->deltay, width() - 2 * _st->deltax, h); + newBar = QRect(_st->deltax, y, width() - 2 * _st->deltax, h); } else { - int sw = _area->scrollWidth(), rw = width() - 2 * _st->deltay, w = sw ? int32((rw * int64(_area->width())) / sw) : 0; + int sw = _area->scrollWidth(), rw = width(), w = sw ? int32((rw * int64(_area->width())) / sw) : 0; if (w >= rw || !_area->scrollLeftMax() || rw < _st->minHeight) { if (!isHidden()) hide(); return; @@ -90,11 +90,11 @@ void ScrollBar::updateBar(bool force) { int slm = _area->scrollLeftMax(), x = slm ? int32(((rw - w) * int64(_area->scrollLeft())) / slm) : 0; if (x > rw - w) x = rw - w; - newBar = QRect(x + _st->deltay, _st->deltax, w, height() - 2 * _st->deltax); + newBar = QRect(x, _st->deltax, w, height() - 2 * _st->deltax); } if (newBar != _bar) { _bar = newBar; - parentWidget()->update(geometry()); + update();// parentWidget()->update(geometry()); } if (_vertical) { bool newTopSh = (_st->topsh < 0) || (_area->scrollTop() > _st->topsh), newBottomSh = (_st->bottomsh < 0) || (_area->scrollTop() < _area->scrollTopMax() - _st->bottomsh); @@ -119,15 +119,16 @@ void ScrollBar::paintEvent(QPaintEvent *e) { if (!a_bg.current().alpha() && !a_bar.current().alpha()) return; QPainter p(this); - int32 deltax = _vertical ? _st->deltax : _st->deltay, deltay = _vertical ? _st->deltay : _st->deltax; + int32 deltal = _vertical ? _st->deltax : 0, deltar = _vertical ? _st->deltax : 0; + int32 deltat = _vertical ? 0 : _st->deltax, deltab = _vertical ? 0 : _st->deltax; p.setPen(Qt::NoPen); if (_st->round) { p.setBrush(a_bg.current()); - p.drawRoundedRect(QRect(deltax, deltay, width() - 2 * deltax, height() - 2 * deltay), _st->round, _st->round); + p.drawRoundedRect(QRect(deltal, deltat, width() - deltal - deltar, height() - deltat - deltab), _st->round, _st->round); p.setBrush(a_bar.current()); p.drawRoundedRect(_bar, _st->round, _st->round); } else { - p.fillRect(QRect(deltax, deltay, width() - 2 * deltax, height() - 2 * deltay), a_bg.current()); + p.fillRect(QRect(deltal, deltat, width() - deltal - deltar, height() - deltat - deltab), a_bg.current()); p.fillRect(_bar, a_bar.current()); } } @@ -143,7 +144,7 @@ bool ScrollBar::animStep(float64 ms) { a_bg.update(dt, anim::linear); a_bar.update(dt, anim::linear); } - parentWidget()->update(geometry()); + update();// parentWidget()->update(geometry()); return res; } @@ -176,6 +177,8 @@ void ScrollBar::leaveEvent(QEvent *e) { anim::start(this); if (_hideIn >= 0) { _hideTimer.start(_hideIn); + } else if (_st->hiding) { + hideTimeout(_st->hiding); } } _over = _overbar = false; @@ -193,7 +196,7 @@ void ScrollBar::mouseMoveEvent(QMouseEvent *e) { } if (_moving) { int delta = 0, barDelta = _vertical ? (_area->height() - _bar.height()) : (_area->width() - _bar.width()); - if (barDelta) { + if (barDelta > 0) { QPoint d = (e->globalPos() - _dragStart); delta = int32((_vertical ? (d.y() * int64(_area->scrollTopMax())) : (d.x() * int64(_area->scrollLeftMax()))) / barDelta); } @@ -209,7 +212,10 @@ void ScrollBar::mousePressEvent(QMouseEvent *e) { if (_overbar) { _startFrom = _connected->value(); } else { - _startFrom = _vertical ? int32((e->pos().y() * int64(_area->scrollTopMax())) / height()) : ((e->pos().x() * int64(_area->scrollLeftMax())) / width()); + int32 val = _vertical ? e->pos().y() : e->pos().x(), div = _vertical ? height() : width(); + val = (val <= _st->deltat) ? 0 : (val - _st->deltat); + div = (div <= _st->deltat + _st->deltab) ? 1 : (div - _st->deltat - _st->deltab); + _startFrom = _vertical ? int32((val * int64(_area->scrollTopMax())) / div) : ((val * int64(_area->scrollLeftMax())) / div); _connected->setValue(_startFrom); if (!_overbar) { _overbar = true; diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp index 41229b795..d41fad940 100644 --- a/Telegram/SourceFiles/gui/text.cpp +++ b/Telegram/SourceFiles/gui/text.cpp @@ -736,10 +736,13 @@ void TextLink::onClick(Qt::MouseButton button) const { QString url = TextLink::encoded(); QRegularExpressionMatch telegramMeUser = QRegularExpression(qsl("^https?://telegram\\.me/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url); QRegularExpressionMatch telegramMeGroup = QRegularExpression(qsl("^https?://telegram\\.me/joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url); + QRegularExpressionMatch telegramMeStickers = QRegularExpression(qsl("^https?://telegram\\.me/addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url); if (telegramMeUser.hasMatch()) { App::openUserByName(telegramMeUser.captured(1)); } else if (telegramMeGroup.hasMatch()) { App::joinGroupByHash(telegramMeGroup.captured(1)); + } else if (telegramMeStickers.hasMatch()) { + App::stickersBox(telegramMeStickers.captured(1)); } else if (QRegularExpression(qsl("^tg://[a-zA-Z0-9]+"), QRegularExpression::CaseInsensitiveOption).match(url).hasMatch()) { App::openLocalUrl(url); } else { diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 9fc8a2d1e..36b133bb2 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -701,6 +701,13 @@ void HistoryList::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(lang(lng_context_reply_msg), historyWidget, SLOT(onReplyToMessage())); } if (item && !isUponSelected && !_contextMenuLnk) { + if (HistorySticker *sticker = dynamic_cast(msg->getMedia())) { + DocumentData *doc = sticker->document(); + if (doc && doc->sticker && doc->sticker->set.type() != mtpc_inputStickerSetEmpty) { + if (!_menu) _menu = new ContextMenu(this); + _menu->addAction(lang(lng_context_pack_info), historyWidget, SLOT(onStickerPackInfo())); + } + } QString contextMenuText = item->selectedText(FullItemSel); if (!contextMenuText.isEmpty() && (!msg || !msg->getMedia() || msg->getMedia()->type() != MediaTypeSticker)) { if (!_menu) _menu = new ContextMenu(this); @@ -771,7 +778,9 @@ void HistoryList::onMenuDestroy(QObject *obj) { } void HistoryList::copySelectedText() { - QApplication::clipboard()->setText(getSelectedText()); + QString sel = getSelectedText(); + DEBUG_LOG(("Setting selected text to clipboard: %1").arg(sel)); + QApplication::clipboard()->setText(sel); } void HistoryList::openContextUrl() { @@ -784,6 +793,7 @@ void HistoryList::openContextUrl() { void HistoryList::copyContextUrl() { QString enc = _contextMenuLnk->encoded(); if (!enc.isEmpty()) { + DEBUG_LOG(("Setting text to clipboard from context url: %1").arg(enc)); QApplication::clipboard()->setText(enc); } } @@ -857,6 +867,7 @@ void HistoryList::copyContextText() { QString contextMenuText = item->selectedText(FullItemSel); if (!contextMenuText.isEmpty()) { + DEBUG_LOG(("Setting text to clipboard from context menu: %1").arg(contextMenuText)); QApplication::clipboard()->setText(contextMenuText); } } @@ -1569,7 +1580,6 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent) , _previewCancelled(false) , _replyForwardPressed(false) , _replyReturn(0) -, _lastStickersUpdate(0) , _stickersUpdateRequest(0) , _loadingMessages(false) , histRequestsCount(0) @@ -1687,6 +1697,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent) } void HistoryWidget::start() { + connect(App::main(), SIGNAL(stickersUpdated()), &_emojiPan, SLOT(refreshStickers())); updateRecentStickers(); connect(App::api(), SIGNAL(fullPeerLoaded(PeerData*)), this, SLOT(onPeerLoaded(PeerData*))); } @@ -1766,6 +1777,10 @@ void HistoryWidget::updateRecentStickers() { _emojiPan.refreshStickers(); } +void HistoryWidget::stickersInstalled(uint64 setId) { + _emojiPan.stickersInstalled(setId); +} + void HistoryWidget::typingDone(const MTPBool &result, mtpRequestId req) { if (_typingRequest == req) { _typingRequest = 0; @@ -1795,116 +1810,210 @@ void HistoryWidget::activate() { } void HistoryWidget::updateStickers() { - if (_lastStickersUpdate && getms(true) < _lastStickersUpdate + StickersUpdateTimeout) return; + if (cLastStickersUpdate() && getms(true) < cLastStickersUpdate() + StickersUpdateTimeout) return; if (_stickersUpdateRequest) return; _stickersUpdateRequest = MTP::send(MTPmessages_GetAllStickers(MTP_string(cStickersHash())), rpcDone(&HistoryWidget::stickersGot), rpcFail(&HistoryWidget::stickersFailed)); } void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { - _lastStickersUpdate = getms(true); + cSetLastStickersUpdate(getms(true)); _stickersUpdateRequest = 0; - if (stickers.type() == mtpc_messages_allStickers) { - const MTPDmessages_allStickers &d(stickers.c_messages_allStickers()); + if (stickers.type() != mtpc_messages_allStickers) return; + const MTPDmessages_allStickers &d(stickers.c_messages_allStickers()); + + EmojiStickersMap map; - AllStickers all; - EmojiStickersMap map; + const QVector &d_docs(d.vdocuments.c_vector().v); + const QVector &d_sets(d.vsets.c_vector().v); - const QVector &docs(d.vdocuments.c_vector().v); + QByteArray wasHash = cStickersHash(); + cSetStickersHash(qba(d.vhash)); - QSet found; - const RecentStickerPack &recent(cRecentStickers()); - RecentStickerPack add; - add.reserve(docs.size()); - ushort addValue = recent.isEmpty() ? 1 : qAbs(recent.front().second); - for (int32 i = 0, l = docs.size(); i < l; ++i) { - DocumentData *doc = App::feedDocument(docs.at(i)); - if (!doc) continue; - int32 j = 0, s = recent.size(); - for (; j < s; ++j) { - if (doc == recent.at(j).first) { + StickerSets &sets(cRefStickerSets()); + StickerSets::iterator def = sets.find(DefaultStickerSetId); + if (def == sets.cend()) { + def = sets.insert(DefaultStickerSetId, StickerSet(DefaultStickerSetId, 0, qsl("Great Minds"), QString())); + } + for (int32 i = 0; i < d_sets.size(); ++i) { + if (d_sets.at(i).type() == mtpc_stickerSet) { + const MTPDstickerSet &set(d_sets.at(i).c_stickerSet()); + StickerSets::iterator i = sets.find(set.vid.v); + if (i == sets.cend()) { + i = sets.insert(set.vid.v, StickerSet(set.vid.v, set.vaccess_hash.v, qs(set.vtitle), qs(set.vshort_name))); + } else { + i->access = set.vaccess_hash.v; + i->title = qs(set.vtitle); + i->shortName = qs(set.vshort_name); + } + } + } + + StickerSets::iterator custom = sets.find(CustomStickerSetId); + + bool added = false, removed = false; + QSet found; + QMap wasCount; + for (int32 i = 0, l = d_docs.size(); i < l; ++i) { + DocumentData *doc = App::feedDocument(d_docs.at(i)); + if (!doc || !doc->sticker) continue; + + switch (doc->sticker->set.type()) { + case mtpc_inputStickerSetEmpty: { // default set - great minds + if (!wasCount.contains(DefaultStickerSetId)) wasCount.insert(DefaultStickerSetId, def->stickers.size()); + if (def->stickers.indexOf(doc) < 0) { + def->stickers.push_back(doc); + added = true; + } else { + found.insert(doc); + } + } break; + case mtpc_inputStickerSetID: { + StickerSets::iterator it = sets.find(doc->sticker->set.c_inputStickerSetID().vid.v); + if (it == sets.cend()) { + LOG(("Sticker Set not found by ID: %1").arg(doc->sticker->set.c_inputStickerSetID().vid.v)); + } else { + if (!wasCount.contains(it->id)) wasCount.insert(it->id, it->stickers.size()); + if (it->stickers.indexOf(doc) < 0) { + it->stickers.push_back(doc); + added = true; + } else { found.insert(doc); + } + } + } break; + case mtpc_inputStickerSetShortName: { + QString name = qs(doc->sticker->set.c_inputStickerSetShortName().vshort_name).toLower().trimmed(); + StickerSets::iterator it = sets.begin(); + for (; it != sets.cend(); ++it) { + if (it->shortName.toLower().trimmed() == name) { break; } } - if (j < s) continue; - add.push_back(qMakePair(doc, addValue)); + if (it == sets.cend()) { + LOG(("Sticker Set not found by name: %1").arg(name)); + } else { + if (!wasCount.contains(it->id)) wasCount.insert(it->id, it->stickers.size()); + if (it->stickers.indexOf(doc) < 0) { + it->stickers.push_back(doc); + added = true; + } else { + found.insert(doc); + } + } + } break; } - bool needRemove = false; - for (int32 i = 0, l = recent.size(); i < l; ++i) { - if (recent.at(i).second > 0 && !found.contains(recent.at(i).first)) { - needRemove = true; - break; + if (custom != sets.cend()) { + int32 index = custom->stickers.indexOf(doc); + if (index >= 0) { + custom->stickers.removeAt(index); + removed = true; } } - if (!add.isEmpty() || needRemove) { - if (needRemove) { - for (int32 i = 0, l = recent.size(); i < l; ++i) { - if (recent.at(i).second <= 0 || found.contains(recent.at(i).first)) { - add.push_back(recent.at(i)); + } + if (custom != sets.cend() && custom->stickers.isEmpty()) { + sets.erase(custom); + custom = sets.end(); + } + bool writeRecent = false; + RecentStickerPack &recent(cGetRecentStickers()); + for (StickerSets::iterator it = sets.begin(); it != sets.cend();) { + if (it->id == CustomStickerSetId || it->id == RecentStickerSetId) { + ++it; + continue; + } + QMap::const_iterator was = wasCount.constFind(it->id); + if (was == wasCount.cend()) { // no such stickers added + for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) { + if (it->stickers.indexOf(i->first) >= 0) { + i = recent.erase(i); + writeRecent = true; + } else { + ++i; + } + } + it = sets.erase(it); + removed = true; + } else { + for (int32 j = 0, l = was.value(); j < l;) { + if (found.contains(it->stickers.at(j))) { + ++j; + } else { + for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) { + if (it->stickers.at(j) == i->first) { + i = recent.erase(i); + writeRecent = true; + } else { + ++i; + } + } + it->stickers.removeAt(j); + --l; + removed = true; + } + } + if (it->stickers.isEmpty()) { + it = sets.erase(it); + } else { + ++it; + } + } + } + if (added || removed || cStickersHash() != wasHash) { + Local::writeStickers(); + } + if (writeRecent) { + Local::writeUserSettings(); + } + + const QVector &packs(d.vpacks.c_vector().v); + for (int32 i = 0, l = packs.size(); i < l; ++i) { + if (packs.at(i).type() == mtpc_stickerPack) { + const MTPDstickerPack &p(packs.at(i).c_stickerPack()); + QString emoticon(qs(p.vemoticon)); + EmojiPtr e = 0; + for (const QChar *ch = emoticon.constData(), *end = emoticon.constEnd(); ch != end; ++ch) { + int len = 0; + e = emojiFromText(ch, end, len); + if (e) break; + + if (ch + 1 < end && ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) ++ch; + } + if (e) { + const QVector docs(p.vdocuments.c_vector().v); + if (!docs.isEmpty()) { + for (int32 j = 0, s = docs.size(); j < s; ++j) { + DocumentData *doc = App::document(docs.at(j).v); + map.insert(doc, e); } } } else { - add += recent; - } - cSetRecentStickers(add); - Local::writeRecentStickers(); - } - - const QVector &packs(d.vpacks.c_vector().v); - for (int32 i = 0, l = packs.size(); i < l; ++i) { - if (packs.at(i).type() == mtpc_stickerPack) { - const MTPDstickerPack &p(packs.at(i).c_stickerPack()); - QString emoticon(qs(p.vemoticon)); - EmojiPtr e = 0; - for (const QChar *ch = emoticon.constData(), *end = emoticon.constEnd(); ch != end; ++ch) { - int len = 0; - e = emojiFromText(ch, end, len); - if (e) break; - - if (ch + 1 < end && ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) ++ch; - } - if (e) { - const QVector docs(p.vdocuments.c_vector().v); - if (!docs.isEmpty()) { - StickerPack &pack(all[e]); - pack.reserve(pack.size() + docs.size()); - for (int32 j = 0, s = docs.size(); j < s; ++j) { - DocumentData *doc = App::document(docs.at(j).v); - pack.push_back(doc); - map.insert(doc, e); - } - } - } else { - LOG(("Sticker Error: Could not find emoji for string: %1").arg(emoticon)); - } + LOG(("Sticker Error: Could not find emoji for string: %1").arg(emoticon)); } } - - cSetStickers(all); - cSetStickersHash(qba(d.vhash)); - cSetEmojiStickers(map); - - const DocumentItems &items(App::documentItems()); - for (EmojiStickersMap::const_iterator i = map.cbegin(), e = map.cend(); i != e; ++i) { - DocumentItems::const_iterator j = items.constFind(i.key()); - if (j != items.cend()) { - for (HistoryItemsMap::const_iterator k = j->cbegin(), end = j->cend(); k != end; ++k) { - k.key()->updateStickerEmoji(); - } - } - } - -// updateStickerPan(); - _emojiPan.refreshStickers(); } + + cSetEmojiStickers(map); + + const DocumentItems &items(App::documentItems()); + for (EmojiStickersMap::const_iterator i = map.cbegin(), e = map.cend(); i != e; ++i) { + DocumentItems::const_iterator j = items.constFind(i.key()); + if (j != items.cend()) { + for (HistoryItemsMap::const_iterator k = j->cbegin(), end = j->cend(); k != end; ++k) { + k.key()->updateStickerEmoji(); + } + } + } + + // updateStickerPan(); + if (App::main()) emit App::main()->stickersUpdated(); } bool HistoryWidget::stickersFailed(const RPCError &error) { if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false; - _lastStickersUpdate = getms(true); + cSetLastStickersUpdate(getms(true)); _stickersUpdateRequest = 0; return true; } @@ -3683,6 +3792,16 @@ void HistoryWidget::onReplyForwardPreviewCancel() { } } +void HistoryWidget::onStickerPackInfo() { + if (HistoryMessage *item = dynamic_cast(App::contextItem())) { + if (HistorySticker *sticker = dynamic_cast(item->getMedia())) { + if (sticker->document() && sticker->document()->sticker && sticker->document()->sticker->set.type() != mtpc_inputStickerSetEmpty) { + App::main()->stickersBox(sticker->document()->sticker->set); + } + } + } +} + void HistoryWidget::previewCancel() { MTP::cancel(_previewRequest); _previewRequest = 0; diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index 5eea3ff7a..0e8f32a1f 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -298,6 +298,7 @@ public: void updateTyping(bool typing = true); // void updateStickerPan(); void updateRecentStickers(); + void stickersInstalled(uint64 setId); void typingDone(const MTPBool &result, mtpRequestId req); void destroyData(); @@ -379,6 +380,8 @@ public slots: void onReplyToMessage(); void onReplyForwardPreviewCancel(); + void onStickerPackInfo(); + void onPreviewParse(); void onPreviewCheck(); void onPreviewTimeout(); @@ -473,7 +476,6 @@ private: void stickersGot(const MTPmessages_AllStickers &stickers); bool stickersFailed(const RPCError &error); - uint64 _lastStickersUpdate; mtpRequestId _stickersUpdateRequest; void writeDraft(MsgId *replyTo = 0, const QString *text = 0, const MessageCursor *cursor = 0, bool *previewCancelled = 0); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 55cd0751e..5b866774a 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -21,6 +21,11 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "lang.h" namespace { + enum StickerSetType { + StickerSetTypeEmpty = 0, + StickerSetTypeID = 1, + StickerSetTypeShortName = 2, + }; typedef quint64 FileKey; @@ -140,6 +145,10 @@ namespace { return sizeof(quint32) + str.size() * sizeof(ushort); } + uint32 _bytearraySize(const QByteArray &arr) { + return sizeof(quint32) + arr.size(); + } + QByteArray _settingsSalt, _passKeySalt, _passKeyEncrypted; mtpAuthKey _oldKey, _settingsKey, _passKey, _localKey; @@ -484,17 +493,18 @@ namespace { FileKey _dataNameKey = 0; enum { // Local Storage Keys - lskUserMap = 0, - lskDraft, // data: PeerId peer - lskDraftPosition, // data: PeerId peer - lskImages, // data: StorageKey location - lskLocations, // no data - lskStickers, // data: StorageKey location - lskAudios, // data: StorageKey location - lskRecentStickers, // no data - lskBackground, // no data - lskUserSettings, // no data - lskRecentHashtags, // no data + lskUserMap = 0x00, + lskDraft = 0x01, // data: PeerId peer + lskDraftPosition = 0x02, // data: PeerId peer + lskImages = 0x03, // data: StorageKey location + lskLocations = 0x04, // no data + lskStickerImages = 0x05, // data: StorageKey location + lskAudios = 0x06, // data: StorageKey location + lskRecentStickersOld = 0x07, // no data + lskBackground = 0x08, // no data + lskUserSettings = 0x09, // no data + lskRecentHashtags = 0x0a, // no data + lskStickers = 0x0b, // no data }; typedef QMap DraftsMap; @@ -509,7 +519,7 @@ namespace { FileLocationPairs _fileLocationPairs; FileKey _locationsKey = 0; - FileKey _recentStickersKey = 0; + FileKey _recentStickersKeyOld = 0, _stickersKey = 0; FileKey _backgroundKey = 0; bool _backgroundWasRead = false; @@ -520,7 +530,7 @@ namespace { typedef QPair FileDesc; // file, size typedef QMap StorageMap; - StorageMap _imagesMap, _stickersMap, _audiosMap; + StorageMap _imagesMap, _stickerImagesMap, _audiosMap; int32 _storageImagesSize = 0, _storageStickersSize = 0, _storageAudiosSize = 0; bool _mapChanged = false; @@ -585,7 +595,7 @@ namespace { locations.stream >> first >> second >> type >> loc.name >> loc.modified >> loc.size; MediaKey key(first, second); - loc.type = type; + loc.type = StorageFileType(type); if (loc.check()) { _fileLocations.insert(key, loc); @@ -953,6 +963,14 @@ namespace { cSetRecentEmojisPreload(v); } break; + case dbiRecentStickers: { + RecentStickerPreload v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetRecentStickersPreload(v); + } break; + case dbiEmojiVariants: { EmojiColorVariants v; stream >> v; @@ -1192,8 +1210,9 @@ namespace { uint32 size = 11 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + _stringSize(cAskDownloadPath() ? QString() : cDownloadPath()); - size += sizeof(quint32) + sizeof(qint32) + cGetRecentEmojis().size() * (sizeof(uint64) + sizeof(ushort)); + 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)); + size += sizeof(quint32) + sizeof(qint32) + (cRecentStickersPreload().isEmpty() ? cGetRecentStickers().size() : cRecentStickersPreload().size()) * (sizeof(uint64) + sizeof(ushort)); size += sizeof(quint32) + _stringSize(cDialogLastPath()); EncryptedDescriptor data(size); @@ -1211,14 +1230,27 @@ namespace { data.stream << quint32(dbiEmojiTab) << qint32(cEmojiTab()); data.stream << quint32(dbiDialogLastPath) << cDialogLastPath(); - RecentEmojisPreload v; - v.reserve(cGetRecentEmojis().size()); - for (RecentEmojiPack::const_iterator i = cGetRecentEmojis().cbegin(), e = cGetRecentEmojis().cend(); i != e; ++i) { - v.push_back(qMakePair(emojiKey(i->first), i->second)); + { + RecentEmojisPreload v(cRecentEmojisPreload()); + if (v.isEmpty()) { + v.reserve(cGetRecentEmojis().size()); + for (RecentEmojiPack::const_iterator i = cGetRecentEmojis().cbegin(), e = cGetRecentEmojis().cend(); i != e; ++i) { + v.push_back(qMakePair(emojiKey(i->first), i->second)); + } + } + data.stream << quint32(dbiRecentEmojis) << v; } - data.stream << quint32(dbiRecentEmojis) << v; - data.stream << quint32(dbiEmojiVariants) << cEmojiVariants(); + { + RecentStickerPreload v(cRecentStickersPreload()); + if (v.isEmpty()) { + v.reserve(cGetRecentStickers().size()); + for (RecentStickerPack::const_iterator i = cGetRecentStickers().cbegin(), e = cGetRecentStickers().cend(); i != e; ++i) { + v.push_back(qMakePair(i->first->id, i->second)); + } + } + data.stream << quint32(dbiRecentStickers) << v; + } FileWriteDescriptor file(_userSettingsKey); file.writeEncrypted(data); @@ -1340,9 +1372,9 @@ namespace { DraftsMap draftsMap, draftsPositionsMap; DraftsNotReadMap draftsNotReadMap; - StorageMap imagesMap, stickersMap, audiosMap; + StorageMap imagesMap, stickerImagesMap, audiosMap; qint64 storageImagesSize = 0, storageStickersSize = 0, storageAudiosSize = 0; - quint64 locationsKey = 0, recentStickersKey = 0, backgroundKey = 0, userSettingsKey = 0, recentHashtagsKey = 0; + quint64 locationsKey = 0, recentStickersKeyOld = 0, stickersKey = 0, backgroundKey = 0, userSettingsKey = 0, recentHashtagsKey = 0; while (!map.stream.atEnd()) { quint32 keyType; map.stream >> keyType; @@ -1380,7 +1412,7 @@ namespace { storageImagesSize += size; } } break; - case lskStickers: { + case lskStickerImages: { quint32 count = 0; map.stream >> count; for (quint32 i = 0; i < count; ++i) { @@ -1388,7 +1420,7 @@ namespace { quint64 first, second; qint32 size; map.stream >> key >> first >> second >> size; - stickersMap.insert(StorageKey(first, second), FileDesc(key, size)); + stickerImagesMap.insert(StorageKey(first, second), FileDesc(key, size)); storageStickersSize += size; } } break; @@ -1407,8 +1439,8 @@ namespace { case lskLocations: { map.stream >> locationsKey; } break; - case lskRecentStickers: { - map.stream >> recentStickersKey; + case lskRecentStickersOld: { + map.stream >> recentStickersKeyOld; } break; case lskBackground: { map.stream >> backgroundKey; @@ -1419,6 +1451,9 @@ namespace { case lskRecentHashtags: { map.stream >> recentHashtagsKey; } break; + case lskStickers: { + map.stream >> stickersKey; + } break; default: LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType)); return Local::ReadMapFailed; @@ -1434,13 +1469,14 @@ namespace { _imagesMap = imagesMap; _storageImagesSize = storageImagesSize; - _stickersMap = stickersMap; + _stickerImagesMap = stickerImagesMap; _storageStickersSize = storageStickersSize; _audiosMap = audiosMap; _storageAudiosSize = storageAudiosSize; _locationsKey = locationsKey; - _recentStickersKey = recentStickersKey; + _recentStickersKeyOld = recentStickersKeyOld; + _stickersKey = stickersKey; _backgroundKey = backgroundKey; _userSettingsKey = userSettingsKey; _recentHashtagsKey = recentHashtagsKey; @@ -1500,10 +1536,11 @@ namespace { if (!_draftsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftsMap.size() * sizeof(quint64) * 2; if (!_draftsPositionsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftsPositionsMap.size() * sizeof(quint64) * 2; if (!_imagesMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _imagesMap.size() * (sizeof(quint64) * 3 + sizeof(qint32)); - if (!_stickersMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _stickersMap.size() * (sizeof(quint64) * 3 + sizeof(qint32)); + if (!_stickerImagesMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _stickerImagesMap.size() * (sizeof(quint64) * 3 + sizeof(qint32)); if (!_audiosMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _audiosMap.size() * (sizeof(quint64) * 3 + sizeof(qint32)); if (_locationsKey) mapSize += sizeof(quint32) + sizeof(quint64); - if (_recentStickersKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_recentStickersKeyOld) mapSize += sizeof(quint32) + sizeof(quint64); + if (_stickersKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_backgroundKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_userSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_recentHashtagsKey) mapSize += sizeof(quint32) + sizeof(quint64); @@ -1526,9 +1563,9 @@ namespace { mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second); } } - if (!_stickersMap.isEmpty()) { - mapData.stream << quint32(lskStickers) << quint32(_stickersMap.size()); - for (StorageMap::const_iterator i = _stickersMap.cbegin(), e = _stickersMap.cend(); i != e; ++i) { + if (!_stickerImagesMap.isEmpty()) { + mapData.stream << quint32(lskStickerImages) << quint32(_stickerImagesMap.size()); + for (StorageMap::const_iterator i = _stickerImagesMap.cbegin(), e = _stickerImagesMap.cend(); i != e; ++i) { mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second); } } @@ -1541,8 +1578,11 @@ namespace { if (_locationsKey) { mapData.stream << quint32(lskLocations) << quint64(_locationsKey); } - if (_recentStickersKey) { - mapData.stream << quint32(lskRecentStickers) << quint64(_recentStickersKey); + if (_recentStickersKeyOld) { + mapData.stream << quint32(lskRecentStickersOld) << quint64(_recentStickersKeyOld); + } + if (_stickersKey) { + mapData.stream << quint32(lskStickers) << quint64(_stickersKey); } if (_backgroundKey) { mapData.stream << quint32(lskBackground) << quint64(_backgroundKey); @@ -1786,9 +1826,9 @@ namespace Local { _draftsPositionsMap.clear(); _imagesMap.clear(); _draftsNotReadMap.clear(); - _stickersMap.clear(); + _stickerImagesMap.clear(); _audiosMap.clear(); - _locationsKey = _recentStickersKey = _backgroundKey = _userSettingsKey = _recentHashtagsKey = 0; + _locationsKey = _recentStickersKeyOld = _stickersKey = _backgroundKey = _userSettingsKey = _recentHashtagsKey = 0; _mapChanged = true; _writeMap(WriteMapNow); @@ -1996,13 +2036,13 @@ namespace Local { if (_imagesMap.constFind(location) != _imagesMap.cend()) return; QByteArray fmt = image->savedFormat(); - mtpTypeId format = 0; + StorageFileType format = StorageFileUnknown; if (fmt == "JPG") { - format = mtpc_storage_fileJpeg; + format = StorageFileJpeg; } else if (fmt == "PNG") { - format = mtpc_storage_filePng; + format = StorageFilePng; } else if (fmt == "GIF") { - format = mtpc_storage_fileGif; + format = StorageFileGif; } if (format) { image->forget(); @@ -2052,7 +2092,7 @@ namespace Local { quint32 imageType; draft.stream >> locFirst >> locSecond >> imageType >> imageData; - return (locFirst == location.first && locSecond == location.second) ? StorageImageSaved(imageType, imageData) : StorageImageSaved(); + return (locFirst == location.first && locSecond == location.second) ? StorageImageSaved(StorageFileType(imageType), imageData) : StorageImageSaved(); } int32 hasImages() { @@ -2063,13 +2103,13 @@ namespace Local { return _storageImagesSize; } - void writeSticker(const StorageKey &location, const QByteArray &sticker, bool overwrite) { + void writeStickerImage(const StorageKey &location, const QByteArray &sticker, bool overwrite) { if (!_working()) return; qint32 size = _storageStickerSize(sticker.size()); - StorageMap::const_iterator i = _stickersMap.constFind(location); - if (i == _stickersMap.cend()) { - i = _stickersMap.insert(location, FileDesc(genKey(UserPath), size)); + StorageMap::const_iterator i = _stickerImagesMap.constFind(location); + if (i == _stickerImagesMap.cend()) { + i = _stickerImagesMap.insert(location, FileDesc(genKey(UserPath), size)); _storageStickersSize += size; _mapChanged = true; _writeMap(); @@ -2083,20 +2123,20 @@ namespace Local { if (i.value().second != size) { _storageStickersSize += size; _storageStickersSize -= i.value().second; - _stickersMap[location].second = size; + _stickerImagesMap[location].second = size; } } - QByteArray readSticker(const StorageKey &location) { - StorageMap::iterator j = _stickersMap.find(location); - if (j == _stickersMap.cend()) { + QByteArray readStickerImage(const StorageKey &location) { + StorageMap::iterator j = _stickerImagesMap.find(location); + if (j == _stickerImagesMap.cend()) { return QByteArray(); } FileReadDescriptor draft; if (!readEncryptedFile(draft, j.value().first, UserPath)) { clearKey(j.value().first, UserPath); _storageStickersSize -= j.value().second; - _stickersMap.erase(j); + _stickerImagesMap.erase(j); return QByteArray(); } @@ -2108,7 +2148,7 @@ namespace Local { } int32 hasStickers() { - return _stickersMap.size(); + return _stickerImagesMap.size(); } qint64 storageStickersSize() { @@ -2167,56 +2207,91 @@ namespace Local { return _storageAudiosSize; } - void writeRecentStickers() { + void writeStickers() { if (!_working()) return; - - const RecentStickerPack &recent(cRecentStickers()); - if (recent.isEmpty()) { - if (_recentStickersKey) { - clearKey(_recentStickersKey); - _recentStickersKey = 0; + + const StickerSets &sets(cStickerSets()); + if (sets.isEmpty()) { + if (_stickersKey) { + clearKey(_stickersKey); + _stickersKey = 0; _mapChanged = true; } _writeMap(); } else { - if (!_recentStickersKey) { - _recentStickersKey = genKey(); + if (!_stickersKey) { + _stickersKey = genKey(); _mapChanged = true; _writeMap(WriteMapFast); } - quint32 size = 0; - for (RecentStickerPack::const_iterator i = recent.cbegin(); i != recent.cend(); ++i) { - DocumentData *doc = i->first; - if (doc->status == FileFailed) continue; + quint32 size = sizeof(quint32) + _bytearraySize(cStickersHash()); + for (StickerSets::const_iterator i = sets.cbegin(); i != sets.cend(); ++i) { + if (i->stickers.isEmpty()) continue; - // id + value + access + date + namelen + name + mimelen + mime + dc + size + width + height + type + alt - size += sizeof(quint64) + sizeof(qint16) + sizeof(quint64) + sizeof(qint32) + _stringSize(doc->name) + _stringSize(doc->mime) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + _stringSize(doc->sticker ? doc->sticker->alt : QString()); + // id + access + title + shortName + stickersCount + size += sizeof(quint64) * 2 + _stringSize(i->title) + _stringSize(i->shortName) + sizeof(quint32); + for (StickerPack::const_iterator j = i->stickers.cbegin(), e = i->stickers.cend(); j != e; ++j) { + DocumentData *doc = *j; + + // id + access + date + namelen + name + mimelen + mime + dc + size + width + height + type + alt + type-of-set + size += sizeof(quint64) + sizeof(quint64) + sizeof(qint32) + _stringSize(doc->name) + _stringSize(doc->mime) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + _stringSize(doc->sticker->alt) + sizeof(qint32); + + // thumb-width + thumb-height + thumb-dc + thumb-volume + thumb-local + thumb-secret + size += sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(quint64) + sizeof(qint32) + sizeof(quint64); + } } EncryptedDescriptor data(size); - for (RecentStickerPack::const_iterator i = recent.cbegin(); i != recent.cend(); ++i) { - DocumentData *doc = i->first; - if (doc->status == FileFailed) continue; + data.stream << quint32(sets.size()) << cStickersHash(); + for (StickerSets::const_iterator i = sets.cbegin(); i != sets.cend(); ++i) { + if (i->stickers.isEmpty()) continue; - data.stream << quint64(doc->id) << qint16(i->second) << 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) << (doc->sticker ? doc->sticker->alt : QString()); + data.stream << quint64(i->id) << quint64(i->access) << i->title << i->shortName << quint32(i->stickers.size()); + for (StickerPack::const_iterator j = i->stickers.cbegin(), e = i->stickers.cend(); j != e; ++j) { + DocumentData *doc = *j; + 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) << doc->sticker->alt; + switch (doc->sticker->set.type()) { + case mtpc_inputStickerSetID: { + data.stream << qint32(StickerSetTypeID); + } break; + case mtpc_inputStickerSetShortName: { + data.stream << qint32(StickerSetTypeShortName); + } break; + case mtpc_inputStickerSetEmpty: + default: { + data.stream << qint32(StickerSetTypeEmpty); + } break; + } + const StorageImageLocation &loc(doc->sticker->loc); + data.stream << qint32(loc.width) << qint32(loc.height) << qint32(loc.dc) << quint64(loc.volume) << qint32(loc.local) << quint64(loc.secret); + } } - FileWriteDescriptor file(_recentStickersKey); + FileWriteDescriptor file(_stickersKey); file.writeEncrypted(data); } } - void readRecentStickers() { - if (!_recentStickersKey) return; + void importOldRecentStickers() { + if (!_recentStickersKeyOld) return; FileReadDescriptor stickers; - if (!readEncryptedFile(stickers, _recentStickersKey)) { - clearKey(_recentStickersKey); - _recentStickersKey = 0; + if (!readEncryptedFile(stickers, _recentStickersKeyOld)) { + clearKey(_recentStickersKeyOld); + _recentStickersKeyOld = 0; _writeMap(); return; } + + StickerSets &sets(cRefStickerSets()); + sets.clear(); + RecentStickerPack &recent(cRefRecentStickers()); + recent.clear(); + + cSetStickersHash(QByteArray()); + + StickerSet &def(sets.insert(DefaultStickerSetId, StickerSet(DefaultStickerSetId, 0, qsl("Great Minds"), QString())).value()); + StickerSet &custom(sets.insert(CustomStickerSetId, StickerSet(CustomStickerSetId, 0, lang(lng_custom_stickers), QString())).value()); QMap read; - RecentStickerPack recent; while (!stickers.stream.atEnd()) { quint64 id, access; QString name, mime, alt; @@ -2226,7 +2301,7 @@ namespace Local { if (stickers.version >= 7021) { stickers.stream >> alt; } - if (read.contains(id)) continue; + if (!value || read.contains(id)) continue; read.insert(id, true); QVector attributes; @@ -2240,10 +2315,109 @@ namespace Local { attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height))); } - recent.push_back(qMakePair(App::document(id, 0, access, date, attributes, mime, ImagePtr(), dc, size), value)); + DocumentData *doc = App::documentSet(id, 0, access, date, attributes, mime, ImagePtr(), dc, size, StorageImageLocation()); + if (!doc->sticker) continue; + + if (value > 0) { + def.stickers.push_back(doc); + } else { + custom.stickers.push_back(doc); + } + if (recent.size() < StickerPanPerRow * StickerPanRowsPerPage && qAbs(value) > 1) recent.push_back(qMakePair(doc, qAbs(value))); + } + if (def.stickers.isEmpty()) sets.remove(DefaultStickerSetId); + if (custom.stickers.isEmpty()) sets.remove(CustomStickerSetId); + + writeStickers(); + writeUserSettings(); + + clearKey(_recentStickersKeyOld); + _recentStickersKeyOld = 0; + _writeMap(); + } + + void readStickers() { + if (!_stickersKey) { + return importOldRecentStickers(); } - cSetRecentStickers(recent); + FileReadDescriptor stickers; + if (!readEncryptedFile(stickers, _stickersKey)) { + clearKey(_stickersKey); + _stickersKey = 0; + _writeMap(); + return; + } + + StickerSets &sets(cRefStickerSets()); + sets.clear(); + + quint32 cnt; + QByteArray hash; + stickers.stream >> cnt >> hash; + for (int32 i = 0; i < cnt; ++i) { + quint64 setId = 0, setAccess = 0; + QString setTitle, setShortName; + quint32 scnt = 0; + stickers.stream >> setId >> setAccess >> setTitle >> setShortName >> scnt; + + if (setId == DefaultStickerSetId) { + setTitle = qsl("Great Minds"); + } else if (setId == CustomStickerSetId) { + setTitle = lang(lng_custom_stickers); + } + StickerSet &set(sets.insert(setId, StickerSet(setId, setAccess, setTitle, setShortName)).value()); + set.stickers.reserve(scnt); + + QMap read; + for (int32 j = 0; j < scnt; ++j) { + quint64 id, access; + QString name, mime, alt; + qint32 date, dc, size, width, height, type, typeOfSet; + stickers.stream >> id >> access >> date >> name >> mime >> dc >> size >> width >> height >> type >> alt >> typeOfSet; + + qint32 thumbWidth, thumbHeight, thumbDc, thumbLocal; + quint64 thumbVolume, thumbSecret; + stickers.stream >> thumbWidth >> thumbHeight >> thumbDc >> thumbVolume >> thumbLocal >> thumbSecret; + + if (read.contains(id)) continue; + read.insert(id, true); + + if (setId == DefaultStickerSetId || setId == CustomStickerSetId) { + typeOfSet = StickerSetTypeEmpty; + } + + QVector attributes; + if (!name.isEmpty()) attributes.push_back(MTP_documentAttributeFilename(MTP_string(name))); + if (type == AnimatedDocument) { + attributes.push_back(MTP_documentAttributeAnimated()); + } else if (type == StickerDocument) { + switch (typeOfSet) { + case StickerSetTypeID: { + attributes.push_back(MTP_documentAttributeSticker(MTP_string(alt), MTP_inputStickerSetID(MTP_long(setId), MTP_long(setAccess)))); + } break; + case StickerSetTypeShortName: { + attributes.push_back(MTP_documentAttributeSticker(MTP_string(alt), MTP_inputStickerSetShortName(MTP_string(setShortName)))); + } break; + case StickerSetTypeEmpty: + default: { + attributes.push_back(MTP_documentAttributeSticker(MTP_string(alt), MTP_inputStickerSetEmpty())); + } break; + } + } + if (width > 0 && height > 0) { + attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height))); + } + + StorageImageLocation thumb(thumbWidth, thumbHeight, thumbDc, thumbVolume, thumbLocal, thumbSecret); + DocumentData *doc = App::documentSet(id, 0, access, date, attributes, mime, thumb.dc ? ImagePtr(thumb) : ImagePtr(), dc, size, thumb); + if (!doc->sticker) continue; + + set.stickers.push_back(doc); + } + } + + cSetStickersHash(hash); } void writeBackground(int32 id, const QImage &img) { @@ -2412,8 +2586,8 @@ namespace Local { _storageImagesSize = 0; _mapChanged = true; } - if (!_stickersMap.isEmpty()) { - _stickersMap.clear(); + if (!_stickerImagesMap.isEmpty()) { + _stickerImagesMap.clear(); _storageStickersSize = 0; _mapChanged = true; } @@ -2434,8 +2608,12 @@ namespace Local { _locationsKey = 0; _mapChanged = true; } - if (_recentStickersKey) { - _recentStickersKey = 0; + if (_recentStickersKeyOld) { + _recentStickersKeyOld = 0; + _mapChanged = true; + } + if (_stickersKey) { + _stickersKey = 0; _mapChanged = true; } if (_recentHashtagsKey) { @@ -2462,9 +2640,9 @@ namespace Local { _mapChanged = true; } if (data->stickers.isEmpty()) { - data->stickers = _stickersMap; + data->stickers = _stickerImagesMap; } else { - for (StorageMap::const_iterator i = _stickersMap.cbegin(), e = _stickersMap.cend(); i != e; ++i) { + for (StorageMap::const_iterator i = _stickerImagesMap.cbegin(), e = _stickerImagesMap.cend(); i != e; ++i) { StorageKey k = i.key(); while (data->stickers.constFind(k) != data->stickers.cend()) { ++k.second; @@ -2472,8 +2650,8 @@ namespace Local { data->stickers.insert(k, i.value()); } } - if (!_stickersMap.isEmpty()) { - _stickersMap.clear(); + if (!_stickerImagesMap.isEmpty()) { + _stickerImagesMap.clear(); _storageStickersSize = 0; _mapChanged = true; } diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index f3a93292d..e306168eb 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -122,8 +122,8 @@ namespace Local { int32 hasImages(); qint64 storageImagesSize(); - void writeSticker(const StorageKey &location, const QByteArray &data, bool overwrite = true); - QByteArray readSticker(const StorageKey &location); + void writeStickerImage(const StorageKey &location, const QByteArray &data, bool overwrite = true); + QByteArray readStickerImage(const StorageKey &location); int32 hasStickers(); qint64 storageStickersSize(); @@ -132,8 +132,8 @@ namespace Local { int32 hasAudios(); qint64 storageAudiosSize(); - void writeRecentStickers(); - void readRecentStickers(); + void writeStickers(); + void readStickers(); void writeBackground(int32 id, const QImage &img); bool readBackground(); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index d2bb140fb..e558a09b4 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -25,6 +25,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "settingswidget.h" #include "mainwidget.h" #include "boxes/confirmbox.h" +#include "boxes/stickersetbox.h" #include "localstorage.h" @@ -549,6 +550,10 @@ void MainWidget::updateMutedIn(int32 seconds) { _updateMutedTimer.start(ms); } +void MainWidget::updateStickers() { + history.updateStickers(); +} + void MainWidget::onUpdateMuted() { App::updateMuted(); } @@ -977,7 +982,7 @@ void MainWidget::saveRecentHashtags(const QString &text) { } } if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) { - Local::readRecentStickers(); + Local::readRecentHashtags(); recent = cRecentWriteHashtags(); } found = true; @@ -2473,7 +2478,7 @@ void MainWidget::start(const MTPUser &user) { } _started = true; App::wnd()->sendServiceHistoryRequest(); - Local::readRecentStickers(); + Local::readStickers(); history.start(); } @@ -2493,6 +2498,11 @@ void MainWidget::openLocalUrl(const QString &url) { if (m.hasMatch()) { joinGroupByHash(m.captured(1)); } + } else if (u.startsWith(QLatin1String("tg://addstickers"), Qt::CaseInsensitive)) { + QRegularExpressionMatch m = QRegularExpression(qsl("^tg://addstickers/?\\?set=([a-zA-Z0-9\\.\\_]+)$"), QRegularExpression::CaseInsensitiveOption).match(u); + if (m.hasMatch()) { + stickersBox(MTP_inputStickerSetShortName(MTP_string(m.captured(1)))); + } } } @@ -2513,6 +2523,17 @@ void MainWidget::joinGroupByHash(const QString &hash) { MTP::send(MTPmessages_CheckChatInvite(MTP_string(hash)), rpcDone(&MainWidget::inviteCheckDone, hash), rpcFail(&MainWidget::inviteCheckFail)); } +void MainWidget::stickersBox(const MTPInputStickerSet &set) { + StickerSetBox *box = new StickerSetBox(set); + connect(box, SIGNAL(installed(uint64)), this, SLOT(onStickersInstalled(uint64))); + App::wnd()->showLayer(box); +} + +void MainWidget::onStickersInstalled(uint64 setId) { + emit stickersUpdated(); + history.stickersInstalled(setId); +} + void MainWidget::usernameResolveDone(bool toProfile, const MTPUser &user) { App::wnd()->hideLayer(); UserData *u = App::feedUsers(MTP_vector(1, user)); @@ -2733,28 +2754,24 @@ void MainWidget::updateNotifySetting(PeerData *peer, bool enabled) { } void MainWidget::incrementSticker(DocumentData *sticker) { - RecentStickerPack recent(cRecentStickers()); + if (!sticker || !sticker->sticker) return; + + RecentStickerPack &recent(cGetRecentStickers()); RecentStickerPack::iterator i = recent.begin(), e = recent.end(); for (; i != e; ++i) { if (i->first == sticker) { - if (i->second > 0) { - ++i->second; - } else { - --i->second; - } - if (qAbs(i->second) > 0x4000) { + ++i->second; + if (i->second > 0x8000) { for (RecentStickerPack::iterator j = recent.begin(); j != e; ++j) { - if (qAbs(j->second) > 1) { + if (j->second > 1) { j->second /= 2; - } else if (j->second > 0) { - j->second = 1; } else { - j->second = -1; + j->second = 1; } } } for (; i != recent.begin(); --i) { - if (qAbs((i - 1)->second) > qAbs(i->second)) { + if ((i - 1)->second > i->second) { break; } qSwap(*i, *(i - 1)); @@ -2763,11 +2780,45 @@ void MainWidget::incrementSticker(DocumentData *sticker) { } } if (i == e) { - recent.push_front(qMakePair(sticker, -(recent.isEmpty() ? 1 : qAbs(recent.front().second)))); + while (recent.size() >= StickerPanPerRow * StickerPanRowsPerPage) recent.pop_back(); + recent.push_back(qMakePair(sticker, 1)); + for (i = recent.end() - 1; i != recent.begin(); --i) { + if ((i - 1)->second > i->second) { + break; + } + qSwap(*i, *(i - 1)); + } } - cSetRecentStickers(recent); - Local::writeRecentStickers(); + Local::writeUserSettings(); + + bool found = false; + uint64 setId = 0; + QString setName; + switch (sticker->sticker->set.type()) { + case mtpc_inputStickerSetID: setId = sticker->sticker->set.c_inputStickerSetID().vid.v; break; + case mtpc_inputStickerSetShortName: setName = qs(sticker->sticker->set.c_inputStickerSetShortName().vshort_name).toLower().trimmed(); break; + } + StickerSets &sets(cRefStickerSets()); + for (StickerSets::const_iterator i = sets.cbegin(); i != sets.cend(); ++i) { + if (i->id == CustomStickerSetId || (setId && i->id == setId) || (!setName.isEmpty() && i->shortName.toLower().trimmed() == setName) || (!setId && setName.isEmpty() && i->id == DefaultStickerSetId)) { + for (int32 j = 0, l = i->stickers.size(); j < l; ++j) { + if (i->stickers.at(j) == sticker) { + found = true; + break; + } + } + if (found) break; + } + } + if (!found) { + StickerSets::iterator it = sets.find(CustomStickerSetId); + if (it == sets.cend()) { + it = sets.insert(CustomStickerSetId, StickerSet(CustomStickerSetId, 0, lang(lng_custom_stickers), QString())); + } + it->stickers.push_back(sticker); + Local::writeStickers(); + } history.updateRecentStickers(); } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 305e1fa62..58554af30 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -185,9 +185,12 @@ public: bool animStep(float64 ms); void start(const MTPUser &user); + void openLocalUrl(const QString &str); void openUserByName(const QString &name, bool toProfile = false); void joinGroupByHash(const QString &hash); + void stickersBox(const MTPInputStickerSet &set); + void startFull(const MTPVector &users); bool started(); void applyNotifySetting(const MTPNotifyPeer &peer, const MTPPeerNotifySettings &settings, History *history = 0); @@ -341,6 +344,8 @@ public: void webPageUpdated(WebPageData *page); void updateMutedIn(int32 seconds); + void updateStickers(); + ~MainWidget(); signals: @@ -352,6 +357,7 @@ signals: void dialogToTop(const History::DialogLinks &links); void dialogsUpdated(); void showPeerAsync(quint64 peer, qint32 msgId, bool back, bool force); + void stickersUpdated(); public slots: @@ -402,6 +408,8 @@ public slots: void onUpdateMuted(); + void onStickersInstalled(uint64 setId); + private: void partWasRead(PeerData *peer, const MTPmessages_AffectedHistory &result); diff --git a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp index 59293f8fc..5d25cecc9 100644 --- a/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp +++ b/Telegram/SourceFiles/mtproto/mtpFileLoader.cpp @@ -44,7 +44,7 @@ namespace { LoaderQueues queues; } -mtpFileLoader::mtpFileLoader(int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size) : prev(0), next(0), +mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &volume, int32 local, const uint64 &secret, int32 size) : prev(0), next(0), priority(0), inQueue(false), complete(false), triedLocal(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false), dc(dc), locationType(0), volume(volume), local(local), secret(secret), id(0), access(0), fileIsOpen(false), size(size), type(mtpc_storage_fileUnknown) { @@ -247,23 +247,24 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe psPostprocessFile(QFileInfo(file).absoluteFilePath()); } removeFromQueue(); - App::wnd()->update(); - App::wnd()->notifyUpdateAllPhotos(); + + emit App::wnd()->imageLoaded(); + if (!queue->queries) { App::app()->killDownloadSessionsStart(dc); } if (!locationType && triedLocal && (fname.isEmpty() || duplicateInData)) { - Local::writeImage(storageKey(dc, volume, local), StorageImageSaved(type, data)); + Local::writeImage(storageKey(dc, volume, local), StorageImageSaved(mtpToStorageType(type), data)); } else if (locationType && triedLocal) { if (!fname.isEmpty()) { - Local::writeFileLocation(mediaKey(locationType, dc, id), FileLocation(type, fname)); + Local::writeFileLocation(mediaKey(mtpToLocationType(locationType), dc, id), FileLocation(mtpToStorageType(type), fname)); } if (duplicateInData) { if (locationType == mtpc_inputDocumentFileLocation) { - Local::writeSticker(mediaKey(locationType, dc, id), data); + Local::writeStickerImage(mediaKey(mtpToLocationType(locationType), dc, id), data); } else if (locationType == mtpc_inputAudioFileLocation) { - Local::writeAudio(mediaKey(locationType, dc, id), data); + Local::writeAudio(mediaKey(mtpToLocationType(locationType), dc, id), data); } } } @@ -308,9 +309,9 @@ void mtpFileLoader::start(bool loadFirst, bool prior) { if (!locationType) { triedLocal = true; StorageImageSaved cached = Local::readImage(storageKey(dc, volume, local)); - if (cached.type != mtpc_storage_fileUnknown) { + if (cached.type != StorageFileUnknown) { data = cached.data; - type = cached.type; + type = mtpFromStorageType(cached.type); } } else if (locationType) { if (!fname.isEmpty()) { @@ -319,11 +320,11 @@ void mtpFileLoader::start(bool loadFirst, bool prior) { if (duplicateInData) { if (locationType == mtpc_inputDocumentFileLocation) { triedLocal = true; - data = Local::readSticker(mediaKey(locationType, dc, id)); + data = Local::readStickerImage(mediaKey(mtpToLocationType(locationType), dc, id)); if (!data.isEmpty()) type = mtpc_storage_filePartial; } else if (locationType == mtpc_inputAudioFileLocation) { triedLocal = true; - data = Local::readAudio(mediaKey(locationType, dc, id)); + data = Local::readAudio(mediaKey(mtpToLocationType(locationType), dc, id)); if (!data.isEmpty()) type = mtpc_storage_filePartial; } } @@ -344,8 +345,7 @@ void mtpFileLoader::start(bool loadFirst, bool prior) { fileIsOpen = false; psPostprocessFile(QFileInfo(file).absoluteFilePath()); } - App::wnd()->update(); - App::wnd()->notifyUpdateAllPhotos(); + emit App::wnd()->imageLoaded(); emit progress(this); return loadNext(); } diff --git a/Telegram/SourceFiles/mtproto/mtpFileLoader.h b/Telegram/SourceFiles/mtproto/mtpFileLoader.h index bd28bcc42..0d1368c62 100644 --- a/Telegram/SourceFiles/mtproto/mtpFileLoader.h +++ b/Telegram/SourceFiles/mtproto/mtpFileLoader.h @@ -27,7 +27,7 @@ class mtpFileLoader : public QObject, public RPCSender { public: - mtpFileLoader(int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size = 0); + mtpFileLoader(int32 dc, const uint64 &volume, int32 local, const uint64 &secret, int32 size = 0); mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, mtpTypeId locType, const QString &to, int32 size); mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, mtpTypeId locType, const QString &to, int32 size, bool todata); bool done() const; @@ -82,9 +82,9 @@ private: int32 dc; mtpTypeId locationType; // 0 or mtpc_inputVideoFileLocation / mtpc_inputAudioFileLocation / mtpc_inputDocumentFileLocation - int64 volume; // for photo locations + uint64 volume; // for photo locations int32 local; - int64 secret; + uint64 secret; uint64 id; // for other locations uint64 access; diff --git a/Telegram/SourceFiles/profilewidget.cpp b/Telegram/SourceFiles/profilewidget.cpp index 8386148a9..f77ef4c4e 100644 --- a/Telegram/SourceFiles/profilewidget.cpp +++ b/Telegram/SourceFiles/profilewidget.cpp @@ -256,6 +256,7 @@ void ProfileInner::onMediaAudios() { } void ProfileInner::onInvitationLink() { + DEBUG_LOG(("Setting text to clipboard from invite url: %1").arg(_peerChat->invitationUrl)); QApplication::clipboard()->setText(_peerChat->invitationUrl); App::wnd()->showLayer(new ConfirmBox(lang(lng_group_invite_copied), true)); } @@ -769,10 +770,12 @@ void ProfileInner::onMenuDestroy(QObject *obj) { } void ProfileInner::onCopyPhone() { + DEBUG_LOG(("Setting text to clipboard from user phone: %1").arg(_phoneText)); QApplication::clipboard()->setText(_phoneText); } void ProfileInner::onCopyUsername() { + DEBUG_LOG(("Setting text to clipboard from username: @%1").arg(_peerUser->username)); QApplication::clipboard()->setText('@' + _peerUser->username); } diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index c0a09c051..741bd5c0c 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -89,12 +89,15 @@ RecentEmojiPack gRecentEmojis; RecentEmojisPreload gRecentEmojisPreload; EmojiColorVariants gEmojiVariants; -AllStickers gStickers; QByteArray gStickersHash; EmojiStickersMap gEmojiStickers; +RecentStickerPreload gRecentStickersPreload; RecentStickerPack gRecentStickers; +StickerSets gStickerSets; + +uint64 gLastStickersUpdate = 0; RecentHashtagPack gRecentWriteHashtags, gRecentSearchHashtags; @@ -249,7 +252,7 @@ RecentEmojiPack &cGetRecentEmojis() { 0xD83DDE15LLU, }; for (int32 i = 0, s = sizeof(defaultRecent) / sizeof(defaultRecent[0]); i < s; ++i) { - if (r.size() >= EmojiPadPerRow * EmojiPadRowsPerPage) break; + if (r.size() >= EmojiPanPerRow * EmojiPanRowsPerPage) break; EmojiPtr ep(emojiGet(defaultRecent[i])); if (!ep || ep == TwoSymbolEmoji) continue; @@ -268,3 +271,20 @@ RecentEmojiPack &cGetRecentEmojis() { } return cRefRecentEmojis(); } + +RecentStickerPack &cGetRecentStickers() { + if (cRecentStickers().isEmpty() && !cRecentStickersPreload().isEmpty()) { + RecentStickerPreload p(cRecentStickersPreload()); + cSetRecentStickersPreload(RecentStickerPreload()); + + RecentStickerPack &recent(cRefRecentStickers()); + recent.reserve(p.size()); + for (RecentStickerPreload::const_iterator i = p.cbegin(), e = p.cend(); i != e; ++i) { + DocumentData *doc = App::document(i->first); + if (!doc || !doc->sticker) continue; + + recent.push_back(qMakePair(doc, i->second)); + } + } + return cRefRecentStickers(); +} diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index b63480b9d..5028c1929 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -183,15 +183,31 @@ RecentEmojiPack &cGetRecentEmojis(); struct DocumentData; typedef QVector StickerPack; -typedef QMap AllStickers; -DeclareSetting(AllStickers, Stickers); DeclareSetting(QByteArray, StickersHash); typedef QMap EmojiStickersMap; DeclareSetting(EmojiStickersMap, EmojiStickers); -typedef QList > RecentStickerPack; -DeclareSetting(RecentStickerPack, RecentStickers); +typedef QList > RecentStickerPackOld; +typedef QVector > RecentStickerPreload; +typedef QVector > RecentStickerPack; +DeclareSetting(RecentStickerPreload, RecentStickersPreload); +DeclareRefSetting(RecentStickerPack, RecentStickers); + +RecentStickerPack &cGetRecentStickers(); + +DeclareSetting(uint64, LastStickersUpdate); + +static const uint64 DefaultStickerSetId = 0, CustomStickerSetId = 0xFFFFFFFFFFFFFFFFLLU, RecentStickerSetId = 0xFFFFFFFFFFFFFFFELLU; +struct StickerSet { + StickerSet(uint64 id, uint64 access, const QString &title, const QString &shortName) : id(id), access(access), title(title), shortName(shortName) { + } + uint64 id, access; + QString title, shortName; + StickerPack stickers; +}; +typedef QMap StickerSets; +DeclareRefSetting(StickerSets, StickerSets); typedef QList > RecentHashtagPack; DeclareSetting(RecentHashtagPack, RecentWriteHashtags); diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 8cd5809f3..3e99b18fc 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -348,7 +348,7 @@ void VideoCancelLink::onClick(Qt::MouseButton button) const { VideoData::VideoData(const VideoId &id, const uint64 &access, int32 user, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size) : id(id), access(access), user(user), date(date), duration(duration), w(w), h(h), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), fileType(0), openOnSave(0), openOnSaveMsgId(0), loader(0) { - location = Local::readFileLocation(mediaKey(mtpc_inputVideoFileLocation, dc, id)); + location = Local::readFileLocation(mediaKey(VideoFileLocation, dc, id)); } void VideoData::save(const QString &toFile) { @@ -361,7 +361,7 @@ void VideoData::save(const QString &toFile) { QString VideoData::already(bool check) { if (!check) return location.name; - if (!location.check()) location = Local::readFileLocation(mediaKey(mtpc_inputVideoFileLocation, dc, id)); + if (!location.check()) location = Local::readFileLocation(mediaKey(VideoFileLocation, dc, id)); return location.name; } @@ -442,7 +442,7 @@ void AudioCancelLink::onClick(Qt::MouseButton button) const { AudioData::AudioData(const AudioId &id, const uint64 &access, int32 user, int32 date, const QString &mime, int32 duration, int32 dc, int32 size) : id(id), access(access), user(user), date(date), mime(mime), duration(duration), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), openOnSaveMsgId(0), loader(0) { - location = Local::readFileLocation(mediaKey(mtpc_inputAudioFileLocation, dc, id)); + location = Local::readFileLocation(mediaKey(AudioFileLocation, dc, id)); } void AudioData::save(const QString &toFile) { @@ -455,7 +455,7 @@ void AudioData::save(const QString &toFile) { QString AudioData::already(bool check) { if (!check) return location.name; - if (!location.check()) location = Local::readFileLocation(mediaKey(mtpc_inputAudioFileLocation, dc, id)); + if (!location.check()) location = Local::readFileLocation(mediaKey(AudioFileLocation, dc, id)); return location.name; } @@ -560,7 +560,7 @@ void DocumentCancelLink::onClick(Qt::MouseButton button) const { DocumentData::DocumentData(const DocumentId &id, const uint64 &access, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size) : id(id), type(FileDocument), duration(0), access(access), date(date), mime(mime), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), openOnSaveMsgId(0), loader(0), sticker(0) { setattributes(attributes); - location = Local::readFileLocation(mediaKey(mtpc_inputDocumentFileLocation, dc, id)); + location = Local::readFileLocation(mediaKey(DocumentFileLocation, dc, id)); } void DocumentData::setattributes(const QVector &attributes) { @@ -607,7 +607,7 @@ void DocumentData::save(const QString &toFile) { QString DocumentData::already(bool check) { if (!check) return location.name; - if (!location.check()) location = Local::readFileLocation(mediaKey(mtpc_inputDocumentFileLocation, dc, id)); + if (!location.check()) location = Local::readFileLocation(mediaKey(DocumentFileLocation, dc, id)); return location.name; } diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 165d80bae..d27d6dee8 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -253,7 +253,7 @@ struct VideoData { void finish() { if (loader->done()) { - location = FileLocation(loader->fileType(), loader->fileName()); + location = FileLocation(mtpToStorageType(loader->fileType()), loader->fileName()); } loader->deleteLater(); loader->rpcInvalidate(); @@ -339,7 +339,7 @@ struct AudioData { void finish() { if (loader->done()) { - location = FileLocation(loader->fileType(), loader->fileName()); + location = FileLocation(mtpToStorageType(loader->fileType()), loader->fileName()); data = loader->bytes(); } loader->deleteLater(); @@ -409,6 +409,7 @@ struct StickerData { QString alt; MTPInputStickerSet set; + StorageImageLocation loc; // doc thumb location }; enum DocumentType { @@ -446,7 +447,7 @@ struct DocumentData { void finish() { if (loader->done()) { - location = FileLocation(loader->fileType(), loader->fileName()); + location = FileLocation(mtpToStorageType(loader->fileType()), loader->fileName()); data = loader->bytes(); } loader->deleteLater(); diff --git a/Telegram/SourceFiles/types.h b/Telegram/SourceFiles/types.h index e72fbd381..27bb6d1fc 100644 --- a/Telegram/SourceFiles/types.h +++ b/Telegram/SourceFiles/types.h @@ -225,51 +225,52 @@ QString translitRusEng(const QString &rus); QString rusKeyboardLayoutSwitch(const QString &from); enum DataBlockId { - dbiKey = 0, - dbiUser = 1, - dbiDcOption = 2, - dbiMaxGroupCount = 3, - dbiMutePeer = 4, - dbiSendKey = 5, - dbiAutoStart = 6, - dbiStartMinimized = 7, - dbiSoundNotify = 8, - dbiWorkMode = 9, - dbiSeenTrayTooltip = 10, - dbiDesktopNotify = 11, - dbiAutoUpdate = 12, - dbiLastUpdateCheck = 13, - dbiWindowPosition = 14, - dbiConnectionType = 15, + dbiKey = 0x00, + dbiUser = 0x01, + dbiDcOption = 0x02, + dbiMaxGroupCount = 0x03, + dbiMutePeer = 0x04, + dbiSendKey = 0x05, + dbiAutoStart = 0x06, + dbiStartMinimized = 0x07, + dbiSoundNotify = 0x08, + dbiWorkMode = 0x09, + dbiSeenTrayTooltip = 0x0a, + dbiDesktopNotify = 0x0b, + dbiAutoUpdate = 0x0c, + dbiLastUpdateCheck = 0x0d, + dbiWindowPosition = 0x0e, + dbiConnectionType = 0x0f, // 16 reserved - dbiDefaultAttach = 17, - dbiCatsAndDogs = 18, - dbiReplaceEmojis = 19, - dbiAskDownloadPath = 20, - dbiDownloadPath = 21, - dbiScale = 22, - dbiEmojiTab = 23, - dbiRecentEmojisOld = 24, - dbiLoggedPhoneNumber = 25, - dbiMutedPeers = 26, + dbiDefaultAttach = 0x11, + dbiCatsAndDogs = 0x12, + dbiReplaceEmojis = 0x13, + dbiAskDownloadPath = 0x14, + dbiDownloadPath = 0x15, + dbiScale = 0x16, + dbiEmojiTab = 0x17, + dbiRecentEmojisOld = 0x18, + dbiLoggedPhoneNumber = 0x19, + dbiMutedPeers = 0x1a, // 27 reserved - dbiNotifyView = 28, - dbiSendToMenu = 29, - dbiCompressPastedImage = 30, - dbiLang = 31, - dbiLangFile = 32, - dbiTileBackground = 33, - dbiAutoLock = 34, - dbiDialogLastPath = 35, - dbiRecentEmojis = 36, - dbiEmojiVariants = 37, + dbiNotifyView = 0x1c, + dbiSendToMenu = 0x1d, + dbiCompressPastedImage = 0x1e, + dbiLang = 0x1f, + dbiLangFile = 0x20, + dbiTileBackground = 0x21, + dbiAutoLock = 0x22, + dbiDialogLastPath = 0x23, + dbiRecentEmojis = 0x24, + dbiEmojiVariants = 0x25, + dbiRecentStickers = 0x26, - dbiEncryptedWithSalt = 333, - dbiEncrypted = 444, + dbiEncryptedWithSalt = 333, + dbiEncrypted = 444, // 500-600 reserved - dbiVersion = 666, + dbiVersion = 666, }; enum DBISendKey { diff --git a/Telegram/SourceFiles/window.cpp b/Telegram/SourceFiles/window.cpp index 420d8b017..b3358ab12 100644 --- a/Telegram/SourceFiles/window.cpp +++ b/Telegram/SourceFiles/window.cpp @@ -381,6 +381,9 @@ _connecting(0), _clearManager(0), dragging(false), _inactivePress(false), _shoul connect(&_isActiveTimer, SIGNAL(timeout()), this, SLOT(updateIsActive())); connect(&_autoLockTimer, SIGNAL(timeout()), this, SLOT(checkAutoLock())); + + connect(this, SIGNAL(imageLoaded()), this, SLOT(update())); + connect(this, SIGNAL(imageLoaded()), this, SLOT(notifyUpdateAllPhotos())); } void Window::inactivePress(bool inactive) { @@ -609,7 +612,7 @@ void Window::sendServiceHistoryRequest() { } void Window::setupMain(bool anim, const MTPUser *self) { - Local::readRecentStickers(); + Local::readStickers(); QPixmap bg = anim ? myGrab(this, QRect(0, st::titleHeight, width(), height() - st::titleHeight)) : QPixmap(); clearWidgets(); diff --git a/Telegram/SourceFiles/window.h b/Telegram/SourceFiles/window.h index e6e2f981e..61504ec17 100644 --- a/Telegram/SourceFiles/window.h +++ b/Telegram/SourceFiles/window.h @@ -219,7 +219,6 @@ public: void notifyItemRemoved(HistoryItem *item); void notifyStopHiding(); void notifyStartHiding(); - void notifyUpdateAllPhotos(); void notifyUpdateAll(); void notifyActivateAll(); @@ -270,6 +269,8 @@ public slots: QImage iconWithCounter(int size, int count, style::color bg, bool smallIcon); + void notifyUpdateAllPhotos(); + signals: void resized(const QSize &size); @@ -277,6 +278,8 @@ signals: void tempDirClearFailed(int task); void newAuthorization(); + void imageLoaded(); + private: void placeSmallCounter(QImage &img, int size, int count, style::color bg, const QPoint &shift, style::color color); diff --git a/Telegram/Telegram.rc b/Telegram/Telegram.rc index 1fc61e8867edf404acb46e622e979c7f0835ced2..e7d8831a39de05ef5b781838328e8c60a727a062 100644 GIT binary patch delta 53 zcmZ3Yy+nIM5ig_3true true + + true + true + true true @@ -635,6 +639,10 @@ true true + + true + true + true true @@ -918,6 +926,10 @@ true true + + true + true + true true @@ -966,6 +978,7 @@ + @@ -1355,6 +1368,20 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/boxes/abstractbox.h" -DAL_LIBTYPE_STATIC -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\libogg-1.3.2\include" "-I.\..\..\Libraries\opus\include" "-I.\..\..\Libraries\opusfile\include" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.4.0\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.4.0\QtGui" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing stickersetbox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/boxes/stickersetbox.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\libogg-1.3.2\include" "-I.\..\..\Libraries\opus\include" "-I.\..\..\Libraries\opusfile\include" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.4.0\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.4.0\QtGui" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing stickersetbox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/boxes/stickersetbox.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\libogg-1.3.2\include" "-I.\..\..\Libraries\opus\include" "-I.\..\..\Libraries\opusfile\include" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.4.0\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.4.0\QtGui" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing stickersetbox.h... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/boxes/stickersetbox.h" -DAL_LIBTYPE_STATIC -DUNICODE -D_WITH_DEBUG -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\OpenSSL-Win32\include" "-I.\..\..\Libraries\libogg-1.3.2\include" "-I.\..\..\Libraries\opus\include" "-I.\..\..\Libraries\opusfile\include" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.4.0\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.4.0\QtGui" + Moc%27ing animation.h... diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 32822c2ce..74e10dc9c 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -879,6 +879,18 @@ Source Files + + Generated Files\Deploy + + + Generated Files\Debug + + + Generated Files\Release + + + boxes + @@ -1168,6 +1180,9 @@ intro + + boxes +