diff --git a/Telegram/Resources/art/sprite.png b/Telegram/Resources/art/sprite.png index 932c1d9903..bb18b32eaa 100644 Binary files a/Telegram/Resources/art/sprite.png and b/Telegram/Resources/art/sprite.png differ diff --git a/Telegram/Resources/art/sprite_200x.png b/Telegram/Resources/art/sprite_200x.png index dcad411963..95f4086129 100644 Binary files a/Telegram/Resources/art/sprite_200x.png and b/Telegram/Resources/art/sprite_200x.png differ diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index 570cc6603d..55c6121ca0 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -1866,6 +1866,8 @@ emojiSymbolsActive: sprite(287px, 286px, 21px, 22px); stickersSettings: sprite(140px, 124px, 21px, 22px); savedGifsOver: sprite(329px, 286px, 21px, 22px); savedGifsActive: sprite(350px, 286px, 21px, 22px); +featuredStickersOver: sprite(329px, 264px, 21px, 22px); +featuredStickersActive: sprite(350px, 264px, 21px, 22px); stickersSettingsUnreadSize: 17px; stickersSettingsUnreadPosition: point(4px, 5px); diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 278915e905..9c2484f6d8 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -580,6 +580,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_action_pinned_media_contact" = "a contact information"; "lng_action_pinned_media_location" = "a location mark"; "lng_action_pinned_media_sticker" = "a sticker"; +"lng_action_game_score" = "{from} scored {score} in {game}"; "lng_profile_migrate_reached" = "{count:_not_used_|# member|# members} limit reached"; "lng_profile_migrate_body" = "To get over this limit, you can upgrade your group to a supergroup."; @@ -683,6 +684,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_switch_stickers" = "Stickers"; "lng_switch_stickers_gifs" = "GIFs & Stickers"; "lng_switch_emoji" = "Emoji"; +"lng_stickers_featured_add" = "Add"; "lng_saved_gifs" = "Saved GIFs"; "lng_inline_bot_results" = "Results from {inline_bot}"; @@ -762,6 +764,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_open_this_link" = "Open this link?"; "lng_open_link" = "Open"; +"lng_allow_bot_pass" = "Do you allow {bot_name} to pass your Telegram name and id to the web pages you open via this bot?"; +"lng_allow_bot" = "Allow"; "lng_bot_start" = "Start"; "lng_bot_choose_group" = "Choose Group"; @@ -870,6 +874,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "lng_reply_cant" = "Sorry, no way to reply to an old message in supergroup :("; "lng_reply_cant_forward" = "Sorry, no way to reply to an old message in supergroup :( Do you wish to forward it and add your comment?"; +"lng_share_title" = "Share to"; +"lng_share_confirm" = "Share"; +"lng_share_wrong_user" = "This game was opened from a different user."; + "lng_contact_phone" = "Phone number"; "lng_enter_contact_data" = "New Contact"; "lng_edit_group_title" = "Edit group name"; diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 4e9b78386d..eedca3a782 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -1857,7 +1857,8 @@ namespace { photoSizes.push_back(MTP_photoSize(MTP_string("a"), uphoto.vphoto_small, MTP_int(160), MTP_int(160), MTP_int(0))); photoSizes.push_back(MTP_photoSize(MTP_string("c"), uphoto.vphoto_big, MTP_int(640), MTP_int(640), MTP_int(0))); - return MTP_photo(uphoto.vphoto_id, MTP_long(0), date, MTP_vector(photoSizes)); + MTPDphoto::Flags photoFlags = 0; + return MTP_photo(MTP_flags(photoFlags), uphoto.vphoto_id, MTP_long(0), date, MTP_vector(photoSizes)); } return MTP_photoEmpty(MTP_long(0)); } diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 3cb326d6cb..90c06fb183 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -877,7 +877,7 @@ bool AppClass::peerPhotoFail(PeerId peer, const RPCError &error) { void AppClass::peerClearPhoto(PeerId id) { if (MTP::authedId() && peerToUser(id) == MTP::authedId()) { - MTP::send(MTPphotos_UpdateProfilePhoto(MTP_inputPhotoEmpty(), MTP_inputPhotoCropAuto()), rpcDone(&AppClass::selfPhotoCleared), rpcFail(&AppClass::peerPhotoFail, id)); + MTP::send(MTPphotos_UpdateProfilePhoto(MTP_inputPhotoEmpty()), rpcDone(&AppClass::selfPhotoCleared), rpcFail(&AppClass::peerPhotoFail, id)); } else if (peerIsChat(id)) { MTP::send(MTPmessages_EditChatPhoto(peerToBareMTPInt(id), MTP_inputChatPhotoEmpty()), rpcDone(&AppClass::chatPhotoCleared, id), rpcFail(&AppClass::peerPhotoFail, id)); } else if (peerIsChannel(id)) { @@ -966,17 +966,17 @@ void AppClass::killDownloadSessions() { void AppClass::photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputFile &file) { if (!App::self()) return; - QMap::iterator i = photoUpdates.find(msgId); + auto i = photoUpdates.find(msgId); if (i != photoUpdates.end()) { - PeerId id = i.value(); + auto id = i.value(); if (MTP::authedId() && peerToUser(id) == MTP::authedId()) { - MTP::send(MTPphotos_UploadProfilePhoto(file, MTP_string(""), MTP_inputGeoPointEmpty(), MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100))), rpcDone(&AppClass::selfPhotoDone), rpcFail(&AppClass::peerPhotoFail, id)); + MTP::send(MTPphotos_UploadProfilePhoto(file), rpcDone(&AppClass::selfPhotoDone), rpcFail(&AppClass::peerPhotoFail, id)); } else if (peerIsChat(id)) { - History *hist = App::history(id); - hist->sendRequestId = MTP::send(MTPmessages_EditChatPhoto(hist->peer->asChat()->inputChat, MTP_inputChatUploadedPhoto(file, MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100)))), rpcDone(&AppClass::chatPhotoDone, id), rpcFail(&AppClass::peerPhotoFail, id), 0, 0, hist->sendRequestId); + auto history = App::history(id); + history->sendRequestId = MTP::send(MTPmessages_EditChatPhoto(history->peer->asChat()->inputChat, MTP_inputChatUploadedPhoto(file)), rpcDone(&AppClass::chatPhotoDone, id), rpcFail(&AppClass::peerPhotoFail, id), 0, 0, history->sendRequestId); } else if (peerIsChannel(id)) { - History *hist = App::history(id); - hist->sendRequestId = MTP::send(MTPchannels_EditPhoto(hist->peer->asChannel()->inputChannel, MTP_inputChatUploadedPhoto(file, MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100)))), rpcDone(&AppClass::chatPhotoDone, id), rpcFail(&AppClass::peerPhotoFail, id), 0, 0, hist->sendRequestId); + auto history = App::history(id); + history->sendRequestId = MTP::send(MTPchannels_EditPhoto(history->peer->asChannel()->inputChannel, MTP_inputChatUploadedPhoto(file)), rpcDone(&AppClass::chatPhotoDone, id), rpcFail(&AppClass::peerPhotoFail, id), 0, 0, history->sendRequestId); } } } @@ -1053,7 +1053,8 @@ void AppClass::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId) { PhotoId id = rand_value(); - MTPPhoto photo(MTP_photo(MTP_long(id), MTP_long(0), MTP_int(unixtime()), MTP_vector(photoSizes))); + MTPDphoto::Flags photoFlags = 0; + auto photo = MTP_photo(MTP_flags(photoFlags), MTP_long(id), MTP_long(0), MTP_int(unixtime()), MTP_vector(photoSizes)); QString file, filename; int32 filesize = 0; @@ -1090,10 +1091,9 @@ void AppClass::checkMapVersion() { AppClass::~AppClass() { Shortcuts::finish(); - if (auto w = _window) { - _window = 0; - delete w; - } + auto window = createAndSwap(_window); + delete window; + anim::stopManager(); stopWebLoadManager(); @@ -1102,7 +1102,7 @@ AppClass::~AppClass() { MTP::finish(); - AppObject = 0; + AppObject = nullptr; deleteAndMark(_uploader); deleteAndMark(_translator); diff --git a/Telegram/SourceFiles/boxes/abstractbox.cpp b/Telegram/SourceFiles/boxes/abstractbox.cpp index f08c6fa442..7e6846c007 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.cpp +++ b/Telegram/SourceFiles/boxes/abstractbox.cpp @@ -193,28 +193,32 @@ void AbstractBox::raiseShadow() { } } -ScrollableBox::ScrollableBox(const style::flatScroll &scroll, int32 w) : AbstractBox(w), -_scroll(this, scroll), _innerPtr(0), _topSkip(st::boxTitleHeight), _bottomSkip(st::boxScrollSkip) { +ScrollableBox::ScrollableBox(const style::flatScroll &scroll, int32 w) : AbstractBox(w) +, _scroll(this, scroll) +, _topSkip(st::boxTitleHeight) +, _bottomSkip(st::boxScrollSkip) { setBlueTitle(true); } void ScrollableBox::resizeEvent(QResizeEvent *e) { - _scroll.setGeometry(0, _topSkip, width(), height() - _topSkip - _bottomSkip); + _scroll->setGeometry(0, _topSkip, width(), height() - _topSkip - _bottomSkip); AbstractBox::resizeEvent(e); } -void ScrollableBox::init(QWidget *inner, int32 bottomSkip, int32 topSkip) { +void ScrollableBox::init(QWidget *inner, int bottomSkip, int topSkip) { _bottomSkip = bottomSkip; _topSkip = topSkip; - _innerPtr = inner; - _scroll.setWidget(_innerPtr); - _scroll.setFocusPolicy(Qt::NoFocus); - ScrollableBox::resizeEvent(0); + _scroll->setWidget(inner); + _scroll->setFocusPolicy(Qt::NoFocus); + ScrollableBox::resizeEvent(nullptr); } -void ScrollableBox::showAll() { - _scroll.show(); - AbstractBox::showAll(); +void ScrollableBox::initOwned(QWidget *inner, int bottomSkip, int topSkip) { + _bottomSkip = bottomSkip; + _topSkip = topSkip; + _scroll->setOwnedWidget(inner); + _scroll->setFocusPolicy(Qt::NoFocus); + ScrollableBox::resizeEvent(nullptr); } ItemListBox::ItemListBox(const style::flatScroll &scroll, int32 w) : ScrollableBox(scroll, w) { diff --git a/Telegram/SourceFiles/boxes/abstractbox.h b/Telegram/SourceFiles/boxes/abstractbox.h index 4d604781e6..5e3861c17b 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.h +++ b/Telegram/SourceFiles/boxes/abstractbox.h @@ -101,18 +101,20 @@ public: class ScrollableBox : public AbstractBox { public: - ScrollableBox(const style::flatScroll &scroll, int32 w = st::boxWideWidth); - void resizeEvent(QResizeEvent *e) override; + ScrollableBox(const style::flatScroll &scroll, int w = st::boxWideWidth); protected: - void init(QWidget *inner, int32 bottomSkip = st::boxScrollSkip, int32 topSkip = st::boxTitleHeight); + void init(QWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); + void initOwned(QWidget *inner, int bottomSkip = st::boxScrollSkip, int topSkip = st::boxTitleHeight); - void showAll() override; + void resizeEvent(QResizeEvent *e) override; - ScrollArea _scroll; + ScrollArea *scrollArea() { + return _scroll; + } private: - QWidget *_innerPtr; + ChildWidget _scroll; int32 _topSkip, _bottomSkip; }; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index dfeb7adff5..119c3414e8 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -49,25 +49,6 @@ confirmInviteUserName: flatLabel(labelDefFlat) { } confirmInviteUserNameTop: 227px; -stickersAddIcon: icon { - { "stickers_add", #ffffff }, -}; -stickersAddSize: size(30px, 24px); - -stickersFeaturedHeight: 32px; -stickersFeaturedFont: contactsNameFont; -stickersFeaturedPosition: point(16px, 6px); -stickersFeaturedBadgeFont: semiboldFont; -stickersFeaturedBadgeSize: 21px; -stickersFeaturedPen: contactsNewItemFg; -stickersFeaturedUnreadBg: msgFileInBg; -stickersFeaturedUnreadSize: 5px; -stickersFeaturedUnreadSkip: 5px; -stickersFeaturedUnreadTop: 7px; -stickersFeaturedInstalled: icon { - { "mediaview_save_check", #40ace3 } -}; - confirmPhoneAboutLabel: flatLabel(labelDefFlat) { width: 282px; } @@ -87,3 +68,26 @@ aboutRevokePublicLabel: flatLabel(labelDefFlat) { } localStorageBoxSkip: 10px; + +shareRowsTop: 12px; +shareRowHeight: 108px; +sharePhotoRadius: 28px; +sharePhotoSmallRadius: 24px; +sharePhotoTop: 6px; +shareSelectWidth: 2px; +shareSelectFg: windowActiveBg; +shareCheckBorder: windowBg; +shareCheckBg: windowActiveBg; +shareCheckRadius: 10px; +shareCheckSmallRadius: 3px; +shareCheckIcon: icon { + { "default_checkbox_check", windowBg, point(3px, 6px) }, +}; +shareNameFont: font(11px); +shareNameFg: windowTextFg; +shareNameActiveFg: btnYesColor; +shareNameTop: 6px; +shareColumnSkip: 6px; +shareSelectDuration: 150; +shareActivateDuration: 150; +shareScrollDuration: 300; diff --git a/Telegram/SourceFiles/boxes/confirmbox.cpp b/Telegram/SourceFiles/boxes/confirmbox.cpp index 71a97e945d..10d67a9cae 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.cpp +++ b/Telegram/SourceFiles/boxes/confirmbox.cpp @@ -19,15 +19,16 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "lang.h" +#include "boxes/confirmbox.h" -#include "confirmbox.h" +#include "lang.h" #include "mainwidget.h" #include "mainwindow.h" #include "apiwrap.h" #include "application.h" #include "core/click_handler_types.h" #include "styles/style_boxes.h" +#include "localstorage.h" TextParseOptions _confirmBoxTextOptions = { TextParseLinks | TextParseMultiline | TextParseRichText, // flags @@ -195,6 +196,18 @@ void ConfirmLinkBox::onOpenLink() { UrlClickHandler::doOpen(_url); } +ConfirmBotGameBox::ConfirmBotGameBox(UserData *bot, const QString &url) : ConfirmBox(lng_allow_bot_pass(lt_bot_name, bot->name), lang(lng_allow_bot)) +, _bot(bot) +, _url(url) { + connect(this, SIGNAL(confirmed()), this, SLOT(onOpenLink())); +} + +void ConfirmBotGameBox::onOpenLink() { + Ui::hideLayer(); + Local::makeBotTrusted(_bot); + UrlClickHandler::doOpen(_url); +} + MaxInviteBox::MaxInviteBox(const QString &link) : AbstractBox(st::boxWidth) , _close(this, lang(lng_box_ok), st::defaultBoxButton) , _text(st::boxTextFont, lng_participant_invite_sorry(lt_count, Global::ChatSizeMax()), _confirmBoxTextOptions, st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right()) diff --git a/Telegram/SourceFiles/boxes/confirmbox.h b/Telegram/SourceFiles/boxes/confirmbox.h index 9df65b1992..2f7ab53bd1 100644 --- a/Telegram/SourceFiles/boxes/confirmbox.h +++ b/Telegram/SourceFiles/boxes/confirmbox.h @@ -125,6 +125,21 @@ private: }; +class ConfirmBotGameBox : public ConfirmBox { + Q_OBJECT + +public: + ConfirmBotGameBox(UserData *bot, const QString &url); + +public slots: + void onOpenLink(); + +private: + UserData *_bot; + QString _url; + +}; + class MaxInviteBox : public AbstractBox { Q_OBJECT diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index 291ccc73ae..f903768da1 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -24,7 +24,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogs/dialogs_indexed_list.h" #include "lang.h" #include "boxes/addcontactbox.h" -#include "boxes/contactsbox.h" #include "mainwidget.h" #include "mainwindow.h" #include "application.h" @@ -1353,11 +1352,11 @@ void ContactsBox::init() { _cancel.hide(); } connect(&_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); connect(&_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate())); connect(&_filter, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(&_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel())); - connect(&_inner, SIGNAL(mustScrollTo(int, int)), &_scroll, SLOT(scrollToY(int, int))); + connect(&_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); connect(&_inner, SIGNAL(selectAllQuery()), &_filter, SLOT(selectAll())); connect(&_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername())); connect(&_inner, SIGNAL(adminAdded()), this, SIGNAL(adminAdded())); @@ -1478,9 +1477,9 @@ void ContactsBox::keyPressEvent(QKeyEvent *e) { } else if (e->key() == Qt::Key_Up) { _inner.selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { - _inner.selectSkipPage(_scroll.height(), 1); + _inner.selectSkipPage(scrollArea()->height(), 1); } else if (e->key() == Qt::Key_PageUp) { - _inner.selectSkipPage(_scroll.height(), -1); + _inner.selectSkipPage(scrollArea()->height(), -1); } else { ItemListBox::keyPressEvent(e); } @@ -1530,7 +1529,7 @@ void ContactsBox::onFilterCancel() { } void ContactsBox::onFilterUpdate() { - _scroll.scrollToY(0); + scrollArea()->scrollToY(0); if (_filter.getLastText().isEmpty()) { _filterCancel.hide(); } else { @@ -1681,7 +1680,7 @@ bool ContactsBox::editAdminFail(const RPCError &error) { } void ContactsBox::onScroll() { - _inner.loadProfilePhotos(_scroll.scrollTop()); + _inner.loadProfilePhotos(scrollArea()->scrollTop()); } void ContactsBox::creationDone(const MTPUpdates &updates) { @@ -2245,8 +2244,8 @@ MembersBox::MembersBox(ChannelData *channel, MembersFilter filter) : ItemListBox connect(&_inner, SIGNAL(addRequested()), this, SLOT(onAdd())); - connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); - connect(&_inner, SIGNAL(mustScrollTo(int, int)), &_scroll, SLOT(scrollToY(int, int))); + connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(&_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); connect(&_inner, SIGNAL(loaded()), this, SLOT(onLoaded())); connect(&_loadTimer, SIGNAL(timeout()), &_inner, SLOT(load())); @@ -2260,9 +2259,9 @@ void MembersBox::keyPressEvent(QKeyEvent *e) { } else if (e->key() == Qt::Key_Up) { _inner.selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { - _inner.selectSkipPage(_scroll.height(), 1); + _inner.selectSkipPage(scrollArea()->height(), 1); } else if (e->key() == Qt::Key_PageUp) { - _inner.selectSkipPage(_scroll.height(), -1); + _inner.selectSkipPage(scrollArea()->height(), -1); } else { ItemListBox::keyPressEvent(e); } @@ -2282,7 +2281,7 @@ void MembersBox::resizeEvent(QResizeEvent *e) { } void MembersBox::onScroll() { - _inner.loadProfilePhotos(_scroll.scrollTop()); + _inner.loadProfilePhotos(scrollArea()->scrollTop()); } void MembersBox::onAdd() { diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp index 03ac71a7d8..b2fc879faf 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.cpp +++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp @@ -252,10 +252,10 @@ void SessionsBox::resizeEvent(QResizeEvent *e) { void SessionsBox::showAll() { _done.show(); if (_loading) { - _scroll.hide(); + scrollArea()->hide(); _shadow.hide(); } else { - _scroll.show(); + scrollArea()->show(); _shadow.show(); } ScrollableBox::showAll(); diff --git a/Telegram/SourceFiles/boxes/sharebox.cpp b/Telegram/SourceFiles/boxes/sharebox.cpp new file mode 100644 index 0000000000..fdeba32c88 --- /dev/null +++ b/Telegram/SourceFiles/boxes/sharebox.cpp @@ -0,0 +1,995 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "boxes/sharebox.h" + +#include "dialogs/dialogs_indexed_list.h" +#include "styles/style_boxes.h" +#include "observer_peer.h" +#include "lang.h" +#include "mainwindow.h" +#include "mainwidget.h" +#include "core/qthelp_url.h" +#include "localstorage.h" +#include "boxes/confirmbox.h" +#include "apiwrap.h" + +ShareBox::ShareBox(SubmitCallback &&callback) : ItemListBox(st::boxScroll) +, _callback(std_::move(callback)) +, _inner(this) +, _filter(this, st::boxSearchField, lang(lng_participant_filter)) +, _filterCancel(this, st::boxSearchCancel) +, _share(this, lang(lng_share_confirm), st::defaultBoxButton) +, _cancel(this, lang(lng_cancel), st::cancelBoxButton) +, _topShadow(this) +, _bottomShadow(this) { + int topSkip = st::boxTitleHeight + _filter->height(); + int bottomSkip = st::boxButtonPadding.top() + _share->height() + st::boxButtonPadding.bottom(); + init(_inner, bottomSkip, topSkip); + + connect(_inner, SIGNAL(selectedChanged()), this, SLOT(onSelectedChanged())); + connect(_inner, SIGNAL(mustScrollTo(int,int)), this, SLOT(onMustScrollTo(int,int))); + connect(_share, SIGNAL(clicked()), this, SLOT(onShare())); + connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); + connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate())); + connect(_filter, SIGNAL(submitted(bool)), _inner, SLOT(onSelectActive())); + connect(_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel())); + connect(_inner, SIGNAL(filterCancel()), this, SLOT(onFilterCancel())); + connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername())); + + _filterCancel->setAttribute(Qt::WA_OpaquePaintEvent); + + _searchTimer.setSingleShot(true); + connect(&_searchTimer, SIGNAL(timeout()), this, SLOT(onSearchByUsername())); + + prepare(); +} + +bool ShareBox::onSearchByUsername(bool searchCache) { + auto query = _filter->getLastText().trimmed(); + if (query.isEmpty()) { + if (_peopleRequest) { + _peopleRequest = 0; + } + return true; + } + if (query.size() >= MinUsernameLength) { + if (searchCache) { + auto i = _peopleCache.constFind(query); + if (i != _peopleCache.cend()) { + _peopleQuery = query; + _peopleRequest = 0; + peopleReceived(i.value(), 0); + return true; + } + } else if (_peopleQuery != query) { + _peopleQuery = query; + _peopleFull = false; + _peopleRequest = MTP::send(MTPcontacts_Search(MTP_string(_peopleQuery), MTP_int(SearchPeopleLimit)), rpcDone(&ShareBox::peopleReceived), rpcFail(&ShareBox::peopleFailed)); + _peopleQueries.insert(_peopleRequest, _peopleQuery); + } + } + return false; +} + +void ShareBox::onNeedSearchByUsername() { + if (!onSearchByUsername(true)) { + _searchTimer.start(AutoSearchTimeout); + } +} + +void ShareBox::peopleReceived(const MTPcontacts_Found &result, mtpRequestId requestId) { + auto query = _peopleQuery; + + auto i = _peopleQueries.find(requestId); + if (i != _peopleQueries.cend()) { + query = i.value(); + _peopleCache[query] = result; + _peopleQueries.erase(i); + } + + if (_peopleRequest == requestId) { + switch (result.type()) { + case mtpc_contacts_found: { + auto &found = result.c_contacts_found(); + App::feedUsers(found.vusers); + App::feedChats(found.vchats); + _inner->peopleReceived(query, found.vresults.c_vector().v); + } break; + } + + _peopleRequest = 0; + onScroll(); + } +} + +bool ShareBox::peopleFailed(const RPCError &error, mtpRequestId requestId) { + if (MTP::isDefaultHandledError(error)) return false; + + if (_peopleRequest == requestId) { + _peopleRequest = 0; + _peopleFull = true; + } + return true; +} + +void ShareBox::doSetInnerFocus() { + _filter->setFocus(); +} + +void ShareBox::paintEvent(QPaintEvent *e) { + Painter p(this); + if (paint(p)) return; + + paintTitle(p, lang(lng_share_title)); +} + +void ShareBox::resizeEvent(QResizeEvent *e) { + ItemListBox::resizeEvent(e); + _filter->resize(width(), _filter->height()); + _filter->moveToLeft(0, st::boxTitleHeight); + _filterCancel->moveToRight(0, st::boxTitleHeight); + _inner->resizeToWidth(width()); + moveButtons(); + _topShadow->setGeometry(0, st::boxTitleHeight + _filter->height(), width(), st::lineWidth); + _bottomShadow->setGeometry(0, height() - st::boxButtonPadding.bottom() - _share->height() - st::boxButtonPadding.top() - st::lineWidth, width(), st::lineWidth); +} + +void ShareBox::keyPressEvent(QKeyEvent *e) { + if (_filter->hasFocus()) { + if (e->key() == Qt::Key_Up) { + _inner->activateSkipColumn(-1); + } else if (e->key() == Qt::Key_Down) { + _inner->activateSkipColumn(1); + } else if (e->key() == Qt::Key_PageUp) { + _inner->activateSkipPage(scrollArea()->height(), -1); + } else if (e->key() == Qt::Key_PageDown) { + _inner->activateSkipPage(scrollArea()->height(), 1); + } else { + ItemListBox::keyPressEvent(e); + } + } else { + ItemListBox::keyPressEvent(e); + } +} + +void ShareBox::moveButtons() { + _share->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _share->height()); + auto cancelRight = st::boxButtonPadding.right(); + if (_inner->hasSelected()) { + cancelRight += _share->width() + st::boxButtonPadding.left(); + } + _cancel->moveToRight(cancelRight, _share->y()); +} + +void ShareBox::onFilterCancel() { + _filter->setText(QString()); +} + +void ShareBox::onFilterUpdate() { + _filterCancel->setVisible(!_filter->getLastText().isEmpty()); + _inner->updateFilter(_filter->getLastText()); +} + +void ShareBox::onShare() { + if (_callback) { + _callback(_inner->selected()); + } +} + +void ShareBox::onSelectedChanged() { + _share->setVisible(_inner->hasSelected()); + moveButtons(); + update(); +} + +void ShareBox::onMustScrollTo(int top, int bottom) { + auto scrollTop = scrollArea()->scrollTop(), scrollBottom = scrollTop + scrollArea()->height(); + auto from = scrollTop, to = scrollTop; + if (scrollTop > top) { + to = top; + } else if (scrollBottom < bottom) { + to = bottom - (scrollBottom - scrollTop); + } + if (from != to) { + START_ANIMATION(_scrollAnimation, func([this]() { + scrollArea()->scrollToY(_scrollAnimation.current(scrollArea()->scrollTop())); + }), from, to, st::shareScrollDuration, anim::sineInOut); + } +} + +void ShareBox::onScroll() { + auto scroll = scrollArea(); + auto scrollTop = scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + scroll->height()); +} + +namespace internal { + +ShareInner::ShareInner(QWidget *parent) : ScrolledWidget(parent) +, _chatsIndexed(std_::make_unique(Dialogs::SortMode::Add)) { + connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); + + _rowsTop = st::shareRowsTop; + _rowHeight = st::shareRowHeight; + setAttribute(Qt::WA_OpaquePaintEvent); + + auto dialogs = App::main()->dialogsList(); + for_const (auto row, dialogs->all()) { + auto history = row->history(); + if (history->peer->canWrite()) { + _chatsIndexed->addToEnd(history); + } + } + + _filter = qsl("a"); + updateFilter(); + + prepareWideCheckIcons(); + + using UpdateFlag = Notify::PeerUpdate::Flag; + auto observeEvents = UpdateFlag::NameChanged | UpdateFlag::PhotoChanged; + Notify::registerPeerObserver(observeEvents, this, &ShareInner::notifyPeerUpdated); +} + +void ShareInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + loadProfilePhotos(visibleTop); +} + +void ShareInner::activateSkipRow(int direction) { + activateSkipColumn(direction * _columnCount); +} + +int ShareInner::displayedChatsCount() const { + return _filter.isEmpty() ? _chatsIndexed->size() : (_filtered.size() + d_byUsernameFiltered.size()); +} + +void ShareInner::activateSkipColumn(int direction) { + if (_active < 0) { + if (direction > 0) { + setActive(0); + } + return; + } + auto count = displayedChatsCount(); + auto active = _active + direction; + if (active < 0) { + active = (_active > 0) ? 0 : -1; + } + if (active >= count) { + active = count - 1; + } + setActive(active); +} + +void ShareInner::activateSkipPage(int pageHeight, int direction) { + activateSkipRow(direction * (pageHeight / _rowHeight)); +} + +void ShareInner::notifyPeerUpdated(const Notify::PeerUpdate &update) { + if (update.flags & Notify::PeerUpdate::Flag::NameChanged) { + _chatsIndexed->peerNameChanged(update.peer, update.oldNames, update.oldNameFirstChars); + } + + updateChat(update.peer); +} + +void ShareInner::updateChat(PeerData *peer) { + auto i = _dataMap.find(peer); + if (i != _dataMap.cend()) { + updateChatName(i.value(), peer); + repaintChat(peer); + } +} + +void ShareInner::updateChatName(Chat *chat, PeerData *peer) { + chat->name.setText(st::shareNameFont, peer->name, _textNameOptions); +} + +void ShareInner::repaintChatAtIndex(int index) { + if (index < 0) return; + + auto row = index / _columnCount; + auto column = index % _columnCount; + update(rtlrect(_rowsLeft + qFloor(column * _rowWidthReal), row * _rowHeight, _rowWidth, _rowHeight, width())); +} + +ShareInner::Chat *ShareInner::getChatAtIndex(int index) { + if (index < 0) return nullptr; + auto row = ([this, index]() -> Dialogs::Row* { + if (_filter.isEmpty()) return _chatsIndexed->rowAtY(index, 1); + return (index < _filtered.size()) ? _filtered[index] : nullptr; + })(); + if (row) { + return static_cast(row->attached); + } + + if (!_filter.isEmpty()) { + index -= _filtered.size(); + if (index >= 0 && index < d_byUsernameFiltered.size()) { + return d_byUsernameFiltered[index]; + } + } + return nullptr; +} + +void ShareInner::repaintChat(PeerData *peer) { + repaintChatAtIndex(chatIndex(peer)); +} + +int ShareInner::chatIndex(PeerData *peer) const { + int index = 0; + if (_filter.isEmpty()) { + for_const (auto row, _chatsIndexed->all()) { + if (row->history()->peer == peer) { + return index; + } + ++index; + } + } else { + for_const (auto row, _filtered) { + if (row->history()->peer == peer) { + return index; + } + ++index; + } + for_const (auto row, d_byUsernameFiltered) { + if (row->peer == peer) { + return index; + } + ++index; + } + } + return -1; +} + +void ShareInner::loadProfilePhotos(int yFrom) { + if (yFrom < 0) { + yFrom = 0; + } + if (auto part = (yFrom % _rowHeight)) { + yFrom -= part; + } + int yTo = yFrom + (parentWidget() ? parentWidget()->height() : App::wnd()->height()) * 5 * _columnCount; + if (!yTo) { + return; + } + yFrom *= _columnCount; + yTo *= _columnCount; + + MTP::clearLoaderPriorities(); + if (_filter.isEmpty()) { + if (!_chatsIndexed->isEmpty()) { + auto i = _chatsIndexed->cfind(yFrom, _rowHeight); + for (auto end = _chatsIndexed->cend(); i != end; ++i) { + if (((*i)->pos() * _rowHeight) >= yTo) { + break; + } + (*i)->history()->peer->loadUserpic(); + } + } + } else if (!_filtered.isEmpty()) { + int from = yFrom / _rowHeight; + if (from < 0) from = 0; + if (from < _filtered.size()) { + int to = (yTo / _rowHeight) + 1; + if (to > _filtered.size()) to = _filtered.size(); + + for (; from < to; ++from) { + _filtered[from]->history()->peer->loadUserpic(); + } + } + } +} + +ShareInner::Chat *ShareInner::getChat(Dialogs::Row *row) { + auto data = static_cast(row->attached); + if (!data) { + auto peer = row->history()->peer; + auto i = _dataMap.constFind(peer); + if (i == _dataMap.cend()) { + _dataMap.insert(peer, data = new Chat(peer)); + updateChatName(data, peer); + } else { + data = i.value(); + } + row->attached = data; + } + return data; +} + +void ShareInner::setActive(int active) { + if (active != _active) { + auto changeNameFg = [this](int index, style::color from, style::color to) { + if (auto chat = getChatAtIndex(index)) { + START_ANIMATION(chat->nameFg, func([this, chat] { + repaintChat(chat->peer); + }), from->c, to->c, st::shareActivateDuration, anim::linear); + } + }; + changeNameFg(_active, st::shareNameActiveFg, st::shareNameFg); + _active = active; + changeNameFg(_active, st::shareNameFg, st::shareNameActiveFg); + } + auto y = (_active < _columnCount) ? 0 : (_rowsTop + ((_active / _columnCount) * _rowHeight)); + emit mustScrollTo(y, y + _rowHeight); +} + +void ShareInner::paintChat(Painter &p, Chat *chat, int index) { + auto x = _rowsLeft + qFloor((index % _columnCount) * _rowWidthReal); + auto y = _rowsTop + (index / _columnCount) * _rowHeight; + + auto selectionLevel = chat->selection.current(chat->selected ? 1. : 0.); + + auto w = width(); + auto photoLeft = (_rowWidth - (st::sharePhotoRadius * 2)) / 2; + auto photoTop = st::sharePhotoTop; + if (chat->selection.isNull()) { + if (!chat->wideUserpicCache.isNull()) { + chat->wideUserpicCache = QPixmap(); + } + auto userpicRadius = chat->selected ? st::sharePhotoSmallRadius : st::sharePhotoRadius; + auto userpicShift = st::sharePhotoRadius - userpicRadius; + auto userpicLeft = x + photoLeft + userpicShift; + auto userpicTop = y + photoTop + userpicShift; + chat->peer->paintUserpicLeft(p, userpicRadius * 2, userpicLeft, userpicTop, w); + } else { + p.setRenderHint(QPainter::SmoothPixmapTransform, true); + auto userpicRadius = qRound(WideCacheScale * (st::sharePhotoRadius + (st::sharePhotoSmallRadius - st::sharePhotoRadius) * selectionLevel)); + auto userpicShift = WideCacheScale * st::sharePhotoRadius - userpicRadius; + auto userpicLeft = x + photoLeft - (WideCacheScale - 1) * st::sharePhotoRadius + userpicShift; + auto userpicTop = y + photoTop - (WideCacheScale - 1) * st::sharePhotoRadius + userpicShift; + auto to = QRect(userpicLeft, userpicTop, userpicRadius * 2, userpicRadius * 2); + auto from = QRect(QPoint(0, 0), chat->wideUserpicCache.size()); + p.drawPixmapLeft(to, w, chat->wideUserpicCache, from); + p.setRenderHint(QPainter::SmoothPixmapTransform, false); + } + + if (selectionLevel > 0) { + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + p.setOpacity(snap(selectionLevel, 0., 1.)); + p.setBrush(Qt::NoBrush); + QPen pen = st::shareSelectFg; + pen.setWidth(st::shareSelectWidth); + p.setPen(pen); + p.drawEllipse(myrtlrect(x + photoLeft, y + photoTop, st::sharePhotoRadius * 2, st::sharePhotoRadius * 2)); + p.setOpacity(1.); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + } + + removeFadeOutedIcons(chat); + p.setRenderHint(QPainter::SmoothPixmapTransform, true); + for (auto &icon : chat->icons) { + auto fadeIn = icon.fadeIn.current(1.); + auto fadeOut = icon.fadeOut.current(1.); + auto iconRadius = qRound(WideCacheScale * (st::shareCheckSmallRadius + fadeOut * (st::shareCheckRadius - st::shareCheckSmallRadius))); + auto iconShift = WideCacheScale * st::shareCheckRadius - iconRadius; + auto iconLeft = x + photoLeft + 2 * st::sharePhotoRadius + st::shareSelectWidth - 2 * st::shareCheckRadius - (WideCacheScale - 1) * st::shareCheckRadius + iconShift; + auto iconTop = y + photoTop + 2 * st::sharePhotoRadius + st::shareSelectWidth - 2 * st::shareCheckRadius - (WideCacheScale - 1) * st::shareCheckRadius + iconShift; + auto to = QRect(iconLeft, iconTop, iconRadius * 2, iconRadius * 2); + auto from = QRect(QPoint(0, 0), _wideCheckIconCache.size()); + auto opacity = fadeIn * fadeOut; + p.setOpacity(opacity); + if (fadeOut < 1.) { + p.drawPixmapLeft(to, w, icon.wideCheckCache, from); + } else { + auto divider = qRound((WideCacheScale - 2) * st::shareCheckRadius + fadeIn * 3 * st::shareCheckRadius); + p.drawPixmapLeft(QRect(iconLeft, iconTop, divider, iconRadius * 2), w, _wideCheckIconCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckIconCache.height())); + p.drawPixmapLeft(QRect(iconLeft + divider, iconTop, iconRadius * 2 - divider, iconRadius * 2), w, _wideCheckCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckCache.width() - divider * cIntRetinaFactor(), _wideCheckCache.height())); + } + } + p.setRenderHint(QPainter::SmoothPixmapTransform, false); + p.setOpacity(1.); + + if (chat->nameFg.isNull()) { + p.setPen((index == _active) ? st::shareNameActiveFg : st::shareNameFg); + } else { + p.setPen(chat->nameFg.current()); + } + auto nameWidth = (_rowWidth - st::shareColumnSkip); + auto nameLeft = st::shareColumnSkip / 2; + auto nameTop = photoTop + st::sharePhotoRadius * 2 + st::shareNameTop; + chat->name.drawLeftElided(p, x + nameLeft, y + nameTop, nameWidth, w, 2, style::al_top, 0, -1, 0, true); +} + +ShareInner::Chat::Chat(PeerData *peer) : peer(peer), name(st::sharePhotoRadius * 2) { +} + +void ShareInner::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto r = e->rect(); + p.setClipRect(r); + p.fillRect(r, st::white); + auto yFrom = r.y(), yTo = r.y() + r.height(); + auto rowFrom = yFrom / _rowHeight; + auto rowTo = (yTo + _rowHeight - 1) / _rowHeight; + auto indexFrom = rowFrom * _columnCount; + auto indexTo = rowTo * _columnCount; + if (_filter.isEmpty()) { + if (!_chatsIndexed->isEmpty()) { + auto i = _chatsIndexed->cfind(indexFrom, 1); + for (auto end = _chatsIndexed->cend(); i != end; ++i) { + if (indexFrom >= indexTo) { + break; + } + paintChat(p, getChat(*i), indexFrom); + ++indexFrom; + } + } else { + // empty + p.setFont(st::noContactsFont); + p.setPen(st::noContactsColor); + } + } else { + if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) { + // empty + p.setFont(st::noContactsFont); + p.setPen(st::noContactsColor); + } else { + auto filteredSize = _filtered.size(); + if (filteredSize) { + if (indexFrom < 0) indexFrom = 0; + while (indexFrom < indexTo) { + if (indexFrom >= _filtered.size()) { + break; + } + paintChat(p, getChat(_filtered[indexFrom]), indexFrom); + ++indexFrom; + } + indexFrom -= filteredSize; + indexTo -= filteredSize; + } + if (!_byUsernameFiltered.isEmpty()) { + if (indexFrom < 0) indexFrom = 0; + while (indexFrom < indexTo) { + if (indexFrom >= d_byUsernameFiltered.size()) { + break; + } + paintChat(p, d_byUsernameFiltered[indexFrom], filteredSize + indexFrom); + ++indexFrom; + } + } + } + } +} + +void ShareInner::enterEvent(QEvent *e) { + setMouseTracking(true); +} + +void ShareInner::leaveEvent(QEvent *e) { + setMouseTracking(false); +} + +void ShareInner::mouseMoveEvent(QMouseEvent *e) { + updateUpon(e->pos()); + setCursor((_upon >= 0) ? style::cur_pointer : style::cur_default); +} + +void ShareInner::updateUpon(const QPoint &pos) { + auto x = pos.x(), y = pos.y(); + auto row = (y - _rowsTop) / _rowHeight; + auto column = qFloor((x - _rowsLeft) / _rowWidthReal); + auto left = _rowsLeft + qFloor(column * _rowWidthReal) + st::shareColumnSkip / 2; + auto top = _rowsTop + row * _rowHeight + st::sharePhotoTop; + auto xupon = (x >= left) && (x < left + (_rowWidth - st::shareColumnSkip)); + auto yupon = (y >= top) && (y < top + st::sharePhotoRadius * 2 + st::shareNameTop + st::shareNameFont->height * 2); + auto upon = (xupon && yupon) ? (row * _columnCount + column) : -1; + if (upon >= displayedChatsCount()) { + upon = -1; + } + _upon = upon; +} + +void ShareInner::mousePressEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton) { + updateUpon(e->pos()); + changeCheckState(getChatAtIndex(_upon)); + } +} + +void ShareInner::onSelectActive() { + changeCheckState(getChatAtIndex(_active > 0 ? _active : 0)); +} + +void ShareInner::resizeEvent(QResizeEvent *e) { + _columnSkip = (width() - _columnCount * st::sharePhotoRadius * 2) / float64(_columnCount + 1); + _rowWidthReal = st::sharePhotoRadius * 2 + _columnSkip; + _rowsLeft = qFloor(_columnSkip / 2); + _rowWidth = qFloor(_rowWidthReal); + update(); +} + +struct AnimBumpy { + AnimBumpy(float64 bump) : bump(bump) + , dt0(bump - sqrt(bump * (bump - 1.))) + , k(1 / (2 * dt0 - 1)) { + } + float64 bump; + float64 dt0; + float64 k; +}; + +float64 anim_bumpy(const float64 &delta, const float64 &dt) { + static AnimBumpy data = { 1.25 }; + return delta * (data.bump - data.k * (dt - data.dt0) * (dt - data.dt0)); +} + +void ShareInner::changeCheckState(Chat *chat) { + if (!chat) return; + + if (!_filter.isEmpty()) { + auto row = _chatsIndexed->getRow(chat->peer->id); + if (!row) { + row = _chatsIndexed->addToEnd(App::history(chat->peer)).value(0); + } + chat = getChat(row); + if (!chat->selected) { + _chatsIndexed->moveToTop(chat->peer); + } + emit filterCancel(); + } + + chat->selected = !chat->selected; + if (chat->selected) { + _selected.insert(chat->peer); + chat->icons.push_back(Chat::Icon()); + START_ANIMATION(chat->icons.back().fadeIn, func([this, chat] { + repaintChat(chat->peer); + }), 0, 1, st::shareSelectDuration, anim::linear); + } else { + _selected.remove(chat->peer); + prepareWideCheckIconCache(&chat->icons.back()); + START_ANIMATION(chat->icons.back().fadeOut, func([this, chat] { + removeFadeOutedIcons(chat); + repaintChat(chat->peer); + }), 1, 0, st::shareSelectDuration, anim::linear); + } + prepareWideUserpicCache(chat); + START_ANIMATION(chat->selection, func([this, chat] { + repaintChat(chat->peer); + }), chat->selected ? 0 : 1, chat->selected ? 1 : 0, st::shareSelectDuration, anim_bumpy); + if (chat->selected) { + setActive(chatIndex(chat->peer)); + } + emit selectedChanged(); +} + +void ShareInner::removeFadeOutedIcons(Chat *chat) { + while (!chat->icons.empty() && chat->icons.front().fadeIn.isNull() && chat->icons.front().fadeOut.isNull()) { + if (chat->icons.size() > 1 || !chat->selected) { + chat->icons.pop_front(); + } else { + break; + } + } +} + +void ShareInner::prepareWideUserpicCache(Chat *chat) { + if (chat->wideUserpicCache.isNull()) { + auto size = st::sharePhotoRadius * 2; + auto wideSize = size * WideCacheScale; + QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + cache.setDevicePixelRatio(cRetinaFactor()); + { + Painter p(&cache); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, wideSize, wideSize, Qt::transparent); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + chat->peer->paintUserpic(p, size, (wideSize - size) / 2, (wideSize - size) / 2); + } + chat->wideUserpicCache = App::pixmapFromImageInPlace(std_::move(cache)); + chat->wideUserpicCache.setDevicePixelRatio(cRetinaFactor()); + } +} + +void ShareInner::prepareWideCheckIconCache(Chat::Icon *icon) { + QImage wideCache(_wideCheckCache.width(), _wideCheckCache.height(), QImage::Format_ARGB32_Premultiplied); + wideCache.setDevicePixelRatio(cRetinaFactor()); + { + Painter p(&wideCache); + p.setCompositionMode(QPainter::CompositionMode_Source); + auto iconRadius = WideCacheScale * st::shareCheckRadius; + auto divider = qRound((WideCacheScale - 2) * st::shareCheckRadius + icon->fadeIn.current(1.) * 3 * st::shareCheckRadius); + p.drawPixmapLeft(QRect(0, 0, divider, iconRadius * 2), width(), _wideCheckIconCache, QRect(0, 0, divider * cIntRetinaFactor(), _wideCheckIconCache.height())); + p.drawPixmapLeft(QRect(divider, 0, iconRadius * 2 - divider, iconRadius * 2), width(), _wideCheckCache, QRect(divider * cIntRetinaFactor(), 0, _wideCheckCache.width() - divider * cIntRetinaFactor(), _wideCheckCache.height())); + } + icon->wideCheckCache = App::pixmapFromImageInPlace(std_::move(wideCache)); + icon->wideCheckCache.setDevicePixelRatio(cRetinaFactor()); +} + +void ShareInner::prepareWideCheckIcons() { + auto size = st::shareCheckRadius * 2; + auto wideSize = size * WideCacheScale; + QImage cache(wideSize * cIntRetinaFactor(), wideSize * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + cache.setDevicePixelRatio(cRetinaFactor()); + { + Painter p(&cache); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(0, 0, wideSize, wideSize, Qt::transparent); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + auto pen = st::shareCheckBorder->p; + pen.setWidth(st::shareSelectWidth); + p.setPen(pen); + p.setBrush(st::shareCheckBg); + auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size); + p.drawEllipse(ellipse); + } + QImage cacheIcon = cache; + { + Painter p(&cacheIcon); + auto ellipse = QRect((wideSize - size) / 2, (wideSize - size) / 2, size, size); + st::shareCheckIcon.paint(p, ellipse.topLeft(), wideSize); + } + _wideCheckCache = App::pixmapFromImageInPlace(std_::move(cache)); + _wideCheckCache.setDevicePixelRatio(cRetinaFactor()); + _wideCheckIconCache = App::pixmapFromImageInPlace(std_::move(cacheIcon)); + _wideCheckIconCache.setDevicePixelRatio(cRetinaFactor()); +} + +bool ShareInner::hasSelected() const { + return _selected.size(); +} + +void ShareInner::updateFilter(QString filter) { + _lastQuery = filter.toLower().trimmed(); + filter = textSearchKey(filter); + + QStringList f; + if (!filter.isEmpty()) { + QStringList filterList = filter.split(cWordSplit(), QString::SkipEmptyParts); + int l = filterList.size(); + + f.reserve(l); + for (int i = 0; i < l; ++i) { + QString filterName = filterList[i].trimmed(); + if (filterName.isEmpty()) continue; + f.push_back(filterName); + } + filter = f.join(' '); + } + if (_filter != filter) { + _filter = filter; + + _byUsernameFiltered.clear(); + for (int i = 0, l = d_byUsernameFiltered.size(); i < l; ++i) { + delete d_byUsernameFiltered[i]; + } + d_byUsernameFiltered.clear(); + + if (_filter.isEmpty()) { + refresh(); + } else { + QStringList::const_iterator fb = f.cbegin(), fe = f.cend(), fi; + + _filtered.clear(); + if (!f.isEmpty()) { + const Dialogs::List *toFilter = nullptr; + if (!_chatsIndexed->isEmpty()) { + for (fi = fb; fi != fe; ++fi) { + auto found = _chatsIndexed->filtered(fi->at(0)); + if (found->isEmpty()) { + toFilter = nullptr; + break; + } + if (!toFilter || toFilter->size() > found->size()) { + toFilter = found; + } + } + } + if (toFilter) { + _filtered.reserve(toFilter->size()); + for_const (auto row, *toFilter) { + auto &names = row->history()->peer->names; + PeerData::Names::const_iterator nb = names.cbegin(), ne = names.cend(), ni; + for (fi = fb; fi != fe; ++fi) { + auto filterName = *fi; + for (ni = nb; ni != ne; ++ni) { + if (ni->startsWith(*fi)) { + break; + } + } + if (ni == ne) { + break; + } + } + if (fi == fe) { + _filtered.push_back(row); + } + } + } + } + refresh(); + + _searching = true; + emit searchByUsername(); + } + setActive(-1); + update(); + loadProfilePhotos(0); + } +} + +void ShareInner::peopleReceived(const QString &query, const QVector &people) { + _lastQuery = query.toLower().trimmed(); + if (_lastQuery.at(0) == '@') _lastQuery = _lastQuery.mid(1); + int32 already = _byUsernameFiltered.size(); + _byUsernameFiltered.reserve(already + people.size()); + d_byUsernameFiltered.reserve(already + people.size()); + for_const (auto &mtpPeer, people) { + auto peerId = peerFromMTP(mtpPeer); + int j = 0; + for (; j < already; ++j) { + if (_byUsernameFiltered[j]->id == peerId) break; + } + if (j == already) { + auto *peer = App::peer(peerId); + if (!peer || !peer->canWrite()) continue; + + auto chat = new Chat(peer); + updateChatName(chat, peer); + if (auto row = _chatsIndexed->getRow(peer->id)) { + continue; + } + + _byUsernameFiltered.push_back(peer); + d_byUsernameFiltered.push_back(chat); + } + } + _searching = false; + refresh(); +} + +void ShareInner::refresh() { + auto count = displayedChatsCount(); + if (count) { + auto rows = (count / _columnCount) + (count % _columnCount ? 1 : 0); + resize(width(), _rowsTop + rows * _rowHeight); + } else { + resize(width(), st::noContactsHeight); + } + update(); +} + +ShareInner::~ShareInner() { + for_const (auto chat, _dataMap) { + delete chat; + } +} + +QVector ShareInner::selected() const { + QVector result; + result.reserve(_dataMap.size()); + for_const (auto chat, _dataMap) { + if (chat->selected) { + result.push_back(chat->peer); + } + } + return result; +} + +} // namespace internal + +QString appendShareGameScoreUrl(const QString &url, const FullMsgId &fullId) { + auto shareHashData = QByteArray(0x10, Qt::Uninitialized); + auto ints = reinterpret_cast(shareHashData.data()); + ints[0] = MTP::authedId(); + ints[1] = fullId.channel; + ints[2] = fullId.msg; + ints[3] = 0; + + auto key128Size = 0x10; + auto shareHashEncrypted = QByteArray(key128Size + shareHashData.size(), Qt::Uninitialized); + hashSha1(shareHashData.constData(), shareHashData.size(), shareHashEncrypted.data()); + if (!Local::encrypt(shareHashData.constData(), shareHashEncrypted.data() + key128Size, shareHashData.size(), shareHashEncrypted.constData())) { + return url; + } + + auto shareHash = shareHashEncrypted.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + auto shareUrl = qsl("tg://share_game_score?hash=") + QString::fromLatin1(shareHash); + + auto shareComponent = qsl("tgShareScoreUrl=") + qthelp::url_encode(shareUrl); + + auto hashPosition = url.indexOf('#'); + if (hashPosition < 0) { + return url + '#' + shareComponent; + } + auto hash = url.mid(hashPosition + 1); + if (hash.indexOf('=') >= 0 || hash.indexOf('?') >= 0) { + return url + '&' + shareComponent; + } + if (!hash.isEmpty()) { + return url + '?' + shareComponent; + } + return url + shareComponent; +} + +namespace { + +void shareGameScoreFromItem(HistoryItem *item) { + Ui::showLayer(new ShareBox([msgId = item->fullId()](const QVector &result) { + MTPmessages_ForwardMessages::Flags sendFlags = MTPmessages_ForwardMessages::Flag::f_with_my_score; + MTPVector msgIds = MTP_vector(1, MTP_int(msgId.msg)); + if (auto main = App::main()) { + if (auto item = App::histItemById(msgId)) { + for_const (auto peer, result) { + MTPVector random = MTP_vector(1, rand_value()); + MTP::send(MTPmessages_ForwardMessages(MTP_flags(sendFlags), item->history()->peer->input, msgIds, random, peer->input), main->rpcDone(&MainWidget::sentUpdatesReceived)); + } + } + } + Ui::hideLayer(); + })); +} + +class GameMessageResolvedCallback : public SharedCallback { +public: + void call(ChannelData *channel, MsgId msgId) const override { + if (auto item = App::histItemById(channel, msgId)) { + shareGameScoreFromItem(item); + } else { + Ui::showLayer(new InformBox(lang(lng_edit_deleted))); + } + } + +}; + +} // namespace + +void shareGameScoreByHash(const QString &hash) { + auto key128Size = 0x10; + + auto hashEncrypted = QByteArray::fromBase64(hash.toLatin1(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + if (hashEncrypted.size() <= key128Size || (hashEncrypted.size() % 0x10) != 0) { + Ui::showLayer(new InformBox(lang(lng_confirm_phone_link_invalid))); + return; + } + + auto hashData = QByteArray(hashEncrypted.size() - key128Size, Qt::Uninitialized); + if (!Local::decrypt(hashEncrypted.constData() + key128Size, hashData.data(), hashEncrypted.size() - key128Size, hashEncrypted.constData())) { + return; + } + + char checkSha1[20] = { 0 }; + if (memcmp(hashSha1(hashData.constData(), hashData.size(), checkSha1), hashEncrypted.constData(), key128Size) != 0) { + Ui::showLayer(new InformBox(lang(lng_share_wrong_user))); + return; + } + auto ints = reinterpret_cast(hashData.data()); + if (ints[0] != MTP::authedId()) { + Ui::showLayer(new InformBox(lang(lng_share_wrong_user))); + return; + } + + auto channelId = ints[1]; + auto msgId = ints[2]; + if (auto item = App::histItemById(channelId, msgId)) { + shareGameScoreFromItem(item); + } else if (App::api()) { + auto channel = channelId ? App::channelLoaded(channelId) : nullptr; + if (channel || !channelId) { + App::api()->requestMessageData(channel, msgId, std_::make_unique()); + } + } +} diff --git a/Telegram/SourceFiles/boxes/sharebox.h b/Telegram/SourceFiles/boxes/sharebox.h new file mode 100644 index 0000000000..cff80d344a --- /dev/null +++ b/Telegram/SourceFiles/boxes/sharebox.h @@ -0,0 +1,217 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "abstractbox.h" +#include "core/lambda_wrap.h" +#include "core/observer.h" + +namespace Dialogs { +class Row; +class IndexedList; +} // namespace Dialogs + +namespace internal { +class ShareInner; +} // namespace internal + +namespace Notify { +struct PeerUpdate; +} // namespace Notify + +QString appendShareGameScoreUrl(const QString &url, const FullMsgId &fullId); +void shareGameScoreByHash(const QString &hash); + +class ShareBox : public ItemListBox, public RPCSender { + Q_OBJECT + +public: + using SubmitCallback = base::lambda_unique &)>; + ShareBox(SubmitCallback &&callback); + +private slots: + void onFilterUpdate(); + void onFilterCancel(); + void onScroll(); + + bool onSearchByUsername(bool searchCache = false); + void onNeedSearchByUsername(); + + void onShare(); + void onSelectedChanged(); + + void onMustScrollTo(int top, int bottom); + +protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + + void doSetInnerFocus() override; + +private: + void moveButtons(); + + void peopleReceived(const MTPcontacts_Found &result, mtpRequestId requestId); + bool peopleFailed(const RPCError &error, mtpRequestId requestId); + + SubmitCallback _callback; + + ChildWidget _inner; + ChildWidget _filter; + ChildWidget _filterCancel; + + ChildWidget _share; + ChildWidget _cancel; + + ChildWidget _topShadow; + ChildWidget _bottomShadow; + + QTimer _searchTimer; + QString _peopleQuery; + bool _peopleFull = false; + mtpRequestId _peopleRequest = 0; + + using PeopleCache = QMap; + PeopleCache _peopleCache; + + using PeopleQueries = QMap; + PeopleQueries _peopleQueries; + + IntAnimation _scrollAnimation; + +}; + +namespace internal { + +class ShareInner : public ScrolledWidget, public RPCSender, public Notify::Observer { + Q_OBJECT + +public: + ShareInner(QWidget *parent); + + QVector selected() const; + bool hasSelected() const; + + void peopleReceived(const QString &query, const QVector &people); + + void activateSkipRow(int direction); + void activateSkipColumn(int direction); + void activateSkipPage(int pageHeight, int direction); + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + void updateFilter(QString filter = QString()); + + ~ShareInner(); + +public slots: + void onSelectActive(); + +signals: + void mustScrollTo(int ymin, int ymax); + void filterCancel(); + void searchByUsername(); + void selectedChanged(); + +protected: + void paintEvent(QPaintEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private: + // Observed notifications. + void notifyPeerUpdated(const Notify::PeerUpdate &update); + + int displayedChatsCount() const; + + static constexpr int WideCacheScale = 4; + struct Chat { + Chat(PeerData *peer); + PeerData *peer; + Text name; + bool selected = false; + QPixmap wideUserpicCache; + ColorAnimation nameFg; + FloatAnimation selection; + struct Icon { + FloatAnimation fadeIn; + FloatAnimation fadeOut; + QPixmap wideCheckCache; + }; + QList icons; + }; + void paintChat(Painter &p, Chat *chat, int index); + void updateChat(PeerData *peer); + void updateChatName(Chat *chat, PeerData *peer); + void repaintChat(PeerData *peer); + void removeFadeOutedIcons(Chat *chat); + void prepareWideUserpicCache(Chat *chat); + void prepareWideCheckIconCache(Chat::Icon *icon); + void prepareWideCheckIcons(); + int chatIndex(PeerData *peer) const; + void repaintChatAtIndex(int index); + Chat *getChatAtIndex(int index); + + void loadProfilePhotos(int yFrom); + void changeCheckState(Chat *chat); + + Chat *getChat(Dialogs::Row *row); + void setActive(int active); + void updateUpon(const QPoint &pos); + + void refresh(); + + float64 _columnSkip = 0.; + float64 _rowWidthReal = 0.; + int _rowsLeft = 0; + int _rowsTop = 0; + int _rowWidth = 0; + int _rowHeight = 0; + int _columnCount = 4; + int _active = -1; + int _upon = -1; + + std_::unique_ptr _chatsIndexed; + QString _filter; + using FilteredDialogs = QVector; + FilteredDialogs _filtered; + + QPixmap _wideCheckCache, _wideCheckIconCache; + + using DataMap = QMap; + DataMap _dataMap; + using SelectedChats = OrderedSet; + SelectedChats _selected; + + ChatData *data(Dialogs::Row *row); + + bool _searching = false; + QString _lastQuery; + using ByUsernameRows = QVector; + using ByUsernameDatas = QVector; + ByUsernameRows _byUsernameFiltered; + ByUsernameDatas d_byUsernameFiltered; + +}; + +} // namespace internal diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 29b8949cf1..c7e46a291b 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -24,11 +24,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stickersetbox.h" #include "mainwidget.h" #include "mainwindow.h" +#include "stickers/stickers.h" #include "boxes/confirmbox.h" #include "apiwrap.h" #include "localstorage.h" #include "dialogs/dialogs_layout.h" #include "styles/style_boxes.h" +#include "styles/style_stickers.h" namespace { @@ -37,40 +39,7 @@ constexpr int kArchivedLimitPerPage = 30; } // namespace -namespace Stickers { - -void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) { - auto &v = d.vsets.c_vector().v; - auto &order = Global::RefStickerSetsOrder(); - Stickers::Order archived; - archived.reserve(v.size()); - QMap setsToRequest; - for_const (auto &stickerSet, v) { - if (stickerSet.type() == mtpc_stickerSetCovered && stickerSet.c_stickerSetCovered().vset.type() == mtpc_stickerSet) { - auto set = Stickers::feedSet(stickerSet.c_stickerSetCovered().vset.c_stickerSet()); - if (set->stickers.isEmpty()) { - setsToRequest.insert(set->id, set->access); - } - auto index = order.indexOf(set->id); - if (index >= 0) { - order.removeAt(index); - } - archived.push_back(set->id); - } - } - if (!setsToRequest.isEmpty()) { - for (auto i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) { - App::api()->scheduleStickerSetRequest(i.key(), i.value()); - } - App::api()->requestStickerSets(); - } - Local::writeArchivedStickers(); - Ui::showLayer(new StickersBox(archived), KeepOtherLayers); -} - -} // namespace Stickers - -StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : TWidget() +StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : ScrolledWidget() , _input(set) { connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); switch (set.type()) { @@ -80,6 +49,8 @@ StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : TWidget() MTP::send(MTPmessages_GetStickerSet(_input), rpcDone(&StickerSetInner::gotSet), rpcFail(&StickerSetInner::failedSet)); App::main()->updateStickers(); + setMouseTracking(true); + _previewTimer.setSingleShot(true); connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); } @@ -87,15 +58,20 @@ StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : TWidget() void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { _pack.clear(); _emoji.clear(); + _packOvers.clear(); + _selected = -1; + setCursor(style::cur_default); if (set.type() == mtpc_messages_stickerSet) { auto &d(set.c_messages_stickerSet()); auto &v(d.vdocuments.c_vector().v); _pack.reserve(v.size()); + _packOvers.reserve(v.size()); for (int i = 0, l = v.size(); i < l; ++i) { auto doc = App::feedDocument(v.at(i)); if (!doc || !doc->sticker()) continue; _pack.push_back(doc); + _packOvers.push_back(FloatAnimation()); } auto &packs(d.vpacks.c_vector().v); for (int i = 0, l = packs.size(); i < l; ++i) { @@ -144,6 +120,8 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { } _loaded = true; + updateSelected(); + emit updateButtons(); } @@ -200,12 +178,13 @@ void StickerSetInner::installDone(const MTPmessages_StickerSetInstallResult &res if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); - } else if (wasArchived) { - Local::writeArchivedStickers(); + } else { + if (wasArchived) { + Local::writeArchivedStickers(); + } + Local::writeInstalledStickers(); + emit App::main()->stickersUpdated(); } - - Local::writeInstalledStickers(); - emit App::main()->stickersUpdated(); emit installed(_setId); } @@ -218,15 +197,16 @@ bool StickerSetInner::installFail(const RPCError &error) { } void StickerSetInner::mousePressEvent(QMouseEvent *e) { - int32 index = stickerFromGlobalPos(e->globalPos()); + int index = stickerFromGlobalPos(e->globalPos()); if (index >= 0 && index < _pack.size()) { _previewTimer.start(QApplication::startDragTime()); } } void StickerSetInner::mouseMoveEvent(QMouseEvent *e) { + updateSelected(); if (_previewShown >= 0) { - int32 index = stickerFromGlobalPos(e->globalPos()); + int index = stickerFromGlobalPos(e->globalPos()); if (index >= 0 && index < _pack.size() && index != _previewShown) { _previewShown = index; Ui::showMediaPreview(_pack.at(_previewShown)); @@ -235,11 +215,47 @@ void StickerSetInner::mouseMoveEvent(QMouseEvent *e) { } void StickerSetInner::mouseReleaseEvent(QMouseEvent *e) { - _previewTimer.stop(); + if (_previewShown >= 0) { + _previewShown = -1; + return; + } + if (_previewTimer.isActive()) { + _previewTimer.stop(); + int index = stickerFromGlobalPos(e->globalPos()); + if (index >= 0 && index < _pack.size()) { + if (auto main = App::main()) { + if (main->onSendSticker(_pack.at(index))) { + Ui::hideSettingsAndLayer(); + } + } + } + } +} + +void StickerSetInner::updateSelected() { + auto index = stickerFromGlobalPos(QCursor::pos()); + if (index != _selected) { + startOverAnimation(_selected, 1., 0.); + _selected = index; + startOverAnimation(_selected, 0., 1.); + setCursor(_selected >= 0 ? style::cur_pointer : style::cur_default); + } +} + +void StickerSetInner::startOverAnimation(int index, float64 from, float64 to) { + if (index >= 0 && index < _packOvers.size()) { + START_ANIMATION(_packOvers[index], func([this, index]() { + int row = index / StickerPanPerRow; + int column = index % StickerPanPerRow; + int left = st::stickersPadding.left() + column * st::stickersSize.width(); + int top = st::stickersPadding.top() + row * st::stickersSize.height(); + rtlupdate(left, top, st::stickersSize.width(), st::stickersSize.height()); + }), from, to, st::emojiPanDuration, anim::linear); + } } void StickerSetInner::onPreview() { - int32 index = stickerFromGlobalPos(QCursor::pos()); + int index = stickerFromGlobalPos(QCursor::pos()); if (index >= 0 && index < _pack.size()) { _previewShown = index; Ui::showMediaPreview(_pack.at(_previewShown)); @@ -271,10 +287,19 @@ void StickerSetInner::paintEvent(QPaintEvent *e) { for (int32 j = 0; j < StickerPanPerRow; ++j) { int32 index = i * StickerPanPerRow + j; if (index >= _pack.size()) break; + t_assert(index < _packOvers.size()); DocumentData *doc = _pack.at(index); QPoint pos(st::stickersPadding.left() + j * st::stickersSize.width(), st::stickersPadding.top() + i * st::stickersSize.height()); + if (auto over = _packOvers[index].current((index == _selected) ? 1. : 0.)) { + p.setOpacity(over); + QPoint tl(pos); + if (rtl()) tl.setX(width() - tl.x() - st::stickersSize.width()); + App::roundRect(p, QRect(tl, st::stickersSize), st::emojiPanHover, StickerHoverCorners); + p.setOpacity(1); + + } bool goodThumb = !doc->thumb->isNull() && ((doc->thumb->width() >= 128) || (doc->thumb->height() >= 128)); if (goodThumb) { doc->thumb->load(); @@ -302,10 +327,9 @@ void StickerSetInner::paintEvent(QPaintEvent *e) { } } -void StickerSetInner::setScrollBottom(int32 bottom) { - if (bottom == _bottom) return; - - _bottom = bottom; +void StickerSetInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; } bool StickerSetInner::loaded() const { @@ -357,7 +381,7 @@ StickerSetBox::StickerSetBox(const MTPInputStickerSet &set) : ScrollableBox(st:: connect(&_done, SIGNAL(clicked()), this, SLOT(onClose())); connect(&_inner, SIGNAL(updateButtons()), this, SLOT(onUpdateButtons())); - connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); connect(&_inner, SIGNAL(installed(uint64)), this, SLOT(onInstalled(uint64))); @@ -394,7 +418,9 @@ void StickerSetBox::onUpdateButtons() { } void StickerSetBox::onScroll() { - _inner.setScrollBottom(_scroll.scrollTop() + _scroll.height()); + auto scroll = scrollArea(); + auto scrollTop = scroll->scrollTop(); + _inner.setVisibleTopBottom(scrollTop, scrollTop + scroll->height()); } void StickerSetBox::showAll() { @@ -454,7 +480,7 @@ void StickerSetBox::resizeEvent(QResizeEvent *e) { namespace internal { -StickersInner::StickersInner(StickersBox::Section section) : TWidget() +StickersInner::StickersInner(StickersBox::Section section) : ScrolledWidget() , _section(section) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) , _a_shifting(animation(this, &StickersInner::step_shifting)) @@ -467,7 +493,7 @@ StickersInner::StickersInner(StickersBox::Section section) : TWidget() setup(); } -StickersInner::StickersInner(const Stickers::Order &archivedIds) : TWidget() +StickersInner::StickersInner(const Stickers::Order &archivedIds) : ScrolledWidget() , _section(StickersBox::Section::ArchivedPart) , _archivedIds(archivedIds) , _rowHeight(st::contactsPadding.top() + st::contactsPhotoSize + st::contactsPadding.bottom()) @@ -482,10 +508,15 @@ StickersInner::StickersInner(const Stickers::Order &archivedIds) : TWidget() } void StickersInner::setup() { - connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); + connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(onImageLoaded())); setMouseTracking(true); } +void StickersInner::onImageLoaded() { + update(); + readVisibleSets(); +} + void StickersInner::paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const { if (selected) { p.fillRect(0, y, width(), _buttonHeight, st::contactsBgOver); @@ -618,18 +649,18 @@ void StickersInner::paintRow(Painter &p, int32 index) { int statusx = namex; int statusy = st::contactsPadding.top() + st::contactsStatusTop; + p.setFont(st::contactsNameFont); + p.setPen(st::black); + p.drawTextLeft(namex, namey, width(), s->title, s->titleWidth); + if (s->unread) { p.setPen(Qt::NoPen); p.setBrush(st::stickersFeaturedUnreadBg); p.setRenderHint(QPainter::HighQualityAntialiasing, true); - p.drawEllipse(rtlrect(namex, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width())); + p.drawEllipse(rtlrect(namex + s->titleWidth + st::stickersFeaturedUnreadSkip, namey + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width())); p.setRenderHint(QPainter::HighQualityAntialiasing, false); - namex += st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; } - p.setFont(st::contactsNameFont); - p.setPen(st::black); - p.drawTextLeft(namex, namey, width(), s->title); p.setFont(st::contactsStatusFont); p.setPen(st::contactsStatusFg); @@ -760,7 +791,8 @@ void StickersInner::onClearRecent() { emit App::main()->updateStickers(); rebuild(); - MTP::send(MTPmessages_ClearRecentStickers()); + MTPmessages_ClearRecentStickers::Flags flags = 0; + MTP::send(MTPmessages_ClearRecentStickers(MTP_flags(flags))); } void StickersInner::onClearBoxDestroyed(QObject *box) { @@ -849,59 +881,13 @@ void StickersInner::installSet(uint64 setId) { MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_boolFalse()), rpcDone(&StickersInner::installDone), rpcFail(&StickersInner::installFail, setId)); - auto flags = it->flags; - it->flags &= ~(MTPDstickerSet::Flag::f_archived | MTPDstickerSet_ClientFlag::f_unread); - it->flags |= MTPDstickerSet::Flag::f_installed; - auto changedFlags = flags ^ it->flags; - - auto &order = Global::RefStickerSetsOrder(); - int insertAtIndex = 0, currentIndex = order.indexOf(setId); - if (currentIndex != insertAtIndex) { - if (currentIndex > 0) { - order.removeAt(currentIndex); - } - order.insert(insertAtIndex, setId); - } - - auto custom = sets.find(Stickers::CustomSetId); - if (custom != sets.cend()) { - for_const (auto sticker, it->stickers) { - int removeIndex = custom->stickers.indexOf(sticker); - if (removeIndex >= 0) custom->stickers.removeAt(removeIndex); - } - if (custom->stickers.isEmpty()) { - sets.erase(custom); - } - } - Local::writeInstalledStickers(); - if (changedFlags & MTPDstickerSet_ClientFlag::f_unread) Local::writeFeaturedStickers(); - if (changedFlags & MTPDstickerSet::Flag::f_archived) { - auto index = Global::RefArchivedStickerSetsOrder().indexOf(setId); - if (index >= 0) { - Global::RefArchivedStickerSetsOrder().removeAt(index); - Local::writeArchivedStickers(); - } - } - emit App::main()->stickersUpdated(); + Stickers::installLocally(setId); } void StickersInner::installDone(const MTPmessages_StickerSetInstallResult &result) { if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); - Local::writeInstalledStickers(); - Local::writeArchivedStickers(); - emit App::main()->stickersUpdated(); } - - // TEST DATA ONLY - //MTPVector v = MTP_vector(0); - //for (auto &set : Global::RefStickerSets()) { - // if (rand() < RAND_MAX / 2) { - // set.flags |= MTPDstickerSet::Flag::f_archived; - // v._vector().v.push_back(MTP_stickerSet(MTP_flags(set.flags), MTP_long(set.id), MTP_long(set.access), MTP_string(set.title), MTP_string(set.shortName), MTP_int(set.count), MTP_int(set.hash))); - // } - //} - //Stickers::applyArchivedResult(MTP_messages_stickerSetInstallResultArchive(v).c_messages_stickerSetInstallResultArchive()); } bool StickersInner::installFail(uint64 setId, const RPCError &error) { @@ -914,19 +900,7 @@ bool StickersInner::installFail(uint64 setId, const RPCError &error) { return true; } - it->flags &= ~MTPDstickerSet::Flag::f_installed; - - auto &order = Global::RefStickerSetsOrder(); - int currentIndex = order.indexOf(setId); - if (currentIndex >= 0) { - order.removeAt(currentIndex); - } - - Local::writeInstalledStickers(); - emit App::main()->stickersUpdated(); - - Ui::showLayer(new InformBox(lang(lng_stickers_not_found)), KeepOtherLayers); - + Stickers::undoInstallLocally(setId); return true; } @@ -1056,14 +1030,6 @@ void StickersInner::rebuild() { } App::api()->requestStickerSets(); updateSize(); - - if (_section == Section::Featured && Global::FeaturedStickerSetsUnreadCount()) { - Global::SetFeaturedStickerSetsUnreadCount(0); - for (auto &set : Global::RefStickerSets()) { - set.flags &= ~MTPDstickerSet_ClientFlag::f_unread; - } - MTP::send(MTPmessages_ReadFeaturedStickers(), rpcDone(&StickersInner::readFeaturedDone), rpcFail(&StickersInner::readFeaturedFail)); - } } void StickersInner::updateSize() { @@ -1091,7 +1057,7 @@ void StickersInner::updateRows() { if (_section == Section::Installed) { row->disabled = false; } - row->title = fillSetTitle(set, maxNameWidth); + row->title = fillSetTitle(set, maxNameWidth, &row->titleWidth); row->count = fillSetCount(set); } } @@ -1115,6 +1081,7 @@ int StickersInner::countMaxNameWidth() const { namew -= qMax(qMax(qMax(_returnWidth, _removeWidth), _restoreWidth), _clearWidth); } else { namew -= st::stickersAddIcon.width() - st::defaultActiveButton.width; + namew -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; } return namew; } @@ -1130,10 +1097,11 @@ void StickersInner::rebuildAppendSet(const Stickers::Set &set, int maxNameWidth) int pixw = 0, pixh = 0; fillSetCover(set, &sticker, &pixw, &pixh); - QString title = fillSetTitle(set, maxNameWidth); + int titleWidth = 0; + QString title = fillSetTitle(set, maxNameWidth, &titleWidth); int count = fillSetCount(set); - _rows.push_back(new StickerSetRow(set.id, sticker, count, title, installed, official, unread, disabled, recent, pixw, pixh)); + _rows.push_back(new StickerSetRow(set.id, sticker, count, title, titleWidth, installed, official, unread, disabled, recent, pixw, pixh)); _animStartTimes.push_back(0); } @@ -1181,11 +1149,15 @@ int StickersInner::fillSetCount(const Stickers::Set &set) const { return result + added; } -QString StickersInner::fillSetTitle(const Stickers::Set &set, int maxNameWidth) const { +QString StickersInner::fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const { auto result = set.title; - int32 titleWidth = st::contactsNameFont->width(result); + int titleWidth = st::contactsNameFont->width(result); if (titleWidth > maxNameWidth) { result = st::contactsNameFont->elided(result, maxNameWidth); + titleWidth = st::contactsNameFont->width(result); + } + if (outTitleWidth) { + *outTitleWidth = titleWidth; } return result; } @@ -1201,35 +1173,11 @@ void StickersInner::fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outOfficial = (set.flags & MTPDstickerSet::Flag::f_official); *outDisabled = (set.flags & MTPDstickerSet::Flag::f_archived); if (_section == Section::Featured) { - *outUnread = _unreadSets.contains(set.id); - if (!*outUnread && (set.flags & MTPDstickerSet_ClientFlag::f_unread)) { - *outUnread = true; - _unreadSets.insert(set.id); - } + *outUnread = (set.flags & MTPDstickerSet_ClientFlag::f_unread); } } } -void StickersInner::readFeaturedDone(const MTPBool &result) { - Local::writeFeaturedStickers(); - emit App::main()->stickersUpdated(); -} - -bool StickersInner::readFeaturedFail(const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - - int unreadCount = 0; - for_const (auto &set, Global::StickerSets()) { - if (!(set.flags & MTPDstickerSet::Flag::f_installed)) { - if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { - ++unreadCount; - } - } - } - Global::SetFeaturedStickerSetsUnreadCount(unreadCount); - return true; -} - Stickers::Order StickersInner::getOrder() const { Stickers::Order result; result.reserve(_rows.size()); @@ -1253,6 +1201,32 @@ Stickers::Order StickersInner::getDisabledSets() const { return result; } +void StickersInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + if (_section == Section::Featured) { + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; + readVisibleSets(); + } +} + +void StickersInner::readVisibleSets() { + auto itemsVisibleTop = _visibleTop - _itemsTop; + auto itemsVisibleBottom = _visibleBottom - _itemsTop; + int rowFrom = floorclamp(itemsVisibleTop, _rowHeight, 0, _rows.size()); + int rowTo = ceilclamp(itemsVisibleBottom, _rowHeight, 0, _rows.size()); + for (int i = rowFrom; i < rowTo; ++i) { + if (!_rows[i]->unread) { + continue; + } + if (i * _rowHeight < itemsVisibleTop || (i + 1) * _rowHeight > itemsVisibleBottom) { + continue; + } + if (!_rows[i]->sticker || _rows[i]->sticker->thumb->loaded() || _rows[i]->sticker->loaded()) { + Stickers::markFeaturedAsRead(_rows[i]->id); + } + } +} + void StickersInner::setVisibleScrollbar(int32 width) { _scrollbar = width; } @@ -1299,9 +1273,24 @@ void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedSti bool addedSet = false; auto &v = stickers.vsets.c_vector().v; for_const (auto &stickerSet, v) { - if (stickerSet.type() != mtpc_stickerSetCovered || stickerSet.c_stickerSetCovered().vset.type() != mtpc_stickerSet) continue; + const MTPDstickerSet *setData = nullptr; + switch (stickerSet.type()) { + case mtpc_stickerSetCovered: { + auto &d = stickerSet.c_stickerSetCovered(); + if (d.vset.type() == mtpc_stickerSet) { + setData = &d.vset.c_stickerSet(); + } + } break; + case mtpc_stickerSetMultiCovered: { + auto &d = stickerSet.c_stickerSetMultiCovered(); + if (d.vset.type() == mtpc_stickerSet) { + setData = &d.vset.c_stickerSet(); + } + } break; + } + if (!setData) continue; - if (auto set = Stickers::feedSet(stickerSet.c_stickerSetCovered().vset.c_stickerSet())) { + if (auto set = Stickers::feedSet(*setData)) { auto index = archived.indexOf(set->id); if (archived.isEmpty() || index != archived.size() - 1) { if (index < archived.size() - 1) { @@ -1326,7 +1315,7 @@ void StickersBox::getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedSti if (addedSet) { _inner->updateSize(); setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); - _inner->setVisibleScrollbar((_scroll.scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); App::api()->requestStickerSets(); } else { _allArchivedLoaded = v.isEmpty() || (offsetId != 0); @@ -1389,7 +1378,7 @@ void StickersBox::setup() { connect(_inner, SIGNAL(checkDraggingScroll(int)), this, SLOT(onCheckDraggingScroll(int))); connect(_inner, SIGNAL(noDraggingScroll()), this, SLOT(onNoDraggingScroll())); connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); - connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + connect(scrollArea(), SIGNAL(scrolled()), this, SLOT(onScroll())); _scrollTimer.setSingleShot(false); rebuildList(); @@ -1398,14 +1387,21 @@ void StickersBox::setup() { } void StickersBox::onScroll() { + updateVisibleTopBottom(); checkLoadMoreArchived(); } +void StickersBox::updateVisibleTopBottom() { + auto visibleTop = scrollArea()->scrollTop(); + auto visibleBottom = visibleTop + scrollArea()->height(); + _inner->setVisibleTopBottom(visibleTop, visibleBottom); +} + void StickersBox::checkLoadMoreArchived() { if (_section != Section::Archived) return; - int scrollTop = _scroll.scrollTop(), scrollTopMax = _scroll.scrollTopMax(); - if (scrollTop + PreloadHeightsCount * _scroll.height() >= scrollTopMax) { + int scrollTop = scrollArea()->scrollTop(), scrollTopMax = scrollArea()->scrollTopMax(); + if (scrollTop + PreloadHeightsCount * scrollArea()->height() >= scrollTopMax) { if (!_archivedRequestId && !_allArchivedLoaded) { uint64 lastId = 0; for (auto setId = Global::ArchivedStickerSetsOrder().cend(), e = Global::ArchivedStickerSetsOrder().cbegin(); setId != e;) { @@ -1452,10 +1448,12 @@ void StickersBox::saveOrder() { if (order.size() > 1) { QVector mtpOrder; mtpOrder.reserve(order.size()); - for (int32 i = 0, l = order.size(); i < l; ++i) { + for (int i = 0, l = order.size(); i < l; ++i) { mtpOrder.push_back(MTP_long(order.at(i))); } - _reorderRequest = MTP::send(MTPmessages_ReorderStickerSets(MTP_vector(mtpOrder)), rpcDone(&StickersBox::reorderDone), rpcFail(&StickersBox::reorderFail)); + + MTPmessages_ReorderStickerSets::Flags flags = 0; + _reorderRequest = MTP::send(MTPmessages_ReorderStickerSets(MTP_flags(flags), MTP_vector(mtpOrder)), rpcDone(&StickersBox::reorderDone), rpcFail(&StickersBox::reorderFail)); } else { reorderDone(MTP_boolTrue()); } @@ -1522,7 +1520,8 @@ StickersBox::~StickersBox() { void StickersBox::resizeEvent(QResizeEvent *e) { ItemListBox::resizeEvent(e); _inner->resize(width(), _inner->height()); - _inner->setVisibleScrollbar((_scroll.scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + updateVisibleTopBottom(); if (_topShadow) { _topShadow->setGeometry(0, st::boxTitleHeight + _aboutHeight, width(), st::lineWidth); } @@ -1546,14 +1545,14 @@ void StickersBox::onStickersUpdated() { void StickersBox::rebuildList() { _inner->rebuild(); setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); - _inner->setVisibleScrollbar((_scroll.scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); } void StickersBox::onCheckDraggingScroll(int localY) { - if (localY < _scroll.scrollTop()) { - _scrollDelta = localY - _scroll.scrollTop(); - } else if (localY >= _scroll.scrollTop() + _scroll.height()) { - _scrollDelta = localY - _scroll.scrollTop() - _scroll.height() + 1; + if (localY < scrollArea()->scrollTop()) { + _scrollDelta = localY - scrollArea()->scrollTop(); + } else if (localY >= scrollArea()->scrollTop() + scrollArea()->height()) { + _scrollDelta = localY - scrollArea()->scrollTop() - scrollArea()->height() + 1; } else { _scrollDelta = 0; } @@ -1570,7 +1569,7 @@ void StickersBox::onNoDraggingScroll() { void StickersBox::onScrollTimer() { int32 d = (_scrollDelta > 0) ? qMin(_scrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_scrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed)); - _scroll.scrollToY(_scroll.scrollTop() + d); + scrollArea()->scrollToY(scrollArea()->scrollTop() + d); } void StickersBox::onSave() { diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index ef73191b2e..d5c4d2f81c 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -24,42 +24,40 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org class ConfirmBox; -class StickerSetInner : public TWidget, public RPCSender { +class StickerSetInner : public ScrolledWidget, public RPCSender { Q_OBJECT public: - StickerSetInner(const MTPInputStickerSet &set); - void mousePressEvent(QMouseEvent *e); - void mouseMoveEvent(QMouseEvent *e); - void mouseReleaseEvent(QMouseEvent *e); - - void paintEvent(QPaintEvent *e); - bool loaded() const; int32 notInstalled() const; bool official() const; QString title() const; QString shortName() const; - void setScrollBottom(int32 bottom); + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; void install(); ~StickerSetInner(); -public slots: +protected: + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void paintEvent(QPaintEvent *e) override; +private slots: void onPreview(); signals: - void updateButtons(); void installed(uint64 id); private: - - int32 stickerFromGlobalPos(const QPoint &p) const; + void updateSelected(); + void startOverAnimation(int index, float64 from, float64 to); + int stickerFromGlobalPos(const QPoint &p) const; void gotSet(const MTPmessages_StickerSet &set); bool failedSet(const RPCError &error); @@ -67,6 +65,7 @@ private: void installDone(const MTPmessages_StickerSetInstallResult &result); bool installFail(const RPCError &error); + QVector _packOvers; StickerPack _pack; StickersByEmojiMap _emoji; bool _loaded = false; @@ -77,13 +76,17 @@ private: int32 _setHash = 0; MTPDstickerSet::Flags _setFlags = 0; - int32 _bottom = 0; + int _visibleTop = 0; + int _visibleBottom = 0; MTPInputStickerSet _input; mtpRequestId _installRequest = 0; + int _selected = -1; + QTimer _previewTimer; - int32 _previewShown = -1; + int _previewShown = -1; + }; class StickerSetBox : public ScrollableBox, public RPCSender { @@ -169,6 +172,7 @@ private: bool reorderFail(const RPCError &result); void saveOrder(); + void updateVisibleTopBottom(); void checkLoadMoreArchived(); void getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result); @@ -198,7 +202,7 @@ int32 stickerPacksCount(bool includeDisabledOfficial = false); namespace internal { -class StickersInner : public TWidget, public RPCSender { +class StickersInner : public ScrolledWidget, public RPCSender { Q_OBJECT public: @@ -220,6 +224,7 @@ public: Stickers::Order getDisabledSets() const; void setVisibleScrollbar(int32 width); + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; ~StickersInner(); @@ -239,6 +244,9 @@ public slots: void onClearRecent(); void onClearBoxDestroyed(QObject *box); +private slots: + void onImageLoaded(); + private: void setup(); void paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const; @@ -249,21 +257,22 @@ private: void setActionSel(int32 actionSel); float64 aboveShadowOpacity() const; + void readVisibleSets(); + void installSet(uint64 setId); void installDone(const MTPmessages_StickerSetInstallResult &result); bool installFail(uint64 setId, const RPCError &error); - void readFeaturedDone(const MTPBool &result); - bool readFeaturedFail(const RPCError &error); Section _section; Stickers::Order _archivedIds; int32 _rowHeight; struct StickerSetRow { - StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, bool installed, bool official, bool unread, bool disabled, bool recent, int32 pixw, int32 pixh) : id(id) + StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, int titleWidth, bool installed, bool official, bool unread, bool disabled, bool recent, int32 pixw, int32 pixh) : id(id) , sticker(sticker) , count(count) , title(title) + , titleWidth(titleWidth) , installed(installed) , official(official) , unread(unread) @@ -277,6 +286,7 @@ private: DocumentData *sticker; int32 count; QString title; + int titleWidth; bool installed, official, unread, disabled, recent; int32 pixw, pixh; anim::ivalue yadd; @@ -286,7 +296,7 @@ private: void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth); void fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const; int fillSetCount(const Stickers::Set &set) const; - QString fillSetTitle(const Stickers::Set &set, int maxNameWidth) const; + QString fillSetTitle(const Stickers::Set &set, int maxNameWidth, int *outTitleWidth) const; void fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled); int countMaxNameWidth() const; @@ -297,7 +307,9 @@ private: anim::fvalue _aboveShadowFadeOpacity = { 0., 0. }; Animation _a_shifting; - int32 _itemsTop; + int _visibleTop = 0; + int _visibleBottom = 0; + int _itemsTop = 0; bool _saving = false; @@ -312,9 +324,6 @@ private: bool _hasFeaturedButton = false; bool _hasArchivedButton = false; - // Remember all the unread set ids to display unread dots. - OrderedSet _unreadSets; - QPoint _mouse; int _selected = -3; // -2 - featured stickers button, -1 - archived stickers button int _pressed = -2; diff --git a/Telegram/SourceFiles/core/basic_types.h b/Telegram/SourceFiles/core/basic_types.h index 42a3143879..68dd42b2ef 100644 --- a/Telegram/SourceFiles/core/basic_types.h +++ b/Telegram/SourceFiles/core/basic_types.h @@ -1275,8 +1275,8 @@ public: template class NullFunctionImplementation : public FunctionImplementation { public: - virtual R call(Args... args) { return R(); } - virtual void destroy() {} + R call(Args... args) override { return R(); } + void destroy() override {} static NullFunctionImplementation SharedInstance; }; @@ -1325,7 +1325,7 @@ class WrappedFunction : public FunctionImplementation { public: using Method = R(*)(Args... args); WrappedFunction(Method method) : _method(method) {} - virtual R call(Args... args) { return (*_method)(args...); } + R call(Args... args) override { return (*_method)(args...); } private: Method _method; @@ -1341,7 +1341,7 @@ class ObjectFunction : public FunctionImplementation { public: using Method = R(I::*)(Args... args); ObjectFunction(O *obj, Method method) : _obj(obj), _method(method) {} - virtual R call(Args... args) { return (_obj->*_method)(args...); } + R call(Args... args) override { return (_obj->*_method)(args...); } private: O *_obj; diff --git a/Telegram/SourceFiles/core/click_handler_types.cpp b/Telegram/SourceFiles/core/click_handler_types.cpp index c7f95f9b25..1c55c648df 100644 --- a/Telegram/SourceFiles/core/click_handler_types.cpp +++ b/Telegram/SourceFiles/core/click_handler_types.cpp @@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/confirmbox.h" #include "core/qthelp_regex.h" #include "core/qthelp_url.h" +#include "localstorage.h" QString UrlClickHandler::copyToClipboardContextItemText() const { return lang(isEmail() ? lng_context_copy_email : lng_context_copy_link); @@ -113,6 +114,20 @@ void HiddenUrlClickHandler::onClick(Qt::MouseButton button) const { } } +void BotGameUrlClickHandler::onClick(Qt::MouseButton button) const { + auto u = url(); + + u = tryConvertUrlToLocal(u); + + if (u.startsWith(qstr("tg://"))) { + App::openLocalUrl(u); + } else if (!_bot || Local::isBotTrusted(_bot)) { + doOpen(u); + } else { + Ui::showLayer(new ConfirmBotGameBox(_bot, u)); + } +} + QString HiddenUrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const { QString result; if (mode == ExpandLinksAll) { diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index 84deafe77c..a64a168497 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -122,6 +122,17 @@ public: }; +class BotGameUrlClickHandler : public UrlClickHandler { +public: + BotGameUrlClickHandler(UserData *bot, QString url) : UrlClickHandler(url, false), _bot(bot) { + } + void onClick(Qt::MouseButton button) const override; + +private: + UserData *_bot; + +}; + class MentionClickHandler : public TextClickHandler { public: MentionClickHandler(const QString &tag) : _tag(tag) { diff --git a/Telegram/SourceFiles/core/lambda_wrap.h b/Telegram/SourceFiles/core/lambda_wrap.h index 549f35363d..765c71859b 100644 --- a/Telegram/SourceFiles/core/lambda_wrap.h +++ b/Telegram/SourceFiles/core/lambda_wrap.h @@ -372,3 +372,42 @@ public: }; } // namespace base + +// While we still use Function<> + +template +struct LambdaFunctionHelper; + +template +struct LambdaFunctionHelper { + using FunctionType = Function; + using UniqueType = base::lambda_unique; +}; + +template +using LambdaGetFunction = typename LambdaFunctionHelper::FunctionType; + +template +using LambdaGetUnique = typename LambdaFunctionHelper::UniqueType; + +template +class LambdaFunctionImplementation : public FunctionImplementation { +public: + LambdaFunctionImplementation(base::lambda_unique &&lambda) : _lambda(std_::move(lambda)) { + } + R call(Args... args) override { return _lambda(std_::forward(args)...); } + +private: + base::lambda_unique _lambda; + +}; + +template +inline Function lambda_wrap_helper(base::lambda_unique &&lambda) { + return Function(new LambdaFunctionImplementation(std_::move(lambda))); +} + +template ::value>> +inline LambdaGetFunction func(Lambda &&lambda) { + return lambda_wrap_helper(LambdaGetUnique(std_::move(lambda))); +} diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp index 7c262f3aa2..d3da6f781f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp @@ -71,6 +71,16 @@ void IndexedList::adjustByPos(const RowsByLetter &links) { } } +void IndexedList::moveToTop(PeerData *peer) { + if (_list.moveToTop(peer->id)) { + for_const (auto ch, peer->chars) { + if (auto list = _index.value(ch)) { + list->moveToTop(peer->id); + } + } + } +} + void IndexedList::peerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { t_assert(_sortMode != SortMode::Date); if (_sortMode == SortMode::Name) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h index 6a5967977c..b2b65dd238 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h @@ -34,6 +34,7 @@ public: RowsByLetter addToEnd(History *history); Row *addByName(History *history); void adjustByPos(const RowsByLetter &links); + void moveToTop(PeerData *peer); // For sortMode != SortMode::Date void peerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_list.cpp index e230f53ef1..63eff845b3 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_list.cpp @@ -189,6 +189,14 @@ void List::adjustByPos(Row *row) { } } +bool List::moveToTop(PeerId peerId) { + auto i = _rowByPeer.find(peerId); + if (i == _rowByPeer.cend()) return false; + + insertBefore(i.value(), _begin); + return true; +} + bool List::del(PeerId peerId, Row *replacedBy) { auto i = _rowByPeer.find(peerId); if (i == _rowByPeer.cend()) return false; diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.h b/Telegram/SourceFiles/dialogs/dialogs_list.h index 422eeb2037..c6c40950a9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_list.h @@ -53,10 +53,9 @@ public: void paint(Painter &p, int32 w, int32 hFrom, int32 hTo, PeerData *act, PeerData *sel, bool onlyBackground) const; Row *addToEnd(History *history); - bool insertBefore(Row *row, Row *before); - bool insertAfter(Row *row, Row *after); Row *adjustByName(const PeerData *peer); Row *addByName(History *history); + bool moveToTop(PeerId peerId); void adjustByPos(Row *row); bool del(PeerId peerId, Row *replacedBy = nullptr); void remove(Row *row); @@ -114,6 +113,8 @@ public: private: void adjustCurrent(int y, int h) const; + bool insertBefore(Row *row, Row *before); + bool insertAfter(Row *row, Row *after); static Row *next(Row *row) { return row->_next; } diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 9ba408f634..1045d9bb08 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "dropdown.h" +#include "styles/style_stickers.h" #include "boxes/confirmbox.h" #include "boxes/stickersetbox.h" #include "inline_bots/inline_bot_result.h" @@ -34,11 +35,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.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) , _a_appearance(animation(this, &Dropdown::step_appearance)) , _shadow(_st.shadow) { @@ -456,3446 +454,3 @@ void DragArea::step_appearance(float64 ms, bool timer) { } if (timer) update(); } - -namespace internal { - -EmojiColorPicker::EmojiColorPicker() : TWidget() -, _ignoreShow(false) -, _a_selected(animation(this, &EmojiColorPicker::step_selected)) -, _selected(-1) -, _pressedSel(-1) -, _hiding(false) -, a_opacity(0) -, _a_appearance(animation(this, &EmojiColorPicker::step_appearance)) -, _shadow(st::dropdownDef.shadow) { - memset(_variants, 0, sizeof(_variants)); - memset(_hovers, 0, sizeof(_hovers)); - - setMouseTracking(true); - setFocusPolicy(Qt::NoFocus); - - int32 w = st::emojiPanSize.width() * (EmojiColorsCount + 1) + 4 * st::emojiColorsPadding + st::emojiColorsSep + st::dropdownDef.shadow.pxWidth() * 2; - int32 h = 2 * st::emojiColorsPadding + st::emojiPanSize.height() + st::dropdownDef.shadow.pxHeight() * 2; - resize(w, h); - - _hideTimer.setSingleShot(true); - connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); -} - -void EmojiColorPicker::showEmoji(uint32 code) { - EmojiPtr e = emojiGet(code); - if (!e || e == TwoSymbolEmoji || !e->color) { - return; - } - _ignoreShow = false; - - _variants[0] = e; - _variants[1] = emojiGet(e, 0xD83CDFFB); - _variants[2] = emojiGet(e, 0xD83CDFFC); - _variants[3] = emojiGet(e, 0xD83CDFFD); - _variants[4] = emojiGet(e, 0xD83CDFFE); - _variants[5] = emojiGet(e, 0xD83CDFFF); - - if (!_cache.isNull()) _cache = QPixmap(); - showStart(); -} - -void EmojiColorPicker::paintEvent(QPaintEvent *e) { - Painter p(this); - - if (!_cache.isNull()) { - p.setOpacity(a_opacity.current()); - } - if (e->rect() != rect()) { - p.setClipRect(e->rect()); - } - - int32 w = st::dropdownDef.shadow.pxWidth(), h = st::dropdownDef.shadow.pxHeight(); - QRect r = QRect(w, h, width() - 2 * w, height() - 2 * h); - _shadow.paint(p, r, st::dropdownDef.shadowShift); - - if (_cache.isNull()) { - p.fillRect(e->rect().intersected(r), st::white->b); - - int32 x = w + 2 * st::emojiColorsPadding + st::emojiPanSize.width(); - if (rtl()) x = width() - x - st::emojiColorsSep; - p.fillRect(x, h + st::emojiColorsPadding, st::emojiColorsSep, r.height() - st::emojiColorsPadding * 2, st::emojiColorsSepColor->b); - - if (!_variants[0]) return; - for (int i = 0; i < EmojiColorsCount + 1; ++i) { - drawVariant(p, i); - } - } else { - p.drawPixmap(r.left(), r.top(), _cache); - } - -} - -void EmojiColorPicker::enterEvent(QEvent *e) { - _hideTimer.stop(); - if (_hiding) showStart(); - TWidget::enterEvent(e); -} - -void EmojiColorPicker::leaveEvent(QEvent *e) { - TWidget::leaveEvent(e); -} - -void EmojiColorPicker::mousePressEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - updateSelected(); - _pressedSel = _selected; -} - -void EmojiColorPicker::mouseReleaseEvent(QMouseEvent *e) { - _lastMousePos = e ? e->globalPos() : QCursor::pos(); - int32 pressed = _pressedSel; - _pressedSel = -1; - - updateSelected(); - if (_selected >= 0 && (pressed < 0 || _selected == pressed)) { - emit emojiSelected(_variants[_selected]); - } - _ignoreShow = true; - hideStart(); -} - -void EmojiColorPicker::mouseMoveEvent(QMouseEvent *e) { - _lastMousePos = e ? e->globalPos() : QCursor::pos(); - updateSelected(); -} - -void EmojiColorPicker::step_appearance(float64 ms, bool timer) { - if (_cache.isNull()) { - _a_appearance.stop(); - return; - } - float64 dt = ms / st::dropdownDef.duration; - if (dt >= 1) { - a_opacity.finish(); - _cache = QPixmap(); - if (_hiding) { - hide(); - emit hidden(); - } else { - _lastMousePos = QCursor::pos(); - updateSelected(); - } - _a_appearance.stop(); - } else { - a_opacity.update(dt, anim::linear); - } - if (timer) update(); -} - -void EmojiColorPicker::step_selected(uint64 ms, bool timer) { - QRegion toUpdate; - for (EmojiAnimations::iterator i = _emojiAnimations.begin(); i != _emojiAnimations.end();) { - int index = qAbs(i.key()) - 1; - float64 dt = float64(ms - i.value()) / st::emojiPanDuration; - if (dt >= 1) { - _hovers[index] = (i.key() > 0) ? 1 : 0; - i = _emojiAnimations.erase(i); - } else { - _hovers[index] = (i.key() > 0) ? dt : (1 - dt); - ++i; - } - toUpdate += QRect(st::dropdownDef.shadow.pxWidth() + st::emojiColorsPadding + index * st::emojiPanSize.width() + (index ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::dropdownDef.shadow.pxHeight() + st::emojiColorsPadding, st::emojiPanSize.width(), st::emojiPanSize.height()); - } - if (timer) rtlupdate(toUpdate.boundingRect()); - if (_emojiAnimations.isEmpty()) _a_selected.stop(); -} - -void EmojiColorPicker::hideStart(bool fast) { - if (fast) { - clearSelection(true); - if (_a_appearance.animating()) _a_appearance.stop(); - if (_a_selected.animating()) _a_selected.stop(); - a_opacity = anim::fvalue(0); - _cache = QPixmap(); - hide(); - emit hidden(); - } else { - if (_cache.isNull()) { - int32 w = st::dropdownDef.shadow.pxWidth(), h = st::dropdownDef.shadow.pxHeight(); - _cache = myGrab(this, QRect(w, h, width() - 2 * w, height() - 2 * h)); - clearSelection(true); - } - _hiding = true; - a_opacity.start(0); - _a_appearance.start(); - } -} - -void EmojiColorPicker::showStart() { - if (_ignoreShow) return; - - _hiding = false; - if (!isHidden() && a_opacity.current() == 1) { - if (_a_appearance.animating()) { - _a_appearance.stop(); - _cache = QPixmap(); - } - return; - } - if (_cache.isNull()) { - int32 w = st::dropdownDef.shadow.pxWidth(), h = st::dropdownDef.shadow.pxHeight(); - _cache = myGrab(this, QRect(w, h, width() - 2 * w, height() - 2 * h)); - clearSelection(true); - } - show(); - a_opacity.start(1); - _a_appearance.start(); -} - -void EmojiColorPicker::clearSelection(bool fast) { - _pressedSel = -1; - _lastMousePos = mapToGlobal(QPoint(-10, -10)); - if (fast) { - _selected = -1; - memset(_hovers, 0, sizeof(_hovers)); - _emojiAnimations.clear(); - } else { - updateSelected(); - } -} - -void EmojiColorPicker::updateSelected() { - int32 selIndex = -1; - QPoint p(mapFromGlobal(_lastMousePos)); - int32 sx = rtl() ? (width() - p.x()) : p.x(), y = p.y() - st::dropdownDef.shadow.pxHeight() - st::emojiColorsPadding; - if (y >= 0 && y < st::emojiPanSize.height()) { - int32 x = sx - st::dropdownDef.shadow.pxWidth() - st::emojiColorsPadding; - if (x >= 0 && x < st::emojiPanSize.width()) { - selIndex = 0; - } else { - x -= st::emojiPanSize.width() + 2 * st::emojiColorsPadding + st::emojiColorsSep; - if (x >= 0 && x < st::emojiPanSize.width() * EmojiColorsCount) { - selIndex = (x / st::emojiPanSize.width()) + 1; - } - } - } - - bool startanim = false; - if (selIndex != _selected) { - if (_selected >= 0) { - _emojiAnimations.remove(_selected + 1); - if (_emojiAnimations.find(-_selected - 1) == _emojiAnimations.end()) { - if (_emojiAnimations.isEmpty()) startanim = true; - _emojiAnimations.insert(-_selected - 1, getms()); - } - } - _selected = selIndex; - if (_selected >= 0) { - _emojiAnimations.remove(-_selected - 1); - if (_emojiAnimations.find(_selected + 1) == _emojiAnimations.end()) { - if (_emojiAnimations.isEmpty()) startanim = true; - _emojiAnimations.insert(_selected + 1, getms()); - } - } - setCursor((_selected >= 0) ? style::cur_pointer : style::cur_default); - } - if (startanim && !_a_selected.animating()) _a_selected.start(); -} - -void EmojiColorPicker::drawVariant(Painter &p, int variant) { - float64 hover = _hovers[variant]; - - QPoint w(st::dropdownDef.shadow.pxWidth() + st::emojiColorsPadding + variant * st::emojiPanSize.width() + (variant ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::dropdownDef.shadow.pxHeight() + st::emojiColorsPadding); - if (hover > 0) { - p.setOpacity(hover); - QPoint tl(w); - if (rtl()) tl.setX(width() - tl.x() - st::emojiPanSize.width()); - App::roundRect(p, QRect(tl, st::emojiPanSize), st::emojiPanHover, StickerHoverCorners); - p.setOpacity(1); - } - int esize = EmojiSizes[EIndex + 1]; - p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_variants[variant]->x * esize, _variants[variant]->y * esize, esize, esize)); -} - -EmojiPanInner::EmojiPanInner() : TWidget() -, _maxHeight(int(st::emojiPanMaxHeight) - st::rbEmoji.height) -, _a_selected(animation(this, &EmojiPanInner::step_selected)) -, _top(0) -, _selected(-1) -, _pressedSel(-1) -, _pickerSel(-1) { - resize(st::emojiPanWidth - st::emojiScroll.width, countHeight()); - - setMouseTracking(true); - setFocusPolicy(Qt::NoFocus); - setAttribute(Qt::WA_OpaquePaintEvent); - - _picker.hide(); - - _esize = EmojiSizes[EIndex + 1]; - - for (int32 i = 0; i < emojiTabCount; ++i) { - _counts[i] = emojiPackCount(emojiTabAtIndex(i)); - _hovers[i] = QVector(_counts[i], 0); - } - - _showPickerTimer.setSingleShot(true); - connect(&_showPickerTimer, SIGNAL(timeout()), this, SLOT(onShowPicker())); - connect(&_picker, SIGNAL(emojiSelected(EmojiPtr)), this, SLOT(onColorSelected(EmojiPtr))); - connect(&_picker, SIGNAL(hidden()), this, SLOT(onPickerHidden())); -} - -void EmojiPanInner::setMaxHeight(int32 h) { - _maxHeight = h; - resize(st::emojiPanWidth - st::emojiScroll.width, countHeight()); -} - -void EmojiPanInner::setScrollTop(int top) { - _top = top; -} - -int EmojiPanInner::countHeight() { - int result = 0; - for (int i = 0; i < emojiTabCount; ++i) { - int cnt = emojiPackCount(emojiTabAtIndex(i)), rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0); - result += st::emojiPanHeader + rows * st::emojiPanSize.height(); - } - - return result + st::emojiPanPadding; -} - -void EmojiPanInner::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 fromcol = floorclamp(r.x() - st::emojiPanPadding, st::emojiPanSize.width(), 0, EmojiPanPerRow); - int32 tocol = ceilclamp(r.x() + r.width() - st::emojiPanPadding, st::emojiPanSize.width(), 0, EmojiPanPerRow); - if (rtl()) { - qSwap(fromcol, tocol); - fromcol = EmojiPanPerRow - fromcol; - tocol = EmojiPanPerRow - tocol; - } - - int32 y, tilly = 0; - for (int c = 0; c < emojiTabCount; ++c) { - y = tilly; - int32 size = _counts[c]; - int32 rows = (size / EmojiPanPerRow) + ((size % EmojiPanPerRow) ? 1 : 0); - tilly = y + st::emojiPanHeader + (rows * st::emojiPanSize.height()); - if (r.top() >= tilly) continue; - - y += st::emojiPanHeader; - if (_emojis[c].isEmpty()) { - _emojis[c] = emojiPack(emojiTabAtIndex(c)); - if (emojiTabAtIndex(c) != dbietRecent) { - for (EmojiPack::iterator i = _emojis[c].begin(), e = _emojis[c].end(); i != e; ++i) { - if ((*i)->color) { - EmojiColorVariants::const_iterator j = cEmojiVariants().constFind((*i)->code); - if (j != cEmojiVariants().cend()) { - EmojiPtr replace = emojiFromKey(j.value()); - if (replace) { - if (replace != TwoSymbolEmoji && replace->code == (*i)->code && replace->code2 == (*i)->code2) { - *i = replace; - } - } - } - } - } - } - } - - int32 fromrow = floorclamp(r.y() - y, st::emojiPanSize.height(), 0, rows); - int32 torow = ceilclamp(r.y() + r.height() - y, st::emojiPanSize.height(), 0, rows); - for (int32 i = fromrow; i < torow; ++i) { - for (int32 j = fromcol; j < tocol; ++j) { - int32 index = i * EmojiPanPerRow + j; - if (index >= size) break; - - float64 hover = (!_picker.isHidden() && c * MatrixRowShift + index == _pickerSel) ? 1 : _hovers[c][index]; - - QPoint w(st::emojiPanPadding + j * st::emojiPanSize.width(), y + i * st::emojiPanSize.height()); - if (hover > 0) { - p.setOpacity(hover); - QPoint tl(w); - if (rtl()) tl.setX(width() - tl.x() - st::emojiPanSize.width()); - App::roundRect(p, QRect(tl, st::emojiPanSize), st::emojiPanHover, StickerHoverCorners); - p.setOpacity(1); - } - p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (_esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (_esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_emojis[c][index]->x * _esize, _emojis[c][index]->y * _esize, _esize, _esize)); - } - } - } -} - -bool EmojiPanInner::checkPickerHide() { - if (!_picker.isHidden() && _selected == _pickerSel) { - _picker.hideStart(); - _pickerSel = -1; - updateSelected(); - return true; - } - return false; -} - -void EmojiPanInner::mousePressEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - updateSelected(); - if (checkPickerHide()) { - return; - } - _pressedSel = _selected; - - if (_selected >= 0) { - int tab = (_selected / MatrixRowShift), sel = _selected % MatrixRowShift; - if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) { - _pickerSel = _selected; - setCursor(style::cur_default); - if (cEmojiVariants().constFind(_emojis[tab][sel]->code) == cEmojiVariants().cend()) { - onShowPicker(); - } else { - _showPickerTimer.start(500); - } - } - } -} - -void EmojiPanInner::mouseReleaseEvent(QMouseEvent *e) { - int32 pressed = _pressedSel; - _pressedSel = -1; - - _lastMousePos = e->globalPos(); - if (!_picker.isHidden()) { - if (_picker.rect().contains(_picker.mapFromGlobal(_lastMousePos))) { - return _picker.mouseReleaseEvent(0); - } else if (_pickerSel >= 0) { - int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift; - if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) { - if (cEmojiVariants().constFind(_emojis[tab][sel]->code) != cEmojiVariants().cend()) { - _picker.hideStart(); - _pickerSel = -1; - } - } - } - } - updateSelected(); - - if (_showPickerTimer.isActive()) { - _showPickerTimer.stop(); - _pickerSel = -1; - _picker.hide(); - } - - if (_selected < 0 || _selected != pressed) return; - - if (_selected >= emojiTabCount * MatrixRowShift) { - return; - } - - int tab = (_selected / MatrixRowShift), sel = _selected % MatrixRowShift; - if (sel < _emojis[tab].size()) { - EmojiPtr emoji(_emojis[tab][sel]); - if (emoji->color && !_picker.isHidden()) return; - - selectEmoji(emoji); - } -} - -void EmojiPanInner::selectEmoji(EmojiPtr emoji) { - RecentEmojiPack &recent(cGetRecentEmojis()); - RecentEmojiPack::iterator i = recent.begin(), e = recent.end(); - for (; i != e; ++i) { - if (i->first == emoji) { - ++i->second; - if (i->second > 0x8000) { - for (RecentEmojiPack::iterator j = recent.begin(); j != e; ++j) { - if (j->second > 1) { - j->second /= 2; - } else { - j->second = 1; - } - } - } - for (; i != recent.begin(); --i) { - if ((i - 1)->second > i->second) { - break; - } - qSwap(*i, *(i - 1)); - } - break; - } - } - if (i == e) { - 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) { - break; - } - qSwap(*i, *(i - 1)); - } - } - emit saveConfigDelayed(SaveRecentEmojisTimeout); - - emit selected(emoji); -} - -void EmojiPanInner::onShowPicker() { - if (_pickerSel < 0) return; - - int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift; - 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 % EmojiPanPerRow)) : _counts[c], rows = (size / EmojiPanPerRow) + ((size % EmojiPanPerRow) ? 1 : 0); - y += st::emojiPanHeader + (rows * st::emojiPanSize.height()); - } - y -= _picker.height() - st::buttonRadius + _top; - if (y < 0) { - y += _picker.height() - st::buttonRadius + st::emojiPanSize.height() - st::buttonRadius; - } - int xmax = width() - _picker.width(); - float64 coef = float64(sel % EmojiPanPerRow) / float64(EmojiPanPerRow - 1); - if (rtl()) coef = 1. - coef; - _picker.move(qRound(xmax * coef), y); - - _picker.showEmoji(_emojis[tab][sel]->code); - emit disableScroll(true); - } -} - -void EmojiPanInner::onPickerHidden() { - _pickerSel = -1; - update(); - emit disableScroll(false); - - _lastMousePos = QCursor::pos(); - updateSelected(); -} - -QRect EmojiPanInner::emojiRect(int tab, int sel) { - int x = 0, y = 0; - for (int i = 0; i < emojiTabCount; ++i) { - if (i == tab) { - int rows = (sel / EmojiPanPerRow); - y += st::emojiPanHeader + rows * st::emojiPanSize.height(); - x = st::emojiPanPadding + ((sel % EmojiPanPerRow) * st::emojiPanSize.width()); - break; - } else { - int cnt = _counts[i]; - int rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0); - y += st::emojiPanHeader + rows * st::emojiPanSize.height(); - } - } - return QRect(x, y, st::emojiPanSize.width(), st::emojiPanSize.height()); -} - -void EmojiPanInner::onColorSelected(EmojiPtr emoji) { - if (emoji->color) { - cRefEmojiVariants().insert(emoji->code, emojiKey(emoji)); - } - if (_pickerSel >= 0) { - int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift; - if (tab >= 0 && tab < emojiTabCount) { - _emojis[tab][sel] = emoji; - rtlupdate(emojiRect(tab, sel)); - } - } - selectEmoji(emoji); - _picker.hideStart(); -} - -void EmojiPanInner::mouseMoveEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - if (!_picker.isHidden()) { - if (_picker.rect().contains(_picker.mapFromGlobal(_lastMousePos))) { - return _picker.mouseMoveEvent(0); - } else { - _picker.clearSelection(); - } - } - updateSelected(); -} - -void EmojiPanInner::leaveEvent(QEvent *e) { - clearSelection(); -} - -void EmojiPanInner::leaveToChildEvent(QEvent *e, QWidget *child) { - clearSelection(); -} - -void EmojiPanInner::enterFromChildEvent(QEvent *e, QWidget *child) { - _lastMousePos = QCursor::pos(); - updateSelected(); -} - -void EmojiPanInner::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 / MatrixRowShift), sel = index % MatrixRowShift; - _hovers[tab][sel] = 0; - } - _animations.clear(); - if (_selected >= 0) { - int index = qAbs(_selected), tab = (index / MatrixRowShift), sel = index % MatrixRowShift; - _hovers[tab][sel] = 0; - } - if (_pressedSel >= 0) { - int index = qAbs(_pressedSel), tab = (index / MatrixRowShift), sel = index % MatrixRowShift; - _hovers[tab][sel] = 0; - } - _selected = _pressedSel = -1; - _a_selected.stop(); - } else { - updateSelected(); - } -} - -DBIEmojiTab EmojiPanInner::currentTab(int yOffset) const { - int y, ytill = 0; - 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 (yOffset < ytill) { - return emojiTabAtIndex(c); - } - } - return emojiTabAtIndex(emojiTabCount - 1); -} - -void EmojiPanInner::hideFinish() { - if (!_picker.isHidden()) { - _picker.hideStart(true); - _pickerSel = -1; - clearSelection(true); - } -} - -void EmojiPanInner::refreshRecent() { - clearSelection(true); - _counts[0] = emojiPackCount(dbietRecent); - if (_hovers[0].size() != _counts[0]) _hovers[0] = QVector(_counts[0], 0); - _emojis[0] = emojiPack(dbietRecent); - int32 h = countHeight(); - if (h != height()) { - resize(width(), h); - emit needRefreshPanels(); - } -} - -void EmojiPanInner::fillPanels(QVector &panels) { - if (_picker.parentWidget() != parentWidget()) { - _picker.setParent(parentWidget()); - } - for (int32 i = 0; i < panels.size(); ++i) { - panels.at(i)->hide(); - panels.at(i)->deleteLater(); - } - panels.clear(); - - int y = 0; - panels.reserve(emojiTabCount); - for (int c = 0; c < emojiTabCount; ++c) { - panels.push_back(new EmojiPanel(parentWidget(), lang(LangKey(lng_emoji_category0 + c)), Stickers::NoneSetId, true, y)); - connect(panels.back(), SIGNAL(mousePressed()), this, SLOT(checkPickerHide())); - int cnt = _counts[c], rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0); - panels.back()->show(); - y += st::emojiPanHeader + rows * st::emojiPanSize.height(); - } - _picker.raise(); -} - -void EmojiPanInner::refreshPanels(QVector &panels) { - if (panels.size() != emojiTabCount) return fillPanels(panels); - - int32 y = 0; - for (int c = 0; c < emojiTabCount; ++c) { - panels.at(c)->setWantedY(y); - int cnt = _counts[c], rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0); - y += st::emojiPanHeader + rows * st::emojiPanSize.height(); - } -} - -void EmojiPanInner::updateSelected() { - if (_pressedSel >= 0 || _pickerSel >= 0) return; - - int32 selIndex = -1; - QPoint p(mapFromGlobal(_lastMousePos)); - 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 * MatrixRowShift; - } - } - break; - } - } - - bool startanim = false; - int oldSel = _selected, newSel = selIndex; - - 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 (newSel >= 0 && !_picker.isHidden()) { - if (newSel != _pickerSel) { - _picker.hideStart(); - } else { - _picker.showStart(); - } - } - } - - _selected = selIndex; - if (startanim && !_a_selected.animating()) _a_selected.start(); -} - -void EmojiPanInner::step_selected(uint64 ms, bool timer) { - QRegion toUpdate; - for (Animations::iterator i = _animations.begin(); i != _animations.end();) { - int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift; - float64 dt = float64(ms - i.value()) / st::emojiPanDuration; - if (dt >= 1) { - _hovers[tab][sel] = (i.key() > 0) ? 1 : 0; - i = _animations.erase(i); - } else { - _hovers[tab][sel] = (i.key() > 0) ? dt : (1 - dt); - ++i; - } - toUpdate += emojiRect(tab, sel); - } - if (timer) rtlupdate(toUpdate.boundingRect()); - if (_animations.isEmpty()) _a_selected.stop(); -} - -void InlineCacheEntry::clearResults() { - for_const (const InlineBots::Result *result, results) { - delete result; - } - results.clear(); -} - -void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) { - clearSelection(true); - - refreshRecent(); - - int32 y = 0; - for (int c = 0; c < emojiTabCount; ++c) { - if (emojiTabAtIndex(c) == packIndex) break; - int rows = (_counts[c] / EmojiPanPerRow) + ((_counts[c] % EmojiPanPerRow) ? 1 : 0); - y += st::emojiPanHeader + rows * st::emojiPanSize.height(); - } - - emit scrollToY(y); - - _lastMousePos = QCursor::pos(); - - update(); -} - -StickerPanInner::StickerPanInner() : TWidget() -, _a_selected(animation(this, &StickerPanInner::step_selected)) -, _top(0) -, _showingSavedGifs(cShowingSavedGifs()) -, _showingInlineItems(_showingSavedGifs) -, _setGifCommand(false) -, _lastScrolled(0) -, _inlineWithThumb(false) -, _selected(-1) -, _pressedSel(-1) -, _settings(this, lang(lng_stickers_you_have)) -, _previewShown(false) { - setMaxHeight(st::emojiPanMaxHeight - st::rbEmoji.height); - - setMouseTracking(true); - setFocusPolicy(Qt::NoFocus); - setAttribute(Qt::WA_OpaquePaintEvent); - - connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); - connect(&_settings, SIGNAL(clicked()), this, SLOT(onSettings())); - - _previewTimer.setSingleShot(true); - connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); - - _updateInlineItems.setSingleShot(true); - connect(&_updateInlineItems, SIGNAL(timeout()), this, SLOT(onUpdateInlineItems())); -} - -void StickerPanInner::setMaxHeight(int32 h) { - _maxHeight = h; - resize(st::emojiPanWidth - st::emojiScroll.width, countHeight()); - _settings.moveToLeft((st::emojiPanWidth - _settings.width()) / 2, height() / 3); -} - -void StickerPanInner::setScrollTop(int top) { - if (top == _top) return; - - _lastScrolled = getms(); - _top = top; -} - -int32 StickerPanInner::countHeight(bool plain) { - int result = 0, minLastH = plain ? 0 : (_maxHeight - st::stickerPanPadding); - if (_showingInlineItems) { - result = st::emojiPanHeader; - if (_switchPmButton) { - result += _switchPmButton->height() + st::inlineResultsSkip; - } - for (int i = 0, l = _inlineRows.count(); i < l; ++i) { - result += _inlineRows.at(i).height; - } - } else { - for (int i = 0; i < _sets.size(); ++i) { - int cnt = _sets.at(i).pack.size(), rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0); - int h = st::emojiPanHeader + rows * st::stickerPanSize.height(); - if (i == _sets.size() - 1 && h < minLastH) h = minLastH; - result += h; - } - } - return qMax(minLastH, result) + st::stickerPanPadding; -} - -StickerPanInner::~StickerPanInner() { - clearInlineRows(true); - deleteUnusedGifLayouts(); - deleteUnusedInlineLayouts(); -} - -QRect StickerPanInner::stickerRect(int tab, int sel) { - int x = 0, y = 0; - for (int i = 0; i < _sets.size(); ++i) { - if (i == tab) { - int rows = (((sel >= _sets.at(i).pack.size()) ? (sel - _sets.at(i).pack.size()) : sel) / StickerPanPerRow); - y += st::emojiPanHeader + rows * st::stickerPanSize.height(); - x = st::stickerPanPadding + ((sel % StickerPanPerRow) * st::stickerPanSize.width()); - break; - } else { - int cnt = _sets.at(i).pack.size(); - int rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0); - y += st::emojiPanHeader + rows * st::stickerPanSize.height(); - } - } - return QRect(x, y, st::stickerPanSize.width(), st::stickerPanSize.height()); -} - -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); - - if (_showingInlineItems) { - paintInlineItems(p, r); - } else { - paintStickers(p, r); - } -} - -void StickerPanInner::paintInlineItems(Painter &p, const QRect &r) { - if (_inlineRows.isEmpty()) { - p.setFont(st::normalFont); - p.setPen(st::noContactsColor); - p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), lang(lng_inline_bot_no_results), style::al_center); - return; - } - InlineBots::Layout::PaintContext context(getms(), false, Ui::isLayerShown() || Ui::isMediaViewShown() || _previewShown, false); - - int top = st::emojiPanHeader; - if (_switchPmButton) { - top += _switchPmButton->height() + st::inlineResultsSkip; - } - - int fromx = rtl() ? (width() - r.x() - r.width()) : r.x(), tox = rtl() ? (width() - r.x()) : (r.x() + r.width()); - for (int row = 0, rows = _inlineRows.size(); row < rows; ++row) { - const InlineRow &inlineRow(_inlineRows.at(row)); - if (top >= r.top() + r.height()) break; - if (top + inlineRow.height > r.top()) { - int left = st::inlineResultsLeft; - if (row == rows - 1) context.lastRow = true; - for (int col = 0, cols = inlineRow.items.size(); col < cols; ++col) { - if (left >= tox) break; - - const InlineItem *item = inlineRow.items.at(col); - int w = item->width(); - if (left + w > fromx) { - p.translate(left, top); - item->paint(p, r.translated(-left, -top), &context); - p.translate(-left, -top); - } - left += w; - if (item->hasRightSkip()) { - left += st::inlineResultsSkip; - } - } - } - top += inlineRow.height; - } -} - -void StickerPanInner::paintStickers(Painter &p, const QRect &r) { - int32 fromcol = floorclamp(r.x() - st::stickerPanPadding, st::stickerPanSize.width(), 0, StickerPanPerRow); - int32 tocol = ceilclamp(r.x() + r.width() - st::stickerPanPadding, st::stickerPanSize.width(), 0, StickerPanPerRow); - if (rtl()) { - qSwap(fromcol, tocol); - fromcol = StickerPanPerRow - fromcol; - tocol = StickerPanPerRow - tocol; - } - - int32 y, tilly = 0; - for (int c = 0, l = _sets.size(); c < l; ++c) { - y = tilly; - int32 size = _sets.at(c).pack.size(); - int32 rows = (size / StickerPanPerRow) + ((size % StickerPanPerRow) ? 1 : 0); - tilly = y + st::emojiPanHeader + (rows * st::stickerPanSize.height()); - if (r.top() >= tilly) continue; - - bool special = (_sets[c].flags & MTPDstickerSet::Flag::f_official); - y += st::emojiPanHeader; - - int32 fromrow = floorclamp(r.y() - y, st::stickerPanSize.height(), 0, rows); - int32 torow = ceilclamp(r.y() + r.height() - y, st::stickerPanSize.height(), 0, rows); - for (int32 i = fromrow; i < torow; ++i) { - for (int32 j = fromcol; j < tocol; ++j) { - int32 index = i * StickerPanPerRow + j; - if (index >= size) break; - - float64 hover = _sets[c].hovers[index]; - - DocumentData *sticker = _sets[c].pack[index]; - if (!sticker->sticker()) continue; - - QPoint pos(st::stickerPanPadding + j * st::stickerPanSize.width(), y + i * st::stickerPanSize.height()); - if (hover > 0) { - p.setOpacity(hover); - QPoint tl(pos); - if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width()); - App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners); - p.setOpacity(1); - } - - bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); - if (goodThumb) { - sticker->thumb->load(); - } else { - sticker->checkSticker(); - } - - float64 coef = qMin((st::stickerPanSize.width() - st::buttonRadius * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::buttonRadius * 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 && _sets[c].id == Stickers::RecentSetId && _custom.at(index)) { - float64 xHover = _sets[c].hovers[_sets[c].pack.size() + index]; - - QPoint xPos = pos + QPoint(st::stickerPanSize.width() - st::stickerPanDelete.pxWidth(), 0); - p.setOpacity(hover * (xHover + (1 - xHover) * st::stickerPanDeleteOpacity)); - p.drawSpriteLeft(xPos, width(), st::stickerPanDelete); - p.setOpacity(1); - } - } - } - } -} - -void StickerPanInner::mousePressEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - updateSelected(); - - _pressedSel = _selected; - ClickHandler::pressed(); - _previewTimer.start(QApplication::startDragTime()); -} - -void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { - _previewTimer.stop(); - - int32 pressed = _pressedSel; - _pressedSel = -1; - - ClickHandlerPtr activated = ClickHandler::unpressed(); - - _lastMousePos = e->globalPos(); - updateSelected(); - - if (_previewShown) { - _previewShown = false; - return; - } - - if (_selected < 0 || _selected != pressed || (_showingInlineItems && !activated)) return; - if (_showingInlineItems) { - if (!dynamic_cast(activated.data())) { - App::activateClickHandler(activated, e->button()); - return; - } - int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; - if (row >= _inlineRows.size() || col >= _inlineRows.at(row).items.size()) { - return; - } - - InlineItem *item = _inlineRows.at(row).items.at(col); - if (PhotoData *photo = item->getPhoto()) { - if (photo->medium->loaded() || photo->thumb->loaded()) { - emit selected(photo); - } else if (!photo->medium->loading()) { - photo->thumb->loadEvenCancelled(); - photo->medium->loadEvenCancelled(); - } - } else if (DocumentData *document = item->getDocument()) { - if (document->loaded()) { - emit selected(document); - } else if (document->loading()) { - document->cancel(); - } else { - DocumentOpenClickHandler::doOpen(document, ActionOnLoadNone); - } - } else if (InlineResult *inlineResult = item->getResult()) { - if (inlineResult->onChoose(item)) { - emit selected(inlineResult, _inlineBot); - } - } - return; - } - if (_selected >= MatrixRowShift * _sets.size()) { - return; - } - - int tab = (_selected / MatrixRowShift), sel = _selected % MatrixRowShift; - if (_sets[tab].id == Stickers::RecentSetId && sel >= _sets[tab].pack.size() && sel < _sets[tab].pack.size() * 2 && _custom.at(sel - _sets[tab].pack.size())) { - clearSelection(true); - bool refresh = false; - DocumentData *sticker = _sets[tab].pack.at(sel - _sets[tab].pack.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 = true; - break; - } - } - auto &sets = Global::RefStickerSets(); - auto it = sets.find(Stickers::CustomSetId); - 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::writeInstalledStickers(); - refresh = true; - break; - } - } - } - if (refresh) { - refreshRecentStickers(); - updateSelected(); - update(); - } - return; - } - if (sel < _sets[tab].pack.size()) { - emit selected(_sets[tab].pack[sel]); - } -} - -void StickerPanInner::mouseMoveEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - updateSelected(); -} - -void StickerPanInner::leaveEvent(QEvent *e) { - clearSelection(); -} - -void StickerPanInner::leaveToChildEvent(QEvent *e, QWidget *child) { - clearSelection(); -} - -void StickerPanInner::enterFromChildEvent(QEvent *e, QWidget *child) { - _lastMousePos = QCursor::pos(); - updateSelected(); -} - -bool StickerPanInner::showSectionIcons() const { - return !inlineResultsShown(); -} - -void StickerPanInner::clearSelection(bool fast) { - _lastMousePos = mapToGlobal(QPoint(-10, -10)); - if (fast) { - if (_showingInlineItems) { - if (_selected >= 0) { - int32 srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; - t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); - ClickHandler::clearActive(_inlineRows.at(srow).items.at(scol)); - setCursor(style::cur_default); - } - _selected = _pressedSel = -1; - return; - } - for (Animations::const_iterator i = _animations.cbegin(); i != _animations.cend(); ++i) { - int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift; - _sets[tab].hovers[sel] = 0; - } - _animations.clear(); - if (_selected >= 0) { - int index = qAbs(_selected), tab = (index / MatrixRowShift), sel = index % MatrixRowShift; - if (index >= 0 && tab < _sets.size() && _sets[tab].id == Stickers::RecentSetId && sel >= tab * MatrixRowShift + _sets[tab].pack.size()) { - _sets[tab].hovers[sel] = 0; - sel -= _sets[tab].pack.size(); - } - _sets[tab].hovers[sel] = 0; - } - if (_pressedSel >= 0) { - int index = qAbs(_pressedSel), tab = (index / MatrixRowShift), sel = index % MatrixRowShift; - if (index >= 0 && tab < _sets.size() && _sets[tab].id == Stickers::RecentSetId && sel >= tab * MatrixRowShift + _sets[tab].pack.size()) { - _sets[tab].hovers[sel] = 0; - sel -= _sets[tab].pack.size(); - } - _sets[tab].hovers[sel] = 0; - } - _selected = _pressedSel = -1; - _a_selected.stop(); - } else { - updateSelected(); - } -} - -void StickerPanInner::hideFinish(bool completely) { - if (completely) { - auto itemForget = [](const InlineItem *item) { - if (DocumentData *document = item->getDocument()) { - document->forget(); - } - if (PhotoData *photo = item->getPhoto()) { - photo->forget(); - } - if (InlineResult *result = item->getResult()) { - result->forget(); - } - }; - clearInlineRows(false); - for_const (InlineItem *item, _gifLayouts) { - itemForget(item); - } - for_const (InlineItem *item, _inlineLayouts) { - itemForget(item); - } - } - if (_setGifCommand && _showingSavedGifs) { - App::insertBotCommand(qsl(""), true); - } - _setGifCommand = false; -} - -void StickerPanInner::refreshStickers() { - clearSelection(true); - - _sets.clear(); - _sets.reserve(Global::StickerSetsOrder().size() + 1); - - refreshRecentStickers(false); - for (auto i = Global::StickerSetsOrder().cbegin(), e = Global::StickerSetsOrder().cend(); i != e; ++i) { - appendSet(*i); - } - - if (_showingInlineItems) { - _settings.hide(); - } else { - int32 h = countHeight(); - if (h != height()) resize(width(), h); - - _settings.setVisible(_sets.isEmpty()); - } - - - emit refreshIcons(); - - updateSelected(); -} - -bool StickerPanInner::inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth) { - InlineItem *layout = nullptr; - if (savedGif) { - layout = layoutPrepareSavedGif(savedGif, (_inlineRows.size() * MatrixRowShift) + row.items.size()); - } else if (result) { - layout = layoutPrepareInlineResult(result, (_inlineRows.size() * MatrixRowShift) + row.items.size()); - } - if (!layout) return false; - - layout->preload(); - if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { - layout->setPosition(_inlineRows.size() * MatrixRowShift); - } - - sumWidth += layout->maxWidth(); - if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { - sumWidth += st::inlineResultsSkip; - } - - row.items.push_back(layout); - return true; -} - -bool StickerPanInner::inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force) { - if (row.items.isEmpty()) return false; - - bool full = (row.items.size() >= InlineItemsMaxPerRow); - bool big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft); - if (full || big || force) { - _inlineRows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); - row = InlineRow(); - row.items.reserve(InlineItemsMaxPerRow); - sumWidth = 0; - return true; - } - return false; -} - -void StickerPanInner::refreshSavedGifs() { - if (_showingSavedGifs) { - _settings.hide(); - clearInlineRows(false); - if (_showingInlineItems) { - const SavedGifs &saved(cSavedGifs()); - if (saved.isEmpty()) { - showStickerSet(Stickers::RecentSetId); - return; - } else { - _inlineRows.reserve(saved.size()); - InlineRow row; - row.items.reserve(InlineItemsMaxPerRow); - int32 sumWidth = 0; - for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) { - inlineRowsAddItem(*i, 0, row, sumWidth); - } - inlineRowFinalize(row, sumWidth, true); - } - deleteUnusedGifLayouts(); - - int32 h = countHeight(); - if (h != height()) resize(width(), h); - - update(); - } - } - emit refreshIcons(); - - updateSelected(); -} - -void StickerPanInner::inlineBotChanged() { - _setGifCommand = false; - refreshInlineRows(nullptr, nullptr, true); -} - -void StickerPanInner::clearInlineRows(bool resultsDeleted) { - if (resultsDeleted) { - if (_showingInlineItems) { - _selected = _pressedSel = -1; - } - } else { - if (_showingInlineItems) { - clearSelection(true); - } - for (InlineRows::const_iterator i = _inlineRows.cbegin(), e = _inlineRows.cend(); i != e; ++i) { - for (InlineItems::const_iterator j = i->items.cbegin(), end = i->items.cend(); j != end; ++j) { - (*j)->setPosition(-1); - } - } - } - _inlineRows.clear(); -} - -InlineItem *StickerPanInner::layoutPrepareSavedGif(DocumentData *doc, int32 position) { - auto i = _gifLayouts.constFind(doc); - if (i == _gifLayouts.cend()) { - if (auto layout = InlineItem::createLayoutGif(doc)) { - i = _gifLayouts.insert(doc, layout.release()); - i.value()->initDimensions(); - } else { - return nullptr; - } - } - if (!i.value()->maxWidth()) return nullptr; - - i.value()->setPosition(position); - return i.value(); -} - -InlineItem *StickerPanInner::layoutPrepareInlineResult(InlineResult *result, int32 position) { - auto i = _inlineLayouts.constFind(result); - if (i == _inlineLayouts.cend()) { - if (auto layout = InlineItem::createLayout(result, _inlineWithThumb)) { - i = _inlineLayouts.insert(result, layout.release()); - i.value()->initDimensions(); - } else { - return nullptr; - } - } - if (!i.value()->maxWidth()) return nullptr; - - i.value()->setPosition(position); - return i.value(); -} - -void StickerPanInner::deleteUnusedGifLayouts() { - if (_inlineRows.isEmpty() || !_showingSavedGifs) { // delete all - for_const (const InlineItem *item, _gifLayouts) { - delete item; - } - _gifLayouts.clear(); - } else { - for (GifLayouts::iterator i = _gifLayouts.begin(); i != _gifLayouts.cend();) { - if (i.value()->position() < 0) { - delete i.value(); - i = _gifLayouts.erase(i); - } else { - ++i; - } - } - } -} - -void StickerPanInner::deleteUnusedInlineLayouts() { - if (_inlineRows.isEmpty() || _showingSavedGifs) { // delete all - for_const (const InlineItem *item, _inlineLayouts) { - delete item; - } - _inlineLayouts.clear(); - } else { - for (InlineLayouts::iterator i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { - if (i.value()->position() < 0) { - delete i.value(); - i = _inlineLayouts.erase(i); - } else { - ++i; - } - } - } -} - -StickerPanInner::InlineRow &StickerPanInner::layoutInlineRow(InlineRow &row, int32 sumWidth) { - int32 count = row.items.size(); - t_assert(count <= InlineItemsMaxPerRow); - - // enumerate items in the order of growing maxWidth() - // for that sort item indices by maxWidth() - int indices[InlineItemsMaxPerRow]; - for (int i = 0; i < count; ++i) { - indices[i] = i; - } - std::sort(indices, indices + count, [&row](int a, int b) -> bool { - return row.items.at(a)->maxWidth() < row.items.at(b)->maxWidth(); - }); - - row.height = 0; - int availw = width() - st::inlineResultsLeft; - for (int i = 0; i < count; ++i) { - int index = indices[i]; - int w = sumWidth ? (row.items.at(index)->maxWidth() * availw / sumWidth) : row.items.at(index)->maxWidth(); - int actualw = qMax(w, int(st::inlineResultsMinWidth)); - row.height = qMax(row.height, row.items.at(index)->resizeGetHeight(actualw)); - if (sumWidth) { - availw -= actualw; - sumWidth -= row.items.at(index)->maxWidth(); - if (index > 0 && row.items.at(index - 1)->hasRightSkip()) { - availw -= st::inlineResultsSkip; - sumWidth -= st::inlineResultsSkip; - } - } - } - return row; -} - -void StickerPanInner::preloadImages() { - if (_showingInlineItems) { - for (int32 row = 0, rows = _inlineRows.size(); row < rows; ++row) { - for (int32 col = 0, cols = _inlineRows.at(row).items.size(); col < cols; ++col) { - _inlineRows.at(row).items.at(col)->preload(); - } - } - return; - } - - for (int32 i = 0, l = _sets.size(), k = 0; i < l; ++i) { - for (int32 j = 0, n = _sets.at(i).pack.size(); j < n; ++j) { - if (++k > StickerPanPerRow * (StickerPanPerRow + 1)) break; - - DocumentData *sticker = _sets.at(i).pack.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 { - sticker->automaticLoad(0); - } - } - if (k > StickerPanPerRow * (StickerPanPerRow + 1)) break; - } -} - -uint64 StickerPanInner::currentSet(int yOffset) const { - if (_showingInlineItems) return Stickers::NoneSetId; - - int y, ytill = 0; - for (int i = 0, l = _sets.size(); i < l; ++i) { - int cnt = _sets.at(i).pack.size(); - y = ytill; - ytill = y + st::emojiPanHeader + ((cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0)) * st::stickerPanSize.height(); - if (yOffset < ytill) { - return _sets.at(i).id; - } - } - return _sets.isEmpty() ? Stickers::RecentSetId : _sets.back().id; -} - -void StickerPanInner::hideInlineRowsPanel() { - clearInlineRows(false); - if (_showingInlineItems) { - _showingSavedGifs = cShowingSavedGifs(); - if (_showingSavedGifs) { - refreshSavedGifs(); - emit scrollToY(0); - emit scrollUpdated(); - } else { - showStickerSet(Stickers::RecentSetId); - } - } -} - -void StickerPanInner::clearInlineRowsPanel() { - clearInlineRows(false); -} - -void StickerPanInner::refreshSwitchPmButton(const InlineCacheEntry *entry) { - if (!entry || entry->switchPmText.isEmpty()) { - _switchPmButton.reset(); - _switchPmStartToken.clear(); - } else { - if (!_switchPmButton) { - _switchPmButton = std_::make_unique(this, QString(), st::switchPmButton); - _switchPmButton->show(); - _switchPmButton->move(st::inlineResultsLeft, st::emojiPanHeader); - connect(_switchPmButton.get(), SIGNAL(clicked()), this, SLOT(onSwitchPm())); - } - _switchPmButton->setText(entry->switchPmText); // doesn't perform text.toUpper() - _switchPmStartToken = entry->switchPmStartToken; - } - update(); -} - -int StickerPanInner::refreshInlineRows(UserData *bot, const InlineCacheEntry *entry, bool resultsDeleted) { - _inlineBot = bot; - refreshSwitchPmButton(entry); - auto clearResults = [this, entry]() { - if (!entry) { - return true; - } - if (entry->results.isEmpty() && entry->switchPmText.isEmpty()) { - if (!_inlineBot || _inlineBot->username != cInlineGifBotUsername()) { - return true; - } - } - return false; - }; - if (clearResults()) { - if (resultsDeleted) { - clearInlineRows(true); - deleteUnusedInlineLayouts(); - } - emit emptyInlineRows(); - return 0; - } - - clearSelection(true); - - t_assert(_inlineBot != 0); - _inlineBotTitle = lng_inline_bot_results(lt_inline_bot, _inlineBot->username.isEmpty() ? _inlineBot->name : ('@' + _inlineBot->username)); - - _showingInlineItems = true; - _showingSavedGifs = false; - _settings.hide(); - - int32 count = entry->results.size(), from = validateExistingInlineRows(entry->results), added = 0; - - if (count) { - _inlineRows.reserve(count); - InlineRow row; - row.items.reserve(InlineItemsMaxPerRow); - int32 sumWidth = 0; - for (int32 i = from; i < count; ++i) { - if (inlineRowsAddItem(0, entry->results.at(i), row, sumWidth)) { - ++added; - } - } - inlineRowFinalize(row, sumWidth, true); - } - - int32 h = countHeight(); - if (h != height()) resize(width(), h); - update(); - - emit refreshIcons(); - - _lastMousePos = QCursor::pos(); - updateSelected(); - - return added; -} - -int32 StickerPanInner::validateExistingInlineRows(const InlineResults &results) { - int32 count = results.size(), until = 0, untilrow = 0, untilcol = 0; - for (; until < count;) { - if (untilrow >= _inlineRows.size() || _inlineRows.at(untilrow).items.at(untilcol)->getResult() != results.at(until)) { - break; - } - ++until; - if (++untilcol == _inlineRows.at(untilrow).items.size()) { - ++untilrow; - untilcol = 0; - } - } - if (until == count) { // all items are layed out - if (untilrow == _inlineRows.size()) { // nothing changed - return until; - } - - for (int32 i = untilrow, l = _inlineRows.size(), skip = untilcol; i < l; ++i) { - for (int32 j = 0, s = _inlineRows.at(i).items.size(); j < s; ++j) { - if (skip) { - --skip; - } else { - _inlineRows.at(i).items.at(j)->setPosition(-1); - } - } - } - if (!untilcol) { // all good rows are filled - _inlineRows.resize(untilrow); - return until; - } - _inlineRows.resize(untilrow + 1); - _inlineRows[untilrow].items.resize(untilcol); - _inlineRows[untilrow] = layoutInlineRow(_inlineRows[untilrow]); - return until; - } - if (untilrow && !untilcol) { // remove last row, maybe it is not full - --untilrow; - untilcol = _inlineRows.at(untilrow).items.size(); - } - until -= untilcol; - - for (int32 i = untilrow, l = _inlineRows.size(); i < l; ++i) { - for (int32 j = 0, s = _inlineRows.at(i).items.size(); j < s; ++j) { - _inlineRows.at(i).items.at(j)->setPosition(-1); - } - } - _inlineRows.resize(untilrow); - - if (_inlineRows.isEmpty()) { - _inlineWithThumb = false; - for (int32 i = until; i < count; ++i) { - if (results.at(i)->hasThumbDisplay()) { - _inlineWithThumb = true; - break; - } - } - } - return until; -} - -void StickerPanInner::notify_inlineItemLayoutChanged(const InlineItem *layout) { - if (_selected < 0 || !_showingInlineItems) { - return; - } - - int32 row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; - if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size()) { - if (layout == _inlineRows.at(row).items.at(col)) { - updateSelected(); - } - } -} - -void StickerPanInner::ui_repaintInlineItem(const InlineItem *layout) { - uint64 ms = getms(); - if (_lastScrolled + 100 <= ms) { - update(); - } else { - _updateInlineItems.start(_lastScrolled + 100 - ms); - } -} - -bool StickerPanInner::ui_isInlineItemVisible(const InlineItem *layout) { - int32 position = layout->position(); - if (!_showingInlineItems || position < 0) return false; - - int32 row = position / MatrixRowShift, col = position % MatrixRowShift; - t_assert((row < _inlineRows.size()) && (col < _inlineRows.at(row).items.size())); - - const InlineItems &inlineItems(_inlineRows.at(row).items); - int32 top = st::emojiPanHeader; - for (int32 i = 0; i < row; ++i) { - top += _inlineRows.at(i).height; - } - - return (top < _top + _maxHeight) && (top + _inlineRows.at(row).items.at(col)->height() > _top); -} - -bool StickerPanInner::ui_isInlineItemBeingChosen() { - return _showingInlineItems; -} - -void StickerPanInner::appendSet(uint64 setId) { - auto &sets = Global::StickerSets(); - auto it = sets.constFind(setId); - if (it == sets.cend() || (it->flags & MTPDstickerSet::Flag::f_archived) || 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)); - } - _sets.push_back(DisplayedSet(it->id, it->flags, it->title, pack.size() + 1, pack)); -} - -void StickerPanInner::refreshRecent() { - if (_showingInlineItems) { - if (_showingSavedGifs) { - refreshSavedGifs(); - } - } else { - refreshRecentStickers(); - } -} - -void StickerPanInner::refreshRecentStickers(bool performResize) { - _custom.clear(); - clearSelection(true); - auto &sets = Global::StickerSets(); - auto &recent = cGetRecentStickers(); - auto customIt = sets.constFind(Stickers::CustomSetId); - auto cloudIt = sets.constFind(Stickers::CloudRecentSetId); - if (recent.isEmpty() - && (customIt == sets.cend() || customIt->stickers.isEmpty()) - && (cloudIt == sets.cend() || cloudIt->stickers.isEmpty())) { - if (!_sets.isEmpty() && _sets.at(0).id == Stickers::RecentSetId) { - _sets.pop_front(); - } - } else { - StickerPack recentPack; - int customCnt = (customIt == sets.cend()) ? 0 : customIt->stickers.size(); - int cloudCnt = (cloudIt == sets.cend()) ? 0 : cloudIt->stickers.size(); - recentPack.reserve(cloudCnt + recent.size() + customCnt); - _custom.reserve(cloudCnt + recent.size() + customCnt); - if (cloudCnt > 0) { - for_const (auto sticker, cloudIt->stickers) { - recentPack.push_back(sticker); - _custom.push_back(false); - } - } - for_const (auto &recentSticker, recent) { - auto sticker = recentSticker.first; - recentPack.push_back(sticker); - _custom.push_back(false); - } - if (customCnt > 0) { - for_const (auto &sticker, customIt->stickers) { - auto index = recentPack.indexOf(sticker); - if (index >= cloudCnt) { - _custom[index] = true; // mark stickers from recent as custom - } else { - recentPack.push_back(sticker); - _custom.push_back(true); - } - } - } - if (_sets.isEmpty() || _sets.at(0).id != Stickers::RecentSetId) { - _sets.push_back(DisplayedSet(Stickers::RecentSetId, MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special, lang(lng_recent_stickers), recentPack.size() * 2, recentPack)); - } else { - _sets[0].pack = recentPack; - _sets[0].hovers.resize(recentPack.size() * 2); - } - } - - if (performResize && !_showingInlineItems) { - int32 h = countHeight(); - if (h != height()) { - resize(width(), h); - emit needRefreshPanels(); - } - - updateSelected(); - } -} - -void StickerPanInner::fillIcons(QList &icons) { - icons.clear(); - icons.reserve(_sets.size() + 1); - if (!cSavedGifs().isEmpty()) icons.push_back(StickerIcon(Stickers::NoneSetId)); - - if (_sets.isEmpty()) return; - int32 i = 0; - if (_sets.at(0).id == Stickers::RecentSetId) ++i; - if (i > 0) icons.push_back(StickerIcon(Stickers::RecentSetId)); - for (int32 l = _sets.size(); i < l; ++i) { - DocumentData *s = _sets.at(i).pack.at(0); - int32 availw = st::rbEmoji.width - 2 * st::stickerIconPadding, availh = st::rbEmoji.height - 2 * st::stickerIconPadding; - int32 thumbw = s->thumb->width(), thumbh = s->thumb->height(), pixw = 1, pixh = 1; - if (availw * thumbh > availh * thumbw) { - pixh = availh; - pixw = (pixh * thumbw) / thumbh; - } else { - pixw = availw; - pixh = thumbw ? ((pixw * thumbh) / thumbw) : 1; - } - if (pixw < 1) pixw = 1; - if (pixh < 1) pixh = 1; - icons.push_back(StickerIcon(_sets.at(i).id, s, pixw, pixh)); - } -} - -void StickerPanInner::fillPanels(QVector &panels) { - for (int32 i = 0; i < panels.size(); ++i) { - panels.at(i)->hide(); - panels.at(i)->deleteLater(); - } - panels.clear(); - - if (_showingInlineItems) { - panels.push_back(new EmojiPanel(parentWidget(), _showingSavedGifs ? lang(lng_saved_gifs) : _inlineBotTitle, Stickers::NoneSetId, true, 0)); - panels.back()->show(); - return; - } - - if (_sets.isEmpty()) return; - - int y = 0; - panels.reserve(_sets.size()); - for (int32 i = 0, l = _sets.size(); i < l; ++i) { - bool special = (_sets.at(i).flags & MTPDstickerSet::Flag::f_official); - panels.push_back(new EmojiPanel(parentWidget(), _sets.at(i).title, _sets.at(i).id, special, y)); - panels.back()->show(); - connect(panels.back(), SIGNAL(deleteClicked(quint64)), this, SIGNAL(removing(quint64))); - int cnt = _sets.at(i).pack.size(), rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0); - int h = st::emojiPanHeader + rows * st::stickerPanSize.height(); - y += h; - } -} - -void StickerPanInner::refreshPanels(QVector &panels) { - if (_showingInlineItems) return; - - if (panels.size() != _sets.size()) return fillPanels(panels); - - int32 y = 0; - for (int32 i = 0, l = _sets.size(); i < l; ++i) { - panels.at(i)->setWantedY(y); - int cnt = _sets.at(i).pack.size(), rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0); - int h = st::emojiPanHeader + rows * st::stickerPanSize.height(); - y += h; - } -} - -void StickerPanInner::updateSelected() { - if (_pressedSel >= 0 && !_previewShown) { - return; - } - - int32 selIndex = -1; - QPoint p(mapFromGlobal(_lastMousePos)); - - if (_showingInlineItems) { - int sx = (rtl() ? width() - p.x() : p.x()) - st::inlineResultsLeft; - int sy = p.y() - st::emojiPanHeader; - if (_switchPmButton) { - sy -= _switchPmButton->height() + st::inlineResultsSkip; - } - int row = -1, col = -1, sel = -1; - ClickHandlerPtr lnk; - ClickHandlerHost *lnkhost = nullptr; - HistoryCursorState cursor = HistoryDefaultCursorState; - if (sy >= 0) { - row = 0; - for (int rows = _inlineRows.size(); row < rows; ++row) { - if (sy < _inlineRows.at(row).height) { - break; - } - sy -= _inlineRows.at(row).height; - } - } - if (sx >= 0 && row >= 0 && row < _inlineRows.size()) { - const InlineItems &inlineItems(_inlineRows.at(row).items); - col = 0; - for (int32 cols = inlineItems.size(); col < cols; ++col) { - int32 width = inlineItems.at(col)->width(); - if (sx < width) { - break; - } - sx -= width; - if (inlineItems.at(col)->hasRightSkip()) { - sx -= st::inlineResultsSkip; - } - } - if (col < inlineItems.size()) { - sel = row * MatrixRowShift + col; - inlineItems.at(col)->getState(lnk, cursor, sx, sy); - lnkhost = inlineItems.at(col); - } else { - row = col = -1; - } - } else { - row = col = -1; - } - int32 srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; - int32 scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; - if (_selected != sel) { - if (srow >= 0 && scol >= 0) { - t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); - Ui::repaintInlineItem(_inlineRows.at(srow).items.at(scol)); - } - _selected = sel; - if (row >= 0 && col >= 0) { - t_assert(row >= 0 && row < _inlineRows.size() && col >= 0 && col < _inlineRows.at(row).items.size()); - Ui::repaintInlineItem(_inlineRows.at(row).items.at(col)); - } - if (_pressedSel >= 0 && _selected >= 0 && _pressedSel != _selected) { - _pressedSel = _selected; - if (row >= 0 && col >= 0) { - auto layout = _inlineRows.at(row).items.at(col); - if (auto previewDocument = layout->getPreviewDocument()) { - Ui::showMediaPreview(previewDocument); - } else if (auto previewPhoto = layout->getPreviewPhoto()) { - Ui::showMediaPreview(previewPhoto); - } - } - } - } - if (ClickHandler::setActive(lnk, lnkhost)) { - setCursor(lnk ? style::cur_pointer : style::cur_default); - } - return; - } - - int y, ytill = 0, sx = (rtl() ? width() - p.x() : p.x()) - st::stickerPanPadding; - for (int c = 0, l = _sets.size(); c < l; ++c) { - const DisplayedSet &set(_sets.at(c)); - int cnt = set.pack.size(); - bool special = (set.flags & MTPDstickerSet::Flag::f_official); - - y = ytill; - ytill = y + st::emojiPanHeader + ((cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0)) * st::stickerPanSize.height(); - if (p.y() >= y && p.y() < ytill) { - 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 >= set.pack.size()) { - selIndex = -1; - } else { - if (set.id == Stickers::RecentSetId && _custom[selIndex]) { - 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 += set.pack.size(); - } - } - selIndex += c * MatrixRowShift; - } - } - break; - } - } - - bool startanim = false; - int oldSel = _selected, oldSelTab = oldSel / MatrixRowShift, xOldSel = -1, newSel = selIndex, newSelTab = newSel / MatrixRowShift, xNewSel = -1; - if (oldSel >= 0 && oldSelTab < _sets.size() && _sets[oldSelTab].id == Stickers::RecentSetId && oldSel >= oldSelTab * MatrixRowShift + _sets[oldSelTab].pack.size()) { - xOldSel = oldSel; - oldSel -= _sets[oldSelTab].pack.size(); - } - if (newSel >= 0 && newSelTab < _sets.size() && _sets[newSelTab].id == Stickers::RecentSetId && newSel >= newSelTab * MatrixRowShift + _sets[newSelTab].pack.size()) { - xNewSel = newSel; - newSel -= _sets[newSelTab].pack.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 (_pressedSel >= 0 && _selected >= 0 && _pressedSel != _selected) { - _pressedSel = _selected; - if (newSel >= 0 && xNewSel < 0) { - Ui::showMediaPreview(_sets.at(newSelTab).pack.at(newSel % MatrixRowShift)); - } - } - if (startanim && !_a_selected.animating()) _a_selected.start(); -} - -void StickerPanInner::onSettings() { - Ui::showLayer(new StickersBox()); -} - -void StickerPanInner::onPreview() { - if (_pressedSel < 0) return; - if (_showingInlineItems) { - int32 row = _pressedSel / MatrixRowShift, col = _pressedSel % MatrixRowShift; - if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size()) { - auto layout = _inlineRows.at(row).items.at(col); - if (auto previewDocument = layout->getPreviewDocument()) { - Ui::showMediaPreview(previewDocument); - _previewShown = true; - } else if (auto previewPhoto = layout->getPreviewPhoto()) { - Ui::showMediaPreview(previewPhoto); - _previewShown = true; - } - } - } else if (_pressedSel < MatrixRowShift * _sets.size()) { - int tab = (_pressedSel / MatrixRowShift), sel = _pressedSel % MatrixRowShift; - if (sel < _sets.at(tab).pack.size()) { - Ui::showMediaPreview(_sets.at(tab).pack.at(sel)); - _previewShown = true; - } - } -} - -void StickerPanInner::onUpdateInlineItems() { - if (!_showingInlineItems) return; - - uint64 ms = getms(); - if (_lastScrolled + 100 <= ms) { - update(); - } else { - _updateInlineItems.start(_lastScrolled + 100 - ms); - } -} - -void StickerPanInner::onSwitchPm() { - if (_inlineBot && _inlineBot->botInfo) { - _inlineBot->botInfo->startToken = _switchPmStartToken; - Ui::showPeerHistory(_inlineBot, ShowAndStartBotMsgId); - } -} - -void StickerPanInner::step_selected(uint64 ms, bool timer) { - QRegion toUpdate; - for (Animations::iterator i = _animations.begin(); i != _animations.end();) { - int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift; - float64 dt = float64(ms - i.value()) / st::emojiPanDuration; - if (dt >= 1) { - _sets[tab].hovers[sel] = (i.key() > 0) ? 1 : 0; - i = _animations.erase(i); - } else { - _sets[tab].hovers[sel] = (i.key() > 0) ? dt : (1 - dt); - ++i; - } - toUpdate += stickerRect(tab, sel); - } - if (timer) rtlupdate(toUpdate.boundingRect()); - if (_animations.isEmpty()) _a_selected.stop(); -} - -void StickerPanInner::showStickerSet(uint64 setId) { - clearSelection(true); - - if (setId == Stickers::NoneSetId) { - bool wasNotShowingGifs = !_showingInlineItems; - if (wasNotShowingGifs) { - _showingInlineItems = true; - _showingSavedGifs = true; - cSetShowingSavedGifs(true); - emit saveConfigDelayed(SaveRecentEmojisTimeout); - } - refreshSavedGifs(); - emit scrollToY(0); - emit scrollUpdated(); - showFinish(); - return; - } - - if (_showingInlineItems) { - if (_setGifCommand && _showingSavedGifs) { - App::insertBotCommand(qsl(""), true); - } - _setGifCommand = false; - - _showingInlineItems = false; - cSetShowingSavedGifs(false); - emit saveConfigDelayed(SaveRecentEmojisTimeout); - - Notify::clipStopperHidden(ClipStopperSavedGifsPanel); - - refreshRecentStickers(true); - emit refreshIcons(); - } - - int32 y = 0; - for (int c = 0; c < _sets.size(); ++c) { - if (_sets.at(c).id == setId) break; - int rows = (_sets[c].pack.size() / StickerPanPerRow) + ((_sets[c].pack.size() % StickerPanPerRow) ? 1 : 0); - y += st::emojiPanHeader + rows * st::stickerPanSize.height(); - } - - emit scrollToY(y); - emit scrollUpdated(); - - _lastMousePos = QCursor::pos(); - - update(); -} - -void StickerPanInner::updateShowingSavedGifs() { - if (cShowingSavedGifs()) { - if (!_showingInlineItems) { - clearSelection(true); - _showingSavedGifs = _showingInlineItems = true; - if (_inlineRows.isEmpty()) refreshSavedGifs(); - } - } else if (!_showingInlineItems) { - clearSelection(true); - _showingSavedGifs = false; - } -} - -void StickerPanInner::showFinish() { - if (_showingInlineItems && _showingSavedGifs) { - _setGifCommand = App::insertBotCommand('@' + cInlineGifBotUsername(), true); - } -} - -EmojiPanel::EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY) : TWidget(parent) -, _wantedY(wantedY) -, _setId(setId) -, _special(special) -, _deleteVisible(false) -, _delete(special ? 0 : new IconedButton(this, st::notifyClose)) { // Stickers::NoneSetId if in emoji - resize(st::emojiPanWidth, st::emojiPanHeader); - setMouseTracking(true); - setFocusPolicy(Qt::NoFocus); - setText(text); - if (_delete) { - _delete->hide(); - _delete->moveToRight(st::emojiPanHeaderLeft - ((_delete->width() - st::notifyClose.icon.pxWidth()) / 2), (st::emojiPanHeader - _delete->height()) / 2, width()); - connect(_delete, SIGNAL(clicked()), this, SLOT(onDelete())); - } -} - -void EmojiPanel::onDelete() { - emit deleteClicked(_setId); -} - -void EmojiPanel::setText(const QString &text) { - _fullText = text; - updateText(); -} - -void EmojiPanel::updateText() { - int32 availw = st::emojiPanWidth - st::emojiPanHeaderLeft * 2; - if (_deleteVisible) { - if (!_special && _setId != Stickers::NoneSetId) { - availw -= st::notifyClose.icon.pxWidth() + st::emojiPanHeaderLeft; - } - } else { - auto switchText = ([this]() { - if (_setId != Stickers::NoneSetId) { - return lang(lng_switch_emoji); - } - if (cSavedGifs().isEmpty()) { - return lang(lng_switch_stickers); - } - return lang(lng_switch_stickers_gifs); - })(); - availw -= st::emojiSwitchSkip + st::emojiPanHeaderFont->width(switchText); - } - _text = st::emojiPanHeaderFont->elided(_fullText, availw); - update(); -} - -void EmojiPanel::setDeleteVisible(bool isVisible) { - if (_deleteVisible != isVisible) { - _deleteVisible = isVisible; - updateText(); - if (_delete) { - _delete->setVisible(_deleteVisible); - } - } -} - -void EmojiPanel::mousePressEvent(QMouseEvent *e) { - emit mousePressed(); -} - -void EmojiPanel::paintEvent(QPaintEvent *e) { - Painter p(this); - - if (!_deleteVisible) { - p.fillRect(0, 0, width(), st::emojiPanHeader, st::emojiPanHeaderBg->b); - } - p.setFont(st::emojiPanHeaderFont->f); - p.setPen(st::emojiPanHeaderColor->p); - p.drawTextLeft(st::emojiPanHeaderLeft, st::emojiPanHeaderTop, width(), _text); -} - -EmojiSwitchButton::EmojiSwitchButton(QWidget *parent, bool toStickers) : Button(parent) -, _toStickers(toStickers) { - setCursor(style::cur_pointer); - updateText(); -} - -void EmojiSwitchButton::updateText(const QString &inlineBotUsername) { - if (_toStickers) { - if (inlineBotUsername.isEmpty()) { - _text = lang(cSavedGifs().isEmpty() ? lng_switch_stickers : lng_switch_stickers_gifs); - } else { - _text = '@' + inlineBotUsername; - } - } else { - _text = lang(lng_switch_emoji); - } - _textWidth = st::emojiPanHeaderFont->width(_text); - if (_toStickers && !inlineBotUsername.isEmpty()) { - int32 maxw = 0; - for (int c = 0; c < emojiTabCount; ++c) { - maxw = qMax(maxw, st::emojiPanHeaderFont->width(lang(LangKey(lng_emoji_category0 + c)))); - } - maxw += st::emojiPanHeaderLeft + st::emojiSwitchSkip + (st::emojiSwitchSkip - st::emojiSwitchImgSkip); - if (_textWidth > st::emojiPanWidth - maxw) { - _text = st::emojiPanHeaderFont->elided(_text, st::emojiPanWidth - maxw); - _textWidth = st::emojiPanHeaderFont->width(_text); - } - } - - int32 w = st::emojiSwitchSkip + _textWidth + (st::emojiSwitchSkip - st::emojiSwitchImgSkip); - resize(w, st::emojiPanHeader); -} - -void EmojiSwitchButton::paintEvent(QPaintEvent *e) { - Painter p(this); - - p.setFont(st::emojiPanHeaderFont->f); - p.setPen(st::emojiSwitchColor->p); - if (_toStickers) { - p.drawTextRight(st::emojiSwitchSkip, st::emojiPanHeaderTop, width(), _text, _textWidth); - p.drawSpriteRight(QPoint(st::emojiSwitchImgSkip - st::emojiSwitchStickers.pxWidth(), (st::emojiPanHeader - st::emojiSwitchStickers.pxHeight()) / 2), width(), st::emojiSwitchStickers); - } else { - p.drawTextRight(st::emojiSwitchImgSkip - st::emojiSwitchEmoji.pxWidth(), st::emojiPanHeaderTop, width(), lang(lng_switch_emoji), _textWidth); - p.drawSpriteRight(QPoint(st::emojiSwitchSkip + _textWidth - st::emojiSwitchEmoji.pxWidth(), (st::emojiPanHeader - st::emojiSwitchEmoji.pxHeight()) / 2), width(), st::emojiSwitchEmoji); - } -} - -} // namespace internal - -EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent) -, _maxHeight(st::emojiPanMaxHeight) -, _contentMaxHeight(st::emojiPanMaxHeight) -, _contentHeight(_contentMaxHeight) -, _contentHeightEmoji(_contentHeight - st::rbEmoji.height) -, _contentHeightStickers(_contentHeight - st::rbEmoji.height) -, _horizontal(false) -, _noTabUpdate(false) -, _hiding(false) -, a_opacity(0) -, _a_appearance(animation(this, &EmojiPan::step_appearance)) -, _shadow(st::dropdownDef.shadow) -, _recent(this , qsl("emoji_group"), dbietRecent , QString(), true , st::rbEmojiRecent) -, _people(this , qsl("emoji_group"), dbietPeople , QString(), false, st::rbEmojiPeople) -, _nature(this , qsl("emoji_group"), dbietNature , QString(), false, st::rbEmojiNature) -, _food(this , qsl("emoji_group"), dbietFood , QString(), false, st::rbEmojiFood) -, _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) -, _symbols(this , qsl("emoji_group"), dbietSymbols , QString(), false, st::rbEmojiSymbols) -, _iconOver(-1) -, _iconSel(0) -, _iconDown(-1) -, _iconsDragging(false) -, _a_icons(animation(this, &EmojiPan::step_icons)) -, _iconsLeft(0) -, _iconsTop(0) -, _iconsStartX(0) -, _iconsMax(0) -, _iconsX(0, 0) -, _iconSelX(0, 0) -, _iconsStartAnim(0) -, _stickersShown(false) -, _shownFromInlineQuery(false) -, _a_slide(animation(this, &EmojiPan::step_slide)) -, e_scroll(this, st::emojiScroll) -, e_inner() -, e_switch(&e_scroll, true) -, s_scroll(this, st::emojiScroll) -, s_inner() -, s_switch(&s_scroll, false) -, _removingSetId(0) -, _inlineBot(0) -, _inlineRequestId(0) { - 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); - - _width = st::dropdownDef.padding.left() + st::emojiPanWidth + st::dropdownDef.padding.right(); - _height = st::dropdownDef.padding.top() + _contentHeight + st::dropdownDef.padding.bottom(); - _bottom = 0; - resize(_width, _height); - - e_scroll.resize(st::emojiPanWidth, _contentHeightEmoji); - s_scroll.resize(st::emojiPanWidth, _contentHeightStickers); - - 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); - - e_inner.moveToLeft(0, 0, e_scroll.width()); - s_inner.moveToLeft(0, 0, s_scroll.width()); - - int32 left = _iconsLeft = st::dropdownDef.padding.left() + (st::emojiPanWidth - 8 * st::rbEmoji.width) / 2; - int32 top = _iconsTop = st::dropdownDef.padding.top() + _contentHeight - st::rbEmoji.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, _activity); - prepareTab(left, top, _width, _travel); - prepareTab(left, top, _width, _objects); - prepareTab(left, top, _width, _symbols); - e_inner.fillPanels(e_panels); - updatePanelsPositions(e_panels, 0); - - _hideTimer.setSingleShot(true); - connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); - - connect(&e_inner, SIGNAL(scrollToY(int)), &e_scroll, SLOT(scrollToY(int))); - connect(&e_inner, SIGNAL(disableScroll(bool)), &e_scroll, SLOT(disableScroll(bool))); - - connect(&s_inner, SIGNAL(scrollToY(int)), &s_scroll, SLOT(scrollToY(int))); - connect(&s_inner, SIGNAL(scrollUpdated()), this, SLOT(onScrollStickers())); - - connect(&e_scroll, SIGNAL(scrolled()), this, SLOT(onScrollEmoji())); - connect(&s_scroll, SIGNAL(scrolled()), this, SLOT(onScrollStickers())); - - connect(&e_inner, SIGNAL(selected(EmojiPtr)), this, SIGNAL(emojiSelected(EmojiPtr))); - connect(&s_inner, SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*))); - connect(&s_inner, SIGNAL(selected(PhotoData*)), this, SIGNAL(photoSelected(PhotoData*))); - connect(&s_inner, SIGNAL(selected(InlineBots::Result*,UserData*)), this, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*))); - - connect(&s_inner, SIGNAL(emptyInlineRows()), this, SLOT(onEmptyInlineRows())); - - connect(&s_switch, SIGNAL(clicked()), this, SLOT(onSwitch())); - connect(&e_switch, SIGNAL(clicked()), this, SLOT(onSwitch())); - s_switch.moveToRight(0, 0, st::emojiPanWidth); - e_switch.moveToRight(0, 0, st::emojiPanWidth); - - connect(&s_inner, SIGNAL(removing(quint64)), this, SLOT(onRemoveSet(quint64))); - connect(&s_inner, SIGNAL(refreshIcons()), this, SLOT(onRefreshIcons())); - connect(&e_inner, SIGNAL(needRefreshPanels()), this, SLOT(onRefreshPanels())); - connect(&s_inner, SIGNAL(needRefreshPanels()), this, SLOT(onRefreshPanels())); - - _saveConfigTimer.setSingleShot(true); - connect(&_saveConfigTimer, SIGNAL(timeout()), this, SLOT(onSaveConfig())); - connect(&e_inner, SIGNAL(saveConfigDelayed(int32)), this, SLOT(onSaveConfigDelayed(int32))); - connect(&s_inner, SIGNAL(saveConfigDelayed(int32)), this, SLOT(onSaveConfigDelayed(int32))); - - // inline bots - _inlineRequestTimer.setSingleShot(true); - connect(&_inlineRequestTimer, SIGNAL(timeout()), this, SLOT(onInlineRequest())); - - if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { - connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); - } - - setMouseTracking(true); -// setAttribute(Qt::WA_AcceptTouchEvents); -} - -void EmojiPan::setMaxHeight(int32 h) { - _maxHeight = h; - updateContentHeight(); -} - -void EmojiPan::updateContentHeight() { - int32 h = qMin(_contentMaxHeight, _maxHeight); - int32 he = h - st::rbEmoji.height; - int32 hs = h - (s_inner.showSectionIcons() ? st::rbEmoji.height : 0); - if (h == _contentHeight && he == _contentHeightEmoji && hs == _contentHeightStickers) return; - - int32 was = _contentHeight, wase = _contentHeightEmoji, wass = _contentHeightStickers; - _contentHeight = h; - _contentHeightEmoji = he; - _contentHeightStickers = hs; - - _height = st::dropdownDef.padding.top() + _contentHeight + st::dropdownDef.padding.bottom(); - - resize(_width, _height); - move(x(), _bottom - height()); - - if (was > _contentHeight || (was == _contentHeight && wass > _contentHeightStickers)) { - e_scroll.resize(st::emojiPanWidth, _contentHeightEmoji); - s_scroll.resize(st::emojiPanWidth, _contentHeightStickers); - s_inner.setMaxHeight(_contentHeightStickers); - e_inner.setMaxHeight(_contentHeightEmoji); - } else { - s_inner.setMaxHeight(_contentHeightStickers); - e_inner.setMaxHeight(_contentHeightEmoji); - e_scroll.resize(st::emojiPanWidth, _contentHeightEmoji); - s_scroll.resize(st::emojiPanWidth, _contentHeightStickers); - } - - _iconsTop = st::dropdownDef.padding.top() + _contentHeight - st::rbEmoji.height; - _recent.move(_recent.x(), _iconsTop); - _people.move(_people.x(), _iconsTop); - _nature.move(_nature.x(), _iconsTop); - _food.move(_food.x(), _iconsTop); - _activity.move(_activity.x(), _iconsTop); - _travel.move(_travel.x(), _iconsTop); - _objects.move(_objects.x(), _iconsTop); - _symbols.move(_symbols.x(), _iconsTop); - - update(); -} - -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); - } -} - -void EmojiPan::onSaveConfig() { - Local::writeUserSettings(); -} - -void EmojiPan::onSaveConfigDelayed(int32 delay) { - _saveConfigTimer.start(delay); -} - -void EmojiPan::paintStickerSettingsIcon(Painter &p) const { - int settingsLeft = _iconsLeft + 7 * st::rbEmoji.width; - p.drawSpriteLeft(settingsLeft + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::stickersSettings); - if (auto unread = Global::FeaturedStickerSetsUnreadCount()) { - Dialogs::Layout::UnreadBadgeStyle unreadSt; - unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInStickersPanel; - unreadSt.size = st::stickersSettingsUnreadSize; - int unreadRight = settingsLeft + st::rbEmoji.width - st::stickersSettingsUnreadPosition.x(); - if (rtl()) unreadRight = width() - unreadRight; - int unreadTop = _iconsTop + st::stickersSettingsUnreadPosition.y(); - Dialogs::Layout::paintUnreadCount(p, QString::number(unread), unreadRight, unreadTop, unreadSt); - } -} - -void EmojiPan::paintEvent(QPaintEvent *e) { - Painter p(this); - - float64 o = 1; - if (!_cache.isNull()) { - 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, st::dropdownDef.shadowShift); - - if (_toCache.isNull()) { - if (_cache.isNull()) { - p.fillRect(myrtlrect(r.x() + r.width() - st::emojiScroll.width, r.y(), st::emojiScroll.width, e_scroll.height()), st::white->b); - if (_stickersShown && s_inner.showSectionIcons()) { - p.fillRect(r.left(), _iconsTop, r.width(), st::rbEmoji.height, st::emojiPanCategories); - paintStickerSettingsIcon(p); - - if (!_icons.isEmpty()) { - int32 x = _iconsLeft, i = 0, selxrel = _iconsLeft + _iconSelX.current(), selx = selxrel - _iconsX.current(); - for (int32 l = _icons.size(); i < l && !_icons.at(i).sticker; ++i) { - bool gifs = (_icons.at(i).setId == Stickers::NoneSetId); - if (selxrel != x) { - p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), gifs ? st::savedGifsOver : st::rbEmojiRecent.imageRect); - } - if (selxrel < x + st::rbEmoji.width && selxrel > x - st::rbEmoji.width) { - p.setOpacity(1 - (qAbs(selxrel - x) / st::rbEmoji.width)); - p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), gifs ? st::savedGifsActive : st::rbEmojiRecent.chkImageRect); - p.setOpacity(1); - } - x += st::rbEmoji.width; - } - int32 skip = i; - - QRect clip(x, _iconsTop, _iconsLeft + 7 * st::rbEmoji.width - x, st::rbEmoji.height); - if (rtl()) clip.moveLeft(width() - x - clip.width()); - p.setClipRect(clip); - - i += _iconsX.current() / int(st::rbEmoji.width); - x -= _iconsX.current() % int(st::rbEmoji.width); - for (int32 l = qMin(_icons.size(), i + 8 - skip); i < l; ++i) { - const internal::StickerIcon &s(_icons.at(i)); - s.sticker->thumb->load(); - QPixmap pix(s.sticker->thumb->pix(s.pixw, s.pixh)); - - p.drawPixmapLeft(x + (st::rbEmoji.width - s.pixw) / 2, _iconsTop + (st::rbEmoji.height - s.pixh) / 2, width(), pix); - x += st::rbEmoji.width; - } - - if (rtl()) selx = width() - selx - st::rbEmoji.width; - p.setOpacity(skip ? qMax(1., selx / float64(skip * st::rbEmoji.width)) : 1.); - p.fillRect(selx, _iconsTop + st::rbEmoji.height - st::stickerIconPadding, st::rbEmoji.width, st::stickerIconSel, st::stickerIconSelColor); - - float64 o_left = snap(float64(_iconsX.current()) / st::stickerIconLeft.pxWidth(), 0., 1.); - if (o_left > 0) { - p.setOpacity(o_left); - p.drawSpriteLeft(QRect(_iconsLeft + skip * st::rbEmoji.width, _iconsTop, st::stickerIconLeft.pxWidth(), st::rbEmoji.height), width(), st::stickerIconLeft); - } - float64 o_right = snap(float64(_iconsMax - _iconsX.current()) / st::stickerIconRight.pxWidth(), 0., 1.); - if (o_right > 0) { - p.setOpacity(o_right); - p.drawSpriteRight(QRect(width() - _iconsLeft - 7 * st::rbEmoji.width, _iconsTop, st::stickerIconRight.pxWidth(), st::rbEmoji.height), width(), st::stickerIconRight); - } - } - } else if (_stickersShown) { - int32 x = rtl() ? (_recent.x() + _recent.width()) : (_objects.x() + _objects.width()); - p.fillRect(x, _recent.y(), r.left() + r.width() - x, st::rbEmoji.height, st::white); - } else { - p.fillRect(r.left(), _recent.y(), (rtl() ? _objects.x() : _recent.x() - r.left()), st::rbEmoji.height, st::emojiPanCategories); - int32 x = rtl() ? (_recent.x() + _recent.width()) : (_objects.x() + _objects.width()); - p.fillRect(x, _recent.y(), r.left() + r.width() - x, st::rbEmoji.height, st::emojiPanCategories); - } - } else { - p.fillRect(r, st::white->b); - p.drawPixmap(r.left(), r.top(), _cache); - } - } else { - p.fillRect(QRect(r.left(), r.top(), r.width(), r.height() - st::rbEmoji.height), st::white->b); - p.fillRect(QRect(r.left(), _iconsTop, r.width(), st::rbEmoji.height), st::emojiPanCategories->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); - } - } -} - -void EmojiPan::moveBottom(int32 bottom, bool force) { - _bottom = bottom; - if (isHidden() && !force) { - move(x(), _bottom - height()); - return; - } - if (_stickersShown && s_inner.inlineResultsShown()) { - moveToLeft(0, _bottom - height()); - } else { - moveToRight(0, _bottom - height()); - } -} - -void EmojiPan::enterEvent(QEvent *e) { - _hideTimer.stop(); - if (_hiding) showStart(); -} - -void EmojiPan::leaveEvent(QEvent *e) { - if (_removingSetId || s_inner.inlineResultsShown()) return; - if (_a_appearance.animating()) { - hideStart(); - } else { - _hideTimer.start(300); - } -} - -void EmojiPan::otherEnter() { - _hideTimer.stop(); - showStart(); -} - -void EmojiPan::otherLeave() { - if (_removingSetId || s_inner.inlineResultsShown()) return; - if (_a_appearance.animating()) { - hideStart(); - } else { - _hideTimer.start(0); - } -} - -void EmojiPan::mousePressEvent(QMouseEvent *e) { - if (!_stickersShown) return; - _iconsMousePos = e ? e->globalPos() : QCursor::pos(); - updateSelected(); - - if (_iconOver == _icons.size()) { - Ui::showLayer(new StickersBox()); - } else { - _iconDown = _iconOver; - _iconsMouseDown = _iconsMousePos; - _iconsStartX = _iconsX.current(); - } -} - -void EmojiPan::mouseMoveEvent(QMouseEvent *e) { - if (!_stickersShown) return; - _iconsMousePos = e ? e->globalPos() : QCursor::pos(); - updateSelected(); - - int32 skip = 0; - for (int32 i = 0, l = _icons.size(); i < l; ++i) { - if (_icons.at(i).sticker) break; - ++skip; - } - if (!_iconsDragging && !_icons.isEmpty() && _iconDown >= skip) { - if ((_iconsMousePos - _iconsMouseDown).manhattanLength() >= QApplication::startDragDistance()) { - _iconsDragging = true; - } - } - if (_iconsDragging) { - int32 newX = snap(_iconsStartX + (rtl() ? -1 : 1) * (_iconsMouseDown.x() - _iconsMousePos.x()), 0, _iconsMax); - if (newX != _iconsX.current()) { - _iconsX = anim::ivalue(newX, newX); - _iconsStartAnim = 0; - if (_iconAnimations.isEmpty()) _a_icons.stop(); - updateIcons(); - } - } -} - -void EmojiPan::mouseReleaseEvent(QMouseEvent *e) { - if (!_stickersShown || _icons.isEmpty()) return; - - int32 wasDown = _iconDown; - _iconDown = -1; - - _iconsMousePos = e ? e->globalPos() : QCursor::pos(); - if (_iconsDragging) { - int32 newX = snap(_iconsStartX + _iconsMouseDown.x() - _iconsMousePos.x(), 0, _iconsMax); - if (newX != _iconsX.current()) { - _iconsX = anim::ivalue(newX, newX); - _iconsStartAnim = 0; - if (_iconAnimations.isEmpty()) _a_icons.stop(); - updateIcons(); - } - _iconsDragging = false; - updateSelected(); - } else { - updateSelected(); - - if (wasDown == _iconOver && _iconOver >= 0 && _iconOver < _icons.size()) { - _iconSelX = anim::ivalue(_iconOver * st::rbEmoji.width, _iconOver * st::rbEmoji.width); - s_inner.showStickerSet(_icons.at(_iconOver).setId); - } - } -} - -bool EmojiPan::event(QEvent *e) { - if (e->type() == QEvent::TouchBegin) { - - } else if (e->type() == QEvent::Wheel) { - int32 skip = 0; - for (int32 i = 0, l = _icons.size(); i < l; ++i) { - if (_icons.at(i).sticker) break; - ++skip; - } - if (!_icons.isEmpty() && _iconOver >= skip && _iconOver < _icons.size() && _iconDown < 0) { - QWheelEvent *ev = static_cast(e); - bool hor = (ev->angleDelta().x() != 0 || ev->orientation() == Qt::Horizontal); - bool ver = (ev->angleDelta().y() != 0 || ev->orientation() == Qt::Vertical); - if (hor) _horizontal = true; - int32 newX = _iconsX.current(); - if (/*_horizontal && */hor) { - newX = snap(newX - (rtl() ? -1 : 1) * (ev->pixelDelta().x() ? ev->pixelDelta().x() : ev->angleDelta().x()), 0, _iconsMax); - } else if (/*!_horizontal && */ver) { - newX = snap(newX - (ev->pixelDelta().y() ? ev->pixelDelta().y() : ev->angleDelta().y()), 0, _iconsMax); - } - if (newX != _iconsX.current()) { - _iconsX = anim::ivalue(newX, newX); - _iconsStartAnim = 0; - if (_iconAnimations.isEmpty()) _a_icons.stop(); - updateSelected(); - updateIcons(); - } - } - } - return TWidget::event(e); -} - -void EmojiPan::fastHide() { - if (_a_appearance.animating()) { - _a_appearance.stop(); - } - a_opacity = anim::fvalue(0, 0); - _hideTimer.stop(); - hide(); - _cache = QPixmap(); -} - -void EmojiPan::refreshStickers() { - s_inner.refreshStickers(); - if (!_stickersShown) { - s_inner.preloadImages(); - } - update(); -} - -void EmojiPan::refreshSavedGifs() { - e_switch.updateText(); - e_switch.moveToRight(0, 0, st::emojiPanWidth); - s_inner.refreshSavedGifs(); - if (!_stickersShown) { - s_inner.preloadImages(); - } -} - -void EmojiPan::onRefreshIcons() { - _iconOver = -1; - _iconHovers.clear(); - _iconAnimations.clear(); - s_inner.fillIcons(_icons); - s_inner.fillPanels(s_panels); - _iconsX = anim::ivalue(0, 0); - _iconSelX.finish(); - _iconsStartAnim = 0; - _a_icons.stop(); - if (_icons.isEmpty()) { - _iconsMax = 0; - } else { - _iconHovers = QVector(_icons.size(), 0); - _iconsMax = qMax(int((_icons.size() - 7) * st::rbEmoji.width), 0); - } - updatePanelsPositions(s_panels, s_scroll.scrollTop()); - updateSelected(); - if (_stickersShown) { - validateSelectedIcon(); - updateContentHeight(); - } - updateIcons(); -} - -void EmojiPan::onRefreshPanels() { - s_inner.refreshPanels(s_panels); - e_inner.refreshPanels(e_panels); - if (_stickersShown) { - updatePanelsPositions(s_panels, s_scroll.scrollTop()); - } else { - updatePanelsPositions(e_panels, e_scroll.scrollTop()); - } -} - -void EmojiPan::leaveToChildEvent(QEvent *e, QWidget *child) { - if (!_stickersShown) return; - _iconsMousePos = QCursor::pos(); - updateSelected(); -} - -void EmojiPan::updateSelected() { - if (_iconDown >= 0) { - return; - } - - QPoint p(mapFromGlobal(_iconsMousePos)); - int32 x = p.x(), y = p.y(), newOver = -1; - if (rtl()) x = width() - x; - x -= _iconsLeft; - if (x >= st::rbEmoji.width * 7 && x < st::rbEmoji.width * 8 && y >= _iconsTop && y < _iconsTop + st::rbEmoji.height) { - newOver = _icons.size(); - } else if (!_icons.isEmpty()) { - if (y >= _iconsTop && y < _iconsTop + st::rbEmoji.height && x >= 0 && x < 7 * st::rbEmoji.width && x < _icons.size() * st::rbEmoji.width) { - int32 skip = 0; - for (int32 i = 0, l = _icons.size(); i < l; ++i) { - if (_icons.at(i).sticker) break; - if (x < st::rbEmoji.width) { - newOver = i; - break; - } - x -= st::rbEmoji.width; - ++skip; - } - if (newOver < 0) { - x += _iconsX.current(); - newOver = qFloor(x / st::rbEmoji.width) + skip; - } - } - } - if (newOver != _iconOver) { - if (newOver < 0) { - setCursor(style::cur_default); - } else if (_iconOver < 0) { - setCursor(style::cur_pointer); - } - bool startanim = false; - if (_iconOver >= 0 && _iconOver < _icons.size()) { - _iconAnimations.remove(_iconOver + 1); - if (_iconAnimations.find(-_iconOver - 1) == _iconAnimations.end()) { - if (_iconAnimations.isEmpty() && !_iconsStartAnim) startanim = true; - _iconAnimations.insert(-_iconOver - 1, getms()); - } - } - _iconOver = newOver; - if (_iconOver >= 0 && _iconOver < _icons.size()) { - _iconAnimations.remove(-_iconOver - 1); - if (_iconAnimations.find(_iconOver + 1) == _iconAnimations.end()) { - if (_iconAnimations.isEmpty() && !_iconsStartAnim) startanim = true; - _iconAnimations.insert(_iconOver + 1, getms()); - } - } - if (startanim && !_a_icons.animating()) _a_icons.start(); - } -} - -void EmojiPan::updateIcons() { - if (!_stickersShown || !s_inner.showSectionIcons()) return; - - 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()); - update(r.left(), _iconsTop, r.width(), st::rbEmoji.height); -} - -void EmojiPan::step_icons(uint64 ms, bool timer) { - if (!_stickersShown) { - _a_icons.stop(); - return; - } - - for (Animations::iterator i = _iconAnimations.begin(); i != _iconAnimations.end();) { - int index = qAbs(i.key()) - 1; - float64 dt = float64(ms - i.value()) / st::emojiPanDuration; - if (index >= _iconHovers.size()) { - i = _iconAnimations.erase(i); - } else if (dt >= 1) { - _iconHovers[index] = (i.key() > 0) ? 1 : 0; - i = _iconAnimations.erase(i); - } else { - _iconHovers[index] = (i.key() > 0) ? dt : (1 - dt); - ++i; - } - } - - if (_iconsStartAnim) { - float64 dt = (ms - _iconsStartAnim) / float64(st::stickerIconMove); - if (dt >= 1) { - _iconsStartAnim = 0; - _iconsX.finish(); - _iconSelX.finish(); - } else { - _iconsX.update(dt, anim::linear); - _iconSelX.update(dt, anim::linear); - } - if (timer) updateSelected(); - } - - if (timer) updateIcons(); - - if (_iconAnimations.isEmpty() && !_iconsStartAnim) { - _a_icons.stop(); - } -} - -void EmojiPan::step_slide(float64 ms, bool timer) { - float64 fullDuration = st::introSlideDelta + st::introSlideDuration, dt = ms / fullDuration; - float64 dt1 = (ms > st::introSlideDuration) ? 1 : (ms / st::introSlideDuration), dt2 = (ms > st::introSlideDelta) ? (ms - st::introSlideDelta) / (st::introSlideDuration) : 0; - if (dt2 >= 1) { - _a_slide.stop(); - a_fromCoord.finish(); - a_fromAlpha.finish(); - a_toCoord.finish(); - a_toAlpha.finish(); - _fromCache = _toCache = QPixmap(); - if (_cache.isNull()) showAll(); - } else { - a_fromCoord.update(dt1, st::introHideFunc); - a_fromAlpha.update(dt1, st::introAlphaHideFunc); - a_toCoord.update(dt2, st::introShowFunc); - a_toAlpha.update(dt2, st::introAlphaShowFunc); - } - if (timer) update(); -} - -void EmojiPan::step_appearance(float64 ms, bool timer) { - if (_cache.isNull()) { - _a_appearance.stop(); - return; - } - - float64 dt = ms / st::dropdownDef.duration; - if (dt >= 1) { - _a_appearance.stop(); - a_opacity.finish(); - if (_hiding) { - hideFinish(); - } else { - _cache = QPixmap(); - if (_toCache.isNull()) showAll(); - } - } else { - a_opacity.update(dt, anim::linear); - } - if (timer) update(); -} - -void EmojiPan::hideStart() { - if (_removingSetId || s_inner.inlineResultsShown()) return; - - hideAnimated(); -} - -void EmojiPan::prepareShowHideCache() { - if (_cache.isNull()) { - QPixmap from = _fromCache, to = _toCache; - _fromCache = _toCache = QPixmap(); - showAll(); - _cache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); - _fromCache = from; _toCache = to; - } -} - -void EmojiPan::hideAnimated() { - if (_hiding) return; - - prepareShowHideCache(); - hideAll(); - _hiding = true; - a_opacity.start(0); - _a_appearance.start(); -} - -void EmojiPan::hideFinish() { - hide(); - e_inner.hideFinish(); - s_inner.hideFinish(true); - _cache = _toCache = _fromCache = QPixmap(); - _a_slide.stop(); - _horizontal = false; - _hiding = false; - - e_scroll.scrollToY(0); - if (!_recent.checked()) { - _noTabUpdate = true; - _recent.setChecked(true); - _noTabUpdate = false; - } - s_scroll.scrollToY(0); - _iconOver = _iconDown = -1; - _iconSel = 0; - _iconsX = anim::ivalue(0, 0); - _iconSelX = anim::ivalue(0, 0); - _iconsStartAnim = 0; - _a_icons.stop(); - _iconHovers = _icons.isEmpty() ? QVector() : QVector(_icons.size(), 0); - _iconAnimations.clear(); - - Notify::clipStopperHidden(ClipStopperSavedGifsPanel); -} - -void EmojiPan::showStart() { - if (!isHidden() && !_hiding) { - return; - } - if (isHidden()) { - e_inner.refreshRecent(); - if (s_inner.inlineResultsShown() && refreshInlineRows()) { - _stickersShown = true; - _shownFromInlineQuery = true; - } else { - s_inner.refreshRecent(); - _stickersShown = false; - _shownFromInlineQuery = false; - _cache = QPixmap(); // clear after refreshInlineRows() - } - recountContentMaxHeight(); - s_inner.preloadImages(); - _fromCache = _toCache = QPixmap(); - _a_slide.stop(); - moveBottom(y() + height(), true); - } else if (_hiding) { - if (s_inner.inlineResultsShown() && refreshInlineRows()) { - onSwitch(); - } - } - prepareShowHideCache(); - hideAll(); - _hiding = false; - show(); - a_opacity.start(1); - _a_appearance.start(); - emit updateStickers(); -} - -bool EmojiPan::eventFilter(QObject *obj, QEvent *e) { - if (e->type() == QEvent::Enter) { - //if (dynamic_cast(obj)) { - // enterEvent(e); - //} else { - otherEnter(); - //} - } else if (e->type() == QEvent::Leave) { - //if (dynamic_cast(obj)) { - // leaveEvent(e); - //} else { - otherLeave(); - //} - } else if (e->type() == QEvent::MouseButtonPress && static_cast(e)->button() == Qt::LeftButton/* && !dynamic_cast(obj)*/) { - if (isHidden() || _hiding) { - _hideTimer.stop(); - showStart(); - } else { - hideAnimated(); - } - } - return false; -} - -void EmojiPan::stickersInstalled(uint64 setId) { - _stickersShown = true; - if (isHidden()) { - moveBottom(y() + height(), true); - show(); - a_opacity = anim::fvalue(0, 1); - a_opacity.update(0, anim::linear); - _cache = _fromCache = _toCache = QPixmap(); - } - showAll(); - s_inner.showStickerSet(setId); - updateContentHeight(); - showStart(); -} - -void EmojiPan::notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) { - if (_stickersShown && !isHidden()) { - s_inner.notify_inlineItemLayoutChanged(layout); - } -} - -void EmojiPan::ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { - if (_stickersShown && !isHidden()) { - s_inner.ui_repaintInlineItem(layout); - } -} - -bool EmojiPan::ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) { - if (_stickersShown && !isHidden()) { - return s_inner.ui_isInlineItemVisible(layout); - } - return false; -} - -bool EmojiPan::ui_isInlineItemBeingChosen() { - if (_stickersShown && !isHidden()) { - return s_inner.ui_isInlineItemBeingChosen(); - } - return false; -} - -void EmojiPan::showAll() { - if (_stickersShown) { - s_scroll.show(); - _recent.hide(); - _people.hide(); - _nature.hide(); - _food.hide(); - _activity.hide(); - _travel.hide(); - _objects.hide(); - _symbols.hide(); - e_scroll.hide(); - } else { - s_scroll.hide(); - _recent.show(); - _people.show(); - _nature.show(); - _food.show(); - _activity.show(); - _travel.show(); - _objects.show(); - _symbols.show(); - e_scroll.show(); - } -} - -void EmojiPan::hideAll() { - _recent.hide(); - _people.hide(); - _nature.hide(); - _food.hide(); - _activity.hide(); - _travel.hide(); - _objects.hide(); - _symbols.hide(); - e_scroll.hide(); - s_scroll.hide(); - e_inner.clearSelection(true); - s_inner.clearSelection(true); -} - -void EmojiPan::onTabChange() { - if (_noTabUpdate) return; - DBIEmojiTab newTab = dbietRecent; - if (_people.checked()) newTab = dbietPeople; - else if (_nature.checked()) newTab = dbietNature; - else if (_food.checked()) newTab = dbietFood; - else if (_activity.checked()) newTab = dbietActivity; - else if (_travel.checked()) newTab = dbietTravel; - else if (_objects.checked()) newTab = dbietObjects; - else if (_symbols.checked()) newTab = dbietSymbols; - e_inner.showEmojiPack(newTab); -} - -void EmojiPan::updatePanelsPositions(const QVector &panels, int32 st) { - for (int32 i = 0, l = panels.size(); i < l; ++i) { - int32 y = panels.at(i)->wantedY() - st; - if (y < 0) { - y = (i + 1 < l) ? qMin(panels.at(i + 1)->wantedY() - st - int(st::emojiPanHeader), 0) : 0; - } - panels.at(i)->move(0, y); - panels.at(i)->setDeleteVisible(y >= st::emojiPanHeader); - } -} - -void EmojiPan::onScrollEmoji() { - auto st = e_scroll.scrollTop(); - - updatePanelsPositions(e_panels, st); - - auto tab = e_inner.currentTab(st); - FlatRadiobutton *check = nullptr; - switch (tab) { - case dbietRecent: check = &_recent; break; - case dbietPeople: check = &_people; break; - case dbietNature: check = &_nature; break; - case dbietFood: check = &_food; break; - case dbietActivity: check = &_activity; break; - case dbietTravel: check = &_travel; break; - case dbietObjects: check = &_objects; break; - case dbietSymbols: check = &_symbols; break; - } - if (check && !check->checked()) { - _noTabUpdate = true; - check->setChecked(true); - _noTabUpdate = false; - } - - e_inner.setScrollTop(st); -} - -void EmojiPan::onScrollStickers() { - auto st = s_scroll.scrollTop(); - - updatePanelsPositions(s_panels, st); - - validateSelectedIcon(true); - if (st + s_scroll.height() > s_scroll.scrollTopMax()) { - onInlineRequest(); - } - - s_inner.setScrollTop(st); -} - -void EmojiPan::validateSelectedIcon(bool animated) { - uint64 setId = s_inner.currentSet(s_scroll.scrollTop()); - int32 newSel = 0; - for (int32 i = 0, l = _icons.size(); i < l; ++i) { - if (_icons.at(i).setId == setId) { - newSel = i; - break; - } - } - if (newSel != _iconSel) { - _iconSel = newSel; - int32 skip = 0; - for (int32 i = 0, l = _icons.size(); i < l; ++i) { - if (_icons.at(i).sticker) break; - ++skip; - } - if (animated) { - _iconSelX.start(newSel * st::rbEmoji.width); - } else { - _iconSelX = anim::ivalue(newSel * st::rbEmoji.width, newSel * st::rbEmoji.width); - } - _iconsX.start(snap((2 * newSel - 7 - skip) * int(st::rbEmoji.width) / 2, 0, _iconsMax)); - _iconsStartAnim = getms(); - _a_icons.start(); - updateSelected(); - updateIcons(); - } -} - -void EmojiPan::onSwitch() { - QPixmap cache = _cache; - _fromCache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); - _stickersShown = !_stickersShown; - if (!_stickersShown) { - Notify::clipStopperHidden(ClipStopperSavedGifsPanel); - } else { - if (cShowingSavedGifs() && cSavedGifs().isEmpty()) { - s_inner.showStickerSet(Stickers::DefaultSetId); - } else if (!cShowingSavedGifs() && !cSavedGifs().isEmpty() && Global::StickerSetsOrder().isEmpty()) { - s_inner.showStickerSet(Stickers::NoneSetId); - } else { - s_inner.updateShowingSavedGifs(); - } - if (cShowingSavedGifs()) { - s_inner.showFinish(); - } - validateSelectedIcon(); - updateContentHeight(); - } - _iconOver = -1; - _iconHovers = _icons.isEmpty() ? QVector() : QVector(_icons.size(), 0); - _iconAnimations.clear(); - _a_icons.stop(); - - _cache = QPixmap(); - showAll(); - _toCache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); - _cache = cache; - - hideAll(); - - if (_stickersShown) { - e_inner.hideFinish(); - } else { - s_inner.hideFinish(false); - } - - a_toCoord = (_stickersShown != rtl()) ? anim::ivalue(st::emojiPanWidth, 0) : anim::ivalue(-st::emojiPanWidth, 0); - a_toAlpha = anim::fvalue(0, 1); - a_fromCoord = (_stickersShown != rtl()) ? anim::ivalue(0, -st::emojiPanWidth) : anim::ivalue(0, st::emojiPanWidth); - a_fromAlpha = anim::fvalue(1, 0); - - _a_slide.start(); - update(); -} - -void EmojiPan::onRemoveSet(quint64 setId) { - auto &sets = Global::StickerSets(); - auto it = sets.constFind(setId); - if (it != sets.cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) { - _removingSetId = it->id; - ConfirmBox *box = new ConfirmBox(lng_stickers_remove_pack(lt_sticker_pack, it->title), lang(lng_box_remove)); - connect(box, SIGNAL(confirmed()), this, SLOT(onRemoveSetSure())); - connect(box, SIGNAL(destroyed(QObject*)), this, SLOT(onDelayedHide())); - Ui::showLayer(box); - } -} - -void EmojiPan::onRemoveSetSure() { - Ui::hideLayer(); - auto &sets = Global::RefStickerSets(); - auto it = sets.find(_removingSetId); - if (it != sets.cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) { - 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; - } - } - it->flags &= ~MTPDstickerSet::Flag::f_installed; - if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) { - sets.erase(it); - } - int removeIndex = Global::StickerSetsOrder().indexOf(_removingSetId); - if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); - refreshStickers(); - Local::writeInstalledStickers(); - if (writeRecent) Local::writeUserSettings(); - } - _removingSetId = 0; -} - -void EmojiPan::onDelayedHide() { - if (!rect().contains(mapFromGlobal(QCursor::pos()))) { - _hideTimer.start(3000); - } - _removingSetId = 0; -} - -void EmojiPan::clearInlineBot() { - inlineBotChanged(); - e_switch.updateText(); - e_switch.moveToRight(0, 0, st::emojiPanWidth); -} - -bool EmojiPan::hideOnNoInlineResults() { - return _inlineBot && _stickersShown && s_inner.inlineResultsShown() && (_shownFromInlineQuery || _inlineBot->username != cInlineGifBotUsername()); -} - -void EmojiPan::inlineBotChanged() { - if (!_inlineBot) return; - - if (!isHidden() && !_hiding) { - if (hideOnNoInlineResults() || !rect().contains(mapFromGlobal(QCursor::pos()))) { - hideAnimated(); - } - } - - if (_inlineRequestId) MTP::cancel(_inlineRequestId); - _inlineRequestId = 0; - _inlineQuery = _inlineNextQuery = _inlineNextOffset = QString(); - _inlineBot = nullptr; - for (InlineCache::const_iterator i = _inlineCache.cbegin(), e = _inlineCache.cend(); i != e; ++i) { - delete i.value(); - } - _inlineCache.clear(); - s_inner.inlineBotChanged(); - s_inner.hideInlineRowsPanel(); - - Notify::inlineBotRequesting(false); -} - -void EmojiPan::inlineResultsDone(const MTPmessages_BotResults &result) { - _inlineRequestId = 0; - Notify::inlineBotRequesting(false); - - auto it = _inlineCache.find(_inlineQuery); - - bool adding = (it != _inlineCache.cend()); - if (result.type() == mtpc_messages_botResults) { - const auto &d(result.c_messages_botResults()); - const auto &v(d.vresults.c_vector().v); - uint64 queryId(d.vquery_id.v); - - if (!adding) { - it = _inlineCache.insert(_inlineQuery, new internal::InlineCacheEntry()); - } - it.value()->nextOffset = qs(d.vnext_offset); - if (d.has_switch_pm() && d.vswitch_pm.type() == mtpc_inlineBotSwitchPM) { - const auto &switchPm = d.vswitch_pm.c_inlineBotSwitchPM(); - it.value()->switchPmText = qs(switchPm.vtext); - it.value()->switchPmStartToken = qs(switchPm.vstart_param); - } - - if (int count = v.size()) { - it.value()->results.reserve(it.value()->results.size() + count); - } - int added = 0; - for_const (const auto &res, v) { - if (auto result = InlineBots::Result::create(queryId, res)) { - ++added; - it.value()->results.push_back(result.release()); - } - } - - if (!added) { - it.value()->nextOffset = QString(); - } - } else if (adding) { - it.value()->nextOffset = QString(); - } - - if (!showInlineRows(!adding)) { - it.value()->nextOffset = QString(); - } - onScrollStickers(); -} - -bool EmojiPan::inlineResultsFail(const RPCError &error) { - // show error? - Notify::inlineBotRequesting(false); - _inlineRequestId = 0; - return true; -} - -void EmojiPan::queryInlineBot(UserData *bot, PeerData *peer, QString query) { - bool force = false; - _inlineQueryPeer = peer; - if (bot != _inlineBot) { - inlineBotChanged(); - _inlineBot = bot; - force = true; - //if (_inlineBot->isBotInlineGeo()) { - // Ui::showLayer(new InformBox(lang(lng_bot_inline_geo_unavailable))); - //} - } - //if (_inlineBot && _inlineBot->isBotInlineGeo()) { - // return; - //} - - if (_inlineQuery != query || force) { - if (_inlineRequestId) { - MTP::cancel(_inlineRequestId); - _inlineRequestId = 0; - Notify::inlineBotRequesting(false); - } - if (_inlineCache.contains(query)) { - _inlineRequestTimer.stop(); - _inlineQuery = _inlineNextQuery = query; - showInlineRows(true); - } else { - _inlineNextQuery = query; - _inlineRequestTimer.start(InlineBotRequestDelay); - } - } -} - -void EmojiPan::onInlineRequest() { - if (_inlineRequestId || !_inlineBot || !_inlineQueryPeer) return; - _inlineQuery = _inlineNextQuery; - - QString nextOffset; - InlineCache::const_iterator i = _inlineCache.constFind(_inlineQuery); - if (i != _inlineCache.cend()) { - nextOffset = i.value()->nextOffset; - if (nextOffset.isEmpty()) return; - } - Notify::inlineBotRequesting(true); - MTPmessages_GetInlineBotResults::Flags flags = 0; - _inlineRequestId = MTP::send(MTPmessages_GetInlineBotResults(MTP_flags(flags), _inlineBot->inputUser, _inlineQueryPeer->input, MTPInputGeoPoint(), MTP_string(_inlineQuery), MTP_string(nextOffset)), rpcDone(&EmojiPan::inlineResultsDone), rpcFail(&EmojiPan::inlineResultsFail)); -} - -void EmojiPan::onEmptyInlineRows() { - if (_shownFromInlineQuery || hideOnNoInlineResults()) { - hideAnimated(); - s_inner.clearInlineRowsPanel(); - } else if (!_inlineBot) { - s_inner.hideInlineRowsPanel(); - } else { - s_inner.clearInlineRowsPanel(); - } -} - -bool EmojiPan::refreshInlineRows(int32 *added) { - auto i = _inlineCache.constFind(_inlineQuery); - const internal::InlineCacheEntry *entry = nullptr; - if (i != _inlineCache.cend()) { - if (!i.value()->results.isEmpty() || !i.value()->switchPmText.isEmpty()) { - entry = i.value(); - } - _inlineNextOffset = i.value()->nextOffset; - } - if (!entry) prepareShowHideCache(); - int32 result = s_inner.refreshInlineRows(_inlineBot, entry, false); - if (added) *added = result; - return (entry != nullptr); -} - -int32 EmojiPan::showInlineRows(bool newResults) { - int32 added = 0; - bool clear = !refreshInlineRows(&added); - if (newResults) s_scroll.scrollToY(0); - - e_switch.updateText(s_inner.inlineResultsShown() ? _inlineBot->username : QString()); - e_switch.moveToRight(0, 0, st::emojiPanWidth); - - bool hidden = isHidden(); - if (!hidden && !clear) { - recountContentMaxHeight(); - } - if (clear) { - if (!hidden && hideOnNoInlineResults()) { - hideAnimated(); - } else if (!_hiding) { - _cache = QPixmap(); // clear after refreshInlineRows() - } - } else { - _hideTimer.stop(); - if (hidden || _hiding) { - showStart(); - } else if (!_stickersShown) { - onSwitch(); - } - } - - return added; -} - -void EmojiPan::recountContentMaxHeight() { - if (_shownFromInlineQuery) { - _contentMaxHeight = qMin(s_inner.countHeight(true), int(st::emojiPanMaxHeight)); - } else { - _contentMaxHeight = st::emojiPanMaxHeight; - } - updateContentHeight(); -} diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h index dfd81a7cfd..836b2a5315 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/dropdown.h @@ -27,7 +27,6 @@ class Dropdown : public TWidget { Q_OBJECT public: - Dropdown(QWidget *parent, const style::dropdown &st = st::dropdownDef); IconedButton *addButton(IconedButton *button); @@ -61,11 +60,9 @@ public: } signals: - void hiding(); public slots: - void hideStart(); void hideFinish(); @@ -75,20 +72,19 @@ public slots: void buttonStateChanged(int oldState, ButtonStateChangeSource source); private: - void adjustButtons(); - bool _ignore; + bool _ignore = false; typedef QVector Buttons; Buttons _buttons; - int32 _selected; + int32 _selected = -1; const style::dropdown &_st; int32 _width, _height; - bool _hiding; + bool _hiding = false; anim::fvalue a_opacity; Animation _a_appearance; @@ -103,7 +99,6 @@ class DragArea : public TWidget { Q_OBJECT public: - DragArea(QWidget *parent); void paintEvent(QPaintEvent *e); @@ -133,18 +128,15 @@ public: } signals: - void dropped(const QMimeData *data); public slots: - void hideStart(); void hideFinish(); void showStart(); private: - bool _hiding, _in; anim::fvalue a_opacity; @@ -156,588 +148,3 @@ private: QString _text, _subtext; }; - -namespace InlineBots { -namespace Layout { -class ItemBase; -} // namespace Layout -class Result; -} // namespace InlineBots - -namespace internal { - -constexpr int InlineItemsMaxPerRow = 5; -constexpr int EmojiColorsCount = 5; - -using InlineResult = InlineBots::Result; -using InlineResults = QList; -using InlineItem = InlineBots::Layout::ItemBase; - -struct InlineCacheEntry { - ~InlineCacheEntry() { - clearResults(); - } - QString nextOffset; - QString switchPmText, switchPmStartToken; - InlineResults results; // owns this results list - void clearResults(); -}; - -class EmojiColorPicker : public TWidget { - Q_OBJECT - -public: - - EmojiColorPicker(); - - void showEmoji(uint32 code); - - void paintEvent(QPaintEvent *e); - void enterEvent(QEvent *e); - void leaveEvent(QEvent *e); - void mousePressEvent(QMouseEvent *e); - void mouseReleaseEvent(QMouseEvent *e); - void mouseMoveEvent(QMouseEvent *e); - - void step_appearance(float64 ms, bool timer); - void step_selected(uint64 ms, bool timer); - void showStart(); - - void clearSelection(bool fast = false); - -public slots: - - void hideStart(bool fast = false); - -signals: - - void emojiSelected(EmojiPtr emoji); - void hidden(); - -private: - - void drawVariant(Painter &p, int variant); - - void updateSelected(); - - bool _ignoreShow; - - EmojiPtr _variants[EmojiColorsCount + 1]; - - typedef QMap EmojiAnimations; // index - showing, -index - hiding - EmojiAnimations _emojiAnimations; - Animation _a_selected; - - float64 _hovers[EmojiColorsCount + 1]; - - int32 _selected, _pressedSel; - QPoint _lastMousePos; - - bool _hiding; - QPixmap _cache; - - anim::fvalue a_opacity; - Animation _a_appearance; - - QTimer _hideTimer; - - BoxShadow _shadow; - -}; - -class EmojiPanel; -class EmojiPanInner : public TWidget { - Q_OBJECT - -public: - - EmojiPanInner(); - - void setMaxHeight(int32 h); - void paintEvent(QPaintEvent *e) override; - - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void leaveEvent(QEvent *e) override; - void leaveToChildEvent(QEvent *e, QWidget *child) override; - void enterFromChildEvent(QEvent *e, QWidget *child) override; - - void step_selected(uint64 ms, bool timer); - void hideFinish(); - - void showEmojiPack(DBIEmojiTab packIndex); - - void clearSelection(bool fast = false); - - DBIEmojiTab currentTab(int yOffset) const; - - void refreshRecent(); - - void setScrollTop(int top); - - void fillPanels(QVector &panels); - void refreshPanels(QVector &panels); - -public slots: - - void updateSelected(); - - void onShowPicker(); - void onPickerHidden(); - void onColorSelected(EmojiPtr emoji); - - bool checkPickerHide(); - -signals: - - void selected(EmojiPtr emoji); - - void switchToStickers(); - - void scrollToY(int y); - void disableScroll(bool dis); - - void needRefreshPanels(); - void saveConfigDelayed(int32 delay); - -private: - - int32 _maxHeight; - - int32 countHeight(); - void selectEmoji(EmojiPtr emoji); - - QRect emojiRect(int tab, int sel); - - typedef QMap Animations; // index - showing, -index - hiding - Animations _animations; - Animation _a_selected; - - int32 _top, _counts[emojiTabCount]; - - QVector _emojis[emojiTabCount]; - QVector _hovers[emojiTabCount]; - - int32 _esize; - - int32 _selected, _pressedSel, _pickerSel; - QPoint _lastMousePos; - - EmojiColorPicker _picker; - QTimer _showPickerTimer; -}; - -struct StickerIcon { - StickerIcon(uint64 setId) : setId(setId), sticker(0), pixw(0), pixh(0) { - } - StickerIcon(uint64 setId, DocumentData *sticker, int32 pixw, int32 pixh) : setId(setId), sticker(sticker), pixw(pixw), pixh(pixh) { - } - uint64 setId; - DocumentData *sticker; - int32 pixw, pixh; -}; - -class StickerPanInner : public TWidget { - Q_OBJECT - -public: - - StickerPanInner(); - - void setMaxHeight(int32 h); - void paintEvent(QPaintEvent *e) override; - - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void leaveEvent(QEvent *e) override; - void leaveToChildEvent(QEvent *e, QWidget *child) override; - void enterFromChildEvent(QEvent *e, QWidget *child) override; - - void step_selected(uint64 ms, bool timer); - - void hideFinish(bool completely); - void showFinish(); - void showStickerSet(uint64 setId); - void updateShowingSavedGifs(); - - bool showSectionIcons() const; - void clearSelection(bool fast = false); - - void refreshStickers(); - void refreshRecentStickers(bool resize = true); - void refreshSavedGifs(); - int refreshInlineRows(UserData *bot, const InlineCacheEntry *results, bool resultsDeleted); - void refreshRecent(); - void inlineBotChanged(); - void hideInlineRowsPanel(); - void clearInlineRowsPanel(); - - void fillIcons(QList &icons); - void fillPanels(QVector &panels); - void refreshPanels(QVector &panels); - - void setScrollTop(int top); - void preloadImages(); - - uint64 currentSet(int yOffset) const; - - void notify_inlineItemLayoutChanged(const InlineItem *layout); - void ui_repaintInlineItem(const InlineItem *layout); - bool ui_isInlineItemVisible(const InlineItem *layout); - bool ui_isInlineItemBeingChosen(); - - bool inlineResultsShown() const { - return _showingInlineItems && !_showingSavedGifs; - } - int32 countHeight(bool plain = false); - - ~StickerPanInner(); - -private slots: - - void updateSelected(); - void onSettings(); - void onPreview(); - void onUpdateInlineItems(); - void onSwitchPm(); - -signals: - - void selected(DocumentData *sticker); - void selected(PhotoData *photo); - void selected(InlineBots::Result *result, UserData *bot); - - void removing(quint64 setId); - - void refreshIcons(); - void emptyInlineRows(); - - void switchToEmoji(); - - void scrollToY(int y); - void scrollUpdated(); - void disableScroll(bool dis); - void needRefreshPanels(); - - void saveConfigDelayed(int32 delay); - -private: - - void paintInlineItems(Painter &p, const QRect &r); - void paintStickers(Painter &p, const QRect &r); - - void refreshSwitchPmButton(const InlineCacheEntry *entry); - - void appendSet(uint64 setId); - - void selectEmoji(EmojiPtr emoji); - QRect stickerRect(int tab, int sel); - - int32 _maxHeight; - - typedef QMap Animations; // index - showing, -index - hiding - Animations _animations; - Animation _a_selected; - - int32 _top; - - struct DisplayedSet { - DisplayedSet(uint64 id, MTPDstickerSet::Flags flags, const QString &title, int32 hoversSize, const StickerPack &pack = StickerPack()) : id(id), flags(flags), title(title), hovers(hoversSize, 0), pack(pack) { - } - uint64 id; - MTPDstickerSet::Flags flags; - QString title; - QVector hovers; - StickerPack pack; - }; - QList _sets; - QList _custom; - - bool _showingSavedGifs, _showingInlineItems; - bool _setGifCommand; - UserData *_inlineBot; - QString _inlineBotTitle; - uint64 _lastScrolled; - QTimer _updateInlineItems; - bool _inlineWithThumb; - - std_::unique_ptr _switchPmButton; - QString _switchPmStartToken; - - typedef QVector InlineItems; - struct InlineRow { - InlineRow() : height(0) { - } - int32 height; - InlineItems items; - }; - typedef QVector InlineRows; - InlineRows _inlineRows; - void clearInlineRows(bool resultsDeleted); - - using GifLayouts = QMap; - GifLayouts _gifLayouts; - InlineItem *layoutPrepareSavedGif(DocumentData *doc, int32 position); - - using InlineLayouts = QMap; - InlineLayouts _inlineLayouts; - InlineItem *layoutPrepareInlineResult(InlineResult *result, int32 position); - - bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth); - bool inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force = false); - - InlineRow &layoutInlineRow(InlineRow &row, int32 sumWidth = 0); - void deleteUnusedGifLayouts(); - - void deleteUnusedInlineLayouts(); - - int32 validateExistingInlineRows(const InlineResults &results); - int32 _selected, _pressedSel; - QPoint _lastMousePos; - - LinkButton _settings; - - QTimer _previewTimer; - bool _previewShown; -}; - -class EmojiPanel : public TWidget { - Q_OBJECT - -public: - - EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY); // Stickers::NoneSetId if in emoji - void setText(const QString &text); - void setDeleteVisible(bool isVisible); - - void paintEvent(QPaintEvent *e); - void mousePressEvent(QMouseEvent *e); - - int32 wantedY() const { - return _wantedY; - } - void setWantedY(int32 y) { - _wantedY = y; - } - -signals: - - void deleteClicked(quint64 setId); - void mousePressed(); - -public slots: - - void onDelete(); - -private: - - void updateText(); - - int32 _wantedY; - QString _text, _fullText; - uint64 _setId; - bool _special, _deleteVisible; - IconedButton *_delete; - -}; - -class EmojiSwitchButton : public Button { -public: - - EmojiSwitchButton(QWidget *parent, bool toStickers); // otherwise toEmoji - void paintEvent(QPaintEvent *e); - void updateText(const QString &inlineBotUsername = QString()); - -protected: - - bool _toStickers; - QString _text; - int32 _textWidth; - -}; - -} // namespace internal - -class EmojiPan : public TWidget, public RPCSender { - Q_OBJECT - -public: - - EmojiPan(QWidget *parent); - - void setMaxHeight(int32 h); - void paintEvent(QPaintEvent *e); - - void moveBottom(int32 bottom, bool force = false); - - void enterEvent(QEvent *e); - void leaveEvent(QEvent *e); - void otherEnter(); - void otherLeave(); - - void mousePressEvent(QMouseEvent *e); - void mouseMoveEvent(QMouseEvent *e); - void mouseReleaseEvent(QMouseEvent *e); - - bool event(QEvent *e); - - void fastHide(); - bool hiding() const { - return _hiding || _hideTimer.isActive(); - } - - void step_appearance(float64 ms, bool timer); - void step_slide(float64 ms, bool timer); - void step_icons(uint64 ms, bool timer); - - bool eventFilter(QObject *obj, QEvent *e); - void stickersInstalled(uint64 setId); - - void queryInlineBot(UserData *bot, PeerData *peer, QString query); - void clearInlineBot(); - - bool overlaps(const QRect &globalRect) { - if (isHidden() || !_cache.isNull()) return false; - - return QRect(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() - ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); - } - - void notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout); - void ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout); - bool ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout); - bool ui_isInlineItemBeingChosen(); - - bool inlineResultsShown() const { - return s_inner.inlineResultsShown(); - } - -public slots: - - void refreshStickers(); - void refreshSavedGifs(); - - void hideStart(); - void hideFinish(); - - void showStart(); - void onWndActiveChanged(); - - void onTabChange(); - void onScrollEmoji(); - void onScrollStickers(); - void onSwitch(); - - void onRemoveSet(quint64 setId); - void onRemoveSetSure(); - void onDelayedHide(); - - void onRefreshIcons(); - void onRefreshPanels(); - - void onSaveConfig(); - void onSaveConfigDelayed(int32 delay); - - void onInlineRequest(); - void onEmptyInlineRows(); - -signals: - - void emojiSelected(EmojiPtr emoji); - void stickerSelected(DocumentData *sticker); - void photoSelected(PhotoData *photo); - void inlineResultSelected(InlineBots::Result *result, UserData *bot); - - void updateStickers(); - -private: - void paintStickerSettingsIcon(Painter &p) const; - - void validateSelectedIcon(bool animated = false); - - int32 _maxHeight, _contentMaxHeight, _contentHeight, _contentHeightEmoji, _contentHeightStickers; - bool _horizontal; - void updateContentHeight(); - - void leaveToChildEvent(QEvent *e, QWidget *child); - void hideAnimated(); - void prepareShowHideCache(); - - void updateSelected(); - void updateIcons(); - - void prepareTab(int32 &left, int32 top, int32 _width, FlatRadiobutton &tab); - void updatePanelsPositions(const QVector &panels, int32 st); - - void showAll(); - void hideAll(); - - bool _noTabUpdate; - - int32 _width, _height, _bottom; - bool _hiding; - QPixmap _cache; - - anim::fvalue a_opacity; - Animation _a_appearance; - - QTimer _hideTimer; - - BoxShadow _shadow; - - FlatRadiobutton _recent, _people, _nature, _food, _activity, _travel, _objects, _symbols; - QList _icons; - QVector _iconHovers; - int32 _iconOver, _iconSel, _iconDown; - bool _iconsDragging; - typedef QMap Animations; // index - showing, -index - hiding - Animations _iconAnimations; - Animation _a_icons; - QPoint _iconsMousePos, _iconsMouseDown; - int32 _iconsLeft, _iconsTop; - int32 _iconsStartX, _iconsMax; - anim::ivalue _iconsX, _iconSelX; - uint64 _iconsStartAnim; - - bool _stickersShown, _shownFromInlineQuery; - QPixmap _fromCache, _toCache; - anim::ivalue a_fromCoord, a_toCoord; - anim::fvalue a_fromAlpha, a_toAlpha; - Animation _a_slide; - - ScrollArea e_scroll; - internal::EmojiPanInner e_inner; - QVector e_panels; - internal::EmojiSwitchButton e_switch; - ScrollArea s_scroll; - internal::StickerPanInner s_inner; - QVector s_panels; - internal::EmojiSwitchButton s_switch; - - uint64 _removingSetId; - - QTimer _saveConfigTimer; - - // inline bots - typedef QMap InlineCache; - InlineCache _inlineCache; - QTimer _inlineRequestTimer; - - void inlineBotChanged(); - int32 showInlineRows(bool newResults); - bool hideOnNoInlineResults(); - void recountContentMaxHeight(); - bool refreshInlineRows(int32 *added = 0); - UserData *_inlineBot; - PeerData *_inlineQueryPeer = nullptr; - QString _inlineQuery, _inlineNextQuery, _inlineNextOffset; - mtpRequestId _inlineRequestId; - void inlineResultsDone(const MTPmessages_BotResults &result); - bool inlineResultsFail(const RPCError &error); - -}; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index f64038d525..41a4ab839f 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -55,7 +55,7 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) { const HistoryMessageReplyMarkup::Button *button = nullptr; if (auto markup = msg->Get()) { if (row < markup->rows.size()) { - const auto &buttonRow(markup->rows.at(row)); + auto &buttonRow = markup->rows[row]; if (col < buttonRow.size()) { button = &buttonRow.at(col); } @@ -63,37 +63,39 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) { } if (!button) return; + using ButtonType = HistoryMessageReplyMarkup::Button::Type; switch (button->type) { - case HistoryMessageReplyMarkup::Button::Default: { + case ButtonType::Default: { // Copy string before passing it to the sending method // because the original button can be destroyed inside. MsgId replyTo = (msg->id > 0) ? msg->id : 0; sendBotCommand(msg->history()->peer, msg->fromOriginal()->asUser(), QString(button->text), replyTo); } break; - case HistoryMessageReplyMarkup::Button::Callback: { - if (MainWidget *m = main()) { + case ButtonType::Callback: + case ButtonType::Game: { + if (auto m = main()) { m->app_sendBotCallback(button, msg, row, col); } } break; - case HistoryMessageReplyMarkup::Button::Url: { + case ButtonType::Url: { auto url = QString::fromUtf8(button->data); - UrlClickHandler(url).onClick(Qt::LeftButton); + HiddenUrlClickHandler(url).onClick(Qt::LeftButton); } break; - case HistoryMessageReplyMarkup::Button::RequestLocation: { + case ButtonType::RequestLocation: { Ui::showLayer(new InformBox(lang(lng_bot_share_location_unavailable))); } break; - case HistoryMessageReplyMarkup::Button::RequestPhone: { + case ButtonType::RequestPhone: { SharePhoneConfirmBox *box = new SharePhoneConfirmBox(msg->history()->peer); box->connect(box, SIGNAL(confirmed(PeerData*)), App::main(), SLOT(onSharePhoneWithBot(PeerData*))); Ui::showLayer(box); } break; - case HistoryMessageReplyMarkup::Button::SwitchInlineSame: - case HistoryMessageReplyMarkup::Button::SwitchInline: { + case ButtonType::SwitchInlineSame: + case ButtonType::SwitchInline: { if (auto m = App::main()) { auto getMessageBot = [msg]() -> UserData* { if (auto bot = msg->viaBot()) { @@ -107,7 +109,7 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) { }; if (auto bot = getMessageBot()) { auto tryFastSwitch = [bot, &button, msgId = msg->id]() -> bool { - auto samePeer = (button->type == HistoryMessageReplyMarkup::Button::SwitchInlineSame); + auto samePeer = (button->type == ButtonType::SwitchInlineSame); if (samePeer) { Notify::switchInlineBotButtonReceived(QString::fromUtf8(button->data), bot, msgId); return true; diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 3150fd6478..cbe01c49a2 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -193,11 +193,12 @@ enum Flags { namespace Stickers { -static const uint64 DefaultSetId = 0; // for backward compatibility -static const uint64 CustomSetId = 0xFFFFFFFFFFFFFFFFULL; -static const uint64 RecentSetId = 0xFFFFFFFFFFFFFFFEULL; // for emoji/stickers panel, should not appear in Sets -static const uint64 NoneSetId = 0xFFFFFFFFFFFFFFFDULL; // for emoji/stickers panel, should not appear in Sets -static const uint64 CloudRecentSetId = 0xFFFFFFFFFFFFFFFCULL; // for cloud-stored recent stickers +constexpr uint64 DefaultSetId = 0; // for backward compatibility +constexpr uint64 CustomSetId = 0xFFFFFFFFFFFFFFFFULL; +constexpr uint64 RecentSetId = 0xFFFFFFFFFFFFFFFEULL; // for emoji/stickers panel, should not appear in Sets +constexpr uint64 NoneSetId = 0xFFFFFFFFFFFFFFFDULL; // for emoji/stickers panel, should not appear in Sets +constexpr uint64 CloudRecentSetId = 0xFFFFFFFFFFFFFFFCULL; // for cloud-stored recent stickers +constexpr uint64 FeaturedSetId = 0xFFFFFFFFFFFFFFFBULL; // for emoji/stickers panel, should not appear in Sets struct Set { Set(uint64 id, uint64 access, const QString &title, const QString &shortName, int32 count, int32 hash, MTPDstickerSet::Flags flags) : id(id) diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index b1f7516453..df5e715afa 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -674,9 +674,9 @@ void checkForSwitchInlineButton(HistoryItem *item) { return; } if (auto markup = item->Get()) { - for_const (const auto &row, markup->rows) { - for_const (const auto &button, row) { - if (button.type == HistoryMessageReplyMarkup::Button::SwitchInline) { + for_const (auto &row, markup->rows) { + for_const (auto &button, row) { + if (button.type == HistoryMessageReplyMarkup::Button::Type::SwitchInline) { Notify::switchInlineBotButtonReceived(QString::fromUtf8(button.data)); return; } @@ -2222,7 +2222,7 @@ public: // Copy to clipboard support. void copyToClipboard() const override { if (auto button = getButton()) { - if (button->type == HistoryMessageReplyMarkup::Button::Url) { + if (button->type == HistoryMessageReplyMarkup::Button::Type::Url) { auto url = QString::fromUtf8(button->data); if (!url.isEmpty()) { QApplication::clipboard()->setText(url); @@ -2232,7 +2232,7 @@ public: } QString copyToClipboardContextItemText() const override { if (auto button = getButton()) { - if (button->type == HistoryMessageReplyMarkup::Button::Url) { + if (button->type == HistoryMessageReplyMarkup::Button::Type::Url) { return lang(lng_context_copy_link); } } @@ -2247,7 +2247,7 @@ public: if (auto item = App::histItemById(_itemId)) { if (auto markup = item->Get()) { if (_row < markup->rows.size()) { - const HistoryMessageReplyMarkup::ButtonRow &row(markup->rows.at(_row)); + auto &row = markup->rows.at(_row); if (_col < row.size()) { return &row.at(_col); } @@ -2292,14 +2292,14 @@ ReplyKeyboard::ReplyKeyboard(const HistoryItem *item, StylePtr &&s) if (auto markup = item->Get()) { _rows.reserve(markup->rows.size()); for (int i = 0, l = markup->rows.size(); i != l; ++i) { - const HistoryMessageReplyMarkup::ButtonRow &row(markup->rows.at(i)); + auto &row = markup->rows.at(i); int s = row.size(); ButtonRow newRow(s, Button()); for (int j = 0; j != s; ++j) { - Button &button(newRow[j]); - QString str = row.at(j).text; + auto &button = newRow[j]; + auto str = row.at(j).text; button.type = row.at(j).type; - button.link.reset(new ReplyMarkupClickHandler(item, i, j)); + button.link = MakeShared(item, i, j); button.text.setText(_st->textFont(), textOneLine(str), _textPlainOptions); button.characters = str.isEmpty() ? 1 : str.size(); } @@ -2323,14 +2323,14 @@ void ReplyKeyboard::resize(int width, int height) { auto markup = _item->Get(); float64 y = 0, buttonHeight = _rows.isEmpty() ? _st->buttonHeight() : (float64(height + _st->buttonSkip()) / _rows.size()); - for (ButtonRow &row : _rows) { + for (auto &row : _rows) { int s = row.size(); int widthForButtons = _width - ((s - 1) * _st->buttonSkip()); int widthForText = widthForButtons; int widthOfText = 0; int maxMinButtonWidth = 0; - for_const (const Button &button, row) { + for_const (auto &button, row) { widthOfText += qMax(button.text.maxWidth(), 1); int minButtonWidth = _st->minButtonWidth(button.type); widthForText -= minButtonWidth; @@ -2368,10 +2368,10 @@ void ReplyKeyboard::resize(int width, int height) { } bool ReplyKeyboard::isEnoughSpace(int width, const style::botKeyboardButton &st) const { - for_const (const auto &row, _rows) { + for_const (auto &row, _rows) { int s = row.size(); int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding); - for_const (const auto &button, row) { + for_const (auto &button, row) { widthLeft -= qMax(button.text.maxWidth(), 1); if (widthLeft < 0) { if (row.size() > 3) { @@ -2416,8 +2416,8 @@ void ReplyKeyboard::paint(Painter &p, const QRect &clip) const { t_assert(_width > 0); _st->startPaint(p); - for_const (const ButtonRow &row, _rows) { - for_const (const Button &button, row) { + for_const (auto &row, _rows) { + for_const (auto &button, row) { QRect rect(button.rect); if (rect.y() >= clip.y() + clip.height()) return; if (rect.y() + rect.height() < clip.y()) continue; @@ -2433,8 +2433,8 @@ void ReplyKeyboard::paint(Painter &p, const QRect &clip) const { ClickHandlerPtr ReplyKeyboard::getState(int x, int y) const { t_assert(_width > 0); - for_const (const ButtonRow &row, _rows) { - for_const (const Button &button, row) { + for_const (auto &row, _rows) { + for_const (auto &button, row) { QRect rect(button.rect); // just ignore the buttons that didn't layout well @@ -2453,7 +2453,7 @@ void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool act bool startAnimation = false; for (int i = 0, rows = _rows.size(); i != rows; ++i) { - const ButtonRow &row(_rows.at(i)); + auto &row = _rows.at(i); for (int j = 0, cols = row.size(); j != cols; ++j) { if (row.at(j).link == p) { bool startAnimation = _animations.isEmpty(); @@ -2514,8 +2514,9 @@ void ReplyKeyboard::Style::paintButton(Painter &p, const ReplyKeyboard::Button & paintButtonBg(p, rect, pressed, button.howMuchOver); paintButtonIcon(p, rect, button.type); - if (button.type == HistoryMessageReplyMarkup::Button::Callback) { - if (const HistoryMessageReplyMarkup::Button *data = button.link->getButton()) { + if (button.type == HistoryMessageReplyMarkup::Button::Type::Callback + || button.type == HistoryMessageReplyMarkup::Button::Type::Game) { + if (auto data = button.link->getButton()) { if (data->requestId) { paintButtonLoading(p, rect); } @@ -2541,43 +2542,48 @@ void HistoryMessageReplyMarkup::createFromButtonRows(const QVector 0) { result = std::max(result, 2 * iconWidth + 4 * int(st::msgBotKbIconPadding)); @@ -7871,23 +7878,25 @@ HistoryMessage::~HistoryMessage() { } void HistoryService::setMessageByAction(const MTPmessageAction &action) { - QList links; - LangString text = lang(lng_message_empty); - QString from = textcmdLink(1, _from->name); + auto text = lang(lng_message_empty); + auto from = textcmdLink(1, _from->name); + + Links links; + links.push_back(MakeShared(_from)); switch (action.type()) { case mtpc_messageActionChatAddUser: { - const auto &d(action.c_messageActionChatAddUser()); - const auto &v(d.vusers.c_vector().v); + auto &d = action.c_messageActionChatAddUser(); + auto &v = d.vusers.c_vector().v; bool foundSelf = false; - for (int32 i = 0, l = v.size(); i < l; ++i) { + for (int i = 0, l = v.size(); i < l; ++i) { if (v.at(i).v == MTP::authedId()) { foundSelf = true; break; } } if (v.size() == 1) { - UserData *u = App::user(peerFromUser(v.at(0))); + auto u = App::user(peerFromUser(v.at(0))); if (u == _from) { text = lng_action_user_joined(lt_from, from); } else { @@ -7897,9 +7906,9 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } else if (v.isEmpty()) { text = lng_action_add_user(lt_from, from, lt_user, "somebody"); } else { - for (int32 i = 0, l = v.size(); i < l; ++i) { - UserData *u = App::user(peerFromUser(v.at(i))); - QString linkText = textcmdLink(i + 2, u->name); + for (int i = 0, l = v.size(); i < l; ++i) { + auto u = App::user(peerFromUser(v.at(i))); + auto linkText = textcmdLink(i + 2, u->name); if (i == 0) { text = linkText; } else if (i + 1 < l) { @@ -7919,7 +7928,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } break; case mtpc_messageActionChatJoinedByLink: { - const auto &d(action.c_messageActionChatJoinedByLink()); + auto &d = action.c_messageActionChatJoinedByLink(); //if (true || peerFromUser(d.vinviter_id) == _from->id) { text = lng_action_user_joined_by_link(lt_from, from); //} else { @@ -7933,12 +7942,12 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } break; case mtpc_messageActionChatCreate: { - const auto &d(action.c_messageActionChatCreate()); + auto &d = action.c_messageActionChatCreate(); text = lng_action_created_chat(lt_from, from, lt_title, textClean(qs(d.vtitle))); } break; case mtpc_messageActionChannelCreate: { - const auto &d(action.c_messageActionChannelCreate()); + auto &d = action.c_messageActionChannelCreate(); if (isPost()) { text = lng_action_created_channel(lt_title, textClean(qs(d.vtitle))); } else { @@ -7955,18 +7964,18 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } break; case mtpc_messageActionChatDeleteUser: { - const auto &d(action.c_messageActionChatDeleteUser()); + auto &d = action.c_messageActionChatDeleteUser(); if (peerFromUser(d.vuser_id) == _from->id) { text = lng_action_user_left(lt_from, from); } else { - UserData *u = App::user(peerFromUser(d.vuser_id)); + auto u = App::user(peerFromUser(d.vuser_id)); links.push_back(MakeShared(u)); text = lng_action_kick_user(lt_from, from, lt_user, textcmdLink(2, u->name)); } } break; case mtpc_messageActionChatEditPhoto: { - const auto &d(action.c_messageActionChatEditPhoto()); + auto &d = action.c_messageActionChatEditPhoto(); if (d.vphoto.type() == mtpc_photo) { _media.reset(new HistoryPhoto(this, history()->peer, d.vphoto.c_photo(), st::msgServicePhotoWidth)); } @@ -7974,13 +7983,13 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } break; case mtpc_messageActionChatEditTitle: { - const auto &d(action.c_messageActionChatEditTitle()); + auto &d = action.c_messageActionChatEditTitle(); text = isPost() ? lng_action_changed_title_channel(lt_title, textClean(qs(d.vtitle))) : lng_action_changed_title(lt_from, from, lt_title, textClean(qs(d.vtitle))); } break; case mtpc_messageActionChatMigrateTo: { _flags |= MTPDmessage_ClientFlag::f_is_group_migrate; - const auto &d(action.c_messageActionChatMigrateTo()); + auto &d = action.c_messageActionChatMigrateTo(); if (true/*PeerData *channel = App::channelLoaded(d.vchannel_id.v)*/) { text = lang(lng_action_group_migrate); } else { @@ -7990,7 +7999,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { case mtpc_messageActionChannelMigrateFrom: { _flags |= MTPDmessage_ClientFlag::f_is_group_migrate; - const auto &d(action.c_messageActionChannelMigrateFrom()); + auto &d = action.c_messageActionChannelMigrateFrom(); if (true/*PeerData *chat = App::chatLoaded(d.vchat_id.v)*/) { text = lang(lng_action_group_migrate); } else { @@ -7999,74 +8008,63 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } break; case mtpc_messageActionPinMessage: { - if (updatePinnedText(&from, &text)) { - auto pinned = Get(); - t_assert(pinned != nullptr); + preparePinnedText(from, &text, &links); + } break; - links.push_back(pinned->lnk); - } + case mtpc_messageActionGameScore: { + prepareGameScoreText(from, &text, &links); } break; default: from = QString(); break; } - textstyleSet(&st::serviceTextStyle); - _text.setText(st::msgServiceFont, text, _historySrvOptions); - textstyleRestore(); - if (!from.isEmpty()) { - _text.setLink(1, MakeShared(_from)); - } - for (int32 i = 0, l = links.size(); i < l; ++i) { - _text.setLink(i + 2, links.at(i)); + setServiceText(text, links); + for (int i = 0, count = links.size(); i != count; ++i) { + _text.setLink(1 + i, links.at(i)); } } -bool HistoryService::updatePinned(bool force) { - auto pinned = Get(); - t_assert(pinned != nullptr); +bool HistoryService::updateDependent(bool force) { + auto dependent = GetDependentData(); + t_assert(dependent != nullptr); if (!force) { - if (!pinned->msgId || pinned->msg) { + if (!dependent->msgId || dependent->msg) { return true; } } - if (!pinned->lnk) { - pinned->lnk.reset(new GoToMessageClickHandler(history()->peer->id, pinned->msgId)); + if (!dependent->lnk) { + dependent->lnk.reset(new GoToMessageClickHandler(history()->peer->id, dependent->msgId)); } bool gotDependencyItem = false; - if (!pinned->msg) { - pinned->msg = App::histItemById(channelId(), pinned->msgId); - if (pinned->msg) { - App::historyRegDependency(this, pinned->msg); + if (!dependent->msg) { + dependent->msg = App::histItemById(channelId(), dependent->msgId); + if (dependent->msg) { + App::historyRegDependency(this, dependent->msg); gotDependencyItem = true; } } - if (pinned->msg) { - updatePinnedText(); + if (dependent->msg) { + updateDependentText(); } else if (force) { - if (pinned->msgId > 0) { - pinned->msgId = 0; + if (dependent->msgId > 0) { + dependent->msgId = 0; gotDependencyItem = true; } - updatePinnedText(); + updateDependentText(); } if (force) { if (gotDependencyItem && App::wnd()) { App::wnd()->notifySettingGot(); } } - return (pinned->msg || !pinned->msgId); + return (dependent->msg || !dependent->msgId); } -bool HistoryService::updatePinnedText(const QString *pfrom, QString *ptext) { +bool HistoryService::preparePinnedText(const QString &from, QString *outText, Links *outLinks) { bool result = false; - QString from, text; - if (pfrom) { - from = *pfrom; - } else { - from = textcmdLink(1, _from->name); - } + QString text; ClickHandlerPtr second; auto pinned = Get(); @@ -8112,21 +8110,47 @@ bool HistoryService::updatePinnedText(const QString *pfrom, QString *ptext) { } else { text = lng_action_pinned_media(lt_from, from, lt_media, lang(lng_deleted_message)); } - if (ptext) { - *ptext = text; + *outText = text; + if (second) { + outLinks->push_back(second); + } + return result; +} + +bool HistoryService::prepareGameScoreText(const QString &from, QString *outText, Links *outLinks) { + bool result = false; + QString text; + + ClickHandlerPtr second; + auto gamescore = Get(); + if (gamescore && gamescore->msg) { + auto getGameTitle = [item = gamescore->msg, &second]() -> QString { + if (auto markup = item->Get()) { + for (int i = 0, rowsCount = markup->rows.size(); i != rowsCount; ++i) { + auto &row = markup->rows[i]; + for (int j = 0, buttonsCount = row.size(); j != buttonsCount; ++j) { + auto &button = row[j]; + if (button.type == HistoryMessageReplyMarkup::Button::Type::Game) { + auto strData = QString::fromUtf8(button.data); + second = MakeShared(item, i, j); + return textcmdLink(2, strData.mid(strData.indexOf(',') + 1)); + } + } + } + } + return lang(lng_deleted_message); + }; + text = lng_action_game_score(lt_from, from, lt_score, QString::number(gamescore->score), lt_game, getGameTitle()); + result = true; + } else if (gamescore && gamescore->msgId) { + text = lng_action_game_score(lt_from, from, lt_score, QString::number(gamescore->score), lt_game, lang(lng_contacts_loading)); + result = true; } else { - setServiceText(text); - _text.setLink(1, MakeShared(_from)); - if (second) { - _text.setLink(2, second); - } - if (history()->textCachedFor == this) { - history()->textCachedFor = 0; - } - if (App::main()) { - App::main()->dlgUpdated(history(), id); - } - App::historyUpdateDependent(this); + text = lng_action_game_score(lt_from, from, lt_score, QString::number(gamescore->score), lt_game, lang(lng_deleted_message)); + } + *outText = text; + if (second) { + outLinks->push_back(second); } return result; } @@ -8134,10 +8158,17 @@ bool HistoryService::updatePinnedText(const QString *pfrom, QString *ptext) { HistoryService::HistoryService(History *history, const MTPDmessageService &msg) : HistoryItem(history, msg.vid.v, mtpCastFlags(msg.vflags.v), ::date(msg.vdate), msg.has_from_id() ? msg.vfrom_id.v : 0) { if (msg.has_reply_to_msg_id()) { - UpdateComponents(HistoryServicePinned::Bit()); - MsgId pinnedMsgId = Get()->msgId = msg.vreply_to_msg_id.v; - if (!updatePinned() && App::api()) { - App::api()->requestMessageData(history->peer->asChannel(), pinnedMsgId, std_::make_unique(fullId())); + if (msg.vaction.type() == mtpc_messageActionPinMessage) { + UpdateComponents(HistoryServicePinned::Bit()); + } else if (msg.vaction.type() == mtpc_messageActionGameScore) { + UpdateComponents(HistoryServiceGameScore::Bit()); + Get()->score = msg.vaction.c_messageActionGameScore().vscore.v; + } + if (auto dependent = GetDependentData()) { + dependent->msgId = msg.vreply_to_msg_id.v; + if (!updateDependent() && App::api()) { + App::api()->requestMessageData(history->peer->asChannel(), dependent->msgId, std_::make_unique(fullId())); + } } } setMessageByAction(msg.vaction); @@ -8145,7 +8176,7 @@ HistoryService::HistoryService(History *history, const MTPDmessageService &msg) HistoryService::HistoryService(History *history, MsgId msgId, QDateTime date, const QString &msg, MTPDmessage::Flags flags, int32 from) : HistoryItem(history, msgId, flags, date, from) { - _text.setText(st::msgServiceFont, msg, _historySrvOptions); + setServiceText(msg, Links()); } void HistoryService::initDimensions() { @@ -8154,6 +8185,13 @@ void HistoryService::initDimensions() { if (_media) _media->initDimensions(); } +bool HistoryService::updateDependencyItem() { + if (GetDependentData()) { + return updateDependent(true); + } + return HistoryItem::updateDependencyItem(); +} + void HistoryService::countPositionAndSize(int32 &left, int32 &width) const { left = st::msgServiceMargin.left(); int32 maxwidth = _history->width; @@ -8176,10 +8214,14 @@ QString HistoryService::inReplyText() const { return result.trimmed().startsWith(author()->name) ? result.trimmed().mid(author()->name.size()).trimmed() : result; } -void HistoryService::setServiceText(const QString &text) { +void HistoryService::setServiceText(const QString &text, const Links &links) { textstyleSet(&st::serviceTextStyle); _text.setText(st::msgServiceFont, text, _historySrvOptions); textstyleRestore(); + for (int i = 0, count = links.size(); i != count; ++i) { + _text.setLink(1 + i, links.at(i)); + } + setPendingInitDimensions(); _textWidth = -1; _textHeight = 0; @@ -8296,13 +8338,19 @@ HistoryTextState HistoryService::getState(int x, int y, HistoryStateRequest requ if (_media) { height -= st::msgServiceMargin.top() + _media->height(); } - QRect trect(QRect(left, st::msgServiceMargin.top(), width, height).marginsAdded(-st::msgServicePadding)); + auto outer = QRect(left, st::msgServiceMargin.top(), width, height); + auto trect = outer.marginsAdded(-st::msgServicePadding); if (trect.contains(x, y)) { textstyleSet(&st::serviceTextStyle); auto textRequest = request.forText(); textRequest.align = style::al_center; result = _text.getState(x - trect.x(), y - trect.y(), trect.width(), textRequest); textstyleRestore(); + if (auto gamescore = Get()) { + if (!result.link && outer.contains(x, y)) { + result.link = gamescore->lnk; + } + } } else if (_media) { result = _media->getState(x - st::msgServiceMargin.left() - (width - _media->maxWidth()) / 2, y - st::msgServiceMargin.top() - height - st::msgServiceMargin.top(), request); } @@ -8311,9 +8359,12 @@ HistoryTextState HistoryService::getState(int x, int y, HistoryStateRequest requ void HistoryService::applyEditionToEmpty() { TextWithEntities textWithEntities = { QString(), EntitiesInText() }; - setServiceText(QString()); + setServiceText(QString(), Links()); removeMedia(); + clearDependency(); + UpdateComponents(0); + finishEditionToEmpty(); } @@ -8352,25 +8403,58 @@ void HistoryService::eraseFromOverview() { } } -HistoryService::~HistoryService() { - if (auto pinned = Get()) { - if (pinned->msg) { - App::historyUnregDependency(this, pinned->msg); +bool HistoryService::updateDependentText() { + auto result = false; + auto from = textcmdLink(1, _from->name); + QString text; + Links links; + links.push_back(MakeShared(_from)); + if (Has()) { + result = preparePinnedText(from, &text, &links); + } else if (Has()) { + result = prepareGameScoreText(from, &text, &links); + } else { + return result; + } + + setServiceText(text, links); + if (history()->textCachedFor == this) { + history()->textCachedFor = 0; + } + if (App::main()) { + App::main()->dlgUpdated(history(), id); + } + App::historyUpdateDependent(this); + return result; +} + +void HistoryService::clearDependency() { + if (auto dependent = GetDependentData()) { + if (dependent->msg) { + App::historyUnregDependency(this, dependent->msg); } } +} + +HistoryService::~HistoryService() { + clearDependency(); _media.clear(); } HistoryJoined::HistoryJoined(History *history, const QDateTime &inviteDate, UserData *inviter, MTPDmessage::Flags flags) : HistoryService(history, clientMsgId(), inviteDate, QString(), flags) { - textstyleSet(&st::serviceTextStyle); - if (peerToUser(inviter->id) == MTP::authedId()) { - _text.setText(st::msgServiceFont, lang(history->isMegagroup() ? lng_action_you_joined_group : lng_action_you_joined), _historySrvOptions); - } else { - _text.setText(st::msgServiceFont, history->isMegagroup() ? lng_action_add_you_group(lt_from, textcmdLink(1, inviter->name)) : lng_action_add_you(lt_from, textcmdLink(1, inviter->name)), _historySrvOptions); - _text.setLink(1, MakeShared(inviter)); - } - textstyleRestore(); + Links links; + auto text = ([history, inviter, &links]() { + if (peerToUser(inviter->id) == MTP::authedId()) { + return lang(history->isMegagroup() ? lng_action_you_joined_group : lng_action_you_joined); + } + links.push_back(MakeShared(inviter)); + if (history->isMegagroup()) { + return lng_action_add_you_group(lt_from, textcmdLink(1, inviter->name)); + } + return lng_action_add_you(lt_from, textcmdLink(1, inviter->name)); + })(); + setServiceText(text, links); } void GoToMessageClickHandler::onClickImpl() const { diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 44c8123443..9773f02193 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -813,7 +813,7 @@ struct HistoryMessageReplyMarkup : public BaseComponent { +struct HistoryServiceDependentData { MsgId msgId = 0; HistoryItem *msg = nullptr; ClickHandlerPtr lnk; }; +struct HistoryServicePinned : public BaseComponent, public HistoryServiceDependentData { +}; + +struct HistoryServiceGameScore : public BaseComponent, public HistoryServiceDependentData { + int score = 0; +}; + namespace HistoryLayout { class ServiceMessagePainter; } // namespace HistoryLayout @@ -2776,18 +2784,16 @@ public: return _create(history, msgId, date, msg, flags, from); } - bool updateDependencyItem() override { - return updatePinned(true); - } + bool updateDependencyItem() override; MsgId dependencyMsgId() const override { - if (const HistoryServicePinned *pinned = Get()) { - return pinned->msgId; + if (auto dependent = GetDependentData()) { + return dependent->msgId; } return 0; } bool notificationReady() const override { - if (const HistoryServicePinned *pinned = Get()) { - return (pinned->msg || !pinned->msgId); + if (auto dependent = GetDependentData()) { + return (dependent->msg || !dependent->msgId); } return true; } @@ -2826,8 +2832,6 @@ public: QString inDialogsText() const override; QString inReplyText() const override; - void setServiceText(const QString &text); - ~HistoryService(); protected: @@ -2840,11 +2844,31 @@ protected: void initDimensions() override; int resizeGetHeight_(int width) override; + using Links = QList; + void setServiceText(const QString &text, const Links &links); + void removeMedia(); +private: + HistoryServiceDependentData *GetDependentData() { + if (auto pinned = Get()) { + return pinned; + } else if (auto gamescore = Get()) { + return gamescore; + } + return nullptr; + } + const HistoryServiceDependentData *GetDependentData() const { + return const_cast(this)->GetDependentData(); + } + bool updateDependent(bool force = false); + bool updateDependentText(); + void clearDependency(); + void setMessageByAction(const MTPmessageAction &action); - bool updatePinned(bool force = false); - bool updatePinnedText(const QString *pfrom = nullptr, QString *ptext = nullptr); + + bool preparePinnedText(const QString &from, QString *outText, Links *outLinks); + bool prepareGameScoreText(const QString &from, QString *outText, Links *outLinks); }; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 4230b4be19..8d27947f31 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_dialogs.h" #include "boxes/confirmbox.h" #include "boxes/photosendbox.h" +#include "boxes/sharebox.h" #include "ui/filedialog.h" #include "ui/toast/toast.h" #include "ui/buttons/history_down_button.h" @@ -34,8 +35,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "history/history_service_layout.h" #include "profile/profile_members_widget.h" #include "core/click_handler_types.h" +#include "stickers/emoji_pan.h" #include "lang.h" #include "application.h" +#include "dropdown.h" #include "mainwidget.h" #include "mainwindow.h" #include "passcodewidget.h" @@ -3054,11 +3057,11 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) connect(&_field, SIGNAL(linksChanged()), this, SLOT(onPreviewCheck())); connect(App::wnd()->windowHandle(), SIGNAL(visibleChanged(bool)), this, SLOT(onWindowVisibleChanged())); connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); - connect(&_emojiPan, SIGNAL(emojiSelected(EmojiPtr)), &_field, SLOT(onEmojiInsert(EmojiPtr))); - connect(&_emojiPan, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*))); - connect(&_emojiPan, SIGNAL(photoSelected(PhotoData*)), this, SLOT(onPhotoSend(PhotoData*))); - connect(&_emojiPan, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*)), this, SLOT(onInlineResultSend(InlineBots::Result*,UserData*))); - connect(&_emojiPan, SIGNAL(updateStickers()), this, SLOT(updateStickers())); + connect(_emojiPan, SIGNAL(emojiSelected(EmojiPtr)), &_field, SLOT(onEmojiInsert(EmojiPtr))); + connect(_emojiPan, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*))); + connect(_emojiPan, SIGNAL(photoSelected(PhotoData*)), this, SLOT(onPhotoSend(PhotoData*))); + connect(_emojiPan, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*)), this, SLOT(onInlineResultSend(InlineBots::Result*,UserData*))); + connect(_emojiPan, SIGNAL(updateStickers()), this, SLOT(updateStickers())); connect(&_sendActionStopTimer, SIGNAL(timeout()), this, SLOT(onCancelSendAction())); connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreviewTimeout())); if (audioCapture()) { @@ -3129,25 +3132,25 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) _silent.hide(); _cmdStart.hide(); - _attachDocument.installEventFilter(&_attachType); - _attachPhoto.installEventFilter(&_attachType); - _attachEmoji.installEventFilter(&_emojiPan); + _attachDocument.installEventFilter(_attachType); + _attachPhoto.installEventFilter(_attachType); + _attachEmoji.installEventFilter(_emojiPan); connect(&_kbShow, SIGNAL(clicked()), this, SLOT(onKbToggle())); connect(&_kbHide, SIGNAL(clicked()), this, SLOT(onKbToggle())); connect(&_cmdStart, SIGNAL(clicked()), this, SLOT(onCmdStart())); - connect(_attachType.addButton(new IconedButton(this, st::dropdownAttachDocument, lang(lng_attach_file))), SIGNAL(clicked()), this, SLOT(onDocumentSelect())); - connect(_attachType.addButton(new IconedButton(this, st::dropdownAttachPhoto, lang(lng_attach_photo))), SIGNAL(clicked()), this, SLOT(onPhotoSelect())); - _attachType.hide(); - _emojiPan.hide(); - _attachDragDocument.hide(); - _attachDragPhoto.hide(); + connect(_attachType->addButton(new IconedButton(this, st::dropdownAttachDocument, lang(lng_attach_file))), SIGNAL(clicked()), this, SLOT(onDocumentSelect())); + connect(_attachType->addButton(new IconedButton(this, st::dropdownAttachPhoto, lang(lng_attach_photo))), SIGNAL(clicked()), this, SLOT(onPhotoSelect())); + _attachType->hide(); + _emojiPan->hide(); + _attachDragDocument->hide(); + _attachDragPhoto->hide(); _topShadow.hide(); - connect(&_attachDragDocument, SIGNAL(dropped(const QMimeData*)), this, SLOT(onDocumentDrop(const QMimeData*))); - connect(&_attachDragPhoto, SIGNAL(dropped(const QMimeData*)), this, SLOT(onPhotoDrop(const QMimeData*))); + connect(_attachDragDocument, SIGNAL(dropped(const QMimeData*)), this, SLOT(onDocumentDrop(const QMimeData*))); + connect(_attachDragPhoto, SIGNAL(dropped(const QMimeData*)), this, SLOT(onPhotoDrop(const QMimeData*))); connect(&_updateEditTimeLeftDisplay, SIGNAL(timeout()), this, SLOT(updateField())); @@ -3156,7 +3159,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) void HistoryWidget::start() { connect(App::main(), SIGNAL(stickersUpdated()), this, SLOT(onStickersUpdated())); - connect(App::main(), SIGNAL(savedGifsUpdated()), &_emojiPan, SLOT(refreshSavedGifs())); + connect(App::main(), SIGNAL(savedGifsUpdated()), _emojiPan, SLOT(refreshSavedGifs())); updateRecentStickers(); if (App::main()) emit App::main()->savedGifsUpdated(); @@ -3165,7 +3168,7 @@ void HistoryWidget::start() { } void HistoryWidget::onStickersUpdated() { - _emojiPan.refreshStickers(); + _emojiPan->refreshStickers(); updateStickersByEmoji(); } @@ -3227,9 +3230,9 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { inlineBotChanged(); } if (_inlineBot->username == cInlineGifBotUsername() && query.isEmpty()) { - _emojiPan.clearInlineBot(); + _emojiPan->clearInlineBot(); } else { - _emojiPan.queryInlineBot(_inlineBot, _peer, query); + _emojiPan->queryInlineBot(_inlineBot, _peer, query); } if (!_fieldAutocomplete->isHidden()) { _fieldAutocomplete->hideStart(); @@ -3449,11 +3452,11 @@ void HistoryWidget::updateSendAction(History *history, SendActionType type, int3 } void HistoryWidget::updateRecentStickers() { - _emojiPan.refreshStickers(); + _emojiPan->refreshStickers(); } void HistoryWidget::stickersInstalled(uint64 setId) { - _emojiPan.stickersInstalled(setId); + _emojiPan->stickersInstalled(setId); } void HistoryWidget::sendActionDone(const MTPBool &result, mtpRequestId req) { @@ -3527,7 +3530,8 @@ void HistoryWidget::updateStickers() { } if (!Global::LastRecentStickersUpdate() || now >= Global::LastRecentStickersUpdate() + StickersUpdateTimeout) { if (!_recentStickersUpdateRequest) { - _recentStickersUpdateRequest = MTP::send(MTPmessages_GetRecentStickers(MTP_int(Local::countRecentStickersHash())), rpcDone(&HistoryWidget::recentStickersGot), rpcFail(&HistoryWidget::recentStickersFailed)); + MTPmessages_GetRecentStickers::Flags flags = 0; + _recentStickersUpdateRequest = MTP::send(MTPmessages_GetRecentStickers(MTP_flags(flags), MTP_int(Local::countRecentStickersHash())), rpcDone(&HistoryWidget::recentStickersGot), rpcFail(&HistoryWidget::recentStickersFailed)); } } if (!Global::LastFeaturedStickersUpdate() || now >= Global::LastFeaturedStickersUpdate() + StickersUpdateTimeout) { @@ -3833,14 +3837,14 @@ void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stic _featuredStickersUpdateRequest = 0; if (stickers.type() != mtpc_messages_featuredStickers) return; - auto &d(stickers.c_messages_featuredStickers()); + auto &d = stickers.c_messages_featuredStickers(); OrderedSet unread; for_const (auto &unreadSetId, d.vunread.c_vector().v) { unread.insert(unreadSetId.v); } - auto &d_sets(d.vsets.c_vector().v); + auto &d_sets = d.vsets.c_vector().v; auto &setsOrder = Global::RefFeaturedStickerSetsOrder(); setsOrder.clear(); @@ -3851,37 +3855,53 @@ void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stic set.flags &= ~MTPDstickerSet_ClientFlag::f_featured; // mark for removing } for (int i = 0, l = d_sets.size(); i != l; ++i) { - if (d_sets.at(i).type() == mtpc_stickerSetCovered && d_sets.at(i).c_stickerSetCovered().vset.type() == mtpc_stickerSet) { - const auto &set(d_sets.at(i).c_stickerSetCovered().vset.c_stickerSet()); - auto it = sets.find(set.vid.v); - QString title = stickerSetTitle(set); + auto &setData = d_sets[i]; + const MTPDstickerSet *set = nullptr; + switch (setData.type()) { + case mtpc_stickerSetCovered: { + auto &d = setData.c_stickerSetCovered(); + if (d.vset.type() == mtpc_stickerSet) { + set = &d.vset.c_stickerSet(); + } + } break; + case mtpc_stickerSetMultiCovered: { + auto &d = setData.c_stickerSetMultiCovered(); + if (d.vset.type() == mtpc_stickerSet) { + set = &d.vset.c_stickerSet(); + } + } break; + } + + if (set) { + auto it = sets.find(set->vid.v); + QString title = stickerSetTitle(*set); if (it == sets.cend()) { auto setClientFlags = MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded; - if (unread.contains(set.vid.v)) { + if (unread.contains(set->vid.v)) { setClientFlags |= MTPDstickerSet_ClientFlag::f_unread; } - it = sets.insert(set.vid.v, Stickers::Set(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | setClientFlags)); + it = sets.insert(set->vid.v, Stickers::Set(set->vid.v, set->vaccess_hash.v, title, qs(set->vshort_name), set->vcount.v, set->vhash.v, set->vflags.v | setClientFlags)); } else { - it->access = set.vaccess_hash.v; + it->access = set->vaccess_hash.v; it->title = title; - it->shortName = qs(set.vshort_name); + it->shortName = qs(set->vshort_name); auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special); - it->flags = set.vflags.v | clientFlags; + it->flags = set->vflags.v | clientFlags; it->flags |= MTPDstickerSet_ClientFlag::f_featured; if (unread.contains(it->id)) { it->flags |= MTPDstickerSet_ClientFlag::f_unread; } else { it->flags &= ~MTPDstickerSet_ClientFlag::f_unread; } - if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) { - it->count = set.vcount.v; - it->hash = set.vhash.v; + if (it->count != set->vcount.v || it->hash != set->vhash.v || it->emoji.isEmpty()) { + it->count = set->vcount.v; + it->hash = set->vhash.v; it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set } } - setsOrder.push_back(set.vid.v); + setsOrder.push_back(set->vid.v); if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) { - setsToRequest.insert(set.vid.v, set.vaccess_hash.v); + setsToRequest.insert(set->vid.v, set->vaccess_hash.v); } } } @@ -4363,11 +4383,11 @@ void HistoryWidget::updateNotifySettings() { } bool HistoryWidget::contentOverlapped(const QRect &globalRect) { - return (_attachDragDocument.overlaps(globalRect) || - _attachDragPhoto.overlaps(globalRect) || - _attachType.overlaps(globalRect) || + return (_attachDragDocument->overlaps(globalRect) || + _attachDragPhoto->overlaps(globalRect) || + _attachType->overlaps(globalRect) || _fieldAutocomplete->overlaps(globalRect) || - _emojiPan.overlaps(globalRect)); + _emojiPan->overlaps(globalRect)); } void HistoryWidget::updateReportSpamStatus() { @@ -4499,8 +4519,8 @@ void HistoryWidget::updateControlsVisibility() { _kbShow.hide(); _kbHide.hide(); _cmdStart.hide(); - _attachType.hide(); - _emojiPan.hide(); + _attachType->hide(); + _emojiPan->hide(); if (_pinnedBar) { _pinnedBar->cancel.hide(); _pinnedBar->shadow.hide(); @@ -4562,8 +4582,8 @@ void HistoryWidget::updateControlsVisibility() { _kbShow.hide(); _kbHide.hide(); _cmdStart.hide(); - _attachType.hide(); - _emojiPan.hide(); + _attachType->hide(); + _emojiPan->hide(); if (!_field.isHidden()) { _field.hide(); resizeEvent(0); @@ -4698,8 +4718,8 @@ void HistoryWidget::updateControlsVisibility() { _kbShow.hide(); _kbHide.hide(); _cmdStart.hide(); - _attachType.hide(); - _emojiPan.hide(); + _attachType->hide(); + _emojiPan->hide(); _kbScroll.hide(); if (!_field.isHidden()) { _field.hide(); @@ -5240,8 +5260,8 @@ void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) { onDraftSave(); if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart(); - if (!_attachType.isHidden()) _attachType.hideStart(); - if (!_emojiPan.isHidden()) _emojiPan.hideStart(); + if (!_attachType->isHidden()) _attachType->hideStart(); + if (!_emojiPan->isHidden()) _emojiPan->hideStart(); if (replyTo < 0) cancelReply(lastKeyboardUsed); if (_previewData && _previewData->pendingTill) previewCancel(); @@ -5569,7 +5589,7 @@ void HistoryWidget::onPhotoSelect() { _attachDocument.clearState(); _attachDocument.hide(); _attachPhoto.show(); - _attachType.fastHide(); + _attachType->fastHide(); if (cDefaultAttach() != dbidaPhoto) { cSetDefaultAttach(dbidaPhoto); @@ -5597,7 +5617,7 @@ void HistoryWidget::onDocumentSelect() { _attachPhoto.clearState(); _attachPhoto.hide(); _attachDocument.show(); - _attachType.fastHide(); + _attachType->fastHide(); if (cDefaultAttach() != dbidaDocument) { cSetDefaultAttach(dbidaDocument); @@ -5634,14 +5654,14 @@ void HistoryWidget::dragEnterEvent(QDragEnterEvent *e) { } void HistoryWidget::dragLeaveEvent(QDragLeaveEvent *e) { - if (_attachDrag != DragStateNone || !_attachDragPhoto.isHidden() || !_attachDragDocument.isHidden()) { + if (_attachDrag != DragStateNone || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) { _attachDrag = DragStateNone; updateDragAreas(); } } void HistoryWidget::leaveEvent(QEvent *e) { - if (_attachDrag != DragStateNone || !_attachDragPhoto.isHidden() || !_attachDragDocument.isHidden()) { + if (_attachDrag != DragStateNone || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) { _attachDrag = DragStateNone; updateDragAreas(); } @@ -5689,7 +5709,7 @@ void HistoryWidget::mouseReleaseEvent(QMouseEvent *e) { _replyForwardPressed = false; update(0, _field.y() - st::sendPadding - st::replyHeight, width(), st::replyHeight); } - if (_attachDrag != DragStateNone || !_attachDragPhoto.isHidden() || !_attachDragDocument.isHidden()) { + if (_attachDrag != DragStateNone || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) { _attachDrag = DragStateNone; updateDragAreas(); } @@ -5763,8 +5783,28 @@ void HistoryWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button bool lastKeyboardUsed = (_keyboard.forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard.forMsgId() == FullMsgId(_channel, msg->id)); - BotCallbackInfo info = { msg->fullId(), row, col }; - button->requestId = MTP::send(MTPmessages_GetBotCallbackAnswer(_peer->input, MTP_int(msg->id), MTP_bytes(button->data)), rpcDone(&HistoryWidget::botCallbackDone, info), rpcFail(&HistoryWidget::botCallbackFail, info)); + auto bot = msg->viaBot(); + if (!bot) { + bot = msg->from()->asUser(); + if (bot && !bot->botInfo) { + bot = nullptr; + } + } + + using ButtonType = HistoryMessageReplyMarkup::Button::Type; + BotCallbackInfo info = { bot, msg->fullId(), row, col, (button->type == ButtonType::Game) }; + MTPmessages_GetBotCallbackAnswer::Flags flags = 0; + QByteArray sendData; + int32 sendGameId = 0; + if (info.game) { + flags = MTPmessages_GetBotCallbackAnswer::Flag::f_game_id; + auto strData = QString::fromUtf8(button->data); + sendGameId = strData.midRef(0, strData.indexOf(',')).toInt(); + } else if (button->type == ButtonType::Callback) { + flags = MTPmessages_GetBotCallbackAnswer::Flag::f_data; + sendData = button->data; + } + button->requestId = MTP::send(MTPmessages_GetBotCallbackAnswer(MTP_flags(flags), _peer->input, MTP_int(msg->id), MTP_bytes(sendData), MTP_int(sendGameId)), rpcDone(&HistoryWidget::botCallbackDone, info), rpcFail(&HistoryWidget::botCallbackFail, info)); Ui::repaintHistoryItem(msg); if (_replyToId == msg->id) { @@ -5777,7 +5817,7 @@ void HistoryWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button } void HistoryWidget::botCallbackDone(BotCallbackInfo info, const MTPmessages_BotCallbackAnswer &answer, mtpRequestId req) { - if (HistoryItem *item = App::histItemById(info.msgId)) { + if (auto item = App::histItemById(info.msgId)) { if (auto markup = item->Get()) { if (info.row < markup->rows.size() && info.col < markup->rows.at(info.row).size()) { if (markup->rows.at(info.row).at(info.col).requestId == req) { @@ -5788,7 +5828,7 @@ void HistoryWidget::botCallbackDone(BotCallbackInfo info, const MTPmessages_BotC } } if (answer.type() == mtpc_messages_botCallbackAnswer) { - const auto &answerData(answer.c_messages_botCallbackAnswer()); + auto &answerData = answer.c_messages_botCallbackAnswer(); if (answerData.has_message()) { if (answerData.is_alert()) { Ui::showLayer(new InformBox(qs(answerData.vmessage))); @@ -5798,7 +5838,13 @@ void HistoryWidget::botCallbackDone(BotCallbackInfo info, const MTPmessages_BotC Ui::Toast::Show(App::wnd(), toast); } } else if (answerData.has_url()) { - UrlClickHandler(qs(answerData.vurl)).onClick(Qt::LeftButton); + auto url = qs(answerData.vurl); + if (info.game) { + url = appendShareGameScoreUrl(url, info.msgId); + BotGameUrlClickHandler(info.bot, url).onClick(Qt::LeftButton); + } else { + UrlClickHandler(url).onClick(Qt::LeftButton); + } } } } @@ -5928,24 +5974,24 @@ void HistoryWidget::updateDragAreas() { _field.setAcceptDrops(!_attachDrag); switch (_attachDrag) { case DragStateNone: - _attachDragDocument.otherLeave(); - _attachDragPhoto.otherLeave(); + _attachDragDocument->otherLeave(); + _attachDragPhoto->otherLeave(); break; case DragStateFiles: - _attachDragDocument.otherEnter(); - _attachDragDocument.setText(lang(lng_drag_files_here), lang(lng_drag_to_send_files)); - _attachDragPhoto.fastHide(); + _attachDragDocument->otherEnter(); + _attachDragDocument->setText(lang(lng_drag_files_here), lang(lng_drag_to_send_files)); + _attachDragPhoto->fastHide(); break; case DragStatePhotoFiles: - _attachDragDocument.otherEnter(); - _attachDragDocument.setText(lang(lng_drag_images_here), lang(lng_drag_to_send_no_compression)); - _attachDragPhoto.otherEnter(); - _attachDragPhoto.setText(lang(lng_drag_photos_here), lang(lng_drag_to_send_quick)); + _attachDragDocument->otherEnter(); + _attachDragDocument->setText(lang(lng_drag_images_here), lang(lng_drag_to_send_no_compression)); + _attachDragPhoto->otherEnter(); + _attachDragPhoto->setText(lang(lng_drag_photos_here), lang(lng_drag_to_send_quick)); break; case DragStateImage: - _attachDragDocument.fastHide(); - _attachDragPhoto.otherEnter(); - _attachDragPhoto.setText(lang(lng_drag_images_here), lang(lng_drag_to_send_quick)); + _attachDragDocument->fastHide(); + _attachDragPhoto->otherEnter(); + _attachDragPhoto->setText(lang(lng_drag_images_here), lang(lng_drag_to_send_quick)); break; }; resizeEvent(0); @@ -6427,8 +6473,8 @@ void HistoryWidget::moveFieldControls() { right = w; _fieldBarCancel.move(right - _fieldBarCancel.width(), _field.y() - st::sendPadding - _fieldBarCancel.height()); - _attachType.move(0, _attachDocument.y() - _attachType.height()); - _emojiPan.moveBottom(_attachEmoji.y()); + _attachType->move(0, _attachDocument.y() - _attachType->height()); + _emojiPan->moveBottom(_attachEmoji.y()); _botStart.setGeometry(0, bottom - _botStart.height(), w, _botStart.height()); _unblock.setGeometry(0, bottom - _unblock.height(), w, _unblock.height()); @@ -6458,7 +6504,7 @@ void HistoryWidget::clearInlineBot() { inlineBotChanged(); _field.finishPlaceholder(); } - _emojiPan.clearInlineBot(); + _emojiPan->clearInlineBot(); onCheckFieldAutocomplete(); } @@ -6679,7 +6725,9 @@ void HistoryWidget::onPhotoUploaded(const FullMsgId &newId, bool silent, const M sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } auto caption = item->getMedia() ? item->getMedia()->getCaption() : TextWithEntities(); - hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedPhoto(file, MTP_string(caption.text)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); + MTPDinputMediaUploadedPhoto::Flags mediaFlags = 0; + auto media = MTP_inputMediaUploadedPhoto(MTP_flags(mediaFlags), file, MTP_string(caption.text), MTPVector()); + hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), media, MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); } } @@ -6697,7 +6745,8 @@ namespace { if (document->type == AnimatedDocument) { attributes.push_back(MTP_documentAttributeAnimated()); } else if (document->type == StickerDocument && document->sticker()) { - attributes.push_back(MTP_documentAttributeSticker(MTP_string(document->sticker()->alt), document->sticker()->set)); + MTPDdocumentAttributeSticker::Flags stickerFlags = 0; + attributes.push_back(MTP_documentAttributeSticker(MTP_flags(stickerFlags), MTP_string(document->sticker()->alt), document->sticker()->set, MTPMaskCoords())); } else if (document->type == SongDocument && document->song()) { attributes.push_back(MTP_documentAttributeAudio(MTP_flags(MTPDdocumentAttributeAudio::Flag::f_title | MTPDdocumentAttributeAudio::Flag::f_performer), MTP_int(document->song()->duration), MTP_string(document->song()->title), MTP_string(document->song()->performer), MTPstring())); } else if (document->type == VoiceDocument && document->voice()) { @@ -6728,7 +6777,9 @@ void HistoryWidget::onDocumentUploaded(const FullMsgId &newId, bool silent, cons sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } auto caption = item->getMedia() ? item->getMedia()->getCaption() : TextWithEntities(); - hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedDocument(file, MTP_string(document->mime), _composeDocumentAttributes(document), MTP_string(caption.text)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); + MTPDinputMediaUploadedDocument::Flags mediaFlags = 0; + auto media = MTP_inputMediaUploadedDocument(MTP_flags(mediaFlags), file, MTP_string(document->mime), _composeDocumentAttributes(document), MTP_string(caption.text), MTPVector()); + hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), media, MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); } } } @@ -6754,7 +6805,9 @@ void HistoryWidget::onThumbDocumentUploaded(const FullMsgId &newId, bool silent, sendFlags |= MTPmessages_SendMedia::Flag::f_silent; } auto caption = item->getMedia() ? item->getMedia()->getCaption() : TextWithEntities(); - hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedThumbDocument(file, thumb, MTP_string(document->mime), _composeDocumentAttributes(document), MTP_string(caption.text)), MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); + MTPDinputMediaUploadedThumbDocument::Flags mediaFlags = 0; + auto media = MTP_inputMediaUploadedThumbDocument(MTP_flags(mediaFlags), file, thumb, MTP_string(document->mime), _composeDocumentAttributes(document), MTP_string(caption.text), MTPVector()); + hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_flags(sendFlags), item->history()->peer->input, MTP_int(replyTo), media, MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId); } } } @@ -6925,15 +6978,15 @@ void HistoryWidget::onUpdateHistoryItems() { } void HistoryWidget::ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { - _emojiPan.ui_repaintInlineItem(layout); + _emojiPan->ui_repaintInlineItem(layout); } bool HistoryWidget::ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) { - return _emojiPan.ui_isInlineItemVisible(layout); + return _emojiPan->ui_isInlineItemVisible(layout); } bool HistoryWidget::ui_isInlineItemBeingChosen() { - return _emojiPan.ui_isInlineItemBeingChosen(); + return _emojiPan->ui_isInlineItemBeingChosen(); } PeerData *HistoryWidget::ui_getPeerForMouseAction() { @@ -6947,7 +7000,7 @@ void HistoryWidget::notify_historyItemLayoutChanged(const HistoryItem *item) { } void HistoryWidget::notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) { - _emojiPan.notify_inlineItemLayoutChanged(layout); + _emojiPan->notify_inlineItemLayoutChanged(layout); } void HistoryWidget::notify_handlePendingHistoryUpdate() { @@ -6982,25 +7035,25 @@ void HistoryWidget::resizeEvent(QResizeEvent *e) { _historyToEnd->moveToRight(st::historyToDownPosition.x(), _scroll.y() + _scroll.height() - _historyToEnd->height() - st::historyToDownPosition.y()); - _emojiPan.setMaxHeight(height() - st::dropdownDef.padding.top() - st::dropdownDef.padding.bottom() - _attachEmoji.height()); + _emojiPan->setMaxHeight(height() - st::dropdownDef.padding.top() - st::dropdownDef.padding.bottom() - _attachEmoji.height()); if (_membersDropdown) { _membersDropdown->setMaxHeight(countMembersDropdownHeightMax()); } switch (_attachDrag) { case DragStateFiles: - _attachDragDocument.resize(width() - st::dragMargin.left() - st::dragMargin.right(), height() - st::dragMargin.top() - st::dragMargin.bottom()); - _attachDragDocument.move(st::dragMargin.left(), st::dragMargin.top()); + _attachDragDocument->resize(width() - st::dragMargin.left() - st::dragMargin.right(), height() - st::dragMargin.top() - st::dragMargin.bottom()); + _attachDragDocument->move(st::dragMargin.left(), st::dragMargin.top()); break; case DragStatePhotoFiles: - _attachDragDocument.resize(width() - st::dragMargin.left() - st::dragMargin.right(), (height() - st::dragMargin.top() - st::dragMargin.bottom()) / 2); - _attachDragDocument.move(st::dragMargin.left(), st::dragMargin.top()); - _attachDragPhoto.resize(_attachDragDocument.width(), _attachDragDocument.height()); - _attachDragPhoto.move(st::dragMargin.left(), height() - _attachDragPhoto.height() - st::dragMargin.bottom()); + _attachDragDocument->resize(width() - st::dragMargin.left() - st::dragMargin.right(), (height() - st::dragMargin.top() - st::dragMargin.bottom()) / 2); + _attachDragDocument->move(st::dragMargin.left(), st::dragMargin.top()); + _attachDragPhoto->resize(_attachDragDocument->width(), _attachDragDocument->height()); + _attachDragPhoto->move(st::dragMargin.left(), height() - _attachDragPhoto->height() - st::dragMargin.bottom()); break; case DragStateImage: - _attachDragPhoto.resize(width() - st::dragMargin.left() - st::dragMargin.right(), height() - st::dragMargin.top() - st::dragMargin.bottom()); - _attachDragPhoto.move(st::dragMargin.left(), st::dragMargin.top()); + _attachDragPhoto->resize(width() - st::dragMargin.left() - st::dragMargin.right(), height() - st::dragMargin.top() - st::dragMargin.bottom()); + _attachDragPhoto->move(st::dragMargin.left(), st::dragMargin.top()); break; } @@ -7423,8 +7476,8 @@ void HistoryWidget::onFieldTabbed() { } } -void HistoryWidget::onStickerSend(DocumentData *sticker) { - sendExistingDocument(sticker, QString()); +bool HistoryWidget::onStickerSend(DocumentData *sticker) { + return sendExistingDocument(sticker, QString()); } void HistoryWidget::onPhotoSend(PhotoData *photo) { @@ -7497,8 +7550,8 @@ void HistoryWidget::onInlineResultSend(InlineBots::Result *result, UserData *bot } if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart(); - if (!_attachType.isHidden()) _attachType.hideStart(); - if (!_emojiPan.isHidden()) _emojiPan.hideStart(); + if (!_attachType->isHidden()) _attachType->hideStart(); + if (!_emojiPan->isHidden()) _emojiPan->hideStart(); _field.setFocus(); } @@ -7567,10 +7620,10 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() { if (_membersDropdown) { _membersDropdown->raise(); } - _attachType.raise(); - _emojiPan.raise(); - _attachDragDocument.raise(); - _attachDragPhoto.raise(); + _attachType->raise(); + _emojiPan->raise(); + _attachDragDocument->raise(); + _attachDragPhoto->raise(); updatePinnedBar(); result = true; @@ -7611,14 +7664,14 @@ void HistoryWidget::ReplyEditMessageDataCallback::call(ChannelData *channel, Msg } } -void HistoryWidget::sendExistingDocument(DocumentData *doc, const QString &caption) { +bool HistoryWidget::sendExistingDocument(DocumentData *doc, const QString &caption) { if (!_history || !doc || !canSendMessages(_peer)) { - return; + return false; } MTPInputDocument mtpInput = doc->mtpInput(); if (mtpInput.type() == mtpc_inputDocumentEmpty) { - return; + return false; } App::main()->readServerHistory(_history); @@ -7668,10 +7721,11 @@ void HistoryWidget::sendExistingDocument(DocumentData *doc, const QString &capti } if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart(); - if (!_attachType.isHidden()) _attachType.hideStart(); - if (!_emojiPan.isHidden()) _emojiPan.hideStart(); + if (!_attachType->isHidden()) _attachType->hideStart(); + if (!_emojiPan->isHidden()) _emojiPan->hideStart(); _field.setFocus(); + return true; } void HistoryWidget::sendExistingPhoto(PhotoData *photo, const QString &caption) { @@ -7714,8 +7768,8 @@ void HistoryWidget::sendExistingPhoto(PhotoData *photo, const QString &caption) App::historyRegRandom(randomId, newId); if (!_fieldAutocomplete->isHidden()) _fieldAutocomplete->hideStart(); - if (!_attachType.isHidden()) _attachType.hideStart(); - if (!_emojiPan.isHidden()) _emojiPan.hideStart(); + if (!_attachType->isHidden()) _attachType->hideStart(); + if (!_emojiPan->isHidden()) _emojiPan->hideStart(); _field.setFocus(); } diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index d5f6251741..e875851208 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -22,7 +22,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localimageloader.h" #include "ui/boxshadow.h" -#include "dropdown.h" #include "history/history_common.h" #include "history/field_autocomplete.h" #include "window/section_widget.h" @@ -39,6 +38,10 @@ class HistoryDownButton; class InnerDropdown; } // namespace Ui +class Dropdown; +class DragArea; +class EmojiPan; + class HistoryWidget; class HistoryInner : public TWidget, public AbstractTooltipShower { Q_OBJECT @@ -800,7 +803,7 @@ public slots: void onTextChange(); void onFieldTabbed(); - void onStickerSend(DocumentData *sticker); + bool onStickerSend(DocumentData *sticker); void onPhotoSend(PhotoData *photo); void onInlineResultSend(InlineBots::Result *result, UserData *bot); @@ -910,7 +913,7 @@ private: void call(ChannelData *channel, MsgId msgId) const override; }; - void sendExistingDocument(DocumentData *doc, const QString &caption); + bool sendExistingDocument(DocumentData *doc, const QString &caption); void sendExistingPhoto(PhotoData *photo, const QString &caption); void drawField(Painter &p, const QRect &rect); @@ -958,8 +961,10 @@ private: void addMessagesToBack(PeerData *peer, const QVector &messages); struct BotCallbackInfo { + UserData *bot; FullMsgId msgId; int row, col; + bool game; }; void botCallbackDone(BotCallbackInfo info, const MTPmessages_BotCallbackAnswer &answer, mtpRequestId req); bool botCallbackFail(BotCallbackInfo info, const RPCError &error, mtpRequestId req); @@ -1123,10 +1128,10 @@ private: ChildWidget _membersDropdown = { nullptr }; QTimer _membersDropdownShowTimer; - Dropdown _attachType; - EmojiPan _emojiPan; + ChildWidget _attachType; + ChildWidget _emojiPan; DragState _attachDrag = DragStateNone; - DragArea _attachDragDocument, _attachDragPhoto; + ChildWidget _attachDragDocument, _attachDragPhoto; int32 _selCount; // < 0 - text selected, focus list, not _field diff --git a/Telegram/SourceFiles/layerwidget.cpp b/Telegram/SourceFiles/layerwidget.cpp index 1d3b2082a9..722a387bf1 100644 --- a/Telegram/SourceFiles/layerwidget.cpp +++ b/Telegram/SourceFiles/layerwidget.cpp @@ -27,6 +27,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "mainwidget.h" #include "ui/filedialog.h" +#include "styles/style_stickers.h" + +namespace { + +constexpr int kStickerPreviewEmojiLimit = 10; + +} // namespace void LayerWidget::setInnerFocus() { auto focused = App::wnd()->focusWidget(); @@ -339,6 +346,7 @@ void LayerStackWidget::activateLayer(LayerWidget *l) { startShow(); } else { l->show(); + l->showDone(); if (App::wnd()) App::wnd()->setInnerFocus(); updateLayerBox(); } @@ -426,7 +434,8 @@ LayerStackWidget::~LayerStackWidget() { MediaPreviewWidget::MediaPreviewWidget(QWidget *parent) : TWidget(parent) , a_shown(0, 0) -, _a_shown(animation(this, &MediaPreviewWidget::step_shown)) { +, _a_shown(animation(this, &MediaPreviewWidget::step_shown)) +, _emojiSize(EmojiSizes[EIndex + 1] / cIntRetinaFactor()) { setAttribute(Qt::WA_TransparentForMouseEvents); connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); } @@ -435,8 +444,8 @@ void MediaPreviewWidget::paintEvent(QPaintEvent *e) { Painter p(this); QRect r(e->rect()); - const QPixmap &draw(currentImage()); - int w = draw.width() / cIntRetinaFactor(), h = draw.height() / cIntRetinaFactor(); + auto &image = currentImage(); + int w = image.width() / cIntRetinaFactor(), h = image.height() / cIntRetinaFactor(); if (_a_shown.animating()) { float64 shown = a_shown.current(); p.setOpacity(shown); @@ -444,7 +453,17 @@ void MediaPreviewWidget::paintEvent(QPaintEvent *e) { // h = qMax(qRound(h * (st::stickerPreviewMin + ((1. - st::stickerPreviewMin) * shown)) / 2.) * 2 + int(h % 2), 1); } p.fillRect(r, st::stickerPreviewBg); - p.drawPixmap((width() - w) / 2, (height() - h) / 2, draw); + p.drawPixmap((width() - w) / 2, (height() - h) / 2, image); + if (!_emojiList.isEmpty()) { + int emojiCount = _emojiList.size(); + int emojiWidth = emojiCount * _emojiSize + (emojiCount - 1) * st::stickerEmojiSkip; + int emojiLeft = (width() - emojiWidth) / 2; + int esize = _emojiSize * cIntRetinaFactor(); + for_const (auto emoji, _emojiList) { + p.drawPixmapLeft(emojiLeft, (height() - h) / 2 - _emojiSize * 2, width(), App::emojiLarge(), QRect(emoji->x * esize, emoji->y * esize, esize, esize)); + emojiLeft += _emojiSize + st::stickerEmojiSkip; + } + } } void MediaPreviewWidget::resizeEvent(QResizeEvent *e) { @@ -472,6 +491,7 @@ void MediaPreviewWidget::showPreview(DocumentData *document) { startShow(); _photo = nullptr; _document = document; + fillEmojiString(); resetGifAndCache(); } @@ -510,6 +530,42 @@ void MediaPreviewWidget::hidePreview() { resetGifAndCache(); } +void MediaPreviewWidget::fillEmojiString() { + auto getStickerEmojiList = [this](uint64 setId) { + QList result; + auto &sets = Global::StickerSets(); + auto it = sets.constFind(setId); + if (it == sets.cend()) { + return result; + } + for (auto i = it->emoji.cbegin(), e = it->emoji.cend(); i != e; ++i) { + for_const (auto document, *i) { + if (document == _document) { + result.append(i.key()); + if (result.size() >= kStickerPreviewEmojiLimit) { + return result; + } + } + } + } + return result; + }; + + if (auto sticker = _document->sticker()) { + auto &inputSet = sticker->set; + if (inputSet.type() == mtpc_inputStickerSetID) { + _emojiList = getStickerEmojiList(inputSet.c_inputStickerSetID().vid.v); + } else { + _emojiList.clear(); + if (auto emoji = emojiFromText(sticker->alt)) { + _emojiList.append(emoji); + } + } + } else { + _emojiList.clear(); + } +} + void MediaPreviewWidget::resetGifAndCache() { if (_gif) { if (gif()) { diff --git a/Telegram/SourceFiles/layerwidget.h b/Telegram/SourceFiles/layerwidget.h index ffba58d3f7..14d1fbc84e 100644 --- a/Telegram/SourceFiles/layerwidget.h +++ b/Telegram/SourceFiles/layerwidget.h @@ -133,7 +133,6 @@ class MediaPreviewWidget : public TWidget { Q_OBJECT public: - MediaPreviewWidget(QWidget *parent); void paintEvent(QPaintEvent *e); @@ -148,10 +147,10 @@ public: ~MediaPreviewWidget(); private: - QSize currentDimensions() const; QPixmap currentImage() const; void startShow(); + void fillEmojiString(); void resetGifAndCache(); anim::fvalue a_shown; @@ -163,6 +162,9 @@ private: return (!_gif || _gif == Media::Clip::BadReader) ? false : true; } + int _emojiSize; + QList _emojiList; + void clipCallback(Media::Clip::Notification notification); enum CacheStatus { diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp index 1da43d5c10..3e15bf9cd3 100644 --- a/Telegram/SourceFiles/localimageloader.cpp +++ b/Telegram/SourceFiles/localimageloader.cpp @@ -386,13 +386,15 @@ void FileLoadTask::process() { full.save(&buffer, "JPG", 77); } - photo = MTP_photo(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_vector(photoSizes)); + MTPDphoto::Flags photoFlags = 0; + photo = MTP_photo(MTP_flags(photoFlags), MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_vector(photoSizes)); } QByteArray thumbFormat = "JPG"; int32 thumbQuality = 87; if (!animated && filemime == stickerMime && w > 0 && h > 0 && w <= StickerMaxSize && h <= StickerMaxSize && filesize < StickerInMemory) { - attributes.push_back(MTP_documentAttributeSticker(MTP_string(""), MTP_inputStickerSetEmpty())); + MTPDdocumentAttributeSticker::Flags stickerFlags = 0; + attributes.push_back(MTP_documentAttributeSticker(MTP_flags(stickerFlags), MTP_string(""), MTP_inputStickerSetEmpty(), MTPMaskCoords())); thumbFormat = "webp"; thumbname = qsl("thumb.webp"); } diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 848fbedb5a..28d1bece68 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -34,4243 +34,4353 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "playerwidget.h" #include "apiwrap.h" +namespace Local { namespace { - typedef quint64 FileKey; - static const char tdfMagic[] = { 'T', 'D', 'F', '$' }; - static const int32 tdfMagicLen = sizeof(tdfMagic); +typedef quint64 FileKey; - QString toFilePart(FileKey val) { - QString result; - result.reserve(0x10); - for (int32 i = 0; i < 0x10; ++i) { - uchar v = (val & 0x0F); - result.push_back((v < 0x0A) ? ('0' + v) : ('A' + (v - 0x0A))); - val >>= 4; - } - return result; +static const char tdfMagic[] = { 'T', 'D', 'F', '$' }; +static const int32 tdfMagicLen = sizeof(tdfMagic); + +QString toFilePart(FileKey val) { + QString result; + result.reserve(0x10); + for (int32 i = 0; i < 0x10; ++i) { + uchar v = (val & 0x0F); + result.push_back((v < 0x0A) ? ('0' + v) : ('A' + (v - 0x0A))); + val >>= 4; + } + return result; +} + +QString _basePath, _userBasePath; + +bool _started = false; +internal::Manager *_manager = nullptr; +TaskQueue *_localLoader = nullptr; + +bool _working() { + return _manager && !_basePath.isEmpty(); +} + +bool _userWorking() { + return _manager && !_basePath.isEmpty() && !_userBasePath.isEmpty(); +} + +enum FileOptions { + UserPath = 0x01, + SafePath = 0x02, +}; + +bool keyAlreadyUsed(QString &name, int options = UserPath | SafePath) { + name += '0'; + if (QFileInfo(name).exists()) return true; + if (options & SafePath) { + name[name.size() - 1] = '1'; + return QFileInfo(name).exists(); + } + return false; +} + +FileKey genKey(int options = UserPath | SafePath) { + if (options & UserPath) { + if (!_userWorking()) return 0; + } else { + if (!_working()) return 0; } - QString _basePath, _userBasePath; + FileKey result; + QString base = (options & UserPath) ? _userBasePath : _basePath, path; + path.reserve(base.size() + 0x11); + path += base; + do { + result = rand_value(); + path.resize(base.size()); + path += toFilePart(result); + } while (!result || keyAlreadyUsed(path, options)); - bool _started = false; - _local_inner::Manager *_manager = 0; - TaskQueue *_localLoader = 0; + return result; +} - bool _working() { - return _manager && !_basePath.isEmpty(); +void clearKey(const FileKey &key, int options = UserPath | SafePath) { + if (options & UserPath) { + if (!_userWorking()) return; + } else { + if (!_working()) return; } - bool _userWorking() { - return _manager && !_basePath.isEmpty() && !_userBasePath.isEmpty(); + QString base = (options & UserPath) ? _userBasePath : _basePath, name; + name.reserve(base.size() + 0x11); + name.append(base).append(toFilePart(key)).append('0'); + QFile::remove(name); + if (options & SafePath) { + name[name.size() - 1] = '1'; + QFile::remove(name); } +} - enum FileOptions { - UserPath = 0x01, - SafePath = 0x02, - }; - - bool keyAlreadyUsed(QString &name, int options = UserPath | SafePath) { - name += '0'; - if (QFileInfo(name).exists()) return true; - if (options & SafePath) { - name[name.size() - 1] = '1'; - return QFileInfo(name).exists(); - } +bool _checkStreamStatus(QDataStream &stream) { + if (stream.status() != QDataStream::Ok) { + LOG(("Bad data stream status: %1").arg(stream.status())); return false; } + return true; +} - FileKey genKey(int options = UserPath | SafePath) { - if (options & UserPath) { - if (!_userWorking()) return 0; - } else { - if (!_working()) return 0; - } +QByteArray _settingsSalt, _passKeySalt, _passKeyEncrypted; - FileKey result; - QString base = (options & UserPath) ? _userBasePath : _basePath, path; - path.reserve(base.size() + 0x11); - path += base; - do { - result = rand_value(); - path.resize(base.size()); - path += toFilePart(result); - } while (!result || keyAlreadyUsed(path, options)); +MTP::AuthKey _oldKey, _settingsKey, _passKey, _localKey; +void createLocalKey(const QByteArray &pass, QByteArray *salt, MTP::AuthKey *result) { + uchar key[LocalEncryptKeySize] = { 0 }; + int32 iterCount = pass.size() ? LocalEncryptIterCount : LocalEncryptNoPwdIterCount; // dont slow down for no password + QByteArray newSalt; + if (!salt) { + newSalt.resize(LocalEncryptSaltSize); + memset_rand(newSalt.data(), newSalt.size()); + salt = &newSalt; - return result; + cSetLocalSalt(newSalt); } - void clearKey(const FileKey &key, int options = UserPath | SafePath) { + PKCS5_PBKDF2_HMAC_SHA1(pass.constData(), pass.size(), (uchar*)salt->data(), salt->size(), iterCount, LocalEncryptKeySize, key); + + result->setKey(key); +} + +struct FileReadDescriptor { + FileReadDescriptor() : version(0) { + } + int32 version; + QByteArray data; + QBuffer buffer; + QDataStream stream; + ~FileReadDescriptor() { + if (version) { + stream.setDevice(0); + if (buffer.isOpen()) buffer.close(); + buffer.setBuffer(0); + } + } +}; + +struct EncryptedDescriptor { + EncryptedDescriptor() { + } + EncryptedDescriptor(uint32 size) { + uint32 fullSize = sizeof(uint32) + size; + if (fullSize & 0x0F) fullSize += 0x10 - (fullSize & 0x0F); + data.reserve(fullSize); + + data.resize(sizeof(uint32)); + buffer.setBuffer(&data); + buffer.open(QIODevice::WriteOnly); + buffer.seek(sizeof(uint32)); + stream.setDevice(&buffer); + stream.setVersion(QDataStream::Qt_5_1); + } + QByteArray data; + QBuffer buffer; + QDataStream stream; + void finish() { + if (stream.device()) stream.setDevice(0); + if (buffer.isOpen()) buffer.close(); + buffer.setBuffer(0); + } + ~EncryptedDescriptor() { + finish(); + } +}; + +struct FileWriteDescriptor { + FileWriteDescriptor(const FileKey &key, int options = UserPath | SafePath) : dataSize(0) { + init(toFilePart(key), options); + } + FileWriteDescriptor(const QString &name, int options = UserPath | SafePath) : dataSize(0) { + init(name, options); + } + void init(const QString &name, int options) { if (options & UserPath) { if (!_userWorking()) return; } else { if (!_working()) return; } - QString base = (options & UserPath) ? _userBasePath : _basePath, name; - name.reserve(base.size() + 0x11); - name.append(base).append(toFilePart(key)).append('0'); - QFile::remove(name); - if (options & SafePath) { - name[name.size() - 1] = '1'; - QFile::remove(name); - } - } - - bool _checkStreamStatus(QDataStream &stream) { - if (stream.status() != QDataStream::Ok) { - LOG(("Bad data stream status: %1").arg(stream.status())); - return false; - } - return true; - } - - QByteArray _settingsSalt, _passKeySalt, _passKeyEncrypted; - - MTP::AuthKey _oldKey, _settingsKey, _passKey, _localKey; - void createLocalKey(const QByteArray &pass, QByteArray *salt, MTP::AuthKey *result) { - uchar key[LocalEncryptKeySize] = { 0 }; - int32 iterCount = pass.size() ? LocalEncryptIterCount : LocalEncryptNoPwdIterCount; // dont slow down for no password - QByteArray newSalt; - if (!salt) { - newSalt.resize(LocalEncryptSaltSize); - memset_rand(newSalt.data(), newSalt.size()); - salt = &newSalt; - - cSetLocalSalt(newSalt); - } - - PKCS5_PBKDF2_HMAC_SHA1(pass.constData(), pass.size(), (uchar*)salt->data(), salt->size(), iterCount, LocalEncryptKeySize, key); - - result->setKey(key); - } - - struct FileReadDescriptor { - FileReadDescriptor() : version(0) { - } - int32 version; - QByteArray data; - QBuffer buffer; - QDataStream stream; - ~FileReadDescriptor() { - if (version) { - stream.setDevice(0); - if (buffer.isOpen()) buffer.close(); - buffer.setBuffer(0); - } - } - }; - - struct EncryptedDescriptor { - EncryptedDescriptor() { - } - EncryptedDescriptor(uint32 size) { - uint32 fullSize = sizeof(uint32) + size; - if (fullSize & 0x0F) fullSize += 0x10 - (fullSize & 0x0F); - data.reserve(fullSize); - - data.resize(sizeof(uint32)); - buffer.setBuffer(&data); - buffer.open(QIODevice::WriteOnly); - buffer.seek(sizeof(uint32)); - stream.setDevice(&buffer); - stream.setVersion(QDataStream::Qt_5_1); - } - QByteArray data; - QBuffer buffer; - QDataStream stream; - void finish() { - if (stream.device()) stream.setDevice(0); - if (buffer.isOpen()) buffer.close(); - buffer.setBuffer(0); - } - ~EncryptedDescriptor() { - finish(); - } - }; - - struct FileWriteDescriptor { - FileWriteDescriptor(const FileKey &key, int options = UserPath | SafePath) : dataSize(0) { - init(toFilePart(key), options); - } - FileWriteDescriptor(const QString &name, int options = UserPath | SafePath) : dataSize(0) { - init(name, options); - } - void init(const QString &name, int options) { - if (options & UserPath) { - if (!_userWorking()) return; - } else { - if (!_working()) return; - } - - // detect order of read attempts and file version - QString toTry[2]; - toTry[0] = ((options & UserPath) ? _userBasePath : _basePath) + name + '0'; - if (options & SafePath) { - toTry[1] = ((options & UserPath) ? _userBasePath : _basePath) + name + '1'; - QFileInfo toTry0(toTry[0]); - QFileInfo toTry1(toTry[1]); - if (toTry0.exists()) { - if (toTry1.exists()) { - QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified(); - if (mod0 > mod1) { - qSwap(toTry[0], toTry[1]); - } - } else { - qSwap(toTry[0], toTry[1]); - } - toDelete = toTry[1]; - } else if (toTry1.exists()) { - toDelete = toTry[1]; - } - } - - file.setFileName(toTry[0]); - if (file.open(QIODevice::WriteOnly)) { - file.write(tdfMagic, tdfMagicLen); - qint32 version = AppVersion; - file.write((const char*)&version, sizeof(version)); - - stream.setDevice(&file); - stream.setVersion(QDataStream::Qt_5_1); - } - } - bool writeData(const QByteArray &data) { - if (!file.isOpen()) return false; - - stream << data; - quint32 len = data.isNull() ? 0xffffffff : data.size(); - if (QSysInfo::ByteOrder != QSysInfo::BigEndian) { - len = qbswap(len); - } - md5.feed(&len, sizeof(len)); - md5.feed(data.constData(), data.size()); - dataSize += sizeof(len) + data.size(); - - return true; - } - static QByteArray prepareEncrypted(EncryptedDescriptor &data, const MTP::AuthKey &key = _localKey) { - data.finish(); - QByteArray &toEncrypt(data.data); - - // prepare for encryption - uint32 size = toEncrypt.size(), fullSize = size; - if (fullSize & 0x0F) { - fullSize += 0x10 - (fullSize & 0x0F); - toEncrypt.resize(fullSize); - memset_rand(toEncrypt.data() + size, fullSize - size); - } - *(uint32*)toEncrypt.data() = size; - QByteArray encrypted(0x10 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data - hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data()); - MTP::aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 0x10, fullSize, &key, encrypted.constData()); - - return encrypted; - } - bool writeEncrypted(EncryptedDescriptor &data, const MTP::AuthKey &key = _localKey) { - return writeData(prepareEncrypted(data, key)); - } - void finish() { - if (!file.isOpen()) return; - - stream.setDevice(0); - - md5.feed(&dataSize, sizeof(dataSize)); - qint32 version = AppVersion; - md5.feed(&version, sizeof(version)); - md5.feed(tdfMagic, tdfMagicLen); - file.write((const char*)md5.result(), 0x10); - file.close(); - - if (!toDelete.isEmpty()) { - QFile::remove(toDelete); - } - } - QFile file; - QDataStream stream; - - QString toDelete; - - HashMd5 md5; - int32 dataSize; - - ~FileWriteDescriptor() { - finish(); - } - }; - - bool readFile(FileReadDescriptor &result, const QString &name, int options = UserPath | SafePath) { - if (options & UserPath) { - if (!_userWorking()) return false; - } else { - if (!_working()) return false; - } - - // detect order of read attempts + // detect order of read attempts and file version QString toTry[2]; toTry[0] = ((options & UserPath) ? _userBasePath : _basePath) + name + '0'; if (options & SafePath) { + toTry[1] = ((options & UserPath) ? _userBasePath : _basePath) + name + '1'; QFileInfo toTry0(toTry[0]); + QFileInfo toTry1(toTry[1]); if (toTry0.exists()) { - toTry[1] = ((options & UserPath) ? _userBasePath : _basePath) + name + '1'; - QFileInfo toTry1(toTry[1]); if (toTry1.exists()) { QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified(); - if (mod0 < mod1) { + if (mod0 > mod1) { qSwap(toTry[0], toTry[1]); } } else { - toTry[1] = QString(); + qSwap(toTry[0], toTry[1]); + } + toDelete = toTry[1]; + } else if (toTry1.exists()) { + toDelete = toTry[1]; + } + } + + file.setFileName(toTry[0]); + if (file.open(QIODevice::WriteOnly)) { + file.write(tdfMagic, tdfMagicLen); + qint32 version = AppVersion; + file.write((const char*)&version, sizeof(version)); + + stream.setDevice(&file); + stream.setVersion(QDataStream::Qt_5_1); + } + } + bool writeData(const QByteArray &data) { + if (!file.isOpen()) return false; + + stream << data; + quint32 len = data.isNull() ? 0xffffffff : data.size(); + if (QSysInfo::ByteOrder != QSysInfo::BigEndian) { + len = qbswap(len); + } + md5.feed(&len, sizeof(len)); + md5.feed(data.constData(), data.size()); + dataSize += sizeof(len) + data.size(); + + return true; + } + static QByteArray prepareEncrypted(EncryptedDescriptor &data, const MTP::AuthKey &key = _localKey) { + data.finish(); + QByteArray &toEncrypt(data.data); + + // prepare for encryption + uint32 size = toEncrypt.size(), fullSize = size; + if (fullSize & 0x0F) { + fullSize += 0x10 - (fullSize & 0x0F); + toEncrypt.resize(fullSize); + memset_rand(toEncrypt.data() + size, fullSize - size); + } + *(uint32*)toEncrypt.data() = size; + QByteArray encrypted(0x10 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data + hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data()); + MTP::aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 0x10, fullSize, &key, encrypted.constData()); + + return encrypted; + } + bool writeEncrypted(EncryptedDescriptor &data, const MTP::AuthKey &key = _localKey) { + return writeData(prepareEncrypted(data, key)); + } + void finish() { + if (!file.isOpen()) return; + + stream.setDevice(0); + + md5.feed(&dataSize, sizeof(dataSize)); + qint32 version = AppVersion; + md5.feed(&version, sizeof(version)); + md5.feed(tdfMagic, tdfMagicLen); + file.write((const char*)md5.result(), 0x10); + file.close(); + + if (!toDelete.isEmpty()) { + QFile::remove(toDelete); + } + } + QFile file; + QDataStream stream; + + QString toDelete; + + HashMd5 md5; + int32 dataSize; + + ~FileWriteDescriptor() { + finish(); + } +}; + +bool readFile(FileReadDescriptor &result, const QString &name, int options = UserPath | SafePath) { + if (options & UserPath) { + if (!_userWorking()) return false; + } else { + if (!_working()) return false; + } + + // detect order of read attempts + QString toTry[2]; + toTry[0] = ((options & UserPath) ? _userBasePath : _basePath) + name + '0'; + if (options & SafePath) { + QFileInfo toTry0(toTry[0]); + if (toTry0.exists()) { + toTry[1] = ((options & UserPath) ? _userBasePath : _basePath) + name + '1'; + QFileInfo toTry1(toTry[1]); + if (toTry1.exists()) { + QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified(); + if (mod0 < mod1) { + qSwap(toTry[0], toTry[1]); } } else { - toTry[0][toTry[0].size() - 1] = '1'; + toTry[1] = QString(); } + } else { + toTry[0][toTry[0].size() - 1] = '1'; } - for (int32 i = 0; i < 2; ++i) { - QString fname(toTry[i]); - if (fname.isEmpty()) break; + } + for (int32 i = 0; i < 2; ++i) { + QString fname(toTry[i]); + if (fname.isEmpty()) break; - QFile f(fname); - if (!f.open(QIODevice::ReadOnly)) { - DEBUG_LOG(("App Info: failed to open '%1' for reading").arg(name)); - continue; - } - - // check magic - char magic[tdfMagicLen]; - if (f.read(magic, tdfMagicLen) != tdfMagicLen) { - DEBUG_LOG(("App Info: failed to read magic from '%1'").arg(name)); - continue; - } - if (memcmp(magic, tdfMagic, tdfMagicLen)) { - DEBUG_LOG(("App Info: bad magic %1 in '%2'").arg(Logs::mb(magic, tdfMagicLen).str()).arg(name)); - continue; - } - - // read app version - qint32 version; - if (f.read((char*)&version, sizeof(version)) != sizeof(version)) { - DEBUG_LOG(("App Info: failed to read version from '%1'").arg(name)); - continue; - } - if (version > AppVersion) { - DEBUG_LOG(("App Info: version too big %1 for '%2', my version %3").arg(version).arg(name).arg(AppVersion)); - continue; - } - - // read data - QByteArray bytes = f.read(f.size()); - int32 dataSize = bytes.size() - 16; - if (dataSize < 0) { - DEBUG_LOG(("App Info: bad file '%1', could not read sign part").arg(name)); - continue; - } - - // check signature - HashMd5 md5; - md5.feed(bytes.constData(), dataSize); - md5.feed(&dataSize, sizeof(dataSize)); - md5.feed(&version, sizeof(version)); - md5.feed(magic, tdfMagicLen); - if (memcmp(md5.result(), bytes.constData() + dataSize, 16)) { - DEBUG_LOG(("App Info: bad file '%1', signature did not match").arg(name)); - continue; - } - - bytes.resize(dataSize); - result.data = bytes; - bytes = QByteArray(); - - result.version = version; - result.buffer.setBuffer(&result.data); - result.buffer.open(QIODevice::ReadOnly); - result.stream.setDevice(&result.buffer); - result.stream.setVersion(QDataStream::Qt_5_1); - - if ((i == 0 && !toTry[1].isEmpty()) || i == 1) { - QFile::remove(toTry[1 - i]); - } - - return true; + QFile f(fname); + if (!f.open(QIODevice::ReadOnly)) { + DEBUG_LOG(("App Info: failed to open '%1' for reading").arg(name)); + continue; } + + // check magic + char magic[tdfMagicLen]; + if (f.read(magic, tdfMagicLen) != tdfMagicLen) { + DEBUG_LOG(("App Info: failed to read magic from '%1'").arg(name)); + continue; + } + if (memcmp(magic, tdfMagic, tdfMagicLen)) { + DEBUG_LOG(("App Info: bad magic %1 in '%2'").arg(Logs::mb(magic, tdfMagicLen).str()).arg(name)); + continue; + } + + // read app version + qint32 version; + if (f.read((char*)&version, sizeof(version)) != sizeof(version)) { + DEBUG_LOG(("App Info: failed to read version from '%1'").arg(name)); + continue; + } + if (version > AppVersion) { + DEBUG_LOG(("App Info: version too big %1 for '%2', my version %3").arg(version).arg(name).arg(AppVersion)); + continue; + } + + // read data + QByteArray bytes = f.read(f.size()); + int32 dataSize = bytes.size() - 16; + if (dataSize < 0) { + DEBUG_LOG(("App Info: bad file '%1', could not read sign part").arg(name)); + continue; + } + + // check signature + HashMd5 md5; + md5.feed(bytes.constData(), dataSize); + md5.feed(&dataSize, sizeof(dataSize)); + md5.feed(&version, sizeof(version)); + md5.feed(magic, tdfMagicLen); + if (memcmp(md5.result(), bytes.constData() + dataSize, 16)) { + DEBUG_LOG(("App Info: bad file '%1', signature did not match").arg(name)); + continue; + } + + bytes.resize(dataSize); + result.data = bytes; + bytes = QByteArray(); + + result.version = version; + result.buffer.setBuffer(&result.data); + result.buffer.open(QIODevice::ReadOnly); + result.stream.setDevice(&result.buffer); + result.stream.setVersion(QDataStream::Qt_5_1); + + if ((i == 0 && !toTry[1].isEmpty()) || i == 1) { + QFile::remove(toTry[1 - i]); + } + + return true; + } + return false; +} + +bool decryptLocal(EncryptedDescriptor &result, const QByteArray &encrypted, const MTP::AuthKey &key = _localKey) { + if (encrypted.size() <= 16 || (encrypted.size() & 0x0F)) { + LOG(("App Error: bad encrypted part size: %1").arg(encrypted.size())); + return false; + } + uint32 fullLen = encrypted.size() - 16; + + QByteArray decrypted; + decrypted.resize(fullLen); + const char *encryptedKey = encrypted.constData(), *encryptedData = encrypted.constData() + 16; + aesDecryptLocal(encryptedData, decrypted.data(), fullLen, &key, encryptedKey); + uchar sha1Buffer[20]; + if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), encryptedKey, 16)) { + LOG(("App Info: bad decrypt key, data not decrypted - incorrect password?")); return false; } - bool decryptLocal(EncryptedDescriptor &result, const QByteArray &encrypted, const MTP::AuthKey &key = _localKey) { - if (encrypted.size() <= 16 || (encrypted.size() & 0x0F)) { - LOG(("App Error: bad encrypted part size: %1").arg(encrypted.size())); - return false; - } - uint32 fullLen = encrypted.size() - 16; - - QByteArray decrypted; - decrypted.resize(fullLen); - const char *encryptedKey = encrypted.constData(), *encryptedData = encrypted.constData() + 16; - aesDecryptLocal(encryptedData, decrypted.data(), fullLen, &key, encryptedKey); - uchar sha1Buffer[20]; - if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), encryptedKey, 16)) { - LOG(("App Info: bad decrypt key, data not decrypted - incorrect password?")); - return false; - } - - uint32 dataLen = *(const uint32*)decrypted.constData(); - if (dataLen > uint32(decrypted.size()) || dataLen <= fullLen - 16 || dataLen < sizeof(uint32)) { - LOG(("App Error: bad decrypted part size: %1, fullLen: %2, decrypted size: %3").arg(dataLen).arg(fullLen).arg(decrypted.size())); - return false; - } - - decrypted.resize(dataLen); - result.data = decrypted; - decrypted = QByteArray(); - - result.buffer.setBuffer(&result.data); - result.buffer.open(QIODevice::ReadOnly); - result.buffer.seek(sizeof(uint32)); // skip len - result.stream.setDevice(&result.buffer); - result.stream.setVersion(QDataStream::Qt_5_1); - - return true; + uint32 dataLen = *(const uint32*)decrypted.constData(); + if (dataLen > uint32(decrypted.size()) || dataLen <= fullLen - 16 || dataLen < sizeof(uint32)) { + LOG(("App Error: bad decrypted part size: %1, fullLen: %2, decrypted size: %3").arg(dataLen).arg(fullLen).arg(decrypted.size())); + return false; } - bool readEncryptedFile(FileReadDescriptor &result, const QString &name, int options = UserPath | SafePath, const MTP::AuthKey &key = _localKey) { - if (!readFile(result, name, options)) { - return false; - } - QByteArray encrypted; - result.stream >> encrypted; + decrypted.resize(dataLen); + result.data = decrypted; + decrypted = QByteArray(); - EncryptedDescriptor data; - if (!decryptLocal(data, encrypted, key)) { - result.stream.setDevice(0); - if (result.buffer.isOpen()) result.buffer.close(); - result.buffer.setBuffer(0); - result.data = QByteArray(); - result.version = 0; - return false; - } + result.buffer.setBuffer(&result.data); + result.buffer.open(QIODevice::ReadOnly); + result.buffer.seek(sizeof(uint32)); // skip len + result.stream.setDevice(&result.buffer); + result.stream.setVersion(QDataStream::Qt_5_1); + return true; +} + +bool readEncryptedFile(FileReadDescriptor &result, const QString &name, int options = UserPath | SafePath, const MTP::AuthKey &key = _localKey) { + if (!readFile(result, name, options)) { + return false; + } + QByteArray encrypted; + result.stream >> encrypted; + + EncryptedDescriptor data; + if (!decryptLocal(data, encrypted, key)) { result.stream.setDevice(0); if (result.buffer.isOpen()) result.buffer.close(); result.buffer.setBuffer(0); - result.data = data.data; - result.buffer.setBuffer(&result.data); - result.buffer.open(QIODevice::ReadOnly); - result.buffer.seek(data.buffer.pos()); - result.stream.setDevice(&result.buffer); - result.stream.setVersion(QDataStream::Qt_5_1); - - return true; + result.data = QByteArray(); + result.version = 0; + return false; } - bool readEncryptedFile(FileReadDescriptor &result, const FileKey &fkey, int options = UserPath | SafePath, const MTP::AuthKey &key = _localKey) { - return readEncryptedFile(result, toFilePart(fkey), options, key); + result.stream.setDevice(0); + if (result.buffer.isOpen()) result.buffer.close(); + result.buffer.setBuffer(0); + result.data = data.data; + result.buffer.setBuffer(&result.data); + result.buffer.open(QIODevice::ReadOnly); + result.buffer.seek(data.buffer.pos()); + result.stream.setDevice(&result.buffer); + result.stream.setVersion(QDataStream::Qt_5_1); + + return true; +} + +bool readEncryptedFile(FileReadDescriptor &result, const FileKey &fkey, int options = UserPath | SafePath, const MTP::AuthKey &key = _localKey) { + return readEncryptedFile(result, toFilePart(fkey), options, key); +} + +FileKey _dataNameKey = 0; + +enum { // Local Storage Keys + 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 + lskRecentHashtagsAndBots = 0x0a, // no data + lskStickersOld = 0x0b, // no data + lskSavedPeers = 0x0c, // no data + lskReportSpamStatuses = 0x0d, // no data + lskSavedGifsOld = 0x0e, // no data + lskSavedGifs = 0x0f, // no data + lskStickersKeys = 0x10, // no data + lskTrustedBots = 0x11, // no data +}; + +enum { + dbiKey = 0x00, + dbiUser = 0x01, + dbiDcOptionOld = 0x02, + dbiChatSizeMax = 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, + // 0x10 reserved + dbiDefaultAttach = 0x11, + dbiCatsAndDogs = 0x12, + dbiReplaceEmojis = 0x13, + dbiAskDownloadPath = 0x14, + dbiDownloadPathOld = 0x15, + dbiScale = 0x16, + dbiEmojiTabOld = 0x17, + dbiRecentEmojisOld = 0x18, + dbiLoggedPhoneNumber = 0x19, + dbiMutedPeers = 0x1a, + // 0x1b reserved + dbiNotifyView = 0x1c, + dbiSendToMenu = 0x1d, + dbiCompressPastedImage = 0x1e, + dbiLang = 0x1f, + dbiLangFile = 0x20, + dbiTileBackground = 0x21, + dbiAutoLock = 0x22, + dbiDialogLastPath = 0x23, + dbiRecentEmojis = 0x24, + dbiEmojiVariants = 0x25, + dbiRecentStickers = 0x26, + dbiDcOption = 0x27, + dbiTryIPv6 = 0x28, + dbiSongVolume = 0x29, + dbiWindowsNotifications = 0x30, + dbiIncludeMuted = 0x31, + dbiMegagroupSizeMax = 0x32, + dbiDownloadPath = 0x33, + dbiAutoDownload = 0x34, + dbiSavedGifsLimit = 0x35, + dbiShowingSavedGifs = 0x36, + dbiAutoPlay = 0x37, + dbiAdaptiveForWide = 0x38, + dbiHiddenPinnedMessages = 0x39, + dbiDialogsMode = 0x40, + dbiModerateMode = 0x41, + dbiVideoVolume = 0x42, + dbiStickersRecentLimit = 0x43, + + dbiEncryptedWithSalt = 333, + dbiEncrypted = 444, + + // 500-600 reserved + + dbiVersion = 666, +}; + + +typedef QMap DraftsMap; +DraftsMap _draftsMap, _draftCursorsMap; +typedef QMap DraftsNotReadMap; +DraftsNotReadMap _draftsNotReadMap; + +typedef QPair FileDesc; // file, size + +typedef QMultiMap FileLocations; +FileLocations _fileLocations; +typedef QPair FileLocationPair; +typedef QMap FileLocationPairs; +FileLocationPairs _fileLocationPairs; +typedef QMap FileLocationAliases; +FileLocationAliases _fileLocationAliases; +typedef QMap WebFilesMap; +WebFilesMap _webFilesMap; +uint64 _storageWebFilesSize = 0; +FileKey _locationsKey = 0, _reportSpamStatusesKey = 0, _trustedBotsKey = 0; + +using TrustedBots = OrderedSet; +TrustedBots _trustedBots; +bool _trustedBotsRead = false; + +FileKey _recentStickersKeyOld = 0; +FileKey _installedStickersKey = 0, _featuredStickersKey = 0, _recentStickersKey = 0, _archivedStickersKey = 0; +FileKey _savedGifsKey = 0; + +FileKey _backgroundKey = 0; +bool _backgroundWasRead = false; + +bool _readingUserSettings = false; +FileKey _userSettingsKey = 0; +FileKey _recentHashtagsAndBotsKey = 0; +bool _recentHashtagsAndBotsWereRead = false; + +FileKey _savedPeersKey = 0; + +typedef QMap StorageMap; +StorageMap _imagesMap, _stickerImagesMap, _audiosMap; +int32 _storageImagesSize = 0, _storageStickersSize = 0, _storageAudiosSize = 0; + +bool _mapChanged = false; +int32 _oldMapVersion = 0, _oldSettingsVersion = 0; + +enum WriteMapWhen { + WriteMapNow, + WriteMapFast, + WriteMapSoon, +}; + +void _writeMap(WriteMapWhen when = WriteMapSoon); + +void _writeLocations(WriteMapWhen when = WriteMapSoon) { + if (when != WriteMapNow) { + _manager->writeLocations(when == WriteMapFast); + return; } + if (!_working()) return; - FileKey _dataNameKey = 0; - - enum { // Local Storage Keys - 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 - lskRecentHashtagsAndBots = 0x0a, // no data - lskStickersOld = 0x0b, // no data - lskSavedPeers = 0x0c, // no data - lskReportSpamStatuses = 0x0d, // no data - lskSavedGifsOld = 0x0e, // no data - lskSavedGifs = 0x0f, // no data - lskStickersKeys = 0x10, // no data - }; - - enum { - dbiKey = 0x00, - dbiUser = 0x01, - dbiDcOptionOld = 0x02, - dbiChatSizeMax = 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, - // 0x10 reserved - dbiDefaultAttach = 0x11, - dbiCatsAndDogs = 0x12, - dbiReplaceEmojis = 0x13, - dbiAskDownloadPath = 0x14, - dbiDownloadPathOld = 0x15, - dbiScale = 0x16, - dbiEmojiTabOld = 0x17, - dbiRecentEmojisOld = 0x18, - dbiLoggedPhoneNumber = 0x19, - dbiMutedPeers = 0x1a, - // 0x1b reserved - dbiNotifyView = 0x1c, - dbiSendToMenu = 0x1d, - dbiCompressPastedImage = 0x1e, - dbiLang = 0x1f, - dbiLangFile = 0x20, - dbiTileBackground = 0x21, - dbiAutoLock = 0x22, - dbiDialogLastPath = 0x23, - dbiRecentEmojis = 0x24, - dbiEmojiVariants = 0x25, - dbiRecentStickers = 0x26, - dbiDcOption = 0x27, - dbiTryIPv6 = 0x28, - dbiSongVolume = 0x29, - dbiWindowsNotifications = 0x30, - dbiIncludeMuted = 0x31, - dbiMegagroupSizeMax = 0x32, - dbiDownloadPath = 0x33, - dbiAutoDownload = 0x34, - dbiSavedGifsLimit = 0x35, - dbiShowingSavedGifs = 0x36, - dbiAutoPlay = 0x37, - dbiAdaptiveForWide = 0x38, - dbiHiddenPinnedMessages = 0x39, - dbiDialogsMode = 0x40, - dbiModerateMode = 0x41, - dbiVideoVolume = 0x42, - dbiStickersRecentLimit = 0x43, - - dbiEncryptedWithSalt = 333, - dbiEncrypted = 444, - - // 500-600 reserved - - dbiVersion = 666, - }; - - - typedef QMap DraftsMap; - DraftsMap _draftsMap, _draftCursorsMap; - typedef QMap DraftsNotReadMap; - DraftsNotReadMap _draftsNotReadMap; - - typedef QPair FileDesc; // file, size - - typedef QMultiMap FileLocations; - FileLocations _fileLocations; - typedef QPair FileLocationPair; - typedef QMap FileLocationPairs; - FileLocationPairs _fileLocationPairs; - typedef QMap FileLocationAliases; - FileLocationAliases _fileLocationAliases; - typedef QMap WebFilesMap; - WebFilesMap _webFilesMap; - uint64 _storageWebFilesSize = 0; - FileKey _locationsKey = 0, _reportSpamStatusesKey = 0; - - FileKey _recentStickersKeyOld = 0; - FileKey _installedStickersKey = 0, _featuredStickersKey = 0, _recentStickersKey = 0, _archivedStickersKey = 0; - FileKey _savedGifsKey = 0; - - FileKey _backgroundKey = 0; - bool _backgroundWasRead = false; - - bool _readingUserSettings = false; - FileKey _userSettingsKey = 0; - FileKey _recentHashtagsAndBotsKey = 0; - bool _recentHashtagsAndBotsWereRead = false; - - FileKey _savedPeersKey = 0; - - typedef QMap StorageMap; - StorageMap _imagesMap, _stickerImagesMap, _audiosMap; - int32 _storageImagesSize = 0, _storageStickersSize = 0, _storageAudiosSize = 0; - - bool _mapChanged = false; - int32 _oldMapVersion = 0, _oldSettingsVersion = 0; - - enum WriteMapWhen { - WriteMapNow, - WriteMapFast, - WriteMapSoon, - }; - - void _writeMap(WriteMapWhen when = WriteMapSoon); - - void _writeLocations(WriteMapWhen when = WriteMapSoon) { - if (when != WriteMapNow) { - _manager->writeLocations(when == WriteMapFast); - return; - } - if (!_working()) return; - - _manager->writingLocations(); - if (_fileLocations.isEmpty() && _webFilesMap.isEmpty()) { - if (_locationsKey) { - clearKey(_locationsKey); - _locationsKey = 0; - _mapChanged = true; - _writeMap(); - } - } else { - if (!_locationsKey) { - _locationsKey = genKey(); - _mapChanged = true; - _writeMap(WriteMapFast); - } - quint32 size = 0; - for (FileLocations::const_iterator i = _fileLocations.cbegin(), e = _fileLocations.cend(); i != e; ++i) { - // location + type + namelen + name - size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(i.value().name()); - if (AppVersion > 9013) { - // bookmark - size += Serialize::bytearraySize(i.value().bookmark()); - } - // date + size - size += Serialize::dateTimeSize() + sizeof(quint32); - } - - //end mark - size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(QString()); - if (AppVersion > 9013) { - size += Serialize::bytearraySize(QByteArray()); - } - size += Serialize::dateTimeSize() + sizeof(quint32); - - size += sizeof(quint32); // aliases count - for (FileLocationAliases::const_iterator i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) { - // alias + location - size += sizeof(quint64) * 2 + sizeof(quint64) * 2; - } - - size += sizeof(quint32); // web files count - for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) { - // url + filekey + size - size += Serialize::stringSize(i.key()) + sizeof(quint64) + sizeof(qint32); - } - - EncryptedDescriptor data(size); - for (FileLocations::const_iterator i = _fileLocations.cbegin(); i != _fileLocations.cend(); ++i) { - data.stream << quint64(i.key().first) << quint64(i.key().second) << quint32(i.value().type) << i.value().name(); - if (AppVersion > 9013) { - data.stream << i.value().bookmark(); - } - data.stream << i.value().modified << quint32(i.value().size); - } - - data.stream << quint64(0) << quint64(0) << quint32(0) << QString(); - if (AppVersion > 9013) { - data.stream << QByteArray(); - } - data.stream << QDateTime::currentDateTime() << quint32(0); - - data.stream << quint32(_fileLocationAliases.size()); - for (FileLocationAliases::const_iterator i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) { - data.stream << quint64(i.key().first) << quint64(i.key().second) << quint64(i.value().first) << quint64(i.value().second); - } - - data.stream << quint32(_webFilesMap.size()); - for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) { - data.stream << i.key() << quint64(i.value().first) << qint32(i.value().second); - } - - FileWriteDescriptor file(_locationsKey); - file.writeEncrypted(data); - } - } - - void _readLocations() { - FileReadDescriptor locations; - if (!readEncryptedFile(locations, _locationsKey)) { + _manager->writingLocations(); + if (_fileLocations.isEmpty() && _webFilesMap.isEmpty()) { + if (_locationsKey) { clearKey(_locationsKey); _locationsKey = 0; + _mapChanged = true; _writeMap(); - return; } - - bool endMarkFound = false; - while (!locations.stream.atEnd()) { - quint64 first, second; - QByteArray bookmark; - FileLocation loc; - quint32 type; - locations.stream >> first >> second >> type >> loc.fname; - if (locations.version > 9013) { - locations.stream >> bookmark; - } - locations.stream >> loc.modified >> loc.size; - loc.setBookmark(bookmark); - - if (!first && !second && !type && loc.fname.isEmpty() && !loc.size) { // end mark - endMarkFound = true; - break; - } - - MediaKey key(first, second); - loc.type = StorageFileType(type); - - _fileLocations.insert(key, loc); - _fileLocationPairs.insert(loc.fname, FileLocationPair(key, loc)); - } - - if (endMarkFound) { - quint32 cnt; - locations.stream >> cnt; - for (quint32 i = 0; i < cnt; ++i) { - quint64 kfirst, ksecond, vfirst, vsecond; - locations.stream >> kfirst >> ksecond >> vfirst >> vsecond; - _fileLocationAliases.insert(MediaKey(kfirst, ksecond), MediaKey(vfirst, vsecond)); - } - - if (!locations.stream.atEnd()) { - _storageWebFilesSize = 0; - _webFilesMap.clear(); - - quint32 webLocationsCount; - locations.stream >> webLocationsCount; - for (quint32 i = 0; i < webLocationsCount; ++i) { - QString url; - quint64 key; - qint32 size; - locations.stream >> url >> key >> size; - _webFilesMap.insert(url, FileDesc(key, size)); - _storageWebFilesSize += size; - } - } - } - } - - void _writeReportSpamStatuses() { - if (!_working()) return; - - if (cReportSpamStatuses().isEmpty()) { - if (_reportSpamStatusesKey) { - clearKey(_reportSpamStatusesKey); - _reportSpamStatusesKey = 0; - _mapChanged = true; - _writeMap(); - } - } else { - if (!_reportSpamStatusesKey) { - _reportSpamStatusesKey = genKey(); - _mapChanged = true; - _writeMap(WriteMapFast); - } - const ReportSpamStatuses &statuses(cReportSpamStatuses()); - - quint32 size = sizeof(qint32); - for (ReportSpamStatuses::const_iterator i = statuses.cbegin(), e = statuses.cend(); i != e; ++i) { - // peer + status - size += sizeof(quint64) + sizeof(qint32); - } - - EncryptedDescriptor data(size); - data.stream << qint32(statuses.size()); - for (ReportSpamStatuses::const_iterator i = statuses.cbegin(), e = statuses.cend(); i != e; ++i) { - data.stream << quint64(i.key()) << qint32(i.value()); - } - - FileWriteDescriptor file(_reportSpamStatusesKey); - file.writeEncrypted(data); - } - } - - void _readReportSpamStatuses() { - FileReadDescriptor statuses; - if (!readEncryptedFile(statuses, _reportSpamStatusesKey)) { - clearKey(_reportSpamStatusesKey); - _reportSpamStatusesKey = 0; - _writeMap(); - return; - } - - ReportSpamStatuses &map(cRefReportSpamStatuses()); - map.clear(); - - qint32 size = 0; - statuses.stream >> size; - for (int32 i = 0; i < size; ++i) { - quint64 peer = 0; - qint32 status = 0; - statuses.stream >> peer >> status; - map.insert(peer, DBIPeerReportSpamStatus(status)); - } - } - - MTP::DcOptions *_dcOpts = 0; - bool _readSetting(quint32 blockId, QDataStream &stream, int version) { - switch (blockId) { - case dbiDcOptionOld: { - quint32 dcId, port; - QString host, ip; - stream >> dcId >> host >> ip >> port; - if (!_checkStreamStatus(stream)) return false; - - if (_dcOpts) _dcOpts->insert(dcId, MTP::DcOption(dcId, 0, ip.toUtf8().constData(), port)); - } break; - - case dbiDcOption: { - quint32 dcIdWithShift, port; - qint32 flags; - QString ip; - stream >> dcIdWithShift >> flags >> ip >> port; - if (!_checkStreamStatus(stream)) return false; - - if (_dcOpts) _dcOpts->insert(dcIdWithShift, MTP::DcOption(MTP::bareDcId(dcIdWithShift), MTPDdcOption::Flags(flags), ip.toUtf8().constData(), port)); - } break; - - case dbiChatSizeMax: { - qint32 maxSize; - stream >> maxSize; - if (!_checkStreamStatus(stream)) return false; - - Global::SetChatSizeMax(maxSize); - } break; - - case dbiSavedGifsLimit: { - qint32 limit; - stream >> limit; - if (!_checkStreamStatus(stream)) return false; - - Global::SetSavedGifsLimit(limit); - } break; - - case dbiStickersRecentLimit: { - qint32 limit; - stream >> limit; - if (!_checkStreamStatus(stream)) return false; - - Global::SetStickersRecentLimit(limit); - } break; - - case dbiMegagroupSizeMax: { - qint32 maxSize; - stream >> maxSize; - if (!_checkStreamStatus(stream)) return false; - - Global::SetMegagroupSizeMax(maxSize); - } break; - - case dbiUser: { - quint32 dcId; - qint32 uid; - stream >> uid >> dcId; - if (!_checkStreamStatus(stream)) return false; - - DEBUG_LOG(("MTP Info: user found, dc %1, uid %2").arg(dcId).arg(uid)); - MTP::configure(dcId, uid); - } break; - - case dbiKey: { - qint32 dcId; - quint32 key[64]; - stream >> dcId; - stream.readRawData((char*)key, 256); - if (!_checkStreamStatus(stream)) return false; - - DEBUG_LOG(("MTP Info: key found, dc %1, key: %2").arg(dcId).arg(Logs::mb(key, 256).str())); - dcId = MTP::bareDcId(dcId); - MTP::AuthKeyPtr keyPtr(new MTP::AuthKey()); - keyPtr->setKey(key); - keyPtr->setDC(dcId); - - MTP::setKey(dcId, keyPtr); - } break; - - case dbiAutoStart: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetAutoStart(v == 1); - } break; - - case dbiStartMinimized: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetStartMinimized(v == 1); - } break; - - case dbiSendToMenu: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetSendToMenu(v == 1); - } break; - - case dbiSoundNotify: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - Global::SetSoundNotify(v == 1); - } break; - - case dbiAutoDownload: { - qint32 photo, audio, gif; - stream >> photo >> audio >> gif; - if (!_checkStreamStatus(stream)) return false; - - cSetAutoDownloadPhoto(photo); - cSetAutoDownloadAudio(audio); - cSetAutoDownloadGif(gif); - } break; - - case dbiAutoPlay: { - qint32 gif; - stream >> gif; - if (!_checkStreamStatus(stream)) return false; - - cSetAutoPlayGif(gif == 1); - } break; - - case dbiDialogsMode: { - qint32 enabled, modeInt; - stream >> enabled >> modeInt; - if (!_checkStreamStatus(stream)) return false; - - Global::SetDialogsModeEnabled(enabled == 1); - Dialogs::Mode mode = Dialogs::Mode::All; - if (enabled) { - mode = static_cast(modeInt); - if (mode != Dialogs::Mode::All && mode != Dialogs::Mode::Important) { - mode = Dialogs::Mode::All; - } - } - Global::SetDialogsMode(mode); - } break; - - case dbiModerateMode: { - qint32 enabled; - stream >> enabled; - if (!_checkStreamStatus(stream)) return false; - - Global::SetModerateModeEnabled(enabled == 1); - } break; - - case dbiIncludeMuted: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - Global::SetIncludeMuted(v == 1); - } break; - - case dbiShowingSavedGifs: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetShowingSavedGifs(v == 1); - } break; - - case dbiDesktopNotify: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - Global::SetDesktopNotify(v == 1); - if (App::wnd()) App::wnd()->updateTrayMenu(); - } break; - - case dbiWindowsNotifications: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - Global::SetWindowsNotifications(v == 1); - if (cPlatform() == dbipWindows) { - Global::SetCustomNotifies((App::wnd() ? !App::wnd()->psHasNativeNotifications() : true) || !Global::WindowsNotifications()); - } - } break; - - case dbiWorkMode: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - switch (v) { - case dbiwmTrayOnly: cSetWorkMode(dbiwmTrayOnly); break; - case dbiwmWindowOnly: cSetWorkMode(dbiwmWindowOnly); break; - default: cSetWorkMode(dbiwmWindowAndTray); break; - }; - } break; - - case dbiConnectionType: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - switch (v) { - case dbictHttpProxy: - case dbictTcpProxy: { - ProxyData p; - qint32 port; - stream >> p.host >> port >> p.user >> p.password; - if (!_checkStreamStatus(stream)) return false; - - p.port = uint32(port); - Global::SetConnectionProxy(p); - Global::SetConnectionType(DBIConnectionType(v)); - } break; - case dbictHttpAuto: - default: Global::SetConnectionType(dbictAuto); break; - }; - } break; - - case dbiTryIPv6: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - Global::SetTryIPv6(v == 1); - } break; - - case dbiSeenTrayTooltip: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetSeenTrayTooltip(v == 1); - } break; - - case dbiAutoUpdate: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetAutoUpdate(v == 1); - } break; - - case dbiLastUpdateCheck: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetLastUpdateCheck(v); - } break; - - case dbiScale: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - DBIScale s = cRealScale(); - switch (v) { - case dbisAuto: s = dbisAuto; break; - case dbisOne: s = dbisOne; break; - case dbisOneAndQuarter: s = dbisOneAndQuarter; break; - case dbisOneAndHalf: s = dbisOneAndHalf; break; - case dbisTwo: s = dbisTwo; break; - } - if (cRetina()) s = dbisOne; - cSetConfigScale(s); - cSetRealScale(s); - } break; - - case dbiLang: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - if (v == languageTest || (v >= 0 && v < languageCount)) { - cSetLang(v); - } - } break; - - case dbiLangFile: { - QString v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetLangFile(v); - } break; - - case dbiWindowPosition: { - TWindowPos pos; - stream >> pos.x >> pos.y >> pos.w >> pos.h >> pos.moncrc >> pos.maximized; - if (!_checkStreamStatus(stream)) return false; - - cSetWindowPos(pos); - } break; - - case dbiLoggedPhoneNumber: { - QString v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetLoggedPhoneNumber(v); - } break; - - case dbiMutePeer: { // deprecated - quint64 peerId; - stream >> peerId; - if (!_checkStreamStatus(stream)) return false; - } break; - - case dbiMutedPeers: { // deprecated - quint32 count; - stream >> count; - if (!_checkStreamStatus(stream)) return false; - - for (uint32 i = 0; i < count; ++i) { - quint64 peerId; - stream >> peerId; - } - if (!_checkStreamStatus(stream)) return false; - } break; - - case dbiSendKey: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetCtrlEnter(v == dbiskCtrlEnter); - if (App::main()) App::main()->ctrlEnterSubmitUpdated(); - } break; - - case dbiCatsAndDogs: { // deprecated - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - } break; - - case dbiTileBackground: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - bool tile = (version < 8005 && !_backgroundKey) ? false : (v == 1); - Window::chatBackground()->setTile(tile); - } break; - - case dbiAdaptiveForWide: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - Global::SetAdaptiveForWide(v == 1); - } break; - - case dbiAutoLock: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - Global::SetAutoLock(v); - Global::RefLocalPasscodeChanged().notify(); - } break; - - case dbiReplaceEmojis: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetReplaceEmojis(v == 1); - } break; - - case dbiDefaultAttach: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - switch (v) { - case dbidaPhoto: cSetDefaultAttach(dbidaPhoto); break; - default: cSetDefaultAttach(dbidaDocument); break; - } - } break; - - case dbiNotifyView: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - switch (v) { - case dbinvShowNothing: Global::SetNotifyView(dbinvShowNothing); break; - case dbinvShowName: Global::SetNotifyView(dbinvShowName); break; - default: Global::SetNotifyView(dbinvShowPreview); break; - } - } break; - - case dbiAskDownloadPath: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - Global::SetAskDownloadPath(v == 1); - } break; - - case dbiDownloadPathOld: { - QString v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - if (!v.isEmpty() && v != qstr("tmp") && !v.endsWith('/')) v += '/'; - Global::SetDownloadPath(v); - Global::SetDownloadPathBookmark(QByteArray()); - Global::RefDownloadPathChanged().notify(); - } break; - - case dbiDownloadPath: { - QString v; - QByteArray bookmark; - stream >> v >> bookmark; - if (!_checkStreamStatus(stream)) return false; - - if (!v.isEmpty() && v != qstr("tmp") && !v.endsWith('/')) v += '/'; - Global::SetDownloadPath(v); - Global::SetDownloadPathBookmark(bookmark); - psDownloadPathEnableAccess(); - Global::RefDownloadPathChanged().notify(); - } break; - - case dbiCompressPastedImage: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetCompressPastedImage(v == 1); - } break; - - case dbiEmojiTabOld: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - // deprecated - } break; - - case dbiRecentEmojisOld: { - RecentEmojisPreloadOld v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - if (!v.isEmpty()) { - RecentEmojisPreload p; - p.reserve(v.size()); - for (int i = 0; i < v.size(); ++i) { - uint64 e(v.at(i).first); - switch (e) { - case 0xD83CDDEFLLU: e = 0xD83CDDEFD83CDDF5LLU; break; - case 0xD83CDDF0LLU: e = 0xD83CDDF0D83CDDF7LLU; break; - case 0xD83CDDE9LLU: e = 0xD83CDDE9D83CDDEALLU; break; - case 0xD83CDDE8LLU: e = 0xD83CDDE8D83CDDF3LLU; break; - case 0xD83CDDFALLU: e = 0xD83CDDFAD83CDDF8LLU; break; - case 0xD83CDDEBLLU: e = 0xD83CDDEBD83CDDF7LLU; break; - case 0xD83CDDEALLU: e = 0xD83CDDEAD83CDDF8LLU; break; - case 0xD83CDDEELLU: e = 0xD83CDDEED83CDDF9LLU; break; - case 0xD83CDDF7LLU: e = 0xD83CDDF7D83CDDFALLU; break; - case 0xD83CDDECLLU: e = 0xD83CDDECD83CDDE7LLU; break; - } - p.push_back(qMakePair(e, v.at(i).second)); - } - cSetRecentEmojisPreload(p); - } - } break; - - case dbiRecentEmojis: { - RecentEmojisPreload v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetRecentEmojisPreload(v); - } break; - - case dbiRecentStickers: { - RecentStickerPreload v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetRecentStickersPreload(v); - } break; - - case dbiEmojiVariants: { - EmojiColorVariants v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - cSetEmojiVariants(v); - } break; - - - case dbiHiddenPinnedMessages: { - Global::HiddenPinnedMessagesMap v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - Global::SetHiddenPinnedMessages(v); - } break; - - case dbiDialogLastPath: { - QString path; - stream >> path; - if (!_checkStreamStatus(stream)) return false; - - cSetDialogLastPath(path); - } break; - - case dbiSongVolume: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - Global::SetSongVolume(snap(v / 1e6, 0., 1.)); - } break; - - case dbiVideoVolume: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - Global::SetVideoVolume(snap(v / 1e6, 0., 1.)); - } break; - - default: - LOG(("App Error: unknown blockId in _readSetting: %1").arg(blockId)); - return false; - } - - return true; - } - - bool _readOldSettings(bool remove = true) { - bool result = false; - QFile file(cWorkingDir() + qsl("tdata/config")); - if (file.open(QIODevice::ReadOnly)) { - LOG(("App Info: reading old config...")); - QDataStream stream(&file); - stream.setVersion(QDataStream::Qt_5_1); - - qint32 version = 0; - while (!stream.atEnd()) { - quint32 blockId; - stream >> blockId; - if (!_checkStreamStatus(stream)) break; - - if (blockId == dbiVersion) { - stream >> version; - if (!_checkStreamStatus(stream)) break; - - if (version > AppVersion) break; - } else if (!_readSetting(blockId, stream, version)) { - break; - } - } - file.close(); - result = true; - } - if (remove) file.remove(); - return result; - } - - void _readOldUserSettingsFields(QIODevice *device, qint32 &version) { - QDataStream stream(device); - stream.setVersion(QDataStream::Qt_5_1); - - while (!stream.atEnd()) { - quint32 blockId; - stream >> blockId; - if (!_checkStreamStatus(stream)) { - break; - } - - if (blockId == dbiVersion) { - stream >> version; - if (!_checkStreamStatus(stream)) { - break; - } - - if (version > AppVersion) return; - } else if (blockId == dbiEncryptedWithSalt) { - QByteArray salt, data, decrypted; - stream >> salt >> data; - if (!_checkStreamStatus(stream)) { - break; - } - - if (salt.size() != 32) { - LOG(("App Error: bad salt in old user config encrypted part, size: %1").arg(salt.size())); - continue; - } - - createLocalKey(QByteArray(), &salt, &_oldKey); - - if (data.size() <= 16 || (data.size() & 0x0F)) { - LOG(("App Error: bad encrypted part size in old user config: %1").arg(data.size())); - continue; - } - uint32 fullDataLen = data.size() - 16; - decrypted.resize(fullDataLen); - const char *dataKey = data.constData(), *encrypted = data.constData() + 16; - aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &_oldKey, dataKey); - uchar sha1Buffer[20]; - if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) { - LOG(("App Error: bad decrypt key, data from old user config not decrypted")); - continue; - } - uint32 dataLen = *(const uint32*)decrypted.constData(); - if (dataLen > uint32(decrypted.size()) || dataLen <= fullDataLen - 16 || dataLen < 4) { - LOG(("App Error: bad decrypted part size in old user config: %1, fullDataLen: %2, decrypted size: %3").arg(dataLen).arg(fullDataLen).arg(decrypted.size())); - continue; - } - decrypted.resize(dataLen); - QBuffer decryptedStream(&decrypted); - decryptedStream.open(QIODevice::ReadOnly); - decryptedStream.seek(4); // skip size - LOG(("App Info: reading encrypted old user config...")); - - _readOldUserSettingsFields(&decryptedStream, version); - } else if (!_readSetting(blockId, stream, version)) { - return; - } - } - } - - bool _readOldUserSettings(bool remove = true) { - bool result = false; - QFile file(cWorkingDir() + cDataFile() + (cTestMode() ? qsl("_test") : QString()) + qsl("_config")); - if (file.open(QIODevice::ReadOnly)) { - LOG(("App Info: reading old user config...")); - qint32 version = 0; - - MTP::DcOptions dcOpts; - { - QReadLocker lock(MTP::dcOptionsMutex()); - dcOpts = Global::DcOptions(); - } - _dcOpts = &dcOpts; - _readOldUserSettingsFields(&file, version); - { - QWriteLocker lock(MTP::dcOptionsMutex()); - Global::SetDcOptions(dcOpts); - } - - file.close(); - result = true; - } - if (remove) file.remove(); - return result; - } - - void _readOldMtpDataFields(QIODevice *device, qint32 &version) { - QDataStream stream(device); - stream.setVersion(QDataStream::Qt_5_1); - - while (!stream.atEnd()) { - quint32 blockId; - stream >> blockId; - if (!_checkStreamStatus(stream)) { - break; - } - - if (blockId == dbiVersion) { - stream >> version; - if (!_checkStreamStatus(stream)) { - break; - } - - if (version > AppVersion) return; - } else if (blockId == dbiEncrypted) { - QByteArray data, decrypted; - stream >> data; - if (!_checkStreamStatus(stream)) { - break; - } - - if (!_oldKey.created()) { - LOG(("MTP Error: reading old encrypted keys without old key!")); - continue; - } - - if (data.size() <= 16 || (data.size() & 0x0F)) { - LOG(("MTP Error: bad encrypted part size in old keys: %1").arg(data.size())); - continue; - } - uint32 fullDataLen = data.size() - 16; - decrypted.resize(fullDataLen); - const char *dataKey = data.constData(), *encrypted = data.constData() + 16; - aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &_oldKey, dataKey); - uchar sha1Buffer[20]; - if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) { - LOG(("MTP Error: bad decrypt key, data from old keys not decrypted")); - continue; - } - uint32 dataLen = *(const uint32*)decrypted.constData(); - if (dataLen > uint32(decrypted.size()) || dataLen <= fullDataLen - 16 || dataLen < 4) { - LOG(("MTP Error: bad decrypted part size in old keys: %1, fullDataLen: %2, decrypted size: %3").arg(dataLen).arg(fullDataLen).arg(decrypted.size())); - continue; - } - decrypted.resize(dataLen); - QBuffer decryptedStream(&decrypted); - decryptedStream.open(QIODevice::ReadOnly); - decryptedStream.seek(4); // skip size - LOG(("App Info: reading encrypted old keys...")); - - _readOldMtpDataFields(&decryptedStream, version); - } else if (!_readSetting(blockId, stream, version)) { - return; - } - } - } - - bool _readOldMtpData(bool remove = true) { - bool result = false; - QFile file(cWorkingDir() + cDataFile() + (cTestMode() ? qsl("_test") : QString())); - if (file.open(QIODevice::ReadOnly)) { - LOG(("App Info: reading old keys...")); - qint32 version = 0; - - MTP::DcOptions dcOpts; - { - QReadLocker lock(MTP::dcOptionsMutex()); - dcOpts = Global::DcOptions(); - } - _dcOpts = &dcOpts; - _readOldMtpDataFields(&file, version); - { - QWriteLocker lock(MTP::dcOptionsMutex()); - Global::SetDcOptions(dcOpts); - } - - file.close(); - result = true; - } - if (remove) file.remove(); - return result; - } - - void _writeUserSettings() { - if (_readingUserSettings) { - LOG(("App Error: attempt to write settings while reading them!")); - return; - } - LOG(("App Info: writing encrypted user settings...")); - - if (!_userSettingsKey) { - _userSettingsKey = genKey(); + } else { + if (!_locationsKey) { + _locationsKey = genKey(); _mapChanged = true; _writeMap(WriteMapFast); } + quint32 size = 0; + for (FileLocations::const_iterator i = _fileLocations.cbegin(), e = _fileLocations.cend(); i != e; ++i) { + // location + type + namelen + name + size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(i.value().name()); + if (AppVersion > 9013) { + // bookmark + size += Serialize::bytearraySize(i.value().bookmark()); + } + // date + size + size += Serialize::dateTimeSize() + sizeof(quint32); + } - uint32 size = 18 * (sizeof(quint32) + sizeof(qint32)); - size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark()); - 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) + Serialize::stringSize(cDialogLastPath()); - size += sizeof(quint32) + 3 * sizeof(qint32); - size += sizeof(quint32) + 2 * sizeof(qint32); - if (!Global::HiddenPinnedMessages().isEmpty()) { - size += sizeof(quint32) + sizeof(qint32) + Global::HiddenPinnedMessages().size() * (sizeof(PeerId) + sizeof(MsgId)); + //end mark + size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(QString()); + if (AppVersion > 9013) { + size += Serialize::bytearraySize(QByteArray()); + } + size += Serialize::dateTimeSize() + sizeof(quint32); + + size += sizeof(quint32); // aliases count + for (FileLocationAliases::const_iterator i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) { + // alias + location + size += sizeof(quint64) * 2 + sizeof(quint64) * 2; + } + + size += sizeof(quint32); // web files count + for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) { + // url + filekey + size + size += Serialize::stringSize(i.key()) + sizeof(quint64) + sizeof(qint32); } EncryptedDescriptor data(size); - data.stream << quint32(dbiSendKey) << qint32(cCtrlEnter() ? dbiskCtrlEnter : dbiskEnter); - data.stream << quint32(dbiTileBackground) << qint32(Window::chatBackground()->tile() ? 1 : 0); - data.stream << quint32(dbiAdaptiveForWide) << qint32(Global::AdaptiveForWide() ? 1 : 0); - data.stream << quint32(dbiAutoLock) << qint32(Global::AutoLock()); - data.stream << quint32(dbiReplaceEmojis) << qint32(cReplaceEmojis() ? 1 : 0); - data.stream << quint32(dbiDefaultAttach) << qint32(cDefaultAttach()); - data.stream << quint32(dbiSoundNotify) << qint32(Global::SoundNotify()); - data.stream << quint32(dbiIncludeMuted) << qint32(Global::IncludeMuted()); - data.stream << quint32(dbiShowingSavedGifs) << qint32(cShowingSavedGifs()); - data.stream << quint32(dbiDesktopNotify) << qint32(Global::DesktopNotify()); - data.stream << quint32(dbiNotifyView) << qint32(Global::NotifyView()); - data.stream << quint32(dbiWindowsNotifications) << qint32(Global::WindowsNotifications()); - data.stream << quint32(dbiAskDownloadPath) << qint32(Global::AskDownloadPath()); - data.stream << quint32(dbiDownloadPath) << (Global::AskDownloadPath() ? QString() : Global::DownloadPath()) << (Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark()); - data.stream << quint32(dbiCompressPastedImage) << qint32(cCompressPastedImage()); - data.stream << quint32(dbiDialogLastPath) << cDialogLastPath(); - data.stream << quint32(dbiSongVolume) << qint32(qRound(Global::SongVolume() * 1e6)); - data.stream << quint32(dbiVideoVolume) << qint32(qRound(Global::VideoVolume() * 1e6)); - data.stream << quint32(dbiAutoDownload) << qint32(cAutoDownloadPhoto()) << qint32(cAutoDownloadAudio()) << qint32(cAutoDownloadGif()); - data.stream << quint32(dbiDialogsMode) << qint32(Global::DialogsModeEnabled() ? 1 : 0) << static_cast(Global::DialogsMode()); - data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0); - data.stream << quint32(dbiAutoPlay) << qint32(cAutoPlayGif() ? 1 : 0); - - { - 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)); - } + for (FileLocations::const_iterator i = _fileLocations.cbegin(); i != _fileLocations.cend(); ++i) { + data.stream << quint64(i.key().first) << quint64(i.key().second) << quint32(i.value().type) << i.value().name(); + if (AppVersion > 9013) { + data.stream << i.value().bookmark(); } - 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; - } - if (!Global::HiddenPinnedMessages().isEmpty()) { - data.stream << quint32(dbiHiddenPinnedMessages) << Global::HiddenPinnedMessages(); + data.stream << i.value().modified << quint32(i.value().size); } - FileWriteDescriptor file(_userSettingsKey); + data.stream << quint64(0) << quint64(0) << quint32(0) << QString(); + if (AppVersion > 9013) { + data.stream << QByteArray(); + } + data.stream << QDateTime::currentDateTime() << quint32(0); + + data.stream << quint32(_fileLocationAliases.size()); + for (FileLocationAliases::const_iterator i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) { + data.stream << quint64(i.key().first) << quint64(i.key().second) << quint64(i.value().first) << quint64(i.value().second); + } + + data.stream << quint32(_webFilesMap.size()); + for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) { + data.stream << i.key() << quint64(i.value().first) << qint32(i.value().second); + } + + FileWriteDescriptor file(_locationsKey); file.writeEncrypted(data); } +} - void _readUserSettings() { - FileReadDescriptor userSettings; - if (!readEncryptedFile(userSettings, _userSettingsKey)) { - LOG(("App Info: could not read encrypted user settings...")); - _readOldUserSettings(); - return _writeUserSettings(); - } - - LOG(("App Info: reading encrypted user settings...")); - _readingUserSettings = true; - while (!userSettings.stream.atEnd()) { - quint32 blockId; - userSettings.stream >> blockId; - if (!_checkStreamStatus(userSettings.stream)) { - _readingUserSettings = false; - return _writeUserSettings(); - } - - if (!_readSetting(blockId, userSettings.stream, userSettings.version)) { - _readingUserSettings = false; - return _writeUserSettings(); - } - } - _readingUserSettings = false; - LOG(("App Info: encrypted user settings read.")); +void _readLocations() { + FileReadDescriptor locations; + if (!readEncryptedFile(locations, _locationsKey)) { + clearKey(_locationsKey); + _locationsKey = 0; + _writeMap(); + return; } - void _writeMtpData() { - FileWriteDescriptor mtp(toFilePart(_dataNameKey), SafePath); - if (!_localKey.created()) { - LOG(("App Error: localkey not created in _writeMtpData()")); - return; + bool endMarkFound = false; + while (!locations.stream.atEnd()) { + quint64 first, second; + QByteArray bookmark; + FileLocation loc; + quint32 type; + locations.stream >> first >> second >> type >> loc.fname; + if (locations.version > 9013) { + locations.stream >> bookmark; + } + locations.stream >> loc.modified >> loc.size; + loc.setBookmark(bookmark); + + if (!first && !second && !type && loc.fname.isEmpty() && !loc.size) { // end mark + endMarkFound = true; + break; } - MTP::AuthKeysMap keys = MTP::getKeys(); + MediaKey key(first, second); + loc.type = StorageFileType(type); - quint32 size = sizeof(quint32) + sizeof(qint32) + sizeof(quint32); - size += keys.size() * (sizeof(quint32) + sizeof(quint32) + 256); - - EncryptedDescriptor data(size); - data.stream << quint32(dbiUser) << qint32(MTP::authedId()) << quint32(MTP::maindc()); - for_const (const MTP::AuthKeyPtr &key, keys) { - data.stream << quint32(dbiKey) << quint32(key->getDC()); - key->write(data.stream); - } - - mtp.writeEncrypted(data, _localKey); + _fileLocations.insert(key, loc); + _fileLocationPairs.insert(loc.fname, FileLocationPair(key, loc)); } - void _readMtpData() { - FileReadDescriptor mtp; - if (!readEncryptedFile(mtp, toFilePart(_dataNameKey), SafePath)) { - if (_localKey.created()) { - _readOldMtpData(); - _writeMtpData(); - } - return; + if (endMarkFound) { + quint32 cnt; + locations.stream >> cnt; + for (quint32 i = 0; i < cnt; ++i) { + quint64 kfirst, ksecond, vfirst, vsecond; + locations.stream >> kfirst >> ksecond >> vfirst >> vsecond; + _fileLocationAliases.insert(MediaKey(kfirst, ksecond), MediaKey(vfirst, vsecond)); } - LOG(("App Info: reading encrypted mtp data...")); - while (!mtp.stream.atEnd()) { - quint32 blockId; - mtp.stream >> blockId; - if (!_checkStreamStatus(mtp.stream)) { - return _writeMtpData(); - } + if (!locations.stream.atEnd()) { + _storageWebFilesSize = 0; + _webFilesMap.clear(); - if (!_readSetting(blockId, mtp.stream, mtp.version)) { - return _writeMtpData(); - } - } - } - - Local::ReadMapState _readMap(const QByteArray &pass) { - uint64 ms = getms(); - QByteArray dataNameUtf8 = (cDataFile() + (cTestMode() ? qsl(":/test/") : QString())).toUtf8(); - FileKey dataNameHash[2]; - hashMd5(dataNameUtf8.constData(), dataNameUtf8.size(), dataNameHash); - _dataNameKey = dataNameHash[0]; - _userBasePath = _basePath + toFilePart(_dataNameKey) + QChar('/'); - - FileReadDescriptor mapData; - if (!readFile(mapData, qsl("map"))) { - return Local::ReadMapFailed; - } - LOG(("App Info: reading map...")); - - QByteArray salt, keyEncrypted, mapEncrypted; - mapData.stream >> salt >> keyEncrypted >> mapEncrypted; - if (!_checkStreamStatus(mapData.stream)) { - return Local::ReadMapFailed; - } - - if (salt.size() != LocalEncryptSaltSize) { - LOG(("App Error: bad salt in map file, size: %1").arg(salt.size())); - return Local::ReadMapFailed; - } - createLocalKey(pass, &salt, &_passKey); - - EncryptedDescriptor keyData, map; - if (!decryptLocal(keyData, keyEncrypted, _passKey)) { - LOG(("App Info: could not decrypt pass-protected key from map file, maybe bad password...")); - return Local::ReadMapPassNeeded; - } - uchar key[LocalEncryptKeySize] = { 0 }; - if (keyData.stream.readRawData((char*)key, LocalEncryptKeySize) != LocalEncryptKeySize || !keyData.stream.atEnd()) { - LOG(("App Error: could not read pass-protected key from map file")); - return Local::ReadMapFailed; - } - _localKey.setKey(key); - - _passKeyEncrypted = keyEncrypted; - _passKeySalt = salt; - - if (!decryptLocal(map, mapEncrypted)) { - LOG(("App Error: could not decrypt map.")); - return Local::ReadMapFailed; - } - LOG(("App Info: reading encrypted map...")); - - DraftsMap draftsMap, draftCursorsMap; - DraftsNotReadMap draftsNotReadMap; - StorageMap imagesMap, stickerImagesMap, audiosMap; - qint64 storageImagesSize = 0, storageStickersSize = 0, storageAudiosSize = 0; - quint64 locationsKey = 0, reportSpamStatusesKey = 0; - quint64 recentStickersKeyOld = 0; - quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, archivedStickersKey = 0; - quint64 savedGifsKey = 0; - quint64 backgroundKey = 0, userSettingsKey = 0, recentHashtagsAndBotsKey = 0, savedPeersKey = 0; - while (!map.stream.atEnd()) { - quint32 keyType; - map.stream >> keyType; - switch (keyType) { - case lskDraft: { - quint32 count = 0; - map.stream >> count; - for (quint32 i = 0; i < count; ++i) { - FileKey key; - quint64 p; - map.stream >> key >> p; - draftsMap.insert(p, key); - draftsNotReadMap.insert(p, true); - } - } break; - case lskDraftPosition: { - quint32 count = 0; - map.stream >> count; - for (quint32 i = 0; i < count; ++i) { - FileKey key; - quint64 p; - map.stream >> key >> p; - draftCursorsMap.insert(p, key); - } - } break; - case lskImages: { - quint32 count = 0; - map.stream >> count; - for (quint32 i = 0; i < count; ++i) { - FileKey key; - quint64 first, second; - qint32 size; - map.stream >> key >> first >> second >> size; - imagesMap.insert(StorageKey(first, second), FileDesc(key, size)); - storageImagesSize += size; - } - } break; - case lskStickerImages: { - quint32 count = 0; - map.stream >> count; - for (quint32 i = 0; i < count; ++i) { - FileKey key; - quint64 first, second; - qint32 size; - map.stream >> key >> first >> second >> size; - stickerImagesMap.insert(StorageKey(first, second), FileDesc(key, size)); - storageStickersSize += size; - } - } break; - case lskAudios: { - quint32 count = 0; - map.stream >> count; - for (quint32 i = 0; i < count; ++i) { - FileKey key; - quint64 first, second; - qint32 size; - map.stream >> key >> first >> second >> size; - audiosMap.insert(StorageKey(first, second), FileDesc(key, size)); - storageAudiosSize += size; - } - } break; - case lskLocations: { - map.stream >> locationsKey; - } break; - case lskReportSpamStatuses: { - map.stream >> reportSpamStatusesKey; - } break; - case lskRecentStickersOld: { - map.stream >> recentStickersKeyOld; - } break; - case lskBackground: { - map.stream >> backgroundKey; - } break; - case lskUserSettings: { - map.stream >> userSettingsKey; - } break; - case lskRecentHashtagsAndBots: { - map.stream >> recentHashtagsAndBotsKey; - } break; - case lskStickersOld: { - map.stream >> installedStickersKey; - } break; - case lskStickersKeys: { - map.stream >> installedStickersKey >> featuredStickersKey >> recentStickersKey >> archivedStickersKey; - } break; - case lskSavedGifsOld: { + quint32 webLocationsCount; + locations.stream >> webLocationsCount; + for (quint32 i = 0; i < webLocationsCount; ++i) { + QString url; quint64 key; - map.stream >> key; - } break; - case lskSavedGifs: { - map.stream >> savedGifsKey; - } break; - case lskSavedPeers: { - map.stream >> savedPeersKey; - } break; - default: - LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType)); - return Local::ReadMapFailed; - } - if (!_checkStreamStatus(map.stream)) { - return Local::ReadMapFailed; + qint32 size; + locations.stream >> url >> key >> size; + _webFilesMap.insert(url, FileDesc(key, size)); + _storageWebFilesSize += size; } } + } +} - _draftsMap = draftsMap; - _draftCursorsMap = draftCursorsMap; - _draftsNotReadMap = draftsNotReadMap; +void _writeReportSpamStatuses() { + if (!_working()) return; - _imagesMap = imagesMap; - _storageImagesSize = storageImagesSize; - _stickerImagesMap = stickerImagesMap; - _storageStickersSize = storageStickersSize; - _audiosMap = audiosMap; - _storageAudiosSize = storageAudiosSize; - - _locationsKey = locationsKey; - _reportSpamStatusesKey = reportSpamStatusesKey; - _recentStickersKeyOld = recentStickersKeyOld; - _installedStickersKey = installedStickersKey; - _featuredStickersKey = featuredStickersKey; - _recentStickersKey = recentStickersKey; - _archivedStickersKey = archivedStickersKey; - _savedGifsKey = savedGifsKey; - _savedPeersKey = savedPeersKey; - _backgroundKey = backgroundKey; - _userSettingsKey = userSettingsKey; - _recentHashtagsAndBotsKey = recentHashtagsAndBotsKey; - _oldMapVersion = mapData.version; - if (_oldMapVersion < AppVersion) { + if (cReportSpamStatuses().isEmpty()) { + if (_reportSpamStatusesKey) { + clearKey(_reportSpamStatusesKey); + _reportSpamStatusesKey = 0; _mapChanged = true; _writeMap(); - } else { - _mapChanged = false; + } + } else { + if (!_reportSpamStatusesKey) { + _reportSpamStatusesKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); + } + const ReportSpamStatuses &statuses(cReportSpamStatuses()); + + quint32 size = sizeof(qint32); + for (ReportSpamStatuses::const_iterator i = statuses.cbegin(), e = statuses.cend(); i != e; ++i) { + // peer + status + size += sizeof(quint64) + sizeof(qint32); } - if (_locationsKey) { - _readLocations(); - } - if (_reportSpamStatusesKey) { - _readReportSpamStatuses(); + EncryptedDescriptor data(size); + data.stream << qint32(statuses.size()); + for (ReportSpamStatuses::const_iterator i = statuses.cbegin(), e = statuses.cend(); i != e; ++i) { + data.stream << quint64(i.key()) << qint32(i.value()); } - _readUserSettings(); - _readMtpData(); - - LOG(("Map read time: %1").arg(getms() - ms)); - if (_oldSettingsVersion < AppVersion) { - Local::writeSettings(); - } - return Local::ReadMapDone; + FileWriteDescriptor file(_reportSpamStatusesKey); + file.writeEncrypted(data); } - - void _writeMap(WriteMapWhen when) { - if (when != WriteMapNow) { - _manager->writeMap(when == WriteMapFast); - return; - } - _manager->writingMap(); - if (!_mapChanged) return; - if (_userBasePath.isEmpty()) { - LOG(("App Error: _userBasePath is empty in writeMap()")); - return; - } - - if (!QDir().exists(_userBasePath)) QDir().mkpath(_userBasePath); - - FileWriteDescriptor map(qsl("map")); - if (_passKeySalt.isEmpty() || _passKeyEncrypted.isEmpty()) { - uchar local5Key[LocalEncryptKeySize] = { 0 }; - QByteArray pass(LocalEncryptKeySize, Qt::Uninitialized), salt(LocalEncryptSaltSize, Qt::Uninitialized); - memset_rand(pass.data(), pass.size()); - memset_rand(salt.data(), salt.size()); - createLocalKey(pass, &salt, &_localKey); - - _passKeySalt.resize(LocalEncryptSaltSize); - memset_rand(_passKeySalt.data(), _passKeySalt.size()); - createLocalKey(QByteArray(), &_passKeySalt, &_passKey); - - EncryptedDescriptor passKeyData(LocalEncryptKeySize); - _localKey.write(passKeyData.stream); - _passKeyEncrypted = FileWriteDescriptor::prepareEncrypted(passKeyData, _passKey); - } - map.writeData(_passKeySalt); - map.writeData(_passKeyEncrypted); - - uint32 mapSize = 0; - if (!_draftsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftsMap.size() * sizeof(quint64) * 2; - if (!_draftCursorsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftCursorsMap.size() * sizeof(quint64) * 2; - if (!_imagesMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _imagesMap.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 (_reportSpamStatusesKey) mapSize += sizeof(quint32) + sizeof(quint64); - if (_recentStickersKeyOld) mapSize += sizeof(quint32) + sizeof(quint64); - if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { - mapSize += sizeof(quint32) + 4 * sizeof(quint64); - } - if (_savedGifsKey) mapSize += sizeof(quint32) + sizeof(quint64); - if (_savedPeersKey) mapSize += sizeof(quint32) + sizeof(quint64); - if (_backgroundKey) mapSize += sizeof(quint32) + sizeof(quint64); - if (_userSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64); - if (_recentHashtagsAndBotsKey) mapSize += sizeof(quint32) + sizeof(quint64); - EncryptedDescriptor mapData(mapSize); - if (!_draftsMap.isEmpty()) { - mapData.stream << quint32(lskDraft) << quint32(_draftsMap.size()); - for (DraftsMap::const_iterator i = _draftsMap.cbegin(), e = _draftsMap.cend(); i != e; ++i) { - mapData.stream << quint64(i.value()) << quint64(i.key()); - } - } - if (!_draftCursorsMap.isEmpty()) { - mapData.stream << quint32(lskDraftPosition) << quint32(_draftCursorsMap.size()); - for (DraftsMap::const_iterator i = _draftCursorsMap.cbegin(), e = _draftCursorsMap.cend(); i != e; ++i) { - mapData.stream << quint64(i.value()) << quint64(i.key()); - } - } - if (!_imagesMap.isEmpty()) { - mapData.stream << quint32(lskImages) << quint32(_imagesMap.size()); - for (StorageMap::const_iterator i = _imagesMap.cbegin(), e = _imagesMap.cend(); i != e; ++i) { - mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second); - } - } - 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); - } - } - if (!_audiosMap.isEmpty()) { - mapData.stream << quint32(lskAudios) << quint32(_audiosMap.size()); - for (StorageMap::const_iterator i = _audiosMap.cbegin(), e = _audiosMap.cend(); i != e; ++i) { - mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second); - } - } - if (_locationsKey) { - mapData.stream << quint32(lskLocations) << quint64(_locationsKey); - } - if (_reportSpamStatusesKey) { - mapData.stream << quint32(lskReportSpamStatuses) << quint64(_reportSpamStatusesKey); - } - if (_recentStickersKeyOld) { - mapData.stream << quint32(lskRecentStickersOld) << quint64(_recentStickersKeyOld); - } - if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { - mapData.stream << quint32(lskStickersKeys); - mapData.stream << quint64(_installedStickersKey) << quint64(_featuredStickersKey) << quint64(_recentStickersKey) << quint64(_archivedStickersKey); - } - if (_savedGifsKey) { - mapData.stream << quint32(lskSavedGifs) << quint64(_savedGifsKey); - } - if (_savedPeersKey) { - mapData.stream << quint32(lskSavedPeers) << quint64(_savedPeersKey); - } - if (_backgroundKey) { - mapData.stream << quint32(lskBackground) << quint64(_backgroundKey); - } - if (_userSettingsKey) { - mapData.stream << quint32(lskUserSettings) << quint64(_userSettingsKey); - } - if (_recentHashtagsAndBotsKey) { - mapData.stream << quint32(lskRecentHashtagsAndBots) << quint64(_recentHashtagsAndBotsKey); - } - map.writeEncrypted(mapData); - - _mapChanged = false; - } - } -namespace _local_inner { - - Manager::Manager() { - _mapWriteTimer.setSingleShot(true); - connect(&_mapWriteTimer, SIGNAL(timeout()), this, SLOT(mapWriteTimeout())); - _locationsWriteTimer.setSingleShot(true); - connect(&_locationsWriteTimer, SIGNAL(timeout()), this, SLOT(locationsWriteTimeout())); +void _readReportSpamStatuses() { + FileReadDescriptor statuses; + if (!readEncryptedFile(statuses, _reportSpamStatusesKey)) { + clearKey(_reportSpamStatusesKey); + _reportSpamStatusesKey = 0; + _writeMap(); + return; } - void Manager::writeMap(bool fast) { - if (!_mapWriteTimer.isActive() || fast) { - _mapWriteTimer.start(fast ? 1 : WriteMapTimeout); - } else if (_mapWriteTimer.remainingTime() <= 0) { - mapWriteTimeout(); - } - } + ReportSpamStatuses &map(cRefReportSpamStatuses()); + map.clear(); - void Manager::writingMap() { - _mapWriteTimer.stop(); + qint32 size = 0; + statuses.stream >> size; + for (int32 i = 0; i < size; ++i) { + quint64 peer = 0; + qint32 status = 0; + statuses.stream >> peer >> status; + map.insert(peer, DBIPeerReportSpamStatus(status)); } - - void Manager::writeLocations(bool fast) { - if (!_locationsWriteTimer.isActive() || fast) { - _locationsWriteTimer.start(fast ? 1 : WriteMapTimeout); - } else if (_locationsWriteTimer.remainingTime() <= 0) { - locationsWriteTimeout(); - } - } - - void Manager::writingLocations() { - _locationsWriteTimer.stop(); - } - - void Manager::mapWriteTimeout() { - _writeMap(WriteMapNow); - } - - void Manager::locationsWriteTimeout() { - _writeLocations(WriteMapNow); - } - - void Manager::finish() { - if (_mapWriteTimer.isActive()) { - mapWriteTimeout(); - } - if (_locationsWriteTimer.isActive()) { - locationsWriteTimeout(); - } - } - } -namespace Local { +MTP::DcOptions *_dcOpts = 0; +bool _readSetting(quint32 blockId, QDataStream &stream, int version) { + switch (blockId) { + case dbiDcOptionOld: { + quint32 dcId, port; + QString host, ip; + stream >> dcId >> host >> ip >> port; + if (!_checkStreamStatus(stream)) return false; - void finish() { - if (_manager) { - _writeMap(WriteMapNow); - _manager->finish(); - _manager->deleteLater(); - _manager = 0; - delete _localLoader; - _localLoader = 0; + if (_dcOpts) _dcOpts->insert(dcId, MTP::DcOption(dcId, 0, ip.toUtf8().constData(), port)); + } break; + + case dbiDcOption: { + quint32 dcIdWithShift, port; + qint32 flags; + QString ip; + stream >> dcIdWithShift >> flags >> ip >> port; + if (!_checkStreamStatus(stream)) return false; + + if (_dcOpts) _dcOpts->insert(dcIdWithShift, MTP::DcOption(MTP::bareDcId(dcIdWithShift), MTPDdcOption::Flags(flags), ip.toUtf8().constData(), port)); + } break; + + case dbiChatSizeMax: { + qint32 maxSize; + stream >> maxSize; + if (!_checkStreamStatus(stream)) return false; + + Global::SetChatSizeMax(maxSize); + } break; + + case dbiSavedGifsLimit: { + qint32 limit; + stream >> limit; + if (!_checkStreamStatus(stream)) return false; + + Global::SetSavedGifsLimit(limit); + } break; + + case dbiStickersRecentLimit: { + qint32 limit; + stream >> limit; + if (!_checkStreamStatus(stream)) return false; + + Global::SetStickersRecentLimit(limit); + } break; + + case dbiMegagroupSizeMax: { + qint32 maxSize; + stream >> maxSize; + if (!_checkStreamStatus(stream)) return false; + + Global::SetMegagroupSizeMax(maxSize); + } break; + + case dbiUser: { + quint32 dcId; + qint32 uid; + stream >> uid >> dcId; + if (!_checkStreamStatus(stream)) return false; + + DEBUG_LOG(("MTP Info: user found, dc %1, uid %2").arg(dcId).arg(uid)); + MTP::configure(dcId, uid); + } break; + + case dbiKey: { + qint32 dcId; + quint32 key[64]; + stream >> dcId; + stream.readRawData((char*)key, 256); + if (!_checkStreamStatus(stream)) return false; + + DEBUG_LOG(("MTP Info: key found, dc %1, key: %2").arg(dcId).arg(Logs::mb(key, 256).str())); + dcId = MTP::bareDcId(dcId); + MTP::AuthKeyPtr keyPtr(new MTP::AuthKey()); + keyPtr->setKey(key); + keyPtr->setDC(dcId); + + MTP::setKey(dcId, keyPtr); + } break; + + case dbiAutoStart: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetAutoStart(v == 1); + } break; + + case dbiStartMinimized: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetStartMinimized(v == 1); + } break; + + case dbiSendToMenu: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetSendToMenu(v == 1); + } break; + + case dbiSoundNotify: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetSoundNotify(v == 1); + } break; + + case dbiAutoDownload: { + qint32 photo, audio, gif; + stream >> photo >> audio >> gif; + if (!_checkStreamStatus(stream)) return false; + + cSetAutoDownloadPhoto(photo); + cSetAutoDownloadAudio(audio); + cSetAutoDownloadGif(gif); + } break; + + case dbiAutoPlay: { + qint32 gif; + stream >> gif; + if (!_checkStreamStatus(stream)) return false; + + cSetAutoPlayGif(gif == 1); + } break; + + case dbiDialogsMode: { + qint32 enabled, modeInt; + stream >> enabled >> modeInt; + if (!_checkStreamStatus(stream)) return false; + + Global::SetDialogsModeEnabled(enabled == 1); + Dialogs::Mode mode = Dialogs::Mode::All; + if (enabled) { + mode = static_cast(modeInt); + if (mode != Dialogs::Mode::All && mode != Dialogs::Mode::Important) { + mode = Dialogs::Mode::All; + } } + Global::SetDialogsMode(mode); + } break; + + case dbiModerateMode: { + qint32 enabled; + stream >> enabled; + if (!_checkStreamStatus(stream)) return false; + + Global::SetModerateModeEnabled(enabled == 1); + } break; + + case dbiIncludeMuted: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetIncludeMuted(v == 1); + } break; + + case dbiShowingSavedGifs: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetShowingSavedGifs(v == 1); + } break; + + case dbiDesktopNotify: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetDesktopNotify(v == 1); + if (App::wnd()) App::wnd()->updateTrayMenu(); + } break; + + case dbiWindowsNotifications: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetWindowsNotifications(v == 1); + if (cPlatform() == dbipWindows) { + Global::SetCustomNotifies((App::wnd() ? !App::wnd()->psHasNativeNotifications() : true) || !Global::WindowsNotifications()); + } + } break; + + case dbiWorkMode: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + switch (v) { + case dbiwmTrayOnly: cSetWorkMode(dbiwmTrayOnly); break; + case dbiwmWindowOnly: cSetWorkMode(dbiwmWindowOnly); break; + default: cSetWorkMode(dbiwmWindowAndTray); break; + }; + } break; + + case dbiConnectionType: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + switch (v) { + case dbictHttpProxy: + case dbictTcpProxy: { + ProxyData p; + qint32 port; + stream >> p.host >> port >> p.user >> p.password; + if (!_checkStreamStatus(stream)) return false; + + p.port = uint32(port); + Global::SetConnectionProxy(p); + Global::SetConnectionType(DBIConnectionType(v)); + } break; + case dbictHttpAuto: + default: Global::SetConnectionType(dbictAuto); break; + }; + } break; + + case dbiTryIPv6: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetTryIPv6(v == 1); + } break; + + case dbiSeenTrayTooltip: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetSeenTrayTooltip(v == 1); + } break; + + case dbiAutoUpdate: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetAutoUpdate(v == 1); + } break; + + case dbiLastUpdateCheck: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetLastUpdateCheck(v); + } break; + + case dbiScale: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + DBIScale s = cRealScale(); + switch (v) { + case dbisAuto: s = dbisAuto; break; + case dbisOne: s = dbisOne; break; + case dbisOneAndQuarter: s = dbisOneAndQuarter; break; + case dbisOneAndHalf: s = dbisOneAndHalf; break; + case dbisTwo: s = dbisTwo; break; + } + if (cRetina()) s = dbisOne; + cSetConfigScale(s); + cSetRealScale(s); + } break; + + case dbiLang: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + if (v == languageTest || (v >= 0 && v < languageCount)) { + cSetLang(v); + } + } break; + + case dbiLangFile: { + QString v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetLangFile(v); + } break; + + case dbiWindowPosition: { + TWindowPos pos; + stream >> pos.x >> pos.y >> pos.w >> pos.h >> pos.moncrc >> pos.maximized; + if (!_checkStreamStatus(stream)) return false; + + cSetWindowPos(pos); + } break; + + case dbiLoggedPhoneNumber: { + QString v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetLoggedPhoneNumber(v); + } break; + + case dbiMutePeer: { // deprecated + quint64 peerId; + stream >> peerId; + if (!_checkStreamStatus(stream)) return false; + } break; + + case dbiMutedPeers: { // deprecated + quint32 count; + stream >> count; + if (!_checkStreamStatus(stream)) return false; + + for (uint32 i = 0; i < count; ++i) { + quint64 peerId; + stream >> peerId; + } + if (!_checkStreamStatus(stream)) return false; + } break; + + case dbiSendKey: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetCtrlEnter(v == dbiskCtrlEnter); + if (App::main()) App::main()->ctrlEnterSubmitUpdated(); + } break; + + case dbiCatsAndDogs: { // deprecated + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + } break; + + case dbiTileBackground: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + bool tile = (version < 8005 && !_backgroundKey) ? false : (v == 1); + Window::chatBackground()->setTile(tile); + } break; + + case dbiAdaptiveForWide: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetAdaptiveForWide(v == 1); + } break; + + case dbiAutoLock: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetAutoLock(v); + Global::RefLocalPasscodeChanged().notify(); + } break; + + case dbiReplaceEmojis: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetReplaceEmojis(v == 1); + } break; + + case dbiDefaultAttach: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + switch (v) { + case dbidaPhoto: cSetDefaultAttach(dbidaPhoto); break; + default: cSetDefaultAttach(dbidaDocument); break; + } + } break; + + case dbiNotifyView: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + switch (v) { + case dbinvShowNothing: Global::SetNotifyView(dbinvShowNothing); break; + case dbinvShowName: Global::SetNotifyView(dbinvShowName); break; + default: Global::SetNotifyView(dbinvShowPreview); break; + } + } break; + + case dbiAskDownloadPath: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetAskDownloadPath(v == 1); + } break; + + case dbiDownloadPathOld: { + QString v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + if (!v.isEmpty() && v != qstr("tmp") && !v.endsWith('/')) v += '/'; + Global::SetDownloadPath(v); + Global::SetDownloadPathBookmark(QByteArray()); + Global::RefDownloadPathChanged().notify(); + } break; + + case dbiDownloadPath: { + QString v; + QByteArray bookmark; + stream >> v >> bookmark; + if (!_checkStreamStatus(stream)) return false; + + if (!v.isEmpty() && v != qstr("tmp") && !v.endsWith('/')) v += '/'; + Global::SetDownloadPath(v); + Global::SetDownloadPathBookmark(bookmark); + psDownloadPathEnableAccess(); + Global::RefDownloadPathChanged().notify(); + } break; + + case dbiCompressPastedImage: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetCompressPastedImage(v == 1); + } break; + + case dbiEmojiTabOld: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + // deprecated + } break; + + case dbiRecentEmojisOld: { + RecentEmojisPreloadOld v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + if (!v.isEmpty()) { + RecentEmojisPreload p; + p.reserve(v.size()); + for (int i = 0; i < v.size(); ++i) { + uint64 e(v.at(i).first); + switch (e) { + case 0xD83CDDEFLLU: e = 0xD83CDDEFD83CDDF5LLU; break; + case 0xD83CDDF0LLU: e = 0xD83CDDF0D83CDDF7LLU; break; + case 0xD83CDDE9LLU: e = 0xD83CDDE9D83CDDEALLU; break; + case 0xD83CDDE8LLU: e = 0xD83CDDE8D83CDDF3LLU; break; + case 0xD83CDDFALLU: e = 0xD83CDDFAD83CDDF8LLU; break; + case 0xD83CDDEBLLU: e = 0xD83CDDEBD83CDDF7LLU; break; + case 0xD83CDDEALLU: e = 0xD83CDDEAD83CDDF8LLU; break; + case 0xD83CDDEELLU: e = 0xD83CDDEED83CDDF9LLU; break; + case 0xD83CDDF7LLU: e = 0xD83CDDF7D83CDDFALLU; break; + case 0xD83CDDECLLU: e = 0xD83CDDECD83CDDE7LLU; break; + } + p.push_back(qMakePair(e, v.at(i).second)); + } + cSetRecentEmojisPreload(p); + } + } break; + + case dbiRecentEmojis: { + RecentEmojisPreload v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetRecentEmojisPreload(v); + } break; + + case dbiRecentStickers: { + RecentStickerPreload v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetRecentStickersPreload(v); + } break; + + case dbiEmojiVariants: { + EmojiColorVariants v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetEmojiVariants(v); + } break; + + + case dbiHiddenPinnedMessages: { + Global::HiddenPinnedMessagesMap v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetHiddenPinnedMessages(v); + } break; + + case dbiDialogLastPath: { + QString path; + stream >> path; + if (!_checkStreamStatus(stream)) return false; + + cSetDialogLastPath(path); + } break; + + case dbiSongVolume: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetSongVolume(snap(v / 1e6, 0., 1.)); + } break; + + case dbiVideoVolume: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetVideoVolume(snap(v / 1e6, 0., 1.)); + } break; + + default: + LOG(("App Error: unknown blockId in _readSetting: %1").arg(blockId)); + return false; } - void start() { - t_assert(_manager == 0); + return true; +} - _manager = new _local_inner::Manager(); - _localLoader = new TaskQueue(0, FileLoaderQueueStopTimeout); +bool _readOldSettings(bool remove = true) { + bool result = false; + QFile file(cWorkingDir() + qsl("tdata/config")); + if (file.open(QIODevice::ReadOnly)) { + LOG(("App Info: reading old config...")); + QDataStream stream(&file); + stream.setVersion(QDataStream::Qt_5_1); - _basePath = cWorkingDir() + qsl("tdata/"); - if (!QDir().exists(_basePath)) QDir().mkpath(_basePath); + qint32 version = 0; + while (!stream.atEnd()) { + quint32 blockId; + stream >> blockId; + if (!_checkStreamStatus(stream)) break; - FileReadDescriptor settingsData; - if (!readFile(settingsData, cTestMode() ? qsl("settings_test") : qsl("settings"), SafePath)) { - _readOldSettings(); - _readOldUserSettings(false); // needed further in _readUserSettings - _readOldMtpData(false); // needed further in _readMtpData - return writeSettings(); + if (blockId == dbiVersion) { + stream >> version; + if (!_checkStreamStatus(stream)) break; + + if (version > AppVersion) break; + } else if (!_readSetting(blockId, stream, version)) { + break; + } } - LOG(("App Info: reading settings...")); + file.close(); + result = true; + } + if (remove) file.remove(); + return result; +} - QByteArray salt, settingsEncrypted; - settingsData.stream >> salt >> settingsEncrypted; - if (!_checkStreamStatus(settingsData.stream)) { - return writeSettings(); +void _readOldUserSettingsFields(QIODevice *device, qint32 &version) { + QDataStream stream(device); + stream.setVersion(QDataStream::Qt_5_1); + + while (!stream.atEnd()) { + quint32 blockId; + stream >> blockId; + if (!_checkStreamStatus(stream)) { + break; } - if (salt.size() != LocalEncryptSaltSize) { - LOG(("App Error: bad salt in settings file, size: %1").arg(salt.size())); - return writeSettings(); - } - createLocalKey(QByteArray(), &salt, &_settingsKey); + if (blockId == dbiVersion) { + stream >> version; + if (!_checkStreamStatus(stream)) { + break; + } - EncryptedDescriptor settings; - if (!decryptLocal(settings, settingsEncrypted, _settingsKey)) { - LOG(("App Error: could not decrypt settings from settings file, maybe bad passcode...")); - return writeSettings(); + if (version > AppVersion) return; + } else if (blockId == dbiEncryptedWithSalt) { + QByteArray salt, data, decrypted; + stream >> salt >> data; + if (!_checkStreamStatus(stream)) { + break; + } + + if (salt.size() != 32) { + LOG(("App Error: bad salt in old user config encrypted part, size: %1").arg(salt.size())); + continue; + } + + createLocalKey(QByteArray(), &salt, &_oldKey); + + if (data.size() <= 16 || (data.size() & 0x0F)) { + LOG(("App Error: bad encrypted part size in old user config: %1").arg(data.size())); + continue; + } + uint32 fullDataLen = data.size() - 16; + decrypted.resize(fullDataLen); + const char *dataKey = data.constData(), *encrypted = data.constData() + 16; + aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &_oldKey, dataKey); + uchar sha1Buffer[20]; + if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) { + LOG(("App Error: bad decrypt key, data from old user config not decrypted")); + continue; + } + uint32 dataLen = *(const uint32*)decrypted.constData(); + if (dataLen > uint32(decrypted.size()) || dataLen <= fullDataLen - 16 || dataLen < 4) { + LOG(("App Error: bad decrypted part size in old user config: %1, fullDataLen: %2, decrypted size: %3").arg(dataLen).arg(fullDataLen).arg(decrypted.size())); + continue; + } + decrypted.resize(dataLen); + QBuffer decryptedStream(&decrypted); + decryptedStream.open(QIODevice::ReadOnly); + decryptedStream.seek(4); // skip size + LOG(("App Info: reading encrypted old user config...")); + + _readOldUserSettingsFields(&decryptedStream, version); + } else if (!_readSetting(blockId, stream, version)) { + return; } + } +} + +bool _readOldUserSettings(bool remove = true) { + bool result = false; + QFile file(cWorkingDir() + cDataFile() + (cTestMode() ? qsl("_test") : QString()) + qsl("_config")); + if (file.open(QIODevice::ReadOnly)) { + LOG(("App Info: reading old user config...")); + qint32 version = 0; + MTP::DcOptions dcOpts; { QReadLocker lock(MTP::dcOptionsMutex()); dcOpts = Global::DcOptions(); } _dcOpts = &dcOpts; - LOG(("App Info: reading encrypted settings...")); - while (!settings.stream.atEnd()) { - quint32 blockId; - settings.stream >> blockId; - if (!_checkStreamStatus(settings.stream)) { - return writeSettings(); - } - - if (!_readSetting(blockId, settings.stream, settingsData.version)) { - return writeSettings(); - } - } - if (dcOpts.isEmpty()) { - const BuiltInDc *bdcs = builtInDcs(); - for (int i = 0, l = builtInDcsCount(); i < l; ++i) { - MTPDdcOption::Flags flags = 0; - MTP::ShiftedDcId idWithShift = MTP::shiftDcId(bdcs[i].id, flags); - dcOpts.insert(idWithShift, MTP::DcOption(bdcs[i].id, flags, bdcs[i].ip, bdcs[i].port)); - DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3").arg(bdcs[i].id).arg(bdcs[i].ip).arg(bdcs[i].port)); - } - - const BuiltInDc *bdcsipv6 = builtInDcsIPv6(); - for (int i = 0, l = builtInDcsCountIPv6(); i < l; ++i) { - MTPDdcOption::Flags flags = MTPDdcOption::Flag::f_ipv6; - MTP::ShiftedDcId idWithShift = MTP::shiftDcId(bdcsipv6[i].id, flags); - dcOpts.insert(idWithShift, MTP::DcOption(bdcsipv6[i].id, flags, bdcsipv6[i].ip, bdcsipv6[i].port)); - DEBUG_LOG(("MTP Info: adding built in DC %1 IPv6 connect option: %2:%3").arg(bdcsipv6[i].id).arg(bdcsipv6[i].ip).arg(bdcsipv6[i].port)); - } - } + _readOldUserSettingsFields(&file, version); { QWriteLocker lock(MTP::dcOptionsMutex()); Global::SetDcOptions(dcOpts); } - _oldSettingsVersion = settingsData.version; - _settingsSalt = salt; + file.close(); + result = true; } + if (remove) file.remove(); + return result; +} - void writeSettings() { - if (_basePath.isEmpty()) { - LOG(("App Error: _basePath is empty in writeSettings()")); +void _readOldMtpDataFields(QIODevice *device, qint32 &version) { + QDataStream stream(device); + stream.setVersion(QDataStream::Qt_5_1); + + while (!stream.atEnd()) { + quint32 blockId; + stream >> blockId; + if (!_checkStreamStatus(stream)) { + break; + } + + if (blockId == dbiVersion) { + stream >> version; + if (!_checkStreamStatus(stream)) { + break; + } + + if (version > AppVersion) return; + } else if (blockId == dbiEncrypted) { + QByteArray data, decrypted; + stream >> data; + if (!_checkStreamStatus(stream)) { + break; + } + + if (!_oldKey.created()) { + LOG(("MTP Error: reading old encrypted keys without old key!")); + continue; + } + + if (data.size() <= 16 || (data.size() & 0x0F)) { + LOG(("MTP Error: bad encrypted part size in old keys: %1").arg(data.size())); + continue; + } + uint32 fullDataLen = data.size() - 16; + decrypted.resize(fullDataLen); + const char *dataKey = data.constData(), *encrypted = data.constData() + 16; + aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &_oldKey, dataKey); + uchar sha1Buffer[20]; + if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) { + LOG(("MTP Error: bad decrypt key, data from old keys not decrypted")); + continue; + } + uint32 dataLen = *(const uint32*)decrypted.constData(); + if (dataLen > uint32(decrypted.size()) || dataLen <= fullDataLen - 16 || dataLen < 4) { + LOG(("MTP Error: bad decrypted part size in old keys: %1, fullDataLen: %2, decrypted size: %3").arg(dataLen).arg(fullDataLen).arg(decrypted.size())); + continue; + } + decrypted.resize(dataLen); + QBuffer decryptedStream(&decrypted); + decryptedStream.open(QIODevice::ReadOnly); + decryptedStream.seek(4); // skip size + LOG(("App Info: reading encrypted old keys...")); + + _readOldMtpDataFields(&decryptedStream, version); + } else if (!_readSetting(blockId, stream, version)) { return; } + } +} - if (!QDir().exists(_basePath)) QDir().mkpath(_basePath); - - FileWriteDescriptor settings(cTestMode() ? qsl("settings_test") : qsl("settings"), SafePath); - if (_settingsSalt.isEmpty() || !_settingsKey.created()) { - _settingsSalt.resize(LocalEncryptSaltSize); - memset_rand(_settingsSalt.data(), _settingsSalt.size()); - createLocalKey(QByteArray(), &_settingsSalt, &_settingsKey); - } - settings.writeData(_settingsSalt); +bool _readOldMtpData(bool remove = true) { + bool result = false; + QFile file(cWorkingDir() + cDataFile() + (cTestMode() ? qsl("_test") : QString())); + if (file.open(QIODevice::ReadOnly)) { + LOG(("App Info: reading old keys...")); + qint32 version = 0; MTP::DcOptions dcOpts; { QReadLocker lock(MTP::dcOptionsMutex()); dcOpts = Global::DcOptions(); } - if (dcOpts.isEmpty()) { - const BuiltInDc *bdcs = builtInDcs(); - for (int i = 0, l = builtInDcsCount(); i < l; ++i) { - MTPDdcOption::Flags flags = 0; - MTP::ShiftedDcId idWithShift = MTP::shiftDcId(bdcs[i].id, flags); - dcOpts.insert(idWithShift, MTP::DcOption(bdcs[i].id, flags, bdcs[i].ip, bdcs[i].port)); - DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3").arg(bdcs[i].id).arg(bdcs[i].ip).arg(bdcs[i].port)); - } - - const BuiltInDc *bdcsipv6 = builtInDcsIPv6(); - for (int i = 0, l = builtInDcsCountIPv6(); i < l; ++i) { - MTPDdcOption::Flags flags = MTPDdcOption::Flag::f_ipv6; - MTP::ShiftedDcId idWithShift = MTP::shiftDcId(bdcsipv6[i].id, flags); - dcOpts.insert(idWithShift, MTP::DcOption(bdcsipv6[i].id, flags, bdcsipv6[i].ip, bdcsipv6[i].port)); - DEBUG_LOG(("MTP Info: adding built in DC %1 IPv6 connect option: %2:%3").arg(bdcsipv6[i].id).arg(bdcsipv6[i].ip).arg(bdcsipv6[i].port)); - } - + _dcOpts = &dcOpts; + _readOldMtpDataFields(&file, version); + { QWriteLocker lock(MTP::dcOptionsMutex()); Global::SetDcOptions(dcOpts); } - quint32 size = 12 * (sizeof(quint32) + sizeof(qint32)); - for (auto i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) { - size += sizeof(quint32) + sizeof(quint32) + sizeof(quint32); - size += sizeof(quint32) + Serialize::stringSize(QString::fromUtf8(i->ip.data(), i->ip.size())); - } - size += sizeof(quint32) + Serialize::stringSize(cLangFile()); - - size += sizeof(quint32) + sizeof(qint32); - if (Global::ConnectionType() == dbictHttpProxy || Global::ConnectionType() == dbictTcpProxy) { - auto &proxy = Global::ConnectionProxy(); - size += Serialize::stringSize(proxy.host) + sizeof(qint32) + Serialize::stringSize(proxy.user) + Serialize::stringSize(proxy.password); - } - - size += sizeof(quint32) + sizeof(qint32) * 7; - - EncryptedDescriptor data(size); - data.stream << quint32(dbiChatSizeMax) << qint32(Global::ChatSizeMax()); - data.stream << quint32(dbiMegagroupSizeMax) << qint32(Global::MegagroupSizeMax()); - data.stream << quint32(dbiSavedGifsLimit) << qint32(Global::SavedGifsLimit()); - data.stream << quint32(dbiStickersRecentLimit) << qint32(Global::StickersRecentLimit()); - data.stream << quint32(dbiAutoStart) << qint32(cAutoStart()); - data.stream << quint32(dbiStartMinimized) << qint32(cStartMinimized()); - data.stream << quint32(dbiSendToMenu) << qint32(cSendToMenu()); - data.stream << quint32(dbiWorkMode) << qint32(cWorkMode()); - data.stream << quint32(dbiSeenTrayTooltip) << qint32(cSeenTrayTooltip()); - data.stream << quint32(dbiAutoUpdate) << qint32(cAutoUpdate()); - data.stream << quint32(dbiLastUpdateCheck) << qint32(cLastUpdateCheck()); - data.stream << quint32(dbiScale) << qint32(cConfigScale()); - data.stream << quint32(dbiLang) << qint32(cLang()); - for (auto i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) { - data.stream << quint32(dbiDcOption) << quint32(i.key()); - data.stream << qint32(i->flags) << QString::fromUtf8(i->ip.data(), i->ip.size()); - data.stream << quint32(i->port); - } - data.stream << quint32(dbiLangFile) << cLangFile(); - - data.stream << quint32(dbiConnectionType) << qint32(Global::ConnectionType()); - if (Global::ConnectionType() == dbictHttpProxy || Global::ConnectionType() == dbictTcpProxy) { - auto &proxy = Global::ConnectionProxy(); - data.stream << proxy.host << qint32(proxy.port) << proxy.user << proxy.password; - } - data.stream << quint32(dbiTryIPv6) << qint32(Global::TryIPv6()); - - TWindowPos pos(cWindowPos()); - data.stream << quint32(dbiWindowPosition) << qint32(pos.x) << qint32(pos.y) << qint32(pos.w) << qint32(pos.h) << qint32(pos.moncrc) << qint32(pos.maximized); - - settings.writeEncrypted(data, _settingsKey); + file.close(); + result = true; } + if (remove) file.remove(); + return result; +} - void writeUserSettings() { - _writeUserSettings(); +void _writeUserSettings() { + if (_readingUserSettings) { + LOG(("App Error: attempt to write settings while reading them!")); + return; } + LOG(("App Info: writing encrypted user settings...")); - void writeMtpData() { - _writeMtpData(); - } - - void reset() { - if (_localLoader) { - _localLoader->stop(); - } - - _passKeySalt.clear(); // reset passcode, local key - _draftsMap.clear(); - _draftCursorsMap.clear(); - _fileLocations.clear(); - _fileLocationPairs.clear(); - _fileLocationAliases.clear(); - _imagesMap.clear(); - _draftsNotReadMap.clear(); - _stickerImagesMap.clear(); - _audiosMap.clear(); - _storageImagesSize = _storageStickersSize = _storageAudiosSize = 0; - _webFilesMap.clear(); - _storageWebFilesSize = 0; - _locationsKey = _reportSpamStatusesKey = 0; - _recentStickersKeyOld = 0; - _installedStickersKey = _featuredStickersKey = _recentStickersKey = _archivedStickersKey = 0; - _savedGifsKey = 0; - _backgroundKey = _userSettingsKey = _recentHashtagsAndBotsKey = _savedPeersKey = 0; - _oldMapVersion = _oldSettingsVersion = 0; + if (!_userSettingsKey) { + _userSettingsKey = genKey(); _mapChanged = true; - _writeMap(WriteMapNow); - - _writeMtpData(); + _writeMap(WriteMapFast); } - bool checkPasscode(const QByteArray &passcode) { - MTP::AuthKey tmp; - createLocalKey(passcode, &_passKeySalt, &tmp); - return (tmp == _passKey); + uint32 size = 18 * (sizeof(quint32) + sizeof(qint32)); + size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark()); + 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) + Serialize::stringSize(cDialogLastPath()); + size += sizeof(quint32) + 3 * sizeof(qint32); + size += sizeof(quint32) + 2 * sizeof(qint32); + if (!Global::HiddenPinnedMessages().isEmpty()) { + size += sizeof(quint32) + sizeof(qint32) + Global::HiddenPinnedMessages().size() * (sizeof(PeerId) + sizeof(MsgId)); } - void setPasscode(const QByteArray &passcode) { - createLocalKey(passcode, &_passKeySalt, &_passKey); + EncryptedDescriptor data(size); + data.stream << quint32(dbiSendKey) << qint32(cCtrlEnter() ? dbiskCtrlEnter : dbiskEnter); + data.stream << quint32(dbiTileBackground) << qint32(Window::chatBackground()->tile() ? 1 : 0); + data.stream << quint32(dbiAdaptiveForWide) << qint32(Global::AdaptiveForWide() ? 1 : 0); + data.stream << quint32(dbiAutoLock) << qint32(Global::AutoLock()); + data.stream << quint32(dbiReplaceEmojis) << qint32(cReplaceEmojis() ? 1 : 0); + data.stream << quint32(dbiDefaultAttach) << qint32(cDefaultAttach()); + data.stream << quint32(dbiSoundNotify) << qint32(Global::SoundNotify()); + data.stream << quint32(dbiIncludeMuted) << qint32(Global::IncludeMuted()); + data.stream << quint32(dbiShowingSavedGifs) << qint32(cShowingSavedGifs()); + data.stream << quint32(dbiDesktopNotify) << qint32(Global::DesktopNotify()); + data.stream << quint32(dbiNotifyView) << qint32(Global::NotifyView()); + data.stream << quint32(dbiWindowsNotifications) << qint32(Global::WindowsNotifications()); + data.stream << quint32(dbiAskDownloadPath) << qint32(Global::AskDownloadPath()); + data.stream << quint32(dbiDownloadPath) << (Global::AskDownloadPath() ? QString() : Global::DownloadPath()) << (Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark()); + data.stream << quint32(dbiCompressPastedImage) << qint32(cCompressPastedImage()); + data.stream << quint32(dbiDialogLastPath) << cDialogLastPath(); + data.stream << quint32(dbiSongVolume) << qint32(qRound(Global::SongVolume() * 1e6)); + data.stream << quint32(dbiVideoVolume) << qint32(qRound(Global::VideoVolume() * 1e6)); + data.stream << quint32(dbiAutoDownload) << qint32(cAutoDownloadPhoto()) << qint32(cAutoDownloadAudio()) << qint32(cAutoDownloadGif()); + data.stream << quint32(dbiDialogsMode) << qint32(Global::DialogsModeEnabled() ? 1 : 0) << static_cast(Global::DialogsMode()); + data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0); + data.stream << quint32(dbiAutoPlay) << qint32(cAutoPlayGif() ? 1 : 0); + + { + 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(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; + } + if (!Global::HiddenPinnedMessages().isEmpty()) { + data.stream << quint32(dbiHiddenPinnedMessages) << Global::HiddenPinnedMessages(); + } + + FileWriteDescriptor file(_userSettingsKey); + file.writeEncrypted(data); +} + +void _readUserSettings() { + FileReadDescriptor userSettings; + if (!readEncryptedFile(userSettings, _userSettingsKey)) { + LOG(("App Info: could not read encrypted user settings...")); + _readOldUserSettings(); + return _writeUserSettings(); + } + + LOG(("App Info: reading encrypted user settings...")); + _readingUserSettings = true; + while (!userSettings.stream.atEnd()) { + quint32 blockId; + userSettings.stream >> blockId; + if (!_checkStreamStatus(userSettings.stream)) { + _readingUserSettings = false; + return _writeUserSettings(); + } + + if (!_readSetting(blockId, userSettings.stream, userSettings.version)) { + _readingUserSettings = false; + return _writeUserSettings(); + } + } + _readingUserSettings = false; + LOG(("App Info: encrypted user settings read.")); +} + +void _writeMtpData() { + FileWriteDescriptor mtp(toFilePart(_dataNameKey), SafePath); + if (!_localKey.created()) { + LOG(("App Error: localkey not created in _writeMtpData()")); + return; + } + + MTP::AuthKeysMap keys = MTP::getKeys(); + + quint32 size = sizeof(quint32) + sizeof(qint32) + sizeof(quint32); + size += keys.size() * (sizeof(quint32) + sizeof(quint32) + 256); + + EncryptedDescriptor data(size); + data.stream << quint32(dbiUser) << qint32(MTP::authedId()) << quint32(MTP::maindc()); + for_const (const MTP::AuthKeyPtr &key, keys) { + data.stream << quint32(dbiKey) << quint32(key->getDC()); + key->write(data.stream); + } + + mtp.writeEncrypted(data, _localKey); +} + +void _readMtpData() { + FileReadDescriptor mtp; + if (!readEncryptedFile(mtp, toFilePart(_dataNameKey), SafePath)) { + if (_localKey.created()) { + _readOldMtpData(); + _writeMtpData(); + } + return; + } + + LOG(("App Info: reading encrypted mtp data...")); + while (!mtp.stream.atEnd()) { + quint32 blockId; + mtp.stream >> blockId; + if (!_checkStreamStatus(mtp.stream)) { + return _writeMtpData(); + } + + if (!_readSetting(blockId, mtp.stream, mtp.version)) { + return _writeMtpData(); + } + } +} + +ReadMapState _readMap(const QByteArray &pass) { + uint64 ms = getms(); + QByteArray dataNameUtf8 = (cDataFile() + (cTestMode() ? qsl(":/test/") : QString())).toUtf8(); + FileKey dataNameHash[2]; + hashMd5(dataNameUtf8.constData(), dataNameUtf8.size(), dataNameHash); + _dataNameKey = dataNameHash[0]; + _userBasePath = _basePath + toFilePart(_dataNameKey) + QChar('/'); + + FileReadDescriptor mapData; + if (!readFile(mapData, qsl("map"))) { + return ReadMapFailed; + } + LOG(("App Info: reading map...")); + + QByteArray salt, keyEncrypted, mapEncrypted; + mapData.stream >> salt >> keyEncrypted >> mapEncrypted; + if (!_checkStreamStatus(mapData.stream)) { + return ReadMapFailed; + } + + if (salt.size() != LocalEncryptSaltSize) { + LOG(("App Error: bad salt in map file, size: %1").arg(salt.size())); + return ReadMapFailed; + } + createLocalKey(pass, &salt, &_passKey); + + EncryptedDescriptor keyData, map; + if (!decryptLocal(keyData, keyEncrypted, _passKey)) { + LOG(("App Info: could not decrypt pass-protected key from map file, maybe bad password...")); + return ReadMapPassNeeded; + } + uchar key[LocalEncryptKeySize] = { 0 }; + if (keyData.stream.readRawData((char*)key, LocalEncryptKeySize) != LocalEncryptKeySize || !keyData.stream.atEnd()) { + LOG(("App Error: could not read pass-protected key from map file")); + return ReadMapFailed; + } + _localKey.setKey(key); + + _passKeyEncrypted = keyEncrypted; + _passKeySalt = salt; + + if (!decryptLocal(map, mapEncrypted)) { + LOG(("App Error: could not decrypt map.")); + return ReadMapFailed; + } + LOG(("App Info: reading encrypted map...")); + + DraftsMap draftsMap, draftCursorsMap; + DraftsNotReadMap draftsNotReadMap; + StorageMap imagesMap, stickerImagesMap, audiosMap; + qint64 storageImagesSize = 0, storageStickersSize = 0, storageAudiosSize = 0; + quint64 locationsKey = 0, reportSpamStatusesKey = 0, trustedBotsKey = 0; + quint64 recentStickersKeyOld = 0; + quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, archivedStickersKey = 0; + quint64 savedGifsKey = 0; + quint64 backgroundKey = 0, userSettingsKey = 0, recentHashtagsAndBotsKey = 0, savedPeersKey = 0; + while (!map.stream.atEnd()) { + quint32 keyType; + map.stream >> keyType; + switch (keyType) { + case lskDraft: { + quint32 count = 0; + map.stream >> count; + for (quint32 i = 0; i < count; ++i) { + FileKey key; + quint64 p; + map.stream >> key >> p; + draftsMap.insert(p, key); + draftsNotReadMap.insert(p, true); + } + } break; + case lskDraftPosition: { + quint32 count = 0; + map.stream >> count; + for (quint32 i = 0; i < count; ++i) { + FileKey key; + quint64 p; + map.stream >> key >> p; + draftCursorsMap.insert(p, key); + } + } break; + case lskImages: { + quint32 count = 0; + map.stream >> count; + for (quint32 i = 0; i < count; ++i) { + FileKey key; + quint64 first, second; + qint32 size; + map.stream >> key >> first >> second >> size; + imagesMap.insert(StorageKey(first, second), FileDesc(key, size)); + storageImagesSize += size; + } + } break; + case lskStickerImages: { + quint32 count = 0; + map.stream >> count; + for (quint32 i = 0; i < count; ++i) { + FileKey key; + quint64 first, second; + qint32 size; + map.stream >> key >> first >> second >> size; + stickerImagesMap.insert(StorageKey(first, second), FileDesc(key, size)); + storageStickersSize += size; + } + } break; + case lskAudios: { + quint32 count = 0; + map.stream >> count; + for (quint32 i = 0; i < count; ++i) { + FileKey key; + quint64 first, second; + qint32 size; + map.stream >> key >> first >> second >> size; + audiosMap.insert(StorageKey(first, second), FileDesc(key, size)); + storageAudiosSize += size; + } + } break; + case lskLocations: { + map.stream >> locationsKey; + } break; + case lskReportSpamStatuses: { + map.stream >> reportSpamStatusesKey; + } break; + case lskTrustedBots: { + map.stream >> trustedBotsKey; + } break; + case lskRecentStickersOld: { + map.stream >> recentStickersKeyOld; + } break; + case lskBackground: { + map.stream >> backgroundKey; + } break; + case lskUserSettings: { + map.stream >> userSettingsKey; + } break; + case lskRecentHashtagsAndBots: { + map.stream >> recentHashtagsAndBotsKey; + } break; + case lskStickersOld: { + map.stream >> installedStickersKey; + } break; + case lskStickersKeys: { + map.stream >> installedStickersKey >> featuredStickersKey >> recentStickersKey >> archivedStickersKey; + } break; + case lskSavedGifsOld: { + quint64 key; + map.stream >> key; + } break; + case lskSavedGifs: { + map.stream >> savedGifsKey; + } break; + case lskSavedPeers: { + map.stream >> savedPeersKey; + } break; + default: + LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType)); + return ReadMapFailed; + } + if (!_checkStreamStatus(map.stream)) { + return ReadMapFailed; + } + } + + _draftsMap = draftsMap; + _draftCursorsMap = draftCursorsMap; + _draftsNotReadMap = draftsNotReadMap; + + _imagesMap = imagesMap; + _storageImagesSize = storageImagesSize; + _stickerImagesMap = stickerImagesMap; + _storageStickersSize = storageStickersSize; + _audiosMap = audiosMap; + _storageAudiosSize = storageAudiosSize; + + _locationsKey = locationsKey; + _reportSpamStatusesKey = reportSpamStatusesKey; + _trustedBotsKey = trustedBotsKey; + _recentStickersKeyOld = recentStickersKeyOld; + _installedStickersKey = installedStickersKey; + _featuredStickersKey = featuredStickersKey; + _recentStickersKey = recentStickersKey; + _archivedStickersKey = archivedStickersKey; + _savedGifsKey = savedGifsKey; + _savedPeersKey = savedPeersKey; + _backgroundKey = backgroundKey; + _userSettingsKey = userSettingsKey; + _recentHashtagsAndBotsKey = recentHashtagsAndBotsKey; + _oldMapVersion = mapData.version; + if (_oldMapVersion < AppVersion) { + _mapChanged = true; + _writeMap(); + } else { + _mapChanged = false; + } + + if (_locationsKey) { + _readLocations(); + } + if (_reportSpamStatusesKey) { + _readReportSpamStatuses(); + } + + _readUserSettings(); + _readMtpData(); + + LOG(("Map read time: %1").arg(getms() - ms)); + if (_oldSettingsVersion < AppVersion) { + writeSettings(); + } + return ReadMapDone; +} + +void _writeMap(WriteMapWhen when) { + if (when != WriteMapNow) { + _manager->writeMap(when == WriteMapFast); + return; + } + _manager->writingMap(); + if (!_mapChanged) return; + if (_userBasePath.isEmpty()) { + LOG(("App Error: _userBasePath is empty in writeMap()")); + return; + } + + if (!QDir().exists(_userBasePath)) QDir().mkpath(_userBasePath); + + FileWriteDescriptor map(qsl("map")); + if (_passKeySalt.isEmpty() || _passKeyEncrypted.isEmpty()) { + uchar local5Key[LocalEncryptKeySize] = { 0 }; + QByteArray pass(LocalEncryptKeySize, Qt::Uninitialized), salt(LocalEncryptSaltSize, Qt::Uninitialized); + memset_rand(pass.data(), pass.size()); + memset_rand(salt.data(), salt.size()); + createLocalKey(pass, &salt, &_localKey); + + _passKeySalt.resize(LocalEncryptSaltSize); + memset_rand(_passKeySalt.data(), _passKeySalt.size()); + createLocalKey(QByteArray(), &_passKeySalt, &_passKey); EncryptedDescriptor passKeyData(LocalEncryptKeySize); _localKey.write(passKeyData.stream); _passKeyEncrypted = FileWriteDescriptor::prepareEncrypted(passKeyData, _passKey); + } + map.writeData(_passKeySalt); + map.writeData(_passKeyEncrypted); + uint32 mapSize = 0; + if (!_draftsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftsMap.size() * sizeof(quint64) * 2; + if (!_draftCursorsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftCursorsMap.size() * sizeof(quint64) * 2; + if (!_imagesMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _imagesMap.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 (_reportSpamStatusesKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_trustedBotsKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_recentStickersKeyOld) mapSize += sizeof(quint32) + sizeof(quint64); + if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { + mapSize += sizeof(quint32) + 4 * sizeof(quint64); + } + if (_savedGifsKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_savedPeersKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_backgroundKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_userSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_recentHashtagsAndBotsKey) mapSize += sizeof(quint32) + sizeof(quint64); + EncryptedDescriptor mapData(mapSize); + if (!_draftsMap.isEmpty()) { + mapData.stream << quint32(lskDraft) << quint32(_draftsMap.size()); + for (DraftsMap::const_iterator i = _draftsMap.cbegin(), e = _draftsMap.cend(); i != e; ++i) { + mapData.stream << quint64(i.value()) << quint64(i.key()); + } + } + if (!_draftCursorsMap.isEmpty()) { + mapData.stream << quint32(lskDraftPosition) << quint32(_draftCursorsMap.size()); + for (DraftsMap::const_iterator i = _draftCursorsMap.cbegin(), e = _draftCursorsMap.cend(); i != e; ++i) { + mapData.stream << quint64(i.value()) << quint64(i.key()); + } + } + if (!_imagesMap.isEmpty()) { + mapData.stream << quint32(lskImages) << quint32(_imagesMap.size()); + for (StorageMap::const_iterator i = _imagesMap.cbegin(), e = _imagesMap.cend(); i != e; ++i) { + mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second); + } + } + 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); + } + } + if (!_audiosMap.isEmpty()) { + mapData.stream << quint32(lskAudios) << quint32(_audiosMap.size()); + for (StorageMap::const_iterator i = _audiosMap.cbegin(), e = _audiosMap.cend(); i != e; ++i) { + mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second); + } + } + if (_locationsKey) { + mapData.stream << quint32(lskLocations) << quint64(_locationsKey); + } + if (_reportSpamStatusesKey) { + mapData.stream << quint32(lskReportSpamStatuses) << quint64(_reportSpamStatusesKey); + } + if (_trustedBotsKey) { + mapData.stream << quint32(lskTrustedBots) << quint64(_trustedBotsKey); + } + if (_recentStickersKeyOld) { + mapData.stream << quint32(lskRecentStickersOld) << quint64(_recentStickersKeyOld); + } + if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { + mapData.stream << quint32(lskStickersKeys); + mapData.stream << quint64(_installedStickersKey) << quint64(_featuredStickersKey) << quint64(_recentStickersKey) << quint64(_archivedStickersKey); + } + if (_savedGifsKey) { + mapData.stream << quint32(lskSavedGifs) << quint64(_savedGifsKey); + } + if (_savedPeersKey) { + mapData.stream << quint32(lskSavedPeers) << quint64(_savedPeersKey); + } + if (_backgroundKey) { + mapData.stream << quint32(lskBackground) << quint64(_backgroundKey); + } + if (_userSettingsKey) { + mapData.stream << quint32(lskUserSettings) << quint64(_userSettingsKey); + } + if (_recentHashtagsAndBotsKey) { + mapData.stream << quint32(lskRecentHashtagsAndBots) << quint64(_recentHashtagsAndBotsKey); + } + map.writeEncrypted(mapData); + + _mapChanged = false; +} + +} // namespace + +void finish() { + if (_manager) { + _writeMap(WriteMapNow); + _manager->finish(); + _manager->deleteLater(); + _manager = 0; + delete _localLoader; + _localLoader = 0; + } +} + +void start() { + t_assert(_manager == 0); + + _manager = new internal::Manager(); + _localLoader = new TaskQueue(0, FileLoaderQueueStopTimeout); + + _basePath = cWorkingDir() + qsl("tdata/"); + if (!QDir().exists(_basePath)) QDir().mkpath(_basePath); + + FileReadDescriptor settingsData; + if (!readFile(settingsData, cTestMode() ? qsl("settings_test") : qsl("settings"), SafePath)) { + _readOldSettings(); + _readOldUserSettings(false); // needed further in _readUserSettings + _readOldMtpData(false); // needed further in _readMtpData + return writeSettings(); + } + LOG(("App Info: reading settings...")); + + QByteArray salt, settingsEncrypted; + settingsData.stream >> salt >> settingsEncrypted; + if (!_checkStreamStatus(settingsData.stream)) { + return writeSettings(); + } + + if (salt.size() != LocalEncryptSaltSize) { + LOG(("App Error: bad salt in settings file, size: %1").arg(salt.size())); + return writeSettings(); + } + createLocalKey(QByteArray(), &salt, &_settingsKey); + + EncryptedDescriptor settings; + if (!decryptLocal(settings, settingsEncrypted, _settingsKey)) { + LOG(("App Error: could not decrypt settings from settings file, maybe bad passcode...")); + return writeSettings(); + } + MTP::DcOptions dcOpts; + { + QReadLocker lock(MTP::dcOptionsMutex()); + dcOpts = Global::DcOptions(); + } + _dcOpts = &dcOpts; + LOG(("App Info: reading encrypted settings...")); + while (!settings.stream.atEnd()) { + quint32 blockId; + settings.stream >> blockId; + if (!_checkStreamStatus(settings.stream)) { + return writeSettings(); + } + + if (!_readSetting(blockId, settings.stream, settingsData.version)) { + return writeSettings(); + } + } + if (dcOpts.isEmpty()) { + const BuiltInDc *bdcs = builtInDcs(); + for (int i = 0, l = builtInDcsCount(); i < l; ++i) { + MTPDdcOption::Flags flags = 0; + MTP::ShiftedDcId idWithShift = MTP::shiftDcId(bdcs[i].id, flags); + dcOpts.insert(idWithShift, MTP::DcOption(bdcs[i].id, flags, bdcs[i].ip, bdcs[i].port)); + DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3").arg(bdcs[i].id).arg(bdcs[i].ip).arg(bdcs[i].port)); + } + + const BuiltInDc *bdcsipv6 = builtInDcsIPv6(); + for (int i = 0, l = builtInDcsCountIPv6(); i < l; ++i) { + MTPDdcOption::Flags flags = MTPDdcOption::Flag::f_ipv6; + MTP::ShiftedDcId idWithShift = MTP::shiftDcId(bdcsipv6[i].id, flags); + dcOpts.insert(idWithShift, MTP::DcOption(bdcsipv6[i].id, flags, bdcsipv6[i].ip, bdcsipv6[i].port)); + DEBUG_LOG(("MTP Info: adding built in DC %1 IPv6 connect option: %2:%3").arg(bdcsipv6[i].id).arg(bdcsipv6[i].ip).arg(bdcsipv6[i].port)); + } + } + { + QWriteLocker lock(MTP::dcOptionsMutex()); + Global::SetDcOptions(dcOpts); + } + + _oldSettingsVersion = settingsData.version; + _settingsSalt = salt; +} + +void writeSettings() { + if (_basePath.isEmpty()) { + LOG(("App Error: _basePath is empty in writeSettings()")); + return; + } + + if (!QDir().exists(_basePath)) QDir().mkpath(_basePath); + + FileWriteDescriptor settings(cTestMode() ? qsl("settings_test") : qsl("settings"), SafePath); + if (_settingsSalt.isEmpty() || !_settingsKey.created()) { + _settingsSalt.resize(LocalEncryptSaltSize); + memset_rand(_settingsSalt.data(), _settingsSalt.size()); + createLocalKey(QByteArray(), &_settingsSalt, &_settingsKey); + } + settings.writeData(_settingsSalt); + + MTP::DcOptions dcOpts; + { + QReadLocker lock(MTP::dcOptionsMutex()); + dcOpts = Global::DcOptions(); + } + if (dcOpts.isEmpty()) { + const BuiltInDc *bdcs = builtInDcs(); + for (int i = 0, l = builtInDcsCount(); i < l; ++i) { + MTPDdcOption::Flags flags = 0; + MTP::ShiftedDcId idWithShift = MTP::shiftDcId(bdcs[i].id, flags); + dcOpts.insert(idWithShift, MTP::DcOption(bdcs[i].id, flags, bdcs[i].ip, bdcs[i].port)); + DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3").arg(bdcs[i].id).arg(bdcs[i].ip).arg(bdcs[i].port)); + } + + const BuiltInDc *bdcsipv6 = builtInDcsIPv6(); + for (int i = 0, l = builtInDcsCountIPv6(); i < l; ++i) { + MTPDdcOption::Flags flags = MTPDdcOption::Flag::f_ipv6; + MTP::ShiftedDcId idWithShift = MTP::shiftDcId(bdcsipv6[i].id, flags); + dcOpts.insert(idWithShift, MTP::DcOption(bdcsipv6[i].id, flags, bdcsipv6[i].ip, bdcsipv6[i].port)); + DEBUG_LOG(("MTP Info: adding built in DC %1 IPv6 connect option: %2:%3").arg(bdcsipv6[i].id).arg(bdcsipv6[i].ip).arg(bdcsipv6[i].port)); + } + + QWriteLocker lock(MTP::dcOptionsMutex()); + Global::SetDcOptions(dcOpts); + } + + quint32 size = 12 * (sizeof(quint32) + sizeof(qint32)); + for (auto i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) { + size += sizeof(quint32) + sizeof(quint32) + sizeof(quint32); + size += sizeof(quint32) + Serialize::stringSize(QString::fromUtf8(i->ip.data(), i->ip.size())); + } + size += sizeof(quint32) + Serialize::stringSize(cLangFile()); + + size += sizeof(quint32) + sizeof(qint32); + if (Global::ConnectionType() == dbictHttpProxy || Global::ConnectionType() == dbictTcpProxy) { + auto &proxy = Global::ConnectionProxy(); + size += Serialize::stringSize(proxy.host) + sizeof(qint32) + Serialize::stringSize(proxy.user) + Serialize::stringSize(proxy.password); + } + + size += sizeof(quint32) + sizeof(qint32) * 7; + + EncryptedDescriptor data(size); + data.stream << quint32(dbiChatSizeMax) << qint32(Global::ChatSizeMax()); + data.stream << quint32(dbiMegagroupSizeMax) << qint32(Global::MegagroupSizeMax()); + data.stream << quint32(dbiSavedGifsLimit) << qint32(Global::SavedGifsLimit()); + data.stream << quint32(dbiStickersRecentLimit) << qint32(Global::StickersRecentLimit()); + data.stream << quint32(dbiAutoStart) << qint32(cAutoStart()); + data.stream << quint32(dbiStartMinimized) << qint32(cStartMinimized()); + data.stream << quint32(dbiSendToMenu) << qint32(cSendToMenu()); + data.stream << quint32(dbiWorkMode) << qint32(cWorkMode()); + data.stream << quint32(dbiSeenTrayTooltip) << qint32(cSeenTrayTooltip()); + data.stream << quint32(dbiAutoUpdate) << qint32(cAutoUpdate()); + data.stream << quint32(dbiLastUpdateCheck) << qint32(cLastUpdateCheck()); + data.stream << quint32(dbiScale) << qint32(cConfigScale()); + data.stream << quint32(dbiLang) << qint32(cLang()); + for (auto i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) { + data.stream << quint32(dbiDcOption) << quint32(i.key()); + data.stream << qint32(i->flags) << QString::fromUtf8(i->ip.data(), i->ip.size()); + data.stream << quint32(i->port); + } + data.stream << quint32(dbiLangFile) << cLangFile(); + + data.stream << quint32(dbiConnectionType) << qint32(Global::ConnectionType()); + if (Global::ConnectionType() == dbictHttpProxy || Global::ConnectionType() == dbictTcpProxy) { + auto &proxy = Global::ConnectionProxy(); + data.stream << proxy.host << qint32(proxy.port) << proxy.user << proxy.password; + } + data.stream << quint32(dbiTryIPv6) << qint32(Global::TryIPv6()); + + TWindowPos pos(cWindowPos()); + data.stream << quint32(dbiWindowPosition) << qint32(pos.x) << qint32(pos.y) << qint32(pos.w) << qint32(pos.h) << qint32(pos.moncrc) << qint32(pos.maximized); + + settings.writeEncrypted(data, _settingsKey); +} + +void writeUserSettings() { + _writeUserSettings(); +} + +void writeMtpData() { + _writeMtpData(); +} + +void reset() { + if (_localLoader) { + _localLoader->stop(); + } + + _passKeySalt.clear(); // reset passcode, local key + _draftsMap.clear(); + _draftCursorsMap.clear(); + _fileLocations.clear(); + _fileLocationPairs.clear(); + _fileLocationAliases.clear(); + _imagesMap.clear(); + _draftsNotReadMap.clear(); + _stickerImagesMap.clear(); + _audiosMap.clear(); + _storageImagesSize = _storageStickersSize = _storageAudiosSize = 0; + _webFilesMap.clear(); + _storageWebFilesSize = 0; + _locationsKey = _reportSpamStatusesKey = _trustedBotsKey = 0; + _recentStickersKeyOld = 0; + _installedStickersKey = _featuredStickersKey = _recentStickersKey = _archivedStickersKey = 0; + _savedGifsKey = 0; + _backgroundKey = _userSettingsKey = _recentHashtagsAndBotsKey = _savedPeersKey = 0; + _oldMapVersion = _oldSettingsVersion = 0; + _mapChanged = true; + _writeMap(WriteMapNow); + + _writeMtpData(); +} + +bool checkPasscode(const QByteArray &passcode) { + MTP::AuthKey tmp; + createLocalKey(passcode, &_passKeySalt, &tmp); + return (tmp == _passKey); +} + +void setPasscode(const QByteArray &passcode) { + createLocalKey(passcode, &_passKeySalt, &_passKey); + + EncryptedDescriptor passKeyData(LocalEncryptKeySize); + _localKey.write(passKeyData.stream); + _passKeyEncrypted = FileWriteDescriptor::prepareEncrypted(passKeyData, _passKey); + + _mapChanged = true; + _writeMap(WriteMapNow); + + Global::SetLocalPasscode(!passcode.isEmpty()); + Global::RefLocalPasscodeChanged().notify(); +} + +ReadMapState readMap(const QByteArray &pass) { + ReadMapState result = _readMap(pass); + if (result == ReadMapFailed) { _mapChanged = true; _writeMap(WriteMapNow); - - Global::SetLocalPasscode(!passcode.isEmpty()); - Global::RefLocalPasscodeChanged().notify(); } + return result; +} - ReadMapState readMap(const QByteArray &pass) { - ReadMapState result = _readMap(pass); - if (result == ReadMapFailed) { - _mapChanged = true; - _writeMap(WriteMapNow); - } - return result; - } +int32 oldMapVersion() { + return _oldMapVersion; +} - int32 oldMapVersion() { - return _oldMapVersion; - } +int32 oldSettingsVersion() { + return _oldSettingsVersion; +} - int32 oldSettingsVersion() { - return _oldSettingsVersion; - } +void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const MessageDraft &editDraft) { + if (!_working()) return; - void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const MessageDraft &editDraft) { - if (!_working()) return; - - if (localDraft.msgId <= 0 && localDraft.textWithTags.text.isEmpty() && editDraft.msgId <= 0) { - auto i = _draftsMap.find(peer); - if (i != _draftsMap.cend()) { - clearKey(i.value()); - _draftsMap.erase(i); - _mapChanged = true; - _writeMap(); - } - - _draftsNotReadMap.remove(peer); - } else { - auto i = _draftsMap.constFind(peer); - if (i == _draftsMap.cend()) { - i = _draftsMap.insert(peer, genKey()); - _mapChanged = true; - _writeMap(WriteMapFast); - } - - auto msgTags = FlatTextarea::serializeTagsList(localDraft.textWithTags.tags); - auto editTags = FlatTextarea::serializeTagsList(editDraft.textWithTags.tags); - - int size = sizeof(quint64); - size += Serialize::stringSize(localDraft.textWithTags.text) + Serialize::bytearraySize(msgTags) + 2 * sizeof(qint32); - size += Serialize::stringSize(editDraft.textWithTags.text) + Serialize::bytearraySize(editTags) + 2 * sizeof(qint32); - - EncryptedDescriptor data(size); - data.stream << quint64(peer); - data.stream << localDraft.textWithTags.text << msgTags; - data.stream << qint32(localDraft.msgId) << qint32(localDraft.previewCancelled ? 1 : 0); - data.stream << editDraft.textWithTags.text << editTags; - data.stream << qint32(editDraft.msgId) << qint32(editDraft.previewCancelled ? 1 : 0); - - FileWriteDescriptor file(i.value()); - file.writeEncrypted(data); - - _draftsNotReadMap.remove(peer); - } - } - - void clearDraftCursors(const PeerId &peer) { - DraftsMap::iterator i = _draftCursorsMap.find(peer); - if (i != _draftCursorsMap.cend()) { + if (localDraft.msgId <= 0 && localDraft.textWithTags.text.isEmpty() && editDraft.msgId <= 0) { + auto i = _draftsMap.find(peer); + if (i != _draftsMap.cend()) { clearKey(i.value()); - _draftCursorsMap.erase(i); + _draftsMap.erase(i); _mapChanged = true; _writeMap(); } + + _draftsNotReadMap.remove(peer); + } else { + auto i = _draftsMap.constFind(peer); + if (i == _draftsMap.cend()) { + i = _draftsMap.insert(peer, genKey()); + _mapChanged = true; + _writeMap(WriteMapFast); + } + + auto msgTags = FlatTextarea::serializeTagsList(localDraft.textWithTags.tags); + auto editTags = FlatTextarea::serializeTagsList(editDraft.textWithTags.tags); + + int size = sizeof(quint64); + size += Serialize::stringSize(localDraft.textWithTags.text) + Serialize::bytearraySize(msgTags) + 2 * sizeof(qint32); + size += Serialize::stringSize(editDraft.textWithTags.text) + Serialize::bytearraySize(editTags) + 2 * sizeof(qint32); + + EncryptedDescriptor data(size); + data.stream << quint64(peer); + data.stream << localDraft.textWithTags.text << msgTags; + data.stream << qint32(localDraft.msgId) << qint32(localDraft.previewCancelled ? 1 : 0); + data.stream << editDraft.textWithTags.text << editTags; + data.stream << qint32(editDraft.msgId) << qint32(editDraft.previewCancelled ? 1 : 0); + + FileWriteDescriptor file(i.value()); + file.writeEncrypted(data); + + _draftsNotReadMap.remove(peer); + } +} + +void clearDraftCursors(const PeerId &peer) { + DraftsMap::iterator i = _draftCursorsMap.find(peer); + if (i != _draftCursorsMap.cend()) { + clearKey(i.value()); + _draftCursorsMap.erase(i); + _mapChanged = true; + _writeMap(); + } +} + +void _readDraftCursors(const PeerId &peer, MessageCursor &localCursor, MessageCursor &editCursor) { + DraftsMap::iterator j = _draftCursorsMap.find(peer); + if (j == _draftCursorsMap.cend()) { + return; } - void _readDraftCursors(const PeerId &peer, MessageCursor &localCursor, MessageCursor &editCursor) { - DraftsMap::iterator j = _draftCursorsMap.find(peer); - if (j == _draftCursorsMap.cend()) { - return; - } - - FileReadDescriptor draft; - if (!readEncryptedFile(draft, j.value())) { - clearDraftCursors(peer); - return; - } - quint64 draftPeer; - qint32 localPosition = 0, localAnchor = 0, localScroll = QFIXED_MAX; - qint32 editPosition = 0, editAnchor = 0, editScroll = QFIXED_MAX; - draft.stream >> draftPeer >> localPosition >> localAnchor >> localScroll; - if (!draft.stream.atEnd()) { - draft.stream >> editPosition >> editAnchor >> editScroll; - } - - if (draftPeer != peer) { - clearDraftCursors(peer); - return; - } - - localCursor = MessageCursor(localPosition, localAnchor, localScroll); - editCursor = MessageCursor(editPosition, editAnchor, editScroll); + FileReadDescriptor draft; + if (!readEncryptedFile(draft, j.value())) { + clearDraftCursors(peer); + return; + } + quint64 draftPeer; + qint32 localPosition = 0, localAnchor = 0, localScroll = QFIXED_MAX; + qint32 editPosition = 0, editAnchor = 0, editScroll = QFIXED_MAX; + draft.stream >> draftPeer >> localPosition >> localAnchor >> localScroll; + if (!draft.stream.atEnd()) { + draft.stream >> editPosition >> editAnchor >> editScroll; } - void readDraftsWithCursors(History *h) { - PeerId peer = h->peer->id; - if (!_draftsNotReadMap.remove(peer)) { - clearDraftCursors(peer); - return; - } + if (draftPeer != peer) { + clearDraftCursors(peer); + return; + } - DraftsMap::iterator j = _draftsMap.find(peer); - if (j == _draftsMap.cend()) { - clearDraftCursors(peer); - return; - } - FileReadDescriptor draft; - if (!readEncryptedFile(draft, j.value())) { - clearKey(j.value()); - _draftsMap.erase(j); - clearDraftCursors(peer); - return; - } + localCursor = MessageCursor(localPosition, localAnchor, localScroll); + editCursor = MessageCursor(editPosition, editAnchor, editScroll); +} - quint64 draftPeer = 0; - TextWithTags msgData, editData; - QByteArray msgTagsSerialized, editTagsSerialized; - qint32 msgReplyTo = 0, msgPreviewCancelled = 0, editMsgId = 0, editPreviewCancelled = 0; - draft.stream >> draftPeer >> msgData.text; - if (draft.version >= 9048) { - draft.stream >> msgTagsSerialized; - } - if (draft.version >= 7021) { - draft.stream >> msgReplyTo; - if (draft.version >= 8001) { - draft.stream >> msgPreviewCancelled; - if (!draft.stream.atEnd()) { - draft.stream >> editData.text; - if (draft.version >= 9048) { - draft.stream >> editTagsSerialized; - } - draft.stream >> editMsgId >> editPreviewCancelled; +void readDraftsWithCursors(History *h) { + PeerId peer = h->peer->id; + if (!_draftsNotReadMap.remove(peer)) { + clearDraftCursors(peer); + return; + } + + DraftsMap::iterator j = _draftsMap.find(peer); + if (j == _draftsMap.cend()) { + clearDraftCursors(peer); + return; + } + FileReadDescriptor draft; + if (!readEncryptedFile(draft, j.value())) { + clearKey(j.value()); + _draftsMap.erase(j); + clearDraftCursors(peer); + return; + } + + quint64 draftPeer = 0; + TextWithTags msgData, editData; + QByteArray msgTagsSerialized, editTagsSerialized; + qint32 msgReplyTo = 0, msgPreviewCancelled = 0, editMsgId = 0, editPreviewCancelled = 0; + draft.stream >> draftPeer >> msgData.text; + if (draft.version >= 9048) { + draft.stream >> msgTagsSerialized; + } + if (draft.version >= 7021) { + draft.stream >> msgReplyTo; + if (draft.version >= 8001) { + draft.stream >> msgPreviewCancelled; + if (!draft.stream.atEnd()) { + draft.stream >> editData.text; + if (draft.version >= 9048) { + draft.stream >> editTagsSerialized; } + draft.stream >> editMsgId >> editPreviewCancelled; } } - if (draftPeer != peer) { - clearKey(j.value()); - _draftsMap.erase(j); - clearDraftCursors(peer); - return; - } + } + if (draftPeer != peer) { + clearKey(j.value()); + _draftsMap.erase(j); + clearDraftCursors(peer); + return; + } - msgData.tags = FlatTextarea::deserializeTagsList(msgTagsSerialized, msgData.text.size()); - editData.tags = FlatTextarea::deserializeTagsList(editTagsSerialized, editData.text.size()); + msgData.tags = FlatTextarea::deserializeTagsList(msgTagsSerialized, msgData.text.size()); + editData.tags = FlatTextarea::deserializeTagsList(editTagsSerialized, editData.text.size()); - MessageCursor msgCursor, editCursor; - _readDraftCursors(peer, msgCursor, editCursor); + MessageCursor msgCursor, editCursor; + _readDraftCursors(peer, msgCursor, editCursor); - if (!h->localDraft()) { - if (msgData.text.isEmpty() && !msgReplyTo) { - h->clearLocalDraft(); - } else { - h->setLocalDraft(std_::make_unique(msgData, msgReplyTo, msgCursor, msgPreviewCancelled)); - } - } - if (!editMsgId) { - h->clearEditDraft(); + if (!h->localDraft()) { + if (msgData.text.isEmpty() && !msgReplyTo) { + h->clearLocalDraft(); } else { - h->setEditDraft(std_::make_unique(editData, editMsgId, editCursor, editPreviewCancelled)); + h->setLocalDraft(std_::make_unique(msgData, msgReplyTo, msgCursor, msgPreviewCancelled)); } } - - void writeDraftCursors(const PeerId &peer, const MessageCursor &msgCursor, const MessageCursor &editCursor) { - if (!_working()) return; - - if (msgCursor == MessageCursor() && editCursor == MessageCursor()) { - clearDraftCursors(peer); - } else { - DraftsMap::const_iterator i = _draftCursorsMap.constFind(peer); - if (i == _draftCursorsMap.cend()) { - i = _draftCursorsMap.insert(peer, genKey()); - _mapChanged = true; - _writeMap(WriteMapFast); - } - - EncryptedDescriptor data(sizeof(quint64) + sizeof(qint32) * 3); - data.stream << quint64(peer) << qint32(msgCursor.position) << qint32(msgCursor.anchor) << qint32(msgCursor.scroll); - data.stream << qint32(editCursor.position) << qint32(editCursor.anchor) << qint32(editCursor.scroll); - - FileWriteDescriptor file(i.value()); - file.writeEncrypted(data); - } + if (!editMsgId) { + h->clearEditDraft(); + } else { + h->setEditDraft(std_::make_unique(editData, editMsgId, editCursor, editPreviewCancelled)); } +} - bool hasDraftCursors(const PeerId &peer) { - return _draftCursorsMap.contains(peer); - } +void writeDraftCursors(const PeerId &peer, const MessageCursor &msgCursor, const MessageCursor &editCursor) { + if (!_working()) return; - bool hasDraft(const PeerId &peer) { - return _draftsMap.contains(peer); - } - - void writeFileLocation(MediaKey location, const FileLocation &local) { - if (local.fname.isEmpty()) return; - - FileLocationAliases::const_iterator aliasIt = _fileLocationAliases.constFind(location); - if (aliasIt != _fileLocationAliases.cend()) { - location = aliasIt.value(); + if (msgCursor == MessageCursor() && editCursor == MessageCursor()) { + clearDraftCursors(peer); + } else { + DraftsMap::const_iterator i = _draftCursorsMap.constFind(peer); + if (i == _draftCursorsMap.cend()) { + i = _draftCursorsMap.insert(peer, genKey()); + _mapChanged = true; + _writeMap(WriteMapFast); } - FileLocationPairs::iterator i = _fileLocationPairs.find(local.fname); - if (i != _fileLocationPairs.cend()) { - if (i.value().second == local) { - if (i.value().first != location) { - _fileLocationAliases.insert(location, i.value().first); - _writeLocations(WriteMapFast); - } - return; - } + EncryptedDescriptor data(sizeof(quint64) + sizeof(qint32) * 3); + data.stream << quint64(peer) << qint32(msgCursor.position) << qint32(msgCursor.anchor) << qint32(msgCursor.scroll); + data.stream << qint32(editCursor.position) << qint32(editCursor.anchor) << qint32(editCursor.scroll); + + FileWriteDescriptor file(i.value()); + file.writeEncrypted(data); + } +} + +bool hasDraftCursors(const PeerId &peer) { + return _draftCursorsMap.contains(peer); +} + +bool hasDraft(const PeerId &peer) { + return _draftsMap.contains(peer); +} + +void writeFileLocation(MediaKey location, const FileLocation &local) { + if (local.fname.isEmpty()) return; + + FileLocationAliases::const_iterator aliasIt = _fileLocationAliases.constFind(location); + if (aliasIt != _fileLocationAliases.cend()) { + location = aliasIt.value(); + } + + FileLocationPairs::iterator i = _fileLocationPairs.find(local.fname); + if (i != _fileLocationPairs.cend()) { + if (i.value().second == local) { if (i.value().first != location) { - for (FileLocations::iterator j = _fileLocations.find(i.value().first), e = _fileLocations.end(); (j != e) && (j.key() == i.value().first);) { - if (j.value() == i.value().second) { - _fileLocations.erase(j); - break; - } - } - _fileLocationPairs.erase(i); + _fileLocationAliases.insert(location, i.value().first); + _writeLocations(WriteMapFast); } - } - _fileLocations.insert(location, local); - _fileLocationPairs.insert(local.fname, FileLocationPair(location, local)); - _writeLocations(WriteMapFast); - } - - FileLocation readFileLocation(MediaKey location, bool check) { - FileLocationAliases::const_iterator aliasIt = _fileLocationAliases.constFind(location); - if (aliasIt != _fileLocationAliases.cend()) { - location = aliasIt.value(); - } - - FileLocations::iterator i = _fileLocations.find(location); - for (FileLocations::iterator i = _fileLocations.find(location); (i != _fileLocations.end()) && (i.key() == location);) { - if (check) { - if (!i.value().check()) { - _fileLocationPairs.remove(i.value().fname); - i = _fileLocations.erase(i); - _writeLocations(); - continue; - } - } - return i.value(); - } - return FileLocation(); - } - - qint32 _storageImageSize(qint32 rawlen) { - // fulllen + storagekey + type + len + data - qint32 result = sizeof(uint32) + sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + rawlen; - if (result & 0x0F) result += 0x10 - (result & 0x0F); - result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5 - return result; - } - - qint32 _storageStickerSize(qint32 rawlen) { - // fulllen + storagekey + len + data - qint32 result = sizeof(uint32) + sizeof(quint64) * 2 + sizeof(quint32) + rawlen; - if (result & 0x0F) result += 0x10 - (result & 0x0F); - result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5 - return result; - } - - qint32 _storageAudioSize(qint32 rawlen) { - // fulllen + storagekey + len + data - qint32 result = sizeof(uint32) + sizeof(quint64) * 2 + sizeof(quint32) + rawlen; - if (result & 0x0F) result += 0x10 - (result & 0x0F); - result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5 - return result; - } - - void writeImage(const StorageKey &location, const ImagePtr &image) { - if (image->isNull() || !image->loaded()) return; - if (_imagesMap.constFind(location) != _imagesMap.cend()) return; - - QByteArray fmt = image->savedFormat(); - StorageFileType format = StorageFileUnknown; - if (fmt == "JPG") { - format = StorageFileJpeg; - } else if (fmt == "PNG") { - format = StorageFilePng; - } else if (fmt == "GIF") { - format = StorageFileGif; - } - if (format) { - image->forget(); - writeImage(location, StorageImageSaved(format, image->savedData()), false); - } - } - - void writeImage(const StorageKey &location, const StorageImageSaved &image, bool overwrite) { - if (!_working()) return; - - qint32 size = _storageImageSize(image.data.size()); - StorageMap::const_iterator i = _imagesMap.constFind(location); - if (i == _imagesMap.cend()) { - i = _imagesMap.insert(location, FileDesc(genKey(UserPath), size)); - _storageImagesSize += size; - _mapChanged = true; - _writeMap(); - } else if (!overwrite) { return; } - EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + image.data.size()); - data.stream << quint64(location.first) << quint64(location.second) << quint32(image.type) << image.data; - FileWriteDescriptor file(i.value().first, UserPath); - file.writeEncrypted(data); - if (i.value().second != size) { - _storageImagesSize += size; - _storageImagesSize -= i.value().second; - _imagesMap[location].second = size; - } - } - - class AbstractCachedLoadTask : public Task { - public: - - AbstractCachedLoadTask(const FileKey &key, const StorageKey &location, bool readImageFlag, mtpFileLoader *loader) : - _key(key), _location(location), _readImageFlag(readImageFlag), _loader(loader), _result(0) { - } - void process() { - FileReadDescriptor image; - if (!readEncryptedFile(image, _key, UserPath)) { - return; - } - - QByteArray imageData; - quint64 locFirst, locSecond; - quint32 imageType; - readFromStream(image.stream, locFirst, locSecond, imageType, imageData); - - // we're saving files now before we have actual location - //if (locFirst != _location.first || locSecond != _location.second) { - // return; - //} - - _result = new Result(StorageFileType(imageType), imageData, _readImageFlag); - } - void finish() { - if (_result) { - _loader->localLoaded(_result->image, _result->format, _result->pixmap); - } else { - clearInMap(); - _loader->localLoaded(StorageImageSaved()); - } - } - virtual void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, quint32 &type, QByteArray &data) = 0; - virtual void clearInMap() = 0; - virtual ~AbstractCachedLoadTask() { - deleteAndMark(_result); - } - - protected: - FileKey _key; - StorageKey _location; - bool _readImageFlag; - struct Result { - Result(StorageFileType type, const QByteArray &data, bool readImageFlag) : image(type, data) { - if (readImageFlag) { - QByteArray guessFormat; - switch (type) { - case StorageFileGif: guessFormat = "GIF"; break; - case StorageFileJpeg: guessFormat = "JPG"; break; - case StorageFilePng: guessFormat = "PNG"; break; - case StorageFileWebp: guessFormat = "WEBP"; break; - default: guessFormat = QByteArray(); break; - } - pixmap = App::pixmapFromImageInPlace(App::readImage(data, &guessFormat, false)); - if (!pixmap.isNull()) { - format = guessFormat; - } + if (i.value().first != location) { + for (FileLocations::iterator j = _fileLocations.find(i.value().first), e = _fileLocations.end(); (j != e) && (j.key() == i.value().first);) { + if (j.value() == i.value().second) { + _fileLocations.erase(j); + break; } } - StorageImageSaved image; - QByteArray format; - QPixmap pixmap; - }; - mtpFileLoader *_loader; - Result *_result; - - }; - - class ImageLoadTask : public AbstractCachedLoadTask { - public: - ImageLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) : - AbstractCachedLoadTask(key, location, true, loader) { + _fileLocationPairs.erase(i); } - void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, quint32 &type, QByteArray &data) { - stream >> first >> second >> type >> data; - } - void clearInMap() { - StorageMap::iterator j = _imagesMap.find(_location); - if (j != _imagesMap.cend() && j->first == _key) { - clearKey(_key, UserPath); - _storageImagesSize -= j->second; - _imagesMap.erase(j); + } + _fileLocations.insert(location, local); + _fileLocationPairs.insert(local.fname, FileLocationPair(location, local)); + _writeLocations(WriteMapFast); +} + +FileLocation readFileLocation(MediaKey location, bool check) { + FileLocationAliases::const_iterator aliasIt = _fileLocationAliases.constFind(location); + if (aliasIt != _fileLocationAliases.cend()) { + location = aliasIt.value(); + } + + FileLocations::iterator i = _fileLocations.find(location); + for (FileLocations::iterator i = _fileLocations.find(location); (i != _fileLocations.end()) && (i.key() == location);) { + if (check) { + if (!i.value().check()) { + _fileLocationPairs.remove(i.value().fname); + i = _fileLocations.erase(i); + _writeLocations(); + continue; } } - }; - - TaskId startImageLoad(const StorageKey &location, mtpFileLoader *loader) { - StorageMap::const_iterator j = _imagesMap.constFind(location); - if (j == _imagesMap.cend() || !_localLoader) { - return 0; - } - return _localLoader->addTask(new ImageLoadTask(j->first, location, loader)); + return i.value(); } + return FileLocation(); +} - int32 hasImages() { - return _imagesMap.size(); +qint32 _storageImageSize(qint32 rawlen) { + // fulllen + storagekey + type + len + data + qint32 result = sizeof(uint32) + sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + rawlen; + if (result & 0x0F) result += 0x10 - (result & 0x0F); + result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5 + return result; +} + +qint32 _storageStickerSize(qint32 rawlen) { + // fulllen + storagekey + len + data + qint32 result = sizeof(uint32) + sizeof(quint64) * 2 + sizeof(quint32) + rawlen; + if (result & 0x0F) result += 0x10 - (result & 0x0F); + result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5 + return result; +} + +qint32 _storageAudioSize(qint32 rawlen) { + // fulllen + storagekey + len + data + qint32 result = sizeof(uint32) + sizeof(quint64) * 2 + sizeof(quint32) + rawlen; + if (result & 0x0F) result += 0x10 - (result & 0x0F); + result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5 + return result; +} + +void writeImage(const StorageKey &location, const ImagePtr &image) { + if (image->isNull() || !image->loaded()) return; + if (_imagesMap.constFind(location) != _imagesMap.cend()) return; + + QByteArray fmt = image->savedFormat(); + StorageFileType format = StorageFileUnknown; + if (fmt == "JPG") { + format = StorageFileJpeg; + } else if (fmt == "PNG") { + format = StorageFilePng; + } else if (fmt == "GIF") { + format = StorageFileGif; } - - qint64 storageImagesSize() { - return _storageImagesSize; + if (format) { + image->forget(); + writeImage(location, StorageImageSaved(format, image->savedData()), false); } +} - void writeStickerImage(const StorageKey &location, const QByteArray &sticker, bool overwrite) { - if (!_working()) return; +void writeImage(const StorageKey &location, const StorageImageSaved &image, bool overwrite) { + if (!_working()) return; - qint32 size = _storageStickerSize(sticker.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(); - } else if (!overwrite) { - return; - } - EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + sticker.size()); - data.stream << quint64(location.first) << quint64(location.second) << sticker; - FileWriteDescriptor file(i.value().first, UserPath); - file.writeEncrypted(data); - if (i.value().second != size) { - _storageStickersSize += size; - _storageStickersSize -= i.value().second; - _stickerImagesMap[location].second = size; - } - } - - class StickerImageLoadTask : public AbstractCachedLoadTask { - public: - StickerImageLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) : - AbstractCachedLoadTask(key, location, true, loader) { - } - void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, quint32 &type, QByteArray &data) { - stream >> first >> second >> data; - type = StorageFilePartial; - } - void clearInMap() { - auto j = _stickerImagesMap.find(_location); - if (j != _stickerImagesMap.cend() && j->first == _key) { - clearKey(j.value().first, UserPath); - _storageStickersSize -= j.value().second; - _stickerImagesMap.erase(j); - } - } - }; - - TaskId startStickerImageLoad(const StorageKey &location, mtpFileLoader *loader) { - auto j = _stickerImagesMap.constFind(location); - if (j == _stickerImagesMap.cend() || !_localLoader) { - return 0; - } - return _localLoader->addTask(new StickerImageLoadTask(j->first, location, loader)); - } - - bool willStickerImageLoad(const StorageKey &location) { - return _stickerImagesMap.constFind(location) != _stickerImagesMap.cend(); - } - - bool copyStickerImage(const StorageKey &oldLocation, const StorageKey &newLocation) { - auto i = _stickerImagesMap.constFind(oldLocation); - if (i == _stickerImagesMap.cend()) { - return false; - } - _stickerImagesMap.insert(newLocation, i.value()); + qint32 size = _storageImageSize(image.data.size()); + StorageMap::const_iterator i = _imagesMap.constFind(location); + if (i == _imagesMap.cend()) { + i = _imagesMap.insert(location, FileDesc(genKey(UserPath), size)); + _storageImagesSize += size; _mapChanged = true; _writeMap(); - return true; + } else if (!overwrite) { + return; } - - int32 hasStickers() { - return _stickerImagesMap.size(); + EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + image.data.size()); + data.stream << quint64(location.first) << quint64(location.second) << quint32(image.type) << image.data; + FileWriteDescriptor file(i.value().first, UserPath); + file.writeEncrypted(data); + if (i.value().second != size) { + _storageImagesSize += size; + _storageImagesSize -= i.value().second; + _imagesMap[location].second = size; } +} - qint64 storageStickersSize() { - return _storageStickersSize; +class AbstractCachedLoadTask : public Task { +public: + + AbstractCachedLoadTask(const FileKey &key, const StorageKey &location, bool readImageFlag, mtpFileLoader *loader) : + _key(key), _location(location), _readImageFlag(readImageFlag), _loader(loader), _result(0) { } - - void writeAudio(const StorageKey &location, const QByteArray &audio, bool overwrite) { - if (!_working()) return; - - qint32 size = _storageAudioSize(audio.size()); - StorageMap::const_iterator i = _audiosMap.constFind(location); - if (i == _audiosMap.cend()) { - i = _audiosMap.insert(location, FileDesc(genKey(UserPath), size)); - _storageAudiosSize += size; - _mapChanged = true; - _writeMap(); - } else if (!overwrite) { + void process() { + FileReadDescriptor image; + if (!readEncryptedFile(image, _key, UserPath)) { return; } - EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + audio.size()); - data.stream << quint64(location.first) << quint64(location.second) << audio; - FileWriteDescriptor file(i.value().first, UserPath); - file.writeEncrypted(data); - if (i.value().second != size) { - _storageAudiosSize += size; - _storageAudiosSize -= i.value().second; - _audiosMap[location].second = size; + + QByteArray imageData; + quint64 locFirst, locSecond; + quint32 imageType; + readFromStream(image.stream, locFirst, locSecond, imageType, imageData); + + // we're saving files now before we have actual location + //if (locFirst != _location.first || locSecond != _location.second) { + // return; + //} + + _result = new Result(StorageFileType(imageType), imageData, _readImageFlag); + } + void finish() { + if (_result) { + _loader->localLoaded(_result->image, _result->format, _result->pixmap); + } else { + clearInMap(); + _loader->localLoaded(StorageImageSaved()); } } - - class AudioLoadTask : public AbstractCachedLoadTask { - public: - AudioLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) : - AbstractCachedLoadTask(key, location, false, loader) { - } - void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, quint32 &type, QByteArray &data) { - stream >> first >> second >> data; - type = StorageFilePartial; - } - void clearInMap() { - auto j = _audiosMap.find(_location); - if (j != _audiosMap.cend() && j->first == _key) { - clearKey(j.value().first, UserPath); - _storageAudiosSize -= j.value().second; - _audiosMap.erase(j); - } - } - }; - - TaskId startAudioLoad(const StorageKey &location, mtpFileLoader *loader) { - auto j = _audiosMap.constFind(location); - if (j == _audiosMap.cend() || !_localLoader) { - return 0; - } - return _localLoader->addTask(new AudioLoadTask(j->first, location, loader)); + virtual void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, quint32 &type, QByteArray &data) = 0; + virtual void clearInMap() = 0; + virtual ~AbstractCachedLoadTask() { + deleteAndMark(_result); } - bool copyAudio(const StorageKey &oldLocation, const StorageKey &newLocation) { - auto i = _audiosMap.constFind(oldLocation); - if (i == _audiosMap.cend()) { - return false; - } - _audiosMap.insert(newLocation, i.value()); - _mapChanged = true; - _writeMap(); - return true; - } - - int32 hasAudios() { - return _audiosMap.size(); - } - - qint64 storageAudiosSize() { - return _storageAudiosSize; - } - - qint32 _storageWebFileSize(const QString &url, qint32 rawlen) { - // fulllen + url + len + data - qint32 result = sizeof(uint32) + Serialize::stringSize(url) + sizeof(quint32) + rawlen; - if (result & 0x0F) result += 0x10 - (result & 0x0F); - result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5 - return result; - } - - void writeWebFile(const QString &url, const QByteArray &content, bool overwrite) { - if (!_working()) return; - - qint32 size = _storageWebFileSize(url, content.size()); - WebFilesMap::const_iterator i = _webFilesMap.constFind(url); - if (i == _webFilesMap.cend()) { - i = _webFilesMap.insert(url, FileDesc(genKey(UserPath), size)); - _storageWebFilesSize += size; - _writeLocations(); - } else if (!overwrite) { - return; - } - EncryptedDescriptor data(Serialize::stringSize(url) + sizeof(quint32) + sizeof(quint32) + content.size()); - data.stream << url << content; - FileWriteDescriptor file(i.value().first, UserPath); - file.writeEncrypted(data); - if (i.value().second != size) { - _storageWebFilesSize += size; - _storageWebFilesSize -= i.value().second; - _webFilesMap[url].second = size; - } - } - - class WebFileLoadTask : public Task { - public: - WebFileLoadTask(const FileKey &key, const QString &url, webFileLoader *loader) - : _key(key) - , _url(url) - , _loader(loader) - , _result(0) { - } - void process() { - FileReadDescriptor image; - if (!readEncryptedFile(image, _key, UserPath)) { - return; - } - - QByteArray imageData; - QString url; - image.stream >> url >> imageData; - - _result = new Result(StorageFilePartial, imageData); - } - void finish() { - if (_result) { - _loader->localLoaded(_result->image, _result->format, _result->pixmap); - } else { - WebFilesMap::iterator j = _webFilesMap.find(_url); - if (j != _webFilesMap.cend() && j->first == _key) { - clearKey(j.value().first, UserPath); - _storageWebFilesSize -= j.value().second; - _webFilesMap.erase(j); - } - _loader->localLoaded(StorageImageSaved()); - } - } - virtual ~WebFileLoadTask() { - deleteAndMark(_result); - } - - protected: - FileKey _key; - QString _url; - struct Result { - Result(StorageFileType type, const QByteArray &data) : image(type, data) { +protected: + FileKey _key; + StorageKey _location; + bool _readImageFlag; + struct Result { + Result(StorageFileType type, const QByteArray &data, bool readImageFlag) : image(type, data) { + if (readImageFlag) { QByteArray guessFormat; + switch (type) { + case StorageFileGif: guessFormat = "GIF"; break; + case StorageFileJpeg: guessFormat = "JPG"; break; + case StorageFilePng: guessFormat = "PNG"; break; + case StorageFileWebp: guessFormat = "WEBP"; break; + default: guessFormat = QByteArray(); break; + } pixmap = App::pixmapFromImageInPlace(App::readImage(data, &guessFormat, false)); if (!pixmap.isNull()) { format = guessFormat; } } - StorageImageSaved image; - QByteArray format; - QPixmap pixmap; - }; - webFileLoader *_loader; - Result *_result; - + } + StorageImageSaved image; + QByteArray format; + QPixmap pixmap; }; + mtpFileLoader *_loader; + Result *_result; - TaskId startWebFileLoad(const QString &url, webFileLoader *loader) { - WebFilesMap::const_iterator j = _webFilesMap.constFind(url); - if (j == _webFilesMap.cend() || !_localLoader) { - return 0; - } - return _localLoader->addTask(new WebFileLoadTask(j->first, url, loader)); +}; + +class ImageLoadTask : public AbstractCachedLoadTask { +public: + ImageLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) : + AbstractCachedLoadTask(key, location, true, loader) { } - - int32 hasWebFiles() { - return _webFilesMap.size(); + void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, quint32 &type, QByteArray &data) { + stream >> first >> second >> type >> data; } - - qint64 storageWebFilesSize() { - return _storageWebFilesSize; - } - - class CountWaveformTask : public Task { - public: - CountWaveformTask(DocumentData *doc) - : _doc(doc) - , _loc(doc->location(true)) - , _data(doc->data()) - , _wavemax(0) { - if (_data.isEmpty() && !_loc.accessEnable()) { - _doc = 0; - } - } - void process() { - if (!_doc) return; - - _waveform = audioCountWaveform(_loc, _data); - uchar wavemax = 0; - for (int32 i = 0, l = _waveform.size(); i < l; ++i) { - uchar waveat = _waveform.at(i); - if (wavemax < waveat) wavemax = waveat; - } - _wavemax = wavemax; - } - void finish() { - if (VoiceData *voice = _doc ? _doc->voice() : 0) { - if (!_waveform.isEmpty()) { - voice->waveform = _waveform; - voice->wavemax = _wavemax; - } - if (voice->waveform.isEmpty()) { - voice->waveform.resize(1); - voice->waveform[0] = -2; - voice->wavemax = 0; - } else if (voice->waveform[0] < 0) { - voice->waveform[0] = -2; - voice->wavemax = 0; - } - const DocumentItems &items(App::documentItems()); - DocumentItems::const_iterator i = items.constFind(_doc); - if (i != items.cend()) { - for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { - Ui::repaintHistoryItem(j.key()); - } - } - } - } - virtual ~CountWaveformTask() { - if (_data.isEmpty() && _doc) { - _loc.accessDisable(); - } - } - - protected: - DocumentData *_doc; - FileLocation _loc; - QByteArray _data; - VoiceWaveform _waveform; - char _wavemax; - - }; - - void countVoiceWaveform(DocumentData *document) { - if (VoiceData *voice = document->voice()) { - if (_localLoader) { - voice->waveform.resize(1 + sizeof(TaskId)); - voice->waveform[0] = -1; // counting - TaskId taskId = _localLoader->addTask(new CountWaveformTask(document)); - memcpy(voice->waveform.data() + 1, &taskId, sizeof(taskId)); - } + void clearInMap() { + StorageMap::iterator j = _imagesMap.find(_location); + if (j != _imagesMap.cend() && j->first == _key) { + clearKey(_key, UserPath); + _storageImagesSize -= j->second; + _imagesMap.erase(j); } } +}; - void cancelTask(TaskId id) { - if (_localLoader) { - _localLoader->cancelTask(id); +TaskId startImageLoad(const StorageKey &location, mtpFileLoader *loader) { + StorageMap::const_iterator j = _imagesMap.constFind(location); + if (j == _imagesMap.cend() || !_localLoader) { + return 0; + } + return _localLoader->addTask(new ImageLoadTask(j->first, location, loader)); +} + +int32 hasImages() { + return _imagesMap.size(); +} + +qint64 storageImagesSize() { + return _storageImagesSize; +} + +void writeStickerImage(const StorageKey &location, const QByteArray &sticker, bool overwrite) { + if (!_working()) return; + + qint32 size = _storageStickerSize(sticker.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(); + } else if (!overwrite) { + return; + } + EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + sticker.size()); + data.stream << quint64(location.first) << quint64(location.second) << sticker; + FileWriteDescriptor file(i.value().first, UserPath); + file.writeEncrypted(data); + if (i.value().second != size) { + _storageStickersSize += size; + _storageStickersSize -= i.value().second; + _stickerImagesMap[location].second = size; + } +} + +class StickerImageLoadTask : public AbstractCachedLoadTask { +public: + StickerImageLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) : + AbstractCachedLoadTask(key, location, true, loader) { + } + void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, quint32 &type, QByteArray &data) { + stream >> first >> second >> data; + type = StorageFilePartial; + } + void clearInMap() { + auto j = _stickerImagesMap.find(_location); + if (j != _stickerImagesMap.cend() && j->first == _key) { + clearKey(j.value().first, UserPath); + _storageStickersSize -= j.value().second; + _stickerImagesMap.erase(j); } } +}; - void _writeStickerSet(QDataStream &stream, const Stickers::Set &set) { - bool notLoaded = (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded); - if (notLoaded) { - stream << quint64(set.id) << quint64(set.access) << set.title << set.shortName << qint32(-set.count) << qint32(set.hash) << qint32(set.flags); +TaskId startStickerImageLoad(const StorageKey &location, mtpFileLoader *loader) { + auto j = _stickerImagesMap.constFind(location); + if (j == _stickerImagesMap.cend() || !_localLoader) { + return 0; + } + return _localLoader->addTask(new StickerImageLoadTask(j->first, location, loader)); +} + +bool willStickerImageLoad(const StorageKey &location) { + return _stickerImagesMap.constFind(location) != _stickerImagesMap.cend(); +} + +bool copyStickerImage(const StorageKey &oldLocation, const StorageKey &newLocation) { + auto i = _stickerImagesMap.constFind(oldLocation); + if (i == _stickerImagesMap.cend()) { + return false; + } + _stickerImagesMap.insert(newLocation, i.value()); + _mapChanged = true; + _writeMap(); + return true; +} + +int32 hasStickers() { + return _stickerImagesMap.size(); +} + +qint64 storageStickersSize() { + return _storageStickersSize; +} + +void writeAudio(const StorageKey &location, const QByteArray &audio, bool overwrite) { + if (!_working()) return; + + qint32 size = _storageAudioSize(audio.size()); + StorageMap::const_iterator i = _audiosMap.constFind(location); + if (i == _audiosMap.cend()) { + i = _audiosMap.insert(location, FileDesc(genKey(UserPath), size)); + _storageAudiosSize += size; + _mapChanged = true; + _writeMap(); + } else if (!overwrite) { + return; + } + EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + audio.size()); + data.stream << quint64(location.first) << quint64(location.second) << audio; + FileWriteDescriptor file(i.value().first, UserPath); + file.writeEncrypted(data); + if (i.value().second != size) { + _storageAudiosSize += size; + _storageAudiosSize -= i.value().second; + _audiosMap[location].second = size; + } +} + +class AudioLoadTask : public AbstractCachedLoadTask { +public: + AudioLoadTask(const FileKey &key, const StorageKey &location, mtpFileLoader *loader) : + AbstractCachedLoadTask(key, location, false, loader) { + } + void readFromStream(QDataStream &stream, quint64 &first, quint64 &second, quint32 &type, QByteArray &data) { + stream >> first >> second >> data; + type = StorageFilePartial; + } + void clearInMap() { + auto j = _audiosMap.find(_location); + if (j != _audiosMap.cend() && j->first == _key) { + clearKey(j.value().first, UserPath); + _storageAudiosSize -= j.value().second; + _audiosMap.erase(j); + } + } +}; + +TaskId startAudioLoad(const StorageKey &location, mtpFileLoader *loader) { + auto j = _audiosMap.constFind(location); + if (j == _audiosMap.cend() || !_localLoader) { + return 0; + } + return _localLoader->addTask(new AudioLoadTask(j->first, location, loader)); +} + +bool copyAudio(const StorageKey &oldLocation, const StorageKey &newLocation) { + auto i = _audiosMap.constFind(oldLocation); + if (i == _audiosMap.cend()) { + return false; + } + _audiosMap.insert(newLocation, i.value()); + _mapChanged = true; + _writeMap(); + return true; +} + +int32 hasAudios() { + return _audiosMap.size(); +} + +qint64 storageAudiosSize() { + return _storageAudiosSize; +} + +qint32 _storageWebFileSize(const QString &url, qint32 rawlen) { + // fulllen + url + len + data + qint32 result = sizeof(uint32) + Serialize::stringSize(url) + sizeof(quint32) + rawlen; + if (result & 0x0F) result += 0x10 - (result & 0x0F); + result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5 + return result; +} + +void writeWebFile(const QString &url, const QByteArray &content, bool overwrite) { + if (!_working()) return; + + qint32 size = _storageWebFileSize(url, content.size()); + WebFilesMap::const_iterator i = _webFilesMap.constFind(url); + if (i == _webFilesMap.cend()) { + i = _webFilesMap.insert(url, FileDesc(genKey(UserPath), size)); + _storageWebFilesSize += size; + _writeLocations(); + } else if (!overwrite) { + return; + } + EncryptedDescriptor data(Serialize::stringSize(url) + sizeof(quint32) + sizeof(quint32) + content.size()); + data.stream << url << content; + FileWriteDescriptor file(i.value().first, UserPath); + file.writeEncrypted(data); + if (i.value().second != size) { + _storageWebFilesSize += size; + _storageWebFilesSize -= i.value().second; + _webFilesMap[url].second = size; + } +} + +class WebFileLoadTask : public Task { +public: + WebFileLoadTask(const FileKey &key, const QString &url, webFileLoader *loader) + : _key(key) + , _url(url) + , _loader(loader) + , _result(0) { + } + void process() { + FileReadDescriptor image; + if (!readEncryptedFile(image, _key, UserPath)) { return; + } + + QByteArray imageData; + QString url; + image.stream >> url >> imageData; + + _result = new Result(StorageFilePartial, imageData); + } + void finish() { + if (_result) { + _loader->localLoaded(_result->image, _result->format, _result->pixmap); } else { - if (set.stickers.isEmpty()) return; + WebFilesMap::iterator j = _webFilesMap.find(_url); + if (j != _webFilesMap.cend() && j->first == _key) { + clearKey(j.value().first, UserPath); + _storageWebFilesSize -= j.value().second; + _webFilesMap.erase(j); + } + _loader->localLoaded(StorageImageSaved()); } + } + virtual ~WebFileLoadTask() { + deleteAndMark(_result); + } - stream << quint64(set.id) << quint64(set.access) << set.title << set.shortName << qint32(set.stickers.size()) << qint32(set.hash) << qint32(set.flags); - for (StickerPack::const_iterator j = set.stickers.cbegin(), e = set.stickers.cend(); j != e; ++j) { - Serialize::Document::writeToStream(stream, *j); +protected: + FileKey _key; + QString _url; + struct Result { + Result(StorageFileType type, const QByteArray &data) : image(type, data) { + QByteArray guessFormat; + pixmap = App::pixmapFromImageInPlace(App::readImage(data, &guessFormat, false)); + if (!pixmap.isNull()) { + format = guessFormat; + } } + StorageImageSaved image; + QByteArray format; + QPixmap pixmap; + }; + webFileLoader *_loader; + Result *_result; - if (AppVersion > 9018) { - stream << qint32(set.emoji.size()); - for (StickersByEmojiMap::const_iterator j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) { - stream << emojiString(j.key()) << qint32(j->size()); - for (int32 k = 0, l = j->size(); k < l; ++k) { - stream << quint64(j->at(k)->id); +}; + +TaskId startWebFileLoad(const QString &url, webFileLoader *loader) { + WebFilesMap::const_iterator j = _webFilesMap.constFind(url); + if (j == _webFilesMap.cend() || !_localLoader) { + return 0; + } + return _localLoader->addTask(new WebFileLoadTask(j->first, url, loader)); +} + +int32 hasWebFiles() { + return _webFilesMap.size(); +} + +qint64 storageWebFilesSize() { + return _storageWebFilesSize; +} + +class CountWaveformTask : public Task { +public: + CountWaveformTask(DocumentData *doc) + : _doc(doc) + , _loc(doc->location(true)) + , _data(doc->data()) + , _wavemax(0) { + if (_data.isEmpty() && !_loc.accessEnable()) { + _doc = 0; + } + } + void process() { + if (!_doc) return; + + _waveform = audioCountWaveform(_loc, _data); + uchar wavemax = 0; + for (int32 i = 0, l = _waveform.size(); i < l; ++i) { + uchar waveat = _waveform.at(i); + if (wavemax < waveat) wavemax = waveat; + } + _wavemax = wavemax; + } + void finish() { + if (VoiceData *voice = _doc ? _doc->voice() : 0) { + if (!_waveform.isEmpty()) { + voice->waveform = _waveform; + voice->wavemax = _wavemax; + } + if (voice->waveform.isEmpty()) { + voice->waveform.resize(1); + voice->waveform[0] = -2; + voice->wavemax = 0; + } else if (voice->waveform[0] < 0) { + voice->waveform[0] = -2; + voice->wavemax = 0; + } + const DocumentItems &items(App::documentItems()); + DocumentItems::const_iterator i = items.constFind(_doc); + if (i != items.cend()) { + for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { + Ui::repaintHistoryItem(j.key()); } } } } - - // In generic method _writeStickerSets() we look through all the sets and call a - // callback on each set to see, if we write it, skip it or abort the whole write. - enum class StickerSetCheckResult { - Write, - Skip, - Abort, - }; - - // CheckSet is a functor on Stickers::Set, which returns a StickerSetCheckResult. - template - void _writeStickerSets(FileKey &stickersKey, CheckSet checkSet, const Stickers::Order &order) { - if (!_working()) return; - - auto &sets = Global::StickerSets(); - if (sets.isEmpty()) { - if (stickersKey) { - clearKey(stickersKey); - stickersKey = 0; - _mapChanged = true; - } - _writeMap(); - return; + virtual ~CountWaveformTask() { + if (_data.isEmpty() && _doc) { + _loc.accessDisable(); } - int32 setsCount = 0; - QByteArray hashToWrite; - quint32 size = sizeof(quint32) + Serialize::bytearraySize(hashToWrite); - for_const (auto &set, sets) { - auto result = checkSet(set); - if (result == StickerSetCheckResult::Abort) { - return; - } else if (result == StickerSetCheckResult::Skip) { - continue; - } - - // id + access + title + shortName + stickersCount + hash + flags - size += sizeof(quint64) * 2 + Serialize::stringSize(set.title) + Serialize::stringSize(set.shortName) + sizeof(quint32) + sizeof(qint32) * 2; - for_const (auto &sticker, set.stickers) { - size += Serialize::Document::sizeInStream(sticker); - } - - size += sizeof(qint32); // emojiCount - for (auto j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) { - size += Serialize::stringSize(emojiString(j.key())) + sizeof(qint32) + (j->size() * sizeof(quint64)); - } - - ++setsCount; - } - if (!setsCount && order.isEmpty()) { - if (stickersKey) { - clearKey(stickersKey); - stickersKey = 0; - _mapChanged = true; - } - _writeMap(); - return; - } - size += sizeof(qint32) + (order.size() * sizeof(quint64)); - - if (!stickersKey) { - stickersKey = genKey(); - _mapChanged = true; - _writeMap(WriteMapFast); - } - EncryptedDescriptor data(size); - data.stream << quint32(setsCount) << hashToWrite; - for_const (auto &set, sets) { - auto result = checkSet(set); - if (result == StickerSetCheckResult::Abort) { - return; - } else if (result == StickerSetCheckResult::Skip) { - continue; - } - _writeStickerSet(data.stream, set); - } - data.stream << order; - - FileWriteDescriptor file(stickersKey); - file.writeEncrypted(data); } - void _readStickerSets(FileKey &stickersKey, Stickers::Order *outOrder = nullptr, MTPDstickerSet::Flags readingFlags = 0) { - FileReadDescriptor stickers; - if (!readEncryptedFile(stickers, stickersKey)) { +protected: + DocumentData *_doc; + FileLocation _loc; + QByteArray _data; + VoiceWaveform _waveform; + char _wavemax; + +}; + +void countVoiceWaveform(DocumentData *document) { + if (VoiceData *voice = document->voice()) { + if (_localLoader) { + voice->waveform.resize(1 + sizeof(TaskId)); + voice->waveform[0] = -1; // counting + TaskId taskId = _localLoader->addTask(new CountWaveformTask(document)); + memcpy(voice->waveform.data() + 1, &taskId, sizeof(taskId)); + } + } +} + +void cancelTask(TaskId id) { + if (_localLoader) { + _localLoader->cancelTask(id); + } +} + +void _writeStickerSet(QDataStream &stream, const Stickers::Set &set) { + bool notLoaded = (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded); + if (notLoaded) { + stream << quint64(set.id) << quint64(set.access) << set.title << set.shortName << qint32(-set.count) << qint32(set.hash) << qint32(set.flags); + return; + } else { + if (set.stickers.isEmpty()) return; + } + + stream << quint64(set.id) << quint64(set.access) << set.title << set.shortName << qint32(set.stickers.size()) << qint32(set.hash) << qint32(set.flags); + for (StickerPack::const_iterator j = set.stickers.cbegin(), e = set.stickers.cend(); j != e; ++j) { + Serialize::Document::writeToStream(stream, *j); + } + + if (AppVersion > 9018) { + stream << qint32(set.emoji.size()); + for (StickersByEmojiMap::const_iterator j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) { + stream << emojiString(j.key()) << qint32(j->size()); + for (int32 k = 0, l = j->size(); k < l; ++k) { + stream << quint64(j->at(k)->id); + } + } + } +} + +// In generic method _writeStickerSets() we look through all the sets and call a +// callback on each set to see, if we write it, skip it or abort the whole write. +enum class StickerSetCheckResult { + Write, + Skip, + Abort, +}; + +// CheckSet is a functor on Stickers::Set, which returns a StickerSetCheckResult. +template +void _writeStickerSets(FileKey &stickersKey, CheckSet checkSet, const Stickers::Order &order) { + if (!_working()) return; + + auto &sets = Global::StickerSets(); + if (sets.isEmpty()) { + if (stickersKey) { clearKey(stickersKey); stickersKey = 0; - _writeMap(); - return; + _mapChanged = true; } - - bool readingInstalled = (readingFlags == qFlags(MTPDstickerSet::Flag::f_installed)); - - auto &sets = Global::RefStickerSets(); - if (outOrder) outOrder->clear(); - - quint32 cnt; - QByteArray hash; - stickers.stream >> cnt >> hash; // ignore hash, it is counted - if (readingInstalled && stickers.version < 8019) { // bad data in old caches - cnt += 2; // try to read at least something - } - for (uint32 i = 0; i < cnt; ++i) { - quint64 setId = 0, setAccess = 0; - QString setTitle, setShortName; - qint32 scnt = 0; - stickers.stream >> setId >> setAccess >> setTitle >> setShortName >> scnt; - - qint32 setHash = 0, setFlags = 0; - if (stickers.version > 8033) { - stickers.stream >> setHash >> setFlags; - if (setFlags & qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old)) { - setFlags &= ~qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old); - setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_not_loaded); - } - } - if (readingInstalled && stickers.version < 9061) { - setFlags |= qFlags(MTPDstickerSet::Flag::f_installed); - } - - if (setId == Stickers::DefaultSetId) { - setTitle = lang(lng_stickers_default_set); - setFlags |= qFlags(MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special); - if (readingInstalled && outOrder && stickers.version < 9061) { - outOrder->push_front(setId); - } - } else if (setId == Stickers::CustomSetId) { - setTitle = lang(lng_custom_stickers); - setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special); - } else if (setId == Stickers::CloudRecentSetId) { - setTitle = lang(lng_recent_stickers); - setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special); - } else if (setId) { - if (readingInstalled && outOrder && stickers.version < 9061) { - outOrder->push_back(setId); - } - } else { - continue; - } - - auto it = sets.find(setId); - if (it == sets.cend()) { - // We will set this flags from order lists when reading those stickers. - setFlags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_featured); - it = sets.insert(setId, Stickers::Set(setId, setAccess, setTitle, setShortName, 0, setHash, MTPDstickerSet::Flags(setFlags))); - } - auto &set = it.value(); - - if (scnt < 0) { // disabled not loaded set - if (!set.count || set.stickers.isEmpty()) { - set.count = -scnt; - } - continue; - } - - bool fillStickers = set.stickers.isEmpty(); - if (fillStickers) { - set.stickers.reserve(scnt); - set.count = 0; - } - - Serialize::Document::StickerSetInfo info(setId, setAccess, setShortName); - OrderedSet read; - for (int32 j = 0; j < scnt; ++j) { - auto document = Serialize::Document::readStickerFromStream(stickers.version, stickers.stream, info); - if (!document || !document->sticker()) continue; - - if (read.contains(document->id)) continue; - read.insert(document->id); - - if (fillStickers) { - set.stickers.push_back(document); - ++set.count; - } - } - - if (stickers.version > 9018) { - qint32 emojiCount; - stickers.stream >> emojiCount; - for (int32 j = 0; j < emojiCount; ++j) { - QString emojiString; - qint32 stickersCount; - stickers.stream >> emojiString >> stickersCount; - StickerPack pack; - pack.reserve(stickersCount); - for (int32 k = 0; k < stickersCount; ++k) { - quint64 id; - stickers.stream >> id; - DocumentData *doc = App::document(id); - if (!doc || !doc->sticker()) continue; - - pack.push_back(doc); - } - if (fillStickers) { - if (auto e = emojiGetNoColor(emojiFromText(emojiString))) { - set.emoji.insert(e, pack); - } - } - } - } - } - - // Read orders of installed and featured stickers. - if (outOrder && stickers.version >= 9061) { - stickers.stream >> *outOrder; - } - - // Set flags that we dropped above from the order. - if (readingFlags && outOrder) { - for_const (auto setId, *outOrder) { - auto it = sets.find(setId); - if (it != sets.cend()) { - it->flags |= readingFlags; - } - } - } - } - - void writeInstalledStickers() { - _writeStickerSets(_installedStickersKey, [](const Stickers::Set &set) { - if (set.id == Stickers::CloudRecentSetId) { // separate file for recent - return StickerSetCheckResult::Skip; - } else if (set.flags & MTPDstickerSet_ClientFlag::f_special) { - if (set.stickers.isEmpty()) { // all other special are "installed" - return StickerSetCheckResult::Skip; - } - } else if (!(set.flags & MTPDstickerSet::Flag::f_installed) || (set.flags & MTPDstickerSet::Flag::f_archived)) { - return StickerSetCheckResult::Skip; - } else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive - return StickerSetCheckResult::Abort; - } else if (set.stickers.isEmpty()) { - return StickerSetCheckResult::Skip; - } - return StickerSetCheckResult::Write; - }, Global::StickerSetsOrder()); - } - - void writeFeaturedStickers() { - _writeStickerSets(_featuredStickersKey, [](const Stickers::Set &set) { - if (set.id == Stickers::CloudRecentSetId) { // separate file for recent - return StickerSetCheckResult::Skip; - } else if (set.flags & MTPDstickerSet_ClientFlag::f_special) { - return StickerSetCheckResult::Skip; - } else if (!(set.flags & MTPDstickerSet_ClientFlag::f_featured)) { - return StickerSetCheckResult::Skip; - } else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive - return StickerSetCheckResult::Abort; - } else if (set.stickers.isEmpty()) { - return StickerSetCheckResult::Skip; - } - return StickerSetCheckResult::Write; - }, Global::FeaturedStickerSetsOrder()); - } - - void writeRecentStickers() { - _writeStickerSets(_recentStickersKey, [](const Stickers::Set &set) { - if (set.id != Stickers::CloudRecentSetId || set.stickers.isEmpty()) { - return StickerSetCheckResult::Skip; - } - return StickerSetCheckResult::Write; - }, Stickers::Order()); - } - - void writeArchivedStickers() { - _writeStickerSets(_archivedStickersKey, [](const Stickers::Set &set) { - if (!(set.flags & MTPDstickerSet::Flag::f_archived) || set.stickers.isEmpty()) { - return StickerSetCheckResult::Skip; - } - return StickerSetCheckResult::Write; - }, Global::ArchivedStickerSetsOrder()); - } - - void importOldRecentStickers() { - if (!_recentStickersKeyOld) return; - - FileReadDescriptor stickers; - if (!readEncryptedFile(stickers, _recentStickersKeyOld)) { - clearKey(_recentStickersKeyOld); - _recentStickersKeyOld = 0; - _writeMap(); - return; - } - - auto &sets = Global::RefStickerSets(); - sets.clear(); - - auto &order = Global::RefStickerSetsOrder(); - order.clear(); - - auto &recent = cRefRecentStickers(); - recent.clear(); - - auto &def = sets.insert(Stickers::DefaultSetId, Stickers::Set(Stickers::DefaultSetId, 0, lang(lng_stickers_default_set), QString(), 0, 0, MTPDstickerSet::Flag::f_official | MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_special)).value(); - auto &custom = sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_special)).value(); - - QMap read; - while (!stickers.stream.atEnd()) { - quint64 id, access; - QString name, mime, alt; - qint32 date, dc, size, width, height, type; - qint16 value; - stickers.stream >> id >> value >> access >> date >> name >> mime >> dc >> size >> width >> height >> type; - if (stickers.version >= 7021) { - stickers.stream >> alt; - } - if (!value || read.contains(id)) continue; - read.insert(id, true); - - 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) { - attributes.push_back(MTP_documentAttributeSticker(MTP_string(alt), MTP_inputStickerSetEmpty())); - } - if (width > 0 && height > 0) { - attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height))); - } - - DocumentData *doc = App::documentSet(id, 0, access, 0, date, attributes, mime, ImagePtr(), dc, size, StorageImageLocation()); - if (!doc->sticker()) continue; - - if (value > 0) { - def.stickers.push_back(doc); - ++def.count; - } else { - custom.stickers.push_back(doc); - ++custom.count; - } - if (recent.size() < Global::StickersRecentLimit() && qAbs(value) > 1) { - recent.push_back(qMakePair(doc, qAbs(value))); - } - } - if (def.stickers.isEmpty()) { - sets.remove(Stickers::DefaultSetId); - } else { - order.push_front(Stickers::DefaultSetId); - } - if (custom.stickers.isEmpty()) sets.remove(Stickers::CustomSetId); - - writeInstalledStickers(); - writeUserSettings(); - - clearKey(_recentStickersKeyOld); - _recentStickersKeyOld = 0; _writeMap(); + return; } - - void readInstalledStickers() { - if (!_installedStickersKey) { - return importOldRecentStickers(); - } - - Global::RefStickerSets().clear(); - _readStickerSets(_installedStickersKey, &Global::RefStickerSetsOrder(), qFlags(MTPDstickerSet::Flag::f_installed)); - } - - void readFeaturedStickers() { - _readStickerSets(_featuredStickersKey, &Global::RefFeaturedStickerSetsOrder(), qFlags(MTPDstickerSet_ClientFlag::f_featured)); - - auto &sets = Global::StickerSets(); - int unreadCount = 0; - for_const (auto setId, Global::FeaturedStickerSetsOrder()) { - auto it = sets.constFind(setId); - if (it != sets.cend() && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { - ++unreadCount; - } - } - Global::SetFeaturedStickerSetsUnreadCount(unreadCount); - } - - void readRecentStickers() { - _readStickerSets(_recentStickersKey); - } - - void readArchivedStickers() { - static bool archivedStickersRead = false; - if (!archivedStickersRead) { - _readStickerSets(_archivedStickersKey, &Global::RefArchivedStickerSetsOrder()); - archivedStickersRead = true; - } - } - - int32 countStickersHash(bool checkOutdatedInfo) { - uint32 acc = 0; - bool foundOutdated = false; - auto &sets = Global::StickerSets(); - auto &order = Global::StickerSetsOrder(); - for (auto i = order.cbegin(), e = order.cend(); i != e; ++i) { - auto j = sets.constFind(*i); - if (j != sets.cend()) { - if (j->id == Stickers::DefaultSetId) { - foundOutdated = true; - } else if (!(j->flags & MTPDstickerSet_ClientFlag::f_special) - && !(j->flags & MTPDstickerSet::Flag::f_archived)) { - acc = (acc * 20261) + j->hash; - } - } - } - return (!checkOutdatedInfo || !foundOutdated) ? int32(acc & 0x7FFFFFFF) : 0; - } - - int32 countRecentStickersHash() { - uint32 acc = 0; - auto &sets = Global::StickerSets(); - auto it = sets.constFind(Stickers::CloudRecentSetId); - if (it != sets.cend()) { - for_const (auto doc, it->stickers) { - auto docId = doc->id; - acc = (acc * 20261) + uint32(docId >> 32); - acc = (acc * 20261) + uint32(docId & 0xFFFFFFFF); - } - } - return int32(acc & 0x7FFFFFFF); - } - - int32 countFeaturedStickersHash() { - uint32 acc = 0; - auto &sets = Global::StickerSets(); - auto &featured = Global::FeaturedStickerSetsOrder(); - for_const (auto setId, featured) { - acc = (acc * 20261) + uint32(setId >> 32); - acc = (acc * 20261) + uint32(setId & 0xFFFFFFFF); - - auto it = sets.constFind(setId); - if (it != sets.cend() && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { - acc = (acc * 20261) + 1U; - } - } - return int32(acc & 0x7FFFFFFF); - } - - int32 countSavedGifsHash() { - uint32 acc = 0; - auto &saved = cSavedGifs(); - for_const (auto doc, saved) { - auto docId = doc->id; - acc = (acc * 20261) + uint32(docId >> 32); - acc = (acc * 20261) + uint32(docId & 0xFFFFFFFF); - } - return int32(acc & 0x7FFFFFFF); - } - - void writeSavedGifs() { - if (!_working()) return; - - const SavedGifs &saved(cSavedGifs()); - if (saved.isEmpty()) { - if (_savedGifsKey) { - clearKey(_savedGifsKey); - _savedGifsKey = 0; - _mapChanged = true; - } - _writeMap(); - } else { - quint32 size = sizeof(quint32); // count - for_const (auto gif, saved) { - size += Serialize::Document::sizeInStream(gif); - } - - if (!_savedGifsKey) { - _savedGifsKey = genKey(); - _mapChanged = true; - _writeMap(WriteMapFast); - } - EncryptedDescriptor data(size); - data.stream << quint32(saved.size()); - for_const (auto gif, saved) { - Serialize::Document::writeToStream(data.stream, gif); - } - FileWriteDescriptor file(_savedGifsKey); - file.writeEncrypted(data); - } - } - - void readSavedGifs() { - if (!_savedGifsKey) return; - - FileReadDescriptor gifs; - if (!readEncryptedFile(gifs, _savedGifsKey)) { - clearKey(_savedGifsKey); - _savedGifsKey = 0; - _writeMap(); + int32 setsCount = 0; + QByteArray hashToWrite; + quint32 size = sizeof(quint32) + Serialize::bytearraySize(hashToWrite); + for_const (auto &set, sets) { + auto result = checkSet(set); + if (result == StickerSetCheckResult::Abort) { return; + } else if (result == StickerSetCheckResult::Skip) { + continue; } - SavedGifs &saved(cRefSavedGifs()); - saved.clear(); + // id + access + title + shortName + stickersCount + hash + flags + size += sizeof(quint64) * 2 + Serialize::stringSize(set.title) + Serialize::stringSize(set.shortName) + sizeof(quint32) + sizeof(qint32) * 2; + for_const (auto &sticker, set.stickers) { + size += Serialize::Document::sizeInStream(sticker); + } - quint32 cnt; - gifs.stream >> cnt; - saved.reserve(cnt); + size += sizeof(qint32); // emojiCount + for (auto j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) { + size += Serialize::stringSize(emojiString(j.key())) + sizeof(qint32) + (j->size() * sizeof(quint64)); + } + + ++setsCount; + } + if (!setsCount && order.isEmpty()) { + if (stickersKey) { + clearKey(stickersKey); + stickersKey = 0; + _mapChanged = true; + } + _writeMap(); + return; + } + size += sizeof(qint32) + (order.size() * sizeof(quint64)); + + if (!stickersKey) { + stickersKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); + } + EncryptedDescriptor data(size); + data.stream << quint32(setsCount) << hashToWrite; + for_const (auto &set, sets) { + auto result = checkSet(set); + if (result == StickerSetCheckResult::Abort) { + return; + } else if (result == StickerSetCheckResult::Skip) { + continue; + } + _writeStickerSet(data.stream, set); + } + data.stream << order; + + FileWriteDescriptor file(stickersKey); + file.writeEncrypted(data); +} + +void _readStickerSets(FileKey &stickersKey, Stickers::Order *outOrder = nullptr, MTPDstickerSet::Flags readingFlags = 0) { + FileReadDescriptor stickers; + if (!readEncryptedFile(stickers, stickersKey)) { + clearKey(stickersKey); + stickersKey = 0; + _writeMap(); + return; + } + + bool readingInstalled = (readingFlags == qFlags(MTPDstickerSet::Flag::f_installed)); + + auto &sets = Global::RefStickerSets(); + if (outOrder) outOrder->clear(); + + quint32 cnt; + QByteArray hash; + stickers.stream >> cnt >> hash; // ignore hash, it is counted + if (readingInstalled && stickers.version < 8019) { // bad data in old caches + cnt += 2; // try to read at least something + } + for (uint32 i = 0; i < cnt; ++i) { + quint64 setId = 0, setAccess = 0; + QString setTitle, setShortName; + qint32 scnt = 0; + stickers.stream >> setId >> setAccess >> setTitle >> setShortName >> scnt; + + qint32 setHash = 0, setFlags = 0; + if (stickers.version > 8033) { + stickers.stream >> setHash >> setFlags; + if (setFlags & qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old)) { + setFlags &= ~qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old); + setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_not_loaded); + } + } + if (readingInstalled && stickers.version < 9061) { + setFlags |= qFlags(MTPDstickerSet::Flag::f_installed); + } + + if (setId == Stickers::DefaultSetId) { + setTitle = lang(lng_stickers_default_set); + setFlags |= qFlags(MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special); + if (readingInstalled && outOrder && stickers.version < 9061) { + outOrder->push_front(setId); + } + } else if (setId == Stickers::CustomSetId) { + setTitle = lang(lng_custom_stickers); + setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special); + } else if (setId == Stickers::CloudRecentSetId) { + setTitle = lang(lng_recent_stickers); + setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special); + } else if (setId) { + if (readingInstalled && outOrder && stickers.version < 9061) { + outOrder->push_back(setId); + } + } else { + continue; + } + + auto it = sets.find(setId); + if (it == sets.cend()) { + // We will set this flags from order lists when reading those stickers. + setFlags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_featured); + it = sets.insert(setId, Stickers::Set(setId, setAccess, setTitle, setShortName, 0, setHash, MTPDstickerSet::Flags(setFlags))); + } + auto &set = it.value(); + auto inputSet = MTP_inputStickerSetID(MTP_long(set.id), MTP_long(set.access)); + + if (scnt < 0) { // disabled not loaded set + if (!set.count || set.stickers.isEmpty()) { + set.count = -scnt; + } + continue; + } + + bool fillStickers = set.stickers.isEmpty(); + if (fillStickers) { + set.stickers.reserve(scnt); + set.count = 0; + } + + Serialize::Document::StickerSetInfo info(setId, setAccess, setShortName); OrderedSet read; - for (uint32 i = 0; i < cnt; ++i) { - DocumentData *document = Serialize::Document::readFromStream(gifs.version, gifs.stream); - if (!document || !document->isAnimation()) continue; + for (int32 j = 0; j < scnt; ++j) { + auto document = Serialize::Document::readStickerFromStream(stickers.version, stickers.stream, info); + if (!document || !document->sticker()) continue; if (read.contains(document->id)) continue; read.insert(document->id); - saved.push_back(document); - } - } - - void writeBackground(int32 id, const QImage &img) { - if (!_working()) return; - - QByteArray png; - if (!img.isNull()) { - QBuffer buf(&png); - if (!img.save(&buf, "BMP")) return; - } - if (!_backgroundKey) { - _backgroundKey = genKey(); - _mapChanged = true; - _writeMap(WriteMapFast); - } - quint32 size = sizeof(qint32) + sizeof(quint32) + (png.isEmpty() ? 0 : (sizeof(quint32) + png.size())); - EncryptedDescriptor data(size); - data.stream << qint32(id); - if (!png.isEmpty()) data.stream << png; - - FileWriteDescriptor file(_backgroundKey); - file.writeEncrypted(data); - } - - bool readBackground() { - if (_backgroundWasRead) return false; - _backgroundWasRead = true; - - FileReadDescriptor bg; - if (!readEncryptedFile(bg, _backgroundKey)) { - clearKey(_backgroundKey); - _backgroundKey = 0; - _writeMap(); - return false; - } - - QByteArray pngData; - qint32 id; - bg.stream >> id; - if (!id || id == DefaultChatBackground) { - if (bg.version < 8005) { - App::initBackground(DefaultChatBackground, QImage(), true); - if (!id) Window::chatBackground()->setTile(!DefaultChatBackground); - } else { - App::initBackground(id, QImage(), true); - } - return true; - } - bg.stream >> pngData; - - QImage img; - QBuffer buf(&pngData); - QImageReader reader(&buf); -#ifndef OS_MAC_OLD - reader.setAutoTransform(true); -#endif // OS_MAC_OLD - if (reader.read(&img)) { - App::initBackground(id, img, true); - return true; - } - return false; - } - - uint32 _peerSize(PeerData *peer) { - uint32 result = sizeof(quint64) + sizeof(quint64) + Serialize::storageImageLocationSize(); - if (peer->isUser()) { - UserData *user = peer->asUser(); - - // first + last + phone + username + access - result += Serialize::stringSize(user->firstName) + Serialize::stringSize(user->lastName) + Serialize::stringSize(user->phone()) + Serialize::stringSize(user->username) + sizeof(quint64); - - // flags - if (AppVersion >= 9012) { - result += sizeof(qint32); - } - - // onlineTill + contact + botInfoVersion - result += sizeof(qint32) + sizeof(qint32) + sizeof(qint32); - } else if (peer->isChat()) { - ChatData *chat = peer->asChat(); - - // name + count + date + version + admin + forbidden + left + inviteLink - result += Serialize::stringSize(chat->name) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(chat->inviteLink()); - } else if (peer->isChannel()) { - ChannelData *channel = peer->asChannel(); - - // name + access + date + version + forbidden + flags + inviteLink - result += Serialize::stringSize(channel->name) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(channel->inviteLink()); - } - return result; - } - - void _writePeer(QDataStream &stream, PeerData *peer) { - stream << quint64(peer->id) << quint64(peer->photoId); - Serialize::writeStorageImageLocation(stream, peer->photoLoc); - if (peer->isUser()) { - UserData *user = peer->asUser(); - - stream << user->firstName << user->lastName << user->phone() << user->username << quint64(user->access); - if (AppVersion >= 9012) { - stream << qint32(user->flags); - } - if (AppVersion >= 9016) { - stream << (user->botInfo ? user->botInfo->inlinePlaceholder : QString()); - } - stream << qint32(user->onlineTill) << qint32(user->contact) << qint32(user->botInfo ? user->botInfo->version : -1); - } else if (peer->isChat()) { - ChatData *chat = peer->asChat(); - - qint32 flagsData = (AppVersion >= 9012) ? chat->flags : (chat->haveLeft() ? 1 : 0); - - stream << chat->name << qint32(chat->count) << qint32(chat->date) << qint32(chat->version) << qint32(chat->creator); - stream << qint32(chat->isForbidden ? 1 : 0) << qint32(flagsData) << chat->inviteLink(); - } else if (peer->isChannel()) { - ChannelData *channel = peer->asChannel(); - - stream << channel->name << quint64(channel->access) << qint32(channel->date) << qint32(channel->version); - stream << qint32(channel->isForbidden ? 1 : 0) << qint32(channel->flags) << channel->inviteLink(); - } - } - - PeerData *_readPeer(FileReadDescriptor &from, int32 fileVersion = 0) { - quint64 peerId = 0, photoId = 0; - from.stream >> peerId >> photoId; - - StorageImageLocation photoLoc(Serialize::readStorageImageLocation(from.stream)); - - PeerData *result = App::peerLoaded(peerId); - bool wasLoaded = (result != nullptr); - if (!wasLoaded) { - result = App::peer(peerId); - result->loadedStatus = PeerData::FullLoaded; - } - if (result->isUser()) { - UserData *user = result->asUser(); - - QString first, last, phone, username, inlinePlaceholder; - quint64 access; - qint32 flags = 0, onlineTill, contact, botInfoVersion; - from.stream >> first >> last >> phone >> username >> access; - if (from.version >= 9012) { - from.stream >> flags; - } - if (from.version >= 9016 || fileVersion >= 9016) { - from.stream >> inlinePlaceholder; - } - from.stream >> onlineTill >> contact >> botInfoVersion; - - bool showPhone = !isServiceUser(user->id) && (peerToUser(user->id) != MTP::authedId()) && (contact <= 0); - QString pname = (showPhone && !phone.isEmpty()) ? App::formatPhone(phone) : QString(); - - if (!wasLoaded) { - user->setPhone(phone); - user->setName(first, last, pname, username); - - user->access = access; - user->flags = MTPDuser::Flags(flags); - user->onlineTill = onlineTill; - user->contact = contact; - user->setBotInfoVersion(botInfoVersion); - if (!inlinePlaceholder.isEmpty() && user->botInfo) { - user->botInfo->inlinePlaceholder = inlinePlaceholder; + if (fillStickers) { + set.stickers.push_back(document); + if (!(set.flags & MTPDstickerSet_ClientFlag::f_special)) { + if (document->sticker()->set.type() != mtpc_inputStickerSetID) { + document->sticker()->set = inputSet; + } } + ++set.count; + } + } - if (peerToUser(user->id) == MTP::authedId()) { - user->input = MTP_inputPeerSelf(); - user->inputUser = MTP_inputUserSelf(); - } else { - user->input = MTP_inputPeerUser(MTP_int(peerToUser(user->id)), MTP_long((user->access == UserNoAccess) ? 0 : user->access)); - user->inputUser = MTP_inputUser(MTP_int(peerToUser(user->id)), MTP_long((user->access == UserNoAccess) ? 0 : user->access)); + if (stickers.version > 9018) { + qint32 emojiCount; + stickers.stream >> emojiCount; + for (int32 j = 0; j < emojiCount; ++j) { + QString emojiString; + qint32 stickersCount; + stickers.stream >> emojiString >> stickersCount; + StickerPack pack; + pack.reserve(stickersCount); + for (int32 k = 0; k < stickersCount; ++k) { + quint64 id; + stickers.stream >> id; + DocumentData *doc = App::document(id); + if (!doc || !doc->sticker()) continue; + + pack.push_back(doc); } - - user->setUserpic(photoLoc.isNull() ? ImagePtr(userDefPhoto(user->colorIndex)) : ImagePtr(photoLoc)); - } - } else if (result->isChat()) { - ChatData *chat = result->asChat(); - - QString name, inviteLink; - qint32 count, date, version, creator, forbidden, flagsData, flags; - from.stream >> name >> count >> date >> version >> creator >> forbidden >> flagsData >> inviteLink; - - if (from.version >= 9012) { - flags = flagsData; - } else { - // flagsData was haveLeft - flags = (flagsData == 1) ? MTPDchat::Flags(MTPDchat::Flag::f_left) : MTPDchat::Flags(0); - } - if (!wasLoaded) { - chat->setName(name); - chat->count = count; - chat->date = date; - chat->version = version; - chat->creator = creator; - chat->isForbidden = (forbidden == 1); - chat->flags = MTPDchat::Flags(flags); - chat->setInviteLink(inviteLink); - - chat->input = MTP_inputPeerChat(MTP_int(peerToChat(chat->id))); - chat->inputChat = MTP_int(peerToChat(chat->id)); - - chat->setUserpic(photoLoc.isNull() ? ImagePtr(chatDefPhoto(chat->colorIndex)) : ImagePtr(photoLoc)); - } - } else if (result->isChannel()) { - ChannelData *channel = result->asChannel(); - - QString name, inviteLink; - quint64 access; - qint32 date, version, forbidden, flags; - from.stream >> name >> access >> date >> version >> forbidden >> flags >> inviteLink; - - if (!wasLoaded) { - channel->setName(name, QString()); - channel->access = access; - channel->date = date; - channel->version = version; - channel->isForbidden = (forbidden == 1); - channel->flags = MTPDchannel::Flags(flags); - channel->setInviteLink(inviteLink); - - channel->input = MTP_inputPeerChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); - channel->inputChannel = MTP_inputChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); - - channel->setUserpic(photoLoc.isNull() ? ImagePtr((channel->isMegagroup() ? chatDefPhoto(channel->colorIndex) : channelDefPhoto(channel->colorIndex))) : ImagePtr(photoLoc)); - } - } - if (!wasLoaded) { - App::markPeerUpdated(result); - emit App::main()->peerPhotoChanged(result); - } - return result; - } - - void writeRecentHashtagsAndBots() { - if (!_working()) return; - - const RecentHashtagPack &write(cRecentWriteHashtags()), &search(cRecentSearchHashtags()); - const RecentInlineBots &bots(cRecentInlineBots()); - if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) readRecentHashtagsAndBots(); - if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) { - if (_recentHashtagsAndBotsKey) { - clearKey(_recentHashtagsAndBotsKey); - _recentHashtagsAndBotsKey = 0; - _mapChanged = true; - } - _writeMap(); - } else { - if (!_recentHashtagsAndBotsKey) { - _recentHashtagsAndBotsKey = genKey(); - _mapChanged = true; - _writeMap(WriteMapFast); - } - quint32 size = sizeof(quint32) * 3, writeCnt = 0, searchCnt = 0, botsCnt = cRecentInlineBots().size(); - for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e; ++i) { - if (!i->first.isEmpty()) { - size += Serialize::stringSize(i->first) + sizeof(quint16); - ++writeCnt; - } - } - for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) { - if (!i->first.isEmpty()) { - size += Serialize::stringSize(i->first) + sizeof(quint16); - ++searchCnt; - } - } - for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) { - size += _peerSize(*i); - } - - EncryptedDescriptor data(size); - data.stream << quint32(writeCnt) << quint32(searchCnt); - for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e; ++i) { - if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); - } - for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) { - if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); - } - data.stream << quint32(botsCnt); - for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) { - _writePeer(data.stream, *i); - } - FileWriteDescriptor file(_recentHashtagsAndBotsKey); - file.writeEncrypted(data); - } - } - - void readRecentHashtagsAndBots() { - if (_recentHashtagsAndBotsWereRead) return; - _recentHashtagsAndBotsWereRead = true; - - if (!_recentHashtagsAndBotsKey) return; - - FileReadDescriptor hashtags; - if (!readEncryptedFile(hashtags, _recentHashtagsAndBotsKey)) { - clearKey(_recentHashtagsAndBotsKey); - _recentHashtagsAndBotsKey = 0; - _writeMap(); - return; - } - - quint32 writeCount = 0, searchCount = 0, botsCount = 0; - hashtags.stream >> writeCount >> searchCount; - - QString tag; - quint16 count; - - RecentHashtagPack write, search; - RecentInlineBots bots; - if (writeCount) { - write.reserve(writeCount); - for (uint32 i = 0; i < writeCount; ++i) { - hashtags.stream >> tag >> count; - write.push_back(qMakePair(tag.trimmed(), count)); - } - } - if (searchCount) { - search.reserve(searchCount); - for (uint32 i = 0; i < searchCount; ++i) { - hashtags.stream >> tag >> count; - search.push_back(qMakePair(tag.trimmed(), count)); - } - } - cSetRecentWriteHashtags(write); - cSetRecentSearchHashtags(search); - - if (!hashtags.stream.atEnd()) { - hashtags.stream >> botsCount; - if (botsCount) { - bots.reserve(botsCount); - for (uint32 i = 0; i < botsCount; ++i) { - PeerData *peer = _readPeer(hashtags, 9016); - if (peer && peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->inlinePlaceholder.isEmpty() && !peer->asUser()->username.isEmpty()) { - bots.push_back(peer->asUser()); + if (fillStickers) { + if (auto e = emojiGetNoColor(emojiFromText(emojiString))) { + set.emoji.insert(e, pack); } } } - cSetRecentInlineBots(bots); } } - void writeSavedPeers() { - if (!_working()) return; + // Read orders of installed and featured stickers. + if (outOrder && stickers.version >= 9061) { + stickers.stream >> *outOrder; + } - const SavedPeers &saved(cSavedPeers()); - if (saved.isEmpty()) { - if (_savedPeersKey) { - clearKey(_savedPeersKey); - _savedPeersKey = 0; - _mapChanged = true; + // Set flags that we dropped above from the order. + if (readingFlags && outOrder) { + for_const (auto setId, *outOrder) { + auto it = sets.find(setId); + if (it != sets.cend()) { + it->flags |= readingFlags; } - _writeMap(); + } + } +} + +void writeInstalledStickers() { + if (!Global::started()) return; + + _writeStickerSets(_installedStickersKey, [](const Stickers::Set &set) { + if (set.id == Stickers::CloudRecentSetId) { // separate file for recent + return StickerSetCheckResult::Skip; + } else if (set.flags & MTPDstickerSet_ClientFlag::f_special) { + if (set.stickers.isEmpty()) { // all other special are "installed" + return StickerSetCheckResult::Skip; + } + } else if (!(set.flags & MTPDstickerSet::Flag::f_installed) || (set.flags & MTPDstickerSet::Flag::f_archived)) { + return StickerSetCheckResult::Skip; + } else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive + return StickerSetCheckResult::Abort; + } else if (set.stickers.isEmpty()) { + return StickerSetCheckResult::Skip; + } + return StickerSetCheckResult::Write; + }, Global::StickerSetsOrder()); +} + +void writeFeaturedStickers() { + if (!Global::started()) return; + + _writeStickerSets(_featuredStickersKey, [](const Stickers::Set &set) { + if (set.id == Stickers::CloudRecentSetId) { // separate file for recent + return StickerSetCheckResult::Skip; + } else if (set.flags & MTPDstickerSet_ClientFlag::f_special) { + return StickerSetCheckResult::Skip; + } else if (!(set.flags & MTPDstickerSet_ClientFlag::f_featured)) { + return StickerSetCheckResult::Skip; + } else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive + return StickerSetCheckResult::Abort; + } else if (set.stickers.isEmpty()) { + return StickerSetCheckResult::Skip; + } + return StickerSetCheckResult::Write; + }, Global::FeaturedStickerSetsOrder()); +} + +void writeRecentStickers() { + if (!Global::started()) return; + + _writeStickerSets(_recentStickersKey, [](const Stickers::Set &set) { + if (set.id != Stickers::CloudRecentSetId || set.stickers.isEmpty()) { + return StickerSetCheckResult::Skip; + } + return StickerSetCheckResult::Write; + }, Stickers::Order()); +} + +void writeArchivedStickers() { + if (!Global::started()) return; + + _writeStickerSets(_archivedStickersKey, [](const Stickers::Set &set) { + if (!(set.flags & MTPDstickerSet::Flag::f_archived) || set.stickers.isEmpty()) { + return StickerSetCheckResult::Skip; + } + return StickerSetCheckResult::Write; + }, Global::ArchivedStickerSetsOrder()); +} + +void importOldRecentStickers() { + if (!_recentStickersKeyOld) return; + + FileReadDescriptor stickers; + if (!readEncryptedFile(stickers, _recentStickersKeyOld)) { + clearKey(_recentStickersKeyOld); + _recentStickersKeyOld = 0; + _writeMap(); + return; + } + + auto &sets = Global::RefStickerSets(); + sets.clear(); + + auto &order = Global::RefStickerSetsOrder(); + order.clear(); + + auto &recent = cRefRecentStickers(); + recent.clear(); + + auto &def = sets.insert(Stickers::DefaultSetId, Stickers::Set(Stickers::DefaultSetId, 0, lang(lng_stickers_default_set), QString(), 0, 0, MTPDstickerSet::Flag::f_official | MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_special)).value(); + auto &custom = sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_special)).value(); + + QMap read; + while (!stickers.stream.atEnd()) { + quint64 id, access; + QString name, mime, alt; + qint32 date, dc, size, width, height, type; + qint16 value; + stickers.stream >> id >> value >> access >> date >> name >> mime >> dc >> size >> width >> height >> type; + if (stickers.version >= 7021) { + stickers.stream >> alt; + } + if (!value || read.contains(id)) continue; + read.insert(id, true); + + 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) { + MTPDdocumentAttributeSticker::Flags stickerFlags = 0; + attributes.push_back(MTP_documentAttributeSticker(MTP_flags(stickerFlags), MTP_string(alt), MTP_inputStickerSetEmpty(), MTPMaskCoords())); + } + if (width > 0 && height > 0) { + attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height))); + } + + DocumentData *doc = App::documentSet(id, 0, access, 0, date, attributes, mime, ImagePtr(), dc, size, StorageImageLocation()); + if (!doc->sticker()) continue; + + if (value > 0) { + def.stickers.push_back(doc); + ++def.count; } else { - if (!_savedPeersKey) { - _savedPeersKey = genKey(); - _mapChanged = true; - _writeMap(WriteMapFast); - } - quint32 size = sizeof(quint32); - for (SavedPeers::const_iterator i = saved.cbegin(); i != saved.cend(); ++i) { - size += _peerSize(i.key()) + Serialize::dateTimeSize(); - } - - EncryptedDescriptor data(size); - data.stream << quint32(saved.size()); - for (SavedPeers::const_iterator i = saved.cbegin(); i != saved.cend(); ++i) { - _writePeer(data.stream, i.key()); - data.stream << i.value(); - } - - FileWriteDescriptor file(_savedPeersKey); - file.writeEncrypted(data); + custom.stickers.push_back(doc); + ++custom.count; + } + if (recent.size() < Global::StickersRecentLimit() && qAbs(value) > 1) { + recent.push_back(qMakePair(doc, qAbs(value))); } } + if (def.stickers.isEmpty()) { + sets.remove(Stickers::DefaultSetId); + } else { + order.push_front(Stickers::DefaultSetId); + } + if (custom.stickers.isEmpty()) sets.remove(Stickers::CustomSetId); - void readSavedPeers() { - if (!_savedPeersKey) return; + writeInstalledStickers(); + writeUserSettings(); - FileReadDescriptor saved; - if (!readEncryptedFile(saved, _savedPeersKey)) { - clearKey(_savedPeersKey); - _savedPeersKey = 0; - _writeMap(); - return; - } - if (saved.version == 9011) { // broken dev version - clearKey(_savedPeersKey); - _savedPeersKey = 0; - _writeMap(); - return; - } + clearKey(_recentStickersKeyOld); + _recentStickersKeyOld = 0; + _writeMap(); +} - quint32 count = 0; - saved.stream >> count; - cRefSavedPeers().clear(); - cRefSavedPeersByTime().clear(); - QList peers; - peers.reserve(count); - for (uint32 i = 0; i < count; ++i) { - PeerData *peer = _readPeer(saved); - if (!peer) break; - - QDateTime t; - saved.stream >> t; - - cRefSavedPeers().insert(peer, t); - cRefSavedPeersByTime().insert(t, peer); - peers.push_back(peer); - } - - if (App::api()) App::api()->requestPeers(peers); +void readInstalledStickers() { + if (!_installedStickersKey) { + return importOldRecentStickers(); } - void addSavedPeer(PeerData *peer, const QDateTime &position) { - SavedPeers &savedPeers(cRefSavedPeers()); - SavedPeers::iterator i = savedPeers.find(peer); - if (i == savedPeers.cend()) { - savedPeers.insert(peer, position); - } else if (i.value() != position) { - cRefSavedPeersByTime().remove(i.value(), peer); - i.value() = position; - cRefSavedPeersByTime().insert(i.value(), peer); + Global::RefStickerSets().clear(); + _readStickerSets(_installedStickersKey, &Global::RefStickerSetsOrder(), qFlags(MTPDstickerSet::Flag::f_installed)); +} + +void readFeaturedStickers() { + _readStickerSets(_featuredStickersKey, &Global::RefFeaturedStickerSetsOrder(), qFlags(MTPDstickerSet_ClientFlag::f_featured)); + + auto &sets = Global::StickerSets(); + int unreadCount = 0; + for_const (auto setId, Global::FeaturedStickerSetsOrder()) { + auto it = sets.constFind(setId); + if (it != sets.cend() && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { + ++unreadCount; } + } + Global::SetFeaturedStickerSetsUnreadCount(unreadCount); +} + +void readRecentStickers() { + _readStickerSets(_recentStickersKey); +} + +void readArchivedStickers() { + static bool archivedStickersRead = false; + if (!archivedStickersRead) { + _readStickerSets(_archivedStickersKey, &Global::RefArchivedStickerSetsOrder()); + archivedStickersRead = true; + } +} + +int32 countStickersHash(bool checkOutdatedInfo) { + uint32 acc = 0; + bool foundOutdated = false; + auto &sets = Global::StickerSets(); + auto &order = Global::StickerSetsOrder(); + for (auto i = order.cbegin(), e = order.cend(); i != e; ++i) { + auto j = sets.constFind(*i); + if (j != sets.cend()) { + if (j->id == Stickers::DefaultSetId) { + foundOutdated = true; + } else if (!(j->flags & MTPDstickerSet_ClientFlag::f_special) + && !(j->flags & MTPDstickerSet::Flag::f_archived)) { + acc = (acc * 20261) + j->hash; + } + } + } + return (!checkOutdatedInfo || !foundOutdated) ? int32(acc & 0x7FFFFFFF) : 0; +} + +int32 countRecentStickersHash() { + uint32 acc = 0; + auto &sets = Global::StickerSets(); + auto it = sets.constFind(Stickers::CloudRecentSetId); + if (it != sets.cend()) { + for_const (auto doc, it->stickers) { + auto docId = doc->id; + acc = (acc * 20261) + uint32(docId >> 32); + acc = (acc * 20261) + uint32(docId & 0xFFFFFFFF); + } + } + return int32(acc & 0x7FFFFFFF); +} + +int32 countFeaturedStickersHash() { + uint32 acc = 0; + auto &sets = Global::StickerSets(); + auto &featured = Global::FeaturedStickerSetsOrder(); + for_const (auto setId, featured) { + acc = (acc * 20261) + uint32(setId >> 32); + acc = (acc * 20261) + uint32(setId & 0xFFFFFFFF); + + auto it = sets.constFind(setId); + if (it != sets.cend() && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) { + acc = (acc * 20261) + 1U; + } + } + return int32(acc & 0x7FFFFFFF); +} + +int32 countSavedGifsHash() { + uint32 acc = 0; + auto &saved = cSavedGifs(); + for_const (auto doc, saved) { + auto docId = doc->id; + acc = (acc * 20261) + uint32(docId >> 32); + acc = (acc * 20261) + uint32(docId & 0xFFFFFFFF); + } + return int32(acc & 0x7FFFFFFF); +} + +void writeSavedGifs() { + if (!_working()) return; + + const SavedGifs &saved(cSavedGifs()); + if (saved.isEmpty()) { + if (_savedGifsKey) { + clearKey(_savedGifsKey); + _savedGifsKey = 0; + _mapChanged = true; + } + _writeMap(); + } else { + quint32 size = sizeof(quint32); // count + for_const (auto gif, saved) { + size += Serialize::Document::sizeInStream(gif); + } + + if (!_savedGifsKey) { + _savedGifsKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); + } + EncryptedDescriptor data(size); + data.stream << quint32(saved.size()); + for_const (auto gif, saved) { + Serialize::Document::writeToStream(data.stream, gif); + } + FileWriteDescriptor file(_savedGifsKey); + file.writeEncrypted(data); + } +} + +void readSavedGifs() { + if (!_savedGifsKey) return; + + FileReadDescriptor gifs; + if (!readEncryptedFile(gifs, _savedGifsKey)) { + clearKey(_savedGifsKey); + _savedGifsKey = 0; + _writeMap(); + return; + } + + SavedGifs &saved(cRefSavedGifs()); + saved.clear(); + + quint32 cnt; + gifs.stream >> cnt; + saved.reserve(cnt); + OrderedSet read; + for (uint32 i = 0; i < cnt; ++i) { + DocumentData *document = Serialize::Document::readFromStream(gifs.version, gifs.stream); + if (!document || !document->isAnimation()) continue; + + if (read.contains(document->id)) continue; + read.insert(document->id); + + saved.push_back(document); + } +} + +void writeBackground(int32 id, const QImage &img) { + if (!_working()) return; + + QByteArray png; + if (!img.isNull()) { + QBuffer buf(&png); + if (!img.save(&buf, "BMP")) return; + } + if (!_backgroundKey) { + _backgroundKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); + } + quint32 size = sizeof(qint32) + sizeof(quint32) + (png.isEmpty() ? 0 : (sizeof(quint32) + png.size())); + EncryptedDescriptor data(size); + data.stream << qint32(id); + if (!png.isEmpty()) data.stream << png; + + FileWriteDescriptor file(_backgroundKey); + file.writeEncrypted(data); +} + +bool readBackground() { + if (_backgroundWasRead) return false; + _backgroundWasRead = true; + + FileReadDescriptor bg; + if (!readEncryptedFile(bg, _backgroundKey)) { + clearKey(_backgroundKey); + _backgroundKey = 0; + _writeMap(); + return false; + } + + QByteArray pngData; + qint32 id; + bg.stream >> id; + if (!id || id == DefaultChatBackground) { + if (bg.version < 8005) { + App::initBackground(DefaultChatBackground, QImage(), true); + if (!id) Window::chatBackground()->setTile(!DefaultChatBackground); + } else { + App::initBackground(id, QImage(), true); + } + return true; + } + bg.stream >> pngData; + + QImage img; + QBuffer buf(&pngData); + QImageReader reader(&buf); +#ifndef OS_MAC_OLD + reader.setAutoTransform(true); +#endif // OS_MAC_OLD + if (reader.read(&img)) { + App::initBackground(id, img, true); + return true; + } + return false; +} + +uint32 _peerSize(PeerData *peer) { + uint32 result = sizeof(quint64) + sizeof(quint64) + Serialize::storageImageLocationSize(); + if (peer->isUser()) { + UserData *user = peer->asUser(); + + // first + last + phone + username + access + result += Serialize::stringSize(user->firstName) + Serialize::stringSize(user->lastName) + Serialize::stringSize(user->phone()) + Serialize::stringSize(user->username) + sizeof(quint64); + + // flags + if (AppVersion >= 9012) { + result += sizeof(qint32); + } + + // onlineTill + contact + botInfoVersion + result += sizeof(qint32) + sizeof(qint32) + sizeof(qint32); + } else if (peer->isChat()) { + ChatData *chat = peer->asChat(); + + // name + count + date + version + admin + forbidden + left + inviteLink + result += Serialize::stringSize(chat->name) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(chat->inviteLink()); + } else if (peer->isChannel()) { + ChannelData *channel = peer->asChannel(); + + // name + access + date + version + forbidden + flags + inviteLink + result += Serialize::stringSize(channel->name) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(channel->inviteLink()); + } + return result; +} + +void _writePeer(QDataStream &stream, PeerData *peer) { + stream << quint64(peer->id) << quint64(peer->photoId); + Serialize::writeStorageImageLocation(stream, peer->photoLoc); + if (peer->isUser()) { + UserData *user = peer->asUser(); + + stream << user->firstName << user->lastName << user->phone() << user->username << quint64(user->access); + if (AppVersion >= 9012) { + stream << qint32(user->flags); + } + if (AppVersion >= 9016) { + stream << (user->botInfo ? user->botInfo->inlinePlaceholder : QString()); + } + stream << qint32(user->onlineTill) << qint32(user->contact) << qint32(user->botInfo ? user->botInfo->version : -1); + } else if (peer->isChat()) { + ChatData *chat = peer->asChat(); + + qint32 flagsData = (AppVersion >= 9012) ? chat->flags : (chat->haveLeft() ? 1 : 0); + + stream << chat->name << qint32(chat->count) << qint32(chat->date) << qint32(chat->version) << qint32(chat->creator); + stream << qint32(chat->isForbidden ? 1 : 0) << qint32(flagsData) << chat->inviteLink(); + } else if (peer->isChannel()) { + ChannelData *channel = peer->asChannel(); + + stream << channel->name << quint64(channel->access) << qint32(channel->date) << qint32(channel->version); + stream << qint32(channel->isForbidden ? 1 : 0) << qint32(channel->flags) << channel->inviteLink(); + } +} + +PeerData *_readPeer(FileReadDescriptor &from, int32 fileVersion = 0) { + quint64 peerId = 0, photoId = 0; + from.stream >> peerId >> photoId; + + StorageImageLocation photoLoc(Serialize::readStorageImageLocation(from.stream)); + + PeerData *result = App::peerLoaded(peerId); + bool wasLoaded = (result != nullptr); + if (!wasLoaded) { + result = App::peer(peerId); + result->loadedStatus = PeerData::FullLoaded; + } + if (result->isUser()) { + UserData *user = result->asUser(); + + QString first, last, phone, username, inlinePlaceholder; + quint64 access; + qint32 flags = 0, onlineTill, contact, botInfoVersion; + from.stream >> first >> last >> phone >> username >> access; + if (from.version >= 9012) { + from.stream >> flags; + } + if (from.version >= 9016 || fileVersion >= 9016) { + from.stream >> inlinePlaceholder; + } + from.stream >> onlineTill >> contact >> botInfoVersion; + + bool showPhone = !isServiceUser(user->id) && (peerToUser(user->id) != MTP::authedId()) && (contact <= 0); + QString pname = (showPhone && !phone.isEmpty()) ? App::formatPhone(phone) : QString(); + + if (!wasLoaded) { + user->setPhone(phone); + user->setName(first, last, pname, username); + + user->access = access; + user->flags = MTPDuser::Flags(flags); + user->onlineTill = onlineTill; + user->contact = contact; + user->setBotInfoVersion(botInfoVersion); + if (!inlinePlaceholder.isEmpty() && user->botInfo) { + user->botInfo->inlinePlaceholder = inlinePlaceholder; + } + + if (peerToUser(user->id) == MTP::authedId()) { + user->input = MTP_inputPeerSelf(); + user->inputUser = MTP_inputUserSelf(); + } else { + user->input = MTP_inputPeerUser(MTP_int(peerToUser(user->id)), MTP_long((user->access == UserNoAccess) ? 0 : user->access)); + user->inputUser = MTP_inputUser(MTP_int(peerToUser(user->id)), MTP_long((user->access == UserNoAccess) ? 0 : user->access)); + } + + user->setUserpic(photoLoc.isNull() ? ImagePtr(userDefPhoto(user->colorIndex)) : ImagePtr(photoLoc)); + } + } else if (result->isChat()) { + ChatData *chat = result->asChat(); + + QString name, inviteLink; + qint32 count, date, version, creator, forbidden, flagsData, flags; + from.stream >> name >> count >> date >> version >> creator >> forbidden >> flagsData >> inviteLink; + + if (from.version >= 9012) { + flags = flagsData; + } else { + // flagsData was haveLeft + flags = (flagsData == 1) ? MTPDchat::Flags(MTPDchat::Flag::f_left) : MTPDchat::Flags(0); + } + if (!wasLoaded) { + chat->setName(name); + chat->count = count; + chat->date = date; + chat->version = version; + chat->creator = creator; + chat->isForbidden = (forbidden == 1); + chat->flags = MTPDchat::Flags(flags); + chat->setInviteLink(inviteLink); + + chat->input = MTP_inputPeerChat(MTP_int(peerToChat(chat->id))); + chat->inputChat = MTP_int(peerToChat(chat->id)); + + chat->setUserpic(photoLoc.isNull() ? ImagePtr(chatDefPhoto(chat->colorIndex)) : ImagePtr(photoLoc)); + } + } else if (result->isChannel()) { + ChannelData *channel = result->asChannel(); + + QString name, inviteLink; + quint64 access; + qint32 date, version, forbidden, flags; + from.stream >> name >> access >> date >> version >> forbidden >> flags >> inviteLink; + + if (!wasLoaded) { + channel->setName(name, QString()); + channel->access = access; + channel->date = date; + channel->version = version; + channel->isForbidden = (forbidden == 1); + channel->flags = MTPDchannel::Flags(flags); + channel->setInviteLink(inviteLink); + + channel->input = MTP_inputPeerChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); + channel->inputChannel = MTP_inputChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); + + channel->setUserpic(photoLoc.isNull() ? ImagePtr((channel->isMegagroup() ? chatDefPhoto(channel->colorIndex) : channelDefPhoto(channel->colorIndex))) : ImagePtr(photoLoc)); + } + } + if (!wasLoaded) { + App::markPeerUpdated(result); + emit App::main()->peerPhotoChanged(result); + } + return result; +} + +void writeRecentHashtagsAndBots() { + if (!_working()) return; + + const RecentHashtagPack &write(cRecentWriteHashtags()), &search(cRecentSearchHashtags()); + const RecentInlineBots &bots(cRecentInlineBots()); + if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) readRecentHashtagsAndBots(); + if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) { + if (_recentHashtagsAndBotsKey) { + clearKey(_recentHashtagsAndBotsKey); + _recentHashtagsAndBotsKey = 0; + _mapChanged = true; + } + _writeMap(); + } else { + if (!_recentHashtagsAndBotsKey) { + _recentHashtagsAndBotsKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); + } + quint32 size = sizeof(quint32) * 3, writeCnt = 0, searchCnt = 0, botsCnt = cRecentInlineBots().size(); + for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e; ++i) { + if (!i->first.isEmpty()) { + size += Serialize::stringSize(i->first) + sizeof(quint16); + ++writeCnt; + } + } + for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) { + if (!i->first.isEmpty()) { + size += Serialize::stringSize(i->first) + sizeof(quint16); + ++searchCnt; + } + } + for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) { + size += _peerSize(*i); + } + + EncryptedDescriptor data(size); + data.stream << quint32(writeCnt) << quint32(searchCnt); + for (RecentHashtagPack::const_iterator i = write.cbegin(), e = write.cend(); i != e; ++i) { + if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); + } + for (RecentHashtagPack::const_iterator i = search.cbegin(), e = search.cend(); i != e; ++i) { + if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); + } + data.stream << quint32(botsCnt); + for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) { + _writePeer(data.stream, *i); + } + FileWriteDescriptor file(_recentHashtagsAndBotsKey); + file.writeEncrypted(data); + } +} + +void readRecentHashtagsAndBots() { + if (_recentHashtagsAndBotsWereRead) return; + _recentHashtagsAndBotsWereRead = true; + + if (!_recentHashtagsAndBotsKey) return; + + FileReadDescriptor hashtags; + if (!readEncryptedFile(hashtags, _recentHashtagsAndBotsKey)) { + clearKey(_recentHashtagsAndBotsKey); + _recentHashtagsAndBotsKey = 0; + _writeMap(); + return; + } + + quint32 writeCount = 0, searchCount = 0, botsCount = 0; + hashtags.stream >> writeCount >> searchCount; + + QString tag; + quint16 count; + + RecentHashtagPack write, search; + RecentInlineBots bots; + if (writeCount) { + write.reserve(writeCount); + for (uint32 i = 0; i < writeCount; ++i) { + hashtags.stream >> tag >> count; + write.push_back(qMakePair(tag.trimmed(), count)); + } + } + if (searchCount) { + search.reserve(searchCount); + for (uint32 i = 0; i < searchCount; ++i) { + hashtags.stream >> tag >> count; + search.push_back(qMakePair(tag.trimmed(), count)); + } + } + cSetRecentWriteHashtags(write); + cSetRecentSearchHashtags(search); + + if (!hashtags.stream.atEnd()) { + hashtags.stream >> botsCount; + if (botsCount) { + bots.reserve(botsCount); + for (uint32 i = 0; i < botsCount; ++i) { + PeerData *peer = _readPeer(hashtags, 9016); + if (peer && peer->isUser() && peer->asUser()->botInfo && !peer->asUser()->botInfo->inlinePlaceholder.isEmpty() && !peer->asUser()->username.isEmpty()) { + bots.push_back(peer->asUser()); + } + } + } + cSetRecentInlineBots(bots); + } +} + +void writeSavedPeers() { + if (!_working()) return; + + const SavedPeers &saved(cSavedPeers()); + if (saved.isEmpty()) { + if (_savedPeersKey) { + clearKey(_savedPeersKey); + _savedPeersKey = 0; + _mapChanged = true; + } + _writeMap(); + } else { + if (!_savedPeersKey) { + _savedPeersKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); + } + quint32 size = sizeof(quint32); + for (SavedPeers::const_iterator i = saved.cbegin(); i != saved.cend(); ++i) { + size += _peerSize(i.key()) + Serialize::dateTimeSize(); + } + + EncryptedDescriptor data(size); + data.stream << quint32(saved.size()); + for (SavedPeers::const_iterator i = saved.cbegin(); i != saved.cend(); ++i) { + _writePeer(data.stream, i.key()); + data.stream << i.value(); + } + + FileWriteDescriptor file(_savedPeersKey); + file.writeEncrypted(data); + } +} + +void readSavedPeers() { + if (!_savedPeersKey) return; + + FileReadDescriptor saved; + if (!readEncryptedFile(saved, _savedPeersKey)) { + clearKey(_savedPeersKey); + _savedPeersKey = 0; + _writeMap(); + return; + } + if (saved.version == 9011) { // broken dev version + clearKey(_savedPeersKey); + _savedPeersKey = 0; + _writeMap(); + return; + } + + quint32 count = 0; + saved.stream >> count; + cRefSavedPeers().clear(); + cRefSavedPeersByTime().clear(); + QList peers; + peers.reserve(count); + for (uint32 i = 0; i < count; ++i) { + PeerData *peer = _readPeer(saved); + if (!peer) break; + + QDateTime t; + saved.stream >> t; + + cRefSavedPeers().insert(peer, t); + cRefSavedPeersByTime().insert(t, peer); + peers.push_back(peer); + } + + if (App::api()) App::api()->requestPeers(peers); +} + +void addSavedPeer(PeerData *peer, const QDateTime &position) { + auto &savedPeers = cRefSavedPeers(); + auto i = savedPeers.find(peer); + if (i == savedPeers.cend()) { + savedPeers.insert(peer, position); + } else if (i.value() != position) { + cRefSavedPeersByTime().remove(i.value(), peer); + i.value() = position; + cRefSavedPeersByTime().insert(i.value(), peer); + } + writeSavedPeers(); +} + +void removeSavedPeer(PeerData *peer) { + auto &savedPeers = cRefSavedPeers(); + if (savedPeers.isEmpty()) return; + + auto i = savedPeers.find(peer); + if (i != savedPeers.cend()) { + cRefSavedPeersByTime().remove(i.value(), peer); + savedPeers.erase(i); + writeSavedPeers(); } +} - void removeSavedPeer(PeerData *peer) { - SavedPeers &savedPeers(cRefSavedPeers()); - if (savedPeers.isEmpty()) return; +void writeReportSpamStatuses() { + _writeReportSpamStatuses(); +} - SavedPeers::iterator i = savedPeers.find(peer); - if (i != savedPeers.cend()) { - cRefSavedPeersByTime().remove(i.value(), peer); - savedPeers.erase(i); +void writeTrustedBots() { + if (!_working()) return; - writeSavedPeers(); + if (_trustedBots.isEmpty()) { + if (_trustedBotsKey) { + clearKey(_trustedBotsKey); + _trustedBotsKey = 0; + _mapChanged = true; + _writeMap(); } + } else { + if (!_trustedBotsKey) { + _trustedBotsKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); + } + quint32 size = sizeof(qint32) + _trustedBots.size() * sizeof(quint64); + EncryptedDescriptor data(size); + data.stream << qint32(_trustedBots.size()); + for_const (auto botId, _trustedBots) { + data.stream << quint64(botId); + } + + FileWriteDescriptor file(_trustedBotsKey); + file.writeEncrypted(data); + } +} + +void readTrustedBots() { + if (!_trustedBotsKey) return; + + FileReadDescriptor trusted; + if (!readEncryptedFile(trusted, _trustedBotsKey)) { + clearKey(_trustedBotsKey); + _trustedBotsKey = 0; + _writeMap(); + return; } - void writeReportSpamStatuses() { - _writeReportSpamStatuses(); + qint32 size = 0; + trusted.stream >> size; + for (int i = 0; i < size; ++i) { + quint64 botId = 0; + trusted.stream >> botId; + _trustedBots.insert(botId); } +} - struct ClearManagerData { - QThread *thread; - StorageMap images, stickers, audios; - WebFilesMap webFiles; - QMutex mutex; - QList tasks; - bool working; - }; - - ClearManager::ClearManager() : data(new ClearManagerData()) { - data->thread = new QThread(); - data->working = true; +void makeBotTrusted(UserData *bot) { + if (!isBotTrusted(bot)) { + _trustedBots.insert(bot->id); + writeTrustedBots(); } +} - bool ClearManager::addTask(int task) { - QMutexLocker lock(&data->mutex); - if (!data->working) return false; +bool isBotTrusted(UserData *bot) { + if (!_trustedBotsRead) { + readTrustedBots(); + _trustedBotsRead = true; + } + return _trustedBots.contains(bot->id); +} - if (!data->tasks.isEmpty() && (data->tasks.at(0) == ClearManagerAll)) return true; - if (task == ClearManagerAll) { - data->tasks.clear(); +bool encrypt(const void *src, void *dst, uint32 len, const void *key128) { + if (!_localKey.created()) { + return false; + } + MTP::aesEncryptLocal(src, dst, len, &_localKey, key128); + return true; +} + +bool decrypt(const void *src, void *dst, uint32 len, const void *key128) { + if (!_localKey.created()) { + return false; + } + MTP::aesDecryptLocal(src, dst, len, &_localKey, key128); + return true; +} + +struct ClearManagerData { + QThread *thread; + StorageMap images, stickers, audios; + WebFilesMap webFiles; + QMutex mutex; + QList tasks; + bool working; +}; + +ClearManager::ClearManager() : data(new ClearManagerData()) { + data->thread = new QThread(); + data->working = true; +} + +bool ClearManager::addTask(int task) { + QMutexLocker lock(&data->mutex); + if (!data->working) return false; + + if (!data->tasks.isEmpty() && (data->tasks.at(0) == ClearManagerAll)) return true; + if (task == ClearManagerAll) { + data->tasks.clear(); + if (!_imagesMap.isEmpty()) { + _imagesMap.clear(); + _storageImagesSize = 0; + _mapChanged = true; + } + if (!_stickerImagesMap.isEmpty()) { + _stickerImagesMap.clear(); + _storageStickersSize = 0; + _mapChanged = true; + } + if (!_audiosMap.isEmpty()) { + _audiosMap.clear(); + _storageAudiosSize = 0; + _mapChanged = true; + } + if (!_draftsMap.isEmpty()) { + _draftsMap.clear(); + _mapChanged = true; + } + if (!_draftCursorsMap.isEmpty()) { + _draftCursorsMap.clear(); + _mapChanged = true; + } + if (_locationsKey) { + _locationsKey = 0; + _mapChanged = true; + } + if (_reportSpamStatusesKey) { + _reportSpamStatusesKey = 0; + _mapChanged = true; + } + if (_trustedBotsKey) { + _trustedBotsKey = 0; + _mapChanged = true; + } + if (_recentStickersKeyOld) { + _recentStickersKeyOld = 0; + _mapChanged = true; + } + if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { + _installedStickersKey = _featuredStickersKey = _recentStickersKey = _archivedStickersKey = 0; + _mapChanged = true; + } + if (_recentHashtagsAndBotsKey) { + _recentHashtagsAndBotsKey = 0; + _mapChanged = true; + } + if (_savedPeersKey) { + _savedPeersKey = 0; + _mapChanged = true; + } + _writeMap(); + } else { + if (task & ClearManagerStorage) { + if (data->images.isEmpty()) { + data->images = _imagesMap; + } else { + for (StorageMap::const_iterator i = _imagesMap.cbegin(), e = _imagesMap.cend(); i != e; ++i) { + StorageKey k = i.key(); + while (data->images.constFind(k) != data->images.cend()) { + ++k.second; + } + data->images.insert(k, i.value()); + } + } if (!_imagesMap.isEmpty()) { _imagesMap.clear(); _storageImagesSize = 0; _mapChanged = true; } + if (data->stickers.isEmpty()) { + data->stickers = _stickerImagesMap; + } else { + 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; + } + data->stickers.insert(k, i.value()); + } + } if (!_stickerImagesMap.isEmpty()) { _stickerImagesMap.clear(); _storageStickersSize = 0; _mapChanged = true; } + if (data->webFiles.isEmpty()) { + data->webFiles = _webFilesMap; + } else { + for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) { + QString k = i.key(); + while (data->webFiles.constFind(k) != data->webFiles.cend()) { + k += '#'; + } + data->webFiles.insert(k, i.value()); + } + } + if (!_webFilesMap.isEmpty()) { + _webFilesMap.clear(); + _storageWebFilesSize = 0; + _writeLocations(); + } + if (data->audios.isEmpty()) { + data->audios = _audiosMap; + } else { + for (StorageMap::const_iterator i = _audiosMap.cbegin(), e = _audiosMap.cend(); i != e; ++i) { + StorageKey k = i.key(); + while (data->audios.constFind(k) != data->audios.cend()) { + ++k.second; + } + data->audios.insert(k, i.value()); + } + } if (!_audiosMap.isEmpty()) { _audiosMap.clear(); _storageAudiosSize = 0; _mapChanged = true; } - if (!_draftsMap.isEmpty()) { - _draftsMap.clear(); - _mapChanged = true; - } - if (!_draftCursorsMap.isEmpty()) { - _draftCursorsMap.clear(); - _mapChanged = true; - } - if (_locationsKey) { - _locationsKey = 0; - _mapChanged = true; - } - if (_reportSpamStatusesKey) { - _reportSpamStatusesKey = 0; - _mapChanged = true; - } - if (_recentStickersKeyOld) { - _recentStickersKeyOld = 0; - _mapChanged = true; - } - if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { - _installedStickersKey = _featuredStickersKey = _recentStickersKey = _archivedStickersKey = 0; - _mapChanged = true; - } - if (_recentHashtagsAndBotsKey) { - _recentHashtagsAndBotsKey = 0; - _mapChanged = true; - } - if (_savedPeersKey) { - _savedPeersKey = 0; - _mapChanged = true; - } _writeMap(); - } else { - if (task & ClearManagerStorage) { - if (data->images.isEmpty()) { - data->images = _imagesMap; - } else { - for (StorageMap::const_iterator i = _imagesMap.cbegin(), e = _imagesMap.cend(); i != e; ++i) { - StorageKey k = i.key(); - while (data->images.constFind(k) != data->images.cend()) { - ++k.second; - } - data->images.insert(k, i.value()); - } - } - if (!_imagesMap.isEmpty()) { - _imagesMap.clear(); - _storageImagesSize = 0; - _mapChanged = true; - } - if (data->stickers.isEmpty()) { - data->stickers = _stickerImagesMap; - } else { - 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; - } - data->stickers.insert(k, i.value()); - } - } - if (!_stickerImagesMap.isEmpty()) { - _stickerImagesMap.clear(); - _storageStickersSize = 0; - _mapChanged = true; - } - if (data->webFiles.isEmpty()) { - data->webFiles = _webFilesMap; - } else { - for (WebFilesMap::const_iterator i = _webFilesMap.cbegin(), e = _webFilesMap.cend(); i != e; ++i) { - QString k = i.key(); - while (data->webFiles.constFind(k) != data->webFiles.cend()) { - k += '#'; - } - data->webFiles.insert(k, i.value()); - } - } - if (!_webFilesMap.isEmpty()) { - _webFilesMap.clear(); - _storageWebFilesSize = 0; - _writeLocations(); - } - if (data->audios.isEmpty()) { - data->audios = _audiosMap; - } else { - for (StorageMap::const_iterator i = _audiosMap.cbegin(), e = _audiosMap.cend(); i != e; ++i) { - StorageKey k = i.key(); - while (data->audios.constFind(k) != data->audios.cend()) { - ++k.second; - } - data->audios.insert(k, i.value()); - } - } - if (!_audiosMap.isEmpty()) { - _audiosMap.clear(); - _storageAudiosSize = 0; - _mapChanged = true; - } - _writeMap(); - } - for (int32 i = 0, l = data->tasks.size(); i < l; ++i) { - if (data->tasks.at(i) == task) return true; - } } - data->tasks.push_back(task); - return true; - } - - bool ClearManager::hasTask(ClearManagerTask task) { - QMutexLocker lock(&data->mutex); - if (data->tasks.isEmpty()) return false; - if (data->tasks.at(0) == ClearManagerAll) return true; for (int32 i = 0, l = data->tasks.size(); i < l; ++i) { if (data->tasks.at(i) == task) return true; } - return false; } + data->tasks.push_back(task); + return true; +} - void ClearManager::start() { - moveToThread(data->thread); - connect(data->thread, SIGNAL(started()), this, SLOT(onStart())); - connect(data->thread, SIGNAL(finished()), data->thread, SLOT(deleteLater())); - connect(data->thread, SIGNAL(finished()), this, SLOT(deleteLater())); - data->thread->start(); +bool ClearManager::hasTask(ClearManagerTask task) { + QMutexLocker lock(&data->mutex); + if (data->tasks.isEmpty()) return false; + if (data->tasks.at(0) == ClearManagerAll) return true; + for (int32 i = 0, l = data->tasks.size(); i < l; ++i) { + if (data->tasks.at(i) == task) return true; } + return false; +} - void ClearManager::stop() { +void ClearManager::start() { + moveToThread(data->thread); + connect(data->thread, SIGNAL(started()), this, SLOT(onStart())); + connect(data->thread, SIGNAL(finished()), data->thread, SLOT(deleteLater())); + connect(data->thread, SIGNAL(finished()), this, SLOT(deleteLater())); + data->thread->start(); +} + +void ClearManager::stop() { + { + QMutexLocker lock(&data->mutex); + data->tasks.clear(); + } + auto thread = data->thread; + thread->quit(); + thread->wait(); +} + +ClearManager::~ClearManager() { + delete data; +} + +void ClearManager::onStart() { + while (true) { + int task = 0; + bool result = false; + StorageMap images, stickers, audios; + WebFilesMap webFiles; { QMutexLocker lock(&data->mutex); - data->tasks.clear(); - } - auto thread = data->thread; - thread->quit(); - thread->wait(); - } - - ClearManager::~ClearManager() { - delete data; - } - - void ClearManager::onStart() { - while (true) { - int task = 0; - bool result = false; - StorageMap images, stickers, audios; - WebFilesMap webFiles; - { - QMutexLocker lock(&data->mutex); - if (data->tasks.isEmpty()) { - data->working = false; - break; - } - task = data->tasks.at(0); - images = data->images; - stickers = data->stickers; - audios = data->audios; - webFiles = data->webFiles; + if (data->tasks.isEmpty()) { + data->working = false; + break; } - switch (task) { - case ClearManagerAll: { - result = QDir(cTempDir()).removeRecursively(); - QDirIterator di(_userBasePath, QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot); - while (di.hasNext()) { - di.next(); - const QFileInfo& fi = di.fileInfo(); - if (fi.isDir() && !fi.isSymLink()) { - if (!QDir(di.filePath()).removeRecursively()) result = false; - } else { - QString path = di.filePath(); - if (!path.endsWith(qstr("map0")) && !path.endsWith(qstr("map1"))) { - if (!QFile::remove(di.filePath())) result = false; - } + task = data->tasks.at(0); + images = data->images; + stickers = data->stickers; + audios = data->audios; + webFiles = data->webFiles; + } + switch (task) { + case ClearManagerAll: { + result = QDir(cTempDir()).removeRecursively(); + QDirIterator di(_userBasePath, QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot); + while (di.hasNext()) { + di.next(); + const QFileInfo& fi = di.fileInfo(); + if (fi.isDir() && !fi.isSymLink()) { + if (!QDir(di.filePath()).removeRecursively()) result = false; + } else { + QString path = di.filePath(); + if (!path.endsWith(qstr("map0")) && !path.endsWith(qstr("map1"))) { + if (!QFile::remove(di.filePath())) result = false; } } - } break; - case ClearManagerDownloads: - result = QDir(cTempDir()).removeRecursively(); - break; - case ClearManagerStorage: - for (StorageMap::const_iterator i = images.cbegin(), e = images.cend(); i != e; ++i) { - clearKey(i.value().first, UserPath); - } - for (StorageMap::const_iterator i = stickers.cbegin(), e = stickers.cend(); i != e; ++i) { - clearKey(i.value().first, UserPath); - } - for (StorageMap::const_iterator i = audios.cbegin(), e = audios.cend(); i != e; ++i) { - clearKey(i.value().first, UserPath); - } - for (WebFilesMap::const_iterator i = webFiles.cbegin(), e = webFiles.cend(); i != e; ++i) { - clearKey(i.value().first, UserPath); - } - result = true; - break; } - { - QMutexLocker lock(&data->mutex); - if (!data->tasks.isEmpty() && data->tasks.at(0) == task) { - data->tasks.pop_front(); - } - if (data->tasks.isEmpty()) { - data->working = false; - } - if (result) { - emit succeed(task, data->working ? 0 : this); - } else { - emit failed(task, data->working ? 0 : this); - } - if (!data->working) break; + } break; + case ClearManagerDownloads: + result = QDir(cTempDir()).removeRecursively(); + break; + case ClearManagerStorage: + for (StorageMap::const_iterator i = images.cbegin(), e = images.cend(); i != e; ++i) { + clearKey(i.value().first, UserPath); } + for (StorageMap::const_iterator i = stickers.cbegin(), e = stickers.cend(); i != e; ++i) { + clearKey(i.value().first, UserPath); + } + for (StorageMap::const_iterator i = audios.cbegin(), e = audios.cend(); i != e; ++i) { + clearKey(i.value().first, UserPath); + } + for (WebFilesMap::const_iterator i = webFiles.cbegin(), e = webFiles.cend(); i != e; ++i) { + clearKey(i.value().first, UserPath); + } + result = true; + break; + } + { + QMutexLocker lock(&data->mutex); + if (!data->tasks.isEmpty() && data->tasks.at(0) == task) { + data->tasks.pop_front(); + } + if (data->tasks.isEmpty()) { + data->working = false; + } + if (result) { + emit succeed(task, data->working ? 0 : this); + } else { + emit failed(task, data->working ? 0 : this); + } + if (!data->working) break; } } - } + +namespace internal { + +Manager::Manager() { + _mapWriteTimer.setSingleShot(true); + connect(&_mapWriteTimer, SIGNAL(timeout()), this, SLOT(mapWriteTimeout())); + _locationsWriteTimer.setSingleShot(true); + connect(&_locationsWriteTimer, SIGNAL(timeout()), this, SLOT(locationsWriteTimeout())); +} + +void Manager::writeMap(bool fast) { + if (!_mapWriteTimer.isActive() || fast) { + _mapWriteTimer.start(fast ? 1 : WriteMapTimeout); + } else if (_mapWriteTimer.remainingTime() <= 0) { + mapWriteTimeout(); + } +} + +void Manager::writingMap() { + _mapWriteTimer.stop(); +} + +void Manager::writeLocations(bool fast) { + if (!_locationsWriteTimer.isActive() || fast) { + _locationsWriteTimer.start(fast ? 1 : WriteMapTimeout); + } else if (_locationsWriteTimer.remainingTime() <= 0) { + locationsWriteTimeout(); + } +} + +void Manager::writingLocations() { + _locationsWriteTimer.stop(); +} + +void Manager::mapWriteTimeout() { + _writeMap(WriteMapNow); +} + +void Manager::locationsWriteTimeout() { + _writeLocations(WriteMapNow); +} + +void Manager::finish() { + if (_mapWriteTimer.isActive()) { + mapWriteTimeout(); + } + if (_locationsWriteTimer.isActive()) { + locationsWriteTimeout(); + } +} + +} // namespace internal +} // namespace Local diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index 46a53a1775..d7b7e718da 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -22,165 +22,170 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/basic_types.h" -namespace _local_inner { +namespace Local { - class Manager : public QObject { +void start(); +void finish(); + +void readSettings(); +void writeSettings(); +void writeUserSettings(); +void writeMtpData(); + +void reset(); + +bool checkPasscode(const QByteArray &passcode); +void setPasscode(const QByteArray &passcode); + +enum ClearManagerTask { + ClearManagerAll = 0xFFFF, + ClearManagerDownloads = 0x01, + ClearManagerStorage = 0x02, +}; + +struct ClearManagerData; +class ClearManager : public QObject { Q_OBJECT - public: +public: + ClearManager(); + bool addTask(int task); + bool hasTask(ClearManagerTask task); + void start(); + void stop(); - Manager(); +signals: + void succeed(int task, void *manager); + void failed(int task, void *manager); - void writeMap(bool fast); - void writingMap(); - void writeLocations(bool fast); - void writingLocations(); - void finish(); +private slots: + void onStart(); + +private: + ~ClearManager(); + + ClearManagerData *data; + +}; + +enum ReadMapState { + ReadMapFailed = 0, + ReadMapDone = 1, + ReadMapPassNeeded = 2, +}; +ReadMapState readMap(const QByteArray &pass); +int32 oldMapVersion(); + +int32 oldSettingsVersion(); + +using TextWithTags = FlatTextarea::TextWithTags; +struct MessageDraft { + MessageDraft(MsgId msgId = 0, TextWithTags textWithTags = TextWithTags(), bool previewCancelled = false) + : msgId(msgId) + , textWithTags(textWithTags) + , previewCancelled(previewCancelled) { + } + MsgId msgId; + TextWithTags textWithTags; + bool previewCancelled; +}; +void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const MessageDraft &editDraft); +void readDraftsWithCursors(History *h); +void writeDraftCursors(const PeerId &peer, const MessageCursor &localCursor, const MessageCursor &editCursor); +bool hasDraftCursors(const PeerId &peer); +bool hasDraft(const PeerId &peer); + +void writeFileLocation(MediaKey location, const FileLocation &local); +FileLocation readFileLocation(MediaKey location, bool check = true); + +void writeImage(const StorageKey &location, const ImagePtr &img); +void writeImage(const StorageKey &location, const StorageImageSaved &jpeg, bool overwrite = true); +TaskId startImageLoad(const StorageKey &location, mtpFileLoader *loader); +int32 hasImages(); +qint64 storageImagesSize(); + +void writeStickerImage(const StorageKey &location, const QByteArray &data, bool overwrite = true); +TaskId startStickerImageLoad(const StorageKey &location, mtpFileLoader *loader); +bool willStickerImageLoad(const StorageKey &location); +bool copyStickerImage(const StorageKey &oldLocation, const StorageKey &newLocation); +int32 hasStickers(); +qint64 storageStickersSize(); + +void writeAudio(const StorageKey &location, const QByteArray &data, bool overwrite = true); +TaskId startAudioLoad(const StorageKey &location, mtpFileLoader *loader); +bool copyAudio(const StorageKey &oldLocation, const StorageKey &newLocation); +int32 hasAudios(); +qint64 storageAudiosSize(); + +void writeWebFile(const QString &url, const QByteArray &data, bool overwrite = true); +TaskId startWebFileLoad(const QString &url, webFileLoader *loader); +int32 hasWebFiles(); +qint64 storageWebFilesSize(); + +void countVoiceWaveform(DocumentData *document); + +void cancelTask(TaskId id); + +void writeInstalledStickers(); +void writeFeaturedStickers(); +void writeRecentStickers(); +void writeArchivedStickers(); +void readInstalledStickers(); +void readFeaturedStickers(); +void readRecentStickers(); +void readArchivedStickers(); +int32 countStickersHash(bool checkOutdatedInfo = false); +int32 countRecentStickersHash(); +int32 countFeaturedStickersHash(); + +void writeSavedGifs(); +void readSavedGifs(); +int32 countSavedGifsHash(); + +void writeBackground(int32 id, const QImage &img); +bool readBackground(); + +void writeRecentHashtagsAndBots(); +void readRecentHashtagsAndBots(); + +void addSavedPeer(PeerData *peer, const QDateTime &position); +void removeSavedPeer(PeerData *peer); +void readSavedPeers(); + +void writeReportSpamStatuses(); + +void makeBotTrusted(UserData *bot); +bool isBotTrusted(UserData *bot); + +bool encrypt(const void *src, void *dst, uint32 len, const void *key128); +bool decrypt(const void *src, void *dst, uint32 len, const void *key128); + +namespace internal { + +class Manager : public QObject { + Q_OBJECT + +public: + + Manager(); + + void writeMap(bool fast); + void writingMap(); + void writeLocations(bool fast); + void writingLocations(); + void finish(); public slots: - void mapWriteTimeout(); - void locationsWriteTimeout(); + void mapWriteTimeout(); + void locationsWriteTimeout(); - private: +private: - QTimer _mapWriteTimer; - QTimer _locationsWriteTimer; - - }; - -} - -namespace Local { - - void start(); - void finish(); - - void readSettings(); - void writeSettings(); - void writeUserSettings(); - void writeMtpData(); - - void reset(); - - bool checkPasscode(const QByteArray &passcode); - void setPasscode(const QByteArray &passcode); - - enum ClearManagerTask { - ClearManagerAll = 0xFFFF, - ClearManagerDownloads = 0x01, - ClearManagerStorage = 0x02, - }; - - struct ClearManagerData; - class ClearManager : public QObject { - Q_OBJECT - - public: - ClearManager(); - bool addTask(int task); - bool hasTask(ClearManagerTask task); - void start(); - void stop(); - - signals: - void succeed(int task, void *manager); - void failed(int task, void *manager); - - private slots: - void onStart(); - - private: - ~ClearManager(); - - ClearManagerData *data; - - }; - - enum ReadMapState { - ReadMapFailed = 0, - ReadMapDone = 1, - ReadMapPassNeeded = 2, - }; - ReadMapState readMap(const QByteArray &pass); - int32 oldMapVersion(); - - int32 oldSettingsVersion(); - - using TextWithTags = FlatTextarea::TextWithTags; - struct MessageDraft { - MessageDraft(MsgId msgId = 0, TextWithTags textWithTags = TextWithTags(), bool previewCancelled = false) - : msgId(msgId) - , textWithTags(textWithTags) - , previewCancelled(previewCancelled) { - } - MsgId msgId; - TextWithTags textWithTags; - bool previewCancelled; - }; - void writeDrafts(const PeerId &peer, const MessageDraft &localDraft, const MessageDraft &editDraft); - void readDraftsWithCursors(History *h); - void writeDraftCursors(const PeerId &peer, const MessageCursor &localCursor, const MessageCursor &editCursor); - bool hasDraftCursors(const PeerId &peer); - bool hasDraft(const PeerId &peer); - - void writeFileLocation(MediaKey location, const FileLocation &local); - FileLocation readFileLocation(MediaKey location, bool check = true); - - void writeImage(const StorageKey &location, const ImagePtr &img); - void writeImage(const StorageKey &location, const StorageImageSaved &jpeg, bool overwrite = true); - TaskId startImageLoad(const StorageKey &location, mtpFileLoader *loader); - int32 hasImages(); - qint64 storageImagesSize(); - - void writeStickerImage(const StorageKey &location, const QByteArray &data, bool overwrite = true); - TaskId startStickerImageLoad(const StorageKey &location, mtpFileLoader *loader); - bool willStickerImageLoad(const StorageKey &location); - bool copyStickerImage(const StorageKey &oldLocation, const StorageKey &newLocation); - int32 hasStickers(); - qint64 storageStickersSize(); - - void writeAudio(const StorageKey &location, const QByteArray &data, bool overwrite = true); - TaskId startAudioLoad(const StorageKey &location, mtpFileLoader *loader); - bool copyAudio(const StorageKey &oldLocation, const StorageKey &newLocation); - int32 hasAudios(); - qint64 storageAudiosSize(); - - void writeWebFile(const QString &url, const QByteArray &data, bool overwrite = true); - TaskId startWebFileLoad(const QString &url, webFileLoader *loader); - int32 hasWebFiles(); - qint64 storageWebFilesSize(); - - void countVoiceWaveform(DocumentData *document); - - void cancelTask(TaskId id); - - void writeInstalledStickers(); - void writeFeaturedStickers(); - void writeRecentStickers(); - void writeArchivedStickers(); - void readInstalledStickers(); - void readFeaturedStickers(); - void readRecentStickers(); - void readArchivedStickers(); - int32 countStickersHash(bool checkOutdatedInfo = false); - int32 countRecentStickersHash(); - int32 countFeaturedStickersHash(); - - void writeSavedGifs(); - void readSavedGifs(); - int32 countSavedGifsHash(); - - void writeBackground(int32 id, const QImage &img); - bool readBackground(); - - void writeRecentHashtagsAndBots(); - void readRecentHashtagsAndBots(); - - void addSavedPeer(PeerData *peer, const QDateTime &position); - void removeSavedPeer(PeerData *peer); - void readSavedPeers(); - - void writeReportSpamStatuses(); + QTimer _mapWriteTimer; + QTimer _locationsWriteTimer; }; + +} // namespace internal +} // namespace Local diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 53fdc26b79..dfcfe94ab0 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -28,6 +28,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "window/section_widget.h" #include "window/top_bar_widget.h" #include "data/data_drafts.h" +#include "dropdown.h" #include "observer_peer.h" #include "apiwrap.h" #include "dialogswidget.h" @@ -45,6 +46,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/contactsbox.h" #include "boxes/downloadpathbox.h" #include "boxes/confirmphonebox.h" +#include "boxes/sharebox.h" #include "localstorage.h" #include "shortcuts.h" #include "media/media_audio.h" @@ -1710,6 +1712,10 @@ void MainWidget::onShareContactCancel() { _history->cancelShareContact(); } +bool MainWidget::onSendSticker(DocumentData *document) { + return _history->onStickerSend(document); +} + void MainWidget::dialogsCancelled() { if (_hider) { _hider->startHide(); @@ -2045,14 +2051,14 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, Ui::Show } } + dlgUpdated(); if (back || (way == Ui::ShowWay::ClearStack)) { - dlgUpdated(); _peerInStack = nullptr; _msgIdInStack = 0; - dlgUpdated(); } else { saveSectionInStack(); } + dlgUpdated(); PeerData *wasActivePeer = activePeer(); @@ -2191,11 +2197,8 @@ void MainWidget::saveSectionInStack() { } else if (_wideSection) { _stack.push_back(std_::make_unique(_wideSection->createMemento())); } else if (_history->peer()) { - dlgUpdated(); _peerInStack = _history->peer(); _msgIdInStack = _history->msgId(); - dlgUpdated(); - _stack.push_back(std_::make_unique(_peerInStack, _msgIdInStack, _history->replyReturns())); } } @@ -3299,33 +3302,34 @@ bool MainWidget::started() { } void MainWidget::openLocalUrl(const QString &url) { - QString u(url.trimmed()); - if (u.size() > 8192) u = u.mid(0, 8192); + auto urlTrimmed = url.trimmed(); + if (urlTrimmed.size() > 8192) urlTrimmed = urlTrimmed.mid(0, 8192); - if (!u.startsWith(qstr("tg://"), Qt::CaseInsensitive)) { + if (!urlTrimmed.startsWith(qstr("tg://"), Qt::CaseInsensitive)) { return; } + auto command = urlTrimmed.midRef(qstr("tg://").size()); using namespace qthelp; auto matchOptions = RegExOption::CaseInsensitive; - if (auto joinChatMatch = regex_match(qsl("^tg://join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), u, matchOptions)) { + if (auto joinChatMatch = regex_match(qsl("^join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), command, matchOptions)) { joinGroupByHash(joinChatMatch->captured(1)); - } else if (auto stickerSetMatch = regex_match(qsl("^tg://addstickers/?\\?set=([a-zA-Z0-9\\.\\_]+)(&|$)"), u, matchOptions)) { + } else if (auto stickerSetMatch = regex_match(qsl("^addstickers/?\\?set=([a-zA-Z0-9\\.\\_]+)(&|$)"), command, matchOptions)) { stickersBox(MTP_inputStickerSetShortName(MTP_string(stickerSetMatch->captured(1)))); - } else if (auto shareUrlMatch = regex_match(qsl("^tg://msg_url/?\\?(.+)(#|$)"), u, matchOptions)) { + } else if (auto shareUrlMatch = regex_match(qsl("^msg_url/?\\?(.+)(#|$)"), command, matchOptions)) { auto params = url_parse_params(shareUrlMatch->captured(1), UrlParamNameTransform::ToLower); auto url = params.value(qsl("url")); if (!url.isEmpty()) { shareUrlLayer(url, params.value("text")); } - } else if (auto confirmPhoneMatch = regex_match(qsl("^tg://confirmphone/?\\?(.+)(#|$)"), u, matchOptions)) { + } else if (auto confirmPhoneMatch = regex_match(qsl("^confirmphone/?\\?(.+)(#|$)"), command, matchOptions)) { auto params = url_parse_params(confirmPhoneMatch->captured(1), UrlParamNameTransform::ToLower); auto phone = params.value(qsl("phone")); auto hash = params.value(qsl("hash")); if (!phone.isEmpty() && !hash.isEmpty()) { ConfirmPhoneBox::start(phone, hash); } - } else if (auto usernameMatch = regex_match(qsl("^tg://resolve/?\\?(.+)(#|$)"), u, matchOptions)) { + } else if (auto usernameMatch = regex_match(qsl("^resolve/?\\?(.+)(#|$)"), command, matchOptions)) { auto params = url_parse_params(usernameMatch->captured(1), UrlParamNameTransform::ToLower); auto domain = params.value(qsl("domain")); if (auto domainMatch = regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), domain, matchOptions)) { @@ -3345,6 +3349,9 @@ void MainWidget::openLocalUrl(const QString &url) { } openPeerByName(domain, post, startToken); } + } else if (auto shareGameScoreMatch = regex_match(qsl("^share_game_score/?\\?(.+)(#|$)"), command, matchOptions)) { + auto params = url_parse_params(shareGameScoreMatch->captured(1), UrlParamNameTransform::ToLower); + shareGameScoreByHash(params.value(qsl("hash"))); } } @@ -4673,91 +4680,97 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { auto &set = d.vstickerset.c_messages_stickerSet(); if (set.vset.type() == mtpc_stickerSet) { auto &s = set.vset.c_stickerSet(); - - auto &sets = Global::RefStickerSets(); - auto it = sets.find(s.vid.v); - if (it == sets.cend()) { - it = sets.insert(s.vid.v, Stickers::Set(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v | MTPDstickerSet::Flag::f_installed)); - } else { - it->flags |= MTPDstickerSet::Flag::f_installed; - if (it->flags & MTPDstickerSet::Flag::f_archived) { - it->flags &= ~MTPDstickerSet::Flag::f_archived; - writeArchived = true; - } - } - - const auto &v(set.vdocuments.c_vector().v); - it->stickers.clear(); - it->stickers.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; - - it->stickers.push_back(doc); - } - it->emoji.clear(); - auto &packs = set.vpacks.c_vector().v; - for (int32 i = 0, l = packs.size(); i < l; ++i) { - if (packs.at(i).type() != mtpc_stickerPack) continue; - auto &pack = packs.at(i).c_stickerPack(); - if (EmojiPtr e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) { - auto &stickers = pack.vdocuments.c_vector().v; - StickerPack p; - p.reserve(stickers.size()); - for (int32 j = 0, c = stickers.size(); j < c; ++j) { - DocumentData *doc = App::document(stickers.at(j).v); - if (!doc || !doc->sticker()) continue; - - p.push_back(doc); + if (!s.is_masks()) { + auto &sets = Global::RefStickerSets(); + auto it = sets.find(s.vid.v); + if (it == sets.cend()) { + it = sets.insert(s.vid.v, Stickers::Set(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v | MTPDstickerSet::Flag::f_installed)); + } else { + it->flags |= MTPDstickerSet::Flag::f_installed; + if (it->flags & MTPDstickerSet::Flag::f_archived) { + it->flags &= ~MTPDstickerSet::Flag::f_archived; + writeArchived = true; } - it->emoji.insert(e, p); } - } + auto inputSet = MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)); + auto &v = set.vdocuments.c_vector().v; + it->stickers.clear(); + it->stickers.reserve(v.size()); + for (int i = 0, l = v.size(); i < l; ++i) { + auto doc = App::feedDocument(v.at(i)); + if (!doc || !doc->sticker()) continue; - auto &order(Global::RefStickerSetsOrder()); - int32 insertAtIndex = 0, currentIndex = order.indexOf(s.vid.v); - if (currentIndex != insertAtIndex) { - if (currentIndex > 0) { - order.removeAt(currentIndex); + it->stickers.push_back(doc); + if (doc->sticker()->set.type() != mtpc_inputStickerSetID) { + doc->sticker()->set = inputSet; + } } - order.insert(insertAtIndex, s.vid.v); - } + it->emoji.clear(); + auto &packs = set.vpacks.c_vector().v; + for (int i = 0, l = packs.size(); i < l; ++i) { + if (packs.at(i).type() != mtpc_stickerPack) continue; + auto &pack = packs.at(i).c_stickerPack(); + if (auto e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) { + auto &stickers = pack.vdocuments.c_vector().v; + StickerPack p; + p.reserve(stickers.size()); + for (int j = 0, c = stickers.size(); j < c; ++j) { + auto doc = App::document(stickers.at(j).v); + if (!doc || !doc->sticker()) continue; - auto custom = sets.find(Stickers::CustomSetId); - if (custom != sets.cend()) { - for (int32 i = 0, l = it->stickers.size(); i < l; ++i) { - int32 removeIndex = custom->stickers.indexOf(it->stickers.at(i)); - if (removeIndex >= 0) custom->stickers.removeAt(removeIndex); + p.push_back(doc); + } + it->emoji.insert(e, p); + } } - if (custom->stickers.isEmpty()) { - sets.erase(custom); + + auto &order(Global::RefStickerSetsOrder()); + int32 insertAtIndex = 0, currentIndex = order.indexOf(s.vid.v); + if (currentIndex != insertAtIndex) { + if (currentIndex > 0) { + order.removeAt(currentIndex); + } + order.insert(insertAtIndex, s.vid.v); } + + auto custom = sets.find(Stickers::CustomSetId); + if (custom != sets.cend()) { + for (int32 i = 0, l = it->stickers.size(); i < l; ++i) { + int32 removeIndex = custom->stickers.indexOf(it->stickers.at(i)); + if (removeIndex >= 0) custom->stickers.removeAt(removeIndex); + } + if (custom->stickers.isEmpty()) { + sets.erase(custom); + } + } + Local::writeInstalledStickers(); + if (writeArchived) Local::writeArchivedStickers(); + emit stickersUpdated(); } - Local::writeInstalledStickers(); - if (writeArchived) Local::writeArchivedStickers(); - emit stickersUpdated(); } } } break; case mtpc_updateStickerSetsOrder: { auto &d = update.c_updateStickerSetsOrder(); - auto &order = d.vorder.c_vector().v; - auto &sets = Global::StickerSets(); - Stickers::Order result; - for (int32 i = 0, l = order.size(); i < l; ++i) { - if (sets.constFind(order.at(i).v) == sets.cend()) { - break; + if (!d.is_masks()) { + auto &order = d.vorder.c_vector().v; + auto &sets = Global::StickerSets(); + Stickers::Order result; + for (int i = 0, l = order.size(); i < l; ++i) { + if (sets.constFind(order.at(i).v) == sets.cend()) { + break; + } + result.push_back(order.at(i).v); + } + if (result.size() != Global::StickerSetsOrder().size() || result.size() != order.size()) { + Global::SetLastStickersUpdate(0); + App::main()->updateStickers(); + } else { + Global::SetStickerSetsOrder(result); + Local::writeInstalledStickers(); + emit stickersUpdated(); } - result.push_back(order.at(i).v); - } - if (result.size() != Global::StickerSetsOrder().size() || result.size() != order.size()) { - Global::SetLastStickersUpdate(0); - App::main()->updateStickers(); - } else { - Global::SetStickerSetsOrder(result); - Local::writeInstalledStickers(); - emit stickersUpdated(); } } break; @@ -4772,16 +4785,11 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { } break; case mtpc_updateReadFeaturedStickers: { - for (auto &set : Global::RefStickerSets()) { - if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { - set.flags &= ~MTPDstickerSet_ClientFlag::f_unread; - } - } - if (Global::FeaturedStickerSetsUnreadCount()) { - Global::SetFeaturedStickerSetsUnreadCount(0); - Local::writeFeaturedStickers(); - emit stickersUpdated(); - } + // We read some of the featured stickers, perhaps not all of them. + // Here we don't know what featured sticker sets were read, so we + // request all of them once again. + Global::SetLastFeaturedStickersUpdate(0); + App::main()->updateStickers(); } break; ////// Cloud saved GIFs diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 8925f7091c..9394949670 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -212,6 +212,7 @@ public: void onSendFileCancel(const FileLoadResultPtr &file); void onShareContactConfirm(const QString &phone, const QString &fname, const QString &lname, MsgId replyTo, bool ctrlShiftEnter); void onShareContactCancel(); + bool onSendSticker(DocumentData *sticker); void destroyData(); void updateOnlineDisplayIn(int32 msecs); diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index 64cb8d5ff1..843bbf9914 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -146,12 +146,12 @@ inputFile#f52ff27f id:long parts:int name:string md5_checksum:string = InputFile inputFileBig#fa4f0bb5 id:long parts:int name:string = InputFile; inputMediaEmpty#9664f57f = InputMedia; -inputMediaUploadedPhoto#f7aff1c0 file:InputFile caption:string = InputMedia; +inputMediaUploadedPhoto#630c9af1 flags:# file:InputFile caption:string stickers:flags.0?Vector = InputMedia; inputMediaPhoto#e9bfb4f3 id:InputPhoto caption:string = InputMedia; inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia; inputMediaContact#a6e45987 phone_number:string first_name:string last_name:string = InputMedia; -inputMediaUploadedDocument#1d89306d file:InputFile mime_type:string attributes:Vector caption:string = InputMedia; -inputMediaUploadedThumbDocument#ad613491 file:InputFile thumb:InputFile mime_type:string attributes:Vector caption:string = InputMedia; +inputMediaUploadedDocument#d070f1e9 flags:# file:InputFile mime_type:string attributes:Vector caption:string stickers:flags.0?Vector = InputMedia; +inputMediaUploadedThumbDocument#50d88cae flags:# file:InputFile thumb:InputFile mime_type:string attributes:Vector caption:string stickers:flags.0?Vector = InputMedia; inputMediaDocument#1a77f29c id:InputDocument caption:string = InputMedia; inputMediaVenue#2827a81a geo_point:InputGeoPoint title:string address:string provider:string venue_id:string = InputMedia; inputMediaGifExternal#4843b0fd url:string q:string = InputMedia; @@ -159,8 +159,8 @@ inputMediaPhotoExternal#b55f4f18 url:string caption:string = InputMedia; inputMediaDocumentExternal#e5e9607c url:string caption:string = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; -inputChatUploadedPhoto#94254732 file:InputFile crop:InputPhotoCrop = InputChatPhoto; -inputChatPhoto#b2e1bf08 id:InputPhoto crop:InputPhotoCrop = InputChatPhoto; +inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto; +inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto; inputGeoPointEmpty#e4c123d6 = InputGeoPoint; inputGeoPoint#f3b7acc9 lat:double long:double = InputGeoPoint; @@ -172,9 +172,6 @@ inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLo inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation; inputDocumentFileLocation#430f0724 id:long access_hash:long version:int = InputFileLocation; -inputPhotoCropAuto#ade6b004 = InputPhotoCrop; -inputPhotoCrop#d9915325 crop_left:double crop_top:double crop_width:double = InputPhotoCrop; - inputAppEvent#770656a8 time:double type:string peer:long data:string = InputAppEvent; peerUser#9db1bc6d user_id:int = Peer; @@ -253,11 +250,12 @@ messageActionChatMigrateTo#51bdb021 channel_id:int = MessageAction; messageActionChannelMigrateFrom#b055eaee title:string chat_id:int = MessageAction; messageActionPinMessage#94bd38ed = MessageAction; messageActionHistoryClear#9fbab604 = MessageAction; +messageActionGameScore#3a14cfa5 game_id:int score:int = MessageAction; dialog#66ffba14 flags:# peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog; photoEmpty#2331b22d id:long = Photo; -photo#cded42fe id:long access_hash:long date:int sizes:Vector = Photo; +photo#9288dd29 flags:# has_stickers:flags.0?true id:long access_hash:long date:int sizes:Vector = Photo; photoSizeEmpty#e17e23c type:string = PhotoSize; photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize; @@ -382,16 +380,16 @@ updateChannelMessageViews#98a12b4b channel_id:int id:int views:int = Update; updateChatAdmins#6e947941 chat_id:int enabled:Bool version:int = Update; updateChatParticipantAdmin#b6901959 chat_id:int user_id:int is_admin:Bool version:int = Update; updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update; -updateStickerSetsOrder#f0dfb451 order:Vector = Update; +updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true order:Vector = Update; updateStickerSets#43ae3dec = Update; updateSavedGifs#9375341e = Update; updateBotInlineQuery#54826690 flags:# query_id:long user_id:int query:string geo:flags.0?GeoPoint offset:string = Update; updateBotInlineSend#e48f964 flags:# user_id:int query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update; updateEditChannelMessage#1b3f4df7 message:Message pts:int pts_count:int = Update; updateChannelPinnedMessage#98592475 channel_id:int id:int = Update; -updateBotCallbackQuery#a68c688c query_id:long user_id:int peer:Peer msg_id:int data:bytes = Update; +updateBotCallbackQuery#81c5615f flags:# query_id:long user_id:int peer:Peer msg_id:int data:flags.0?bytes game_id:flags.1?int = Update; updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update; -updateInlineBotCallbackQuery#2cbd95af query_id:long user_id:int msg_id:InputBotInlineMessageID data:bytes = Update; +updateInlineBotCallbackQuery#d618a28b flags:# query_id:long user_id:int msg_id:InputBotInlineMessageID data:flags.0?bytes game_id:flags.1?int = Update; updateReadChannelOutbox#25d6c9c7 channel_id:int max_id:int = Update; updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update; updateReadFeaturedStickers#571d2742 = Update; @@ -508,10 +506,11 @@ accountDaysTTL#b8d0afdf days:int = AccountDaysTTL; documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute; documentAttributeAnimated#11b58939 = DocumentAttribute; -documentAttributeSticker#3a556302 alt:string stickerset:InputStickerSet = DocumentAttribute; +documentAttributeSticker#6319d612 flags:# mask:flags.1?true alt:string stickerset:InputStickerSet mask_coords:flags.0?MaskCoords = DocumentAttribute; documentAttributeVideo#5910cccb duration:int w:int h:int = DocumentAttribute; documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute; documentAttributeFilename#15590068 file_name:string = DocumentAttribute; +documentAttributeHasStickers#9801d2f7 = DocumentAttribute; messages.stickersNotModified#f1749a22 = messages.Stickers; messages.stickers#8a8ecd32 hash:string stickers:Vector = messages.Stickers; @@ -559,7 +558,7 @@ inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; -stickerSet#cd303b41 flags:# installed:flags.0?true archived:flags.1?true official:flags.2?true id:long access_hash:long title:string short_name:string count:int hash:int = StickerSet; +stickerSet#cd303b41 flags:# installed:flags.0?true archived:flags.1?true official:flags.2?true masks:flags.3?true id:long access_hash:long title:string short_name:string count:int hash:int = StickerSet; messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet; @@ -573,6 +572,7 @@ keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton; keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton; keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton; keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton; +keyboardButtonGame#28fc3164 text:string game_title:string game_id:int start_param:string = KeyboardButton; keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; @@ -714,6 +714,12 @@ messages.stickerSetInstallResultSuccess#38641628 = messages.StickerSetInstallRes messages.stickerSetInstallResultArchive#35e410a8 sets:Vector = messages.StickerSetInstallResult; stickerSetCovered#6410a5d2 set:StickerSet cover:Document = StickerSetCovered; +stickerSetMultiCovered#3407e51b set:StickerSet covers:Vector = StickerSetCovered; + +maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords; + +inputStickeredMediaPhoto#4a992157 id:InputPhoto = InputStickeredMedia; +inputStickeredMediaDocument#438865b id:InputDocument = InputStickeredMedia; ---functions--- @@ -739,6 +745,7 @@ auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery; auth.recoverPassword#4ea56e92 code:string = auth.Authorization; auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode; auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool; +auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector = Bool; account.registerDevice#637ea878 token_type:int token:string = Bool; account.unregisterDevice#65c55b40 token_type:int token:string = Bool; @@ -796,7 +803,7 @@ messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool; messages.sendMessage#fa88427a flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Updates; messages.sendMedia#c8f16791 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia random_id:long reply_markup:flags.2?ReplyMarkup = Updates; -messages.forwardMessages#708e0195 flags:# silent:flags.5?true background:flags.6?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer = Updates; +messages.forwardMessages#708e0195 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.hideReportSpam#a8f1709b peer:InputPeer = Bool; messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings; @@ -819,7 +826,6 @@ messages.sendEncryptedFile#9a901b66 peer:InputEncryptedChat random_id:long data: messages.sendEncryptedService#32d439a4 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; messages.receivedQueue#55a5bb66 max_qts:int = Vector; messages.readMessageContents#36a73f77 id:Vector = messages.AffectedMessages; -messages.getStickers#ae22e045 emoticon:string hash:string = messages.Stickers; messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers; messages.getWebPagePreview#25223e24 message:string = MessageMedia; messages.exportChatInvite#7d885289 chat_id:int = ExportedChatInvite; @@ -834,7 +840,7 @@ messages.toggleChatAdmins#ec8bd9e1 chat_id:int enabled:Bool = Updates; messages.editChatAdmin#a9e69f2e chat_id:int user_id:InputUser is_admin:Bool = Bool; messages.migrateChat#15a3b8e3 chat_id:int = Updates; messages.searchGlobal#9e3cacb0 q:string offset_date:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; -messages.reorderStickerSets#9fcfbc30 order:Vector = Bool; +messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; messages.searchGifs#bf9a776b q:string offset:int = messages.FoundGifs; messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs; @@ -845,25 +851,28 @@ messages.sendInlineBotResult#b16e06fe flags:# silent:flags.5?true background:fla messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.editMessage#ce91e4ca flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Updates; messages.editInlineBotMessage#130c2c85 flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; -messages.getBotCallbackAnswer#a6e94f04 peer:InputPeer msg_id:int data:bytes = messages.BotCallbackAnswer; +messages.getBotCallbackAnswer#6c996518 flags:# peer:InputPeer msg_id:int data:flags.0?bytes game_id:flags.1?int = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#c927d44b flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string = Bool; messages.getPeerDialogs#2d9776b9 peers:Vector = messages.PeerDialogs; messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector = Bool; messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#2dacca4f hash:int = messages.FeaturedStickers; -messages.readFeaturedStickers#e21cbb = Bool; -messages.getRecentStickers#99197c2c hash:int = messages.RecentStickers; -messages.saveRecentSticker#348e39bf id:InputDocument unsave:Bool = Bool; -messages.clearRecentStickers#ab02e5d2 = Bool; -messages.getUnusedStickers#4309d65b limit:int = Vector; +messages.readFeaturedStickers#5b118126 id:Vector = Bool; +messages.getRecentStickers#5ea192c9 flags:# attached:flags.0?true hash:int = messages.RecentStickers; +messages.saveRecentSticker#392718f8 flags:# attached:flags.0?true id:InputDocument unsave:Bool = Bool; +messages.clearRecentStickers#8999602d flags:# attached:flags.0?true = Bool; messages.getArchivedStickers#906e241f offset_id:long limit:int = messages.ArchivedStickers; +messages.setGameScore#dfbc7c1f flags:# edit_message:flags.0?true peer:InputPeer id:int user_id:InputUser game_id:int score:int = Updates; +messages.setInlineGameScore#54f882f1 flags:# edit_message:flags.0?true id:InputBotInlineMessageID user_id:InputUser game_id:int score:int = Bool; +messages.getMaskStickers#65b8c79f hash:int = messages.AllStickers; +messages.getAttachedStickers#cc5b67cc media:InputStickeredMedia = Vector; updates.getState#edd4882a = updates.State; updates.getDifference#a041495 pts:int date:int qts:int = updates.Difference; updates.getChannelDifference#bb32d7c0 channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; -photos.updateProfilePhoto#eef579a0 id:InputPhoto crop:InputPhotoCrop = UserProfilePhoto; -photos.uploadProfilePhoto#d50f9c88 file:InputFile caption:string geo_point:InputGeoPoint crop:InputPhotoCrop = photos.Photo; +photos.updateProfilePhoto#f0bb5152 id:InputPhoto = UserProfilePhoto; +photos.uploadProfilePhoto#4f32c098 file:InputFile = photos.Photo; photos.deletePhotos#87cf7f2f id:Vector = Vector; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; @@ -908,4 +917,4 @@ channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; channels.updatePinnedMessage#a72ded52 flags:# silent:flags.0?true channel:InputChannel id:int = Updates; channels.getAdminedPublicChannels#8d8d82d7 = messages.Chats; -// LAYER 55 +// LAYER 56 diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.cpp b/Telegram/SourceFiles/mtproto/scheme_auto.cpp index 19ad70dc3a..99d2c3491c 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.cpp +++ b/Telegram/SourceFiles/mtproto/scheme_auto.cpp @@ -588,6 +588,8 @@ void _serialize_inputMediaEmpty(MTPStringLogger &to, int32 stage, int32 lev, Typ } void _serialize_inputMediaUploadedPhoto(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPDinputMediaUploadedPhoto::Flags flag(iflag); + if (stage) { to.add(",\n").addSpaces(lev); } else { @@ -595,8 +597,10 @@ void _serialize_inputMediaUploadedPhoto(MTPStringLogger &to, int32 stage, int32 to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" file: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" caption: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" file: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" caption: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" stickers: "); ++stages.back(); if (flag & MTPDinputMediaUploadedPhoto::Flag::f_stickers) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -644,6 +648,8 @@ void _serialize_inputMediaContact(MTPStringLogger &to, int32 stage, int32 lev, T } void _serialize_inputMediaUploadedDocument(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPDinputMediaUploadedDocument::Flags flag(iflag); + if (stage) { to.add(",\n").addSpaces(lev); } else { @@ -651,15 +657,19 @@ void _serialize_inputMediaUploadedDocument(MTPStringLogger &to, int32 stage, int to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" file: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" mime_type: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" attributes: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" caption: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" file: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" mime_type: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" attributes: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" caption: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" stickers: "); ++stages.back(); if (flag & MTPDinputMediaUploadedDocument::Flag::f_stickers) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } void _serialize_inputMediaUploadedThumbDocument(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPDinputMediaUploadedThumbDocument::Flags flag(iflag); + if (stage) { to.add(",\n").addSpaces(lev); } else { @@ -667,11 +677,13 @@ void _serialize_inputMediaUploadedThumbDocument(MTPStringLogger &to, int32 stage to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" file: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" thumb: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" mime_type: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" attributes: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 4: to.add(" caption: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" file: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" thumb: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" mime_type: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" attributes: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" caption: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 6: to.add(" stickers: "); ++stages.back(); if (flag & MTPDinputMediaUploadedThumbDocument::Flag::f_stickers) { types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -762,7 +774,6 @@ void _serialize_inputChatUploadedPhoto(MTPStringLogger &to, int32 stage, int32 l } switch (stage) { case 0: to.add(" file: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" crop: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -776,7 +787,6 @@ void _serialize_inputChatPhoto(MTPStringLogger &to, int32 stage, int32 lev, Type } switch (stage) { case 0: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" crop: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -861,25 +871,6 @@ void _serialize_inputDocumentFileLocation(MTPStringLogger &to, int32 stage, int3 } } -void _serialize_inputPhotoCropAuto(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - to.add("{ inputPhotoCropAuto }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); -} - -void _serialize_inputPhotoCrop(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ inputPhotoCrop"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" crop_left: "); ++stages.back(); types.push_back(mtpc_double+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" crop_top: "); ++stages.back(); types.push_back(mtpc_double+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" crop_width: "); ++stages.back(); types.push_back(mtpc_double+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - void _serialize_inputAppEvent(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -1672,6 +1663,20 @@ void _serialize_messageActionHistoryClear(MTPStringLogger &to, int32 stage, int3 to.add("{ messageActionHistoryClear }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } +void _serialize_messageActionGameScore(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messageActionGameScore"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" game_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" score: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_dialog(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { MTPDdialog::Flags flag(iflag); @@ -1709,6 +1714,8 @@ void _serialize_photoEmpty(MTPStringLogger &to, int32 stage, int32 lev, Types &t } void _serialize_photo(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPDphoto::Flags flag(iflag); + if (stage) { to.add(",\n").addSpaces(lev); } else { @@ -1716,10 +1723,12 @@ void _serialize_photo(MTPStringLogger &to, int32 stage, int32 lev, Types &types, to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" date: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" sizes: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" has_stickers: "); ++stages.back(); if (flag & MTPDphoto::Flag::f_has_stickers) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" date: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" sizes: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -2880,6 +2889,8 @@ void _serialize_updateNewStickerSet(MTPStringLogger &to, int32 stage, int32 lev, } void _serialize_updateStickerSetsOrder(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPDupdateStickerSetsOrder::Flags flag(iflag); + if (stage) { to.add(",\n").addSpaces(lev); } else { @@ -2887,7 +2898,9 @@ void _serialize_updateStickerSetsOrder(MTPStringLogger &to, int32 stage, int32 l to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" order: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_long+0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" masks: "); ++stages.back(); if (flag & MTPDupdateStickerSetsOrder::Flag::f_masks) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" order: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_long+0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -2970,6 +2983,8 @@ void _serialize_updateChannelPinnedMessage(MTPStringLogger &to, int32 stage, int } void _serialize_updateBotCallbackQuery(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPDupdateBotCallbackQuery::Flags flag(iflag); + if (stage) { to.add(",\n").addSpaces(lev); } else { @@ -2977,11 +2992,13 @@ void _serialize_updateBotCallbackQuery(MTPStringLogger &to, int32 stage, int32 l to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" query_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" user_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" msg_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 4: to.add(" data: "); ++stages.back(); types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" query_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" user_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" msg_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" data: "); ++stages.back(); if (flag & MTPDupdateBotCallbackQuery::Flag::f_data) { types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 6: to.add(" game_id: "); ++stages.back(); if (flag & MTPDupdateBotCallbackQuery::Flag::f_game_id) { types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -3002,6 +3019,8 @@ void _serialize_updateEditMessage(MTPStringLogger &to, int32 stage, int32 lev, T } void _serialize_updateInlineBotCallbackQuery(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPDupdateInlineBotCallbackQuery::Flags flag(iflag); + if (stage) { to.add(",\n").addSpaces(lev); } else { @@ -3009,10 +3028,12 @@ void _serialize_updateInlineBotCallbackQuery(MTPStringLogger &to, int32 stage, i to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" query_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" user_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" msg_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" data: "); ++stages.back(); types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" query_id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" user_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" msg_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" data: "); ++stages.back(); if (flag & MTPDupdateInlineBotCallbackQuery::Flag::f_data) { types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 5: to.add(" game_id: "); ++stages.back(); if (flag & MTPDupdateInlineBotCallbackQuery::Flag::f_game_id) { types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -4008,6 +4029,8 @@ void _serialize_documentAttributeAnimated(MTPStringLogger &to, int32 stage, int3 } void _serialize_documentAttributeSticker(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPDdocumentAttributeSticker::Flags flag(iflag); + if (stage) { to.add(",\n").addSpaces(lev); } else { @@ -4015,8 +4038,11 @@ void _serialize_documentAttributeSticker(MTPStringLogger &to, int32 stage, int32 to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" alt: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" stickerset: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" mask: "); ++stages.back(); if (flag & MTPDdocumentAttributeSticker::Flag::f_mask) { to.add("YES [ BY BIT 1 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; + case 2: to.add(" alt: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" stickerset: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" mask_coords: "); ++stages.back(); if (flag & MTPDdocumentAttributeSticker::Flag::f_mask_coords) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -4069,6 +4095,10 @@ void _serialize_documentAttributeFilename(MTPStringLogger &to, int32 stage, int3 } } +void _serialize_documentAttributeHasStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + to.add("{ documentAttributeHasStickers }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); +} + void _serialize_messages_stickersNotModified(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { to.add("{ messages_stickersNotModified }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } @@ -4446,12 +4476,13 @@ void _serialize_stickerSet(MTPStringLogger &to, int32 stage, int32 lev, Types &t case 1: to.add(" installed: "); ++stages.back(); if (flag & MTPDstickerSet::Flag::f_installed) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; case 2: to.add(" archived: "); ++stages.back(); if (flag & MTPDstickerSet::Flag::f_archived) { to.add("YES [ BY BIT 1 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; case 3: to.add(" official: "); ++stages.back(); if (flag & MTPDstickerSet::Flag::f_official) { to.add("YES [ BY BIT 2 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break; - case 4: to.add(" id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 5: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 6: to.add(" title: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 7: to.add(" short_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 8: to.add(" count: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 9: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" masks: "); ++stages.back(); if (flag & MTPDstickerSet::Flag::f_masks) { to.add("YES [ BY BIT 3 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break; + case 5: to.add(" id: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 6: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 7: to.add(" title: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 8: to.add(" short_name: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 9: to.add(" count: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 10: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -4585,6 +4616,22 @@ void _serialize_keyboardButtonSwitchInline(MTPStringLogger &to, int32 stage, int } } +void _serialize_keyboardButtonGame(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ keyboardButtonGame"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" text: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" game_title: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" game_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" start_param: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_keyboardButtonRow(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -5891,6 +5938,62 @@ void _serialize_stickerSetCovered(MTPStringLogger &to, int32 stage, int32 lev, T } } +void _serialize_stickerSetMultiCovered(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ stickerSetMultiCovered"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" set: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" covers: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_maskCoords(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ maskCoords"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" n: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" x: "); ++stages.back(); types.push_back(mtpc_double+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" y: "); ++stages.back(); types.push_back(mtpc_double+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" zoom: "); ++stages.back(); types.push_back(mtpc_double+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_inputStickeredMediaPhoto(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ inputStickeredMediaPhoto"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_inputStickeredMediaDocument(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ inputStickeredMediaDocument"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_req_pq(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -6072,6 +6175,19 @@ void _serialize_auth_cancelCode(MTPStringLogger &to, int32 stage, int32 lev, Typ } } +void _serialize_auth_dropTempAuthKeys(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ auth_dropTempAuthKeys"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" except_auth_keys: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_long+0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_account_registerDevice(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -6401,6 +6517,8 @@ void _serialize_messages_editChatAdmin(MTPStringLogger &to, int32 stage, int32 l } void _serialize_messages_reorderStickerSets(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPmessages_reorderStickerSets::Flags flag(iflag); + if (stage) { to.add(",\n").addSpaces(lev); } else { @@ -6408,7 +6526,9 @@ void _serialize_messages_reorderStickerSets(MTPStringLogger &to, int32 stage, in to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" order: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_long+0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" masks: "); ++stages.back(); if (flag & MTPmessages_reorderStickerSets::Flag::f_masks) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" order: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_long+0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -6509,10 +6629,21 @@ void _serialize_messages_saveDraft(MTPStringLogger &to, int32 stage, int32 lev, } void _serialize_messages_readFeaturedStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - to.add("{ messages_readFeaturedStickers }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_readFeaturedStickers"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_long+0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } } void _serialize_messages_saveRecentSticker(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPmessages_saveRecentSticker::Flags flag(iflag); + if (stage) { to.add(",\n").addSpaces(lev); } else { @@ -6520,14 +6651,48 @@ void _serialize_messages_saveRecentSticker(MTPStringLogger &to, int32 stage, int to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" unsave: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" attached: "); ++stages.back(); if (flag & MTPmessages_saveRecentSticker::Flag::f_attached) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" unsave: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } void _serialize_messages_clearRecentStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - to.add("{ messages_clearRecentStickers }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); + MTPmessages_clearRecentStickers::Flags flag(iflag); + + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_clearRecentStickers"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" attached: "); ++stages.back(); if (flag & MTPmessages_clearRecentStickers::Flag::f_attached) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + +void _serialize_messages_setInlineGameScore(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPmessages_setInlineGameScore::Flags flag(iflag); + + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_setInlineGameScore"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" edit_message: "); ++stages.back(); if (flag & MTPmessages_setInlineGameScore::Flag::f_edit_message) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" user_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" game_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" score: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } } void _serialize_upload_saveFilePart(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { @@ -7447,10 +7612,11 @@ void _serialize_messages_forwardMessages(MTPStringLogger &to, int32 stage, int32 case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; case 1: to.add(" silent: "); ++stages.back(); if (flag & MTPmessages_forwardMessages::Flag::f_silent) { to.add("YES [ BY BIT 5 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 5 IN FIELD flags ]"); } break; case 2: to.add(" background: "); ++stages.back(); if (flag & MTPmessages_forwardMessages::Flag::f_background) { to.add("YES [ BY BIT 6 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break; - case 3: to.add(" from_peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 4: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_int+0); stages.push_back(0); flags.push_back(0); break; - case 5: to.add(" random_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_long+0); stages.push_back(0); flags.push_back(0); break; - case 6: to.add(" to_peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" with_my_score: "); ++stages.back(); if (flag & MTPmessages_forwardMessages::Flag::f_with_my_score) { to.add("YES [ BY BIT 8 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 8 IN FIELD flags ]"); } break; + case 4: to.add(" from_peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_int+0); stages.push_back(0); flags.push_back(0); break; + case 6: to.add(" random_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(mtpc_long+0); stages.push_back(0); flags.push_back(0); break; + case 7: to.add(" to_peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -7645,6 +7811,27 @@ void _serialize_messages_getAllDrafts(MTPStringLogger &to, int32 stage, int32 le to.add("{ messages_getAllDrafts }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } +void _serialize_messages_setGameScore(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPmessages_setGameScore::Flags flag(iflag); + + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_setGameScore"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" edit_message: "); ++stages.back(); if (flag & MTPmessages_setGameScore::Flag::f_edit_message) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" user_id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 5: to.add(" game_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 6: to.add(" score: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_channels_createChannel(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { MTPchannels_createChannel::Flags flag(iflag); @@ -8006,20 +8193,6 @@ void _serialize_photos_deletePhotos(MTPStringLogger &to, int32 stage, int32 lev, } } -void _serialize_messages_getStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ messages_getStickers"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" emoticon: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - void _serialize_messages_getAllStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -8033,6 +8206,19 @@ void _serialize_messages_getAllStickers(MTPStringLogger &to, int32 stage, int32 } } +void _serialize_messages_getMaskStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_getMaskStickers"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_messages_getWebPagePreview(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { if (stage) { to.add(",\n").addSpaces(lev); @@ -8189,6 +8375,8 @@ void _serialize_messages_getMessageEditData(MTPStringLogger &to, int32 stage, in } void _serialize_messages_getBotCallbackAnswer(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPmessages_getBotCallbackAnswer::Flags flag(iflag); + if (stage) { to.add(",\n").addSpaces(lev); } else { @@ -8196,9 +8384,11 @@ void _serialize_messages_getBotCallbackAnswer(MTPStringLogger &to, int32 stage, to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" msg_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" data: "); ++stages.back(); types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" msg_id: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 3: to.add(" data: "); ++stages.back(); if (flag & MTPmessages_getBotCallbackAnswer::Flag::f_data) { types.push_back(mtpc_bytes+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 4: to.add(" game_id: "); ++stages.back(); if (flag & MTPmessages_getBotCallbackAnswer::Flag::f_game_id) { types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -8230,6 +8420,8 @@ void _serialize_messages_getFeaturedStickers(MTPStringLogger &to, int32 stage, i } void _serialize_messages_getRecentStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + MTPmessages_getRecentStickers::Flags flag(iflag); + if (stage) { to.add(",\n").addSpaces(lev); } else { @@ -8237,20 +8429,9 @@ void _serialize_messages_getRecentStickers(MTPStringLogger &to, int32 stage, int to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - -void _serialize_messages_getUnusedStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { - if (stage) { - to.add(",\n").addSpaces(lev); - } else { - to.add("{ messages_getUnusedStickers"); - to.add("\n").addSpaces(lev); - } - switch (stage) { - case 0: to.add(" limit: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" attached: "); ++stages.back(); if (flag & MTPmessages_getRecentStickers::Flag::f_attached) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 2: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -8269,6 +8450,19 @@ void _serialize_messages_getArchivedStickers(MTPStringLogger &to, int32 stage, i } } +void _serialize_messages_getAttachedStickers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_getAttachedStickers"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" media: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } +} + void _serialize_updates_getState(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) { to.add("{ updates_getState }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } @@ -8313,7 +8507,6 @@ void _serialize_photos_updateProfilePhoto(MTPStringLogger &to, int32 stage, int3 } switch (stage) { case 0: to.add(" id: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" crop: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -8327,9 +8520,6 @@ void _serialize_photos_uploadProfilePhoto(MTPStringLogger &to, int32 stage, int3 } switch (stage) { case 0: to.add(" file: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" caption: "); ++stages.back(); types.push_back(mtpc_string+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" geo_point: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" crop: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } } @@ -8553,8 +8743,6 @@ namespace { _serializers.insert(mtpc_inputFileLocation, _serialize_inputFileLocation); _serializers.insert(mtpc_inputEncryptedFileLocation, _serialize_inputEncryptedFileLocation); _serializers.insert(mtpc_inputDocumentFileLocation, _serialize_inputDocumentFileLocation); - _serializers.insert(mtpc_inputPhotoCropAuto, _serialize_inputPhotoCropAuto); - _serializers.insert(mtpc_inputPhotoCrop, _serialize_inputPhotoCrop); _serializers.insert(mtpc_inputAppEvent, _serialize_inputAppEvent); _serializers.insert(mtpc_peerUser, _serialize_peerUser); _serializers.insert(mtpc_peerChat, _serialize_peerChat); @@ -8619,6 +8807,7 @@ namespace { _serializers.insert(mtpc_messageActionChannelMigrateFrom, _serialize_messageActionChannelMigrateFrom); _serializers.insert(mtpc_messageActionPinMessage, _serialize_messageActionPinMessage); _serializers.insert(mtpc_messageActionHistoryClear, _serialize_messageActionHistoryClear); + _serializers.insert(mtpc_messageActionGameScore, _serialize_messageActionGameScore); _serializers.insert(mtpc_dialog, _serialize_dialog); _serializers.insert(mtpc_photoEmpty, _serialize_photoEmpty); _serializers.insert(mtpc_photo, _serialize_photo); @@ -8815,6 +9004,7 @@ namespace { _serializers.insert(mtpc_documentAttributeVideo, _serialize_documentAttributeVideo); _serializers.insert(mtpc_documentAttributeAudio, _serialize_documentAttributeAudio); _serializers.insert(mtpc_documentAttributeFilename, _serialize_documentAttributeFilename); + _serializers.insert(mtpc_documentAttributeHasStickers, _serialize_documentAttributeHasStickers); _serializers.insert(mtpc_messages_stickersNotModified, _serialize_messages_stickersNotModified); _serializers.insert(mtpc_messages_stickers, _serialize_messages_stickers); _serializers.insert(mtpc_stickerPack, _serialize_stickerPack); @@ -8854,6 +9044,7 @@ namespace { _serializers.insert(mtpc_keyboardButtonRequestPhone, _serialize_keyboardButtonRequestPhone); _serializers.insert(mtpc_keyboardButtonRequestGeoLocation, _serialize_keyboardButtonRequestGeoLocation); _serializers.insert(mtpc_keyboardButtonSwitchInline, _serialize_keyboardButtonSwitchInline); + _serializers.insert(mtpc_keyboardButtonGame, _serialize_keyboardButtonGame); _serializers.insert(mtpc_keyboardButtonRow, _serialize_keyboardButtonRow); _serializers.insert(mtpc_replyKeyboardHide, _serialize_replyKeyboardHide); _serializers.insert(mtpc_replyKeyboardForceReply, _serialize_replyKeyboardForceReply); @@ -8953,6 +9144,10 @@ namespace { _serializers.insert(mtpc_messages_stickerSetInstallResultSuccess, _serialize_messages_stickerSetInstallResultSuccess); _serializers.insert(mtpc_messages_stickerSetInstallResultArchive, _serialize_messages_stickerSetInstallResultArchive); _serializers.insert(mtpc_stickerSetCovered, _serialize_stickerSetCovered); + _serializers.insert(mtpc_stickerSetMultiCovered, _serialize_stickerSetMultiCovered); + _serializers.insert(mtpc_maskCoords, _serialize_maskCoords); + _serializers.insert(mtpc_inputStickeredMediaPhoto, _serialize_inputStickeredMediaPhoto); + _serializers.insert(mtpc_inputStickeredMediaDocument, _serialize_inputStickeredMediaDocument); _serializers.insert(mtpc_req_pq, _serialize_req_pq); _serializers.insert(mtpc_req_DH_params, _serialize_req_DH_params); @@ -8968,6 +9163,7 @@ namespace { _serializers.insert(mtpc_auth_sendInvites, _serialize_auth_sendInvites); _serializers.insert(mtpc_auth_bindTempAuthKey, _serialize_auth_bindTempAuthKey); _serializers.insert(mtpc_auth_cancelCode, _serialize_auth_cancelCode); + _serializers.insert(mtpc_auth_dropTempAuthKeys, _serialize_auth_dropTempAuthKeys); _serializers.insert(mtpc_account_registerDevice, _serialize_account_registerDevice); _serializers.insert(mtpc_account_unregisterDevice, _serialize_account_unregisterDevice); _serializers.insert(mtpc_account_updateNotifySettings, _serialize_account_updateNotifySettings); @@ -9002,6 +9198,7 @@ namespace { _serializers.insert(mtpc_messages_readFeaturedStickers, _serialize_messages_readFeaturedStickers); _serializers.insert(mtpc_messages_saveRecentSticker, _serialize_messages_saveRecentSticker); _serializers.insert(mtpc_messages_clearRecentStickers, _serialize_messages_clearRecentStickers); + _serializers.insert(mtpc_messages_setInlineGameScore, _serialize_messages_setInlineGameScore); _serializers.insert(mtpc_upload_saveFilePart, _serialize_upload_saveFilePart); _serializers.insert(mtpc_upload_saveBigFilePart, _serialize_upload_saveBigFilePart); _serializers.insert(mtpc_help_saveAppLog, _serialize_help_saveAppLog); @@ -9081,6 +9278,7 @@ namespace { _serializers.insert(mtpc_messages_sendInlineBotResult, _serialize_messages_sendInlineBotResult); _serializers.insert(mtpc_messages_editMessage, _serialize_messages_editMessage); _serializers.insert(mtpc_messages_getAllDrafts, _serialize_messages_getAllDrafts); + _serializers.insert(mtpc_messages_setGameScore, _serialize_messages_setGameScore); _serializers.insert(mtpc_channels_createChannel, _serialize_channels_createChannel); _serializers.insert(mtpc_channels_editAdmin, _serialize_channels_editAdmin); _serializers.insert(mtpc_channels_editTitle, _serialize_channels_editTitle); @@ -9107,8 +9305,8 @@ namespace { _serializers.insert(mtpc_messages_sendEncryptedService, _serialize_messages_sendEncryptedService); _serializers.insert(mtpc_messages_receivedQueue, _serialize_messages_receivedQueue); _serializers.insert(mtpc_photos_deletePhotos, _serialize_photos_deletePhotos); - _serializers.insert(mtpc_messages_getStickers, _serialize_messages_getStickers); _serializers.insert(mtpc_messages_getAllStickers, _serialize_messages_getAllStickers); + _serializers.insert(mtpc_messages_getMaskStickers, _serialize_messages_getMaskStickers); _serializers.insert(mtpc_messages_getWebPagePreview, _serialize_messages_getWebPagePreview); _serializers.insert(mtpc_messages_exportChatInvite, _serialize_messages_exportChatInvite); _serializers.insert(mtpc_channels_exportInvite, _serialize_channels_exportInvite); @@ -9124,8 +9322,8 @@ namespace { _serializers.insert(mtpc_messages_getPeerDialogs, _serialize_messages_getPeerDialogs); _serializers.insert(mtpc_messages_getFeaturedStickers, _serialize_messages_getFeaturedStickers); _serializers.insert(mtpc_messages_getRecentStickers, _serialize_messages_getRecentStickers); - _serializers.insert(mtpc_messages_getUnusedStickers, _serialize_messages_getUnusedStickers); _serializers.insert(mtpc_messages_getArchivedStickers, _serialize_messages_getArchivedStickers); + _serializers.insert(mtpc_messages_getAttachedStickers, _serialize_messages_getAttachedStickers); _serializers.insert(mtpc_updates_getState, _serialize_updates_getState); _serializers.insert(mtpc_updates_getDifference, _serialize_updates_getDifference); _serializers.insert(mtpc_updates_getChannelDifference, _serialize_updates_getChannelDifference); diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.h b/Telegram/SourceFiles/mtproto/scheme_auto.h index b4e9ede567..393e133ecd 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.h +++ b/Telegram/SourceFiles/mtproto/scheme_auto.h @@ -30,7 +30,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org namespace MTP { namespace internal { -static constexpr mtpPrime CurrentLayer = 55; +static constexpr mtpPrime CurrentLayer = 56; class TypeCreator; @@ -94,20 +94,20 @@ enum { mtpc_inputFile = 0xf52ff27f, mtpc_inputFileBig = 0xfa4f0bb5, mtpc_inputMediaEmpty = 0x9664f57f, - mtpc_inputMediaUploadedPhoto = 0xf7aff1c0, + mtpc_inputMediaUploadedPhoto = 0x630c9af1, mtpc_inputMediaPhoto = 0xe9bfb4f3, mtpc_inputMediaGeoPoint = 0xf9c44144, mtpc_inputMediaContact = 0xa6e45987, - mtpc_inputMediaUploadedDocument = 0x1d89306d, - mtpc_inputMediaUploadedThumbDocument = 0xad613491, + mtpc_inputMediaUploadedDocument = 0xd070f1e9, + mtpc_inputMediaUploadedThumbDocument = 0x50d88cae, mtpc_inputMediaDocument = 0x1a77f29c, mtpc_inputMediaVenue = 0x2827a81a, mtpc_inputMediaGifExternal = 0x4843b0fd, mtpc_inputMediaPhotoExternal = 0xb55f4f18, mtpc_inputMediaDocumentExternal = 0xe5e9607c, mtpc_inputChatPhotoEmpty = 0x1ca48f57, - mtpc_inputChatUploadedPhoto = 0x94254732, - mtpc_inputChatPhoto = 0xb2e1bf08, + mtpc_inputChatUploadedPhoto = 0x927c55b4, + mtpc_inputChatPhoto = 0x8953ad37, mtpc_inputGeoPointEmpty = 0xe4c123d6, mtpc_inputGeoPoint = 0xf3b7acc9, mtpc_inputPhotoEmpty = 0x1cd7bf0d, @@ -115,8 +115,6 @@ enum { mtpc_inputFileLocation = 0x14637196, mtpc_inputEncryptedFileLocation = 0xf5235d55, mtpc_inputDocumentFileLocation = 0x430f0724, - mtpc_inputPhotoCropAuto = 0xade6b004, - mtpc_inputPhotoCrop = 0xd9915325, mtpc_inputAppEvent = 0x770656a8, mtpc_peerUser = 0x9db1bc6d, mtpc_peerChat = 0xbad0e5bb, @@ -181,9 +179,10 @@ enum { mtpc_messageActionChannelMigrateFrom = 0xb055eaee, mtpc_messageActionPinMessage = 0x94bd38ed, mtpc_messageActionHistoryClear = 0x9fbab604, + mtpc_messageActionGameScore = 0x3a14cfa5, mtpc_dialog = 0x66ffba14, mtpc_photoEmpty = 0x2331b22d, - mtpc_photo = 0xcded42fe, + mtpc_photo = 0x9288dd29, mtpc_photoSizeEmpty = 0xe17e23c, mtpc_photoSize = 0x77bfb61b, mtpc_photoCachedSize = 0xe9a734fa, @@ -278,16 +277,16 @@ enum { mtpc_updateChatAdmins = 0x6e947941, mtpc_updateChatParticipantAdmin = 0xb6901959, mtpc_updateNewStickerSet = 0x688a30aa, - mtpc_updateStickerSetsOrder = 0xf0dfb451, + mtpc_updateStickerSetsOrder = 0xbb2d201, mtpc_updateStickerSets = 0x43ae3dec, mtpc_updateSavedGifs = 0x9375341e, mtpc_updateBotInlineQuery = 0x54826690, mtpc_updateBotInlineSend = 0xe48f964, mtpc_updateEditChannelMessage = 0x1b3f4df7, mtpc_updateChannelPinnedMessage = 0x98592475, - mtpc_updateBotCallbackQuery = 0xa68c688c, + mtpc_updateBotCallbackQuery = 0x81c5615f, mtpc_updateEditMessage = 0xe40370a3, - mtpc_updateInlineBotCallbackQuery = 0x2cbd95af, + mtpc_updateInlineBotCallbackQuery = 0xd618a28b, mtpc_updateReadChannelOutbox = 0x25d6c9c7, mtpc_updateDraftMessage = 0xee2bb969, mtpc_updateReadFeaturedStickers = 0x571d2742, @@ -373,10 +372,11 @@ enum { mtpc_accountDaysTTL = 0xb8d0afdf, mtpc_documentAttributeImageSize = 0x6c37c15c, mtpc_documentAttributeAnimated = 0x11b58939, - mtpc_documentAttributeSticker = 0x3a556302, + mtpc_documentAttributeSticker = 0x6319d612, mtpc_documentAttributeVideo = 0x5910cccb, mtpc_documentAttributeAudio = 0x9852f9c6, mtpc_documentAttributeFilename = 0x15590068, + mtpc_documentAttributeHasStickers = 0x9801d2f7, mtpc_messages_stickersNotModified = 0xf1749a22, mtpc_messages_stickers = 0x8a8ecd32, mtpc_stickerPack = 0x12b299d4, @@ -416,6 +416,7 @@ enum { mtpc_keyboardButtonRequestPhone = 0xb16a6c29, mtpc_keyboardButtonRequestGeoLocation = 0xfc796b3f, mtpc_keyboardButtonSwitchInline = 0x568a748, + mtpc_keyboardButtonGame = 0x28fc3164, mtpc_keyboardButtonRow = 0x77608b83, mtpc_replyKeyboardHide = 0xa03e5b85, mtpc_replyKeyboardForceReply = 0xf4108aa0, @@ -515,6 +516,10 @@ enum { mtpc_messages_stickerSetInstallResultSuccess = 0x38641628, mtpc_messages_stickerSetInstallResultArchive = 0x35e410a8, mtpc_stickerSetCovered = 0x6410a5d2, + mtpc_stickerSetMultiCovered = 0x3407e51b, + mtpc_maskCoords = 0xaed6dbb2, + mtpc_inputStickeredMediaPhoto = 0x4a992157, + mtpc_inputStickeredMediaDocument = 0x438865b, mtpc_invokeAfterMsg = 0xcb9f372d, mtpc_invokeAfterMsgs = 0x3dc4b4f0, mtpc_initConnection = 0x69796de9, @@ -536,6 +541,7 @@ enum { mtpc_auth_recoverPassword = 0x4ea56e92, mtpc_auth_resendCode = 0x3ef1a9bf, mtpc_auth_cancelCode = 0x1f040578, + mtpc_auth_dropTempAuthKeys = 0x8e48a188, mtpc_account_registerDevice = 0x637ea878, mtpc_account_unregisterDevice = 0x65c55b40, mtpc_account_updateNotifySettings = 0x84be5b93, @@ -612,7 +618,6 @@ enum { mtpc_messages_sendEncryptedService = 0x32d439a4, mtpc_messages_receivedQueue = 0x55a5bb66, mtpc_messages_readMessageContents = 0x36a73f77, - mtpc_messages_getStickers = 0xae22e045, mtpc_messages_getAllStickers = 0x1c9618b1, mtpc_messages_getWebPagePreview = 0x25223e24, mtpc_messages_exportChatInvite = 0x7d885289, @@ -627,7 +632,7 @@ enum { mtpc_messages_editChatAdmin = 0xa9e69f2e, mtpc_messages_migrateChat = 0x15a3b8e3, mtpc_messages_searchGlobal = 0x9e3cacb0, - mtpc_messages_reorderStickerSets = 0x9fcfbc30, + mtpc_messages_reorderStickerSets = 0x78337739, mtpc_messages_getDocumentByHash = 0x338e2464, mtpc_messages_searchGifs = 0xbf9a776b, mtpc_messages_getSavedGifs = 0x83bf3d52, @@ -638,23 +643,26 @@ enum { mtpc_messages_getMessageEditData = 0xfda68d36, mtpc_messages_editMessage = 0xce91e4ca, mtpc_messages_editInlineBotMessage = 0x130c2c85, - mtpc_messages_getBotCallbackAnswer = 0xa6e94f04, + mtpc_messages_getBotCallbackAnswer = 0x6c996518, mtpc_messages_setBotCallbackAnswer = 0xc927d44b, mtpc_messages_getPeerDialogs = 0x2d9776b9, mtpc_messages_saveDraft = 0xbc39e14b, mtpc_messages_getAllDrafts = 0x6a3f8d65, mtpc_messages_getFeaturedStickers = 0x2dacca4f, - mtpc_messages_readFeaturedStickers = 0xe21cbb, - mtpc_messages_getRecentStickers = 0x99197c2c, - mtpc_messages_saveRecentSticker = 0x348e39bf, - mtpc_messages_clearRecentStickers = 0xab02e5d2, - mtpc_messages_getUnusedStickers = 0x4309d65b, + mtpc_messages_readFeaturedStickers = 0x5b118126, + mtpc_messages_getRecentStickers = 0x5ea192c9, + mtpc_messages_saveRecentSticker = 0x392718f8, + mtpc_messages_clearRecentStickers = 0x8999602d, mtpc_messages_getArchivedStickers = 0x906e241f, + mtpc_messages_setGameScore = 0xdfbc7c1f, + mtpc_messages_setInlineGameScore = 0x54f882f1, + mtpc_messages_getMaskStickers = 0x65b8c79f, + mtpc_messages_getAttachedStickers = 0xcc5b67cc, mtpc_updates_getState = 0xedd4882a, mtpc_updates_getDifference = 0xa041495, mtpc_updates_getChannelDifference = 0xbb32d7c0, - mtpc_photos_updateProfilePhoto = 0xeef579a0, - mtpc_photos_uploadProfilePhoto = 0xd50f9c88, + mtpc_photos_updateProfilePhoto = 0xf0bb5152, + mtpc_photos_uploadProfilePhoto = 0x4f32c098, mtpc_photos_deletePhotos = 0x87cf7f2f, mtpc_photos_getUserPhotos = 0x91cd32a8, mtpc_upload_saveFilePart = 0xb304a621, @@ -819,9 +827,6 @@ class MTPDinputFileLocation; class MTPDinputEncryptedFileLocation; class MTPDinputDocumentFileLocation; -class MTPinputPhotoCrop; -class MTPDinputPhotoCrop; - class MTPinputAppEvent; class MTPDinputAppEvent; @@ -893,6 +898,7 @@ class MTPDmessageActionChatJoinedByLink; class MTPDmessageActionChannelCreate; class MTPDmessageActionChatMigrateTo; class MTPDmessageActionChannelMigrateFrom; +class MTPDmessageActionGameScore; class MTPdialog; class MTPDdialog; @@ -1233,6 +1239,7 @@ class MTPDkeyboardButtonCallback; class MTPDkeyboardButtonRequestPhone; class MTPDkeyboardButtonRequestGeoLocation; class MTPDkeyboardButtonSwitchInline; +class MTPDkeyboardButtonGame; class MTPkeyboardButtonRow; class MTPDkeyboardButtonRow; @@ -1392,6 +1399,14 @@ class MTPDmessages_stickerSetInstallResultArchive; class MTPstickerSetCovered; class MTPDstickerSetCovered; +class MTPDstickerSetMultiCovered; + +class MTPmaskCoords; +class MTPDmaskCoords; + +class MTPinputStickeredMedia; +class MTPDinputStickeredMediaPhoto; +class MTPDinputStickeredMediaDocument; // Boxed types definitions @@ -1429,7 +1444,6 @@ typedef MTPBoxed MTPInputChatPhoto; typedef MTPBoxed MTPInputGeoPoint; typedef MTPBoxed MTPInputPhoto; typedef MTPBoxed MTPInputFileLocation; -typedef MTPBoxed MTPInputPhotoCrop; typedef MTPBoxed MTPInputAppEvent; typedef MTPBoxed MTPPeer; typedef MTPBoxed MTPstorage_FileType; @@ -1572,6 +1586,8 @@ typedef MTPBoxed MTPmessages_RecentStickers; typedef MTPBoxed MTPmessages_ArchivedStickers; typedef MTPBoxed MTPmessages_StickerSetInstallResult; typedef MTPBoxed MTPStickerSetCovered; +typedef MTPBoxed MTPMaskCoords; +typedef MTPBoxed MTPInputStickeredMedia; // Type classes definitions @@ -2972,43 +2988,6 @@ private: }; typedef MTPBoxed MTPInputFileLocation; -class MTPinputPhotoCrop : private mtpDataOwner { -public: - MTPinputPhotoCrop() : mtpDataOwner(0), _type(0) { - } - MTPinputPhotoCrop(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) { - read(from, end, cons); - } - - MTPDinputPhotoCrop &_inputPhotoCrop() { - if (!data) throw mtpErrorUninitialized(); - if (_type != mtpc_inputPhotoCrop) throw mtpErrorWrongTypeId(_type, mtpc_inputPhotoCrop); - split(); - return *(MTPDinputPhotoCrop*)data; - } - const MTPDinputPhotoCrop &c_inputPhotoCrop() const { - if (!data) throw mtpErrorUninitialized(); - if (_type != mtpc_inputPhotoCrop) throw mtpErrorWrongTypeId(_type, mtpc_inputPhotoCrop); - return *(const MTPDinputPhotoCrop*)data; - } - - uint32 innerLength() const; - mtpTypeId type() const; - void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); - void write(mtpBuffer &to) const; - - typedef void ResponseType; - -private: - explicit MTPinputPhotoCrop(mtpTypeId type); - explicit MTPinputPhotoCrop(MTPDinputPhotoCrop *_data); - - friend class MTP::internal::TypeCreator; - - mtpTypeId _type; -}; -typedef MTPBoxed MTPInputPhotoCrop; - class MTPinputAppEvent : private mtpDataOwner { public: MTPinputAppEvent(); @@ -3884,6 +3863,18 @@ public: return *(const MTPDmessageActionChannelMigrateFrom*)data; } + MTPDmessageActionGameScore &_messageActionGameScore() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_messageActionGameScore) throw mtpErrorWrongTypeId(_type, mtpc_messageActionGameScore); + split(); + return *(MTPDmessageActionGameScore*)data; + } + const MTPDmessageActionGameScore &c_messageActionGameScore() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_messageActionGameScore) throw mtpErrorWrongTypeId(_type, mtpc_messageActionGameScore); + return *(const MTPDmessageActionGameScore*)data; + } + uint32 innerLength() const; mtpTypeId type() const; void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); @@ -3902,6 +3893,7 @@ private: explicit MTPmessageAction(MTPDmessageActionChannelCreate *_data); explicit MTPmessageAction(MTPDmessageActionChatMigrateTo *_data); explicit MTPmessageAction(MTPDmessageActionChannelMigrateFrom *_data); + explicit MTPmessageAction(MTPDmessageActionGameScore *_data); friend class MTP::internal::TypeCreator; @@ -7885,6 +7877,18 @@ public: return *(const MTPDkeyboardButtonSwitchInline*)data; } + MTPDkeyboardButtonGame &_keyboardButtonGame() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_keyboardButtonGame) throw mtpErrorWrongTypeId(_type, mtpc_keyboardButtonGame); + split(); + return *(MTPDkeyboardButtonGame*)data; + } + const MTPDkeyboardButtonGame &c_keyboardButtonGame() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_keyboardButtonGame) throw mtpErrorWrongTypeId(_type, mtpc_keyboardButtonGame); + return *(const MTPDkeyboardButtonGame*)data; + } + uint32 innerLength() const; mtpTypeId type() const; void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); @@ -7900,6 +7904,7 @@ private: explicit MTPkeyboardButton(MTPDkeyboardButtonRequestPhone *_data); explicit MTPkeyboardButton(MTPDkeyboardButtonRequestGeoLocation *_data); explicit MTPkeyboardButton(MTPDkeyboardButtonSwitchInline *_data); + explicit MTPkeyboardButton(MTPDkeyboardButtonGame *_data); friend class MTP::internal::TypeCreator; @@ -9747,34 +9752,134 @@ typedef MTPBoxed MTPmessages_StickerSetInst class MTPstickerSetCovered : private mtpDataOwner { public: - MTPstickerSetCovered(); - MTPstickerSetCovered(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_stickerSetCovered) : mtpDataOwner(0) { + MTPstickerSetCovered() : mtpDataOwner(0), _type(0) { + } + MTPstickerSetCovered(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) { read(from, end, cons); } MTPDstickerSetCovered &_stickerSetCovered() { if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_stickerSetCovered) throw mtpErrorWrongTypeId(_type, mtpc_stickerSetCovered); split(); return *(MTPDstickerSetCovered*)data; } const MTPDstickerSetCovered &c_stickerSetCovered() const { if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_stickerSetCovered) throw mtpErrorWrongTypeId(_type, mtpc_stickerSetCovered); return *(const MTPDstickerSetCovered*)data; } + MTPDstickerSetMultiCovered &_stickerSetMultiCovered() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_stickerSetMultiCovered) throw mtpErrorWrongTypeId(_type, mtpc_stickerSetMultiCovered); + split(); + return *(MTPDstickerSetMultiCovered*)data; + } + const MTPDstickerSetMultiCovered &c_stickerSetMultiCovered() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_stickerSetMultiCovered) throw mtpErrorWrongTypeId(_type, mtpc_stickerSetMultiCovered); + return *(const MTPDstickerSetMultiCovered*)data; + } + uint32 innerLength() const; mtpTypeId type() const; - void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_stickerSetCovered); + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); void write(mtpBuffer &to) const; typedef void ResponseType; private: + explicit MTPstickerSetCovered(mtpTypeId type); explicit MTPstickerSetCovered(MTPDstickerSetCovered *_data); + explicit MTPstickerSetCovered(MTPDstickerSetMultiCovered *_data); + + friend class MTP::internal::TypeCreator; + + mtpTypeId _type; +}; +typedef MTPBoxed MTPStickerSetCovered; + +class MTPmaskCoords : private mtpDataOwner { +public: + MTPmaskCoords(); + MTPmaskCoords(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_maskCoords) : mtpDataOwner(0) { + read(from, end, cons); + } + + MTPDmaskCoords &_maskCoords() { + if (!data) throw mtpErrorUninitialized(); + split(); + return *(MTPDmaskCoords*)data; + } + const MTPDmaskCoords &c_maskCoords() const { + if (!data) throw mtpErrorUninitialized(); + return *(const MTPDmaskCoords*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_maskCoords); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPmaskCoords(MTPDmaskCoords *_data); friend class MTP::internal::TypeCreator; }; -typedef MTPBoxed MTPStickerSetCovered; +typedef MTPBoxed MTPMaskCoords; + +class MTPinputStickeredMedia : private mtpDataOwner { +public: + MTPinputStickeredMedia() : mtpDataOwner(0), _type(0) { + } + MTPinputStickeredMedia(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) { + read(from, end, cons); + } + + MTPDinputStickeredMediaPhoto &_inputStickeredMediaPhoto() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_inputStickeredMediaPhoto) throw mtpErrorWrongTypeId(_type, mtpc_inputStickeredMediaPhoto); + split(); + return *(MTPDinputStickeredMediaPhoto*)data; + } + const MTPDinputStickeredMediaPhoto &c_inputStickeredMediaPhoto() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_inputStickeredMediaPhoto) throw mtpErrorWrongTypeId(_type, mtpc_inputStickeredMediaPhoto); + return *(const MTPDinputStickeredMediaPhoto*)data; + } + + MTPDinputStickeredMediaDocument &_inputStickeredMediaDocument() { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_inputStickeredMediaDocument) throw mtpErrorWrongTypeId(_type, mtpc_inputStickeredMediaDocument); + split(); + return *(MTPDinputStickeredMediaDocument*)data; + } + const MTPDinputStickeredMediaDocument &c_inputStickeredMediaDocument() const { + if (!data) throw mtpErrorUninitialized(); + if (_type != mtpc_inputStickeredMediaDocument) throw mtpErrorWrongTypeId(_type, mtpc_inputStickeredMediaDocument); + return *(const MTPDinputStickeredMediaDocument*)data; + } + + uint32 innerLength() const; + mtpTypeId type() const; + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons); + void write(mtpBuffer &to) const; + + typedef void ResponseType; + +private: + explicit MTPinputStickeredMedia(mtpTypeId type); + explicit MTPinputStickeredMedia(MTPDinputStickeredMediaPhoto *_data); + explicit MTPinputStickeredMedia(MTPDinputStickeredMediaDocument *_data); + + friend class MTP::internal::TypeCreator; + + mtpTypeId _type; +}; +typedef MTPBoxed MTPInputStickeredMedia; // Type constructors with data @@ -10192,13 +10297,24 @@ public: class MTPDinputMediaUploadedPhoto : public mtpDataImpl { public: + enum class Flag : int32 { + f_stickers = (1 << 0), + MAX_FIELD = (1 << 0), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool has_stickers() const { return vflags.v & Flag::f_stickers; } + MTPDinputMediaUploadedPhoto() { } - MTPDinputMediaUploadedPhoto(const MTPInputFile &_file, const MTPstring &_caption) : vfile(_file), vcaption(_caption) { + MTPDinputMediaUploadedPhoto(const MTPflags &_flags, const MTPInputFile &_file, const MTPstring &_caption, const MTPVector &_stickers) : vflags(_flags), vfile(_file), vcaption(_caption), vstickers(_stickers) { } + MTPflags vflags; MTPInputFile vfile; MTPstring vcaption; + MTPVector vstickers; }; class MTPDinputMediaPhoto : public mtpDataImpl { @@ -10236,29 +10352,51 @@ public: class MTPDinputMediaUploadedDocument : public mtpDataImpl { public: + enum class Flag : int32 { + f_stickers = (1 << 0), + MAX_FIELD = (1 << 0), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool has_stickers() const { return vflags.v & Flag::f_stickers; } + MTPDinputMediaUploadedDocument() { } - MTPDinputMediaUploadedDocument(const MTPInputFile &_file, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption) : vfile(_file), vmime_type(_mime_type), vattributes(_attributes), vcaption(_caption) { + MTPDinputMediaUploadedDocument(const MTPflags &_flags, const MTPInputFile &_file, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption, const MTPVector &_stickers) : vflags(_flags), vfile(_file), vmime_type(_mime_type), vattributes(_attributes), vcaption(_caption), vstickers(_stickers) { } + MTPflags vflags; MTPInputFile vfile; MTPstring vmime_type; MTPVector vattributes; MTPstring vcaption; + MTPVector vstickers; }; class MTPDinputMediaUploadedThumbDocument : public mtpDataImpl { public: + enum class Flag : int32 { + f_stickers = (1 << 0), + MAX_FIELD = (1 << 0), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool has_stickers() const { return vflags.v & Flag::f_stickers; } + MTPDinputMediaUploadedThumbDocument() { } - MTPDinputMediaUploadedThumbDocument(const MTPInputFile &_file, const MTPInputFile &_thumb, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption) : vfile(_file), vthumb(_thumb), vmime_type(_mime_type), vattributes(_attributes), vcaption(_caption) { + MTPDinputMediaUploadedThumbDocument(const MTPflags &_flags, const MTPInputFile &_file, const MTPInputFile &_thumb, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption, const MTPVector &_stickers) : vflags(_flags), vfile(_file), vthumb(_thumb), vmime_type(_mime_type), vattributes(_attributes), vcaption(_caption), vstickers(_stickers) { } + MTPflags vflags; MTPInputFile vfile; MTPInputFile vthumb; MTPstring vmime_type; MTPVector vattributes; MTPstring vcaption; + MTPVector vstickers; }; class MTPDinputMediaDocument : public mtpDataImpl { @@ -10323,22 +10461,20 @@ class MTPDinputChatUploadedPhoto : public mtpDataImpl { public: MTPDinputChatPhoto() { } - MTPDinputChatPhoto(const MTPInputPhoto &_id, const MTPInputPhotoCrop &_crop) : vid(_id), vcrop(_crop) { + MTPDinputChatPhoto(const MTPInputPhoto &_id) : vid(_id) { } MTPInputPhoto vid; - MTPInputPhotoCrop vcrop; }; class MTPDinputGeoPoint : public mtpDataImpl { @@ -10398,18 +10534,6 @@ public: MTPint vversion; }; -class MTPDinputPhotoCrop : public mtpDataImpl { -public: - MTPDinputPhotoCrop() { - } - MTPDinputPhotoCrop(const MTPdouble &_crop_left, const MTPdouble &_crop_top, const MTPdouble &_crop_width) : vcrop_left(_crop_left), vcrop_top(_crop_top), vcrop_width(_crop_width) { - } - - MTPdouble vcrop_left; - MTPdouble vcrop_top; - MTPdouble vcrop_width; -}; - class MTPDinputAppEvent : public mtpDataImpl { public: MTPDinputAppEvent() { @@ -11142,6 +11266,17 @@ public: MTPint vchat_id; }; +class MTPDmessageActionGameScore : public mtpDataImpl { +public: + MTPDmessageActionGameScore() { + } + MTPDmessageActionGameScore(MTPint _game_id, MTPint _score) : vgame_id(_game_id), vscore(_score) { + } + + MTPint vgame_id; + MTPint vscore; +}; + class MTPDdialog : public mtpDataImpl { public: enum class Flag : int32 { @@ -11184,11 +11319,21 @@ public: class MTPDphoto : public mtpDataImpl { public: + enum class Flag : int32 { + f_has_stickers = (1 << 0), + MAX_FIELD = (1 << 0), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool is_has_stickers() const { return vflags.v & Flag::f_has_stickers; } + MTPDphoto() { } - MTPDphoto(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, const MTPVector &_sizes) : vid(_id), vaccess_hash(_access_hash), vdate(_date), vsizes(_sizes) { + MTPDphoto(const MTPflags &_flags, const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, const MTPVector &_sizes) : vflags(_flags), vid(_id), vaccess_hash(_access_hash), vdate(_date), vsizes(_sizes) { } + MTPflags vflags; MTPlong vid; MTPlong vaccess_hash; MTPint vdate; @@ -12112,11 +12257,21 @@ public: class MTPDupdateStickerSetsOrder : public mtpDataImpl { public: + enum class Flag : int32 { + f_masks = (1 << 0), + MAX_FIELD = (1 << 0), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool is_masks() const { return vflags.v & Flag::f_masks; } + MTPDupdateStickerSetsOrder() { } - MTPDupdateStickerSetsOrder(const MTPVector &_order) : vorder(_order) { + MTPDupdateStickerSetsOrder(const MTPflags &_flags, const MTPVector &_order) : vflags(_flags), vorder(_order) { } + MTPflags vflags; MTPVector vorder; }; @@ -12196,16 +12351,30 @@ public: class MTPDupdateBotCallbackQuery : public mtpDataImpl { public: + enum class Flag : int32 { + f_data = (1 << 0), + f_game_id = (1 << 1), + + MAX_FIELD = (1 << 1), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool has_data() const { return vflags.v & Flag::f_data; } + bool has_game_id() const { return vflags.v & Flag::f_game_id; } + MTPDupdateBotCallbackQuery() { } - MTPDupdateBotCallbackQuery(const MTPlong &_query_id, MTPint _user_id, const MTPPeer &_peer, MTPint _msg_id, const MTPbytes &_data) : vquery_id(_query_id), vuser_id(_user_id), vpeer(_peer), vmsg_id(_msg_id), vdata(_data) { + MTPDupdateBotCallbackQuery(const MTPflags &_flags, const MTPlong &_query_id, MTPint _user_id, const MTPPeer &_peer, MTPint _msg_id, const MTPbytes &_data, MTPint _game_id) : vflags(_flags), vquery_id(_query_id), vuser_id(_user_id), vpeer(_peer), vmsg_id(_msg_id), vdata(_data), vgame_id(_game_id) { } + MTPflags vflags; MTPlong vquery_id; MTPint vuser_id; MTPPeer vpeer; MTPint vmsg_id; MTPbytes vdata; + MTPint vgame_id; }; class MTPDupdateEditMessage : public mtpDataImpl { @@ -12222,15 +12391,29 @@ public: class MTPDupdateInlineBotCallbackQuery : public mtpDataImpl { public: + enum class Flag : int32 { + f_data = (1 << 0), + f_game_id = (1 << 1), + + MAX_FIELD = (1 << 1), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool has_data() const { return vflags.v & Flag::f_data; } + bool has_game_id() const { return vflags.v & Flag::f_game_id; } + MTPDupdateInlineBotCallbackQuery() { } - MTPDupdateInlineBotCallbackQuery(const MTPlong &_query_id, MTPint _user_id, const MTPInputBotInlineMessageID &_msg_id, const MTPbytes &_data) : vquery_id(_query_id), vuser_id(_user_id), vmsg_id(_msg_id), vdata(_data) { + MTPDupdateInlineBotCallbackQuery(const MTPflags &_flags, const MTPlong &_query_id, MTPint _user_id, const MTPInputBotInlineMessageID &_msg_id, const MTPbytes &_data, MTPint _game_id) : vflags(_flags), vquery_id(_query_id), vuser_id(_user_id), vmsg_id(_msg_id), vdata(_data), vgame_id(_game_id) { } + MTPflags vflags; MTPlong vquery_id; MTPint vuser_id; MTPInputBotInlineMessageID vmsg_id; MTPbytes vdata; + MTPint vgame_id; }; class MTPDupdateReadChannelOutbox : public mtpDataImpl { @@ -13002,13 +13185,27 @@ public: class MTPDdocumentAttributeSticker : public mtpDataImpl { public: + enum class Flag : int32 { + f_mask = (1 << 1), + f_mask_coords = (1 << 0), + + MAX_FIELD = (1 << 1), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool is_mask() const { return vflags.v & Flag::f_mask; } + bool has_mask_coords() const { return vflags.v & Flag::f_mask_coords; } + MTPDdocumentAttributeSticker() { } - MTPDdocumentAttributeSticker(const MTPstring &_alt, const MTPInputStickerSet &_stickerset) : valt(_alt), vstickerset(_stickerset) { + MTPDdocumentAttributeSticker(const MTPflags &_flags, const MTPstring &_alt, const MTPInputStickerSet &_stickerset, const MTPMaskCoords &_mask_coords) : vflags(_flags), valt(_alt), vstickerset(_stickerset), vmask_coords(_mask_coords) { } + MTPflags vflags; MTPstring valt; MTPInputStickerSet vstickerset; + MTPMaskCoords vmask_coords; }; class MTPDdocumentAttributeVideo : public mtpDataImpl { @@ -13393,8 +13590,9 @@ public: f_installed = (1 << 0), f_archived = (1 << 1), f_official = (1 << 2), + f_masks = (1 << 3), - MAX_FIELD = (1 << 2), + MAX_FIELD = (1 << 3), }; Q_DECLARE_FLAGS(Flags, Flag); friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } @@ -13402,6 +13600,7 @@ public: bool is_installed() const { return vflags.v & Flag::f_installed; } bool is_archived() const { return vflags.v & Flag::f_archived; } bool is_official() const { return vflags.v & Flag::f_official; } + bool is_masks() const { return vflags.v & Flag::f_masks; } MTPDstickerSet() { } @@ -13525,6 +13724,19 @@ public: MTPstring vquery; }; +class MTPDkeyboardButtonGame : public mtpDataImpl { +public: + MTPDkeyboardButtonGame() { + } + MTPDkeyboardButtonGame(const MTPstring &_text, const MTPstring &_game_title, MTPint _game_id, const MTPstring &_start_param) : vtext(_text), vgame_title(_game_title), vgame_id(_game_id), vstart_param(_start_param) { + } + + MTPstring vtext; + MTPstring vgame_title; + MTPint vgame_id; + MTPstring vstart_param; +}; + class MTPDkeyboardButtonRow : public mtpDataImpl { public: MTPDkeyboardButtonRow() { @@ -14778,6 +14990,50 @@ public: MTPDocument vcover; }; +class MTPDstickerSetMultiCovered : public mtpDataImpl { +public: + MTPDstickerSetMultiCovered() { + } + MTPDstickerSetMultiCovered(const MTPStickerSet &_set, const MTPVector &_covers) : vset(_set), vcovers(_covers) { + } + + MTPStickerSet vset; + MTPVector vcovers; +}; + +class MTPDmaskCoords : public mtpDataImpl { +public: + MTPDmaskCoords() { + } + MTPDmaskCoords(MTPint _n, const MTPdouble &_x, const MTPdouble &_y, const MTPdouble &_zoom) : vn(_n), vx(_x), vy(_y), vzoom(_zoom) { + } + + MTPint vn; + MTPdouble vx; + MTPdouble vy; + MTPdouble vzoom; +}; + +class MTPDinputStickeredMediaPhoto : public mtpDataImpl { +public: + MTPDinputStickeredMediaPhoto() { + } + MTPDinputStickeredMediaPhoto(const MTPInputPhoto &_id) : vid(_id) { + } + + MTPInputPhoto vid; +}; + +class MTPDinputStickeredMediaDocument : public mtpDataImpl { +public: + MTPDinputStickeredMediaDocument() { + } + MTPDinputStickeredMediaDocument(const MTPInputDocument &_id) : vid(_id) { + } + + MTPInputDocument vid; +}; + // RPC methods class MTPreq_pq { // RPC method 'req_pq' @@ -16059,6 +16315,45 @@ public: } }; +class MTPauth_dropTempAuthKeys { // RPC method 'auth.dropTempAuthKeys' +public: + MTPVector vexcept_auth_keys; + + MTPauth_dropTempAuthKeys() { + } + MTPauth_dropTempAuthKeys(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_auth_dropTempAuthKeys) { + read(from, end, cons); + } + MTPauth_dropTempAuthKeys(const MTPVector &_except_auth_keys) : vexcept_auth_keys(_except_auth_keys) { + } + + uint32 innerLength() const { + return vexcept_auth_keys.innerLength(); + } + mtpTypeId type() const { + return mtpc_auth_dropTempAuthKeys; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_auth_dropTempAuthKeys) { + vexcept_auth_keys.read(from, end); + } + void write(mtpBuffer &to) const { + vexcept_auth_keys.write(to); + } + + typedef MTPBool ResponseType; +}; +class MTPauth_DropTempAuthKeys : public MTPBoxed { +public: + MTPauth_DropTempAuthKeys() { + } + MTPauth_DropTempAuthKeys(const MTPauth_dropTempAuthKeys &v) : MTPBoxed(v) { + } + MTPauth_DropTempAuthKeys(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPauth_DropTempAuthKeys(const MTPVector &_except_auth_keys) : MTPBoxed(MTPauth_dropTempAuthKeys(_except_auth_keys)) { + } +}; + class MTPaccount_registerDevice { // RPC method 'account.registerDevice' public: MTPint vtoken_type; @@ -18363,14 +18658,16 @@ public: enum class Flag : int32 { f_silent = (1 << 5), f_background = (1 << 6), + f_with_my_score = (1 << 8), - MAX_FIELD = (1 << 6), + MAX_FIELD = (1 << 8), }; Q_DECLARE_FLAGS(Flags, Flag); friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } bool is_silent() const { return vflags.v & Flag::f_silent; } bool is_background() const { return vflags.v & Flag::f_background; } + bool is_with_my_score() const { return vflags.v & Flag::f_with_my_score; } MTPflags vflags; MTPInputPeer vfrom_peer; @@ -19347,48 +19644,6 @@ public: } }; -class MTPmessages_getStickers { // RPC method 'messages.getStickers' -public: - MTPstring vemoticon; - MTPstring vhash; - - MTPmessages_getStickers() { - } - MTPmessages_getStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getStickers) { - read(from, end, cons); - } - MTPmessages_getStickers(const MTPstring &_emoticon, const MTPstring &_hash) : vemoticon(_emoticon), vhash(_hash) { - } - - uint32 innerLength() const { - return vemoticon.innerLength() + vhash.innerLength(); - } - mtpTypeId type() const { - return mtpc_messages_getStickers; - } - void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getStickers) { - vemoticon.read(from, end); - vhash.read(from, end); - } - void write(mtpBuffer &to) const { - vemoticon.write(to); - vhash.write(to); - } - - typedef MTPmessages_Stickers ResponseType; -}; -class MTPmessages_GetStickers : public MTPBoxed { -public: - MTPmessages_GetStickers() { - } - MTPmessages_GetStickers(const MTPmessages_getStickers &v) : MTPBoxed(v) { - } - MTPmessages_GetStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { - } - MTPmessages_GetStickers(const MTPstring &_emoticon, const MTPstring &_hash) : MTPBoxed(MTPmessages_getStickers(_emoticon, _hash)) { - } -}; - class MTPmessages_getAllStickers { // RPC method 'messages.getAllStickers' public: MTPint vhash; @@ -19976,6 +20231,16 @@ public: class MTPmessages_reorderStickerSets { // RPC method 'messages.reorderStickerSets' public: + enum class Flag : int32 { + f_masks = (1 << 0), + MAX_FIELD = (1 << 0), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool is_masks() const { return vflags.v & Flag::f_masks; } + + MTPflags vflags; MTPVector vorder; MTPmessages_reorderStickerSets() { @@ -19983,24 +20248,28 @@ public: MTPmessages_reorderStickerSets(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_reorderStickerSets) { read(from, end, cons); } - MTPmessages_reorderStickerSets(const MTPVector &_order) : vorder(_order) { + MTPmessages_reorderStickerSets(const MTPflags &_flags, const MTPVector &_order) : vflags(_flags), vorder(_order) { } uint32 innerLength() const { - return vorder.innerLength(); + return vflags.innerLength() + vorder.innerLength(); } mtpTypeId type() const { return mtpc_messages_reorderStickerSets; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_reorderStickerSets) { + vflags.read(from, end); vorder.read(from, end); } void write(mtpBuffer &to) const { + vflags.write(to); vorder.write(to); } typedef MTPBool ResponseType; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPmessages_reorderStickerSets::Flags) + class MTPmessages_ReorderStickerSets : public MTPBoxed { public: MTPmessages_ReorderStickerSets() { @@ -20009,7 +20278,7 @@ public: } MTPmessages_ReorderStickerSets(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } - MTPmessages_ReorderStickerSets(const MTPVector &_order) : MTPBoxed(MTPmessages_reorderStickerSets(_order)) { + MTPmessages_ReorderStickerSets(const MTPflags &_flags, const MTPVector &_order) : MTPBoxed(MTPmessages_reorderStickerSets(_flags, _order)) { } }; @@ -20575,37 +20844,57 @@ public: class MTPmessages_getBotCallbackAnswer { // RPC method 'messages.getBotCallbackAnswer' public: + enum class Flag : int32 { + f_data = (1 << 0), + f_game_id = (1 << 1), + + MAX_FIELD = (1 << 1), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool has_data() const { return vflags.v & Flag::f_data; } + bool has_game_id() const { return vflags.v & Flag::f_game_id; } + + MTPflags vflags; MTPInputPeer vpeer; MTPint vmsg_id; MTPbytes vdata; + MTPint vgame_id; MTPmessages_getBotCallbackAnswer() { } MTPmessages_getBotCallbackAnswer(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getBotCallbackAnswer) { read(from, end, cons); } - MTPmessages_getBotCallbackAnswer(const MTPInputPeer &_peer, MTPint _msg_id, const MTPbytes &_data) : vpeer(_peer), vmsg_id(_msg_id), vdata(_data) { + MTPmessages_getBotCallbackAnswer(const MTPflags &_flags, const MTPInputPeer &_peer, MTPint _msg_id, const MTPbytes &_data, MTPint _game_id) : vflags(_flags), vpeer(_peer), vmsg_id(_msg_id), vdata(_data), vgame_id(_game_id) { } uint32 innerLength() const { - return vpeer.innerLength() + vmsg_id.innerLength() + vdata.innerLength(); + return vflags.innerLength() + vpeer.innerLength() + vmsg_id.innerLength() + (has_data() ? vdata.innerLength() : 0) + (has_game_id() ? vgame_id.innerLength() : 0); } mtpTypeId type() const { return mtpc_messages_getBotCallbackAnswer; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getBotCallbackAnswer) { + vflags.read(from, end); vpeer.read(from, end); vmsg_id.read(from, end); - vdata.read(from, end); + if (has_data()) { vdata.read(from, end); } else { vdata = MTPbytes(); } + if (has_game_id()) { vgame_id.read(from, end); } else { vgame_id = MTPint(); } } void write(mtpBuffer &to) const { + vflags.write(to); vpeer.write(to); vmsg_id.write(to); - vdata.write(to); + if (has_data()) vdata.write(to); + if (has_game_id()) vgame_id.write(to); } typedef MTPmessages_BotCallbackAnswer ResponseType; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPmessages_getBotCallbackAnswer::Flags) + class MTPmessages_GetBotCallbackAnswer : public MTPBoxed { public: MTPmessages_GetBotCallbackAnswer() { @@ -20614,7 +20903,7 @@ public: } MTPmessages_GetBotCallbackAnswer(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } - MTPmessages_GetBotCallbackAnswer(const MTPInputPeer &_peer, MTPint _msg_id, const MTPbytes &_data) : MTPBoxed(MTPmessages_getBotCallbackAnswer(_peer, _msg_id, _data)) { + MTPmessages_GetBotCallbackAnswer(const MTPflags &_flags, const MTPInputPeer &_peer, MTPint _msg_id, const MTPbytes &_data, MTPint _game_id) : MTPBoxed(MTPmessages_getBotCallbackAnswer(_flags, _peer, _msg_id, _data, _game_id)) { } }; @@ -20860,21 +21149,27 @@ public: class MTPmessages_readFeaturedStickers { // RPC method 'messages.readFeaturedStickers' public: + MTPVector vid; + MTPmessages_readFeaturedStickers() { } MTPmessages_readFeaturedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_readFeaturedStickers) { read(from, end, cons); } + MTPmessages_readFeaturedStickers(const MTPVector &_id) : vid(_id) { + } uint32 innerLength() const { - return 0; + return vid.innerLength(); } mtpTypeId type() const { return mtpc_messages_readFeaturedStickers; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_readFeaturedStickers) { + vid.read(from, end); } void write(mtpBuffer &to) const { + vid.write(to); } typedef MTPBool ResponseType; @@ -20887,10 +21182,22 @@ public: } MTPmessages_ReadFeaturedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } + MTPmessages_ReadFeaturedStickers(const MTPVector &_id) : MTPBoxed(MTPmessages_readFeaturedStickers(_id)) { + } }; class MTPmessages_getRecentStickers { // RPC method 'messages.getRecentStickers' public: + enum class Flag : int32 { + f_attached = (1 << 0), + MAX_FIELD = (1 << 0), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool is_attached() const { return vflags.v & Flag::f_attached; } + + MTPflags vflags; MTPint vhash; MTPmessages_getRecentStickers() { @@ -20898,24 +21205,28 @@ public: MTPmessages_getRecentStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getRecentStickers) { read(from, end, cons); } - MTPmessages_getRecentStickers(MTPint _hash) : vhash(_hash) { + MTPmessages_getRecentStickers(const MTPflags &_flags, MTPint _hash) : vflags(_flags), vhash(_hash) { } uint32 innerLength() const { - return vhash.innerLength(); + return vflags.innerLength() + vhash.innerLength(); } mtpTypeId type() const { return mtpc_messages_getRecentStickers; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getRecentStickers) { + vflags.read(from, end); vhash.read(from, end); } void write(mtpBuffer &to) const { + vflags.write(to); vhash.write(to); } typedef MTPmessages_RecentStickers ResponseType; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPmessages_getRecentStickers::Flags) + class MTPmessages_GetRecentStickers : public MTPBoxed { public: MTPmessages_GetRecentStickers() { @@ -20924,12 +21235,22 @@ public: } MTPmessages_GetRecentStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } - MTPmessages_GetRecentStickers(MTPint _hash) : MTPBoxed(MTPmessages_getRecentStickers(_hash)) { + MTPmessages_GetRecentStickers(const MTPflags &_flags, MTPint _hash) : MTPBoxed(MTPmessages_getRecentStickers(_flags, _hash)) { } }; class MTPmessages_saveRecentSticker { // RPC method 'messages.saveRecentSticker' public: + enum class Flag : int32 { + f_attached = (1 << 0), + MAX_FIELD = (1 << 0), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool is_attached() const { return vflags.v & Flag::f_attached; } + + MTPflags vflags; MTPInputDocument vid; MTPBool vunsave; @@ -20938,26 +21259,30 @@ public: MTPmessages_saveRecentSticker(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_saveRecentSticker) { read(from, end, cons); } - MTPmessages_saveRecentSticker(const MTPInputDocument &_id, MTPBool _unsave) : vid(_id), vunsave(_unsave) { + MTPmessages_saveRecentSticker(const MTPflags &_flags, const MTPInputDocument &_id, MTPBool _unsave) : vflags(_flags), vid(_id), vunsave(_unsave) { } uint32 innerLength() const { - return vid.innerLength() + vunsave.innerLength(); + return vflags.innerLength() + vid.innerLength() + vunsave.innerLength(); } mtpTypeId type() const { return mtpc_messages_saveRecentSticker; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_saveRecentSticker) { + vflags.read(from, end); vid.read(from, end); vunsave.read(from, end); } void write(mtpBuffer &to) const { + vflags.write(to); vid.write(to); vunsave.write(to); } typedef MTPBool ResponseType; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPmessages_saveRecentSticker::Flags) + class MTPmessages_SaveRecentSticker : public MTPBoxed { public: MTPmessages_SaveRecentSticker() { @@ -20966,31 +21291,48 @@ public: } MTPmessages_SaveRecentSticker(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } - MTPmessages_SaveRecentSticker(const MTPInputDocument &_id, MTPBool _unsave) : MTPBoxed(MTPmessages_saveRecentSticker(_id, _unsave)) { + MTPmessages_SaveRecentSticker(const MTPflags &_flags, const MTPInputDocument &_id, MTPBool _unsave) : MTPBoxed(MTPmessages_saveRecentSticker(_flags, _id, _unsave)) { } }; class MTPmessages_clearRecentStickers { // RPC method 'messages.clearRecentStickers' public: + enum class Flag : int32 { + f_attached = (1 << 0), + MAX_FIELD = (1 << 0), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool is_attached() const { return vflags.v & Flag::f_attached; } + + MTPflags vflags; + MTPmessages_clearRecentStickers() { } MTPmessages_clearRecentStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_clearRecentStickers) { read(from, end, cons); } + MTPmessages_clearRecentStickers(const MTPflags &_flags) : vflags(_flags) { + } uint32 innerLength() const { - return 0; + return vflags.innerLength(); } mtpTypeId type() const { return mtpc_messages_clearRecentStickers; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_clearRecentStickers) { + vflags.read(from, end); } void write(mtpBuffer &to) const { + vflags.write(to); } typedef MTPBool ResponseType; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPmessages_clearRecentStickers::Flags) + class MTPmessages_ClearRecentStickers : public MTPBoxed { public: MTPmessages_ClearRecentStickers() { @@ -20999,44 +21341,7 @@ public: } MTPmessages_ClearRecentStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } -}; - -class MTPmessages_getUnusedStickers { // RPC method 'messages.getUnusedStickers' -public: - MTPint vlimit; - - MTPmessages_getUnusedStickers() { - } - MTPmessages_getUnusedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getUnusedStickers) { - read(from, end, cons); - } - MTPmessages_getUnusedStickers(MTPint _limit) : vlimit(_limit) { - } - - uint32 innerLength() const { - return vlimit.innerLength(); - } - mtpTypeId type() const { - return mtpc_messages_getUnusedStickers; - } - void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getUnusedStickers) { - vlimit.read(from, end); - } - void write(mtpBuffer &to) const { - vlimit.write(to); - } - - typedef MTPVector ResponseType; -}; -class MTPmessages_GetUnusedStickers : public MTPBoxed { -public: - MTPmessages_GetUnusedStickers() { - } - MTPmessages_GetUnusedStickers(const MTPmessages_getUnusedStickers &v) : MTPBoxed(v) { - } - MTPmessages_GetUnusedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { - } - MTPmessages_GetUnusedStickers(MTPint _limit) : MTPBoxed(MTPmessages_getUnusedStickers(_limit)) { + MTPmessages_ClearRecentStickers(const MTPflags &_flags) : MTPBoxed(MTPmessages_clearRecentStickers(_flags)) { } }; @@ -21082,6 +21387,211 @@ public: } }; +class MTPmessages_setGameScore { // RPC method 'messages.setGameScore' +public: + enum class Flag : int32 { + f_edit_message = (1 << 0), + MAX_FIELD = (1 << 0), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool is_edit_message() const { return vflags.v & Flag::f_edit_message; } + + MTPflags vflags; + MTPInputPeer vpeer; + MTPint vid; + MTPInputUser vuser_id; + MTPint vgame_id; + MTPint vscore; + + MTPmessages_setGameScore() { + } + MTPmessages_setGameScore(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_setGameScore) { + read(from, end, cons); + } + MTPmessages_setGameScore(const MTPflags &_flags, const MTPInputPeer &_peer, MTPint _id, const MTPInputUser &_user_id, MTPint _game_id, MTPint _score) : vflags(_flags), vpeer(_peer), vid(_id), vuser_id(_user_id), vgame_id(_game_id), vscore(_score) { + } + + uint32 innerLength() const { + return vflags.innerLength() + vpeer.innerLength() + vid.innerLength() + vuser_id.innerLength() + vgame_id.innerLength() + vscore.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_setGameScore; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_setGameScore) { + vflags.read(from, end); + vpeer.read(from, end); + vid.read(from, end); + vuser_id.read(from, end); + vgame_id.read(from, end); + vscore.read(from, end); + } + void write(mtpBuffer &to) const { + vflags.write(to); + vpeer.write(to); + vid.write(to); + vuser_id.write(to); + vgame_id.write(to); + vscore.write(to); + } + + typedef MTPUpdates ResponseType; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPmessages_setGameScore::Flags) + +class MTPmessages_SetGameScore : public MTPBoxed { +public: + MTPmessages_SetGameScore() { + } + MTPmessages_SetGameScore(const MTPmessages_setGameScore &v) : MTPBoxed(v) { + } + MTPmessages_SetGameScore(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_SetGameScore(const MTPflags &_flags, const MTPInputPeer &_peer, MTPint _id, const MTPInputUser &_user_id, MTPint _game_id, MTPint _score) : MTPBoxed(MTPmessages_setGameScore(_flags, _peer, _id, _user_id, _game_id, _score)) { + } +}; + +class MTPmessages_setInlineGameScore { // RPC method 'messages.setInlineGameScore' +public: + enum class Flag : int32 { + f_edit_message = (1 << 0), + MAX_FIELD = (1 << 0), + }; + Q_DECLARE_FLAGS(Flags, Flag); + friend inline Flags operator~(Flag v) { return QFlag(~static_cast(v)); } + + bool is_edit_message() const { return vflags.v & Flag::f_edit_message; } + + MTPflags vflags; + MTPInputBotInlineMessageID vid; + MTPInputUser vuser_id; + MTPint vgame_id; + MTPint vscore; + + MTPmessages_setInlineGameScore() { + } + MTPmessages_setInlineGameScore(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_setInlineGameScore) { + read(from, end, cons); + } + MTPmessages_setInlineGameScore(const MTPflags &_flags, const MTPInputBotInlineMessageID &_id, const MTPInputUser &_user_id, MTPint _game_id, MTPint _score) : vflags(_flags), vid(_id), vuser_id(_user_id), vgame_id(_game_id), vscore(_score) { + } + + uint32 innerLength() const { + return vflags.innerLength() + vid.innerLength() + vuser_id.innerLength() + vgame_id.innerLength() + vscore.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_setInlineGameScore; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_setInlineGameScore) { + vflags.read(from, end); + vid.read(from, end); + vuser_id.read(from, end); + vgame_id.read(from, end); + vscore.read(from, end); + } + void write(mtpBuffer &to) const { + vflags.write(to); + vid.write(to); + vuser_id.write(to); + vgame_id.write(to); + vscore.write(to); + } + + typedef MTPBool ResponseType; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPmessages_setInlineGameScore::Flags) + +class MTPmessages_SetInlineGameScore : public MTPBoxed { +public: + MTPmessages_SetInlineGameScore() { + } + MTPmessages_SetInlineGameScore(const MTPmessages_setInlineGameScore &v) : MTPBoxed(v) { + } + MTPmessages_SetInlineGameScore(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_SetInlineGameScore(const MTPflags &_flags, const MTPInputBotInlineMessageID &_id, const MTPInputUser &_user_id, MTPint _game_id, MTPint _score) : MTPBoxed(MTPmessages_setInlineGameScore(_flags, _id, _user_id, _game_id, _score)) { + } +}; + +class MTPmessages_getMaskStickers { // RPC method 'messages.getMaskStickers' +public: + MTPint vhash; + + MTPmessages_getMaskStickers() { + } + MTPmessages_getMaskStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getMaskStickers) { + read(from, end, cons); + } + MTPmessages_getMaskStickers(MTPint _hash) : vhash(_hash) { + } + + uint32 innerLength() const { + return vhash.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_getMaskStickers; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getMaskStickers) { + vhash.read(from, end); + } + void write(mtpBuffer &to) const { + vhash.write(to); + } + + typedef MTPmessages_AllStickers ResponseType; +}; +class MTPmessages_GetMaskStickers : public MTPBoxed { +public: + MTPmessages_GetMaskStickers() { + } + MTPmessages_GetMaskStickers(const MTPmessages_getMaskStickers &v) : MTPBoxed(v) { + } + MTPmessages_GetMaskStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_GetMaskStickers(MTPint _hash) : MTPBoxed(MTPmessages_getMaskStickers(_hash)) { + } +}; + +class MTPmessages_getAttachedStickers { // RPC method 'messages.getAttachedStickers' +public: + MTPInputStickeredMedia vmedia; + + MTPmessages_getAttachedStickers() { + } + MTPmessages_getAttachedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getAttachedStickers) { + read(from, end, cons); + } + MTPmessages_getAttachedStickers(const MTPInputStickeredMedia &_media) : vmedia(_media) { + } + + uint32 innerLength() const { + return vmedia.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_getAttachedStickers; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getAttachedStickers) { + vmedia.read(from, end); + } + void write(mtpBuffer &to) const { + vmedia.write(to); + } + + typedef MTPVector ResponseType; +}; +class MTPmessages_GetAttachedStickers : public MTPBoxed { +public: + MTPmessages_GetAttachedStickers() { + } + MTPmessages_GetAttachedStickers(const MTPmessages_getAttachedStickers &v) : MTPBoxed(v) { + } + MTPmessages_GetAttachedStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_GetAttachedStickers(const MTPInputStickeredMedia &_media) : MTPBoxed(MTPmessages_getAttachedStickers(_media)) { + } +}; + class MTPupdates_getState { // RPC method 'updates.getState' public: MTPupdates_getState() { @@ -21209,29 +21719,26 @@ public: class MTPphotos_updateProfilePhoto { // RPC method 'photos.updateProfilePhoto' public: MTPInputPhoto vid; - MTPInputPhotoCrop vcrop; MTPphotos_updateProfilePhoto() { } MTPphotos_updateProfilePhoto(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_photos_updateProfilePhoto) { read(from, end, cons); } - MTPphotos_updateProfilePhoto(const MTPInputPhoto &_id, const MTPInputPhotoCrop &_crop) : vid(_id), vcrop(_crop) { + MTPphotos_updateProfilePhoto(const MTPInputPhoto &_id) : vid(_id) { } uint32 innerLength() const { - return vid.innerLength() + vcrop.innerLength(); + return vid.innerLength(); } mtpTypeId type() const { return mtpc_photos_updateProfilePhoto; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_photos_updateProfilePhoto) { vid.read(from, end); - vcrop.read(from, end); } void write(mtpBuffer &to) const { vid.write(to); - vcrop.write(to); } typedef MTPUserProfilePhoto ResponseType; @@ -21244,42 +21751,33 @@ public: } MTPphotos_UpdateProfilePhoto(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } - MTPphotos_UpdateProfilePhoto(const MTPInputPhoto &_id, const MTPInputPhotoCrop &_crop) : MTPBoxed(MTPphotos_updateProfilePhoto(_id, _crop)) { + MTPphotos_UpdateProfilePhoto(const MTPInputPhoto &_id) : MTPBoxed(MTPphotos_updateProfilePhoto(_id)) { } }; class MTPphotos_uploadProfilePhoto { // RPC method 'photos.uploadProfilePhoto' public: MTPInputFile vfile; - MTPstring vcaption; - MTPInputGeoPoint vgeo_point; - MTPInputPhotoCrop vcrop; MTPphotos_uploadProfilePhoto() { } MTPphotos_uploadProfilePhoto(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_photos_uploadProfilePhoto) { read(from, end, cons); } - MTPphotos_uploadProfilePhoto(const MTPInputFile &_file, const MTPstring &_caption, const MTPInputGeoPoint &_geo_point, const MTPInputPhotoCrop &_crop) : vfile(_file), vcaption(_caption), vgeo_point(_geo_point), vcrop(_crop) { + MTPphotos_uploadProfilePhoto(const MTPInputFile &_file) : vfile(_file) { } uint32 innerLength() const { - return vfile.innerLength() + vcaption.innerLength() + vgeo_point.innerLength() + vcrop.innerLength(); + return vfile.innerLength(); } mtpTypeId type() const { return mtpc_photos_uploadProfilePhoto; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_photos_uploadProfilePhoto) { vfile.read(from, end); - vcaption.read(from, end); - vgeo_point.read(from, end); - vcrop.read(from, end); } void write(mtpBuffer &to) const { vfile.write(to); - vcaption.write(to); - vgeo_point.write(to); - vcrop.write(to); } typedef MTPphotos_Photo ResponseType; @@ -21292,7 +21790,7 @@ public: } MTPphotos_UploadProfilePhoto(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } - MTPphotos_UploadProfilePhoto(const MTPInputFile &_file, const MTPstring &_caption, const MTPInputGeoPoint &_geo_point, const MTPInputPhotoCrop &_crop) : MTPBoxed(MTPphotos_uploadProfilePhoto(_file, _caption, _geo_point, _crop)) { + MTPphotos_UploadProfilePhoto(const MTPInputFile &_file) : MTPBoxed(MTPphotos_uploadProfilePhoto(_file)) { } }; @@ -23072,8 +23570,8 @@ public: inline static MTPinputMedia new_inputMediaEmpty() { return MTPinputMedia(mtpc_inputMediaEmpty); } - inline static MTPinputMedia new_inputMediaUploadedPhoto(const MTPInputFile &_file, const MTPstring &_caption) { - return MTPinputMedia(new MTPDinputMediaUploadedPhoto(_file, _caption)); + inline static MTPinputMedia new_inputMediaUploadedPhoto(const MTPflags &_flags, const MTPInputFile &_file, const MTPstring &_caption, const MTPVector &_stickers) { + return MTPinputMedia(new MTPDinputMediaUploadedPhoto(_flags, _file, _caption, _stickers)); } inline static MTPinputMedia new_inputMediaPhoto(const MTPInputPhoto &_id, const MTPstring &_caption) { return MTPinputMedia(new MTPDinputMediaPhoto(_id, _caption)); @@ -23084,11 +23582,11 @@ public: inline static MTPinputMedia new_inputMediaContact(const MTPstring &_phone_number, const MTPstring &_first_name, const MTPstring &_last_name) { return MTPinputMedia(new MTPDinputMediaContact(_phone_number, _first_name, _last_name)); } - inline static MTPinputMedia new_inputMediaUploadedDocument(const MTPInputFile &_file, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption) { - return MTPinputMedia(new MTPDinputMediaUploadedDocument(_file, _mime_type, _attributes, _caption)); + inline static MTPinputMedia new_inputMediaUploadedDocument(const MTPflags &_flags, const MTPInputFile &_file, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption, const MTPVector &_stickers) { + return MTPinputMedia(new MTPDinputMediaUploadedDocument(_flags, _file, _mime_type, _attributes, _caption, _stickers)); } - inline static MTPinputMedia new_inputMediaUploadedThumbDocument(const MTPInputFile &_file, const MTPInputFile &_thumb, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption) { - return MTPinputMedia(new MTPDinputMediaUploadedThumbDocument(_file, _thumb, _mime_type, _attributes, _caption)); + inline static MTPinputMedia new_inputMediaUploadedThumbDocument(const MTPflags &_flags, const MTPInputFile &_file, const MTPInputFile &_thumb, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption, const MTPVector &_stickers) { + return MTPinputMedia(new MTPDinputMediaUploadedThumbDocument(_flags, _file, _thumb, _mime_type, _attributes, _caption, _stickers)); } inline static MTPinputMedia new_inputMediaDocument(const MTPInputDocument &_id, const MTPstring &_caption) { return MTPinputMedia(new MTPDinputMediaDocument(_id, _caption)); @@ -23108,11 +23606,11 @@ public: inline static MTPinputChatPhoto new_inputChatPhotoEmpty() { return MTPinputChatPhoto(mtpc_inputChatPhotoEmpty); } - inline static MTPinputChatPhoto new_inputChatUploadedPhoto(const MTPInputFile &_file, const MTPInputPhotoCrop &_crop) { - return MTPinputChatPhoto(new MTPDinputChatUploadedPhoto(_file, _crop)); + inline static MTPinputChatPhoto new_inputChatUploadedPhoto(const MTPInputFile &_file) { + return MTPinputChatPhoto(new MTPDinputChatUploadedPhoto(_file)); } - inline static MTPinputChatPhoto new_inputChatPhoto(const MTPInputPhoto &_id, const MTPInputPhotoCrop &_crop) { - return MTPinputChatPhoto(new MTPDinputChatPhoto(_id, _crop)); + inline static MTPinputChatPhoto new_inputChatPhoto(const MTPInputPhoto &_id) { + return MTPinputChatPhoto(new MTPDinputChatPhoto(_id)); } inline static MTPinputGeoPoint new_inputGeoPointEmpty() { return MTPinputGeoPoint(mtpc_inputGeoPointEmpty); @@ -23135,12 +23633,6 @@ public: inline static MTPinputFileLocation new_inputDocumentFileLocation(const MTPlong &_id, const MTPlong &_access_hash, MTPint _version) { return MTPinputFileLocation(new MTPDinputDocumentFileLocation(_id, _access_hash, _version)); } - inline static MTPinputPhotoCrop new_inputPhotoCropAuto() { - return MTPinputPhotoCrop(mtpc_inputPhotoCropAuto); - } - inline static MTPinputPhotoCrop new_inputPhotoCrop(const MTPdouble &_crop_left, const MTPdouble &_crop_top, const MTPdouble &_crop_width) { - return MTPinputPhotoCrop(new MTPDinputPhotoCrop(_crop_left, _crop_top, _crop_width)); - } inline static MTPinputAppEvent new_inputAppEvent(const MTPdouble &_time, const MTPstring &_type, const MTPlong &_peer, const MTPstring &_data) { return MTPinputAppEvent(new MTPDinputAppEvent(_time, _type, _peer, _data)); } @@ -23333,14 +23825,17 @@ public: inline static MTPmessageAction new_messageActionHistoryClear() { return MTPmessageAction(mtpc_messageActionHistoryClear); } + inline static MTPmessageAction new_messageActionGameScore(MTPint _game_id, MTPint _score) { + return MTPmessageAction(new MTPDmessageActionGameScore(_game_id, _score)); + } inline static MTPdialog new_dialog(const MTPflags &_flags, const MTPPeer &_peer, MTPint _top_message, MTPint _read_inbox_max_id, MTPint _read_outbox_max_id, MTPint _unread_count, const MTPPeerNotifySettings &_notify_settings, MTPint _pts, const MTPDraftMessage &_draft) { return MTPdialog(new MTPDdialog(_flags, _peer, _top_message, _read_inbox_max_id, _read_outbox_max_id, _unread_count, _notify_settings, _pts, _draft)); } inline static MTPphoto new_photoEmpty(const MTPlong &_id) { return MTPphoto(new MTPDphotoEmpty(_id)); } - inline static MTPphoto new_photo(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, const MTPVector &_sizes) { - return MTPphoto(new MTPDphoto(_id, _access_hash, _date, _sizes)); + inline static MTPphoto new_photo(const MTPflags &_flags, const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, const MTPVector &_sizes) { + return MTPphoto(new MTPDphoto(_flags, _id, _access_hash, _date, _sizes)); } inline static MTPphotoSize new_photoSizeEmpty(const MTPstring &_type) { return MTPphotoSize(new MTPDphotoSizeEmpty(_type)); @@ -23624,8 +24119,8 @@ public: inline static MTPupdate new_updateNewStickerSet(const MTPmessages_StickerSet &_stickerset) { return MTPupdate(new MTPDupdateNewStickerSet(_stickerset)); } - inline static MTPupdate new_updateStickerSetsOrder(const MTPVector &_order) { - return MTPupdate(new MTPDupdateStickerSetsOrder(_order)); + inline static MTPupdate new_updateStickerSetsOrder(const MTPflags &_flags, const MTPVector &_order) { + return MTPupdate(new MTPDupdateStickerSetsOrder(_flags, _order)); } inline static MTPupdate new_updateStickerSets() { return MTPupdate(mtpc_updateStickerSets); @@ -23645,14 +24140,14 @@ public: inline static MTPupdate new_updateChannelPinnedMessage(MTPint _channel_id, MTPint _id) { return MTPupdate(new MTPDupdateChannelPinnedMessage(_channel_id, _id)); } - inline static MTPupdate new_updateBotCallbackQuery(const MTPlong &_query_id, MTPint _user_id, const MTPPeer &_peer, MTPint _msg_id, const MTPbytes &_data) { - return MTPupdate(new MTPDupdateBotCallbackQuery(_query_id, _user_id, _peer, _msg_id, _data)); + inline static MTPupdate new_updateBotCallbackQuery(const MTPflags &_flags, const MTPlong &_query_id, MTPint _user_id, const MTPPeer &_peer, MTPint _msg_id, const MTPbytes &_data, MTPint _game_id) { + return MTPupdate(new MTPDupdateBotCallbackQuery(_flags, _query_id, _user_id, _peer, _msg_id, _data, _game_id)); } inline static MTPupdate new_updateEditMessage(const MTPMessage &_message, MTPint _pts, MTPint _pts_count) { return MTPupdate(new MTPDupdateEditMessage(_message, _pts, _pts_count)); } - inline static MTPupdate new_updateInlineBotCallbackQuery(const MTPlong &_query_id, MTPint _user_id, const MTPInputBotInlineMessageID &_msg_id, const MTPbytes &_data) { - return MTPupdate(new MTPDupdateInlineBotCallbackQuery(_query_id, _user_id, _msg_id, _data)); + inline static MTPupdate new_updateInlineBotCallbackQuery(const MTPflags &_flags, const MTPlong &_query_id, MTPint _user_id, const MTPInputBotInlineMessageID &_msg_id, const MTPbytes &_data, MTPint _game_id) { + return MTPupdate(new MTPDupdateInlineBotCallbackQuery(_flags, _query_id, _user_id, _msg_id, _data, _game_id)); } inline static MTPupdate new_updateReadChannelOutbox(MTPint _channel_id, MTPint _max_id) { return MTPupdate(new MTPDupdateReadChannelOutbox(_channel_id, _max_id)); @@ -23909,8 +24404,8 @@ public: inline static MTPdocumentAttribute new_documentAttributeAnimated() { return MTPdocumentAttribute(mtpc_documentAttributeAnimated); } - inline static MTPdocumentAttribute new_documentAttributeSticker(const MTPstring &_alt, const MTPInputStickerSet &_stickerset) { - return MTPdocumentAttribute(new MTPDdocumentAttributeSticker(_alt, _stickerset)); + inline static MTPdocumentAttribute new_documentAttributeSticker(const MTPflags &_flags, const MTPstring &_alt, const MTPInputStickerSet &_stickerset, const MTPMaskCoords &_mask_coords) { + return MTPdocumentAttribute(new MTPDdocumentAttributeSticker(_flags, _alt, _stickerset, _mask_coords)); } inline static MTPdocumentAttribute new_documentAttributeVideo(MTPint _duration, MTPint _w, MTPint _h) { return MTPdocumentAttribute(new MTPDdocumentAttributeVideo(_duration, _w, _h)); @@ -23921,6 +24416,9 @@ public: inline static MTPdocumentAttribute new_documentAttributeFilename(const MTPstring &_file_name) { return MTPdocumentAttribute(new MTPDdocumentAttributeFilename(_file_name)); } + inline static MTPdocumentAttribute new_documentAttributeHasStickers() { + return MTPdocumentAttribute(mtpc_documentAttributeHasStickers); + } inline static MTPmessages_stickers new_messages_stickersNotModified() { return MTPmessages_stickers(mtpc_messages_stickersNotModified); } @@ -24038,6 +24536,9 @@ public: inline static MTPkeyboardButton new_keyboardButtonSwitchInline(const MTPflags &_flags, const MTPstring &_text, const MTPstring &_query) { return MTPkeyboardButton(new MTPDkeyboardButtonSwitchInline(_flags, _text, _query)); } + inline static MTPkeyboardButton new_keyboardButtonGame(const MTPstring &_text, const MTPstring &_game_title, MTPint _game_id, const MTPstring &_start_param) { + return MTPkeyboardButton(new MTPDkeyboardButtonGame(_text, _game_title, _game_id, _start_param)); + } inline static MTPkeyboardButtonRow new_keyboardButtonRow(const MTPVector &_buttons) { return MTPkeyboardButtonRow(new MTPDkeyboardButtonRow(_buttons)); } @@ -24335,6 +24836,18 @@ public: inline static MTPstickerSetCovered new_stickerSetCovered(const MTPStickerSet &_set, const MTPDocument &_cover) { return MTPstickerSetCovered(new MTPDstickerSetCovered(_set, _cover)); } + inline static MTPstickerSetCovered new_stickerSetMultiCovered(const MTPStickerSet &_set, const MTPVector &_covers) { + return MTPstickerSetCovered(new MTPDstickerSetMultiCovered(_set, _covers)); + } + inline static MTPmaskCoords new_maskCoords(MTPint _n, const MTPdouble &_x, const MTPdouble &_y, const MTPdouble &_zoom) { + return MTPmaskCoords(new MTPDmaskCoords(_n, _x, _y, _zoom)); + } + inline static MTPinputStickeredMedia new_inputStickeredMediaPhoto(const MTPInputPhoto &_id) { + return MTPinputStickeredMedia(new MTPDinputStickeredMediaPhoto(_id)); + } + inline static MTPinputStickeredMedia new_inputStickeredMediaDocument(const MTPInputDocument &_id) { + return MTPinputStickeredMedia(new MTPDinputStickeredMediaDocument(_id)); + } }; } // namespace internal @@ -25584,7 +26097,7 @@ inline uint32 MTPinputMedia::innerLength() const { switch (_type) { case mtpc_inputMediaUploadedPhoto: { const MTPDinputMediaUploadedPhoto &v(c_inputMediaUploadedPhoto()); - return v.vfile.innerLength() + v.vcaption.innerLength(); + return v.vflags.innerLength() + v.vfile.innerLength() + v.vcaption.innerLength() + (v.has_stickers() ? v.vstickers.innerLength() : 0); } case mtpc_inputMediaPhoto: { const MTPDinputMediaPhoto &v(c_inputMediaPhoto()); @@ -25600,11 +26113,11 @@ inline uint32 MTPinputMedia::innerLength() const { } case mtpc_inputMediaUploadedDocument: { const MTPDinputMediaUploadedDocument &v(c_inputMediaUploadedDocument()); - return v.vfile.innerLength() + v.vmime_type.innerLength() + v.vattributes.innerLength() + v.vcaption.innerLength(); + return v.vflags.innerLength() + v.vfile.innerLength() + v.vmime_type.innerLength() + v.vattributes.innerLength() + v.vcaption.innerLength() + (v.has_stickers() ? v.vstickers.innerLength() : 0); } case mtpc_inputMediaUploadedThumbDocument: { const MTPDinputMediaUploadedThumbDocument &v(c_inputMediaUploadedThumbDocument()); - return v.vfile.innerLength() + v.vthumb.innerLength() + v.vmime_type.innerLength() + v.vattributes.innerLength() + v.vcaption.innerLength(); + return v.vflags.innerLength() + v.vfile.innerLength() + v.vthumb.innerLength() + v.vmime_type.innerLength() + v.vattributes.innerLength() + v.vcaption.innerLength() + (v.has_stickers() ? v.vstickers.innerLength() : 0); } case mtpc_inputMediaDocument: { const MTPDinputMediaDocument &v(c_inputMediaDocument()); @@ -25640,8 +26153,10 @@ inline void MTPinputMedia::read(const mtpPrime *&from, const mtpPrime *end, mtpT case mtpc_inputMediaUploadedPhoto: _type = cons; { if (!data) setData(new MTPDinputMediaUploadedPhoto()); MTPDinputMediaUploadedPhoto &v(_inputMediaUploadedPhoto()); + v.vflags.read(from, end); v.vfile.read(from, end); v.vcaption.read(from, end); + if (v.has_stickers()) { v.vstickers.read(from, end); } else { v.vstickers = MTPVector(); } } break; case mtpc_inputMediaPhoto: _type = cons; { if (!data) setData(new MTPDinputMediaPhoto()); @@ -25664,19 +26179,23 @@ inline void MTPinputMedia::read(const mtpPrime *&from, const mtpPrime *end, mtpT case mtpc_inputMediaUploadedDocument: _type = cons; { if (!data) setData(new MTPDinputMediaUploadedDocument()); MTPDinputMediaUploadedDocument &v(_inputMediaUploadedDocument()); + v.vflags.read(from, end); v.vfile.read(from, end); v.vmime_type.read(from, end); v.vattributes.read(from, end); v.vcaption.read(from, end); + if (v.has_stickers()) { v.vstickers.read(from, end); } else { v.vstickers = MTPVector(); } } break; case mtpc_inputMediaUploadedThumbDocument: _type = cons; { if (!data) setData(new MTPDinputMediaUploadedThumbDocument()); MTPDinputMediaUploadedThumbDocument &v(_inputMediaUploadedThumbDocument()); + v.vflags.read(from, end); v.vfile.read(from, end); v.vthumb.read(from, end); v.vmime_type.read(from, end); v.vattributes.read(from, end); v.vcaption.read(from, end); + if (v.has_stickers()) { v.vstickers.read(from, end); } else { v.vstickers = MTPVector(); } } break; case mtpc_inputMediaDocument: _type = cons; { if (!data) setData(new MTPDinputMediaDocument()); @@ -25718,8 +26237,10 @@ inline void MTPinputMedia::write(mtpBuffer &to) const { switch (_type) { case mtpc_inputMediaUploadedPhoto: { const MTPDinputMediaUploadedPhoto &v(c_inputMediaUploadedPhoto()); + v.vflags.write(to); v.vfile.write(to); v.vcaption.write(to); + if (v.has_stickers()) v.vstickers.write(to); } break; case mtpc_inputMediaPhoto: { const MTPDinputMediaPhoto &v(c_inputMediaPhoto()); @@ -25738,18 +26259,22 @@ inline void MTPinputMedia::write(mtpBuffer &to) const { } break; case mtpc_inputMediaUploadedDocument: { const MTPDinputMediaUploadedDocument &v(c_inputMediaUploadedDocument()); + v.vflags.write(to); v.vfile.write(to); v.vmime_type.write(to); v.vattributes.write(to); v.vcaption.write(to); + if (v.has_stickers()) v.vstickers.write(to); } break; case mtpc_inputMediaUploadedThumbDocument: { const MTPDinputMediaUploadedThumbDocument &v(c_inputMediaUploadedThumbDocument()); + v.vflags.write(to); v.vfile.write(to); v.vthumb.write(to); v.vmime_type.write(to); v.vattributes.write(to); v.vcaption.write(to); + if (v.has_stickers()) v.vstickers.write(to); } break; case mtpc_inputMediaDocument: { const MTPDinputMediaDocument &v(c_inputMediaDocument()); @@ -25823,8 +26348,9 @@ inline MTPinputMedia::MTPinputMedia(MTPDinputMediaDocumentExternal *_data) : mtp inline MTPinputMedia MTP_inputMediaEmpty() { return MTP::internal::TypeCreator::new_inputMediaEmpty(); } -inline MTPinputMedia MTP_inputMediaUploadedPhoto(const MTPInputFile &_file, const MTPstring &_caption) { - return MTP::internal::TypeCreator::new_inputMediaUploadedPhoto(_file, _caption); +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDinputMediaUploadedPhoto::Flags) +inline MTPinputMedia MTP_inputMediaUploadedPhoto(const MTPflags &_flags, const MTPInputFile &_file, const MTPstring &_caption, const MTPVector &_stickers) { + return MTP::internal::TypeCreator::new_inputMediaUploadedPhoto(_flags, _file, _caption, _stickers); } inline MTPinputMedia MTP_inputMediaPhoto(const MTPInputPhoto &_id, const MTPstring &_caption) { return MTP::internal::TypeCreator::new_inputMediaPhoto(_id, _caption); @@ -25835,11 +26361,13 @@ inline MTPinputMedia MTP_inputMediaGeoPoint(const MTPInputGeoPoint &_geo_point) inline MTPinputMedia MTP_inputMediaContact(const MTPstring &_phone_number, const MTPstring &_first_name, const MTPstring &_last_name) { return MTP::internal::TypeCreator::new_inputMediaContact(_phone_number, _first_name, _last_name); } -inline MTPinputMedia MTP_inputMediaUploadedDocument(const MTPInputFile &_file, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption) { - return MTP::internal::TypeCreator::new_inputMediaUploadedDocument(_file, _mime_type, _attributes, _caption); +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDinputMediaUploadedDocument::Flags) +inline MTPinputMedia MTP_inputMediaUploadedDocument(const MTPflags &_flags, const MTPInputFile &_file, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption, const MTPVector &_stickers) { + return MTP::internal::TypeCreator::new_inputMediaUploadedDocument(_flags, _file, _mime_type, _attributes, _caption, _stickers); } -inline MTPinputMedia MTP_inputMediaUploadedThumbDocument(const MTPInputFile &_file, const MTPInputFile &_thumb, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption) { - return MTP::internal::TypeCreator::new_inputMediaUploadedThumbDocument(_file, _thumb, _mime_type, _attributes, _caption); +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDinputMediaUploadedThumbDocument::Flags) +inline MTPinputMedia MTP_inputMediaUploadedThumbDocument(const MTPflags &_flags, const MTPInputFile &_file, const MTPInputFile &_thumb, const MTPstring &_mime_type, const MTPVector &_attributes, const MTPstring &_caption, const MTPVector &_stickers) { + return MTP::internal::TypeCreator::new_inputMediaUploadedThumbDocument(_flags, _file, _thumb, _mime_type, _attributes, _caption, _stickers); } inline MTPinputMedia MTP_inputMediaDocument(const MTPInputDocument &_id, const MTPstring &_caption) { return MTP::internal::TypeCreator::new_inputMediaDocument(_id, _caption); @@ -25861,11 +26389,11 @@ inline uint32 MTPinputChatPhoto::innerLength() const { switch (_type) { case mtpc_inputChatUploadedPhoto: { const MTPDinputChatUploadedPhoto &v(c_inputChatUploadedPhoto()); - return v.vfile.innerLength() + v.vcrop.innerLength(); + return v.vfile.innerLength(); } case mtpc_inputChatPhoto: { const MTPDinputChatPhoto &v(c_inputChatPhoto()); - return v.vid.innerLength() + v.vcrop.innerLength(); + return v.vid.innerLength(); } } return 0; @@ -25882,13 +26410,11 @@ inline void MTPinputChatPhoto::read(const mtpPrime *&from, const mtpPrime *end, if (!data) setData(new MTPDinputChatUploadedPhoto()); MTPDinputChatUploadedPhoto &v(_inputChatUploadedPhoto()); v.vfile.read(from, end); - v.vcrop.read(from, end); } break; case mtpc_inputChatPhoto: _type = cons; { if (!data) setData(new MTPDinputChatPhoto()); MTPDinputChatPhoto &v(_inputChatPhoto()); v.vid.read(from, end); - v.vcrop.read(from, end); } break; default: throw mtpErrorUnexpected(cons, "MTPinputChatPhoto"); } @@ -25898,12 +26424,10 @@ inline void MTPinputChatPhoto::write(mtpBuffer &to) const { case mtpc_inputChatUploadedPhoto: { const MTPDinputChatUploadedPhoto &v(c_inputChatUploadedPhoto()); v.vfile.write(to); - v.vcrop.write(to); } break; case mtpc_inputChatPhoto: { const MTPDinputChatPhoto &v(c_inputChatPhoto()); v.vid.write(to); - v.vcrop.write(to); } break; } } @@ -25922,11 +26446,11 @@ inline MTPinputChatPhoto::MTPinputChatPhoto(MTPDinputChatPhoto *_data) : mtpData inline MTPinputChatPhoto MTP_inputChatPhotoEmpty() { return MTP::internal::TypeCreator::new_inputChatPhotoEmpty(); } -inline MTPinputChatPhoto MTP_inputChatUploadedPhoto(const MTPInputFile &_file, const MTPInputPhotoCrop &_crop) { - return MTP::internal::TypeCreator::new_inputChatUploadedPhoto(_file, _crop); +inline MTPinputChatPhoto MTP_inputChatUploadedPhoto(const MTPInputFile &_file) { + return MTP::internal::TypeCreator::new_inputChatUploadedPhoto(_file); } -inline MTPinputChatPhoto MTP_inputChatPhoto(const MTPInputPhoto &_id, const MTPInputPhotoCrop &_crop) { - return MTP::internal::TypeCreator::new_inputChatPhoto(_id, _crop); +inline MTPinputChatPhoto MTP_inputChatPhoto(const MTPInputPhoto &_id) { + return MTP::internal::TypeCreator::new_inputChatPhoto(_id); } inline uint32 MTPinputGeoPoint::innerLength() const { @@ -26123,59 +26647,6 @@ inline MTPinputFileLocation MTP_inputDocumentFileLocation(const MTPlong &_id, co return MTP::internal::TypeCreator::new_inputDocumentFileLocation(_id, _access_hash, _version); } -inline uint32 MTPinputPhotoCrop::innerLength() const { - switch (_type) { - case mtpc_inputPhotoCrop: { - const MTPDinputPhotoCrop &v(c_inputPhotoCrop()); - return v.vcrop_left.innerLength() + v.vcrop_top.innerLength() + v.vcrop_width.innerLength(); - } - } - return 0; -} -inline mtpTypeId MTPinputPhotoCrop::type() const { - if (!_type) throw mtpErrorUninitialized(); - return _type; -} -inline void MTPinputPhotoCrop::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { - if (cons != _type) setData(0); - switch (cons) { - case mtpc_inputPhotoCropAuto: _type = cons; break; - case mtpc_inputPhotoCrop: _type = cons; { - if (!data) setData(new MTPDinputPhotoCrop()); - MTPDinputPhotoCrop &v(_inputPhotoCrop()); - v.vcrop_left.read(from, end); - v.vcrop_top.read(from, end); - v.vcrop_width.read(from, end); - } break; - default: throw mtpErrorUnexpected(cons, "MTPinputPhotoCrop"); - } -} -inline void MTPinputPhotoCrop::write(mtpBuffer &to) const { - switch (_type) { - case mtpc_inputPhotoCrop: { - const MTPDinputPhotoCrop &v(c_inputPhotoCrop()); - v.vcrop_left.write(to); - v.vcrop_top.write(to); - v.vcrop_width.write(to); - } break; - } -} -inline MTPinputPhotoCrop::MTPinputPhotoCrop(mtpTypeId type) : mtpDataOwner(0), _type(type) { - switch (type) { - case mtpc_inputPhotoCropAuto: break; - case mtpc_inputPhotoCrop: setData(new MTPDinputPhotoCrop()); break; - default: throw mtpErrorBadTypeId(type, "MTPinputPhotoCrop"); - } -} -inline MTPinputPhotoCrop::MTPinputPhotoCrop(MTPDinputPhotoCrop *_data) : mtpDataOwner(_data), _type(mtpc_inputPhotoCrop) { -} -inline MTPinputPhotoCrop MTP_inputPhotoCropAuto() { - return MTP::internal::TypeCreator::new_inputPhotoCropAuto(); -} -inline MTPinputPhotoCrop MTP_inputPhotoCrop(const MTPdouble &_crop_left, const MTPdouble &_crop_top, const MTPdouble &_crop_width) { - return MTP::internal::TypeCreator::new_inputPhotoCrop(_crop_left, _crop_top, _crop_width); -} - inline MTPinputAppEvent::MTPinputAppEvent() : mtpDataOwner(new MTPDinputAppEvent()) { } @@ -27461,6 +27932,10 @@ inline uint32 MTPmessageAction::innerLength() const { const MTPDmessageActionChannelMigrateFrom &v(c_messageActionChannelMigrateFrom()); return v.vtitle.innerLength() + v.vchat_id.innerLength(); } + case mtpc_messageActionGameScore: { + const MTPDmessageActionGameScore &v(c_messageActionGameScore()); + return v.vgame_id.innerLength() + v.vscore.innerLength(); + } } return 0; } @@ -27522,6 +27997,12 @@ inline void MTPmessageAction::read(const mtpPrime *&from, const mtpPrime *end, m } break; case mtpc_messageActionPinMessage: _type = cons; break; case mtpc_messageActionHistoryClear: _type = cons; break; + case mtpc_messageActionGameScore: _type = cons; { + if (!data) setData(new MTPDmessageActionGameScore()); + MTPDmessageActionGameScore &v(_messageActionGameScore()); + v.vgame_id.read(from, end); + v.vscore.read(from, end); + } break; default: throw mtpErrorUnexpected(cons, "MTPmessageAction"); } } @@ -27565,6 +28046,11 @@ inline void MTPmessageAction::write(mtpBuffer &to) const { v.vtitle.write(to); v.vchat_id.write(to); } break; + case mtpc_messageActionGameScore: { + const MTPDmessageActionGameScore &v(c_messageActionGameScore()); + v.vgame_id.write(to); + v.vscore.write(to); + } break; } } inline MTPmessageAction::MTPmessageAction(mtpTypeId type) : mtpDataOwner(0), _type(type) { @@ -27582,6 +28068,7 @@ inline MTPmessageAction::MTPmessageAction(mtpTypeId type) : mtpDataOwner(0), _ty case mtpc_messageActionChannelMigrateFrom: setData(new MTPDmessageActionChannelMigrateFrom()); break; case mtpc_messageActionPinMessage: break; case mtpc_messageActionHistoryClear: break; + case mtpc_messageActionGameScore: setData(new MTPDmessageActionGameScore()); break; default: throw mtpErrorBadTypeId(type, "MTPmessageAction"); } } @@ -27603,6 +28090,8 @@ inline MTPmessageAction::MTPmessageAction(MTPDmessageActionChatMigrateTo *_data) } inline MTPmessageAction::MTPmessageAction(MTPDmessageActionChannelMigrateFrom *_data) : mtpDataOwner(_data), _type(mtpc_messageActionChannelMigrateFrom) { } +inline MTPmessageAction::MTPmessageAction(MTPDmessageActionGameScore *_data) : mtpDataOwner(_data), _type(mtpc_messageActionGameScore) { +} inline MTPmessageAction MTP_messageActionEmpty() { return MTP::internal::TypeCreator::new_messageActionEmpty(); } @@ -27642,6 +28131,9 @@ inline MTPmessageAction MTP_messageActionPinMessage() { inline MTPmessageAction MTP_messageActionHistoryClear() { return MTP::internal::TypeCreator::new_messageActionHistoryClear(); } +inline MTPmessageAction MTP_messageActionGameScore(MTPint _game_id, MTPint _score) { + return MTP::internal::TypeCreator::new_messageActionGameScore(_game_id, _score); +} inline MTPdialog::MTPdialog() : mtpDataOwner(new MTPDdialog()) { } @@ -27695,7 +28187,7 @@ inline uint32 MTPphoto::innerLength() const { } case mtpc_photo: { const MTPDphoto &v(c_photo()); - return v.vid.innerLength() + v.vaccess_hash.innerLength() + v.vdate.innerLength() + v.vsizes.innerLength(); + return v.vflags.innerLength() + v.vid.innerLength() + v.vaccess_hash.innerLength() + v.vdate.innerLength() + v.vsizes.innerLength(); } } return 0; @@ -27715,6 +28207,7 @@ inline void MTPphoto::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId case mtpc_photo: _type = cons; { if (!data) setData(new MTPDphoto()); MTPDphoto &v(_photo()); + v.vflags.read(from, end); v.vid.read(from, end); v.vaccess_hash.read(from, end); v.vdate.read(from, end); @@ -27731,6 +28224,7 @@ inline void MTPphoto::write(mtpBuffer &to) const { } break; case mtpc_photo: { const MTPDphoto &v(c_photo()); + v.vflags.write(to); v.vid.write(to); v.vaccess_hash.write(to); v.vdate.write(to); @@ -27752,8 +28246,9 @@ inline MTPphoto::MTPphoto(MTPDphoto *_data) : mtpDataOwner(_data), _type(mtpc_ph inline MTPphoto MTP_photoEmpty(const MTPlong &_id) { return MTP::internal::TypeCreator::new_photoEmpty(_id); } -inline MTPphoto MTP_photo(const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, const MTPVector &_sizes) { - return MTP::internal::TypeCreator::new_photo(_id, _access_hash, _date, _sizes); +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDphoto::Flags) +inline MTPphoto MTP_photo(const MTPflags &_flags, const MTPlong &_id, const MTPlong &_access_hash, MTPint _date, const MTPVector &_sizes) { + return MTP::internal::TypeCreator::new_photo(_flags, _id, _access_hash, _date, _sizes); } inline uint32 MTPphotoSize::innerLength() const { @@ -29229,7 +29724,7 @@ inline uint32 MTPupdate::innerLength() const { } case mtpc_updateStickerSetsOrder: { const MTPDupdateStickerSetsOrder &v(c_updateStickerSetsOrder()); - return v.vorder.innerLength(); + return v.vflags.innerLength() + v.vorder.innerLength(); } case mtpc_updateBotInlineQuery: { const MTPDupdateBotInlineQuery &v(c_updateBotInlineQuery()); @@ -29249,7 +29744,7 @@ inline uint32 MTPupdate::innerLength() const { } case mtpc_updateBotCallbackQuery: { const MTPDupdateBotCallbackQuery &v(c_updateBotCallbackQuery()); - return v.vquery_id.innerLength() + v.vuser_id.innerLength() + v.vpeer.innerLength() + v.vmsg_id.innerLength() + v.vdata.innerLength(); + return v.vflags.innerLength() + v.vquery_id.innerLength() + v.vuser_id.innerLength() + v.vpeer.innerLength() + v.vmsg_id.innerLength() + (v.has_data() ? v.vdata.innerLength() : 0) + (v.has_game_id() ? v.vgame_id.innerLength() : 0); } case mtpc_updateEditMessage: { const MTPDupdateEditMessage &v(c_updateEditMessage()); @@ -29257,7 +29752,7 @@ inline uint32 MTPupdate::innerLength() const { } case mtpc_updateInlineBotCallbackQuery: { const MTPDupdateInlineBotCallbackQuery &v(c_updateInlineBotCallbackQuery()); - return v.vquery_id.innerLength() + v.vuser_id.innerLength() + v.vmsg_id.innerLength() + v.vdata.innerLength(); + return v.vflags.innerLength() + v.vquery_id.innerLength() + v.vuser_id.innerLength() + v.vmsg_id.innerLength() + (v.has_data() ? v.vdata.innerLength() : 0) + (v.has_game_id() ? v.vgame_id.innerLength() : 0); } case mtpc_updateReadChannelOutbox: { const MTPDupdateReadChannelOutbox &v(c_updateReadChannelOutbox()); @@ -29528,6 +30023,7 @@ inline void MTPupdate::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeI case mtpc_updateStickerSetsOrder: _type = cons; { if (!data) setData(new MTPDupdateStickerSetsOrder()); MTPDupdateStickerSetsOrder &v(_updateStickerSetsOrder()); + v.vflags.read(from, end); v.vorder.read(from, end); } break; case mtpc_updateStickerSets: _type = cons; break; @@ -29568,11 +30064,13 @@ inline void MTPupdate::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeI case mtpc_updateBotCallbackQuery: _type = cons; { if (!data) setData(new MTPDupdateBotCallbackQuery()); MTPDupdateBotCallbackQuery &v(_updateBotCallbackQuery()); + v.vflags.read(from, end); v.vquery_id.read(from, end); v.vuser_id.read(from, end); v.vpeer.read(from, end); v.vmsg_id.read(from, end); - v.vdata.read(from, end); + if (v.has_data()) { v.vdata.read(from, end); } else { v.vdata = MTPbytes(); } + if (v.has_game_id()) { v.vgame_id.read(from, end); } else { v.vgame_id = MTPint(); } } break; case mtpc_updateEditMessage: _type = cons; { if (!data) setData(new MTPDupdateEditMessage()); @@ -29584,10 +30082,12 @@ inline void MTPupdate::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeI case mtpc_updateInlineBotCallbackQuery: _type = cons; { if (!data) setData(new MTPDupdateInlineBotCallbackQuery()); MTPDupdateInlineBotCallbackQuery &v(_updateInlineBotCallbackQuery()); + v.vflags.read(from, end); v.vquery_id.read(from, end); v.vuser_id.read(from, end); v.vmsg_id.read(from, end); - v.vdata.read(from, end); + if (v.has_data()) { v.vdata.read(from, end); } else { v.vdata = MTPbytes(); } + if (v.has_game_id()) { v.vgame_id.read(from, end); } else { v.vgame_id = MTPint(); } } break; case mtpc_updateReadChannelOutbox: _type = cons; { if (!data) setData(new MTPDupdateReadChannelOutbox()); @@ -29823,6 +30323,7 @@ inline void MTPupdate::write(mtpBuffer &to) const { } break; case mtpc_updateStickerSetsOrder: { const MTPDupdateStickerSetsOrder &v(c_updateStickerSetsOrder()); + v.vflags.write(to); v.vorder.write(to); } break; case mtpc_updateBotInlineQuery: { @@ -29856,11 +30357,13 @@ inline void MTPupdate::write(mtpBuffer &to) const { } break; case mtpc_updateBotCallbackQuery: { const MTPDupdateBotCallbackQuery &v(c_updateBotCallbackQuery()); + v.vflags.write(to); v.vquery_id.write(to); v.vuser_id.write(to); v.vpeer.write(to); v.vmsg_id.write(to); - v.vdata.write(to); + if (v.has_data()) v.vdata.write(to); + if (v.has_game_id()) v.vgame_id.write(to); } break; case mtpc_updateEditMessage: { const MTPDupdateEditMessage &v(c_updateEditMessage()); @@ -29870,10 +30373,12 @@ inline void MTPupdate::write(mtpBuffer &to) const { } break; case mtpc_updateInlineBotCallbackQuery: { const MTPDupdateInlineBotCallbackQuery &v(c_updateInlineBotCallbackQuery()); + v.vflags.write(to); v.vquery_id.write(to); v.vuser_id.write(to); v.vmsg_id.write(to); - v.vdata.write(to); + if (v.has_data()) v.vdata.write(to); + if (v.has_game_id()) v.vgame_id.write(to); } break; case mtpc_updateReadChannelOutbox: { const MTPDupdateReadChannelOutbox &v(c_updateReadChannelOutbox()); @@ -30151,8 +30656,9 @@ inline MTPupdate MTP_updateChatParticipantAdmin(MTPint _chat_id, MTPint _user_id inline MTPupdate MTP_updateNewStickerSet(const MTPmessages_StickerSet &_stickerset) { return MTP::internal::TypeCreator::new_updateNewStickerSet(_stickerset); } -inline MTPupdate MTP_updateStickerSetsOrder(const MTPVector &_order) { - return MTP::internal::TypeCreator::new_updateStickerSetsOrder(_order); +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDupdateStickerSetsOrder::Flags) +inline MTPupdate MTP_updateStickerSetsOrder(const MTPflags &_flags, const MTPVector &_order) { + return MTP::internal::TypeCreator::new_updateStickerSetsOrder(_flags, _order); } inline MTPupdate MTP_updateStickerSets() { return MTP::internal::TypeCreator::new_updateStickerSets(); @@ -30174,14 +30680,16 @@ inline MTPupdate MTP_updateEditChannelMessage(const MTPMessage &_message, MTPint inline MTPupdate MTP_updateChannelPinnedMessage(MTPint _channel_id, MTPint _id) { return MTP::internal::TypeCreator::new_updateChannelPinnedMessage(_channel_id, _id); } -inline MTPupdate MTP_updateBotCallbackQuery(const MTPlong &_query_id, MTPint _user_id, const MTPPeer &_peer, MTPint _msg_id, const MTPbytes &_data) { - return MTP::internal::TypeCreator::new_updateBotCallbackQuery(_query_id, _user_id, _peer, _msg_id, _data); +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDupdateBotCallbackQuery::Flags) +inline MTPupdate MTP_updateBotCallbackQuery(const MTPflags &_flags, const MTPlong &_query_id, MTPint _user_id, const MTPPeer &_peer, MTPint _msg_id, const MTPbytes &_data, MTPint _game_id) { + return MTP::internal::TypeCreator::new_updateBotCallbackQuery(_flags, _query_id, _user_id, _peer, _msg_id, _data, _game_id); } inline MTPupdate MTP_updateEditMessage(const MTPMessage &_message, MTPint _pts, MTPint _pts_count) { return MTP::internal::TypeCreator::new_updateEditMessage(_message, _pts, _pts_count); } -inline MTPupdate MTP_updateInlineBotCallbackQuery(const MTPlong &_query_id, MTPint _user_id, const MTPInputBotInlineMessageID &_msg_id, const MTPbytes &_data) { - return MTP::internal::TypeCreator::new_updateInlineBotCallbackQuery(_query_id, _user_id, _msg_id, _data); +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDupdateInlineBotCallbackQuery::Flags) +inline MTPupdate MTP_updateInlineBotCallbackQuery(const MTPflags &_flags, const MTPlong &_query_id, MTPint _user_id, const MTPInputBotInlineMessageID &_msg_id, const MTPbytes &_data, MTPint _game_id) { + return MTP::internal::TypeCreator::new_updateInlineBotCallbackQuery(_flags, _query_id, _user_id, _msg_id, _data, _game_id); } inline MTPupdate MTP_updateReadChannelOutbox(MTPint _channel_id, MTPint _max_id) { return MTP::internal::TypeCreator::new_updateReadChannelOutbox(_channel_id, _max_id); @@ -32128,7 +32636,7 @@ inline uint32 MTPdocumentAttribute::innerLength() const { } case mtpc_documentAttributeSticker: { const MTPDdocumentAttributeSticker &v(c_documentAttributeSticker()); - return v.valt.innerLength() + v.vstickerset.innerLength(); + return v.vflags.innerLength() + v.valt.innerLength() + v.vstickerset.innerLength() + (v.has_mask_coords() ? v.vmask_coords.innerLength() : 0); } case mtpc_documentAttributeVideo: { const MTPDdocumentAttributeVideo &v(c_documentAttributeVideo()); @@ -32162,8 +32670,10 @@ inline void MTPdocumentAttribute::read(const mtpPrime *&from, const mtpPrime *en case mtpc_documentAttributeSticker: _type = cons; { if (!data) setData(new MTPDdocumentAttributeSticker()); MTPDdocumentAttributeSticker &v(_documentAttributeSticker()); + v.vflags.read(from, end); v.valt.read(from, end); v.vstickerset.read(from, end); + if (v.has_mask_coords()) { v.vmask_coords.read(from, end); } else { v.vmask_coords = MTPMaskCoords(); } } break; case mtpc_documentAttributeVideo: _type = cons; { if (!data) setData(new MTPDdocumentAttributeVideo()); @@ -32186,6 +32696,7 @@ inline void MTPdocumentAttribute::read(const mtpPrime *&from, const mtpPrime *en MTPDdocumentAttributeFilename &v(_documentAttributeFilename()); v.vfile_name.read(from, end); } break; + case mtpc_documentAttributeHasStickers: _type = cons; break; default: throw mtpErrorUnexpected(cons, "MTPdocumentAttribute"); } } @@ -32198,8 +32709,10 @@ inline void MTPdocumentAttribute::write(mtpBuffer &to) const { } break; case mtpc_documentAttributeSticker: { const MTPDdocumentAttributeSticker &v(c_documentAttributeSticker()); + v.vflags.write(to); v.valt.write(to); v.vstickerset.write(to); + if (v.has_mask_coords()) v.vmask_coords.write(to); } break; case mtpc_documentAttributeVideo: { const MTPDdocumentAttributeVideo &v(c_documentAttributeVideo()); @@ -32229,6 +32742,7 @@ inline MTPdocumentAttribute::MTPdocumentAttribute(mtpTypeId type) : mtpDataOwner case mtpc_documentAttributeVideo: setData(new MTPDdocumentAttributeVideo()); break; case mtpc_documentAttributeAudio: setData(new MTPDdocumentAttributeAudio()); break; case mtpc_documentAttributeFilename: setData(new MTPDdocumentAttributeFilename()); break; + case mtpc_documentAttributeHasStickers: break; default: throw mtpErrorBadTypeId(type, "MTPdocumentAttribute"); } } @@ -32248,8 +32762,9 @@ inline MTPdocumentAttribute MTP_documentAttributeImageSize(MTPint _w, MTPint _h) inline MTPdocumentAttribute MTP_documentAttributeAnimated() { return MTP::internal::TypeCreator::new_documentAttributeAnimated(); } -inline MTPdocumentAttribute MTP_documentAttributeSticker(const MTPstring &_alt, const MTPInputStickerSet &_stickerset) { - return MTP::internal::TypeCreator::new_documentAttributeSticker(_alt, _stickerset); +Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDdocumentAttributeSticker::Flags) +inline MTPdocumentAttribute MTP_documentAttributeSticker(const MTPflags &_flags, const MTPstring &_alt, const MTPInputStickerSet &_stickerset, const MTPMaskCoords &_mask_coords) { + return MTP::internal::TypeCreator::new_documentAttributeSticker(_flags, _alt, _stickerset, _mask_coords); } inline MTPdocumentAttribute MTP_documentAttributeVideo(MTPint _duration, MTPint _w, MTPint _h) { return MTP::internal::TypeCreator::new_documentAttributeVideo(_duration, _w, _h); @@ -32261,6 +32776,9 @@ inline MTPdocumentAttribute MTP_documentAttributeAudio(const MTPflags &_flags, const MTPstring &_text, const MTPstring &_query) { return MTP::internal::TypeCreator::new_keyboardButtonSwitchInline(_flags, _text, _query); } +inline MTPkeyboardButton MTP_keyboardButtonGame(const MTPstring &_text, const MTPstring &_game_title, MTPint _game_id, const MTPstring &_start_param) { + return MTP::internal::TypeCreator::new_keyboardButtonGame(_text, _game_title, _game_id, _start_param); +} inline MTPkeyboardButtonRow::MTPkeyboardButtonRow() : mtpDataOwner(new MTPDkeyboardButtonRow()) { } @@ -35927,34 +36470,168 @@ inline MTPmessages_stickerSetInstallResult MTP_messages_stickerSetInstallResultA return MTP::internal::TypeCreator::new_messages_stickerSetInstallResultArchive(_sets); } -inline MTPstickerSetCovered::MTPstickerSetCovered() : mtpDataOwner(new MTPDstickerSetCovered()) { -} - inline uint32 MTPstickerSetCovered::innerLength() const { - const MTPDstickerSetCovered &v(c_stickerSetCovered()); - return v.vset.innerLength() + v.vcover.innerLength(); + switch (_type) { + case mtpc_stickerSetCovered: { + const MTPDstickerSetCovered &v(c_stickerSetCovered()); + return v.vset.innerLength() + v.vcover.innerLength(); + } + case mtpc_stickerSetMultiCovered: { + const MTPDstickerSetMultiCovered &v(c_stickerSetMultiCovered()); + return v.vset.innerLength() + v.vcovers.innerLength(); + } + } + return 0; } inline mtpTypeId MTPstickerSetCovered::type() const { - return mtpc_stickerSetCovered; + if (!_type) throw mtpErrorUninitialized(); + return _type; } inline void MTPstickerSetCovered::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { - if (cons != mtpc_stickerSetCovered) throw mtpErrorUnexpected(cons, "MTPstickerSetCovered"); - - if (!data) setData(new MTPDstickerSetCovered()); - MTPDstickerSetCovered &v(_stickerSetCovered()); - v.vset.read(from, end); - v.vcover.read(from, end); + if (cons != _type) setData(0); + switch (cons) { + case mtpc_stickerSetCovered: _type = cons; { + if (!data) setData(new MTPDstickerSetCovered()); + MTPDstickerSetCovered &v(_stickerSetCovered()); + v.vset.read(from, end); + v.vcover.read(from, end); + } break; + case mtpc_stickerSetMultiCovered: _type = cons; { + if (!data) setData(new MTPDstickerSetMultiCovered()); + MTPDstickerSetMultiCovered &v(_stickerSetMultiCovered()); + v.vset.read(from, end); + v.vcovers.read(from, end); + } break; + default: throw mtpErrorUnexpected(cons, "MTPstickerSetCovered"); + } } inline void MTPstickerSetCovered::write(mtpBuffer &to) const { - const MTPDstickerSetCovered &v(c_stickerSetCovered()); - v.vset.write(to); - v.vcover.write(to); + switch (_type) { + case mtpc_stickerSetCovered: { + const MTPDstickerSetCovered &v(c_stickerSetCovered()); + v.vset.write(to); + v.vcover.write(to); + } break; + case mtpc_stickerSetMultiCovered: { + const MTPDstickerSetMultiCovered &v(c_stickerSetMultiCovered()); + v.vset.write(to); + v.vcovers.write(to); + } break; + } } -inline MTPstickerSetCovered::MTPstickerSetCovered(MTPDstickerSetCovered *_data) : mtpDataOwner(_data) { +inline MTPstickerSetCovered::MTPstickerSetCovered(mtpTypeId type) : mtpDataOwner(0), _type(type) { + switch (type) { + case mtpc_stickerSetCovered: setData(new MTPDstickerSetCovered()); break; + case mtpc_stickerSetMultiCovered: setData(new MTPDstickerSetMultiCovered()); break; + default: throw mtpErrorBadTypeId(type, "MTPstickerSetCovered"); + } +} +inline MTPstickerSetCovered::MTPstickerSetCovered(MTPDstickerSetCovered *_data) : mtpDataOwner(_data), _type(mtpc_stickerSetCovered) { +} +inline MTPstickerSetCovered::MTPstickerSetCovered(MTPDstickerSetMultiCovered *_data) : mtpDataOwner(_data), _type(mtpc_stickerSetMultiCovered) { } inline MTPstickerSetCovered MTP_stickerSetCovered(const MTPStickerSet &_set, const MTPDocument &_cover) { return MTP::internal::TypeCreator::new_stickerSetCovered(_set, _cover); } +inline MTPstickerSetCovered MTP_stickerSetMultiCovered(const MTPStickerSet &_set, const MTPVector &_covers) { + return MTP::internal::TypeCreator::new_stickerSetMultiCovered(_set, _covers); +} + +inline MTPmaskCoords::MTPmaskCoords() : mtpDataOwner(new MTPDmaskCoords()) { +} + +inline uint32 MTPmaskCoords::innerLength() const { + const MTPDmaskCoords &v(c_maskCoords()); + return v.vn.innerLength() + v.vx.innerLength() + v.vy.innerLength() + v.vzoom.innerLength(); +} +inline mtpTypeId MTPmaskCoords::type() const { + return mtpc_maskCoords; +} +inline void MTPmaskCoords::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != mtpc_maskCoords) throw mtpErrorUnexpected(cons, "MTPmaskCoords"); + + if (!data) setData(new MTPDmaskCoords()); + MTPDmaskCoords &v(_maskCoords()); + v.vn.read(from, end); + v.vx.read(from, end); + v.vy.read(from, end); + v.vzoom.read(from, end); +} +inline void MTPmaskCoords::write(mtpBuffer &to) const { + const MTPDmaskCoords &v(c_maskCoords()); + v.vn.write(to); + v.vx.write(to); + v.vy.write(to); + v.vzoom.write(to); +} +inline MTPmaskCoords::MTPmaskCoords(MTPDmaskCoords *_data) : mtpDataOwner(_data) { +} +inline MTPmaskCoords MTP_maskCoords(MTPint _n, const MTPdouble &_x, const MTPdouble &_y, const MTPdouble &_zoom) { + return MTP::internal::TypeCreator::new_maskCoords(_n, _x, _y, _zoom); +} + +inline uint32 MTPinputStickeredMedia::innerLength() const { + switch (_type) { + case mtpc_inputStickeredMediaPhoto: { + const MTPDinputStickeredMediaPhoto &v(c_inputStickeredMediaPhoto()); + return v.vid.innerLength(); + } + case mtpc_inputStickeredMediaDocument: { + const MTPDinputStickeredMediaDocument &v(c_inputStickeredMediaDocument()); + return v.vid.innerLength(); + } + } + return 0; +} +inline mtpTypeId MTPinputStickeredMedia::type() const { + if (!_type) throw mtpErrorUninitialized(); + return _type; +} +inline void MTPinputStickeredMedia::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) { + if (cons != _type) setData(0); + switch (cons) { + case mtpc_inputStickeredMediaPhoto: _type = cons; { + if (!data) setData(new MTPDinputStickeredMediaPhoto()); + MTPDinputStickeredMediaPhoto &v(_inputStickeredMediaPhoto()); + v.vid.read(from, end); + } break; + case mtpc_inputStickeredMediaDocument: _type = cons; { + if (!data) setData(new MTPDinputStickeredMediaDocument()); + MTPDinputStickeredMediaDocument &v(_inputStickeredMediaDocument()); + v.vid.read(from, end); + } break; + default: throw mtpErrorUnexpected(cons, "MTPinputStickeredMedia"); + } +} +inline void MTPinputStickeredMedia::write(mtpBuffer &to) const { + switch (_type) { + case mtpc_inputStickeredMediaPhoto: { + const MTPDinputStickeredMediaPhoto &v(c_inputStickeredMediaPhoto()); + v.vid.write(to); + } break; + case mtpc_inputStickeredMediaDocument: { + const MTPDinputStickeredMediaDocument &v(c_inputStickeredMediaDocument()); + v.vid.write(to); + } break; + } +} +inline MTPinputStickeredMedia::MTPinputStickeredMedia(mtpTypeId type) : mtpDataOwner(0), _type(type) { + switch (type) { + case mtpc_inputStickeredMediaPhoto: setData(new MTPDinputStickeredMediaPhoto()); break; + case mtpc_inputStickeredMediaDocument: setData(new MTPDinputStickeredMediaDocument()); break; + default: throw mtpErrorBadTypeId(type, "MTPinputStickeredMedia"); + } +} +inline MTPinputStickeredMedia::MTPinputStickeredMedia(MTPDinputStickeredMediaPhoto *_data) : mtpDataOwner(_data), _type(mtpc_inputStickeredMediaPhoto) { +} +inline MTPinputStickeredMedia::MTPinputStickeredMedia(MTPDinputStickeredMediaDocument *_data) : mtpDataOwner(_data), _type(mtpc_inputStickeredMediaDocument) { +} +inline MTPinputStickeredMedia MTP_inputStickeredMediaPhoto(const MTPInputPhoto &_id) { + return MTP::internal::TypeCreator::new_inputStickeredMediaPhoto(_id); +} +inline MTPinputStickeredMedia MTP_inputStickeredMediaDocument(const MTPInputDocument &_id) { + return MTP::internal::TypeCreator::new_inputStickeredMediaDocument(_id); +} inline MTPDmessage::Flags mtpCastFlags(MTPDmessageService::Flags flags) { return MTPDmessage::Flags(QFlag(flags)); } inline MTPDmessage::Flags mtpCastFlags(MTPflags flags) { return mtpCastFlags(flags.v); } inline MTPDmessage::Flags mtpCastFlags(MTPDupdateShortMessage::Flags flags) { return MTPDmessage::Flags(QFlag(flags)); } diff --git a/Telegram/SourceFiles/serialize/serialize_document.cpp b/Telegram/SourceFiles/serialize/serialize_document.cpp index b83f420f34..089f942e69 100644 --- a/Telegram/SourceFiles/serialize/serialize_document.cpp +++ b/Telegram/SourceFiles/serialize/serialize_document.cpp @@ -90,8 +90,9 @@ DocumentData *Document::readFromStreamHelper(int streamAppVersion, QDataStream & thumb = readStorageImageLocation(stream); + MTPDdocumentAttributeSticker::Flags stickerFlags = 0; if (typeOfSet == StickerSetTypeEmpty) { - attributes.push_back(MTP_documentAttributeSticker(MTP_string(alt), MTP_inputStickerSetEmpty())); + attributes.push_back(MTP_documentAttributeSticker(MTP_flags(stickerFlags), MTP_string(alt), MTP_inputStickerSetEmpty(), MTPMaskCoords())); } else if (info) { if (info->setId == Stickers::DefaultSetId || info->setId == Stickers::CloudRecentSetId || info->setId == Stickers::CustomSetId) { typeOfSet = StickerSetTypeEmpty; @@ -99,14 +100,14 @@ DocumentData *Document::readFromStreamHelper(int streamAppVersion, QDataStream & switch (typeOfSet) { case StickerSetTypeID: { - attributes.push_back(MTP_documentAttributeSticker(MTP_string(alt), MTP_inputStickerSetID(MTP_long(info->setId), MTP_long(info->accessHash)))); + attributes.push_back(MTP_documentAttributeSticker(MTP_flags(stickerFlags), MTP_string(alt), MTP_inputStickerSetID(MTP_long(info->setId), MTP_long(info->accessHash)), MTPMaskCoords())); } break; case StickerSetTypeShortName: { - attributes.push_back(MTP_documentAttributeSticker(MTP_string(alt), MTP_inputStickerSetShortName(MTP_string(info->shortName)))); + attributes.push_back(MTP_documentAttributeSticker(MTP_flags(stickerFlags), MTP_string(alt), MTP_inputStickerSetShortName(MTP_string(info->shortName)), MTPMaskCoords())); } break; case StickerSetTypeEmpty: default: { - attributes.push_back(MTP_documentAttributeSticker(MTP_string(alt), MTP_inputStickerSetEmpty())); + attributes.push_back(MTP_documentAttributeSticker(MTP_flags(stickerFlags), MTP_string(alt), MTP_inputStickerSetEmpty(), MTPMaskCoords())); } break; } } diff --git a/Telegram/SourceFiles/stickers/emoji_pan.cpp b/Telegram/SourceFiles/stickers/emoji_pan.cpp new file mode 100644 index 0000000000..a39f1e1944 --- /dev/null +++ b/Telegram/SourceFiles/stickers/emoji_pan.cpp @@ -0,0 +1,3784 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "stickers/emoji_pan.h" + +#include "styles/style_stickers.h" +#include "boxes/confirmbox.h" +#include "boxes/stickersetbox.h" +#include "inline_bots/inline_bot_result.h" +#include "inline_bots/inline_bot_layout_item.h" +#include "dialogs/dialogs_layout.h" +#include "stickers/stickers.h" +#include "historywidget.h" +#include "localstorage.h" +#include "lang.h" +#include "mainwindow.h" +#include "apiwrap.h" +#include "mainwidget.h" + +namespace internal { + +EmojiColorPicker::EmojiColorPicker() : TWidget() +, _a_selected(animation(this, &EmojiColorPicker::step_selected)) +, a_opacity(0) +, _a_appearance(animation(this, &EmojiColorPicker::step_appearance)) +, _shadow(st::dropdownDef.shadow) { + memset(_variants, 0, sizeof(_variants)); + memset(_hovers, 0, sizeof(_hovers)); + + setMouseTracking(true); + setFocusPolicy(Qt::NoFocus); + + int32 w = st::emojiPanSize.width() * (EmojiColorsCount + 1) + 4 * st::emojiColorsPadding + st::emojiColorsSep + st::dropdownDef.shadow.pxWidth() * 2; + int32 h = 2 * st::emojiColorsPadding + st::emojiPanSize.height() + st::dropdownDef.shadow.pxHeight() * 2; + resize(w, h); + + _hideTimer.setSingleShot(true); + connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); +} + +void EmojiColorPicker::showEmoji(uint32 code) { + EmojiPtr e = emojiGet(code); + if (!e || e == TwoSymbolEmoji || !e->color) { + return; + } + _ignoreShow = false; + + _variants[0] = e; + _variants[1] = emojiGet(e, 0xD83CDFFB); + _variants[2] = emojiGet(e, 0xD83CDFFC); + _variants[3] = emojiGet(e, 0xD83CDFFD); + _variants[4] = emojiGet(e, 0xD83CDFFE); + _variants[5] = emojiGet(e, 0xD83CDFFF); + + if (!_cache.isNull()) _cache = QPixmap(); + showStart(); +} + +void EmojiColorPicker::paintEvent(QPaintEvent *e) { + Painter p(this); + + if (!_cache.isNull()) { + p.setOpacity(a_opacity.current()); + } + if (e->rect() != rect()) { + p.setClipRect(e->rect()); + } + + int32 w = st::dropdownDef.shadow.pxWidth(), h = st::dropdownDef.shadow.pxHeight(); + QRect r = QRect(w, h, width() - 2 * w, height() - 2 * h); + _shadow.paint(p, r, st::dropdownDef.shadowShift); + + if (_cache.isNull()) { + p.fillRect(e->rect().intersected(r), st::white->b); + + int32 x = w + 2 * st::emojiColorsPadding + st::emojiPanSize.width(); + if (rtl()) x = width() - x - st::emojiColorsSep; + p.fillRect(x, h + st::emojiColorsPadding, st::emojiColorsSep, r.height() - st::emojiColorsPadding * 2, st::emojiColorsSepColor->b); + + if (!_variants[0]) return; + for (int i = 0; i < EmojiColorsCount + 1; ++i) { + drawVariant(p, i); + } + } else { + p.drawPixmap(r.left(), r.top(), _cache); + } + +} + +void EmojiColorPicker::enterEvent(QEvent *e) { + _hideTimer.stop(); + if (_hiding) showStart(); + TWidget::enterEvent(e); +} + +void EmojiColorPicker::leaveEvent(QEvent *e) { + TWidget::leaveEvent(e); +} + +void EmojiColorPicker::mousePressEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); + _pressedSel = _selected; +} + +void EmojiColorPicker::mouseReleaseEvent(QMouseEvent *e) { + _lastMousePos = e ? e->globalPos() : QCursor::pos(); + int32 pressed = _pressedSel; + _pressedSel = -1; + + updateSelected(); + if (_selected >= 0 && (pressed < 0 || _selected == pressed)) { + emit emojiSelected(_variants[_selected]); + } + _ignoreShow = true; + hideStart(); +} + +void EmojiColorPicker::mouseMoveEvent(QMouseEvent *e) { + _lastMousePos = e ? e->globalPos() : QCursor::pos(); + updateSelected(); +} + +void EmojiColorPicker::step_appearance(float64 ms, bool timer) { + if (_cache.isNull()) { + _a_appearance.stop(); + return; + } + float64 dt = ms / st::dropdownDef.duration; + if (dt >= 1) { + a_opacity.finish(); + _cache = QPixmap(); + if (_hiding) { + hide(); + emit hidden(); + } else { + _lastMousePos = QCursor::pos(); + updateSelected(); + } + _a_appearance.stop(); + } else { + a_opacity.update(dt, anim::linear); + } + if (timer) update(); +} + +void EmojiColorPicker::step_selected(uint64 ms, bool timer) { + QRegion toUpdate; + for (EmojiAnimations::iterator i = _emojiAnimations.begin(); i != _emojiAnimations.end();) { + int index = qAbs(i.key()) - 1; + float64 dt = float64(ms - i.value()) / st::emojiPanDuration; + if (dt >= 1) { + _hovers[index] = (i.key() > 0) ? 1 : 0; + i = _emojiAnimations.erase(i); + } else { + _hovers[index] = (i.key() > 0) ? dt : (1 - dt); + ++i; + } + toUpdate += QRect(st::dropdownDef.shadow.pxWidth() + st::emojiColorsPadding + index * st::emojiPanSize.width() + (index ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::dropdownDef.shadow.pxHeight() + st::emojiColorsPadding, st::emojiPanSize.width(), st::emojiPanSize.height()); + } + if (timer) rtlupdate(toUpdate.boundingRect()); + if (_emojiAnimations.isEmpty()) _a_selected.stop(); +} + +void EmojiColorPicker::hideStart(bool fast) { + if (fast) { + clearSelection(true); + if (_a_appearance.animating()) _a_appearance.stop(); + if (_a_selected.animating()) _a_selected.stop(); + a_opacity = anim::fvalue(0); + _cache = QPixmap(); + hide(); + emit hidden(); + } else { + if (_cache.isNull()) { + int32 w = st::dropdownDef.shadow.pxWidth(), h = st::dropdownDef.shadow.pxHeight(); + _cache = myGrab(this, QRect(w, h, width() - 2 * w, height() - 2 * h)); + clearSelection(true); + } + _hiding = true; + a_opacity.start(0); + _a_appearance.start(); + } +} + +void EmojiColorPicker::showStart() { + if (_ignoreShow) return; + + _hiding = false; + if (!isHidden() && a_opacity.current() == 1) { + if (_a_appearance.animating()) { + _a_appearance.stop(); + _cache = QPixmap(); + } + return; + } + if (_cache.isNull()) { + int32 w = st::dropdownDef.shadow.pxWidth(), h = st::dropdownDef.shadow.pxHeight(); + _cache = myGrab(this, QRect(w, h, width() - 2 * w, height() - 2 * h)); + clearSelection(true); + } + show(); + a_opacity.start(1); + _a_appearance.start(); +} + +void EmojiColorPicker::clearSelection(bool fast) { + _pressedSel = -1; + _lastMousePos = mapToGlobal(QPoint(-10, -10)); + if (fast) { + _selected = -1; + memset(_hovers, 0, sizeof(_hovers)); + _emojiAnimations.clear(); + } else { + updateSelected(); + } +} + +void EmojiColorPicker::updateSelected() { + int32 selIndex = -1; + QPoint p(mapFromGlobal(_lastMousePos)); + int32 sx = rtl() ? (width() - p.x()) : p.x(), y = p.y() - st::dropdownDef.shadow.pxHeight() - st::emojiColorsPadding; + if (y >= 0 && y < st::emojiPanSize.height()) { + int32 x = sx - st::dropdownDef.shadow.pxWidth() - st::emojiColorsPadding; + if (x >= 0 && x < st::emojiPanSize.width()) { + selIndex = 0; + } else { + x -= st::emojiPanSize.width() + 2 * st::emojiColorsPadding + st::emojiColorsSep; + if (x >= 0 && x < st::emojiPanSize.width() * EmojiColorsCount) { + selIndex = (x / st::emojiPanSize.width()) + 1; + } + } + } + + bool startanim = false; + if (selIndex != _selected) { + if (_selected >= 0) { + _emojiAnimations.remove(_selected + 1); + if (_emojiAnimations.find(-_selected - 1) == _emojiAnimations.end()) { + if (_emojiAnimations.isEmpty()) startanim = true; + _emojiAnimations.insert(-_selected - 1, getms()); + } + } + _selected = selIndex; + if (_selected >= 0) { + _emojiAnimations.remove(-_selected - 1); + if (_emojiAnimations.find(_selected + 1) == _emojiAnimations.end()) { + if (_emojiAnimations.isEmpty()) startanim = true; + _emojiAnimations.insert(_selected + 1, getms()); + } + } + setCursor((_selected >= 0) ? style::cur_pointer : style::cur_default); + } + if (startanim && !_a_selected.animating()) _a_selected.start(); +} + +void EmojiColorPicker::drawVariant(Painter &p, int variant) { + float64 hover = _hovers[variant]; + + QPoint w(st::dropdownDef.shadow.pxWidth() + st::emojiColorsPadding + variant * st::emojiPanSize.width() + (variant ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::dropdownDef.shadow.pxHeight() + st::emojiColorsPadding); + if (hover > 0) { + p.setOpacity(hover); + QPoint tl(w); + if (rtl()) tl.setX(width() - tl.x() - st::emojiPanSize.width()); + App::roundRect(p, QRect(tl, st::emojiPanSize), st::emojiPanHover, StickerHoverCorners); + p.setOpacity(1); + } + int esize = EmojiSizes[EIndex + 1]; + p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_variants[variant]->x * esize, _variants[variant]->y * esize, esize, esize)); +} + +EmojiPanInner::EmojiPanInner() : ScrolledWidget() +, _maxHeight(int(st::emojiPanMaxHeight) - st::rbEmoji.height) +, _a_selected(animation(this, &EmojiPanInner::step_selected)) { + resize(st::emojiPanWidth - st::emojiScroll.width, countHeight()); + + setMouseTracking(true); + setFocusPolicy(Qt::NoFocus); + setAttribute(Qt::WA_OpaquePaintEvent); + + _picker.hide(); + + _esize = EmojiSizes[EIndex + 1]; + + for (int32 i = 0; i < emojiTabCount; ++i) { + _counts[i] = emojiPackCount(emojiTabAtIndex(i)); + _hovers[i] = QVector(_counts[i], 0); + } + + _showPickerTimer.setSingleShot(true); + connect(&_showPickerTimer, SIGNAL(timeout()), this, SLOT(onShowPicker())); + connect(&_picker, SIGNAL(emojiSelected(EmojiPtr)), this, SLOT(onColorSelected(EmojiPtr))); + connect(&_picker, SIGNAL(hidden()), this, SLOT(onPickerHidden())); +} + +void EmojiPanInner::setMaxHeight(int32 h) { + _maxHeight = h; + resize(st::emojiPanWidth - st::emojiScroll.width, countHeight()); +} + +void EmojiPanInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; +} + +int EmojiPanInner::countHeight() { + int result = 0; + for (int i = 0; i < emojiTabCount; ++i) { + int cnt = emojiPackCount(emojiTabAtIndex(i)), rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0); + result += st::emojiPanHeader + rows * st::emojiPanSize.height(); + } + + return result + st::emojiPanPadding; +} + +void EmojiPanInner::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 fromcol = floorclamp(r.x() - st::emojiPanPadding, st::emojiPanSize.width(), 0, EmojiPanPerRow); + int32 tocol = ceilclamp(r.x() + r.width() - st::emojiPanPadding, st::emojiPanSize.width(), 0, EmojiPanPerRow); + if (rtl()) { + qSwap(fromcol, tocol); + fromcol = EmojiPanPerRow - fromcol; + tocol = EmojiPanPerRow - tocol; + } + + int32 y, tilly = 0; + for (int c = 0; c < emojiTabCount; ++c) { + y = tilly; + int32 size = _counts[c]; + int32 rows = (size / EmojiPanPerRow) + ((size % EmojiPanPerRow) ? 1 : 0); + tilly = y + st::emojiPanHeader + (rows * st::emojiPanSize.height()); + if (r.top() >= tilly) continue; + + y += st::emojiPanHeader; + if (_emojis[c].isEmpty()) { + _emojis[c] = emojiPack(emojiTabAtIndex(c)); + if (emojiTabAtIndex(c) != dbietRecent) { + for (EmojiPack::iterator i = _emojis[c].begin(), e = _emojis[c].end(); i != e; ++i) { + if ((*i)->color) { + EmojiColorVariants::const_iterator j = cEmojiVariants().constFind((*i)->code); + if (j != cEmojiVariants().cend()) { + EmojiPtr replace = emojiFromKey(j.value()); + if (replace) { + if (replace != TwoSymbolEmoji && replace->code == (*i)->code && replace->code2 == (*i)->code2) { + *i = replace; + } + } + } + } + } + } + } + + int32 fromrow = floorclamp(r.y() - y, st::emojiPanSize.height(), 0, rows); + int32 torow = ceilclamp(r.y() + r.height() - y, st::emojiPanSize.height(), 0, rows); + for (int32 i = fromrow; i < torow; ++i) { + for (int32 j = fromcol; j < tocol; ++j) { + int32 index = i * EmojiPanPerRow + j; + if (index >= size) break; + + float64 hover = (!_picker.isHidden() && c * MatrixRowShift + index == _pickerSel) ? 1 : _hovers[c][index]; + + QPoint w(st::emojiPanPadding + j * st::emojiPanSize.width(), y + i * st::emojiPanSize.height()); + if (hover > 0) { + p.setOpacity(hover); + QPoint tl(w); + if (rtl()) tl.setX(width() - tl.x() - st::emojiPanSize.width()); + App::roundRect(p, QRect(tl, st::emojiPanSize), st::emojiPanHover, StickerHoverCorners); + p.setOpacity(1); + } + p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (_esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (_esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_emojis[c][index]->x * _esize, _emojis[c][index]->y * _esize, _esize, _esize)); + } + } + } +} + +bool EmojiPanInner::checkPickerHide() { + if (!_picker.isHidden() && _selected == _pickerSel) { + _picker.hideStart(); + _pickerSel = -1; + updateSelected(); + return true; + } + return false; +} + +void EmojiPanInner::mousePressEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); + if (checkPickerHide()) { + return; + } + _pressedSel = _selected; + + if (_selected >= 0) { + int tab = (_selected / MatrixRowShift), sel = _selected % MatrixRowShift; + if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) { + _pickerSel = _selected; + setCursor(style::cur_default); + if (cEmojiVariants().constFind(_emojis[tab][sel]->code) == cEmojiVariants().cend()) { + onShowPicker(); + } else { + _showPickerTimer.start(500); + } + } + } +} + +void EmojiPanInner::mouseReleaseEvent(QMouseEvent *e) { + int32 pressed = _pressedSel; + _pressedSel = -1; + + _lastMousePos = e->globalPos(); + if (!_picker.isHidden()) { + if (_picker.rect().contains(_picker.mapFromGlobal(_lastMousePos))) { + return _picker.mouseReleaseEvent(0); + } else if (_pickerSel >= 0) { + int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift; + if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) { + if (cEmojiVariants().constFind(_emojis[tab][sel]->code) != cEmojiVariants().cend()) { + _picker.hideStart(); + _pickerSel = -1; + } + } + } + } + updateSelected(); + + if (_showPickerTimer.isActive()) { + _showPickerTimer.stop(); + _pickerSel = -1; + _picker.hide(); + } + + if (_selected < 0 || _selected != pressed) return; + + if (_selected >= emojiTabCount * MatrixRowShift) { + return; + } + + int tab = (_selected / MatrixRowShift), sel = _selected % MatrixRowShift; + if (sel < _emojis[tab].size()) { + EmojiPtr emoji(_emojis[tab][sel]); + if (emoji->color && !_picker.isHidden()) return; + + selectEmoji(emoji); + } +} + +void EmojiPanInner::selectEmoji(EmojiPtr emoji) { + RecentEmojiPack &recent(cGetRecentEmojis()); + RecentEmojiPack::iterator i = recent.begin(), e = recent.end(); + for (; i != e; ++i) { + if (i->first == emoji) { + ++i->second; + if (i->second > 0x8000) { + for (RecentEmojiPack::iterator j = recent.begin(); j != e; ++j) { + if (j->second > 1) { + j->second /= 2; + } else { + j->second = 1; + } + } + } + for (; i != recent.begin(); --i) { + if ((i - 1)->second > i->second) { + break; + } + qSwap(*i, *(i - 1)); + } + break; + } + } + if (i == e) { + 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) { + break; + } + qSwap(*i, *(i - 1)); + } + } + emit saveConfigDelayed(SaveRecentEmojisTimeout); + + emit selected(emoji); +} + +void EmojiPanInner::onShowPicker() { + if (_pickerSel < 0) return; + + int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift; + 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 % EmojiPanPerRow)) : _counts[c], rows = (size / EmojiPanPerRow) + ((size % EmojiPanPerRow) ? 1 : 0); + y += st::emojiPanHeader + (rows * st::emojiPanSize.height()); + } + y -= _picker.height() - st::buttonRadius + _visibleTop; + if (y < 0) { + y += _picker.height() - st::buttonRadius + st::emojiPanSize.height() - st::buttonRadius; + } + int xmax = width() - _picker.width(); + float64 coef = float64(sel % EmojiPanPerRow) / float64(EmojiPanPerRow - 1); + if (rtl()) coef = 1. - coef; + _picker.move(qRound(xmax * coef), y); + + _picker.showEmoji(_emojis[tab][sel]->code); + emit disableScroll(true); + } +} + +void EmojiPanInner::onPickerHidden() { + _pickerSel = -1; + update(); + emit disableScroll(false); + + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +QRect EmojiPanInner::emojiRect(int tab, int sel) { + int x = 0, y = 0; + for (int i = 0; i < emojiTabCount; ++i) { + if (i == tab) { + int rows = (sel / EmojiPanPerRow); + y += st::emojiPanHeader + rows * st::emojiPanSize.height(); + x = st::emojiPanPadding + ((sel % EmojiPanPerRow) * st::emojiPanSize.width()); + break; + } else { + int cnt = _counts[i]; + int rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0); + y += st::emojiPanHeader + rows * st::emojiPanSize.height(); + } + } + return QRect(x, y, st::emojiPanSize.width(), st::emojiPanSize.height()); +} + +void EmojiPanInner::onColorSelected(EmojiPtr emoji) { + if (emoji->color) { + cRefEmojiVariants().insert(emoji->code, emojiKey(emoji)); + } + if (_pickerSel >= 0) { + int tab = (_pickerSel / MatrixRowShift), sel = _pickerSel % MatrixRowShift; + if (tab >= 0 && tab < emojiTabCount) { + _emojis[tab][sel] = emoji; + rtlupdate(emojiRect(tab, sel)); + } + } + selectEmoji(emoji); + _picker.hideStart(); +} + +void EmojiPanInner::mouseMoveEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + if (!_picker.isHidden()) { + if (_picker.rect().contains(_picker.mapFromGlobal(_lastMousePos))) { + return _picker.mouseMoveEvent(0); + } else { + _picker.clearSelection(); + } + } + updateSelected(); +} + +void EmojiPanInner::leaveEvent(QEvent *e) { + clearSelection(); +} + +void EmojiPanInner::leaveToChildEvent(QEvent *e, QWidget *child) { + clearSelection(); +} + +void EmojiPanInner::enterFromChildEvent(QEvent *e, QWidget *child) { + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +void EmojiPanInner::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 / MatrixRowShift), sel = index % MatrixRowShift; + _hovers[tab][sel] = 0; + } + _animations.clear(); + if (_selected >= 0) { + int index = qAbs(_selected), tab = (index / MatrixRowShift), sel = index % MatrixRowShift; + _hovers[tab][sel] = 0; + } + if (_pressedSel >= 0) { + int index = qAbs(_pressedSel), tab = (index / MatrixRowShift), sel = index % MatrixRowShift; + _hovers[tab][sel] = 0; + } + _selected = _pressedSel = -1; + _a_selected.stop(); + } else { + updateSelected(); + } +} + +DBIEmojiTab EmojiPanInner::currentTab(int yOffset) const { + int y, ytill = 0; + 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 (yOffset < ytill) { + return emojiTabAtIndex(c); + } + } + return emojiTabAtIndex(emojiTabCount - 1); +} + +void EmojiPanInner::hideFinish() { + if (!_picker.isHidden()) { + _picker.hideStart(true); + _pickerSel = -1; + clearSelection(true); + } +} + +void EmojiPanInner::refreshRecent() { + clearSelection(true); + _counts[0] = emojiPackCount(dbietRecent); + if (_hovers[0].size() != _counts[0]) _hovers[0] = QVector(_counts[0], 0); + _emojis[0] = emojiPack(dbietRecent); + int32 h = countHeight(); + if (h != height()) { + resize(width(), h); + emit needRefreshPanels(); + } +} + +void EmojiPanInner::fillPanels(QVector &panels) { + if (_picker.parentWidget() != parentWidget()) { + _picker.setParent(parentWidget()); + } + for (int32 i = 0; i < panels.size(); ++i) { + panels.at(i)->hide(); + panels.at(i)->deleteLater(); + } + panels.clear(); + + int y = 0; + panels.reserve(emojiTabCount); + for (int c = 0; c < emojiTabCount; ++c) { + panels.push_back(new EmojiPanel(parentWidget(), lang(LangKey(lng_emoji_category0 + c)), Stickers::NoneSetId, true, y)); + connect(panels.back(), SIGNAL(mousePressed()), this, SLOT(checkPickerHide())); + int cnt = _counts[c], rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0); + panels.back()->show(); + y += st::emojiPanHeader + rows * st::emojiPanSize.height(); + } + _picker.raise(); +} + +void EmojiPanInner::refreshPanels(QVector &panels) { + if (panels.size() != emojiTabCount) return fillPanels(panels); + + int32 y = 0; + for (int c = 0; c < emojiTabCount; ++c) { + panels.at(c)->setWantedY(y); + int cnt = _counts[c], rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0); + y += st::emojiPanHeader + rows * st::emojiPanSize.height(); + } +} + +void EmojiPanInner::updateSelected() { + if (_pressedSel >= 0 || _pickerSel >= 0) return; + + int32 selIndex = -1; + QPoint p(mapFromGlobal(_lastMousePos)); + 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 * MatrixRowShift; + } + } + break; + } + } + + bool startanim = false; + int oldSel = _selected, newSel = selIndex; + + 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 (newSel >= 0 && !_picker.isHidden()) { + if (newSel != _pickerSel) { + _picker.hideStart(); + } else { + _picker.showStart(); + } + } + } + + _selected = selIndex; + if (startanim && !_a_selected.animating()) _a_selected.start(); +} + +void EmojiPanInner::step_selected(uint64 ms, bool timer) { + QRegion toUpdate; + for (Animations::iterator i = _animations.begin(); i != _animations.end();) { + int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift; + float64 dt = float64(ms - i.value()) / st::emojiPanDuration; + if (dt >= 1) { + _hovers[tab][sel] = (i.key() > 0) ? 1 : 0; + i = _animations.erase(i); + } else { + _hovers[tab][sel] = (i.key() > 0) ? dt : (1 - dt); + ++i; + } + toUpdate += emojiRect(tab, sel); + } + if (timer) rtlupdate(toUpdate.boundingRect()); + if (_animations.isEmpty()) _a_selected.stop(); +} + +void InlineCacheEntry::clearResults() { + for_const (const InlineBots::Result *result, results) { + delete result; + } + results.clear(); +} + +void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) { + clearSelection(true); + + refreshRecent(); + + int32 y = 0; + for (int c = 0; c < emojiTabCount; ++c) { + if (emojiTabAtIndex(c) == packIndex) break; + int rows = (_counts[c] / EmojiPanPerRow) + ((_counts[c] % EmojiPanPerRow) ? 1 : 0); + y += st::emojiPanHeader + rows * st::emojiPanSize.height(); + } + + emit scrollToY(y); + + _lastMousePos = QCursor::pos(); + + update(); +} + +StickerPanInner::StickerPanInner() : ScrolledWidget() +, _a_selected(animation(this, &StickerPanInner::step_selected)) +, _section(cShowingSavedGifs() ? Section::Gifs : Section::Stickers) +, _addText(lang(lng_stickers_featured_add).toUpper()) +, _addWidth(st::featuredStickersAdd.font->width(_addText)) +, _settings(this, lang(lng_stickers_you_have)) { + setMaxHeight(st::emojiPanMaxHeight - st::rbEmoji.height); + + setMouseTracking(true); + setFocusPolicy(Qt::NoFocus); + setAttribute(Qt::WA_OpaquePaintEvent); + + connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(onImageLoaded())); + connect(&_settings, SIGNAL(clicked()), this, SLOT(onSettings())); + + _previewTimer.setSingleShot(true); + connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview())); + + _updateInlineItems.setSingleShot(true); + connect(&_updateInlineItems, SIGNAL(timeout()), this, SLOT(onUpdateInlineItems())); +} + +void StickerPanInner::setMaxHeight(int32 h) { + _maxHeight = h; + resize(st::emojiPanWidth - st::emojiScroll.width, countHeight()); + _settings.moveToLeft((st::emojiPanWidth - _settings.width()) / 2, height() / 3); +} + +void StickerPanInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + _visibleBottom = visibleBottom; + if (_visibleTop != visibleTop) { + _visibleTop = visibleTop; + _lastScrolled = getms(); + } + if (_section == Section::Featured) { + readVisibleSets(); + } +} + +void StickerPanInner::readVisibleSets() { + auto itemsVisibleTop = _visibleTop - st::emojiPanHeader; + auto itemsVisibleBottom = _visibleBottom - st::emojiPanHeader; + auto rowHeight = featuredRowHeight(); + int rowFrom = floorclamp(itemsVisibleTop, rowHeight, 0, _featuredSets.size()); + int rowTo = ceilclamp(itemsVisibleBottom, rowHeight, 0, _featuredSets.size()); + for (int i = rowFrom; i < rowTo; ++i) { + auto &set = _featuredSets[i]; + if (!(set.flags & MTPDstickerSet_ClientFlag::f_unread)) { + continue; + } + if (i * rowHeight < itemsVisibleTop || (i + 1) * rowHeight > itemsVisibleBottom) { + continue; + } + int count = qMin(set.pack.size(), static_cast(StickerPanPerRow)); + int loaded = 0; + for (int j = 0; j < count; ++j) { + if (set.pack[j]->thumb->loaded() || set.pack[j]->loaded()) { + ++loaded; + } + } + if (loaded == count) { + Stickers::markFeaturedAsRead(set.id); + } + } +} + +void StickerPanInner::onImageLoaded() { + update(); + readVisibleSets(); +} + +int StickerPanInner::featuredRowHeight() const { + return st::featuredStickersHeader + st::stickerPanSize.height() + st::featuredStickersSkip; +} + +int StickerPanInner::countHeight(bool plain) { + int result = 0, minLastH = plain ? 0 : (_maxHeight - st::stickerPanPadding); + if (showingInlineItems()) { + result = st::emojiPanHeader; + if (_switchPmButton) { + result += _switchPmButton->height() + st::inlineResultsSkip; + } + for (int i = 0, l = _inlineRows.count(); i < l; ++i) { + result += _inlineRows[i].height; + } + } else if (_section == Section::Featured) { + result = st::emojiPanHeader + shownSets().size() * featuredRowHeight(); + } else { + auto &sets = shownSets(); + for (int i = 0; i < sets.size(); ++i) { + int cnt = sets[i].pack.size(), rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0); + int h = st::emojiPanHeader + rows * st::stickerPanSize.height(); + if (i == sets.size() - 1 && h < minLastH) h = minLastH; + result += h; + } + } + return qMax(minLastH, result) + st::stickerPanPadding; +} + +StickerPanInner::~StickerPanInner() { + clearInlineRows(true); + deleteUnusedGifLayouts(); + deleteUnusedInlineLayouts(); +} + +QRect StickerPanInner::stickerRect(int tab, int sel) { + int x = 0, y = 0; + if (_section == Section::Featured) { + y += st::emojiPanHeader + (tab * featuredRowHeight()) + st::featuredStickersHeader; + x = st::stickerPanPadding + (sel * st::stickerPanSize.width()); + } else { + auto &sets = shownSets(); + for (int i = 0; i < sets.size(); ++i) { + if (i == tab) { + int rows = (((sel >= sets[i].pack.size()) ? (sel - sets[i].pack.size()) : sel) / StickerPanPerRow); + y += st::emojiPanHeader + rows * st::stickerPanSize.height(); + x = st::stickerPanPadding + ((sel % StickerPanPerRow) * st::stickerPanSize.width()); + break; + } else { + int cnt = sets[i].pack.size(); + int rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0); + y += st::emojiPanHeader + rows * st::stickerPanSize.height(); + } + } + } + return QRect(x, y, st::stickerPanSize.width(), st::stickerPanSize.height()); +} + +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); + + if (showingInlineItems()) { + paintInlineItems(p, r); + } else { + paintStickers(p, r); + } +} + +void StickerPanInner::paintInlineItems(Painter &p, const QRect &r) { + if (_inlineRows.isEmpty()) { + p.setFont(st::normalFont); + p.setPen(st::noContactsColor); + p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), lang(lng_inline_bot_no_results), style::al_center); + return; + } + InlineBots::Layout::PaintContext context(getms(), false, Ui::isLayerShown() || Ui::isMediaViewShown() || _previewShown, false); + + int top = st::emojiPanHeader; + if (_switchPmButton) { + top += _switchPmButton->height() + st::inlineResultsSkip; + } + + int fromx = rtl() ? (width() - r.x() - r.width()) : r.x(), tox = rtl() ? (width() - r.x()) : (r.x() + r.width()); + for (int row = 0, rows = _inlineRows.size(); row < rows; ++row) { + auto &inlineRow = _inlineRows[row]; + if (top >= r.top() + r.height()) break; + if (top + inlineRow.height > r.top()) { + int left = st::inlineResultsLeft; + if (row == rows - 1) context.lastRow = true; + for (int col = 0, cols = inlineRow.items.size(); col < cols; ++col) { + if (left >= tox) break; + + const InlineItem *item = inlineRow.items.at(col); + int w = item->width(); + if (left + w > fromx) { + p.translate(left, top); + item->paint(p, r.translated(-left, -top), &context); + p.translate(-left, -top); + } + left += w; + if (item->hasRightSkip()) { + left += st::inlineResultsSkip; + } + } + } + top += inlineRow.height; + } +} + +void StickerPanInner::paintStickers(Painter &p, const QRect &r) { + int32 fromcol = floorclamp(r.x() - st::stickerPanPadding, st::stickerPanSize.width(), 0, StickerPanPerRow); + int32 tocol = ceilclamp(r.x() + r.width() - st::stickerPanPadding, st::stickerPanSize.width(), 0, StickerPanPerRow); + if (rtl()) { + qSwap(fromcol, tocol); + fromcol = StickerPanPerRow - fromcol; + tocol = StickerPanPerRow - tocol; + } + + int y, tilly = 0; + + auto &sets = shownSets(); + if (_section == Section::Featured) { + tilly += st::emojiPanHeader; + for (int c = 0, l = sets.size(); c < l; ++c) { + y = tilly; + auto &set = sets[c]; + tilly = y + featuredRowHeight(); + if (r.top() >= tilly) continue; + if (y >= r.y() + r.height()) break; + + int size = set.pack.size(); + + int widthForTitle = featuredContentWidth() - st::emojiPanHeaderLeft; + if (featuredHasAddButton(c)) { + auto add = featuredAddRect(c); + auto selected = (_selectedFeaturedSetAdd == c); + auto textBg = selected ? st::featuredStickersAdd.textBgOver : st::featuredStickersAdd.textBg; + auto textTop = (selected && _selectedFeaturedSetAdd == _pressedFeaturedSetAdd) ? st::featuredStickersAdd.downTextTop : st::featuredStickersAdd.textTop; + + App::roundRect(p, myrtlrect(add), textBg, ImageRoundRadius::Small); + p.setFont(st::featuredStickersAdd.font); + p.setPen(selected ? st::featuredStickersAdd.textFgOver : st::featuredStickersAdd.textFg); + p.drawTextLeft(add.x() - (st::featuredStickersAdd.width / 2), add.y() + textTop, width(), _addText, _addWidth); + + widthForTitle -= add.width() - (st::featuredStickersAdd.width / 2); + } + if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { + widthForTitle -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip; + } + + auto titleText = set.title; + auto titleWidth = st::featuredStickersHeaderFont->width(titleText); + if (titleWidth > widthForTitle) { + titleText = st::featuredStickersHeaderFont->elided(titleText, widthForTitle); + titleWidth = st::featuredStickersHeaderFont->width(titleText); + } + p.setFont(st::featuredStickersHeaderFont); + p.setPen(st::featuredStickersHeaderFg); + p.drawTextLeft(st::emojiPanHeaderLeft, y + st::featuredStickersHeaderTop, width(), titleText, titleWidth); + + if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { + p.setPen(Qt::NoPen); + p.setBrush(st::stickersFeaturedUnreadBg); + + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + p.drawEllipse(rtlrect(st::emojiPanHeaderLeft + titleWidth + st::stickersFeaturedUnreadSkip, y + st::featuredStickersHeaderTop + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width())); + p.setRenderHint(QPainter::HighQualityAntialiasing, false); + } + + p.setFont(st::featuredStickersSubheaderFont); + p.setPen(st::featuredStickersSubheaderFg); + p.drawTextLeft(st::emojiPanHeaderLeft, y + st::featuredStickersSubheaderTop, width(), lng_stickers_count(lt_count, size)); + + y += st::featuredStickersHeader; + if (y >= r.y() + r.height()) break; + + for (int j = fromcol; j < tocol; ++j) { + int index = j; + if (index >= size) break; + + paintSticker(p, set, y, index); + } + } + } else { + for (int c = 0, l = sets.size(); c < l; ++c) { + y = tilly; + auto &set = sets[c]; + int32 size = set.pack.size(); + int32 rows = (size / StickerPanPerRow) + ((size % StickerPanPerRow) ? 1 : 0); + tilly = y + st::emojiPanHeader + (rows * st::stickerPanSize.height()); + if (r.y() >= tilly) continue; + + bool special = (set.flags & MTPDstickerSet::Flag::f_official); + y += st::emojiPanHeader; + if (y >= r.y() + r.height()) break; + + int fromrow = floorclamp(r.y() - y, st::stickerPanSize.height(), 0, rows); + int torow = ceilclamp(r.y() + r.height() - y, st::stickerPanSize.height(), 0, rows); + for (int i = fromrow; i < torow; ++i) { + for (int j = fromcol; j < tocol; ++j) { + int index = i * StickerPanPerRow + j; + if (index >= size) break; + + paintSticker(p, set, y, index); + } + } + } + } +} + +void StickerPanInner::paintSticker(Painter &p, Set &set, int y, int index) { + float64 hover = set.hovers[index]; + + auto sticker = set.pack[index]; + if (!sticker->sticker()) return; + + int row = (index / StickerPanPerRow), col = (index % StickerPanPerRow); + + QPoint pos(st::stickerPanPadding + col * st::stickerPanSize.width(), y + row * st::stickerPanSize.height()); + if (hover > 0) { + p.setOpacity(hover); + QPoint tl(pos); + if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width()); + App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners); + p.setOpacity(1); + } + + bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); + if (goodThumb) { + sticker->thumb->load(); + } else { + sticker->checkSticker(); + } + + float64 coef = qMin((st::stickerPanSize.width() - st::buttonRadius * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::buttonRadius * 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 && set.id == Stickers::RecentSetId && _custom.at(index)) { + float64 xHover = set.hovers[set.pack.size() + index]; + + QPoint xPos = pos + QPoint(st::stickerPanSize.width() - st::stickerPanDelete.pxWidth(), 0); + p.setOpacity(hover * (xHover + (1 - xHover) * st::stickerPanDeleteOpacity)); + p.drawSpriteLeft(xPos, width(), st::stickerPanDelete); + p.setOpacity(1); + } +} + +bool StickerPanInner::featuredHasAddButton(int index) const { + if (index < 0 || index >= _featuredSets.size()) { + return false; + } + auto flags = _featuredSets[index].flags; + return !(flags & MTPDstickerSet::Flag::f_installed) || (flags & MTPDstickerSet::Flag::f_archived); +} + +int StickerPanInner::featuredContentWidth() const { + return st::stickerPanPadding + (StickerPanPerRow * st::stickerPanSize.width()); +} + +QRect StickerPanInner::featuredAddRect(int index) const { + int addw = _addWidth - st::featuredStickersAdd.width; + int addh = st::featuredStickersAdd.height; + int addx = featuredContentWidth() - addw; + int addy = st::emojiPanHeader + index * featuredRowHeight() + st::featuredStickersAddTop; + return QRect(addx, addy, addw, addh); +} + +void StickerPanInner::mousePressEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); + + _pressed = _selected; + _pressedFeaturedSet = _selectedFeaturedSet; + _pressedFeaturedSetAdd = _selectedFeaturedSetAdd; + ClickHandler::pressed(); + _previewTimer.start(QApplication::startDragTime()); +} + +void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) { + _previewTimer.stop(); + + auto pressed = _pressed; + _pressed = -1; + auto pressedFeaturedSet = _pressedFeaturedSet; + _pressedFeaturedSet = -1; + auto pressedFeaturedSetAdd = _pressedFeaturedSetAdd; + if (_pressedFeaturedSetAdd != _selectedFeaturedSetAdd) { + update(); + } + _pressedFeaturedSetAdd = -1; + + ClickHandlerPtr activated = ClickHandler::unpressed(); + + _lastMousePos = e->globalPos(); + updateSelected(); + + if (_previewShown) { + _previewShown = false; + return; + } + + if (showingInlineItems()) { + if (_selected < 0 || _selected != pressed || !activated) { + return; + } + + if (dynamic_cast(activated.data())) { + int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift; + selectInlineResult(row, column); + } else { + App::activateClickHandler(activated, e->button()); + } + return; + } + + auto &sets = shownSets(); + if (_selected >= 0 && _selected < MatrixRowShift * sets.size() && _selected == pressed) { + int tab = (_selected / MatrixRowShift), sel = _selected % MatrixRowShift; + if (sets[tab].id == Stickers::RecentSetId && sel >= sets[tab].pack.size() && sel < sets[tab].pack.size() * 2 && _custom.at(sel - sets[tab].pack.size())) { + removeRecentSticker(tab, sel - sets[tab].pack.size()); + return; + } + if (sel < sets[tab].pack.size()) { + emit selected(sets[tab].pack[sel]); + } + } else if (_selectedFeaturedSet >= 0 && _selectedFeaturedSet < sets.size() && _selectedFeaturedSet == pressedFeaturedSet) { + emit displaySet(sets[_selectedFeaturedSet].id); + } else if (_selectedFeaturedSetAdd >= 0 && _selectedFeaturedSetAdd < sets.size() && _selectedFeaturedSetAdd == pressedFeaturedSetAdd) { + emit installSet(sets[_selectedFeaturedSetAdd].id); + } +} + +void StickerPanInner::selectInlineResult(int row, int column) { + if (row >= _inlineRows.size() || column >= _inlineRows.at(row).items.size()) { + return; + } + + auto item = _inlineRows[row].items[column]; + if (auto photo = item->getPhoto()) { + if (photo->medium->loaded() || photo->thumb->loaded()) { + emit selected(photo); + } else if (!photo->medium->loading()) { + photo->thumb->loadEvenCancelled(); + photo->medium->loadEvenCancelled(); + } + } else if (auto document = item->getDocument()) { + if (document->loaded()) { + emit selected(document); + } else if (document->loading()) { + document->cancel(); + } else { + DocumentOpenClickHandler::doOpen(document, ActionOnLoadNone); + } + } else if (auto inlineResult = item->getResult()) { + if (inlineResult->onChoose(item)) { + emit selected(inlineResult, _inlineBot); + } + } +} + +void StickerPanInner::removeRecentSticker(int tab, int index) { + if (_section != Section::Stickers || tab >= _mySets.size() || _mySets[tab].id != Stickers::RecentSetId) { + return; + } + + clearSelection(true); + bool refresh = false; + auto sticker = _mySets[tab].pack[index]; + auto &recent = cGetRecentStickers(); + for (int32 i = 0, l = recent.size(); i < l; ++i) { + if (recent.at(i).first == sticker) { + recent.removeAt(i); + Local::writeUserSettings(); + refresh = true; + break; + } + } + auto &sets = Global::RefStickerSets(); + auto it = sets.find(Stickers::CustomSetId); + if (it != sets.cend()) { + for (int 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::writeInstalledStickers(); + refresh = true; + break; + } + } + } + if (refresh) { + refreshRecentStickers(); + updateSelected(); + update(); + } +} + +void StickerPanInner::mouseMoveEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); +} + +void StickerPanInner::leaveEvent(QEvent *e) { + clearSelection(); +} + +void StickerPanInner::leaveToChildEvent(QEvent *e, QWidget *child) { + clearSelection(); +} + +void StickerPanInner::enterFromChildEvent(QEvent *e, QWidget *child) { + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +bool StickerPanInner::showSectionIcons() const { + return !inlineResultsShown(); +} + +void StickerPanInner::clearSelection(bool fast) { + if (fast) { + if (showingInlineItems()) { + if (_selected >= 0) { + int srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; + t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); + ClickHandler::clearActive(_inlineRows.at(srow).items.at(scol)); + setCursor(style::cur_default); + } + _selected = _pressed = -1; + return; + } + + auto &sets = shownSets(); + for (auto i = _animations.cbegin(); i != _animations.cend(); ++i) { + int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift; + sets[tab].hovers[sel] = 0; + } + _animations.clear(); + if (_selected >= 0) { + int index = qAbs(_selected), tab = (index / MatrixRowShift), sel = index % MatrixRowShift; + if (index >= 0 && tab < sets.size() && sets[tab].id == Stickers::RecentSetId && sel >= tab * MatrixRowShift + sets[tab].pack.size()) { + sets[tab].hovers[sel] = 0; + sel -= sets[tab].pack.size(); + } + sets[tab].hovers[sel] = 0; + } + if (_pressed >= 0) { + int index = qAbs(_pressed), tab = (index / MatrixRowShift), sel = index % MatrixRowShift; + if (index >= 0 && tab < sets.size() && sets[tab].id == Stickers::RecentSetId && sel >= tab * MatrixRowShift + sets[tab].pack.size()) { + sets[tab].hovers[sel] = 0; + sel -= sets[tab].pack.size(); + } + sets[tab].hovers[sel] = 0; + } + _selected = _pressed = -1; + _selectedFeaturedSet = _pressedFeaturedSet = -1; + _selectedFeaturedSetAdd = _pressedFeaturedSetAdd = -1; + _a_selected.stop(); + update(); + } else { + auto pos = _lastMousePos; + _lastMousePos = mapToGlobal(QPoint(-10, -10)); + updateSelected(); + _lastMousePos = pos; + } +} + +void StickerPanInner::hideFinish(bool completely) { + if (completely) { + auto itemForget = [](const InlineItem *item) { + if (auto document = item->getDocument()) { + document->forget(); + } + if (auto photo = item->getPhoto()) { + photo->forget(); + } + if (auto result = item->getResult()) { + result->forget(); + } + }; + clearInlineRows(false); + for_const (auto item, _gifLayouts) { + itemForget(item); + } + for_const (auto item, _inlineLayouts) { + itemForget(item); + } + } + if (_setGifCommand && _section == Section::Gifs) { + App::insertBotCommand(qsl(""), true); + } + _setGifCommand = false; + + // Reset to the recent stickers section. + if (_section == Section::Featured) { + _section = Section::Stickers; + } +} + +void StickerPanInner::refreshStickers() { + auto stickersShown = (_section == Section::Stickers || _section == Section::Featured); + if (stickersShown) { + clearSelection(true); + } + + _mySets.clear(); + _mySets.reserve(Global::StickerSetsOrder().size() + 1); + + refreshRecentStickers(false); + for_const (auto setId, Global::StickerSetsOrder()) { + appendSet(_mySets, setId, AppendSkip::Archived); + } + + _featuredSets.clear(); + _featuredSets.reserve(Global::FeaturedStickerSetsOrder().size()); + + for_const (auto setId, Global::FeaturedStickerSetsOrder()) { + appendSet(_featuredSets, setId, AppendSkip::Installed); + } + + if (stickersShown) { + int h = countHeight(); + if (h != height()) resize(width(), h); + + _settings.setVisible(_section == Section::Stickers && _mySets.isEmpty()); + } else { + _settings.hide(); + } + + emit refreshIcons(); + + // Hack: skip over animations to the very end, + // so that currently selected sticker won't get + // blinking background when refreshing stickers. + if (stickersShown) { + updateSelected(); + int sel = _selected, tab = sel / MatrixRowShift, xsel = -1; + if (sel >= 0) { + auto &sets = shownSets(); + if (tab < sets.size() && sets[tab].id == Stickers::RecentSetId && sel >= tab * MatrixRowShift + sets[tab].pack.size()) { + xsel = sel; + sel -= sets[tab].pack.size(); + } + auto i = _animations.find(sel + 1); + if (i != _animations.cend()) { + i.value() = (i.value() >= st::emojiPanDuration) ? (i.value() - st::emojiPanDuration) : 0; + } + if (xsel >= 0) { + auto j = _animations.find(xsel + 1); + if (j != _animations.cend()) { + j.value() = (j.value() >= st::emojiPanDuration) ? (j.value() - st::emojiPanDuration) : 0; + } + } + step_selected(getms(), true); + } + } +} + +bool StickerPanInner::inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth) { + InlineItem *layout = nullptr; + if (savedGif) { + layout = layoutPrepareSavedGif(savedGif, (_inlineRows.size() * MatrixRowShift) + row.items.size()); + } else if (result) { + layout = layoutPrepareInlineResult(result, (_inlineRows.size() * MatrixRowShift) + row.items.size()); + } + if (!layout) return false; + + layout->preload(); + if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { + layout->setPosition(_inlineRows.size() * MatrixRowShift); + } + + sumWidth += layout->maxWidth(); + if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { + sumWidth += st::inlineResultsSkip; + } + + row.items.push_back(layout); + return true; +} + +bool StickerPanInner::inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force) { + if (row.items.isEmpty()) return false; + + bool full = (row.items.size() >= InlineItemsMaxPerRow); + bool big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft); + if (full || big || force) { + _inlineRows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); + row = InlineRow(); + row.items.reserve(InlineItemsMaxPerRow); + sumWidth = 0; + return true; + } + return false; +} + +void StickerPanInner::refreshSavedGifs() { + if (_section == Section::Gifs) { + _settings.hide(); + clearInlineRows(false); + + auto &saved = cSavedGifs(); + if (saved.isEmpty()) { + showStickerSet(Stickers::RecentSetId); + return; + } else { + _inlineRows.reserve(saved.size()); + InlineRow row; + row.items.reserve(InlineItemsMaxPerRow); + int sumWidth = 0; + for_const (auto &gif, saved) { + inlineRowsAddItem(gif, 0, row, sumWidth); + } + inlineRowFinalize(row, sumWidth, true); + } + deleteUnusedGifLayouts(); + + int32 h = countHeight(); + if (h != height()) resize(width(), h); + + update(); + } + emit refreshIcons(); + + updateSelected(); +} + +void StickerPanInner::inlineBotChanged() { + _setGifCommand = false; + refreshInlineRows(nullptr, nullptr, true); +} + +void StickerPanInner::clearInlineRows(bool resultsDeleted) { + if (resultsDeleted) { + if (showingInlineItems()) { + _selected = _pressed = -1; + } + } else { + if (showingInlineItems()) { + clearSelection(true); + } + for_const (auto &row, _inlineRows) { + for_const (auto &item, row.items) { + item->setPosition(-1); + } + } + } + _inlineRows.clear(); +} + +InlineItem *StickerPanInner::layoutPrepareSavedGif(DocumentData *doc, int32 position) { + auto i = _gifLayouts.constFind(doc); + if (i == _gifLayouts.cend()) { + if (auto layout = InlineItem::createLayoutGif(doc)) { + i = _gifLayouts.insert(doc, layout.release()); + i.value()->initDimensions(); + } else { + return nullptr; + } + } + if (!i.value()->maxWidth()) return nullptr; + + i.value()->setPosition(position); + return i.value(); +} + +InlineItem *StickerPanInner::layoutPrepareInlineResult(InlineResult *result, int32 position) { + auto i = _inlineLayouts.constFind(result); + if (i == _inlineLayouts.cend()) { + if (auto layout = InlineItem::createLayout(result, _inlineWithThumb)) { + i = _inlineLayouts.insert(result, layout.release()); + i.value()->initDimensions(); + } else { + return nullptr; + } + } + if (!i.value()->maxWidth()) return nullptr; + + i.value()->setPosition(position); + return i.value(); +} + +void StickerPanInner::deleteUnusedGifLayouts() { + if (_inlineRows.isEmpty() || _section != Section::Gifs) { // delete all + for_const (auto item, _gifLayouts) { + delete item; + } + _gifLayouts.clear(); + } else { + for (auto i = _gifLayouts.begin(); i != _gifLayouts.cend();) { + if (i.value()->position() < 0) { + delete i.value(); + i = _gifLayouts.erase(i); + } else { + ++i; + } + } + } +} + +void StickerPanInner::deleteUnusedInlineLayouts() { + if (_inlineRows.isEmpty() || _section == Section::Gifs) { // delete all + for_const (auto item, _inlineLayouts) { + delete item; + } + _inlineLayouts.clear(); + } else { + for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { + if (i.value()->position() < 0) { + delete i.value(); + i = _inlineLayouts.erase(i); + } else { + ++i; + } + } + } +} + +StickerPanInner::InlineRow &StickerPanInner::layoutInlineRow(InlineRow &row, int32 sumWidth) { + int32 count = row.items.size(); + t_assert(count <= InlineItemsMaxPerRow); + + // enumerate items in the order of growing maxWidth() + // for that sort item indices by maxWidth() + int indices[InlineItemsMaxPerRow]; + for (int i = 0; i < count; ++i) { + indices[i] = i; + } + std::sort(indices, indices + count, [&row](int a, int b) -> bool { + return row.items.at(a)->maxWidth() < row.items.at(b)->maxWidth(); + }); + + row.height = 0; + int availw = width() - st::inlineResultsLeft; + for (int i = 0; i < count; ++i) { + int index = indices[i]; + int w = sumWidth ? (row.items.at(index)->maxWidth() * availw / sumWidth) : row.items.at(index)->maxWidth(); + int actualw = qMax(w, int(st::inlineResultsMinWidth)); + row.height = qMax(row.height, row.items.at(index)->resizeGetHeight(actualw)); + if (sumWidth) { + availw -= actualw; + sumWidth -= row.items.at(index)->maxWidth(); + if (index > 0 && row.items.at(index - 1)->hasRightSkip()) { + availw -= st::inlineResultsSkip; + sumWidth -= st::inlineResultsSkip; + } + } + } + return row; +} + +void StickerPanInner::preloadImages() { + if (showingInlineItems()) { + for (int32 row = 0, rows = _inlineRows.size(); row < rows; ++row) { + for (int32 col = 0, cols = _inlineRows.at(row).items.size(); col < cols; ++col) { + _inlineRows.at(row).items.at(col)->preload(); + } + } + return; + } + + auto &sets = shownSets(); + for (int i = 0, l = sets.size(), k = 0; i < l; ++i) { + int count = sets[i].pack.size(); + if (_section == Section::Featured) { + accumulate_min(count, static_cast(StickerPanPerRow)); + } + for (int j = 0; j != count; ++j) { + if (++k > StickerPanPerRow * (StickerPanPerRow + 1)) break; + + auto sticker = sets.at(i).pack.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 { + sticker->automaticLoad(0); + } + } + if (k > StickerPanPerRow * (StickerPanPerRow + 1)) break; + } +} + +uint64 StickerPanInner::currentSet(int yOffset) const { + if (showingInlineItems()) { + return Stickers::NoneSetId; + } else if (_section == Section::Featured) { + return Stickers::FeaturedSetId; + } + + int y, ytill = 0; + for (int i = 0, l = _mySets.size(); i < l; ++i) { + int cnt = _mySets[i].pack.size(); + y = ytill; + ytill = y + st::emojiPanHeader + ((cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0)) * st::stickerPanSize.height(); + if (yOffset < ytill) { + return _mySets[i].id; + } + } + return _mySets.isEmpty() ? Stickers::RecentSetId : _mySets.back().id; +} + +void StickerPanInner::hideInlineRowsPanel() { + clearInlineRows(false); + if (showingInlineItems()) { + _section = cShowingSavedGifs() ? Section::Gifs : Section::Inlines; + if (_section == Section::Gifs) { + refreshSavedGifs(); + emit scrollToY(0); + emit scrollUpdated(); + } else { + showStickerSet(Stickers::RecentSetId); + } + } +} + +void StickerPanInner::clearInlineRowsPanel() { + clearInlineRows(false); +} + +void StickerPanInner::refreshSwitchPmButton(const InlineCacheEntry *entry) { + if (!entry || entry->switchPmText.isEmpty()) { + _switchPmButton.reset(); + _switchPmStartToken.clear(); + } else { + if (!_switchPmButton) { + _switchPmButton = std_::make_unique(this, QString(), st::switchPmButton); + _switchPmButton->show(); + _switchPmButton->move(st::inlineResultsLeft, st::emojiPanHeader); + connect(_switchPmButton.get(), SIGNAL(clicked()), this, SLOT(onSwitchPm())); + } + _switchPmButton->setText(entry->switchPmText); // doesn't perform text.toUpper() + _switchPmStartToken = entry->switchPmStartToken; + } + update(); +} + +int StickerPanInner::refreshInlineRows(UserData *bot, const InlineCacheEntry *entry, bool resultsDeleted) { + _inlineBot = bot; + refreshSwitchPmButton(entry); + auto clearResults = [this, entry]() { + if (!entry) { + return true; + } + if (entry->results.isEmpty() && entry->switchPmText.isEmpty()) { + if (!_inlineBot || _inlineBot->username != cInlineGifBotUsername()) { + return true; + } + } + return false; + }; + if (clearResults()) { + if (resultsDeleted) { + clearInlineRows(true); + deleteUnusedInlineLayouts(); + } + emit emptyInlineRows(); + return 0; + } + + clearSelection(true); + + t_assert(_inlineBot != 0); + _inlineBotTitle = lng_inline_bot_results(lt_inline_bot, _inlineBot->username.isEmpty() ? _inlineBot->name : ('@' + _inlineBot->username)); + + _section = Section::Inlines; + _settings.hide(); + + int32 count = entry->results.size(), from = validateExistingInlineRows(entry->results), added = 0; + + if (count) { + _inlineRows.reserve(count); + InlineRow row; + row.items.reserve(InlineItemsMaxPerRow); + int32 sumWidth = 0; + for (int32 i = from; i < count; ++i) { + if (inlineRowsAddItem(0, entry->results.at(i), row, sumWidth)) { + ++added; + } + } + inlineRowFinalize(row, sumWidth, true); + } + + int32 h = countHeight(); + if (h != height()) resize(width(), h); + update(); + + emit refreshIcons(); + + _lastMousePos = QCursor::pos(); + updateSelected(); + + return added; +} + +int StickerPanInner::validateExistingInlineRows(const InlineResults &results) { + int count = results.size(), until = 0, untilrow = 0, untilcol = 0; + for (; until < count;) { + if (untilrow >= _inlineRows.size() || _inlineRows.at(untilrow).items.at(untilcol)->getResult() != results.at(until)) { + break; + } + ++until; + if (++untilcol == _inlineRows.at(untilrow).items.size()) { + ++untilrow; + untilcol = 0; + } + } + if (until == count) { // all items are layed out + if (untilrow == _inlineRows.size()) { // nothing changed + return until; + } + + for (int i = untilrow, l = _inlineRows.size(), skip = untilcol; i < l; ++i) { + for (int j = 0, s = _inlineRows.at(i).items.size(); j < s; ++j) { + if (skip) { + --skip; + } else { + _inlineRows.at(i).items.at(j)->setPosition(-1); + } + } + } + if (!untilcol) { // all good rows are filled + _inlineRows.resize(untilrow); + return until; + } + _inlineRows.resize(untilrow + 1); + _inlineRows[untilrow].items.resize(untilcol); + _inlineRows[untilrow] = layoutInlineRow(_inlineRows[untilrow]); + return until; + } + if (untilrow && !untilcol) { // remove last row, maybe it is not full + --untilrow; + untilcol = _inlineRows.at(untilrow).items.size(); + } + until -= untilcol; + + for (int i = untilrow, l = _inlineRows.size(); i < l; ++i) { + for (int j = 0, s = _inlineRows.at(i).items.size(); j < s; ++j) { + _inlineRows.at(i).items.at(j)->setPosition(-1); + } + } + _inlineRows.resize(untilrow); + + if (_inlineRows.isEmpty()) { + _inlineWithThumb = false; + for (int i = until; i < count; ++i) { + if (results.at(i)->hasThumbDisplay()) { + _inlineWithThumb = true; + break; + } + } + } + return until; +} + +void StickerPanInner::notify_inlineItemLayoutChanged(const InlineItem *layout) { + if (_selected < 0 || !showingInlineItems()) { + return; + } + + int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; + if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size()) { + if (layout == _inlineRows.at(row).items.at(col)) { + updateSelected(); + } + } +} + +void StickerPanInner::ui_repaintInlineItem(const InlineItem *layout) { + uint64 ms = getms(); + if (_lastScrolled + 100 <= ms) { + update(); + } else { + _updateInlineItems.start(_lastScrolled + 100 - ms); + } +} + +bool StickerPanInner::ui_isInlineItemVisible(const InlineItem *layout) { + int32 position = layout->position(); + if (!showingInlineItems() || position < 0) { + return false; + } + + int row = position / MatrixRowShift, col = position % MatrixRowShift; + t_assert((row < _inlineRows.size()) && (col < _inlineRows[row].items.size())); + + auto &inlineItems = _inlineRows[row].items; + int top = st::emojiPanHeader; + for (int32 i = 0; i < row; ++i) { + top += _inlineRows.at(i).height; + } + + return (top < _visibleTop + _maxHeight) && (top + _inlineRows[row].items[col]->height() > _visibleTop); +} + +bool StickerPanInner::ui_isInlineItemBeingChosen() { + return showingInlineItems(); +} + +void StickerPanInner::appendSet(Sets &to, uint64 setId, AppendSkip skip) { + auto &sets = Global::StickerSets(); + auto it = sets.constFind(setId); + if (it == sets.cend() || it->stickers.isEmpty()) return; + if ((skip == AppendSkip::Archived) && (it->flags & MTPDstickerSet::Flag::f_archived)) return; + if ((skip == AppendSkip::Installed) && (it->flags & MTPDstickerSet::Flag::f_installed) && !(it->flags & MTPDstickerSet::Flag::f_archived)) return; + + to.push_back(Set(it->id, it->flags, it->title, it->stickers.size() + 1, it->stickers)); +} + +void StickerPanInner::refreshRecent() { + if (_section == Section::Gifs) { + refreshSavedGifs(); + } else if (_section == Section::Stickers) { + refreshRecentStickers(); + } +} + +void StickerPanInner::refreshRecentStickers(bool performResize) { + _custom.clear(); + clearSelection(true); + auto &sets = Global::StickerSets(); + auto &recent = cGetRecentStickers(); + auto customIt = sets.constFind(Stickers::CustomSetId); + auto cloudIt = sets.constFind(Stickers::CloudRecentSetId); + if (recent.isEmpty() + && (customIt == sets.cend() || customIt->stickers.isEmpty()) + && (cloudIt == sets.cend() || cloudIt->stickers.isEmpty())) { + if (!_mySets.isEmpty() && _mySets.at(0).id == Stickers::RecentSetId) { + _mySets.pop_front(); + } + } else { + StickerPack recentPack; + int customCnt = (customIt == sets.cend()) ? 0 : customIt->stickers.size(); + int cloudCnt = (cloudIt == sets.cend()) ? 0 : cloudIt->stickers.size(); + recentPack.reserve(cloudCnt + recent.size() + customCnt); + _custom.reserve(cloudCnt + recent.size() + customCnt); + if (cloudCnt > 0) { + for_const (auto sticker, cloudIt->stickers) { + recentPack.push_back(sticker); + _custom.push_back(false); + } + } + for_const (auto &recentSticker, recent) { + auto sticker = recentSticker.first; + recentPack.push_back(sticker); + _custom.push_back(false); + } + if (customCnt > 0) { + for_const (auto &sticker, customIt->stickers) { + auto index = recentPack.indexOf(sticker); + if (index >= cloudCnt) { + _custom[index] = true; // mark stickers from recent as custom + } else { + recentPack.push_back(sticker); + _custom.push_back(true); + } + } + } + if (_mySets.isEmpty() || _mySets.at(0).id != Stickers::RecentSetId) { + _mySets.push_back(Set(Stickers::RecentSetId, MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special, lang(lng_recent_stickers), recentPack.size() * 2, recentPack)); + } else { + _mySets[0].pack = recentPack; + _mySets[0].hovers.resize(recentPack.size() * 2); + } + } + + if (performResize && (_section == Section::Stickers || _section == Section::Featured)) { + int32 h = countHeight(); + if (h != height()) { + resize(width(), h); + emit needRefreshPanels(); + } + + updateSelected(); + } +} + +void StickerPanInner::fillIcons(QList &icons) { + icons.clear(); + icons.reserve(_mySets.size() + 1); + if (!cSavedGifs().isEmpty()) { + icons.push_back(StickerIcon(Stickers::NoneSetId)); + } + if (Global::FeaturedStickerSetsUnreadCount() && !_featuredSets.isEmpty()) { + icons.push_back(StickerIcon(Stickers::FeaturedSetId)); + } + + if (!_mySets.isEmpty()) { + int i = 0; + if (_mySets[0].id == Stickers::RecentSetId) { + ++i; + icons.push_back(StickerIcon(Stickers::RecentSetId)); + } + for (int l = _mySets.size(); i < l; ++i) { + auto s = _mySets[i].pack[0]; + int32 availw = st::rbEmoji.width - 2 * st::stickerIconPadding, availh = st::rbEmoji.height - 2 * st::stickerIconPadding; + int32 thumbw = s->thumb->width(), thumbh = s->thumb->height(), pixw = 1, pixh = 1; + if (availw * thumbh > availh * thumbw) { + pixh = availh; + pixw = (pixh * thumbw) / thumbh; + } else { + pixw = availw; + pixh = thumbw ? ((pixw * thumbh) / thumbw) : 1; + } + if (pixw < 1) pixw = 1; + if (pixh < 1) pixh = 1; + icons.push_back(StickerIcon(_mySets[i].id, s, pixw, pixh)); + } + } + + if (!Global::FeaturedStickerSetsUnreadCount() && !_featuredSets.isEmpty()) { + icons.push_back(StickerIcon(Stickers::FeaturedSetId)); + } +} + +void StickerPanInner::fillPanels(QVector &panels) { + for (int32 i = 0; i < panels.size(); ++i) { + panels.at(i)->hide(); + panels.at(i)->deleteLater(); + } + panels.clear(); + + if (_section != Section::Stickers) { + auto title = [this]() -> QString { + if (_section == Section::Gifs) { + return lang(lng_saved_gifs); + } else if (_section == Section::Inlines) { + return _inlineBotTitle; + } + return lang(lng_stickers_featured); + }; + panels.push_back(new EmojiPanel(parentWidget(), title(), Stickers::NoneSetId, true, 0)); + panels.back()->show(); + return; + } + + if (_mySets.isEmpty()) return; + + int y = 0; + panels.reserve(_mySets.size()); + for (int32 i = 0, l = _mySets.size(); i < l; ++i) { + bool special = (_mySets[i].flags & MTPDstickerSet::Flag::f_official); + panels.push_back(new EmojiPanel(parentWidget(), _mySets[i].title, _mySets[i].id, special, y)); + panels.back()->show(); + connect(panels.back(), SIGNAL(deleteClicked(quint64)), this, SIGNAL(removeSet(quint64))); + int cnt = _mySets[i].pack.size(), rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0); + int h = st::emojiPanHeader + rows * st::stickerPanSize.height(); + y += h; + } +} + +void StickerPanInner::refreshPanels(QVector &panels) { + if (_section != Section::Stickers) return; + + if (panels.size() != _mySets.size()) { + return fillPanels(panels); + } + + int y = 0; + for (int i = 0, l = _mySets.size(); i < l; ++i) { + panels.at(i)->setWantedY(y); + int cnt = _mySets[i].pack.size(), rows = (cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0); + int h = st::emojiPanHeader + rows * st::stickerPanSize.height(); + y += h; + } +} + +void StickerPanInner::updateSelected() { + if (_pressed >= 0 && !_previewShown) { + return; + } + + int selIndex = -1; + auto p = mapFromGlobal(_lastMousePos); + + if (showingInlineItems()) { + int sx = (rtl() ? width() - p.x() : p.x()) - st::inlineResultsLeft; + int sy = p.y() - st::emojiPanHeader; + if (_switchPmButton) { + sy -= _switchPmButton->height() + st::inlineResultsSkip; + } + int row = -1, col = -1, sel = -1; + ClickHandlerPtr lnk; + ClickHandlerHost *lnkhost = nullptr; + HistoryCursorState cursor = HistoryDefaultCursorState; + if (sy >= 0) { + row = 0; + for (int rows = _inlineRows.size(); row < rows; ++row) { + if (sy < _inlineRows.at(row).height) { + break; + } + sy -= _inlineRows.at(row).height; + } + } + if (sx >= 0 && row >= 0 && row < _inlineRows.size()) { + auto &inlineItems = _inlineRows[row].items; + col = 0; + for (int cols = inlineItems.size(); col < cols; ++col) { + int width = inlineItems.at(col)->width(); + if (sx < width) { + break; + } + sx -= width; + if (inlineItems.at(col)->hasRightSkip()) { + sx -= st::inlineResultsSkip; + } + } + if (col < inlineItems.size()) { + sel = row * MatrixRowShift + col; + inlineItems.at(col)->getState(lnk, cursor, sx, sy); + lnkhost = inlineItems.at(col); + } else { + row = col = -1; + } + } else { + row = col = -1; + } + int srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; + int scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; + if (_selected != sel) { + if (srow >= 0 && scol >= 0) { + t_assert(srow >= 0 && srow < _inlineRows.size() && scol >= 0 && scol < _inlineRows.at(srow).items.size()); + Ui::repaintInlineItem(_inlineRows.at(srow).items.at(scol)); + } + _selected = sel; + if (row >= 0 && col >= 0) { + t_assert(row >= 0 && row < _inlineRows.size() && col >= 0 && col < _inlineRows.at(row).items.size()); + Ui::repaintInlineItem(_inlineRows.at(row).items.at(col)); + } + if (_pressed >= 0 && _selected >= 0 && _pressed != _selected) { + _pressed = _selected; + if (row >= 0 && col >= 0) { + auto layout = _inlineRows.at(row).items.at(col); + if (auto previewDocument = layout->getPreviewDocument()) { + Ui::showMediaPreview(previewDocument); + } else if (auto previewPhoto = layout->getPreviewPhoto()) { + Ui::showMediaPreview(previewPhoto); + } + } + } + } + if (ClickHandler::setActive(lnk, lnkhost)) { + setCursor(lnk ? style::cur_pointer : style::cur_default); + } + return; + } + + int selectedFeaturedSet = -1; + int selectedFeaturedSetAdd = -1; + auto featured = (_section == Section::Featured); + auto &sets = shownSets(); + int y, ytill = 0, sx = (rtl() ? width() - p.x() : p.x()) - st::stickerPanPadding; + if (featured) { + ytill += st::emojiPanHeader; + } + for (int c = 0, l = sets.size(); c < l; ++c) { + auto &set = sets[c]; + bool special = featured ? false : (set.flags & MTPDstickerSet::Flag::f_official); + + y = ytill; + if (featured) { + ytill = y + featuredRowHeight(); + } else { + int cnt = set.pack.size(); + ytill = y + st::emojiPanHeader + ((cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0)) * st::stickerPanSize.height(); + } + if (p.y() >= y && p.y() < ytill) { + if (featured) { + if (p.y() < y + st::featuredStickersHeader) { + if (featuredHasAddButton(c) && myrtlrect(featuredAddRect(c)).contains(p.x(), p.y())) { + selectedFeaturedSetAdd = c; + } else { + selectedFeaturedSet = c; + } + break; + } + y += st::featuredStickersHeader; + } else { + y += st::emojiPanHeader; + } + if (p.y() >= y && sx >= 0 && sx < StickerPanPerRow * st::stickerPanSize.width()) { + auto rowIndex = qFloor((p.y() - y) / st::stickerPanSize.height()); + if (!featured || !rowIndex) { + selIndex = rowIndex * StickerPanPerRow + qFloor(sx / st::stickerPanSize.width()); + if (selIndex >= set.pack.size()) { + selIndex = -1; + } else { + if (set.id == Stickers::RecentSetId && _custom[selIndex]) { + int 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 += set.pack.size(); + } + } + selIndex += c * MatrixRowShift; + } + } + } + break; + } + } + + bool startanim = false; + int oldSel = _selected, oldSelTab = oldSel / MatrixRowShift, xOldSel = -1, newSel = selIndex, newSelTab = newSel / MatrixRowShift, xNewSel = -1; + if (oldSel >= 0 && oldSelTab < sets.size() && sets[oldSelTab].id == Stickers::RecentSetId && oldSel >= oldSelTab * MatrixRowShift + sets[oldSelTab].pack.size()) { + xOldSel = oldSel; + oldSel -= sets[oldSelTab].pack.size(); + } + if (newSel >= 0 && newSelTab < sets.size() && sets[newSelTab].id == Stickers::RecentSetId && newSel >= newSelTab * MatrixRowShift + sets[newSelTab].pack.size()) { + xNewSel = newSel; + newSel -= sets[newSelTab].pack.size(); + } + if (newSel != oldSel || selectedFeaturedSet != _selectedFeaturedSet || selectedFeaturedSetAdd != _selectedFeaturedSetAdd) { + setCursor((newSel >= 0 || selectedFeaturedSet >= 0 || selectedFeaturedSetAdd >= 0) ? style::cur_pointer : style::cur_default); + } + 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()); + } + } + } + if (selectedFeaturedSet != _selectedFeaturedSet) { + _selectedFeaturedSet = selectedFeaturedSet; + } + if (selectedFeaturedSetAdd != _selectedFeaturedSetAdd) { + _selectedFeaturedSetAdd = selectedFeaturedSetAdd; + update(); + } + 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 (_pressed >= 0 && _selected >= 0 && _pressed != _selected) { + _pressed = _selected; + if (newSel >= 0 && xNewSel < 0) { + Ui::showMediaPreview(sets.at(newSelTab).pack.at(newSel % MatrixRowShift)); + } + } + if (startanim && !_a_selected.animating()) _a_selected.start(); +} + +void StickerPanInner::onSettings() { + Ui::showLayer(new StickersBox()); +} + +void StickerPanInner::onPreview() { + if (_pressed < 0) return; + if (showingInlineItems()) { + int row = _pressed / MatrixRowShift, col = _pressed % MatrixRowShift; + if (row < _inlineRows.size() && col < _inlineRows.at(row).items.size()) { + auto layout = _inlineRows.at(row).items.at(col); + if (auto previewDocument = layout->getPreviewDocument()) { + Ui::showMediaPreview(previewDocument); + _previewShown = true; + } else if (auto previewPhoto = layout->getPreviewPhoto()) { + Ui::showMediaPreview(previewPhoto); + _previewShown = true; + } + } + } else { + auto &sets = shownSets(); + if (_pressed < MatrixRowShift * sets.size()) { + int tab = (_pressed / MatrixRowShift), sel = _pressed % MatrixRowShift; + if (sel < sets[tab].pack.size()) { + Ui::showMediaPreview(sets[tab].pack[sel]); + _previewShown = true; + } + } + } +} + +void StickerPanInner::onUpdateInlineItems() { + if (!showingInlineItems()) return; + + uint64 ms = getms(); + if (_lastScrolled + 100 <= ms) { + update(); + } else { + _updateInlineItems.start(_lastScrolled + 100 - ms); + } +} + +void StickerPanInner::onSwitchPm() { + if (_inlineBot && _inlineBot->botInfo) { + _inlineBot->botInfo->startToken = _switchPmStartToken; + Ui::showPeerHistory(_inlineBot, ShowAndStartBotMsgId); + } +} + +void StickerPanInner::step_selected(uint64 ms, bool timer) { + QRegion toUpdate; + auto &sets = shownSets(); + for (auto i = _animations.begin(); i != _animations.end();) { + int index = qAbs(i.key()) - 1, tab = (index / MatrixRowShift), sel = index % MatrixRowShift; + float64 dt = float64(ms - i.value()) / st::emojiPanDuration; + if (dt >= 1) { + sets[tab].hovers[sel] = (i.key() > 0) ? 1 : 0; + i = _animations.erase(i); + } else { + sets[tab].hovers[sel] = (i.key() > 0) ? dt : (1 - dt); + ++i; + } + toUpdate += stickerRect(tab, sel); + } + if (timer) rtlupdate(toUpdate.boundingRect()); + if (_animations.isEmpty()) _a_selected.stop(); +} + +void StickerPanInner::showStickerSet(uint64 setId) { + clearSelection(true); + + if (setId == Stickers::NoneSetId) { + if (!showingInlineItems()) { + _section = Section::Gifs; + cSetShowingSavedGifs(true); + emit saveConfigDelayed(SaveRecentEmojisTimeout); + } + refreshSavedGifs(); + emit scrollToY(0); + emit scrollUpdated(); + showFinish(); + return; + } + + if (showingInlineItems()) { + if (_setGifCommand && _section == Section::Gifs) { + App::insertBotCommand(qsl(""), true); + } + _setGifCommand = false; + + cSetShowingSavedGifs(false); + emit saveConfigDelayed(SaveRecentEmojisTimeout); + Notify::clipStopperHidden(ClipStopperSavedGifsPanel); + } + + if (setId == Stickers::FeaturedSetId) { + if (_section != Section::Featured) { + _section = Section::Featured; + + refreshRecentStickers(true); + emit refreshIcons(); + update(); + } + + emit scrollToY(0); + emit scrollUpdated(); + return; + } + + bool needRefresh = (_section != Section::Stickers); + if (needRefresh) { + _section = Section::Stickers; + refreshRecentStickers(true); + } + + int32 y = 0; + for (int c = 0; c < _mySets.size(); ++c) { + if (_mySets.at(c).id == setId) break; + int rows = (_mySets[c].pack.size() / StickerPanPerRow) + ((_mySets[c].pack.size() % StickerPanPerRow) ? 1 : 0); + y += st::emojiPanHeader + rows * st::stickerPanSize.height(); + } + + emit scrollToY(y); + emit scrollUpdated(); + + if (needRefresh) { + emit refreshIcons(); + } + + _lastMousePos = QCursor::pos(); + + update(); +} + +void StickerPanInner::updateShowingSavedGifs() { + if (cShowingSavedGifs()) { + if (!showingInlineItems()) { + clearSelection(true); + _section = Section::Gifs; + if (_inlineRows.isEmpty()) refreshSavedGifs(); + } + } else if (!showingInlineItems()) { + clearSelection(true); + } +} + +void StickerPanInner::showFinish() { + if (_section == Section::Gifs) { + _setGifCommand = App::insertBotCommand('@' + cInlineGifBotUsername(), true); + } +} + +EmojiPanel::EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY) : TWidget(parent) +, _wantedY(wantedY) +, _setId(setId) +, _special(special) +, _deleteVisible(false) +, _delete(special ? 0 : new IconedButton(this, st::notifyClose)) { // Stickers::NoneSetId if in emoji + resize(st::emojiPanWidth, st::emojiPanHeader); + setMouseTracking(true); + setFocusPolicy(Qt::NoFocus); + setText(text); + if (_delete) { + _delete->hide(); + _delete->moveToRight(st::emojiPanHeaderLeft - ((_delete->width() - st::notifyClose.icon.pxWidth()) / 2), (st::emojiPanHeader - _delete->height()) / 2, width()); + connect(_delete, SIGNAL(clicked()), this, SLOT(onDelete())); + } +} + +void EmojiPanel::onDelete() { + emit deleteClicked(_setId); +} + +void EmojiPanel::setText(const QString &text) { + _fullText = text; + updateText(); +} + +void EmojiPanel::updateText() { + int32 availw = st::emojiPanWidth - st::emojiPanHeaderLeft * 2; + if (_deleteVisible) { + if (!_special && _setId != Stickers::NoneSetId) { + availw -= st::notifyClose.icon.pxWidth() + st::emojiPanHeaderLeft; + } + } else { + auto switchText = ([this]() { + if (_setId != Stickers::NoneSetId) { + return lang(lng_switch_emoji); + } + if (cSavedGifs().isEmpty()) { + return lang(lng_switch_stickers); + } + return lang(lng_switch_stickers_gifs); + })(); + availw -= st::emojiSwitchSkip + st::emojiPanHeaderFont->width(switchText); + } + _text = st::emojiPanHeaderFont->elided(_fullText, availw); + update(); +} + +void EmojiPanel::setDeleteVisible(bool isVisible) { + if (_deleteVisible != isVisible) { + _deleteVisible = isVisible; + updateText(); + if (_delete) { + _delete->setVisible(_deleteVisible); + } + } +} + +void EmojiPanel::mousePressEvent(QMouseEvent *e) { + emit mousePressed(); +} + +void EmojiPanel::paintEvent(QPaintEvent *e) { + Painter p(this); + + if (!_deleteVisible) { + p.fillRect(0, 0, width(), st::emojiPanHeader, st::emojiPanHeaderBg->b); + } + p.setFont(st::emojiPanHeaderFont); + p.setPen(st::emojiPanHeaderColor); + p.drawTextLeft(st::emojiPanHeaderLeft, st::emojiPanHeaderTop, width(), _text); +} + +EmojiSwitchButton::EmojiSwitchButton(QWidget *parent, bool toStickers) : Button(parent) +, _toStickers(toStickers) { + setCursor(style::cur_pointer); + updateText(); +} + +void EmojiSwitchButton::updateText(const QString &inlineBotUsername) { + if (_toStickers) { + if (inlineBotUsername.isEmpty()) { + _text = lang(cSavedGifs().isEmpty() ? lng_switch_stickers : lng_switch_stickers_gifs); + } else { + _text = '@' + inlineBotUsername; + } + } else { + _text = lang(lng_switch_emoji); + } + _textWidth = st::emojiPanHeaderFont->width(_text); + if (_toStickers && !inlineBotUsername.isEmpty()) { + int32 maxw = 0; + for (int c = 0; c < emojiTabCount; ++c) { + maxw = qMax(maxw, st::emojiPanHeaderFont->width(lang(LangKey(lng_emoji_category0 + c)))); + } + maxw += st::emojiPanHeaderLeft + st::emojiSwitchSkip + (st::emojiSwitchSkip - st::emojiSwitchImgSkip); + if (_textWidth > st::emojiPanWidth - maxw) { + _text = st::emojiPanHeaderFont->elided(_text, st::emojiPanWidth - maxw); + _textWidth = st::emojiPanHeaderFont->width(_text); + } + } + + int32 w = st::emojiSwitchSkip + _textWidth + (st::emojiSwitchSkip - st::emojiSwitchImgSkip); + resize(w, st::emojiPanHeader); +} + +void EmojiSwitchButton::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.setFont(st::emojiPanHeaderFont->f); + p.setPen(st::emojiSwitchColor->p); + if (_toStickers) { + p.drawTextRight(st::emojiSwitchSkip, st::emojiPanHeaderTop, width(), _text, _textWidth); + p.drawSpriteRight(QPoint(st::emojiSwitchImgSkip - st::emojiSwitchStickers.pxWidth(), (st::emojiPanHeader - st::emojiSwitchStickers.pxHeight()) / 2), width(), st::emojiSwitchStickers); + } else { + p.drawTextRight(st::emojiSwitchImgSkip - st::emojiSwitchEmoji.pxWidth(), st::emojiPanHeaderTop, width(), lang(lng_switch_emoji), _textWidth); + p.drawSpriteRight(QPoint(st::emojiSwitchSkip + _textWidth - st::emojiSwitchEmoji.pxWidth(), (st::emojiPanHeader - st::emojiSwitchEmoji.pxHeight()) / 2), width(), st::emojiSwitchEmoji); + } +} + +} // namespace internal + +EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent) +, _maxHeight(st::emojiPanMaxHeight) +, _contentMaxHeight(st::emojiPanMaxHeight) +, _contentHeight(_contentMaxHeight) +, _contentHeightEmoji(_contentHeight - st::rbEmoji.height) +, _contentHeightStickers(_contentHeight - st::rbEmoji.height) +, _a_appearance(animation(this, &EmojiPan::step_appearance)) +, _shadow(st::dropdownDef.shadow) +, _recent(this , qsl("emoji_group"), dbietRecent , QString(), true , st::rbEmojiRecent) +, _people(this , qsl("emoji_group"), dbietPeople , QString(), false, st::rbEmojiPeople) +, _nature(this , qsl("emoji_group"), dbietNature , QString(), false, st::rbEmojiNature) +, _food(this , qsl("emoji_group"), dbietFood , QString(), false, st::rbEmojiFood) +, _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) +, _symbols(this , qsl("emoji_group"), dbietSymbols , QString(), false, st::rbEmojiSymbols) +, _a_icons(animation(this, &EmojiPan::step_icons)) +, _a_slide(animation(this, &EmojiPan::step_slide)) +, e_scroll(this, st::emojiScroll) +, e_inner() +, e_switch(&e_scroll, true) +, s_scroll(this, st::emojiScroll) +, s_inner() +, s_switch(&s_scroll, false) { + 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); + + _width = st::dropdownDef.padding.left() + st::emojiPanWidth + st::dropdownDef.padding.right(); + _height = st::dropdownDef.padding.top() + _contentHeight + st::dropdownDef.padding.bottom(); + _bottom = 0; + resize(_width, _height); + + e_scroll.resize(st::emojiPanWidth, _contentHeightEmoji); + s_scroll.resize(st::emojiPanWidth, _contentHeightStickers); + + 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); + + e_inner.moveToLeft(0, 0, e_scroll.width()); + s_inner.moveToLeft(0, 0, s_scroll.width()); + + int32 left = _iconsLeft = st::dropdownDef.padding.left() + (st::emojiPanWidth - 8 * st::rbEmoji.width) / 2; + int32 top = _iconsTop = st::dropdownDef.padding.top() + _contentHeight - st::rbEmoji.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, _activity); + prepareTab(left, top, _width, _travel); + prepareTab(left, top, _width, _objects); + prepareTab(left, top, _width, _symbols); + e_inner.fillPanels(e_panels); + updatePanelsPositions(e_panels, 0); + + _hideTimer.setSingleShot(true); + connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); + + connect(&e_inner, SIGNAL(scrollToY(int)), &e_scroll, SLOT(scrollToY(int))); + connect(&e_inner, SIGNAL(disableScroll(bool)), &e_scroll, SLOT(disableScroll(bool))); + + connect(&s_inner, SIGNAL(scrollToY(int)), &s_scroll, SLOT(scrollToY(int))); + connect(&s_inner, SIGNAL(scrollUpdated()), this, SLOT(onScrollStickers())); + + connect(&e_scroll, SIGNAL(scrolled()), this, SLOT(onScrollEmoji())); + connect(&s_scroll, SIGNAL(scrolled()), this, SLOT(onScrollStickers())); + + connect(&e_inner, SIGNAL(selected(EmojiPtr)), this, SIGNAL(emojiSelected(EmojiPtr))); + connect(&s_inner, SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*))); + connect(&s_inner, SIGNAL(selected(PhotoData*)), this, SIGNAL(photoSelected(PhotoData*))); + connect(&s_inner, SIGNAL(selected(InlineBots::Result*,UserData*)), this, SIGNAL(inlineResultSelected(InlineBots::Result*,UserData*))); + + connect(&s_inner, SIGNAL(emptyInlineRows()), this, SLOT(onEmptyInlineRows())); + + connect(&s_switch, SIGNAL(clicked()), this, SLOT(onSwitch())); + connect(&e_switch, SIGNAL(clicked()), this, SLOT(onSwitch())); + s_switch.moveToRight(0, 0, st::emojiPanWidth); + e_switch.moveToRight(0, 0, st::emojiPanWidth); + + connect(&s_inner, SIGNAL(displaySet(quint64)), this, SLOT(onDisplaySet(quint64))); + connect(&s_inner, SIGNAL(installSet(quint64)), this, SLOT(onInstallSet(quint64))); + connect(&s_inner, SIGNAL(removeSet(quint64)), this, SLOT(onRemoveSet(quint64))); + connect(&s_inner, SIGNAL(refreshIcons()), this, SLOT(onRefreshIcons())); + connect(&e_inner, SIGNAL(needRefreshPanels()), this, SLOT(onRefreshPanels())); + connect(&s_inner, SIGNAL(needRefreshPanels()), this, SLOT(onRefreshPanels())); + + _saveConfigTimer.setSingleShot(true); + connect(&_saveConfigTimer, SIGNAL(timeout()), this, SLOT(onSaveConfig())); + connect(&e_inner, SIGNAL(saveConfigDelayed(int32)), this, SLOT(onSaveConfigDelayed(int32))); + connect(&s_inner, SIGNAL(saveConfigDelayed(int32)), this, SLOT(onSaveConfigDelayed(int32))); + + // inline bots + _inlineRequestTimer.setSingleShot(true); + connect(&_inlineRequestTimer, SIGNAL(timeout()), this, SLOT(onInlineRequest())); + + if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { + connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged())); + } + + setMouseTracking(true); +// setAttribute(Qt::WA_AcceptTouchEvents); +} + +void EmojiPan::setMaxHeight(int32 h) { + _maxHeight = h; + updateContentHeight(); +} + +void EmojiPan::updateContentHeight() { + int32 h = qMin(_contentMaxHeight, _maxHeight); + int32 he = h - st::rbEmoji.height; + int32 hs = h - (s_inner.showSectionIcons() ? st::rbEmoji.height : 0); + if (h == _contentHeight && he == _contentHeightEmoji && hs == _contentHeightStickers) return; + + int32 was = _contentHeight, wase = _contentHeightEmoji, wass = _contentHeightStickers; + _contentHeight = h; + _contentHeightEmoji = he; + _contentHeightStickers = hs; + + _height = st::dropdownDef.padding.top() + _contentHeight + st::dropdownDef.padding.bottom(); + + resize(_width, _height); + move(x(), _bottom - height()); + + if (was > _contentHeight || (was == _contentHeight && wass > _contentHeightStickers)) { + e_scroll.resize(st::emojiPanWidth, _contentHeightEmoji); + s_scroll.resize(st::emojiPanWidth, _contentHeightStickers); + s_inner.setMaxHeight(_contentHeightStickers); + e_inner.setMaxHeight(_contentHeightEmoji); + } else { + s_inner.setMaxHeight(_contentHeightStickers); + e_inner.setMaxHeight(_contentHeightEmoji); + e_scroll.resize(st::emojiPanWidth, _contentHeightEmoji); + s_scroll.resize(st::emojiPanWidth, _contentHeightStickers); + } + + _iconsTop = st::dropdownDef.padding.top() + _contentHeight - st::rbEmoji.height; + _recent.move(_recent.x(), _iconsTop); + _people.move(_people.x(), _iconsTop); + _nature.move(_nature.x(), _iconsTop); + _food.move(_food.x(), _iconsTop); + _activity.move(_activity.x(), _iconsTop); + _travel.move(_travel.x(), _iconsTop); + _objects.move(_objects.x(), _iconsTop); + _symbols.move(_symbols.x(), _iconsTop); + + update(); +} + +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); + } +} + +void EmojiPan::onSaveConfig() { + Local::writeUserSettings(); +} + +void EmojiPan::onSaveConfigDelayed(int32 delay) { + _saveConfigTimer.start(delay); +} + +void EmojiPan::paintStickerSettingsIcon(Painter &p) const { + int settingsLeft = _iconsLeft + 7 * st::rbEmoji.width; + p.drawSpriteLeft(settingsLeft + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::stickersSettings); +} + +void EmojiPan::paintFeaturedStickerSetsBadge(Painter &p, int iconLeft) const { + if (auto unread = Global::FeaturedStickerSetsUnreadCount()) { + Dialogs::Layout::UnreadBadgeStyle unreadSt; + unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInStickersPanel; + unreadSt.size = st::stickersSettingsUnreadSize; + int unreadRight = iconLeft + st::rbEmoji.width - st::stickersSettingsUnreadPosition.x(); + if (rtl()) unreadRight = width() - unreadRight; + int unreadTop = _iconsTop + st::stickersSettingsUnreadPosition.y(); + Dialogs::Layout::paintUnreadCount(p, QString::number(unread), unreadRight, unreadTop, unreadSt); + } +} + +void EmojiPan::paintEvent(QPaintEvent *e) { + Painter p(this); + + float64 o = 1; + if (!_cache.isNull()) { + 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, st::dropdownDef.shadowShift); + + if (_toCache.isNull()) { + if (_cache.isNull()) { + p.fillRect(myrtlrect(r.x() + r.width() - st::emojiScroll.width, r.y(), st::emojiScroll.width, e_scroll.height()), st::white->b); + if (_stickersShown && s_inner.showSectionIcons()) { + p.fillRect(r.left(), _iconsTop, r.width(), st::rbEmoji.height, st::emojiPanCategories); + paintStickerSettingsIcon(p); + + if (!_icons.isEmpty()) { + int x = _iconsLeft, selxrel = _iconsLeft + _iconSelX.current(), selx = selxrel - _iconsX.current(); + + QRect clip(x, _iconsTop, _iconsLeft + 7 * st::rbEmoji.width - x, st::rbEmoji.height); + if (rtl()) clip.moveLeft(width() - x - clip.width()); + p.setClipRect(clip); + + auto getSpecialSetIcon = [](uint64 setId, bool active) { + if (setId == Stickers::NoneSetId) { + return active ? st::savedGifsActive : st::savedGifsOver; + } else if (setId == Stickers::FeaturedSetId) { + return active ? st::featuredStickersActive : st::featuredStickersOver; + } + return active ? st::rbEmojiRecent.chkImageRect : st::rbEmojiRecent.imageRect; + }; + + int i = 0; + i += _iconsX.current() / int(st::rbEmoji.width); + x -= _iconsX.current() % int(st::rbEmoji.width); + selxrel -= _iconsX.current(); + for (int l = qMin(_icons.size(), i + 8); i < l; ++i) { + auto &s = _icons.at(i); + if (s.sticker) { + s.sticker->thumb->load(); + QPixmap pix(s.sticker->thumb->pix(s.pixw, s.pixh)); + + p.drawPixmapLeft(x + (st::rbEmoji.width - s.pixw) / 2, _iconsTop + (st::rbEmoji.height - s.pixh) / 2, width(), pix); + x += st::rbEmoji.width; + } else { + if (true || selxrel != x) { + p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), getSpecialSetIcon(s.setId, false)); + } + //if (selxrel < x + st::rbEmoji.width && selxrel > x - st::rbEmoji.width) { + // p.setOpacity(1 - (qAbs(selxrel - x) / float64(st::rbEmoji.width))); + // p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), getSpecialSetIcon(s.setId, true)); + // p.setOpacity(1); + //} + if (s.setId == Stickers::FeaturedSetId) { + paintFeaturedStickerSetsBadge(p, x); + } + x += st::rbEmoji.width; + } + } + + if (rtl()) selx = width() - selx - st::rbEmoji.width; + p.setOpacity(1.); + p.fillRect(selx, _iconsTop + st::rbEmoji.height - st::stickerIconPadding, st::rbEmoji.width, st::stickerIconSel, st::stickerIconSelColor); + + float64 o_left = snap(float64(_iconsX.current()) / st::stickerIconLeft.pxWidth(), 0., 1.); + if (o_left > 0) { + p.setOpacity(o_left); + p.drawSpriteLeft(QRect(_iconsLeft, _iconsTop, st::stickerIconLeft.pxWidth(), st::rbEmoji.height), width(), st::stickerIconLeft); + } + float64 o_right = snap(float64(_iconsMax - _iconsX.current()) / st::stickerIconRight.pxWidth(), 0., 1.); + if (o_right > 0) { + p.setOpacity(o_right); + p.drawSpriteRight(QRect(width() - _iconsLeft - 7 * st::rbEmoji.width, _iconsTop, st::stickerIconRight.pxWidth(), st::rbEmoji.height), width(), st::stickerIconRight); + } + } + } else if (_stickersShown) { + int32 x = rtl() ? (_recent.x() + _recent.width()) : (_objects.x() + _objects.width()); + p.fillRect(x, _recent.y(), r.left() + r.width() - x, st::rbEmoji.height, st::white); + } else { + p.fillRect(r.left(), _recent.y(), (rtl() ? _objects.x() : _recent.x() - r.left()), st::rbEmoji.height, st::emojiPanCategories); + int32 x = rtl() ? (_recent.x() + _recent.width()) : (_objects.x() + _objects.width()); + p.fillRect(x, _recent.y(), r.left() + r.width() - x, st::rbEmoji.height, st::emojiPanCategories); + } + } else { + p.fillRect(r, st::white); + p.drawPixmap(r.left(), r.top(), _cache); + } + } else { + p.fillRect(QRect(r.left(), r.top(), r.width(), r.height() - st::rbEmoji.height), st::white->b); + p.fillRect(QRect(r.left(), _iconsTop, r.width(), st::rbEmoji.height), st::emojiPanCategories->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); + } + } +} + +void EmojiPan::moveBottom(int32 bottom, bool force) { + _bottom = bottom; + if (isHidden() && !force) { + move(x(), _bottom - height()); + return; + } + if (_stickersShown && s_inner.inlineResultsShown()) { + moveToLeft(0, _bottom - height()); + } else { + moveToRight(0, _bottom - height()); + } +} + +void EmojiPan::enterEvent(QEvent *e) { + _hideTimer.stop(); + if (_hiding) showStart(); +} + +bool EmojiPan::preventAutoHide() const { + return _removingSetId || _displayingSetId; +} + +void EmojiPan::leaveEvent(QEvent *e) { + if (preventAutoHide() || s_inner.inlineResultsShown()) return; + if (_a_appearance.animating()) { + hideStart(); + } else { + _hideTimer.start(300); + } +} + +void EmojiPan::otherEnter() { + _hideTimer.stop(); + showStart(); +} + +void EmojiPan::otherLeave() { + if (preventAutoHide() || s_inner.inlineResultsShown()) return; + if (_a_appearance.animating()) { + hideStart(); + } else { + _hideTimer.start(0); + } +} + +void EmojiPan::mousePressEvent(QMouseEvent *e) { + if (!_stickersShown) return; + _iconsMousePos = e ? e->globalPos() : QCursor::pos(); + updateSelected(); + + if (_iconOver == _icons.size()) { + Ui::showLayer(new StickersBox()); + } else { + _iconDown = _iconOver; + _iconsMouseDown = _iconsMousePos; + _iconsStartX = _iconsX.current(); + } +} + +void EmojiPan::mouseMoveEvent(QMouseEvent *e) { + if (!_stickersShown) return; + _iconsMousePos = e ? e->globalPos() : QCursor::pos(); + updateSelected(); + + if (!_iconsDragging && !_icons.isEmpty() && _iconDown >= 0) { + if ((_iconsMousePos - _iconsMouseDown).manhattanLength() >= QApplication::startDragDistance()) { + _iconsDragging = true; + } + } + if (_iconsDragging) { + int32 newX = snap(_iconsStartX + (rtl() ? -1 : 1) * (_iconsMouseDown.x() - _iconsMousePos.x()), 0, _iconsMax); + if (newX != _iconsX.current()) { + _iconsX = anim::ivalue(newX, newX); + _iconsStartAnim = 0; + if (_iconAnimations.isEmpty()) _a_icons.stop(); + updateIcons(); + } + } +} + +void EmojiPan::mouseReleaseEvent(QMouseEvent *e) { + if (!_stickersShown || _icons.isEmpty()) return; + + int32 wasDown = _iconDown; + _iconDown = -1; + + _iconsMousePos = e ? e->globalPos() : QCursor::pos(); + if (_iconsDragging) { + int32 newX = snap(_iconsStartX + _iconsMouseDown.x() - _iconsMousePos.x(), 0, _iconsMax); + if (newX != _iconsX.current()) { + _iconsX = anim::ivalue(newX, newX); + _iconsStartAnim = 0; + if (_iconAnimations.isEmpty()) _a_icons.stop(); + updateIcons(); + } + _iconsDragging = false; + updateSelected(); + } else { + updateSelected(); + + if (wasDown == _iconOver && _iconOver >= 0 && _iconOver < _icons.size()) { + _iconSelX = anim::ivalue(_iconOver * st::rbEmoji.width, _iconOver * st::rbEmoji.width); + s_inner.showStickerSet(_icons.at(_iconOver).setId); + } + } +} + +bool EmojiPan::event(QEvent *e) { + if (e->type() == QEvent::TouchBegin) { + + } else if (e->type() == QEvent::Wheel) { + if (!_icons.isEmpty() && _iconOver >= 0 && _iconOver < _icons.size() && _iconDown < 0) { + QWheelEvent *ev = static_cast(e); + bool hor = (ev->angleDelta().x() != 0 || ev->orientation() == Qt::Horizontal); + bool ver = (ev->angleDelta().y() != 0 || ev->orientation() == Qt::Vertical); + if (hor) _horizontal = true; + int32 newX = _iconsX.current(); + if (/*_horizontal && */hor) { + newX = snap(newX - (rtl() ? -1 : 1) * (ev->pixelDelta().x() ? ev->pixelDelta().x() : ev->angleDelta().x()), 0, _iconsMax); + } else if (/*!_horizontal && */ver) { + newX = snap(newX - (ev->pixelDelta().y() ? ev->pixelDelta().y() : ev->angleDelta().y()), 0, _iconsMax); + } + if (newX != _iconsX.current()) { + _iconsX = anim::ivalue(newX, newX); + _iconsStartAnim = 0; + if (_iconAnimations.isEmpty()) _a_icons.stop(); + updateSelected(); + updateIcons(); + } + } + } + return TWidget::event(e); +} + +void EmojiPan::fastHide() { + if (_a_appearance.animating()) { + _a_appearance.stop(); + } + a_opacity = anim::fvalue(0, 0); + _hideTimer.stop(); + hide(); + _cache = QPixmap(); +} + +void EmojiPan::refreshStickers() { + s_inner.refreshStickers(); + if (!_stickersShown) { + s_inner.preloadImages(); + } + update(); +} + +void EmojiPan::refreshSavedGifs() { + e_switch.updateText(); + e_switch.moveToRight(0, 0, st::emojiPanWidth); + s_inner.refreshSavedGifs(); + if (!_stickersShown) { + s_inner.preloadImages(); + } +} + +void EmojiPan::onRefreshIcons() { + _iconOver = -1; + _iconHovers.clear(); + _iconAnimations.clear(); + s_inner.fillIcons(_icons); + s_inner.fillPanels(s_panels); + _iconsX.finish(); + _iconSelX.finish(); + _iconsStartAnim = 0; + _a_icons.stop(); + if (_icons.isEmpty()) { + _iconsMax = 0; + } else { + _iconHovers = QVector(_icons.size(), 0); + _iconsMax = qMax(int((_icons.size() - 7) * st::rbEmoji.width), 0); + } + if (_iconsX.current() > _iconsMax) { + _iconsX = anim::ivalue(_iconsMax, _iconsMax); + } + updatePanelsPositions(s_panels, s_scroll.scrollTop()); + updateSelected(); + if (_stickersShown) { + validateSelectedIcon(); + updateContentHeight(); + } + updateIcons(); +} + +void EmojiPan::onRefreshPanels() { + s_inner.refreshPanels(s_panels); + e_inner.refreshPanels(e_panels); + if (_stickersShown) { + updatePanelsPositions(s_panels, s_scroll.scrollTop()); + } else { + updatePanelsPositions(e_panels, e_scroll.scrollTop()); + } +} + +void EmojiPan::leaveToChildEvent(QEvent *e, QWidget *child) { + if (!_stickersShown) return; + _iconsMousePos = QCursor::pos(); + updateSelected(); +} + +void EmojiPan::updateSelected() { + if (_iconDown >= 0) { + return; + } + + QPoint p(mapFromGlobal(_iconsMousePos)); + int32 x = p.x(), y = p.y(), newOver = -1; + if (rtl()) x = width() - x; + x -= _iconsLeft; + if (x >= st::rbEmoji.width * 7 && x < st::rbEmoji.width * 8 && y >= _iconsTop && y < _iconsTop + st::rbEmoji.height) { + newOver = _icons.size(); + } else if (!_icons.isEmpty()) { + if (y >= _iconsTop && y < _iconsTop + st::rbEmoji.height && x >= 0 && x < 7 * st::rbEmoji.width && x < _icons.size() * st::rbEmoji.width) { + x += _iconsX.current(); + newOver = qFloor(x / st::rbEmoji.width); + } + } + if (newOver != _iconOver) { + if (newOver < 0) { + setCursor(style::cur_default); + } else if (_iconOver < 0) { + setCursor(style::cur_pointer); + } + bool startanim = false; + if (_iconOver >= 0 && _iconOver < _icons.size()) { + _iconAnimations.remove(_iconOver + 1); + if (_iconAnimations.find(-_iconOver - 1) == _iconAnimations.end()) { + if (_iconAnimations.isEmpty() && !_iconsStartAnim) startanim = true; + _iconAnimations.insert(-_iconOver - 1, getms()); + } + } + _iconOver = newOver; + if (_iconOver >= 0 && _iconOver < _icons.size()) { + _iconAnimations.remove(-_iconOver - 1); + if (_iconAnimations.find(_iconOver + 1) == _iconAnimations.end()) { + if (_iconAnimations.isEmpty() && !_iconsStartAnim) startanim = true; + _iconAnimations.insert(_iconOver + 1, getms()); + } + } + if (startanim && !_a_icons.animating()) _a_icons.start(); + } +} + +void EmojiPan::updateIcons() { + if (!_stickersShown || !s_inner.showSectionIcons()) return; + + 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()); + update(r.left(), _iconsTop, r.width(), st::rbEmoji.height); +} + +void EmojiPan::step_icons(uint64 ms, bool timer) { + if (!_stickersShown) { + _a_icons.stop(); + return; + } + + for (Animations::iterator i = _iconAnimations.begin(); i != _iconAnimations.end();) { + int index = qAbs(i.key()) - 1; + float64 dt = float64(ms - i.value()) / st::emojiPanDuration; + if (index >= _iconHovers.size()) { + i = _iconAnimations.erase(i); + } else if (dt >= 1) { + _iconHovers[index] = (i.key() > 0) ? 1 : 0; + i = _iconAnimations.erase(i); + } else { + _iconHovers[index] = (i.key() > 0) ? dt : (1 - dt); + ++i; + } + } + + if (_iconsStartAnim) { + float64 dt = (ms - _iconsStartAnim) / float64(st::stickerIconMove); + if (dt >= 1) { + _iconsStartAnim = 0; + _iconsX.finish(); + _iconSelX.finish(); + } else { + _iconsX.update(dt, anim::linear); + _iconSelX.update(dt, anim::linear); + } + if (timer) updateSelected(); + } + + if (timer) updateIcons(); + + if (_iconAnimations.isEmpty() && !_iconsStartAnim) { + _a_icons.stop(); + } +} + +void EmojiPan::step_slide(float64 ms, bool timer) { + float64 fullDuration = st::introSlideDelta + st::introSlideDuration, dt = ms / fullDuration; + float64 dt1 = (ms > st::introSlideDuration) ? 1 : (ms / st::introSlideDuration), dt2 = (ms > st::introSlideDelta) ? (ms - st::introSlideDelta) / (st::introSlideDuration) : 0; + if (dt2 >= 1) { + _a_slide.stop(); + a_fromCoord.finish(); + a_fromAlpha.finish(); + a_toCoord.finish(); + a_toAlpha.finish(); + _fromCache = _toCache = QPixmap(); + if (_cache.isNull()) showAll(); + } else { + a_fromCoord.update(dt1, st::introHideFunc); + a_fromAlpha.update(dt1, st::introAlphaHideFunc); + a_toCoord.update(dt2, st::introShowFunc); + a_toAlpha.update(dt2, st::introAlphaShowFunc); + } + if (timer) update(); +} + +void EmojiPan::step_appearance(float64 ms, bool timer) { + if (_cache.isNull()) { + _a_appearance.stop(); + return; + } + + float64 dt = ms / st::dropdownDef.duration; + if (dt >= 1) { + _a_appearance.stop(); + a_opacity.finish(); + if (_hiding) { + hideFinish(); + } else { + _cache = QPixmap(); + if (_toCache.isNull()) showAll(); + } + } else { + a_opacity.update(dt, anim::linear); + } + if (timer) update(); +} + +void EmojiPan::hideStart() { + if (preventAutoHide() || s_inner.inlineResultsShown()) return; + + hideAnimated(); +} + +void EmojiPan::prepareShowHideCache() { + if (_cache.isNull()) { + QPixmap from = _fromCache, to = _toCache; + _fromCache = _toCache = QPixmap(); + showAll(); + _cache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); + _fromCache = from; _toCache = to; + } +} + +void EmojiPan::hideAnimated() { + if (_hiding) return; + + prepareShowHideCache(); + hideAll(); + _hiding = true; + a_opacity.start(0); + _a_appearance.start(); +} + +void EmojiPan::hideFinish() { + hide(); + e_inner.hideFinish(); + s_inner.hideFinish(true); + _cache = _toCache = _fromCache = QPixmap(); + _a_slide.stop(); + _horizontal = false; + _hiding = false; + + e_scroll.scrollToY(0); + if (!_recent.checked()) { + _noTabUpdate = true; + _recent.setChecked(true); + _noTabUpdate = false; + } + s_scroll.scrollToY(0); + _iconOver = _iconDown = -1; + _iconSel = 0; + _iconsX = anim::ivalue(0, 0); + _iconSelX = anim::ivalue(0, 0); + _iconsStartAnim = 0; + _a_icons.stop(); + _iconHovers = _icons.isEmpty() ? QVector() : QVector(_icons.size(), 0); + _iconAnimations.clear(); + + Notify::clipStopperHidden(ClipStopperSavedGifsPanel); +} + +void EmojiPan::showStart() { + if (!isHidden() && !_hiding) { + return; + } + if (isHidden()) { + e_inner.refreshRecent(); + if (s_inner.inlineResultsShown() && refreshInlineRows()) { + _stickersShown = true; + _shownFromInlineQuery = true; + } else { + s_inner.refreshRecent(); + _stickersShown = false; + _shownFromInlineQuery = false; + _cache = QPixmap(); // clear after refreshInlineRows() + } + recountContentMaxHeight(); + s_inner.preloadImages(); + _fromCache = _toCache = QPixmap(); + _a_slide.stop(); + moveBottom(y() + height(), true); + } else if (_hiding) { + if (s_inner.inlineResultsShown() && refreshInlineRows()) { + onSwitch(); + } + } + prepareShowHideCache(); + hideAll(); + _hiding = false; + show(); + a_opacity.start(1); + _a_appearance.start(); + emit updateStickers(); +} + +bool EmojiPan::eventFilter(QObject *obj, QEvent *e) { + if (e->type() == QEvent::Enter) { + //if (dynamic_cast(obj)) { + // enterEvent(e); + //} else { + otherEnter(); + //} + } else if (e->type() == QEvent::Leave) { + //if (dynamic_cast(obj)) { + // leaveEvent(e); + //} else { + otherLeave(); + //} + } else if (e->type() == QEvent::MouseButtonPress && static_cast(e)->button() == Qt::LeftButton/* && !dynamic_cast(obj)*/) { + if (isHidden() || _hiding) { + _hideTimer.stop(); + showStart(); + } else { + hideAnimated(); + } + } + return false; +} + +void EmojiPan::stickersInstalled(uint64 setId) { + _stickersShown = true; + if (isHidden()) { + moveBottom(y() + height(), true); + show(); + a_opacity = anim::fvalue(0, 1); + a_opacity.update(0, anim::linear); + _cache = _fromCache = _toCache = QPixmap(); + } + showAll(); + s_inner.showStickerSet(setId); + updateContentHeight(); + showStart(); +} + +void EmojiPan::notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) { + if (_stickersShown && !isHidden()) { + s_inner.notify_inlineItemLayoutChanged(layout); + } +} + +void EmojiPan::ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout) { + if (_stickersShown && !isHidden()) { + s_inner.ui_repaintInlineItem(layout); + } +} + +bool EmojiPan::ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout) { + if (_stickersShown && !isHidden()) { + return s_inner.ui_isInlineItemVisible(layout); + } + return false; +} + +bool EmojiPan::ui_isInlineItemBeingChosen() { + if (_stickersShown && !isHidden()) { + return s_inner.ui_isInlineItemBeingChosen(); + } + return false; +} + +void EmojiPan::showAll() { + if (_stickersShown) { + s_scroll.show(); + _recent.hide(); + _people.hide(); + _nature.hide(); + _food.hide(); + _activity.hide(); + _travel.hide(); + _objects.hide(); + _symbols.hide(); + e_scroll.hide(); + } else { + s_scroll.hide(); + _recent.show(); + _people.show(); + _nature.show(); + _food.show(); + _activity.show(); + _travel.show(); + _objects.show(); + _symbols.show(); + e_scroll.show(); + } +} + +void EmojiPan::hideAll() { + _recent.hide(); + _people.hide(); + _nature.hide(); + _food.hide(); + _activity.hide(); + _travel.hide(); + _objects.hide(); + _symbols.hide(); + e_scroll.hide(); + s_scroll.hide(); + e_inner.clearSelection(true); + s_inner.clearSelection(true); +} + +void EmojiPan::onTabChange() { + if (_noTabUpdate) return; + DBIEmojiTab newTab = dbietRecent; + if (_people.checked()) newTab = dbietPeople; + else if (_nature.checked()) newTab = dbietNature; + else if (_food.checked()) newTab = dbietFood; + else if (_activity.checked()) newTab = dbietActivity; + else if (_travel.checked()) newTab = dbietTravel; + else if (_objects.checked()) newTab = dbietObjects; + else if (_symbols.checked()) newTab = dbietSymbols; + e_inner.showEmojiPack(newTab); +} + +void EmojiPan::updatePanelsPositions(const QVector &panels, int32 st) { + for (int32 i = 0, l = panels.size(); i < l; ++i) { + int32 y = panels.at(i)->wantedY() - st; + if (y < 0) { + y = (i + 1 < l) ? qMin(panels.at(i + 1)->wantedY() - st - int(st::emojiPanHeader), 0) : 0; + } + panels.at(i)->move(0, y); + panels.at(i)->setDeleteVisible(y >= st::emojiPanHeader); + + // Somehow the panels gets hidden (not displayed) when scrolling + // by clicking on the scroll bar to the middle of the panel. + // This bug occurs only in the Section::Featured stickers. + if (s_inner.currentSet(0) == Stickers::FeaturedSetId) { + panels.at(i)->repaint(); + } + } +} + +void EmojiPan::onScrollEmoji() { + auto st = e_scroll.scrollTop(); + + updatePanelsPositions(e_panels, st); + + auto tab = e_inner.currentTab(st); + FlatRadiobutton *check = nullptr; + switch (tab) { + case dbietRecent: check = &_recent; break; + case dbietPeople: check = &_people; break; + case dbietNature: check = &_nature; break; + case dbietFood: check = &_food; break; + case dbietActivity: check = &_activity; break; + case dbietTravel: check = &_travel; break; + case dbietObjects: check = &_objects; break; + case dbietSymbols: check = &_symbols; break; + } + if (check && !check->checked()) { + _noTabUpdate = true; + check->setChecked(true); + _noTabUpdate = false; + } + + e_inner.setVisibleTopBottom(st, st + e_scroll.height()); +} + +void EmojiPan::onScrollStickers() { + auto st = s_scroll.scrollTop(); + + updatePanelsPositions(s_panels, st); + + validateSelectedIcon(true); + if (st + s_scroll.height() > s_scroll.scrollTopMax()) { + onInlineRequest(); + } + + s_inner.setVisibleTopBottom(st, st + s_scroll.height()); +} + +void EmojiPan::validateSelectedIcon(bool animated) { + uint64 setId = s_inner.currentSet(s_scroll.scrollTop()); + int32 newSel = 0; + for (int i = 0, l = _icons.size(); i < l; ++i) { + if (_icons.at(i).setId == setId) { + newSel = i; + break; + } + } + if (newSel != _iconSel) { + _iconSel = newSel; + if (animated) { + _iconSelX.start(newSel * st::rbEmoji.width); + } else { + _iconSelX = anim::ivalue(newSel * st::rbEmoji.width, newSel * st::rbEmoji.width); + } + _iconsX.start(snap((2 * newSel - 7) * int(st::rbEmoji.width) / 2, 0, _iconsMax)); + _iconsStartAnim = getms(); + _a_icons.start(); + updateSelected(); + updateIcons(); + } +} + +void EmojiPan::onSwitch() { + QPixmap cache = _cache; + _fromCache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); + _stickersShown = !_stickersShown; + if (!_stickersShown) { + Notify::clipStopperHidden(ClipStopperSavedGifsPanel); + } else { + if (cShowingSavedGifs() && cSavedGifs().isEmpty()) { + s_inner.showStickerSet(Stickers::DefaultSetId); + } else if (!cShowingSavedGifs() && !cSavedGifs().isEmpty() && Global::StickerSetsOrder().isEmpty()) { + s_inner.showStickerSet(Stickers::NoneSetId); + } else { + s_inner.updateShowingSavedGifs(); + } + if (cShowingSavedGifs()) { + s_inner.showFinish(); + } + validateSelectedIcon(); + updateContentHeight(); + } + _iconOver = -1; + _iconHovers = _icons.isEmpty() ? QVector() : QVector(_icons.size(), 0); + _iconAnimations.clear(); + _a_icons.stop(); + + _cache = QPixmap(); + showAll(); + _toCache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); + _cache = cache; + + hideAll(); + + if (_stickersShown) { + e_inner.hideFinish(); + } else { + s_inner.hideFinish(false); + } + + a_toCoord = (_stickersShown != rtl()) ? anim::ivalue(st::emojiPanWidth, 0) : anim::ivalue(-st::emojiPanWidth, 0); + a_toAlpha = anim::fvalue(0, 1); + a_fromCoord = (_stickersShown != rtl()) ? anim::ivalue(0, -st::emojiPanWidth) : anim::ivalue(0, st::emojiPanWidth); + a_fromAlpha = anim::fvalue(1, 0); + + _a_slide.start(); + update(); +} + +void EmojiPan::onDisplaySet(quint64 setId) { + auto &sets = Global::StickerSets(); + auto it = sets.constFind(setId); + if (it != sets.cend()) { + _displayingSetId = setId; + auto box = new StickerSetBox(Stickers::inputSetId(*it)); + connect(box, SIGNAL(destroyed(QObject*)), this, SLOT(onDelayedHide())); + Ui::showLayer(box, KeepOtherLayers); + } +} + +void EmojiPan::onInstallSet(quint64 setId) { + auto &sets = Global::StickerSets(); + auto it = sets.constFind(setId); + if (it != sets.cend()) { + MTP::send(MTPmessages_InstallStickerSet(Stickers::inputSetId(*it), MTP_bool(false)), rpcDone(&EmojiPan::installSetDone), rpcFail(&EmojiPan::installSetFail, setId)); + + Stickers::installLocally(setId); + } +} + +void EmojiPan::installSetDone(const MTPmessages_StickerSetInstallResult &result) { + if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { + Stickers::applyArchivedResult(result.c_messages_stickerSetInstallResultArchive()); + } +} + +bool EmojiPan::installSetFail(uint64 setId, const RPCError &error) { + if (MTP::isDefaultHandledError(error)) { + return false; + } + Stickers::undoInstallLocally(setId); + return true; +} + +void EmojiPan::onRemoveSet(quint64 setId) { + auto &sets = Global::StickerSets(); + auto it = sets.constFind(setId); + if (it != sets.cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) { + _removingSetId = it->id; + ConfirmBox *box = new ConfirmBox(lng_stickers_remove_pack(lt_sticker_pack, it->title), lang(lng_box_remove)); + connect(box, SIGNAL(confirmed()), this, SLOT(onRemoveSetSure())); + connect(box, SIGNAL(destroyed(QObject*)), this, SLOT(onDelayedHide())); + Ui::showLayer(box); + } +} + +void EmojiPan::onRemoveSetSure() { + Ui::hideLayer(); + auto &sets = Global::RefStickerSets(); + auto it = sets.find(_removingSetId); + if (it != sets.cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) { + 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; + } + } + it->flags &= ~MTPDstickerSet::Flag::f_installed; + if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) { + sets.erase(it); + } + int removeIndex = Global::StickerSetsOrder().indexOf(_removingSetId); + if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex); + refreshStickers(); + Local::writeInstalledStickers(); + if (writeRecent) Local::writeUserSettings(); + } + _removingSetId = 0; +} + +void EmojiPan::onDelayedHide() { + if (!rect().contains(mapFromGlobal(QCursor::pos()))) { + _hideTimer.start(3000); + } + _removingSetId = 0; + _displayingSetId = 0; +} + +void EmojiPan::clearInlineBot() { + inlineBotChanged(); + e_switch.updateText(); + e_switch.moveToRight(0, 0, st::emojiPanWidth); +} + +bool EmojiPan::hideOnNoInlineResults() { + return _inlineBot && _stickersShown && s_inner.inlineResultsShown() && (_shownFromInlineQuery || _inlineBot->username != cInlineGifBotUsername()); +} + +void EmojiPan::inlineBotChanged() { + if (!_inlineBot) return; + + if (!isHidden() && !_hiding) { + if (hideOnNoInlineResults() || !rect().contains(mapFromGlobal(QCursor::pos()))) { + hideAnimated(); + } + } + + if (_inlineRequestId) MTP::cancel(_inlineRequestId); + _inlineRequestId = 0; + _inlineQuery = _inlineNextQuery = _inlineNextOffset = QString(); + _inlineBot = nullptr; + for (InlineCache::const_iterator i = _inlineCache.cbegin(), e = _inlineCache.cend(); i != e; ++i) { + delete i.value(); + } + _inlineCache.clear(); + s_inner.inlineBotChanged(); + s_inner.hideInlineRowsPanel(); + + Notify::inlineBotRequesting(false); +} + +void EmojiPan::inlineResultsDone(const MTPmessages_BotResults &result) { + _inlineRequestId = 0; + Notify::inlineBotRequesting(false); + + auto it = _inlineCache.find(_inlineQuery); + + bool adding = (it != _inlineCache.cend()); + if (result.type() == mtpc_messages_botResults) { + const auto &d(result.c_messages_botResults()); + const auto &v(d.vresults.c_vector().v); + uint64 queryId(d.vquery_id.v); + + if (!adding) { + it = _inlineCache.insert(_inlineQuery, new internal::InlineCacheEntry()); + } + it.value()->nextOffset = qs(d.vnext_offset); + if (d.has_switch_pm() && d.vswitch_pm.type() == mtpc_inlineBotSwitchPM) { + const auto &switchPm = d.vswitch_pm.c_inlineBotSwitchPM(); + it.value()->switchPmText = qs(switchPm.vtext); + it.value()->switchPmStartToken = qs(switchPm.vstart_param); + } + + if (int count = v.size()) { + it.value()->results.reserve(it.value()->results.size() + count); + } + int added = 0; + for_const (const auto &res, v) { + if (auto result = InlineBots::Result::create(queryId, res)) { + ++added; + it.value()->results.push_back(result.release()); + } + } + + if (!added) { + it.value()->nextOffset = QString(); + } + } else if (adding) { + it.value()->nextOffset = QString(); + } + + if (!showInlineRows(!adding)) { + it.value()->nextOffset = QString(); + } + onScrollStickers(); +} + +bool EmojiPan::inlineResultsFail(const RPCError &error) { + // show error? + Notify::inlineBotRequesting(false); + _inlineRequestId = 0; + return true; +} + +void EmojiPan::queryInlineBot(UserData *bot, PeerData *peer, QString query) { + bool force = false; + _inlineQueryPeer = peer; + if (bot != _inlineBot) { + inlineBotChanged(); + _inlineBot = bot; + force = true; + //if (_inlineBot->isBotInlineGeo()) { + // Ui::showLayer(new InformBox(lang(lng_bot_inline_geo_unavailable))); + //} + } + //if (_inlineBot && _inlineBot->isBotInlineGeo()) { + // return; + //} + + if (_inlineQuery != query || force) { + if (_inlineRequestId) { + MTP::cancel(_inlineRequestId); + _inlineRequestId = 0; + Notify::inlineBotRequesting(false); + } + if (_inlineCache.contains(query)) { + _inlineRequestTimer.stop(); + _inlineQuery = _inlineNextQuery = query; + showInlineRows(true); + } else { + _inlineNextQuery = query; + _inlineRequestTimer.start(InlineBotRequestDelay); + } + } +} + +void EmojiPan::onInlineRequest() { + if (_inlineRequestId || !_inlineBot || !_inlineQueryPeer) return; + _inlineQuery = _inlineNextQuery; + + QString nextOffset; + InlineCache::const_iterator i = _inlineCache.constFind(_inlineQuery); + if (i != _inlineCache.cend()) { + nextOffset = i.value()->nextOffset; + if (nextOffset.isEmpty()) return; + } + Notify::inlineBotRequesting(true); + MTPmessages_GetInlineBotResults::Flags flags = 0; + _inlineRequestId = MTP::send(MTPmessages_GetInlineBotResults(MTP_flags(flags), _inlineBot->inputUser, _inlineQueryPeer->input, MTPInputGeoPoint(), MTP_string(_inlineQuery), MTP_string(nextOffset)), rpcDone(&EmojiPan::inlineResultsDone), rpcFail(&EmojiPan::inlineResultsFail)); +} + +void EmojiPan::onEmptyInlineRows() { + if (_shownFromInlineQuery || hideOnNoInlineResults()) { + hideAnimated(); + s_inner.clearInlineRowsPanel(); + } else if (!_inlineBot) { + s_inner.hideInlineRowsPanel(); + } else { + s_inner.clearInlineRowsPanel(); + } +} + +bool EmojiPan::refreshInlineRows(int32 *added) { + auto i = _inlineCache.constFind(_inlineQuery); + const internal::InlineCacheEntry *entry = nullptr; + if (i != _inlineCache.cend()) { + if (!i.value()->results.isEmpty() || !i.value()->switchPmText.isEmpty()) { + entry = i.value(); + } + _inlineNextOffset = i.value()->nextOffset; + } + if (!entry) prepareShowHideCache(); + int32 result = s_inner.refreshInlineRows(_inlineBot, entry, false); + if (added) *added = result; + return (entry != nullptr); +} + +int32 EmojiPan::showInlineRows(bool newResults) { + int32 added = 0; + bool clear = !refreshInlineRows(&added); + if (newResults) s_scroll.scrollToY(0); + + e_switch.updateText(s_inner.inlineResultsShown() ? _inlineBot->username : QString()); + e_switch.moveToRight(0, 0, st::emojiPanWidth); + + bool hidden = isHidden(); + if (!hidden && !clear) { + recountContentMaxHeight(); + } + if (clear) { + if (!hidden && hideOnNoInlineResults()) { + hideAnimated(); + } else if (!_hiding) { + _cache = QPixmap(); // clear after refreshInlineRows() + } + } else { + _hideTimer.stop(); + if (hidden || _hiding) { + showStart(); + } else if (!_stickersShown) { + onSwitch(); + } + } + + return added; +} + +void EmojiPan::recountContentMaxHeight() { + if (_shownFromInlineQuery) { + _contentMaxHeight = qMin(s_inner.countHeight(true), int(st::emojiPanMaxHeight)); + } else { + _contentMaxHeight = st::emojiPanMaxHeight; + } + updateContentHeight(); +} diff --git a/Telegram/SourceFiles/stickers/emoji_pan.h b/Telegram/SourceFiles/stickers/emoji_pan.h new file mode 100644 index 0000000000..16c8edc775 --- /dev/null +++ b/Telegram/SourceFiles/stickers/emoji_pan.h @@ -0,0 +1,664 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "ui/twidget.h" +#include "ui/boxshadow.h" + +namespace InlineBots { +namespace Layout { +class ItemBase; +} // namespace Layout +class Result; +} // namespace InlineBots + +namespace internal { + +constexpr int InlineItemsMaxPerRow = 5; +constexpr int EmojiColorsCount = 5; + +using InlineResult = InlineBots::Result; +using InlineResults = QList; +using InlineItem = InlineBots::Layout::ItemBase; + +struct InlineCacheEntry { + ~InlineCacheEntry() { + clearResults(); + } + QString nextOffset; + QString switchPmText, switchPmStartToken; + InlineResults results; // owns this results list + void clearResults(); +}; + +class EmojiColorPicker : public TWidget { + Q_OBJECT + +public: + + EmojiColorPicker(); + + void showEmoji(uint32 code); + + void paintEvent(QPaintEvent *e); + void enterEvent(QEvent *e); + void leaveEvent(QEvent *e); + void mousePressEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + void mouseMoveEvent(QMouseEvent *e); + + void step_appearance(float64 ms, bool timer); + void step_selected(uint64 ms, bool timer); + void showStart(); + + void clearSelection(bool fast = false); + +public slots: + + void hideStart(bool fast = false); + +signals: + + void emojiSelected(EmojiPtr emoji); + void hidden(); + +private: + + void drawVariant(Painter &p, int variant); + + void updateSelected(); + + bool _ignoreShow = false; + + EmojiPtr _variants[EmojiColorsCount + 1]; + + typedef QMap EmojiAnimations; // index - showing, -index - hiding + EmojiAnimations _emojiAnimations; + Animation _a_selected; + + float64 _hovers[EmojiColorsCount + 1]; + + int _selected = -1; + int _pressedSel = -1; + QPoint _lastMousePos; + + bool _hiding = false; + QPixmap _cache; + + anim::fvalue a_opacity; + Animation _a_appearance; + + QTimer _hideTimer; + + BoxShadow _shadow; + +}; + +class EmojiPanel; +class EmojiPanInner : public ScrolledWidget { + Q_OBJECT + +public: + EmojiPanInner(); + + void setMaxHeight(int32 h); + void paintEvent(QPaintEvent *e) override; + + void step_selected(uint64 ms, bool timer); + void hideFinish(); + + void showEmojiPack(DBIEmojiTab packIndex); + + void clearSelection(bool fast = false); + + DBIEmojiTab currentTab(int yOffset) const; + + void refreshRecent(); + + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + + void fillPanels(QVector &panels); + void refreshPanels(QVector &panels); + +protected: + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void leaveEvent(QEvent *e) override; + void leaveToChildEvent(QEvent *e, QWidget *child) override; + void enterFromChildEvent(QEvent *e, QWidget *child) override; + +public slots: + void updateSelected(); + + void onShowPicker(); + void onPickerHidden(); + void onColorSelected(EmojiPtr emoji); + + bool checkPickerHide(); + +signals: + void selected(EmojiPtr emoji); + + void switchToStickers(); + + void scrollToY(int y); + void disableScroll(bool dis); + + void needRefreshPanels(); + void saveConfigDelayed(int32 delay); + +private: + int32 _maxHeight; + + int countHeight(); + void selectEmoji(EmojiPtr emoji); + + QRect emojiRect(int tab, int sel); + + typedef QMap Animations; // index - showing, -index - hiding + Animations _animations; + Animation _a_selected; + + int _visibleTop = 0; + int _visibleBottom = 0; + int _counts[emojiTabCount]; + + QVector _emojis[emojiTabCount]; + QVector _hovers[emojiTabCount]; + + int32 _esize; + + int _selected = -1; + int _pressedSel = -1; + int _pickerSel = -1; + QPoint _lastMousePos; + + EmojiColorPicker _picker; + QTimer _showPickerTimer; +}; + +struct StickerIcon { + StickerIcon(uint64 setId) : setId(setId) { + } + StickerIcon(uint64 setId, DocumentData *sticker, int32 pixw, int32 pixh) : setId(setId), sticker(sticker), pixw(pixw), pixh(pixh) { + } + uint64 setId; + DocumentData *sticker = nullptr; + int pixw = 0; + int pixh = 0; +}; + +class StickerPanInner : public ScrolledWidget { + Q_OBJECT + +public: + StickerPanInner(); + + void setMaxHeight(int32 h); + void paintEvent(QPaintEvent *e) override; + + void step_selected(uint64 ms, bool timer); + + void hideFinish(bool completely); + void showFinish(); + void showStickerSet(uint64 setId); + void updateShowingSavedGifs(); + + bool showSectionIcons() const; + void clearSelection(bool fast = false); + + void refreshStickers(); + void refreshRecentStickers(bool resize = true); + void refreshSavedGifs(); + int refreshInlineRows(UserData *bot, const InlineCacheEntry *results, bool resultsDeleted); + void refreshRecent(); + void inlineBotChanged(); + void hideInlineRowsPanel(); + void clearInlineRowsPanel(); + + void fillIcons(QList &icons); + void fillPanels(QVector &panels); + void refreshPanels(QVector &panels); + + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + void preloadImages(); + + uint64 currentSet(int yOffset) const; + + void notify_inlineItemLayoutChanged(const InlineItem *layout); + void ui_repaintInlineItem(const InlineItem *layout); + bool ui_isInlineItemVisible(const InlineItem *layout); + bool ui_isInlineItemBeingChosen(); + + bool inlineResultsShown() const { + return (_section == Section::Inlines); + } + int countHeight(bool plain = false); + + ~StickerPanInner(); + +protected: + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void leaveEvent(QEvent *e) override; + void leaveToChildEvent(QEvent *e, QWidget *child) override; + void enterFromChildEvent(QEvent *e, QWidget *child) override; + +private slots: + void updateSelected(); + void onSettings(); + void onPreview(); + void onUpdateInlineItems(); + void onSwitchPm(); + void onImageLoaded(); + +signals: + void selected(DocumentData *sticker); + void selected(PhotoData *photo); + void selected(InlineBots::Result *result, UserData *bot); + + void displaySet(quint64 setId); + void installSet(quint64 setId); + void removeSet(quint64 setId); + + void refreshIcons(); + void emptyInlineRows(); + + void switchToEmoji(); + + void scrollToY(int y); + void scrollUpdated(); + void disableScroll(bool dis); + void needRefreshPanels(); + + void saveConfigDelayed(int32 delay); + +private: + struct Set { + Set(uint64 id, MTPDstickerSet::Flags flags, const QString &title, int32 hoversSize, const StickerPack &pack = StickerPack()) : id(id), flags(flags), title(title), hovers(hoversSize, 0), pack(pack) { + } + uint64 id; + MTPDstickerSet::Flags flags; + QString title; + QVector hovers; + StickerPack pack; + }; + using Sets = QList; + Sets &shownSets() { + return (_section == Section::Featured) ? _featuredSets : _mySets; + } + const Sets &shownSets() const { + return const_cast(this)->shownSets(); + } + int featuredRowHeight() const; + void readVisibleSets(); + + bool showingInlineItems() const { // Gifs or Inline results + return (_section == Section::Inlines) || (_section == Section::Gifs); + } + + void paintInlineItems(Painter &p, const QRect &r); + void paintStickers(Painter &p, const QRect &r); + void paintSticker(Painter &p, Set &set, int y, int index); + bool featuredHasAddButton(int index) const; + int featuredContentWidth() const; + QRect featuredAddRect(int y) const; + + void refreshSwitchPmButton(const InlineCacheEntry *entry); + + enum class AppendSkip { + Archived, + Installed, + }; + void appendSet(Sets &to, uint64 setId, AppendSkip skip); + + void selectEmoji(EmojiPtr emoji); + QRect stickerRect(int tab, int sel); + + int32 _maxHeight; + + typedef QMap Animations; // index - showing, -index - hiding + Animations _animations; + Animation _a_selected; + + int _visibleTop = 0; + int _visibleBottom = 0; + + Sets _mySets; + Sets _featuredSets; + QList _custom; + + enum class Section { + Inlines, + Gifs, + Featured, + Stickers, + }; + Section _section = Section::Stickers; + bool _setGifCommand = false; + UserData *_inlineBot; + QString _inlineBotTitle; + uint64 _lastScrolled = 0; + QTimer _updateInlineItems; + bool _inlineWithThumb = false; + + std_::unique_ptr _switchPmButton; + QString _switchPmStartToken; + + typedef QVector InlineItems; + struct InlineRow { + InlineRow() : height(0) { + } + int32 height; + InlineItems items; + }; + typedef QVector InlineRows; + InlineRows _inlineRows; + void clearInlineRows(bool resultsDeleted); + + using GifLayouts = QMap; + GifLayouts _gifLayouts; + InlineItem *layoutPrepareSavedGif(DocumentData *doc, int32 position); + + using InlineLayouts = QMap; + InlineLayouts _inlineLayouts; + InlineItem *layoutPrepareInlineResult(InlineResult *result, int32 position); + + bool inlineRowsAddItem(DocumentData *savedGif, InlineResult *result, InlineRow &row, int32 &sumWidth); + bool inlineRowFinalize(InlineRow &row, int32 &sumWidth, bool force = false); + + InlineRow &layoutInlineRow(InlineRow &row, int32 sumWidth = 0); + void deleteUnusedGifLayouts(); + + void deleteUnusedInlineLayouts(); + + int validateExistingInlineRows(const InlineResults &results); + void selectInlineResult(int row, int column); + void removeRecentSticker(int tab, int index); + + int _selected = -1; + int _pressed = -1; + int _selectedFeaturedSet = -1; + int _pressedFeaturedSet = -1; + int _selectedFeaturedSetAdd = -1; + int _pressedFeaturedSetAdd = -1; + QPoint _lastMousePos; + + QString _addText; + int _addWidth; + + LinkButton _settings; + + QTimer _previewTimer; + bool _previewShown = false; +}; + +class EmojiPanel : public TWidget { + Q_OBJECT + +public: + + EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY); // Stickers::NoneSetId if in emoji + void setText(const QString &text); + void setDeleteVisible(bool isVisible); + + void paintEvent(QPaintEvent *e); + void mousePressEvent(QMouseEvent *e); + + int32 wantedY() const { + return _wantedY; + } + void setWantedY(int32 y) { + _wantedY = y; + } + +signals: + + void deleteClicked(quint64 setId); + void mousePressed(); + +public slots: + + void onDelete(); + +private: + + void updateText(); + + int32 _wantedY; + QString _text, _fullText; + uint64 _setId; + bool _special, _deleteVisible; + IconedButton *_delete; + +}; + +class EmojiSwitchButton : public Button { +public: + + EmojiSwitchButton(QWidget *parent, bool toStickers); // otherwise toEmoji + void paintEvent(QPaintEvent *e); + void updateText(const QString &inlineBotUsername = QString()); + +protected: + + bool _toStickers; + QString _text; + int32 _textWidth; + +}; + +} // namespace internal + +class EmojiPan : public TWidget, public RPCSender { + Q_OBJECT + +public: + EmojiPan(QWidget *parent); + + void setMaxHeight(int32 h); + void paintEvent(QPaintEvent *e); + + void moveBottom(int32 bottom, bool force = false); + + void enterEvent(QEvent *e); + void leaveEvent(QEvent *e); + void otherEnter(); + void otherLeave(); + + void mousePressEvent(QMouseEvent *e); + void mouseMoveEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + + bool event(QEvent *e); + + void fastHide(); + bool hiding() const { + return _hiding || _hideTimer.isActive(); + } + + void step_appearance(float64 ms, bool timer); + void step_slide(float64 ms, bool timer); + void step_icons(uint64 ms, bool timer); + + bool eventFilter(QObject *obj, QEvent *e); + void stickersInstalled(uint64 setId); + + void queryInlineBot(UserData *bot, PeerData *peer, QString query); + void clearInlineBot(); + + bool overlaps(const QRect &globalRect) { + if (isHidden() || !_cache.isNull()) return false; + + return QRect(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() + ).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); + } + + void notify_inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout); + void ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout); + bool ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout); + bool ui_isInlineItemBeingChosen(); + + bool inlineResultsShown() const { + return s_inner.inlineResultsShown(); + } + +public slots: + void refreshStickers(); + void refreshSavedGifs(); + + void hideStart(); + void hideFinish(); + + void showStart(); + void onWndActiveChanged(); + + void onTabChange(); + void onScrollEmoji(); + void onScrollStickers(); + void onSwitch(); + + void onDisplaySet(quint64 setId); + void onInstallSet(quint64 setId); + void onRemoveSet(quint64 setId); + void onRemoveSetSure(); + void onDelayedHide(); + + void onRefreshIcons(); + void onRefreshPanels(); + + void onSaveConfig(); + void onSaveConfigDelayed(int32 delay); + + void onInlineRequest(); + void onEmptyInlineRows(); + +signals: + void emojiSelected(EmojiPtr emoji); + void stickerSelected(DocumentData *sticker); + void photoSelected(PhotoData *photo); + void inlineResultSelected(InlineBots::Result *result, UserData *bot); + + void updateStickers(); + +private: + bool preventAutoHide() const; + void installSetDone(const MTPmessages_StickerSetInstallResult &result); + bool installSetFail(uint64 setId, const RPCError &error); + + void paintStickerSettingsIcon(Painter &p) const; + void paintFeaturedStickerSetsBadge(Painter &p, int iconLeft) const; + + void validateSelectedIcon(bool animated = false); + void updateContentHeight(); + + void leaveToChildEvent(QEvent *e, QWidget *child); + void hideAnimated(); + void prepareShowHideCache(); + + void updateSelected(); + void updateIcons(); + + void prepareTab(int32 &left, int32 top, int32 _width, FlatRadiobutton &tab); + void updatePanelsPositions(const QVector &panels, int32 st); + + void showAll(); + void hideAll(); + + int32 _maxHeight, _contentMaxHeight, _contentHeight, _contentHeightEmoji, _contentHeightStickers; + bool _horizontal = false; + + bool _noTabUpdate = false; + + int32 _width, _height, _bottom; + bool _hiding = false; + QPixmap _cache; + + anim::fvalue a_opacity = { 0. }; + Animation _a_appearance; + + QTimer _hideTimer; + + BoxShadow _shadow; + + FlatRadiobutton _recent, _people, _nature, _food, _activity, _travel, _objects, _symbols; + QList _icons; + QVector _iconHovers; + int _iconOver = -1; + int _iconSel = 0; + int _iconDown = -1; + bool _iconsDragging = false; + typedef QMap Animations; // index - showing, -index - hiding + Animations _iconAnimations; + Animation _a_icons; + QPoint _iconsMousePos, _iconsMouseDown; + int _iconsLeft = 0; + int _iconsTop = 0; + int _iconsStartX = 0; + int _iconsMax = 0; + anim::ivalue _iconsX = { 0, 0 }; + anim::ivalue _iconSelX = { 0, 0 }; + uint64 _iconsStartAnim = 0; + + bool _stickersShown = false; + bool _shownFromInlineQuery = false; + QPixmap _fromCache, _toCache; + anim::ivalue a_fromCoord, a_toCoord; + anim::fvalue a_fromAlpha, a_toAlpha; + Animation _a_slide; + + ScrollArea e_scroll; + internal::EmojiPanInner e_inner; + QVector e_panels; + internal::EmojiSwitchButton e_switch; + ScrollArea s_scroll; + internal::StickerPanInner s_inner; + QVector s_panels; + internal::EmojiSwitchButton s_switch; + + uint64 _displayingSetId = 0; + uint64 _removingSetId = 0; + + QTimer _saveConfigTimer; + + // inline bots + typedef QMap InlineCache; + InlineCache _inlineCache; + QTimer _inlineRequestTimer; + + void inlineBotChanged(); + int32 showInlineRows(bool newResults); + bool hideOnNoInlineResults(); + void recountContentMaxHeight(); + bool refreshInlineRows(int32 *added = 0); + UserData *_inlineBot = nullptr; + PeerData *_inlineQueryPeer = nullptr; + QString _inlineQuery, _inlineNextQuery, _inlineNextOffset; + mtpRequestId _inlineRequestId = 0; + void inlineResultsDone(const MTPmessages_BotResults &result); + bool inlineResultsFail(const RPCError &error); + +}; diff --git a/Telegram/SourceFiles/stickers/stickers.cpp b/Telegram/SourceFiles/stickers/stickers.cpp new file mode 100644 index 0000000000..266ede52dc --- /dev/null +++ b/Telegram/SourceFiles/stickers/stickers.cpp @@ -0,0 +1,207 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "stickers.h" + +#include "boxes/stickersetbox.h" +#include "boxes/confirmbox.h" +#include "lang.h" +#include "apiwrap.h" +#include "localstorage.h" +#include "mainwidget.h" + +namespace Stickers { +namespace { + +constexpr int kReadFeaturedSetsTimeoutMs = 1000; +internal::FeaturedReader *FeaturedReaderInstance = nullptr; + +} // namespace + +void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) { + auto &v = d.vsets.c_vector().v; + auto &order = Global::RefStickerSetsOrder(); + Stickers::Order archived; + archived.reserve(v.size()); + QMap setsToRequest; + for_const (auto &stickerSet, v) { + const MTPDstickerSet *setData = nullptr; + switch (stickerSet.type()) { + case mtpc_stickerSetCovered: { + auto &d = stickerSet.c_stickerSetCovered(); + if (d.vset.type() == mtpc_stickerSet) { + setData = &d.vset.c_stickerSet(); + } + } break; + case mtpc_stickerSetMultiCovered: { + auto &d = stickerSet.c_stickerSetMultiCovered(); + if (d.vset.type() == mtpc_stickerSet) { + setData = &d.vset.c_stickerSet(); + } + } break; + } + if (setData) { + auto set = Stickers::feedSet(*setData); + if (set->stickers.isEmpty()) { + setsToRequest.insert(set->id, set->access); + } + auto index = order.indexOf(set->id); + if (index >= 0) { + order.removeAt(index); + } + archived.push_back(set->id); + } + } + if (!setsToRequest.isEmpty()) { + for (auto i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) { + App::api()->scheduleStickerSetRequest(i.key(), i.value()); + } + App::api()->requestStickerSets(); + } + Local::writeInstalledStickers(); + Local::writeArchivedStickers(); + Ui::showLayer(new StickersBox(archived), KeepOtherLayers); + + emit App::main()->stickersUpdated(); +} + +void installLocally(uint64 setId) { + auto &sets = Global::RefStickerSets(); + auto it = sets.find(setId); + if (it == sets.end()) { + return; + } + + auto flags = it->flags; + it->flags &= ~(MTPDstickerSet::Flag::f_archived | MTPDstickerSet_ClientFlag::f_unread); + it->flags |= MTPDstickerSet::Flag::f_installed; + auto changedFlags = flags ^ it->flags; + + auto &order = Global::RefStickerSetsOrder(); + int insertAtIndex = 0, currentIndex = order.indexOf(setId); + if (currentIndex != insertAtIndex) { + if (currentIndex > 0) { + order.removeAt(currentIndex); + } + order.insert(insertAtIndex, setId); + } + + auto custom = sets.find(Stickers::CustomSetId); + if (custom != sets.cend()) { + for_const (auto sticker, it->stickers) { + int removeIndex = custom->stickers.indexOf(sticker); + if (removeIndex >= 0) custom->stickers.removeAt(removeIndex); + } + if (custom->stickers.isEmpty()) { + sets.erase(custom); + } + } + Local::writeInstalledStickers(); + if (changedFlags & MTPDstickerSet_ClientFlag::f_unread) Local::writeFeaturedStickers(); + if (changedFlags & MTPDstickerSet::Flag::f_archived) { + auto index = Global::RefArchivedStickerSetsOrder().indexOf(setId); + if (index >= 0) { + Global::RefArchivedStickerSetsOrder().removeAt(index); + Local::writeArchivedStickers(); + } + } + emit App::main()->stickersUpdated(); +} + +void undoInstallLocally(uint64 setId) { + auto &sets = Global::RefStickerSets(); + auto it = sets.find(setId); + if (it == sets.end()) { + return; + } + + it->flags &= ~MTPDstickerSet::Flag::f_installed; + + auto &order = Global::RefStickerSetsOrder(); + int currentIndex = order.indexOf(setId); + if (currentIndex >= 0) { + order.removeAt(currentIndex); + } + + Local::writeInstalledStickers(); + emit App::main()->stickersUpdated(); + + Ui::showLayer(new InformBox(lang(lng_stickers_not_found)), KeepOtherLayers); +} + +void markFeaturedAsRead(uint64 setId) { + if (!FeaturedReaderInstance) { + if (auto main = App::main()) { + FeaturedReaderInstance = new internal::FeaturedReader(main); + } else { + return; + } + } + FeaturedReaderInstance->scheduleRead(setId); +} + +namespace internal { + +void readFeaturedDone() { + Local::writeFeaturedStickers(); + if (App::main()) { + emit App::main()->stickersUpdated(); + } +} + +FeaturedReader::FeaturedReader(QObject *parent) : QObject(parent) +, _timer(new QTimer(this)) { + _timer->setSingleShot(true); + connect(_timer, SIGNAL(timeout()), this, SLOT(onReadSets())); +} + +void FeaturedReader::scheduleRead(uint64 setId) { + if (!_setIds.contains(setId)) { + _setIds.insert(setId); + _timer->start(kReadFeaturedSetsTimeoutMs); + } +} + +void FeaturedReader::onReadSets() { + auto &sets = Global::RefStickerSets(); + auto count = Global::FeaturedStickerSetsUnreadCount(); + QVector wrappedIds; + wrappedIds.reserve(_setIds.size()); + for_const (auto setId, _setIds) { + auto it = sets.find(setId); + if (it != sets.cend()) { + it->flags &= ~MTPDstickerSet_ClientFlag::f_unread; + wrappedIds.append(MTP_long(setId)); + if (count) { + --count; + } + } + } + _setIds.clear(); + + if (!wrappedIds.empty()) { + MTP::send(MTPmessages_ReadFeaturedStickers(MTP_vector(wrappedIds)), rpcDone(&readFeaturedDone)); + Global::SetFeaturedStickerSetsUnreadCount(count); + } +} + +} // namespace internal +} // namespace Stickers diff --git a/Telegram/SourceFiles/stickers/stickers.h b/Telegram/SourceFiles/stickers/stickers.h new file mode 100644 index 0000000000..c2449c6af4 --- /dev/null +++ b/Telegram/SourceFiles/stickers/stickers.h @@ -0,0 +1,49 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +namespace Stickers { + +void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d); +void installLocally(uint64 setId); +void undoInstallLocally(uint64 setId); +void markFeaturedAsRead(uint64 setId); + +namespace internal { + +class FeaturedReader : public QObject { + Q_OBJECT + +public: + FeaturedReader(QObject *parent); + void scheduleRead(uint64 setId); + +private slots: + void onReadSets(); + +private: + QTimer *_timer; + OrderedSet _setIds; + +}; + +} // namespace internal +} // namespace Stickers diff --git a/Telegram/SourceFiles/stickers/stickers.style b/Telegram/SourceFiles/stickers/stickers.style new file mode 100644 index 0000000000..ae74a48f76 --- /dev/null +++ b/Telegram/SourceFiles/stickers/stickers.style @@ -0,0 +1,60 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +using "basic.style"; + +featuredStickersHeader: 45px; +featuredStickersSkip: 15px; + +featuredStickersHeaderFont: semiboldFont; +featuredStickersHeaderFg: windowTextFg; +featuredStickersHeaderTop: 0px; +featuredStickersSubheaderFont: normalFont; +featuredStickersSubheaderFg: #777; +featuredStickersSubheaderTop: 20px; + +featuredStickersAddTop: 3px; +featuredStickersAdd: RoundButton(defaultActiveButton) { + width: -17px; + height: 26px; + textTop: 4px; + downTextTop: 5px; +} + +stickerEmojiSkip: 5px; + +stickersAddIcon: icon { + { "stickers_add", #ffffff }, +}; +stickersAddSize: size(30px, 24px); + +stickersFeaturedHeight: 32px; +stickersFeaturedFont: contactsNameFont; +stickersFeaturedPosition: point(16px, 6px); +stickersFeaturedBadgeFont: semiboldFont; +stickersFeaturedBadgeSize: 21px; +stickersFeaturedPen: contactsNewItemFg; +stickersFeaturedUnreadBg: msgFileInBg; +stickersFeaturedUnreadSize: 5px; +stickersFeaturedUnreadSkip: 5px; +stickersFeaturedUnreadTop: 7px; +stickersFeaturedInstalled: icon { + { "mediaview_save_check", #40ace3 } +}; diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index e6098fa03e..82a9867529 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -1140,7 +1140,7 @@ void DocumentData::setattributes(const QVector &attributes for (int32 i = 0, l = attributes.size(); i < l; ++i) { switch (attributes[i].type()) { case mtpc_documentAttributeImageSize: { - const auto &d(attributes[i].c_documentAttributeImageSize()); + auto &d = attributes[i].c_documentAttributeImageSize(); dimensions = QSize(d.vw.v, d.vh.v); } break; case mtpc_documentAttributeAnimated: if (type == FileDocument || type == StickerDocument || type == VideoDocument) { @@ -1148,14 +1148,16 @@ void DocumentData::setattributes(const QVector &attributes _additional = nullptr; } break; case mtpc_documentAttributeSticker: { - const auto &d(attributes[i].c_documentAttributeSticker()); + auto &d = attributes[i].c_documentAttributeSticker(); if (type == FileDocument) { type = StickerDocument; _additional = std_::make_unique(); } if (sticker()) { sticker()->alt = qs(d.valt); - sticker()->set = d.vstickerset; + if (sticker()->set.type() != mtpc_inputStickerSetID || d.vstickerset.type() == mtpc_inputStickerSetID) { + sticker()->set = d.vstickerset; + } } } break; case mtpc_documentAttributeVideo: { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 3fc4cec2ef..f9627501ce 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -1029,12 +1029,10 @@ struct DocumentAdditionalData { }; struct StickerData : public DocumentAdditionalData { - StickerData() : set(MTP_inputStickerSetEmpty()) { - } ImagePtr img; QString alt; - MTPInputStickerSet set; + MTPInputStickerSet set = MTP_inputStickerSetEmpty(); bool setInstalled() const; StorageImageLocation loc; // doc thumb location diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp index 5c628fb8d7..40a2c0f165 100644 --- a/Telegram/SourceFiles/ui/countryinput.cpp +++ b/Telegram/SourceFiles/ui/countryinput.cpp @@ -438,7 +438,7 @@ CountrySelectBox::CountrySelectBox() : ItemListBox(st::countriesScroll, st::boxW connect(&_filter, SIGNAL(changed()), this, SLOT(onFilterUpdate())); connect(&_filter, SIGNAL(submitted(bool)), this, SLOT(onSubmit())); connect(&_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel())); - connect(&_inner, SIGNAL(mustScrollTo(int, int)), &_scroll, SLOT(scrollToY(int, int))); + connect(&_inner, SIGNAL(mustScrollTo(int, int)), scrollArea(), SLOT(scrollToY(int, int))); connect(&_inner, SIGNAL(countryChosen(const QString&)), this, SIGNAL(countryChosen(const QString&))); _filterCancel.setAttribute(Qt::WA_OpaquePaintEvent); @@ -456,9 +456,9 @@ void CountrySelectBox::keyPressEvent(QKeyEvent *e) { } else if (e->key() == Qt::Key_Up) { _inner.selectSkip(-1); } else if (e->key() == Qt::Key_PageDown) { - _inner.selectSkipPage(_scroll.height(), 1); + _inner.selectSkipPage(scrollArea()->height(), 1); } else if (e->key() == Qt::Key_PageUp) { - _inner.selectSkipPage(_scroll.height(), -1); + _inner.selectSkipPage(scrollArea()->height(), -1); } else { ItemListBox::keyPressEvent(e); } @@ -496,7 +496,7 @@ void CountrySelectBox::onFilterCancel() { } void CountrySelectBox::onFilterUpdate() { - _scroll.scrollToY(0); + scrollArea()->scrollToY(0); if (_filter.getLastText().isEmpty()) { _filterCancel.hide(); } else { diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 63659a9a7d..4af728fe9b 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -39,6 +39,7 @@ '<(src_loc)/overview/overview.style', '<(src_loc)/profile/profile.style', '<(src_loc)/settings/settings.style', + '<(src_loc)/stickers/stickers.style', '<(src_loc)/ui/widgets/widgets.style', ], 'langpacks': [ @@ -185,6 +186,8 @@ '<(src_loc)/boxes/report_box.h', '<(src_loc)/boxes/sessionsbox.cpp', '<(src_loc)/boxes/sessionsbox.h', + '<(src_loc)/boxes/sharebox.cpp', + '<(src_loc)/boxes/sharebox.h', '<(src_loc)/boxes/stickersetbox.cpp', '<(src_loc)/boxes/stickersetbox.h', '<(src_loc)/boxes/usernamebox.cpp', @@ -390,6 +393,10 @@ '<(src_loc)/settings/settings_scale_widget.h', '<(src_loc)/settings/settings_widget.cpp', '<(src_loc)/settings/settings_widget.h', + '<(src_loc)/stickers/emoji_pan.cpp', + '<(src_loc)/stickers/emoji_pan.h', + '<(src_loc)/stickers/stickers.cpp', + '<(src_loc)/stickers/stickers.h', '<(src_loc)/ui/buttons/history_down_button.cpp', '<(src_loc)/ui/buttons/history_down_button.h', '<(src_loc)/ui/buttons/icon_button.cpp',