From 160895f6e33929f45efeeccfe19cc7a0d7b05005 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 2 Sep 2016 12:11:23 -0400 Subject: [PATCH 01/10] Support for game keyboard buttons and score service messages added. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/facades.cpp | 18 +- Telegram/SourceFiles/history.cpp | 181 +++++++--- Telegram/SourceFiles/history.h | 48 ++- Telegram/SourceFiles/historywidget.cpp | 18 +- Telegram/SourceFiles/mtproto/scheme.tl | 14 +- Telegram/SourceFiles/mtproto/scheme_auto.cpp | 120 ++++++- Telegram/SourceFiles/mtproto/scheme_auto.h | 347 +++++++++++++++++-- 8 files changed, 629 insertions(+), 118 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c06c6e297b..510227e206 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."; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index f64038d525..211267147d 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -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: { + case ButtonType::Callback: + case ButtonType::Game: { if (MainWidget *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); } 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/history.cpp b/Telegram/SourceFiles/history.cpp index 60892aea25..55b63f32a8 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -676,7 +676,7 @@ void checkForSwitchInlineButton(HistoryItem *item) { if (auto markup = item->Get()) { for_const (const auto &row, markup->rows) { for_const (const auto &button, row) { - if (button.type == HistoryMessageReplyMarkup::Button::SwitchInline) { + 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); } } @@ -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); } @@ -2552,32 +2553,37 @@ void HistoryMessageReplyMarkup::createFromButtonRows(const QVector 0) { result = std::max(result, 2 * iconWidth + 4 * int(st::msgBotKbIconPadding)); @@ -8005,6 +8012,10 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } } break; + case mtpc_messageActionGameScore: { + updateGameScoreText(&text); + } break; + default: from = QString(); break; } @@ -8019,42 +8030,42 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } } -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) { @@ -8129,13 +8140,62 @@ bool HistoryService::updatePinnedText(const QString *pfrom, QString *ptext) { return result; } +bool HistoryService::updateGameScoreText(QString *ptext) { + bool result = false; + QString from = _from->name, text; + + auto gamescore = Get(); + if (gamescore && gamescore->msg) { + auto getGameTitle = [item = gamescore->msg]() -> QString { + if (auto markup = item->Get()) { + for_const (auto &row, markup->rows) { + for_const (auto &button, row) { + if (button.type == HistoryMessageReplyMarkup::Button::Type::Game) { + auto strData = QString::fromUtf8(button.data); + return 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 { + text = lng_action_game_score(lt_from, from, lt_score, QString::number(gamescore->score), lt_game, lang(lng_deleted_message)); + } + if (ptext) { + *ptext = text; + } else { + setServiceText(text); + if (history()->textCachedFor == this) { + history()->textCachedFor = 0; + } + if (App::main()) { + App::main()->dlgUpdated(history(), id); + } + App::historyUpdateDependent(this); + } + return result; +} + 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); @@ -8152,6 +8212,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; @@ -8294,7 +8361,14 @@ 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 (auto gamescore = Get()) { + if (outer.contains(x, y)) { + result.link = gamescore->lnk; + return result; + } + } if (trect.contains(x, y)) { textstyleSet(&st::serviceTextStyle); auto textRequest = request.forText(); @@ -8312,6 +8386,9 @@ void HistoryService::applyEditionToEmpty() { setServiceText(QString()); removeMedia(); + clearDependency(); + UpdateComponents(0); + finishEditionToEmpty(); } @@ -8350,12 +8427,16 @@ void HistoryService::eraseFromOverview() { } } -HistoryService::~HistoryService() { - if (auto pinned = Get()) { - if (pinned->msg) { - App::historyUnregDependency(this, pinned->msg); +void HistoryService::clearDependency() { + if (auto dependent = GetDependentData()) { + if (dependent->msg) { + App::historyUnregDependency(this, dependent->msg); } } +} + +HistoryService::~HistoryService() { + clearDependency(); _media.clear(); } diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 44c8123443..debb1f0d2e 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; } @@ -2842,9 +2848,31 @@ protected: void removeMedia(); + 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() { + if (Has()) { + return updatePinnedText(); + } else if (Has()) { + return updateGameScoreText(); + } + return false; + } + void clearDependency(); + void setMessageByAction(const MTPmessageAction &action); - bool updatePinned(bool force = false); bool updatePinnedText(const QString *pfrom = nullptr, QString *ptext = nullptr); + bool updateGameScoreText(QString *ptext = nullptr); }; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 4230b4be19..dd67031522 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -5764,7 +5764,19 @@ 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)); + MTPmessages_GetBotCallbackAnswer::Flags flags = 0; + QByteArray sendData; + int32 sendGameId = 0; + using ButtonType = HistoryMessageReplyMarkup::Button::Type; + if (button->type == ButtonType::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 +5789,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 +5800,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))); diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index 64cb8d5ff1..5d29d2a42f 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -253,6 +253,7 @@ messageActionChatMigrateTo#51bdb021 channel_id:int = MessageAction; messageActionChannelMigrateFrom#b055eaee title:string chat_id:int = MessageAction; messageActionPinMessage#94bd38ed = MessageAction; messageActionHistoryClear#9fbab604 = MessageAction; +messageActionGameScore#3a14cfa5 game_id:int score:int = MessageAction; 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; @@ -389,9 +390,9 @@ updateBotInlineQuery#54826690 flags:# query_id:long user_id:int query:string geo updateBotInlineSend#e48f964 flags:# user_id:int query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update; updateEditChannelMessage#1b3f4df7 message:Message pts:int pts_count:int = Update; updateChannelPinnedMessage#98592475 channel_id:int id:int = Update; -updateBotCallbackQuery#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; @@ -573,6 +574,7 @@ keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton; keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton; keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton; keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton; +keyboardButtonGame#28fc3164 text:string game_title:string game_id:int start_param:string = KeyboardButton; keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; @@ -796,7 +798,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; @@ -845,7 +847,7 @@ messages.sendInlineBotResult#b16e06fe flags:# silent:flags.5?true background:fla messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.editMessage#ce91e4ca flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = 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; @@ -857,6 +859,8 @@ messages.saveRecentSticker#348e39bf id:InputDocument unsave:Bool = Bool; messages.clearRecentStickers#ab02e5d2 = Bool; messages.getUnusedStickers#4309d65b limit:int = Vector; 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; updates.getState#edd4882a = updates.State; updates.getDifference#a041495 pts:int date:int qts:int = updates.Difference; @@ -908,4 +912,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..5043ff1f7c 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.cpp +++ b/Telegram/SourceFiles/mtproto/scheme_auto.cpp @@ -1672,6 +1672,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); @@ -2970,6 +2984,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 +2993,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 +3020,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 +3029,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; } } @@ -4585,6 +4607,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); @@ -6530,6 +6568,26 @@ void _serialize_messages_clearRecentStickers(MTPStringLogger &to, int32 stage, i to.add("{ messages_clearRecentStickers }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); } +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) { if (stage) { to.add(",\n").addSpaces(lev); @@ -7447,10 +7505,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 +7704,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); @@ -8189,6 +8269,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 +8278,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; } } @@ -8619,6 +8703,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); @@ -8854,6 +8939,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); @@ -9002,6 +9088,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 +9168,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); diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.h b/Telegram/SourceFiles/mtproto/scheme_auto.h index b4e9ede567..ee3c653ae4 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; @@ -181,6 +181,7 @@ enum { mtpc_messageActionChannelMigrateFrom = 0xb055eaee, mtpc_messageActionPinMessage = 0x94bd38ed, mtpc_messageActionHistoryClear = 0x9fbab604, + mtpc_messageActionGameScore = 0x3a14cfa5, mtpc_dialog = 0x66ffba14, mtpc_photoEmpty = 0x2331b22d, mtpc_photo = 0xcded42fe, @@ -285,9 +286,9 @@ enum { 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, @@ -416,6 +417,7 @@ enum { mtpc_keyboardButtonRequestPhone = 0xb16a6c29, mtpc_keyboardButtonRequestGeoLocation = 0xfc796b3f, mtpc_keyboardButtonSwitchInline = 0x568a748, + mtpc_keyboardButtonGame = 0x28fc3164, mtpc_keyboardButtonRow = 0x77608b83, mtpc_replyKeyboardHide = 0xa03e5b85, mtpc_replyKeyboardForceReply = 0xf4108aa0, @@ -638,7 +640,7 @@ 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, @@ -650,6 +652,8 @@ enum { mtpc_messages_clearRecentStickers = 0xab02e5d2, mtpc_messages_getUnusedStickers = 0x4309d65b, mtpc_messages_getArchivedStickers = 0x906e241f, + mtpc_messages_setGameScore = 0xdfbc7c1f, + mtpc_messages_setInlineGameScore = 0x54f882f1, mtpc_updates_getState = 0xedd4882a, mtpc_updates_getDifference = 0xa041495, mtpc_updates_getChannelDifference = 0xbb32d7c0, @@ -893,6 +897,7 @@ class MTPDmessageActionChatJoinedByLink; class MTPDmessageActionChannelCreate; class MTPDmessageActionChatMigrateTo; class MTPDmessageActionChannelMigrateFrom; +class MTPDmessageActionGameScore; class MTPdialog; class MTPDdialog; @@ -1233,6 +1238,7 @@ class MTPDkeyboardButtonCallback; class MTPDkeyboardButtonRequestPhone; class MTPDkeyboardButtonRequestGeoLocation; class MTPDkeyboardButtonSwitchInline; +class MTPDkeyboardButtonGame; class MTPkeyboardButtonRow; class MTPDkeyboardButtonRow; @@ -3884,6 +3890,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 +3920,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 +7904,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 +7931,7 @@ private: explicit MTPkeyboardButton(MTPDkeyboardButtonRequestPhone *_data); explicit MTPkeyboardButton(MTPDkeyboardButtonRequestGeoLocation *_data); explicit MTPkeyboardButton(MTPDkeyboardButtonSwitchInline *_data); + explicit MTPkeyboardButton(MTPDkeyboardButtonGame *_data); friend class MTP::internal::TypeCreator; @@ -11142,6 +11174,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 { @@ -12196,16 +12239,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 +12279,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 { @@ -13525,6 +13596,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() { @@ -18363,14 +18447,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; @@ -20575,37 +20661,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 +20720,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)) { } }; @@ -21082,6 +21188,133 @@ 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 MTPupdates_getState { // RPC method 'updates.getState' public: MTPupdates_getState() { @@ -23333,6 +23566,9 @@ 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)); } @@ -23645,14 +23881,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)); @@ -24038,6 +24274,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)); } @@ -27461,6 +27700,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 +27765,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 +27814,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 +27836,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 +27858,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 +27899,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()) { } @@ -29249,7 +29509,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 +29517,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()); @@ -29568,11 +29828,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 +29846,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()); @@ -29856,11 +30120,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 +30136,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()); @@ -30174,14 +30442,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); @@ -33224,6 +33494,10 @@ inline uint32 MTPkeyboardButton::innerLength() const { const MTPDkeyboardButtonSwitchInline &v(c_keyboardButtonSwitchInline()); return v.vflags.innerLength() + v.vtext.innerLength() + v.vquery.innerLength(); } + case mtpc_keyboardButtonGame: { + const MTPDkeyboardButtonGame &v(c_keyboardButtonGame()); + return v.vtext.innerLength() + v.vgame_title.innerLength() + v.vgame_id.innerLength() + v.vstart_param.innerLength(); + } } return 0; } @@ -33268,6 +33542,14 @@ inline void MTPkeyboardButton::read(const mtpPrime *&from, const mtpPrime *end, v.vtext.read(from, end); v.vquery.read(from, end); } break; + case mtpc_keyboardButtonGame: _type = cons; { + if (!data) setData(new MTPDkeyboardButtonGame()); + MTPDkeyboardButtonGame &v(_keyboardButtonGame()); + v.vtext.read(from, end); + v.vgame_title.read(from, end); + v.vgame_id.read(from, end); + v.vstart_param.read(from, end); + } break; default: throw mtpErrorUnexpected(cons, "MTPkeyboardButton"); } } @@ -33301,6 +33583,13 @@ inline void MTPkeyboardButton::write(mtpBuffer &to) const { v.vtext.write(to); v.vquery.write(to); } break; + case mtpc_keyboardButtonGame: { + const MTPDkeyboardButtonGame &v(c_keyboardButtonGame()); + v.vtext.write(to); + v.vgame_title.write(to); + v.vgame_id.write(to); + v.vstart_param.write(to); + } break; } } inline MTPkeyboardButton::MTPkeyboardButton(mtpTypeId type) : mtpDataOwner(0), _type(type) { @@ -33311,6 +33600,7 @@ inline MTPkeyboardButton::MTPkeyboardButton(mtpTypeId type) : mtpDataOwner(0), _ case mtpc_keyboardButtonRequestPhone: setData(new MTPDkeyboardButtonRequestPhone()); break; case mtpc_keyboardButtonRequestGeoLocation: setData(new MTPDkeyboardButtonRequestGeoLocation()); break; case mtpc_keyboardButtonSwitchInline: setData(new MTPDkeyboardButtonSwitchInline()); break; + case mtpc_keyboardButtonGame: setData(new MTPDkeyboardButtonGame()); break; default: throw mtpErrorBadTypeId(type, "MTPkeyboardButton"); } } @@ -33326,6 +33616,8 @@ inline MTPkeyboardButton::MTPkeyboardButton(MTPDkeyboardButtonRequestGeoLocation } inline MTPkeyboardButton::MTPkeyboardButton(MTPDkeyboardButtonSwitchInline *_data) : mtpDataOwner(_data), _type(mtpc_keyboardButtonSwitchInline) { } +inline MTPkeyboardButton::MTPkeyboardButton(MTPDkeyboardButtonGame *_data) : mtpDataOwner(_data), _type(mtpc_keyboardButtonGame) { +} inline MTPkeyboardButton MTP_keyboardButton(const MTPstring &_text) { return MTP::internal::TypeCreator::new_keyboardButton(_text); } @@ -33345,6 +33637,9 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(MTPDkeyboardButtonSwitchInline::Flags) inline MTPkeyboardButton MTP_keyboardButtonSwitchInline(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()) { } From 589b7310c1d3c89c038d360bc89545e46c398c81 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 3 Sep 2016 17:27:22 -0400 Subject: [PATCH 02/10] Improved service messages about game scores: some links added. --- Telegram/SourceFiles/application.cpp | 9 +- Telegram/SourceFiles/history.cpp | 221 ++++++++++++++------------- Telegram/SourceFiles/history.h | 20 +-- 3 files changed, 124 insertions(+), 126 deletions(-) diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index c4274b9a3f..fea12175a1 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -1083,10 +1083,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(); @@ -1095,7 +1094,7 @@ AppClass::~AppClass() { MTP::finish(); - AppObject = 0; + AppObject = nullptr; deleteAndMark(_uploader); deleteAndMark(_translator); diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index f7d992c72a..a8a2553323 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -674,8 +674,8 @@ void checkForSwitchInlineButton(HistoryItem *item) { return; } if (auto markup = item->Get()) { - for_const (const auto &row, markup->rows) { - for_const (const auto &button, row) { + 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; @@ -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(); @@ -2542,11 +2542,11 @@ void HistoryMessageReplyMarkup::createFromButtonRows(const QVector 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 { @@ -7902,9 +7904,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) { @@ -7924,7 +7926,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 { @@ -7938,12 +7940,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 { @@ -7960,18 +7962,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)); } @@ -7979,13 +7981,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 { @@ -7995,7 +7997,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 { @@ -8004,29 +8006,19 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } break; case mtpc_messageActionPinMessage: { - if (updatePinnedText(&from, &text)) { - auto pinned = Get(); - t_assert(pinned != nullptr); - - links.push_back(pinned->lnk); - } + preparePinnedText(from, &text, &links); } break; case mtpc_messageActionGameScore: { - updateGameScoreText(&text); + 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)); } } @@ -8068,14 +8060,9 @@ bool HistoryService::updateDependent(bool force) { 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(); @@ -8121,38 +8108,30 @@ 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; - } 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); + *outText = text; + if (second) { + outLinks->push_back(second); } return result; } -bool HistoryService::updateGameScoreText(QString *ptext) { +bool HistoryService::prepareGameScoreText(const QString &from, QString *outText, Links *outLinks) { bool result = false; - QString from = _from->name, text; + QString text; + ClickHandlerPtr second; auto gamescore = Get(); if (gamescore && gamescore->msg) { - auto getGameTitle = [item = gamescore->msg]() -> QString { + auto getGameTitle = [item = gamescore->msg, &second]() -> QString { if (auto markup = item->Get()) { - for_const (auto &row, markup->rows) { - for_const (auto &button, row) { + 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); - return strData.mid(strData.indexOf(',') + 1); + second = MakeShared(item, i, j); + return textcmdLink(2, strData.mid(strData.indexOf(',') + 1)); } } } @@ -8167,17 +8146,9 @@ bool HistoryService::updateGameScoreText(QString *ptext) { } else { text = lng_action_game_score(lt_from, from, lt_score, QString::number(gamescore->score), lt_game, lang(lng_deleted_message)); } - if (ptext) { - *ptext = text; - } else { - setServiceText(text); - if (history()->textCachedFor == this) { - history()->textCachedFor = 0; - } - if (App::main()) { - App::main()->dlgUpdated(history(), id); - } - App::historyUpdateDependent(this); + *outText = text; + if (second) { + outLinks->push_back(second); } return result; } @@ -8203,7 +8174,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() { @@ -8241,10 +8212,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; @@ -8363,18 +8338,17 @@ HistoryTextState HistoryService::getState(int x, int y, HistoryStateRequest requ } auto outer = QRect(left, st::msgServiceMargin.top(), width, height); auto trect = outer.marginsAdded(-st::msgServicePadding); - if (auto gamescore = Get()) { - if (outer.contains(x, y)) { - result.link = gamescore->lnk; - return result; - } - } 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); } @@ -8383,7 +8357,7 @@ HistoryTextState HistoryService::getState(int x, int y, HistoryStateRequest requ void HistoryService::applyEditionToEmpty() { TextWithEntities textWithEntities = { QString(), EntitiesInText() }; - setServiceText(QString()); + setServiceText(QString(), Links()); removeMedia(); clearDependency(); @@ -8427,6 +8401,31 @@ void HistoryService::eraseFromOverview() { } } +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) { @@ -8442,14 +8441,18 @@ HistoryService::~HistoryService() { 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 debb1f0d2e..9773f02193 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -2832,8 +2832,6 @@ public: QString inDialogsText() const override; QString inReplyText() const override; - void setServiceText(const QString &text); - ~HistoryService(); protected: @@ -2846,8 +2844,12 @@ 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; @@ -2860,19 +2862,13 @@ protected: return const_cast(this)->GetDependentData(); } bool updateDependent(bool force = false); - bool updateDependentText() { - if (Has()) { - return updatePinnedText(); - } else if (Has()) { - return updateGameScoreText(); - } - return false; - } + bool updateDependentText(); void clearDependency(); void setMessageByAction(const MTPmessageAction &action); - bool updatePinnedText(const QString *pfrom = nullptr, QString *ptext = nullptr); - bool updateGameScoreText(QString *ptext = nullptr); + + bool preparePinnedText(const QString &from, QString *outText, Links *outLinks); + bool prepareGameScoreText(const QString &from, QString *outText, Links *outLinks); }; From 52a7ed77ba81c3a9b83d453c2febd88b8e73349a Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 6 Sep 2016 15:28:37 +0300 Subject: [PATCH 03/10] First version of ShareBox done, cute animations. Temporarily ShareBox is opened instead of ContactsBox, for testing. --- Telegram/Resources/langs/lang.strings | 3 + Telegram/SourceFiles/boxes/abstractbox.cpp | 26 +- Telegram/SourceFiles/boxes/abstractbox.h | 14 +- Telegram/SourceFiles/boxes/boxes.style | 22 + Telegram/SourceFiles/boxes/contactsbox.cpp | 23 +- Telegram/SourceFiles/boxes/sessionsbox.cpp | 4 +- Telegram/SourceFiles/boxes/sharebox.cpp | 838 +++++++++++++++++++ Telegram/SourceFiles/boxes/sharebox.h | 201 +++++ Telegram/SourceFiles/boxes/stickersetbox.cpp | 26 +- Telegram/SourceFiles/core/basic_types.h | 8 +- Telegram/SourceFiles/core/lambda_wrap.h | 39 + Telegram/SourceFiles/title.cpp | 5 +- Telegram/SourceFiles/ui/countryinput.cpp | 8 +- Telegram/Telegram.pro | 2 + Telegram/gyp/Telegram.gyp | 2 + 15 files changed, 1167 insertions(+), 54 deletions(-) create mode 100644 Telegram/SourceFiles/boxes/sharebox.cpp create mode 100644 Telegram/SourceFiles/boxes/sharebox.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 510227e206..39229559a8 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -871,6 +871,9 @@ 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_contact_phone" = "Phone number"; "lng_enter_contact_data" = "New Contact"; "lng_edit_group_title" = "Edit group name"; 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..b97752c2f0 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -87,3 +87,25 @@ 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; 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..6e6b6ad5c9 --- /dev/null +++ b/Telegram/SourceFiles/boxes/sharebox.cpp @@ -0,0 +1,838 @@ +/* +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" + +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(_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)), this, SLOT(onSubmit())); + connect(_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel())); + connect(_inner, SIGNAL(selectAllQuery()), _filter, SLOT(selectAll())); + 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::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() { + scrollArea()->scrollToY(0); + _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::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::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) { + auto row = ([this, index]() -> Dialogs::Row* { + if (index < 0) return nullptr; + if (_filter.isEmpty()) return _chatsIndexed->rowAtY(index, 1); + return (index < _filtered.size()) ? _filtered[index] : nullptr; + })(); + return row ? static_cast(row->attached) : 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; + } + } + 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); + } +} + +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() || !_byUsername.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; + } + indexFrom -= _chatsIndexed->size(); + indexTo -= _chatsIndexed->size(); + } + if (!_byUsername.isEmpty()) { + if (indexFrom < 0) indexFrom = 0; + while (indexFrom < indexTo) { + if (indexFrom >= d_byUsername.size()) { + break; + } + paintChat(p, d_byUsername[indexFrom], 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 { + if (!_filtered.isEmpty()) { + if (indexFrom < 0) indexFrom = 0; + while (indexFrom < indexTo) { + if (indexFrom >= _filtered.size()) { + break; + } + paintChat(p, getChat(_filtered[indexFrom]), indexFrom); + ++indexFrom; + } + indexFrom -= _filtered.size(); + indexTo -= _filtered.size(); + } + if (!_byUsernameFiltered.isEmpty()) { + if (indexFrom < 0) indexFrom = 0; + while (indexFrom < indexTo) { + if (indexFrom >= d_byUsernameFiltered.size()) { + break; + } + paintChat(p, d_byUsernameFiltered[indexFrom], 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; + auto count = _filter.isEmpty() ? (_chatsIndexed->size() + d_byUsername.size()) : (_filtered.size() + d_byUsernameFiltered.size()); + if (upon >= count) { + upon = -1; + } + _upon = upon; +} + +void ShareInner::mousePressEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton) { + updateUpon(e->pos()); + if (_upon >= 0) { + changeCheckState(getChatAtIndex(_upon)); + } + } +} + +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) { + 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); + 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(); + d_byUsernameFiltered.clear(); + for (int i = 0, l = _byUsernameDatas.size(); i < l; ++i) { + delete _byUsernameDatas[i]; + } + _byUsernameDatas.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); + } + } + } + + _byUsernameFiltered.reserve(_byUsername.size()); + d_byUsernameFiltered.reserve(d_byUsername.size()); + for (int i = 0, l = _byUsername.size(); i < l; ++i) { + auto &names = _byUsername[i]->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) { + _byUsernameFiltered.push_back(_byUsername[i]); + d_byUsernameFiltered.push_back(d_byUsername[i]); + } + } + } + refresh(); + + _searching = true; + emit searchByUsername(); + } + 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); + _byUsernameDatas.push_back(chat); + updateChatName(chat, peer); + + _byUsernameFiltered.push_back(peer); + d_byUsernameFiltered.push_back(chat); + } + } + _searching = false; + refresh(); +} + +void ShareInner::refresh() { + if (_filter.isEmpty()) { + if (!_chatsIndexed->isEmpty() || !_byUsername.isEmpty()) { + auto count = _chatsIndexed->size() + _byUsername.size(); + auto rows = (count / _columnCount) + (count % _columnCount ? 1 : 0); + resize(width(), _rowsTop + rows * _rowHeight); + } else { + resize(width(), st::noContactsHeight); + } + } else { + if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) { + resize(width(), st::noContactsHeight); + } else { + auto count = _filtered.size() + _byUsernameFiltered.size(); + auto rows = (count / _columnCount) + (count % _columnCount ? 1 : 0); + resize(width(), _rowsTop + rows * _rowHeight); + } + } + update(); +} + +ShareInner::~ShareInner() { + for_const (auto chat, _dataMap) { + delete chat; + } +} + +QVector ShareInner::selected() const { + QVector result; + result.reserve(_dataMap.size() + _byUsername.size()); + for_const (auto chat, _dataMap) { + if (chat->selected) { + result.push_back(chat->peer); + } + } + for (int i = 0, l = _byUsername.size(); i < l; ++i) { + if (d_byUsername[i]->selected) { + result.push_back(_byUsername[i]); + } + } + return result; +} + +} // namespace internal diff --git a/Telegram/SourceFiles/boxes/sharebox.h b/Telegram/SourceFiles/boxes/sharebox.h new file mode 100644 index 0000000000..a7b9820bd2 --- /dev/null +++ b/Telegram/SourceFiles/boxes/sharebox.h @@ -0,0 +1,201 @@ +/* +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 + +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(); + +protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *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; + +}; + +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 setVisibleTopBottom(int visibleTop, int visibleBottom) override; + void updateFilter(QString filter = QString()); + + ~ShareInner(); + +signals: + void selectAllQuery(); + 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); + + 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 _byUsername, _byUsernameFiltered; + ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas + ByUsernameDatas _byUsernameDatas; + +}; + +} // namespace internal diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 29b8949cf1..27523d7077 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -357,7 +357,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 +394,7 @@ void StickerSetBox::onUpdateButtons() { } void StickerSetBox::onScroll() { - _inner.setScrollBottom(_scroll.scrollTop() + _scroll.height()); + _inner.setScrollBottom(scrollArea()->scrollTop() + scrollArea()->height()); } void StickerSetBox::showAll() { @@ -1326,7 +1326,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 +1389,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(); @@ -1404,8 +1404,8 @@ void StickersBox::onScroll() { 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;) { @@ -1522,7 +1522,7 @@ 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); if (_topShadow) { _topShadow->setGeometry(0, st::boxTitleHeight + _aboutHeight, width(), st::lineWidth); } @@ -1546,14 +1546,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 +1570,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/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/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/title.cpp b/Telegram/SourceFiles/title.cpp index f75eb8d58b..29a9e7ca62 100644 --- a/Telegram/SourceFiles/title.cpp +++ b/Telegram/SourceFiles/title.cpp @@ -142,12 +142,13 @@ void TitleWidget::setHideLevel(float64 level) { } } } - +#include "boxes/sharebox.h" // TODO void TitleWidget::onContacts() { if (App::wnd() && App::wnd()->isHidden()) App::wnd()->showFromTray(); if (!App::self()) return; - Ui::showLayer(new ContactsBox()); + Ui::showLayer(new ShareBox([](const QVector &result) { + })); } void TitleWidget::onAbout() { 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/Telegram.pro b/Telegram/Telegram.pro index 0b1fff886d..73b4b11db4 100644 --- a/Telegram/Telegram.pro +++ b/Telegram/Telegram.pro @@ -162,6 +162,7 @@ SOURCES += \ ./SourceFiles/boxes/photosendbox.cpp \ ./SourceFiles/boxes/report_box.cpp \ ./SourceFiles/boxes/sessionsbox.cpp \ + ./SourceFiles/boxes/sharebox.cpp \ ./SourceFiles/boxes/stickersetbox.cpp \ ./SourceFiles/boxes/usernamebox.cpp \ ./SourceFiles/core/basic_types.cpp \ @@ -350,6 +351,7 @@ HEADERS += \ ./SourceFiles/boxes/photosendbox.h \ ./SourceFiles/boxes/report_box.h \ ./SourceFiles/boxes/sessionsbox.h \ + ./SourceFiles/boxes/sharebox.h \ ./SourceFiles/boxes/stickersetbox.h \ ./SourceFiles/boxes/usernamebox.h \ ./SourceFiles/core/basic_types.h \ diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 0583d2dbd8..9bffcf52f4 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -183,6 +183,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', From 34331f558f199a9f520efb7ffab26d2568452f59 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 6 Sep 2016 17:45:10 +0300 Subject: [PATCH 04/10] ShareBox: keyboard handle, animated scroll, chosen items jump to top. --- Telegram/SourceFiles/boxes/boxes.style | 1 + Telegram/SourceFiles/boxes/sharebox.cpp | 218 +++++++++++------- Telegram/SourceFiles/boxes/sharebox.h | 21 +- .../dialogs/dialogs_indexed_list.cpp | 10 + .../dialogs/dialogs_indexed_list.h | 1 + Telegram/SourceFiles/dialogs/dialogs_list.cpp | 8 + Telegram/SourceFiles/dialogs/dialogs_list.h | 5 +- 7 files changed, 174 insertions(+), 90 deletions(-) diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index b97752c2f0..820e9a1e81 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -109,3 +109,4 @@ shareNameTop: 6px; shareColumnSkip: 6px; shareSelectDuration: 150; shareActivateDuration: 150; +shareScrollDuration: 300; diff --git a/Telegram/SourceFiles/boxes/sharebox.cpp b/Telegram/SourceFiles/boxes/sharebox.cpp index 6e6b6ad5c9..911b997786 100644 --- a/Telegram/SourceFiles/boxes/sharebox.cpp +++ b/Telegram/SourceFiles/boxes/sharebox.cpp @@ -42,13 +42,14 @@ ShareBox::ShareBox(SubmitCallback &&callback) : ItemListBox(st::boxScroll) 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)), this, SLOT(onSubmit())); + connect(_filter, SIGNAL(submitted(bool)), _inner, SLOT(onSelectActive())); connect(_filterCancel, SIGNAL(clicked()), this, SLOT(onFilterCancel())); - connect(_inner, SIGNAL(selectAllQuery()), _filter, SLOT(selectAll())); + connect(_inner, SIGNAL(filterCancel()), this, SLOT(onFilterCancel())); connect(_inner, SIGNAL(searchByUsername()), this, SLOT(onNeedSearchByUsername())); _filterCancel->setAttribute(Qt::WA_OpaquePaintEvent); @@ -149,6 +150,24 @@ void ShareBox::resizeEvent(QResizeEvent *e) { _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(); @@ -163,7 +182,6 @@ void ShareBox::onFilterCancel() { } void ShareBox::onFilterUpdate() { - scrollArea()->scrollToY(0); _filterCancel->setVisible(!_filter->getLastText().isEmpty()); _inner->updateFilter(_filter->getLastText()); } @@ -180,6 +198,21 @@ void ShareBox::onSelectedChanged() { 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(); @@ -218,6 +251,36 @@ 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); @@ -247,12 +310,22 @@ void ShareInner::repaintChatAtIndex(int index) { } ShareInner::Chat *ShareInner::getChatAtIndex(int index) { + if (index < 0) return nullptr; auto row = ([this, index]() -> Dialogs::Row* { - if (index < 0) return nullptr; if (_filter.isEmpty()) return _chatsIndexed->rowAtY(index, 1); return (index < _filtered.size()) ? _filtered[index] : nullptr; })(); - return row ? static_cast(row->attached) : 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) { @@ -275,6 +348,12 @@ int ShareInner::chatIndex(PeerData *peer) const { } ++index; } + for_const (auto row, d_byUsernameFiltered) { + if (row->peer == peer) { + return index; + } + ++index; + } } return -1; } @@ -347,6 +426,8 @@ void ShareInner::setActive(int active) { _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) { @@ -441,28 +522,14 @@ void ShareInner::paintEvent(QPaintEvent *e) { auto indexFrom = rowFrom * _columnCount; auto indexTo = rowTo * _columnCount; if (_filter.isEmpty()) { - if (!_chatsIndexed->isEmpty() || !_byUsername.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; - } - indexFrom -= _chatsIndexed->size(); - indexTo -= _chatsIndexed->size(); - } - if (!_byUsername.isEmpty()) { - if (indexFrom < 0) indexFrom = 0; - while (indexFrom < indexTo) { - if (indexFrom >= d_byUsername.size()) { - break; - } - paintChat(p, d_byUsername[indexFrom], indexFrom); - ++indexFrom; + 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 @@ -475,7 +542,8 @@ void ShareInner::paintEvent(QPaintEvent *e) { p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); } else { - if (!_filtered.isEmpty()) { + auto filteredSize = _filtered.size(); + if (filteredSize) { if (indexFrom < 0) indexFrom = 0; while (indexFrom < indexTo) { if (indexFrom >= _filtered.size()) { @@ -484,8 +552,8 @@ void ShareInner::paintEvent(QPaintEvent *e) { paintChat(p, getChat(_filtered[indexFrom]), indexFrom); ++indexFrom; } - indexFrom -= _filtered.size(); - indexTo -= _filtered.size(); + indexFrom -= filteredSize; + indexTo -= filteredSize; } if (!_byUsernameFiltered.isEmpty()) { if (indexFrom < 0) indexFrom = 0; @@ -493,7 +561,7 @@ void ShareInner::paintEvent(QPaintEvent *e) { if (indexFrom >= d_byUsernameFiltered.size()) { break; } - paintChat(p, d_byUsernameFiltered[indexFrom], indexFrom); + paintChat(p, d_byUsernameFiltered[indexFrom], filteredSize + indexFrom); ++indexFrom; } } @@ -523,8 +591,7 @@ void ShareInner::updateUpon(const QPoint &pos) { 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; - auto count = _filter.isEmpty() ? (_chatsIndexed->size() + d_byUsername.size()) : (_filtered.size() + d_byUsernameFiltered.size()); - if (upon >= count) { + if (upon >= displayedChatsCount()) { upon = -1; } _upon = upon; @@ -533,12 +600,14 @@ void ShareInner::updateUpon(const QPoint &pos) { void ShareInner::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { updateUpon(e->pos()); - if (_upon >= 0) { - changeCheckState(getChatAtIndex(_upon)); - } + 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; @@ -563,6 +632,20 @@ float64 anim_bumpy(const float64 &delta, const float64 &dt) { } 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); @@ -582,7 +665,9 @@ void ShareInner::changeCheckState(Chat *chat) { START_ANIMATION(chat->selection, func([this, chat] { repaintChat(chat->peer); }), chat->selected ? 0 : 1, chat->selected ? 1 : 0, st::shareSelectDuration, anim_bumpy); - setActive(chatIndex(chat->peer)); + if (chat->selected) { + setActive(chatIndex(chat->peer)); + } emit selectedChanged(); } @@ -684,11 +769,10 @@ void ShareInner::updateFilter(QString filter) { _filter = filter; _byUsernameFiltered.clear(); - d_byUsernameFiltered.clear(); - for (int i = 0, l = _byUsernameDatas.size(); i < l; ++i) { - delete _byUsernameDatas[i]; + for (int i = 0, l = d_byUsernameFiltered.size(); i < l; ++i) { + delete d_byUsernameFiltered[i]; } - _byUsernameDatas.clear(); + d_byUsernameFiltered.clear(); if (_filter.isEmpty()) { refresh(); @@ -731,34 +815,13 @@ void ShareInner::updateFilter(QString filter) { } } } - - _byUsernameFiltered.reserve(_byUsername.size()); - d_byUsernameFiltered.reserve(d_byUsername.size()); - for (int i = 0, l = _byUsername.size(); i < l; ++i) { - auto &names = _byUsername[i]->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) { - _byUsernameFiltered.push_back(_byUsername[i]); - d_byUsernameFiltered.push_back(d_byUsername[i]); - } - } } refresh(); _searching = true; emit searchByUsername(); } + setActive(-1); update(); loadProfilePhotos(0); } @@ -781,8 +844,10 @@ void ShareInner::peopleReceived(const QString &query, const QVector &pe if (!peer || !peer->canWrite()) continue; auto chat = new Chat(peer); - _byUsernameDatas.push_back(chat); updateChatName(chat, peer); + if (auto row = _chatsIndexed->getRow(peer->id)) { + continue; + } _byUsernameFiltered.push_back(peer); d_byUsernameFiltered.push_back(chat); @@ -793,22 +858,12 @@ void ShareInner::peopleReceived(const QString &query, const QVector &pe } void ShareInner::refresh() { - if (_filter.isEmpty()) { - if (!_chatsIndexed->isEmpty() || !_byUsername.isEmpty()) { - auto count = _chatsIndexed->size() + _byUsername.size(); - auto rows = (count / _columnCount) + (count % _columnCount ? 1 : 0); - resize(width(), _rowsTop + rows * _rowHeight); - } else { - resize(width(), st::noContactsHeight); - } + auto count = displayedChatsCount(); + if (count) { + auto rows = (count / _columnCount) + (count % _columnCount ? 1 : 0); + resize(width(), _rowsTop + rows * _rowHeight); } else { - if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) { - resize(width(), st::noContactsHeight); - } else { - auto count = _filtered.size() + _byUsernameFiltered.size(); - auto rows = (count / _columnCount) + (count % _columnCount ? 1 : 0); - resize(width(), _rowsTop + rows * _rowHeight); - } + resize(width(), st::noContactsHeight); } update(); } @@ -821,17 +876,12 @@ ShareInner::~ShareInner() { QVector ShareInner::selected() const { QVector result; - result.reserve(_dataMap.size() + _byUsername.size()); + result.reserve(_dataMap.size()); for_const (auto chat, _dataMap) { if (chat->selected) { result.push_back(chat->peer); } } - for (int i = 0, l = _byUsername.size(); i < l; ++i) { - if (d_byUsername[i]->selected) { - result.push_back(_byUsername[i]); - } - } return result; } diff --git a/Telegram/SourceFiles/boxes/sharebox.h b/Telegram/SourceFiles/boxes/sharebox.h index a7b9820bd2..fa4c1ab8a3 100644 --- a/Telegram/SourceFiles/boxes/sharebox.h +++ b/Telegram/SourceFiles/boxes/sharebox.h @@ -55,9 +55,12 @@ private slots: 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; @@ -90,6 +93,8 @@ private: using PeopleQueries = QMap; PeopleQueries _peopleQueries; + IntAnimation _scrollAnimation; + }; namespace internal { @@ -105,13 +110,20 @@ public: 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 selectAllQuery(); + void mustScrollTo(int ymin, int ymax); + void filterCancel(); void searchByUsername(); void selectedChanged(); @@ -127,6 +139,8 @@ private: // Observed notifications. void notifyPeerUpdated(const Notify::PeerUpdate &update); + int displayedChatsCount() const; + static constexpr int WideCacheScale = 4; struct Chat { Chat(PeerData *peer); @@ -192,9 +206,8 @@ private: QString _lastQuery; using ByUsernameRows = QVector; using ByUsernameDatas = QVector; - ByUsernameRows _byUsername, _byUsernameFiltered; - ByUsernameDatas d_byUsername, d_byUsernameFiltered; // filtered is partly subset of d_byUsername, partly subset of _byUsernameDatas - ByUsernameDatas _byUsernameDatas; + ByUsernameRows _byUsernameFiltered; + ByUsernameDatas d_byUsernameFiltered; }; 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; } From 82d92d21f6b79c2e941d1626864563e326292082 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 7 Sep 2016 12:04:57 +0300 Subject: [PATCH 05/10] ShareBox used to share game score using tg:// link. Scheme updated. --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/boxes/sharebox.cpp | 107 ++++++++ Telegram/SourceFiles/boxes/sharebox.h | 3 + Telegram/SourceFiles/boxes/stickersetbox.cpp | 53 +++- Telegram/SourceFiles/facades.cpp | 2 +- Telegram/SourceFiles/historywidget.cpp | 57 +++-- Telegram/SourceFiles/historywidget.h | 1 + Telegram/SourceFiles/localstorage.cpp | 16 ++ Telegram/SourceFiles/localstorage.h | 3 + Telegram/SourceFiles/mainwidget.cpp | 53 ++-- Telegram/SourceFiles/mtproto/scheme.tl | 11 +- Telegram/SourceFiles/mtproto/scheme_auto.cpp | 79 ++++-- Telegram/SourceFiles/mtproto/scheme_auto.h | 256 +++++++++++++------ Telegram/SourceFiles/title.cpp | 5 +- 14 files changed, 493 insertions(+), 154 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 39229559a8..ee4bbf2b98 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -873,6 +873,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org "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"; diff --git a/Telegram/SourceFiles/boxes/sharebox.cpp b/Telegram/SourceFiles/boxes/sharebox.cpp index 911b997786..fdeba32c88 100644 --- a/Telegram/SourceFiles/boxes/sharebox.cpp +++ b/Telegram/SourceFiles/boxes/sharebox.cpp @@ -27,6 +27,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #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)) @@ -886,3 +890,106 @@ QVector ShareInner::selected() const { } } // 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 index fa4c1ab8a3..cff80d344a 100644 --- a/Telegram/SourceFiles/boxes/sharebox.h +++ b/Telegram/SourceFiles/boxes/sharebox.h @@ -37,6 +37,9 @@ 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 diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 27523d7077..848bd99553 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -46,8 +46,23 @@ void applyArchivedResult(const MTPDmessages_stickerSetInstallResultArchive &d) { 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()); + 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); } @@ -1059,10 +1074,15 @@ void StickersInner::rebuild() { if (_section == Section::Featured && Global::FeaturedStickerSetsUnreadCount()) { Global::SetFeaturedStickerSetsUnreadCount(0); + QVector readIds; + readIds.reserve(Global::StickerSets().size()); for (auto &set : Global::RefStickerSets()) { - set.flags &= ~MTPDstickerSet_ClientFlag::f_unread; + if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { + set.flags &= ~MTPDstickerSet_ClientFlag::f_unread; + readIds.push_back(MTP_long(set.id)); + } } - MTP::send(MTPmessages_ReadFeaturedStickers(), rpcDone(&StickersInner::readFeaturedDone), rpcFail(&StickersInner::readFeaturedFail)); + MTP::send(MTPmessages_ReadFeaturedStickers(MTP_vector(readIds)), rpcDone(&StickersInner::readFeaturedDone), rpcFail(&StickersInner::readFeaturedFail)); } } @@ -1299,9 +1319,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) { @@ -1452,10 +1487,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()); } diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 211267147d..c088ee535f 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -74,7 +74,7 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) { case ButtonType::Callback: case ButtonType::Game: { - if (MainWidget *m = main()) { + if (auto m = main()) { m->app_sendBotCallback(button, msg, row, col); } } break; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index dd67031522..a3a57fb2c8 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" @@ -3851,37 +3852,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); } } } @@ -5763,12 +5780,12 @@ 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 }; + using ButtonType = HistoryMessageReplyMarkup::Button::Type; + BotCallbackInfo info = { msg->fullId(), row, col, (button->type == ButtonType::Game) }; MTPmessages_GetBotCallbackAnswer::Flags flags = 0; QByteArray sendData; int32 sendGameId = 0; - using ButtonType = HistoryMessageReplyMarkup::Button::Type; - if (button->type == ButtonType::Game) { + if (info.game) { flags = MTPmessages_GetBotCallbackAnswer::Flag::f_game_id; auto strData = QString::fromUtf8(button->data); sendGameId = strData.midRef(0, strData.indexOf(',')).toInt(); @@ -5810,7 +5827,11 @@ 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); + } + UrlClickHandler(url).onClick(Qt::LeftButton); } } } diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index d5f6251741..e73e2b87de 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -960,6 +960,7 @@ private: struct BotCallbackInfo { 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); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 3cceb24e43..b662396c9a 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -4011,6 +4011,22 @@ namespace Local { _writeReportSpamStatuses(); } + 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; diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index 46a53a1775..5f53d4f4d4 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -183,4 +183,7 @@ namespace Local { void writeReportSpamStatuses(); + bool encrypt(const void *src, void *dst, uint32 len, const void *key128); + bool decrypt(const void *src, void *dst, uint32 len, const void *key128); + }; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 53fdc26b79..af1ca0e980 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -45,6 +45,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" @@ -3299,33 +3300,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 +3347,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"))); } } @@ -4742,22 +4747,24 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { 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 (int32 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; diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index 5d29d2a42f..a944a0d772 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -383,7 +383,7 @@ 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; @@ -560,7 +560,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; @@ -716,6 +716,7 @@ 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; ---functions--- @@ -836,7 +837,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; @@ -853,14 +854,14 @@ 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.readFeaturedStickers#5b118126 id:Vector = 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.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; updates.getState#edd4882a = updates.State; updates.getDifference#a041495 pts:int date:int qts:int = updates.Difference; diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.cpp b/Telegram/SourceFiles/mtproto/scheme_auto.cpp index 5043ff1f7c..a98f3e7996 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.cpp +++ b/Telegram/SourceFiles/mtproto/scheme_auto.cpp @@ -2894,6 +2894,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 { @@ -2901,7 +2903,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; } } @@ -4468,12 +4472,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; } } @@ -5929,6 +5934,20 @@ 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_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); @@ -6439,6 +6458,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 { @@ -6446,7 +6467,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; } } @@ -6547,7 +6570,16 @@ 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) { @@ -8113,6 +8145,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); @@ -8326,19 +8371,6 @@ void _serialize_messages_getRecentStickers(MTPStringLogger &to, int32 stage, int } } -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; - default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; - } -} - void _serialize_messages_getArchivedStickers(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); @@ -9039,6 +9071,7 @@ 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_req_pq, _serialize_req_pq); _serializers.insert(mtpc_req_DH_params, _serialize_req_DH_params); @@ -9197,6 +9230,7 @@ namespace { _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); @@ -9212,7 +9246,6 @@ 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_updates_getState, _serialize_updates_getState); _serializers.insert(mtpc_updates_getDifference, _serialize_updates_getDifference); diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.h b/Telegram/SourceFiles/mtproto/scheme_auto.h index ee3c653ae4..075b88e64f 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.h +++ b/Telegram/SourceFiles/mtproto/scheme_auto.h @@ -279,7 +279,7 @@ 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, @@ -517,6 +517,7 @@ enum { mtpc_messages_stickerSetInstallResultSuccess = 0x38641628, mtpc_messages_stickerSetInstallResultArchive = 0x35e410a8, mtpc_stickerSetCovered = 0x6410a5d2, + mtpc_stickerSetMultiCovered = 0x3407e51b, mtpc_invokeAfterMsg = 0xcb9f372d, mtpc_invokeAfterMsgs = 0x3dc4b4f0, mtpc_initConnection = 0x69796de9, @@ -629,7 +630,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, @@ -646,14 +647,14 @@ enum { mtpc_messages_saveDraft = 0xbc39e14b, mtpc_messages_getAllDrafts = 0x6a3f8d65, mtpc_messages_getFeaturedStickers = 0x2dacca4f, - mtpc_messages_readFeaturedStickers = 0xe21cbb, + mtpc_messages_readFeaturedStickers = 0x5b118126, mtpc_messages_getRecentStickers = 0x99197c2c, mtpc_messages_saveRecentSticker = 0x348e39bf, mtpc_messages_clearRecentStickers = 0xab02e5d2, - mtpc_messages_getUnusedStickers = 0x4309d65b, mtpc_messages_getArchivedStickers = 0x906e241f, mtpc_messages_setGameScore = 0xdfbc7c1f, mtpc_messages_setInlineGameScore = 0x54f882f1, + mtpc_messages_getMaskStickers = 0x65b8c79f, mtpc_updates_getState = 0xedd4882a, mtpc_updates_getDifference = 0xa041495, mtpc_updates_getChannelDifference = 0xbb32d7c0, @@ -1398,6 +1399,7 @@ class MTPDmessages_stickerSetInstallResultArchive; class MTPstickerSetCovered; class MTPDstickerSetCovered; +class MTPDstickerSetMultiCovered; // Boxed types definitions @@ -9779,32 +9781,51 @@ 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; @@ -12155,11 +12176,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; }; @@ -13464,8 +13495,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)); } @@ -13473,6 +13505,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() { } @@ -14862,6 +14895,17 @@ public: MTPDocument vcover; }; +class MTPDstickerSetMultiCovered : public mtpDataImpl { +public: + MTPDstickerSetMultiCovered() { + } + MTPDstickerSetMultiCovered(const MTPStickerSet &_set, const MTPVector &_covers) : vset(_set), vcovers(_covers) { + } + + MTPStickerSet vset; + MTPVector vcovers; +}; + // RPC methods class MTPreq_pq { // RPC method 'req_pq' @@ -20062,6 +20106,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() { @@ -20069,24 +20123,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() { @@ -20095,7 +20153,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)) { } }; @@ -20966,21 +21024,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; @@ -20993,6 +21057,8 @@ 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' @@ -21107,45 +21173,6 @@ public: } }; -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)) { - } -}; - class MTPmessages_getArchivedStickers { // RPC method 'messages.getArchivedStickers' public: MTPlong voffset_id; @@ -21315,6 +21342,45 @@ public: } }; +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 MTPupdates_getState { // RPC method 'updates.getState' public: MTPupdates_getState() { @@ -23860,8 +23926,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); @@ -24574,6 +24640,9 @@ 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)); + } }; } // namespace internal @@ -29489,7 +29558,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()); @@ -29788,6 +29857,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; @@ -30087,6 +30157,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: { @@ -30419,8 +30490,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(); @@ -36222,34 +36294,72 @@ 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 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/title.cpp b/Telegram/SourceFiles/title.cpp index 29a9e7ca62..f75eb8d58b 100644 --- a/Telegram/SourceFiles/title.cpp +++ b/Telegram/SourceFiles/title.cpp @@ -142,13 +142,12 @@ void TitleWidget::setHideLevel(float64 level) { } } } -#include "boxes/sharebox.h" // TODO + void TitleWidget::onContacts() { if (App::wnd() && App::wnd()->isHidden()) App::wnd()->showFromTray(); if (!App::self()) return; - Ui::showLayer(new ShareBox([](const QVector &result) { - })); + Ui::showLayer(new ContactsBox()); } void TitleWidget::onAbout() { From ff657347b8cc5979208d648e461edc5218885771 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 9 Sep 2016 18:52:46 +0300 Subject: [PATCH 06/10] Featured stickers section done in StickersPanel. EmojiPan moved to a separate module stickers/emoji_pan. FFmpeg linked by msvs linker flags in GYP to use ".a" extension. --- Telegram/Resources/art/sprite.png | Bin 180375 -> 181502 bytes Telegram/Resources/art/sprite_200x.png | Bin 244010 -> 246441 bytes Telegram/Resources/basic.style | 2 + Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/application.cpp | 12 +- Telegram/SourceFiles/boxes/stickersetbox.cpp | 122 +- Telegram/SourceFiles/dropdown.cpp | 3447 +--------------- Telegram/SourceFiles/dropdown.h | 599 +-- Telegram/SourceFiles/facades.h | 11 +- Telegram/SourceFiles/historywidget.cpp | 156 +- Telegram/SourceFiles/historywidget.h | 11 +- Telegram/SourceFiles/mainwidget.cpp | 1 + Telegram/SourceFiles/stickers/emoji_pan.cpp | 3704 ++++++++++++++++++ Telegram/SourceFiles/stickers/emoji_pan.h | 657 ++++ Telegram/SourceFiles/stickers/stickers.cpp | 143 + Telegram/SourceFiles/stickers/stickers.h | 29 + Telegram/SourceFiles/stickers/stickers.style | 39 + Telegram/gyp/Telegram.gyp | 5 + Telegram/gyp/telegram_win.gypi | 16 +- 19 files changed, 4703 insertions(+), 4252 deletions(-) create mode 100644 Telegram/SourceFiles/stickers/emoji_pan.cpp create mode 100644 Telegram/SourceFiles/stickers/emoji_pan.h create mode 100644 Telegram/SourceFiles/stickers/stickers.cpp create mode 100644 Telegram/SourceFiles/stickers/stickers.h create mode 100644 Telegram/SourceFiles/stickers/stickers.style diff --git a/Telegram/Resources/art/sprite.png b/Telegram/Resources/art/sprite.png index 932c1d9903fca15bd7022f818909ed8c20104a1b..bb18b32eaa1586bd26aa1c5584e9d31476880fce 100644 GIT binary patch delta 56550 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 bEPxHo3V99c0Zh0O41D(6JL4+$66601c3T_G delta 55440 zcma%ibyQSQ+wU2=ySqU`YUu6~q+3FfP8FmNt$;Mr4T6fagyaAsARr}O14u~?O6PZc z-*2tE?)~eo#af&*=gjQApWM&$`|aCU{LFCtWGPrcope7sp}2sEq_ME1w2-j0uy`5~ zhSdM_i7|#8hmeTKlP4k)Z~^6i&M@Eo`HU4P~wS0Oxn+;AJo33Kut1&V%O-<$BDj|vfp@?k*e^g5i-j6k-@zqe=x6oli#~nU4YEbWQ{c1UD$0gA1I0aRlQ{dsz{jYE zDc-9;(0Q=DNfn<4o!9kO_?j&GougJU!V-Yvpwm5Cb&{ydK$t%&-^3=DG~{m&h4R}a zzxohH0m%zJEFtytG608cAQEPWiNIwBSfYqIU5xNq=nsdYWD;hj|7jV9uWr+0zT6 zrmeg}RNpaB^3D^W!yaIIGlqEsRI5+YNurG2DQ0P&p(hg|VSo(m(+j7?zhXT&7~V2r zM}RthPE<1~hFuma`<}ZDv7JoV3E1=MC^|l+X)VG?;0d(AZs3*(iTTcl z)7`h&5#^K7H9K85r}V}X`o$$vq{K*jpc(f`i9yzO>$`It<}}n1wh#NM-XBKvjnYjMfYR8%%*5eZ@x2nA|KsPF$kt@fS(%fMK#BQ+<9Z5Sxyuu4Feu(?_p z4(|-G$6M7k(hlmP`Z520=A)XhQZ3b2^!M2MTTd@cmAG(`HUK*i%<~{UBuA(RCd)JY zC+KR7Fdh@tf|A0q!wtJbb9U~1Zi#F6iH~u*#ultBEL@={OXwDGhUdI$cAuNj6d~wk zfM-7t)XfrFmB@6$TmeHnrkp>4p1Ufe3@<*9m;_ikDuj$ZDol`6ycBV1c`|hNcBSX% zjE&d3ZKlxB)|&lB;p{iW2Bw?*j3l2sIDgpx&RA#`H>WGOx|m)emY&~?l@&w~BV>Uy zn4R1hPr9X#`v=fHl?4dc^pRsbuVXB(MY{y>?;BFfs4=5n|`mua9&)u3+F!8i)vkiOzZr#pg zDks7paILGX+&tf0yRx;FZsWu%{b1#}qV4u!{1)XEtoN%afNc~X2NNX-G~+dZO_W~E z7GvU$cxfA%-5@r8qm=y^P=M*>M(pNvsQ+1!<~gh#7ujGce`~sG|IF9!r8@8S#5;Ut zW#yuAXMHQH`1*Pg3To;gek)IJCmP3vnn`B5CtkU2e_rnp2Y5^$RF0s)>G-&QH_eyYQXT_`_cTFp#xpR>UytsJX9!+m&7-l9)G; zD%jamG`Z@9avG3_iSm=-z`wVKA+U}UMsIc0VuYD`iLMx1TSX8Y+-SA^^8 zx&QgqRpe6(Rdw|evl^T2l`aL%37I5yoisI_EdO~cNhd4Pv}Yb>XD2r`g84T~3QqUM zAw7O@0LO9igo!XpGV;u?tvPo1;5(je)6=Vo< zKq&Ye1p@wzh-JB;QwIWO`D?vZI_@ua)yg$C9f~9vdU=xJ6BCA^;_t_coJI>?zkaRw z_%YkFXU}A%y=*z~7nBUNiOfo+J~rkpqPZyk1mVW@05DAIc+x0;tob_U8-snsiYwhW z3cyL4#X|FlCv4JtoOPd7d^UN|G8{3@N12?Jg_EEj+2AxPp878!Q02)6kTo_oYHDhR z>1_oM4rSFSJYfE3J{@XQ)dNQ3oh(yuS>6s6{u)_(soTcBW4+lrnxN1h<&4_s>>KIpl|q~=sChIf6t zdt|ATkU{jPz?n*WBLKIqhoC?W+?8&5t1Me1RcnF2<&kFW1l*x_i6dQm6%ZQ6O9@m2 z-AfHl#Mq(srTc!OVY%r8MJ{wy@qGuOKCH}Y26|6#q;HCa*TU`-{h8c^SCwBs4geGQAee&2D`qkCD&fv&t*8(43L^Jn8#-;`CUkr=S61`t)MhaUsk6XIFAg`a5 zT_sG23H~+(TPctgv`vJA_C02YPrq2Mu1^&!OU{HYPYZd5$NO9`(r6*6uM;S4o2cm0 zh^5*0*VZ0Gl4cVVqvU>A;GPN>;NihJIyw@{e(?U2aY3oUPlB(9K<%wpQak)$p+-Zq z8S?qe@dE0ZMsbVRsPb~wQ0y=hiE7XVGJ*d-9cp0?ZUu?Gs*qfgi4E1ZUAH{_K3S4J zk9n0yDB9Pl?W3P}Fe8G!NWaMeon`?gxYylRWS$sZF(qO}k{5P9KEfrXrKGg9vB$?R zD5$7<97I^*a0*;pToxQOryqGfQdi}Rh=W198du5!(p15)dJ!i^sTQ?~qEEvXFOyg$ zYJ)^(n?VwT*J*>ht4n=ds=B&_qQ1X>Ml$plx3ow~ad1S%oH6XEql%lffFD8HG-qh$ zRh>WloUW~+uz!tG$tLOyV#Ai}LXNi$%F4@2o^Soo)YbJ^bP~_?xjc4B&&;%?lo+&X zxX$=VWob$7q9sy-9?>L2?_kOHd0N9K-VTOpJp?1lT@tc<=7(zWwu14)1f7Y&c5SL3 zb&SC|YwSz|W4-u;*txQprhN7F^~q^zlMe4*Leu*I^zwM<*vO*cFw0h{bFUccoy7X)-ozq0%6g} zW>5q^K%y=Z#__W3C0f^ai|A(w@Csl&X#Swx1%;}PXHh#fmTJZxpf_N0n?XT;?F z_JH?evK->hJe7n@Vx8%!DY3G$vQMzEy*+SGD?B{>RzUGVH_++h`|Zm1Ex&9JA##%D zuD37L2zE{1PF%`-&T#q^883H|w#!1W-St7R?u^U2)13R!;GxlO+Z6`cnDSd$OlB7 zx9Lx@za)tg<^Nq+HaL!Ch`CLJ$0-YkSe;nR<5a-%^<_evcI23XV)9wzFs*spjg zKYK2l`GX-vjq&BMX$$+bs;Z_eI2hHzS;%x-gp7Z}>{G}PdUxHAy<&U( zJ!Y%4BY>fFyk*Kx$RB*FSkd^?8BDlqOKb|z@zWvt>7C3S$DyTnF3C=q^jB9ZKDDGf z^Y>q~{xhm>P82OGqw?!?S7U{a@D=dxMlY@;V-mi!w=6GXqMzI49vdwbQ0B%WP-`RsMVmrK589~i;n?ONfffLuj9VeVK^{%&oKwz0>;WBqRTZ4Y=16vV_B8z>?_Gv%)Z2_6E!P%%0!6 zMX1%NLT@ygLn@u+wQAz}GmDHRGaH-R)$5DD@zJ{njZT}r9r%Z3_eP#+>xc!}@H@Ii z_gPDLF2Bw^3kU@NBZ$#aug`-H^W~E}i+ba$`)t}j9CT`$8L#nJu&Sjxae+l!@WwZ% zgvWEX);`zS>=&VfeW6*#7x$3f0SKsky2#A%d?{0nVLfn z9jX~yFl7C`^5lyaXbbz1Sm9boGq!zgZH>dKe$2Hol9Pj)jD!R=-RiqbQKd@YS3~dFV!u`WsO>cJEJQpPrcZ&H%CRwR zPXY|=FEk&vU2NvQls_I*0WJ8c8uAF7OhG}wBr#MGCyaVZxTEhVd?JQItwY1W7_Mee z43z}}sCXA^bwx#l&a<58kvg)g)BQHZ*!J4mS|~rO>lhgCjYzD>8tq8oVF;$= zs5X*V0AX6^3sa6k60w61afiik?AT|14Qh4X-Cp_7C2*y!w=AitsmVEw7_1s&w4pKW{DvDn_EK*rrbM=R8ojCtWv#TE$Y*2-H!`8`0fYe z^p+coE_Io|D@~H8E1Gg0eDmfFd(hc|hdNzJuhlmOHK6GBa<`^~oLf&;-GJg`&Z+QZ z(itw@QjnL7roZ-D=yn4OAN=6G9)cnC(0;)(mwxtj=*^`r#pk5dVwG1m>#9Fgqx+5K zJ=2wY=r_Jc*Nww3aU6NQlKbp|D-Q4u!&}ezca+Bg!j65b_SbXe*-$;_Evnnebl+vr_{E=b;poJb-dyjwCu3Cd*~%s#L>?iNQ9G-1#Tw>zl7s^nJqro zXo??rwVTZ(2`oZiRyfYI4W(5~JI)FebSLX`Vt?{ChFtk2u1i+@7*3ME97tlpu`o*e$^TS5mzV@& z_2Xaqe<5S=p~B6JNJWZ}sF8x?KHvDQhs#gBwjhJw0uX!Al{J`!KQytBcXbZU4u)5qMM-+CF<5o@E2+ zY_Y}XmDfK>K>?6sa&i(|1!a=VfveSIi3a#05#b@tOMVOX!1+d|?PU^;@MD^sE5-B( zm^b*NAVye&vDb=Xq5k0yM)V}Ccu{Zbk16XFzO~{xy4*z z_^P!20M5Oh z?uH3{%X(1m!RF#W)Ga`?HvtQoO4zlnU)mIyV*-(~=QvUc*uCVU{wO4C*X^a7w3-2p zjNe}OdVdmg8Bg4%WFm`JYLQEhJfzHSpY?By95@jREk5fA``#Er%7LDxVqNyUs{pB< z7Iek)DX2&TB7omCL;+J&c{TM#Mbi=OuK0MnH2SKQ(msJ`H11wbP%$Oh?{k-)6|@PS z?9K?9{JGojQLz`JA=qVLeBar5>zna(L=Wq15pwSKs$XCE`1q)MwdNfwrz~4sCd=P3 zrS>HtL+H2>8V|qd(Ct(44|x{xxL(SgEwWdtEk7TXyQNUmb+Yh_Y)-T=GP`M=bM-xM z7c%8K`&l4IR7ic##Xpk2483hX?0z#iVpYEsMcH2SU_G3G2oQzRMmJ<+3=c>!JilyY zptrp|l>apDteK#$^3UmCd3BsjqXVejnl2(HS$~v%ciyi-S#$QhHj_jlrcOG|lJM1J zE069Ec%nSsSrc+Jg-MA$XHTA>P6!QX$hVRfU9qb2n)g{32#br0?RR!|!1$o!ZJ&a2 z7nc@?xUR0do#W%<4V4&z>|vip5mcf#FB{uh1r)dDbYtc1d!qQ``$VGcX)I(Udxa>u z(snwX3Ify(p4M(}Z~ujE)n;RXe_Mj$;?<2EWJ>EzHZoe@D2wu0SZlzhRK@TqNcn!lEUJnd$KL6if^hh!?dgHy<&b+vsBh6$$0`#I$yU~IUZ*#kA`MJ2T)^>#rW}i99 zc6Qc@-cIvjH2V>L{mI07e$-^Xn1@V?7kl7Vu-VZ>A9%bioK(JpeJ>90?XKeFF+7#S zj1(g!bfcMU=l1qC_Z0zc{oHc+KkYT1oSdN3##p%4nQKSS?Q+K0%YXRrVFy}m%Tjfo zzqdDP8iXnj7l!^SZk!UsWV4?7y|asp3!|Ri-lPC+slouB_bA5Inj^xhMZ zOgzhV&iKA-EfW)quC1C7gQLGClRMw6qa9dtz<0M8MSqVCV2IzLvH#dfhgF}ca zJ{qch3?KdT<=m3$uf0}PrubT^>KRYL zd%y;T8Yj-e2!|FEHxAp+@ougMS>9MS!-@>_^g%F$n+-`Lo}RMPVe4+SA#0#(ydqs? z(WkL=gH9SIL3BXc2%y|kUMUIjC-RyCzp}` zw&zV~4Cu@+L}+zqOup#S{ma64{mkGwOi^1~`=MY8CPKq2R50(kXb((8QfFr8X z$mOxY1cD`*ZA`F-_{o}zaFZJlqtTYRRQ7&4f=)F4wIW&-jm%V;-&yQcYj{i2-SXHN zDr?Z;l3UH-mY}!27@XX>og}sW({|9hi$P(^)xKNZrj4}3U0}9C{j^eTVyxFMwo^R& z`#aE>`Pz zRh3n9sJ!EFmZh)r_N*PvrW`Q((P8y6?7nYx5MTNX{xdunKN?D0RzkOXNENo4r@6|2 zzfbzjxAT_dbuTE_TU?tM{&qe2_D8v)zO6FoJF% z##H~HvMSd(PnQK!*C;Q^S{_Z2C5*F2swX98HJ3vPP>)gZ>7XPeh@Vx&Git?N&A1=l z@=gcn@lBseYP(P-tS2A{RNFz+xx3x%!GOYPYdENZ%Q5Z{@&IZtvPHilq)=O`nsP0T z?(Xq)O|B$__CJ1Zzl=qYyt-PRGVgVs23bB|G9{lgQwvjFt;1gZMzm2@ zU9q*-G>f#q-hMxg5y3!mqOuD#E&vydNaErUTtBu8)gBW3&d*X>Lj3yEc53ei1~ci2Oju_0^9a!%XVHIN9X%K~fyEag269|R zxe0gXGBx|+tKQ*snJQG=JvCeX6t2`ogT}wW6;+Ix3?S26R=>uQuflaD%CU-F0&QGe zb}|MA21EgUTidQjrQ_zXqW1RSeANYDLEJL+qn74?GG|lLRO+}h1M9y{ng9-YSTn*&(BHE5{Bv0ot1BfYQit!8(S#Pq zo3GQY-E^snE|HY)2nkYE;QsZtJl~+qxh)Ag->V;CMxd$4`BBi4l2hOid1%wz1Cn!c zq#ssJ%?qr59Ct2v0pU3=;tLaEbR&*wgP%8kelYs|{_!h*=NvuTFV8q7pnl0K_RBa* zK1q$T?(gr54SV!IuGaAn85pnYAm+Z;i+QCuJh_GaNX*{-Xvh1dd(jF#n7&NN0vkzk zo)_92o@6c(VXLr5sE^Ei_%qx#{p_tGU7Q7$vrR_5nqnHp{V7nmNDCPcWq$d4OovHt zdITP!g+%ZJ$eDIaYggcUP(10`E(ZY{x&YOXQheXDF;|bA>k-#R%VEdo34M=0NaT)O zr0zTZdtw1OEUn?luW#dt=5CKh$%XG$53n2pZ_&&6T zxlhZ#PUF!);0~kk&5I628YN`@I)}ARi7rmaH3qG^(xys}FrKxPutD$kBAr?B^l$_k zYSKfPhGr+;4MXZW)hsC)5HPPE!Brs!Dka=UlCxPC(U4rgQZ)7&LD&c~-&EI;4c+ju z)X7a+KO|S&6#idu!h04Ea>YmX=UU|mm14QrF2SNQ=DH-)N)I*-4*VE<<1Ul_F!SYR zne$=n8FWoDtWdgOJd(N}nB1&5$~4?x#T$EQ>K@T>|M()eLwy_5_^oEN=Bd!juw{)w zG)Y^Vq@_OfuTdCi&VQ-(s`)qJz#*(wAUYd{v;g{$j81%0FGoZtQ0e^cTFM`l23*LR zqCnOrCyS<7Q&z&qICyxj%KTLEm=N3nHNHpSB$WGfTT!ErQ3sKsNZn~%Bp+W44jl&K zO8H|q_1Jf8Te?%4FzXgGHq#6tG^=$pt4|DTkgxYuxeOy6m z)0&Z=Q3}PVm96TZjPCIr)A#1VgE+!Y73uaFMsZ*eM~ZV{W5!I zkBW~iB+>BFb-SL|S0GyuoLY|!4P^eTZc%p|EFg_}aS#h*bp$?=(INLAC^_(TaWZB(=un^)82hjis<|^*=^h}P)yX(%7WpJTs zvW!6|v8-@q{66nDDqXOGpt7f3|t*Wzj3HkllX>+=`ph)SG$l^8oSJmA8AjFjfSpC+$O?|d$w0wVmnFT+- zZ(49Qmo+|yQdgm;b($OEPP&f5&U$)_QiCZ!k=AI%JZt`HP0)uQ*&(zGTd)wDZ#8?4g^xZ&P3G6bFbY4 zb|P?p;{r*Mf=Cez-O3*!fYyfmJwv05dc2UUQx6WpxBUL~Bwac;)YYjm`|BDJelFiZR~kW+rTHcxmACmb`dTluO=rA_)U}^S%XO#!#7Q^|${I zsV2x^sO%y%w$}VuG;~C;FM<0bO7W27!EegMY5usJY%6?r|L5t%E;NQ@h3N9B9^&Wc zzw?o$W%A_x(x{7Ftw9yx(E=n?Cc{%zRysreAv4E&b4Hx_^iY^dAQ^nz7e|>V%H1OR z+*nyTV*N~cime^Z>xvJ_WJ8$mA|6jso>dE&=HhM;!-JsHMcpX^F=;c*mcLBt|O zwWQ)wri!sP`poW6x#8la%B~9oK`$~X0!tP9B3%j(`0jCgm#j#jhE(x1Tl*f7Pl;Tw z#fLqBp_kySm|9*E8o*`i6hj?TZZl~e-a{Fy6318^EAsB;q;q*Y7 zwXUL3Tg;3*Cq|_{nM_^J>efF49Vb!EP$=5QKxy`o7IInlUCT+oY;d6o(cwD-mqgq8 zO7E7%H+s?&GX=O`g6i>t4eRTWZn!nn@^_?7oPc?iYKwr&XX^TGDDCzp89peK4-x7W z!p`4FA=8Sq&x%YmjGVQ44|0bTFR)vvMq&J}SQObSex0n?A%os2;UGyb`PSWyFQCdk zku1c)(5NDve~%=F!9|h-|AW@Y0qXl0LolQ{2bNJS8%Y}<|7`0QibAH!uTNY8AMC7$ z7){0C`xe3Hn|RZIr3>APyI6}7(gqt_2>`;6i%e&y-BB`u$M20MG(m0co{PU*GVP8> ze-oeR-ZC#%-PtX8uub329k(tC*RLRVCM)zb{m4pOph)s^^KwFk*Oei*o5GMC z&F6E>*Ea%BX&QgaHf?|=0sE3BCtU7=a5nMTtziSxfn3}}3~v&|%ZRxh$JM{@Yxh~g zWUQr<`yO{^i#k=~iKe%;JaJH{8Ae-Rzny))hJ7y}>hcPccbqKn9mm%g=rYd1p0BdX zX}gnulySoQFKvUbw+!JIO5An$xvZ)@ros9)-B;r zi8-AsMoyi-yzy*rPmX0WR3N;Q5#p4tVFfL++bFFlP1?N|eCZ z3Z-at>CesmSol9Z+_tU{;HBVvOJES%pJCK6g{WNCRql^cTHT{B3-HYaXA>zK=m zG8JoDX>;)D9|#?3|HVJ_Oj~~cLOJjI55Z5aLQ`{-5b0u@#DSHQ&_Y+`qx~LMVb|-^ z4cp;oipnGj9+`BttTj~YOA{iJ*UyTnKqYAf+q?^th*4X?H3{HM`&I$C{(Xp!e6ATg zZ_gN}Bwim%4|g70%rFeM!jlU;b|z(1*!?41lZFY5!F6p2OB3XD+4;^UY=JQuVb3US zDgit+r$I=6d#S~2^0^7Vly2$E$BQ^` z`Gc9zAF<#E<$P0I$R2;Vzna;=Fvj`%+hzoo``}ACuQkChzB~=dV`TC!oaWpO`-Moe z=jg$%$#>x|p9x~3z?L-(>ccJYC17K+lH!=fA$xseT#vf%^4?+fufO#eS$b-;fJ-nD z(kEhFvaG6w<=_oqJ3qRmM5d_s7E|Ke)jCM)Q%z$ebkJ|TCr0ubA0?%u%y|CnOF}$b z;zPH3k}bb*oIR6?F9b-+dpmC~Rycn+pU4YCj6{_tjRRd4tl+{GwjNtB!Da0%l^yMs zL$QNgOwEkmzuMN{gUx=o*%`Oq|0XtQe&lO~9JV-5bX8XgLGXC~_`cH6HdRTtAocIM z#v-C8Yi~`m<$S(S1?h~uCOV02U1~DN?{Cz)#g?ZW;6a(38Ak1IA-FlNCr>q|L>V4T zC*HB}se-ty42`T9g_9t^X|=VKWyPeN3dvXJxRPUtLS}Dq3Q`N&Si@f)SVsyv)0I3# z2aHEb)>VFV-}nZx5CBCuh$Jplp~~Ah-1W>wOB#L*vfxtB=EX*@2<}c7Dq0-?gs?Fhvmxg(|{0^h_EdzXmT&-xcH zXa4rn@ge;Is^nI{{D~DmkDL+!qY<`M3dF%&R+N8|*$L0dEIvJI^TXAh6s3(B6s1dO zDA&txB?RQpk?0}N9Oa8#0v#dnZk|~F+^z_cQ>Px)Ke(aWNr47pj0r%c=eP5ViyQi| zLTAS^9$qTMPl%Urw6U@A5aNOCl*w$Vl3;+{J`5W0W?M25(4$M{GFfEh9dt1i!jpZN z0B+z!!cc@^6$`!SN@QUfT&Tf1?TRR$viC&TLnE(=@&7coE&-^Nw3`*=cQkqO^Vd8n zG+dD{!!#maCawVTjIDT2%T{%A`X1zzJ6Tqls@T}rkSmjPjzaW+1f|NU9}r9r4l%s% zCe#~*_$?ljJ1?H>c{Hf``1qu1Qmu(snaa#U;5`Y}b#ho(So3t1mBiDvudye7i30g4 z&r-XrRaDR+RDUie28$|qS<;l|WnU6z@Fnxn6AF34)Weh1hxw>x(?55X#g7t%Y<_Az zg*ZkH&2R$1eAWghawE*Mo6a2^sO(=}!y<@3ZN3w6G%HPfr|6R>$VGoWjGO)1p*!Nt zb`Kksg}5$68^E%i%*E*X-bNI>%1QZMvzL1SN)-3&EURK#TU&)8evBB@4KB;=wu8{* z^|ikE8cr^*9}xKK#cS`M$q%sb^Y6j#Sqy}iyr0eW>I^84JewvC2AAl-Rj;C20rPV( zxmG~&oQdV4>wJ7}U<`dsTb|CIfLfu64vn?+o#JL&f0PeyaLeY(eDtFP49$qMr`P_u zvi9b*SlI!-uZd3o_8GHy;SLMFYC~i`r9AqeNM+^IH}fJ1b@K!m#aSqTF(Zn4q+Ad) z10`i;9ET9^VgBPd=&Y`xs%p1C->7rg{`ZGPNqL>${(PZJPQiy|<-^qIFazyG6@#BV zK~b#3$PwV<+kx-H!xUY5fDm;s7q*!x=L{bCw=+=@5fRFPUliXL1>f#Co1EeAM+=z~ zY2r;Gw-%+ZUsTh*^d)wgc3kMA%M&sm7fn4Dd!CM&NRFxZ8N9e-S}nKboAXPk^(fR` zSdf#d(**HyxxPN0FCQBC2*3K;m-;+WXLZ29lbhf4oaV%L%`1Bv}fu89#$mlql(E6H<|MwZ7SxF;}m8k zlKLi(D$@mXP;m${F&3PXv`lci3)cS2afJnSnuCID<&OXwgbg+Hufo`*C5izF7h_izK?nWl>)r7XNs$Hnd`A^%PCVRch)(2ET z?b_xKv#T-_4*W3NfI~5U+s@!H2G%mM5c8lHffziw~3?MSe{cs^E0<+!pP zkx+%^ySud|G4BlbJSlMu|EI05KGvdLQBYfu1wt76ecsw!Ytg2L-(Ui)`kUKS3akJX z&W$akp1d(Kk^GH>K&ERvrNYs-z0VZW6{2-c5KHue>pPu|G~*Vp_O#jM;;+j9F2qLr|B%fN&NQdfg9mfXDH4dWzxe5d^f2z)^Gj&Qot*E*FwK2ku5Dvb-&wd9SOT!yre z(%#-~LH2wK+q7~@|Iwok3IGE8ExY+oR!yp(zDbE1Q0Bn;KfOfCFK^$zjqmO4?GJm& zyTBgbqnj%(KzVl#NYq?Jsp^mKOipR>`d$^~(#Zh3uD?+Q`N%<1EJA36X0ayUqS zLoegrI&_fs-8kp_H4_l9ziY6j7#WEg-AZR|q1La(yYBg(3tPlsF_{geLpTDtMJy|^ z+iA0K)}|B+<9cUCsQ+ev^hhGcK0_}D?lPVu<|d!aj!*miZMw0ek{Y`U&)nw3HX}oO zBUoGOZCRn0@0iLFz}W9&&p(s_%$n-!*TOkD-<2hVdSXzx=RWr`3?W)}rrX-&aA*Af@KwLs9l{?hLhf zYHvAiT~_^$h^ZFC0%I1xC(PFND=F+V$YTL0+Sn{F-hb3;odpGrN@dA?2K(Xyev8>j z#h)|ppOXF;29x)*B*vk_La%nH^%QGa8@{DcEWe})cvFKc7Y)4p(N+%QA$ef&7twDP z@1hh6(-#WadSsM8c+aC&+{eL|(*YLF&qAUcXqAkKs3}cOR!fTxjQHXj3;wijgy?f- zCiVKPZ)TG4Rx%N{^8y;P_bxhzqC@Vx(6D)87yjkZ>v-w8ij#Lv&5*3t@RMbz`cXqF z3(kX;0JF58ER!#nH{fEgHR<+82sZ4Md%DS#CGAvjs+6hN+-GbSU#^5`L|Gdour@*i zmYY_F^tm?D{odVm`rzPW0zP2~Q$?AcjxMGPPmF}zccTHkn3>-#OEl|a%f$Z3v;S7D zHDVaa-{v>+owkijP8?d+!+&?KC~=K(P&f#B9-rS00j z2W(($Y|OiY!D2CBE5E^AjKp?8ud+nlJVdtcqmUCdU7Ova{DF90J)!d z-`;K};C~0ag$5KW$I7mAn~;N6EE))nrE3^DJS*lx-xbv>?DAmY#f57tG4@k9;b$(0 zmqfF};aCDipxyatmiSZSWKA`44j2tz@LhYAUe6L{z%J~8*YG zLgMTZuPMj+5(iH(<&1~W%opfD##8!+4Kj>k>R^iJnGi!ctmHj0pg6^EjN@-iQzO_T zYFj^K-?+>|5N7LfogT=Xzs3ZB{$pKG6Z+{)wMzz_>g;CYPZl#)m(O?>00JUF{Q<%_ z2Gx~Ehlc3WGBYK(czHkO!j-i_PJBva!&IF8Puqx5&;9$BoS&wdzFHeGSDqPD83EVZ zv0=T`%eFGLyX{;blpObRj`Qsz0vUkjy?L?1(OIz%{~htd;{K=UT<`x>gG0&Rx~GGM zXksq1%Xeg;xFG5@QKl8Y7d`p0CK=Cuf)*x!wZGOROFEA)A`im!G3~MgZF3QB7P^^_ z+8lqpd6bq@_8*~tf|hbY06>HOosrRJNgIn=0dAq(~ zh4Y{K>wrZq-d_LQ2k>WBe3M@u4Qz%4Ej_G!N8gdWg%wXNxQs2|ZNJVzi&)ZkKFyH} z62C)`{@88ume-dz8w?1H`=_tT-4o_9c*v_FUk2gaks93s*$XG%H}1Bm_P1N2LBr$3ME7K zuE}0CSN|kj(|M__XYGZe(oO49nq=5JNr+!)RegusINhUIIx_|@^J$Eloz$jt@aeQk&VA_)HGg$4h*+R50~FTp(m?3LEz>kL_TAe)u$%Y9aoi9dL|t=ra4^3pq1vZF8bZuno-jD z(VSOR<1_b!sW_4NIdcOulPBian|Xd6G@8;%&nIOYnGp0yd?tZoeRh9#cJ?P7`Xy79 zB32VCwK&0&J6937@7NzF22C5#Bg zA_x4#?J~f*5r+`3TS4IlM$n&8&u`@j6=V;}Td$L-WS<@Jwk--q+Ukg5BlHymNAgPa zwy%`b)xTY4q|Qx%M8Ce~|qhZI7qT1#!7k9QL+WAC|8 z5fTA0#LS7WU*kOdg@Hz6WNIqDi8s|uBoC{l3%>dNW&gZW9Pp<`Sa}N?l?`cfH2;%m zvf2~ZWJtQp4W8eJ$06hLNTqY=`Odj8{V@Ne;mxc7GKA{cd2jgl>u85de}eY)B?vd! zXMWiE2lR1QH20^|b-NtLAl=5_QY9_RC`tZEIQEX~Y)>Y%`xW2wPh6+xG4B+do<6;? zJAJo%29fN#C6Chuj1=!t)8}24Bkhjh@F&w>`|=P3T_%kGz~hj;!}pxzK=9 zQ;Zsz_ed=&$6P}Mx}ip!z1jGic867L&96m5$YK|Iu}esZ5eCY^mzfzp^Y?=nxYL;N z#r0^fh>o&f%d!%t@RvOs5O2wtL+9!*Zs z883$eZb_I;ML{RD|1PN$`~zM@4`BlyA{k%&mY74$f67+FNZ3gt*y~@D&btD7Dn<_P zikswj_WDxY5K6s1V|g9sB0c&u$*M*Uln58VnT?I@AJtji;uR62vPHJrbD4Vp>gqrih1CpI$Yk926UH9rInPn%I|2n zOu8f+2i@sXsHvV~yB_)6Dl?E)N9a@)$@=wlH`=4xq{Re230UST1r6|FQ*a zb6dqc)_sxGO!Dj`vmB_ChLCVXEoAdjEvYIoXtmEFUp0c9_G0exLxX%F6c3G2bDZ=> zk)H~s-E8pl@VJwW<-%M5rkFUtVgvqj=MC`JY)ec3XS#Zo80AS0QmXBmeAGn$io)ML zu#<6(kM5OfrM@cE&Q&sSnA(Zw#P7OZsm>nsA=P{nYwijcjT(1nOuZIvr{dx5^6ZW&TZ(D@LR|P8PGdxM>K((sE41Zb#?mz2FzNQ3u)t#l>G*oOM^1-_h05BBj}fFPi%kf_D-3OB07yFE=qokt6# zpK;abu(hHvu(rQ-oge*v0jikH#f;j7vaOhn`kMRee$pg?@HS;C!0zcR(o5n1YS^;@+`vlc@RLQZ)?Cx_Me zC|0!;q21B+JU6`aKehLelSwbghS_0ABOqQ7lim5gFY>v}TRikfV1Xjl-2T2A2^D%3 zl)pao{$$xLAO2iHnSgQlnc2|^Y)5^;MC>A;+sk2Ja5xMBFR+={Z{)%%x@*`dvFCmu z+3O6uD`X|>{Eya#kokY~jABVJ#vP6(-_LmwoJ?LU>87@Xuf292GtQfG9odkq&U!K@ zP#$wh3ErMf@tb6VEfG%37hGaOEdKnuWcFt3hZR~P(ro;{yQ$?ZLs6rQ*tfGcz1W0R z0oSjXLuZTEtrFp1eMYBg3v4YfS_U7p8m+P4y_vwi^K{H&Y3SqaWHH{VOiAPY^giF50vs<}+ z9FQo_?~F$nNTSQ0XrI+dP&6_sD5p)LTDMq66WmH=_Vai(;?%dxuAre$0W;zp`y8j< z0L_O48N%4{^`)}1Ar)Zxj@x@F@=lWw2gibjfyZ4GwO%%tALpd9rln=ZfXrbxiA;TxVLADYO7hZSS zE7FZ>?Tm&&UrM-^RvyNm^W1O|v1mpdciHQo5yOZ$w9cO^KC(`~eF-Y;4-CB(Q}Mv< zrFG%q#7_~D%nLcw|0n5sGx}b=;39Wn@B}*?gdabE5N!tF>$h(rE)N1<+r~}?PMBS$h3KpH4dO78p zyUz1nM_dyx*a&6Ql1ZdO#wVuMH;41w>20o<78`a9BXA!i3Q2~;e}vl>3eyJT4&vFf zWJaokJK`w8_hy)jy066T}hG%WS4Vr&2FBbYyD2b^H8~84AS2+VOnZ zNp{6MDULHyNc3~L4q5v4!@|owtC{)v7qPq_tLsv#@xI8yJiah7Ocv^V#T(}L3!or3twUB|iJgk*9Zwh{{9<)*6T z)|~>x#`*#m(l`FxW!fCD7T2Jx2GcKMotUfC3&78{Rm&~-4&S$~N;1~!@Tt}14m`uTNfym-=(8nFi4oWlI z-miRPe=)3T50<0|X`-QoZtlQ2=o|sLcdechM7hv6F?nlgVlwq!yqUtq!O<}hhH6LI z3e&DVv5|j6bgjL8d;gdgSS~oM_^hIDKBi+>pUen%kv@tsK&Srvr!9-!nfeLf9)>;s zbVeTajJLQrqfH^d?DMqZ(%rY=9Oe))f{3$rl;3u|sBP8;l(bB=^}!;MG*wM~=xFEP zl`GGd8fPj)59r9ZGPpn{OQze)SCf6BH)ulg6||KEqx?5BFvLZc}eB zVfUz2(0%n9-K-{{vq2?V-;MI117y#50saC7DNm^Rm`7Ic|7VbU^clk|=x7DpWRW{L z=F#7DdYqKxUInOY3f|^tXG>9=?d?`0h=Y%(2~dgu!`4@ZWz}?TU!-&?jWh_7(v5Tq zD&5j84I;7W?v(CMY3Y#e?gqJ~Lt5Y)-{}fK0Yk53IrRob{ByBf zH0x>Bm*L{C%g7(&U%;64YJE9LQl?}I-$lUtEN1<6B)nN798)jphj03oR@8R_qj0z~ zfYtIv#n&s~A;!YIA4U+!`Wc5_@0W+I4m^7j=3?NpubK*i{fr_`s5t>KlGo|`iGM7*P_{Sc(A3nF zwE*)>vZq>sV2leL(h*Y@I{T@`WOM~ddQ;{JafdN%ZlIE!C>#JjnB$2`0_ky6|Ef=Kgi_c6MW(sbl;#=ODo;cJg0@$b!jKwZ}Fi*$LKt81mma zX%J*_%oaxO&DnI@usqF>qw^G2=`gBd139PuTdFTx*c0n&-5F3H&b8#4@PO$L4-A}2 zw;_9x3XM1PuRt&nl0o7*`F6sw$DL%&XZcje;kZ|mFf%!n98}ANm|k~Xxt8NMC~-~t zYd<9ozpT@)aMkf5UmqgJ*$CWu>B+U%kE@$0Te4adqhjeH2-({=v|{);uB@qMn6aC$W} z#i$j6&<;{dTu=p#?oBg_r&WYAQpcXq7Xt}upS{Caq<#l-!q0B-2tE`@Q(w_Nzq&=& zw9Ki`wU4pef167&{I7xd^3%)&4z`Sy(ZIVce<+Kv{1-bp`omyu zAQz@z#36asO{bS~8{QNglvx}q0^6tp9IOs5u+TQuICK+NJL5tBZx5+{FvxCA#`?uSP|Pbs~A(N%ITl zCN$Jv=s|mfu_6e<$IDIuFG#_}u#C|YYdsj=HgrVmQj2%4Lc0rPQ4^lgoe&+x~x z6f{+n70_BT5ZQ@3=VO#JE#OWV$Whu!a#U0`H1eA7!mtkp@98TlD#~A4n0zacrcoKP z#&0q)`KF9fkoW+01+B}zP0~~Lk_0?Z{u^m=5|&&olreiIBJ1Zzzgr?(&bnWTZv8Fz z*y43HmB7=@KyH zpo}*AOZMLUrH|$pJMqey58$8F8(NXuo#!ouj2^Kc!Nt;6^zHZh%KQXoSf@7hArVdF zWeex%7SHLAQkcm4tjfm0fAF5Nzh%08bWprkyA2tNS#a_2rha;=Yiepz;Nr&iJFLLP z(dLz_TT_YQxc~U^qmQuF&TRX|=L;g{F4K5V@t@LUz+ zNefe%&no*3Zn*^5DQ`Z9QOXuum|nbCH3lbBH(feW4Rr6@E(1XS@U{Y_*n5pKf`AJ1 z6{ zS}N_`t4_H7)Cu0Lk=Dm42UXD_s`FZPy9}jN)0y|5evCr}DQ6SGnbILbMeB zYn?775tqyg@61e%$<=s=={< z;DCUQlA=tGl+j;bb*8{Q_cIgZKYi=XH-pRGAH@~nz47i0eBzxc;4N2X@Ccoz3E3)J zHYXd8bGdgoJQ&AzJqaUbKU#6Ux&C`QD(c!e|Gh*-wg?hrMQ>uRNaKRi5Zp+AqDB5I#I1>8 zRDq|%>RRyy=BK`(;7qx%c2#BJxOP7ix873tvSC|e*TY0`6A=sxk7Jl=->VW0Sk-^F zK4JUs7c@>-UQt#?Y-9Q@GE9JdBy4G`)EDx;ygoX*Wo}o0M%o$bBkOLM1PxOi`O^M8tpI6B84UFJ(s4K*)PSt~H2%(LPUVOyGNR z&9H|O?>|S?`sr`bv}Yr|xZ^iFHy8CCvm#NqaUZ}?_`zAa-}5X`MS#j;lgz;OZuxMt zH(Em}Pp$Tz%cuz>6^0I((3k4Lr)SvYiVSTQ zl~^Q7`-XoA>w=BM;w{u^1ZijxK((U@aCJjDTEp`qY1lK4=HfF|?m`vYh8%dNBcB;e z)iuW5+qUEs7Z+Rqe{X^L`G4foQK;ulE*uI}Glvfi$pdjL8lcm|^-h6V3PUMNB=W!i zECPaq{S(~3JUl#vmZh*~dR=$4Aw2k>*p@kUe130rl5jJyCg%INNh^k;{_q00KsE!XSe)(LQ>IP+|3e-mCG`rx9l^Ykw04fKimcWPzj9AKM|8e;i zZW@|x$MtdHGA8e>H<_n}l~qKrHG@-VdkA&&HcH=_e`+9w_fJH(M3SEYQ;lMuXFl%8 zdpq&lA(!0dW)i>>jwD@Mx|5fGFFT+>E4QZjL6ywd)RCXyU??nn&D-`b6sB{YQ%{Ex zwQc|31eL1#`H(&sNI+gdiqFGi?9AM(Ze_8E2hYiQ%7gj&SS>-H^>2lXT6!T_=l8{VfuLlakynlA{UJV&`b-c=So%dsoG6IO|ymBzLMS`_pW52@#`?1r91nsQYnXnk=gOQPb%KYvzrtTK# zm)dWU3=GakM80|U<$xDIk*EgrQ=@k)93F_G%Ie-!O}w43-EhRk+&5wC_4nCr$Ua=60iq0A>+bmeI}|7=_@z z8`xEu$FD7?$p;2!mN5SydY0e3|2`6ywYFmYvCLg-a{t+Kk0#IFcKDgrT1`U z50L&oYF)LoHJase8nYe?G|s9&9h) zr50Wu3gmO6_|NCo)5K)9kCR?@CQ5S34QvX+rtw1_2sCner4r!3P;?gkMoIQhd4v*>!cUnmS~BZRW`@QVRulwmL&5vRUDxUVnDi8vX{YxB9M8qsti9@}vgH z5~RmpT`zwtenkKD6Nq`Ny9Ptd`6S3N8@SVG_0~oQ*k}AY621>vx>RAbU3HhuCKrPG zd&uiez3>haS+BCti|WqHGAWL(owvC%c$L3TvbY$jsi`qAT9SuI#nFCL zQNbmg=ejoj_z@52z1RaXie}*KBG~g6SM0Kw<<{yZ-Joo-!p!-i1R+avhyqqZ*8ecG zhTPzO;u)B|+?ZAqS3qt%*!+O^NBizcFOIP}9p__frEDE;b;paV8rYN0z z6rRgYCa6XT1v__+ng`u)xVr1xFt5D0X8%cr8p_F`0+bJ6R?i2_Y;C_xt1>n={-CLe z;@>sV!~(Ii;{Y_hy~(2hqpm-FJ{29h$5Jqx@l|-ZfHeb0Wkr-74`*MLB9V63OvC<} zUme0G?U_~Z?-wISKD*r1*+DHTWj{`K>voFGrwjm>lale zW!}OJnovyAg0+hqCNWXZ zt5h9=)MxAZmMvr5zlE@e#I2<;b3de=y;x&@V36i-+Yexd%^!LhB}_j=fqP5=4uHsk z!t?Zpw?>i})oaywe&qDpR9F=Au3axa7oAzMdfh;Q+81sBUUk{#d+2C`Al%!cX>P62 zqrIY$eg<105?7~f`drMH=n7SGvJzmOtw$D;2>ovPCv;#e941t^opqI0698PpQUJdiMZ|;AM#fc>XDx4&BZYOIqKyoGHdo2qh3>^OR5Yl)Z7<5|bZ2N1NnF-Lj zQp?KVf3GwWyqZM(y`Hoe<5g0(@eAv#SDBR+X0K^b>6}Pq(A*#C=^CUV8OS>hvA^bB zZo#403?5=d&a7NdT7V%82UWyxAWdnW;}lm7*FH0^q^G-Y(i^#x`G+f;!xQpNc~>$F z13qhGf}aVS)Kyv@2gdIF-$XV@U9(|`@TchKU_-XL<2f`m1cWsI`zxKq&0>(5m>AgZ zbuR$rqzX2fzT7;5YtG()+HJkC{ymhVgv<9=C48Rv=4>xc?83LLWT`hZ3D>Y5HZzb| z;}b17KIWQ+*e)vyU*(ps2Xv^kdPZ%1eEL!9orCfn&&Z`@VYu3`XY6pfvC8$oi5xLU zaJmP=ptc0+U z`URK#4ODbAYAvBT-v$Bi*{ILNQ3tQ~S7>-Ox|NV9F0t1VPJ)ci3Or4CaxJ8>gR+E&4dCWIy zYYooG_ya8z-}(n6bxJwB$;r`I6?AlTd@lP5<*fk$h`lCMw=ZiZRn}fEv{fCAdpmsC zYv7RyU2qcGTjPxn)$dhzPVMlD&SBNkt0`?Hm8b$4RH=6&7NMX}o9S1a&pZb&>cLR>azoXMcl-hTA`bJz;&UpM$0#VR|5g#-eM zFIfT1t_Yea*_13Ir)_mNomee=gGe>@gFhHk^MN#y29p&@LzI1+esoL>4k9u#kAo*r z5=rNC0Pu;I-M!(2A9Qsi0BCCq=n(%aYec`~XL#OCqoAUS+1ppwqLisFIM^$2MXDM= z?TjDej$h!gENK&y&+zl7p!&1UVQn ztP(fm7|Ayw34w@(>jC=)fAIn~q7V*Yd&*G~Wo3E2{`t0R#6$sEpAP+*k{u4BZF*WR>;Q7!X8|x3vg$8YDHJ5jT$~sqU z5AzzL9go8X!5VKOtm}DRW6bWZKh)d{+RgoUismNNk`1kWp6a|6rqDotw-X!PPYPfntJ z5uR-U6{OycE`L%dL%6?7;a8eugMysOZOTE_(61fB;yJGLuMMp=V4A5^(dCVr#q3=NfeTFnZ`bynOnXDlqn0T7$?bIcw! z5LQb}Wvy^0_v|~TMW@$1gAE3@O9?(~mR|{d_!7&dOOGzN6ok~-(%oP2 ztj(aqn}Ln3zd#{*qSjm)h(hyHMaREVrUvp&xVX3)wh!py;bZMzKlkpc-J-sU6$Wic zV{SoFsKRyVT~9w9og}9H^|zL&jNt@G=aab=dm6gJ>oM9sx8uEH(H{1du4%$ruOsa# zT4DjGUy3hbjnnZV-l!#P09M%ZnKse++^l6?M{+WiM4II$VMElvid2xCqr@G+16UEh zXikuk0782bAG-oE3n7UlNaagkUC_Z?yQQ2G!{S><3^Ns0Ao?*Z}D1xfGqSJ3x) zH)j+Qsw)N9)uhkq6{{84A(j#~E~=mM2e*P!d*$Qw%b$hLestkM9g0&|fo6X0A zgI;~;M|}4Q2$Q<;yvtoi{K}CIJ1zDd-R*oO6fqqi8tB(521C zmO00`1<1k-ZbQ-)kRcw82=a5;vA)B@g|bO!qfbt}Bn!Gq=ZnSpqu_gy;6eBY&GSZJ z`*o2%mm7rM-U#i~c}D*YO7c@X=wxaTB>~{c+v2LVixs~q4)aMl?Pm9>s=OjL-Qto8 zr@`R&>S`WucsqV>`mMI+PNA9aIsMUn_q^3dUe(IvDDfFUGD{Md0Zk$VPk~ z0(hjrr@fDjOI$5#g=M;=Zuf&!;$L_uUJL>it!7{qiei4A@f)Ur8KC70sc+T=C_6W?R;M*W(vde2aMBi)v z4CIc1_)~m$u$oEUDPavVG0+ao8+;>qg#bZ{0rv;!soxqfT=3Dbpb>wGM()Y7q}O=P+K!fCjvR_|E)(lB!8J`c9WTm3_z#0+l3%P>nf%j|*I z%={ofH8B3MJ%n{b;r0y}7^l(B zp#|FlF3TR&%Q;VC6eKTja44bCQqUtEOJ{q1em4C3@}4DVWT|rAK0DU)-{r$Kl~$UW zOxp3kYm%w})co3pm;USmB!5hD7dwBA;eTGD!F%V6hx|8~ZnOiDT|=2Z6{7P`b_Y+Z z@X%kF|804ajvCQv>LoJIH4V(}U{KPZcgNRY;D+bsj#ZxuE9sM0yZp@Hib8Uq!Th?R zNKscCo}I{-yKZ6!)5`&|k_Wxfr^;?+^)kZSo^V`c;DUhZgFIdzm$%t zd>(YvS;e(n-gZd>BZp_lh5p7N$Q+Cj5iT{@b>nnf3J1}q&zaX@38z?aRsXmMfYYBr zCa=cG|Bz&tM%WC#J{9Si!-t*iuMt`p-sRhSy<74c^15SPHZ=TKvDQGyO8jr zaYt@cwJzxV;u75wg(X8{5QHuB63`ELxn5&xddSovHVdn32kox}50M&}h* zFo{Hvh}m9qvG_FZ=whZy3*Wprb+p!KM?S5a=v&L)t?QU5Y@>niEP^%M6}P(j4w1x9 zQ${P@#r&7@Tv}KJGVXSXUgRX@mr!jIc|4Od&TPI+t?Rm*$Yy$c=|_3|!+jufGUHE8 z0ct5YJsowDA-OpQx#LkYO};+dG=nX}{yrZYHN{$9nr3%|TR^TA&3^S90wc${`3b23 zkALI9sfD_TrT&{`Q=FPcNg=1{xI5Esc~*oOr>TwnkX14CeiC_rjkRqh8!lZYSGWlYm0Vv1ykoWFO^_J>LAH2pzomE=}4kK~#XNBdTPU(wK zHx3B+Q!y58nTibmqGb#EoX{<=&Bup|qSdYy*uQV-P9X!_gSRE=4Vk-Du+t!PJ^k7E z&@vElP0-4C5_5q4QnZPpT(0|XnjbMbs8sw%Y=n->SRar@n9hhO66aZC5_J-m3HmYT zgPp?;$R<~D!O1yi`t~TdoVmx5K;wR(qK6WtyYd%hQEW*5wxkX*#;00#R!KkQfw1lm4LA3MM#V1F8%vLWE%5&cf?`{$9dE6J#;n7(20q0r

D!@e{*-7t@vS*f{aVXCz09nfs%rDxs)2}i_ba7;l*y)R#R&Vr-2eOi978vmWWe_HB52&T9U1#9%|X_~ zakSe_keqODG8EaG8|JE_6}$aOsvnVRmOXPOJ~Y?{c~JZrf<=<=s2BX`1CDYO7{<$L zLHV6zy5c91lW`m_Osvjf4wJKH|CIlR{W`WxiECmG%v*|;Ff!`V0;q#VL|#uqFElp= zmX1Vu=z3pcSdHD?b6oitaMsAM-e$i#7sB0=eNNTMU6+hYCD?Ij6aDk1tKC@%qsRWf z#xc4f?L%VV@bz*g@n9_drV_;<;_v6MC;0ciOo<;})_WmznttRU^4{))HLIH0|Lu#k zZmnw*!uZN@Mgi#ct5jODl?t`qkSn<1F2k1R_ILjnAD^knRIu!TKy*FfAQN7I zX_R>|^B*t2%X>>epWDqrLJ#cQh#b)6isxop`Kl)9 zaR#3^6y}4c4ZA~^G+KV*-z$)Pf1h;!Ng&e+`3RM4 zuU@hFYM@gNrGka0>$Y21%GUC9Gtr>y!FeMYi8w!c;LR7UF*PLN6u!9(9^;Hxw!I4p z3E$C_x8+X5gLPe<<0#VxwW4Ur+xcOw_om3+k#y zrr=M+gH?%Z$ytF16HfWhvQHZiMl3t?MJg&2!&NGZsC7k?5!=sSIHvfAN=Z!YU>73n zaG=C8fs=!OAvUjF&i3=^*^ttAnxaCvj@Ut{%RfgvDh5luFruoveY61yFJm=nuU_ue zF!5WwNn0;le}9CB-6=>i@R?fySL&*Ic%UImM16HR2UCGUMDBUdpyfpRS$HS|d+Gm$aie!Sh>n5TV z(hm9+Q(;5l?tyfXmwi1Ff5Fu@J)DJc?jOcC9}rYv<>%Z}#VnPtFMX%KXo{s}ZulEK zVrA>fEn{gpVEbsiK0X2iU&c{E3=8Yp<8=JvcZ+Hp79g?(cFzsM+!0{nra)n76T zA#d@t+?V{^FH5)d(TAP%dVDTx*f7k3tnzFSqsNpQUU#MOUjj zx7-h6mP?{9oJSHC?IL5JA)Wdy{euAq{CBT^!Nn-7=QM4tMWv^L-Sm5=V|NBS6GwJ1 z%1l<)pNrY90>icR^_UchNHE{g^-5i0pv{~;2N$kN>1;Q{DXpCDQj7A@UU%XSX9mIJ zkGc7_YVRsG$7%LR(CJ*FW`YBdo^hAm5#o}Q$yiwoX^^WYNip6)u<$v0SOF2@hTCb1 zrC`gFdPUP-nuYosY@addTKkfJPeYQSTI-|jFm8RG5Yv|_@rv!XTZ&6{ei8E)1MM`N z{2N&ovla0&R=U8JB2Bes1#5-Xnme;#*~fwNOh(r7z(dPG>1a z3C66q|J`q6iy%NpJg3B#t5(Y&Q2b+Vn=ZDVH)QeL2|G$H>xBu)=V*TyxHeY2!rM~(w3dSk+f6Fx9_W2?V zI2+a`=|A4rEyp%B4V=nqL>s7T|9;QJ$g4_d$8X>4U<$cOYTCd5sJjFE(ST{zS41F{ zN;GF3uxaBJh|W0K54(BpiM;eld8w2n$gZ0iW5Zy{0xb(YtDknc%Q5q~?DxDF1YgV)z_Lti+7MGqs6G_Tj#dftNttqtrli@)cI zJqs5{?a2nU&_(_muw1@UB1-peaH5Za)1L8|tMfI^`N~jD7O1Cvj7s<#32ExOv4a>nq>Y7)1 zDVk_n8Hpz-XiMAsjC~denaVFF>uqyVke0L=w$0zHa$)fb z#_+GVS-ZPIaQ5^)qvtXY*_DEnXv4123}i=7^BUuNT?vms$Z=ayQ#noFn7lIUl-soob+iBItg%IE0ZOwWs9zuI$d{Os?(~qOR zkKTrkj1ts6J_=~p`lZ+xNAdaX6GyKyw>F>eP0se;&C|a zWS`+0-dQMnE0pQa3(zmGwtqb8RQT|*EC6*}VT1D}A9YMG%p3j`+uRmkV+Ha*NIVyi z>}})uH$E}gKagN3n{`BwXHa+)jk1YNDYvIFwft%8&#EgejD4t4UMYgu6|F+Js zazA8ySSQwi#?Y{%J-j>Ym?DMju!PK@lZ)>rzL-bWm*`o&^(?-;#YR@Mq z_4aVRRFGZJFtKR4xlG?^eH+)XyS*)@d(xnjJK= ze4#WxD_2?B_f5_o?`-u(uRBhjPJXoJ8o^L7&#pU6HBZt6+@{=$EK^VC>?|$qF@VVw zuTC$28TJ*v7OtoH?cV|A-7+PlI?BRz0#s#Xj3V*vF{)WOp zdw%sp8B9G|5vvbmqU5_sMJ$4Y*GpyJuJhl@s_TY2f0bOgieTR`lu?QtesJ5F5t@#o zF5nZ})rYlL296InNz<-(Jc!RKP(V;@;Q$Y(LJA8sbolr$o&S{lb40#=yY(AAF!?pW zK%qu$d*9fe6y}NrDa_~7MWdr(SZsA-fdf#pi15zX!L&zCLuLD%jiKV^U5rwVm9Eri z0mb9Oy@W$b4jK$4mDY}Y#oR*NS(42|>DVlwOVevBg_~!_UFgiEo-!_+Rs~x`)m8XL z7V1n}wPb2zRL*smUlu=Qe7!@dK2+NqPZRIoM%ACZQ9e?dz)Uh+5v7Qwz5=SzsWnew ziy8h!fN!SMg$qG68scX46Ijy+;NxFz_UI%OPW?q!a~dW@r`?aw8Fnk1hV_}gc1z1W zTZuGot>xgqp9=%UZ!-+?=Bh6yQlRo{^}p>Fdz+joYTWh6T7oj4-o}staonB^J~zX? zB5NKtTyU{jNwH977yWa=P#%0SVy2LmmQR#aakp_&JKp%~jqrpl7F?55^>ISel6t9O zlMZ85==N6=$7+q88wSfx-@a2cA2a+vppbSjW6-+ZgNbW6GS_)?qQwPaX5cgZ5);RW zr~SEH-ofSU3VzHIOkf`9B8B@QSEH>B3LS8}%ZG~p1hQlqO&4b^{T~q*>0M^D2o+zw zkis?W@}10kRuF}WjkVCi%XFgp4zoi$y`FvUT}w2@O5NVi97C@-m94xUyj*JWEgEsZ zmI>#J;*#dOpu1d-9;jsWUim0NePSEMkBGC$d_BhtuYbReBzYu{9)0>3JtKe=qEw_~ z{X~yC8%MgB7UIK}v9nQFF5V^2i=1bzL$(~4`;|n4PC6BH^h$!j=UuOmFQv^S>=V`t z)fLhdGMhO5q99N^ukP;gJR7z$%Ot6`b_P#I(M9xK5^@KqAX|=3tqKB-rD?8r0YQjT zm{o%sz~e7D~q+&L1C_4<5*n*RJAv_^iy-s-;c+B;9Z8(I=j zkpKtTT4LL`*<*HpL+TDbw)A2F2OJU&NWp(_rjP^~VNM%3Y*bgZas9)oG(DA7wZq>8 zmb%t1iTs7wI}QMzatmB1%+0Qt%anDn-($L!&rqhCDAM<^>5q2$gIj&qpJvFw$rmz{-UVJnacFNijqH;$QO zXRz?4JN(%-^ocQ|;z%AN;=~8J_^QLf^^wC^0!yj}6j) zUDB$ls;>4?OHV^bPh)-JQ;lZ1dXg~`JQbr~He(pZ%&p~i-k%IGsDK@89GejDMw#Ox!9U$i=(p3;UyZQuJSkrHHI zOdtHMgCk)pqBEPOk0g zI&ao%uitqm%Kr3U8-F=y&3WuvA*sYk58qSxRTbWAXnVQ`vt=gFBp@O>!Pp_@R%vzP z_kM<(+zUEg>)MIzs>K!(su~H07|~(J75b>x+0#}_v{NHCrsK@sPD*L&A8h0?VsIp? z`+rhCteN;_i#Wbkt$QireL2|O0o^<}R0JtnR~X09hD13&{H;?hdmfayy|8~?Q&Hg} z#BX$C)}^WyDfT%=Lmn@TOa2l{{P)nYS@HEHI#wq$bK1!{dCO+%Yt#WbIXbJBW7Tv1 zS#4JV^9sk!;uzTNZBs|vP4al}=t=hvO2ws?w_2k0E6j+7l`iF*uprS$s4NbtpXcun z<8=bIIK)>$%uecy?q zZ26M2?gOXr%H^ScH4n)g$utY}Vv-;jZs_%{e){|13aXOVM1(E_376MuNu`v&n#odpXbOiaw0 zI!zT7|J+<^RW-FP3$AXX?{D)a0WRmCE#{jyZ&0vYZRcYfu0Q-yy zc(@g=8M5W&=OY4Rm(%NOnd{@#P3cjHG&y_vcSzYC(n}%Di)*cmB1;sG4_fhGE z|BzVVY8NXSV2qS91Olm3sEq*HN6Oi`PI+yoyIVY6z@>DuPr&)V_W=O`d`G|@G-uW- zE#_b>L(s5ZdzCt3f8UCX-w_eU{;3ltwU>>D9PN?yg&QiCIa$Mgk4RjpADQGoXmq=M zWL{Wmshy51ZE48}ycLk6wt;9_B)RZcE@I{#bxgeaB`E}=srY_xIf=}hF! zTYy2+XIg4=U|dTh9bw`67x)HeH#88?$_da#0_VcbsRD(X`~QJ`*CLoN&d&kK7%40l z!XYPLi)aD>IS25Ts_N=~hC~3+LMf{Td9 zhR9hULn87NaOR6kOT{fL82tSFaEORP!0YCfmZAZ$>BMhmfED>@6K6z(4C3(Z=g+J# z$~SLtfs`lGyXn`43_rafNDwO5(LKjVrR|<6+w>wLzPNJB%xqBmzqxF0CkNIufwNXp zy1HavFZT;HefUB?z6b=^FDgpPYzN9e4T_Ba@RjE#(_v{q5q@%V5~wTn_Vxm9h*G^z zhM-%w#m>QiAax{w%L9o1#4-OfBqT%NKOXr6^rs}0l$2_r0aV~F2PE!+xwCG&C-Fbq zTdp(N*q9ifnVA{-Wu;7^MadR$Mg?(-f{7WZ4@_@mQDozsWeE_TVL9{FvN1At0daPa zU4S_sFsCHFMnXo;1<)%LbSK-^*GxqLL&GM998c$CE0*`#7vG$cUKYU+SyN-xSHt=-;NCY{NT=$!tjfXooPJ?|I5jdd zI>b7J{+>O1yxER6TlToz4+OsxD7U8bJN3%sOBM<*#4`qFWq~&Kzlj1qQNA-Xs=!0K zb@6b%A({_G{+&{P)JvdPb3tYgC9?@uCQ{RJ`*%?)5WwvEMZsS=zqmL8U_;=%%**+m zj+T~-xSWLmU0y*UHX|bvy!NN%2HS-WNEA$V@WtMEiy{G-;7;I?@^+f*?$2+3>#8;a ziC`?-nW6ptef!f5F;xvFk9Phj2{AFae+XZIMC1MEDqW$j3xP2QEA}6hB0I>9spmSY zU0VlqTInJZhgGVA_C9rL9~_W++aT0Tz-c078!$cXc*E3D``t3wOUueW=<8!a5Co*& zrz<%BTdaNc_u&D2FAA)+x&TETK!>2=xf=SK18l+Q1AqqLykr`{K*whgyB*9`gkH>& zTSDIy>4W!32?^&-s@=F?Vflv9%#tA@FL9};sAQFu`vDlj^x+Nja4rbye=i9%%Jrzx zCv9fRG}yq9l7Wm1Qbj>Q!Mi#PnCqxx16U5-Kb-bbaROPuWPuSX`WB!HQ%GWd(|!w` zF!So9sO-<$Q-FS)hK4G14Q1^WW?6t$g|L9Kam;h~+YMoXAwAeS;DheSJX~l5kW2cZ zk(QBLENvhGypRtEM+OJGfC6kRa0$-uSaH=!ag4!g8Z#@tx(W^q%mLWQntZCr{W#8W zxk2&$Y~XaJ|D#Mz?VM@@+oB~G9XV$70fXVP}lZWc0K%#w4 z&B)67ITu0#io*2w=;-Jlw6qvKj=^|>a-hsPH845~2*yCYvBnG4?=4W#`2XWofg$Trc!v@5U7qqmr-pT$@&JqcUjt&NY?wxnds`GOg*O)UYC~GgP8aRL@YZw6h%1ChtkT?($d0w$~%fGD=Qs*^O*)f=F-#C+kOIIPLRB# zIy>J26Y3rtp1ksM3|cwr|2cM#atUalyv2xA-xOH8zmKTBX8p1ke(Vq$ZxMEwtB>2F z9T@msljrOa3iHspTmU(T0*msw&yluo^e<*X0Cd%Y6~PX5xM+@ANzW4rW@PJcAq|+| z97> zr7fQ``c5e-x?XA7w@BR%Xi9*<1d0kUdZVC)L68@J#~|iFB^b}EGdd<#fXoj*GcTof4 zB$!gu&b5opU7zejfij_3G(;|Jf4K1G+o&1Hh9;(_RDjY2d^28uxTy|B>!eU_JzGqv zju}{H3=3&mstQfq)z*9F1u`%&bUr@ZlWM>T zQpW-zVWas<&hqkd`pNhH$e31U z8pDy^ga#ZCRwm z7p_6O%o+zS)&GyNw+yT5d%}hf-O?o~h#-w1CEZGQcXxMe8l()QK_sL@y5Z0w2qMzm zAl;46;`jgZUhnhe0WO6T`|Q2ftciPO?$Mc+*Zi77+Ho@UN6}G=WDJ4T>jP_g+z6}y zVT#Xs`skAuzvW}N*>dYT#nzeMLrZ*P;}qLw5&CFn+2js_n3k1+0Yo|cSvdBgIjpD% zT`=pBOkNdN{%@~e4N1?_o50=%n=%+Uj==lL1!9Meiz^F;;F)VE1K#n6&DY#4m?`92 z{d%e(FPbrN;7^bBhvZ~b;Bo?|A|)ltyg%Xj8~Wd%Mi4mb0yzCiNYbi5GY`ea0(61W zQnZklgscILt;^N%x{O(INeSX$wIazG!Yy4J@K}IAfFDlmu$dV>V5NX?EQ3L?;$VKq zgBt^H7xKs7+iJ&abyn9| z^|e|qXWrn}9Tk@$9Iy@?swnZnAbqgkKQCaW_o0IOw5U2dz4Q80%($H+Pp6cuZ1?9O zzz0h!EKCKQDH%XB4le%TjsUYpFD%@KVLJ7L=m`U^E9V?&$^pI(Afe_nfbPy3!ARnV zu`i&N-2}+WPqW*-E7W|89MF4b0uPpP*Lm{>6GS8%F0N2E5%AnVp_98W-cxi$;8G_s zyo{F_^VvWDF2N)JRs=2h-6esdeSLUlcJ@2qXFoV;Al!&|1M@;hefGh5IVn`gjn(GD z-h%s-l1=Y@UE~sXuo#r{x7>Q)P+e8+R#Vov!PK(JE^pv=kV2j`XbZ_A1nh~w2y+-F zP%&u_15D?4S79p=Jgduh@SQSgxwW6yij!~x_;Q#j{?r{^JPn7Gqj&3e8& z9R`9aX$&+RR3s35j6ePH1_KcZX|zwF{%a_sKN8d{iq&;Hf8WWyI2N8*TpeH4m zHoZw4s6J`i+t5Ji$%_wDA+kNMM1ip4c&kq2W4JmxE9cIFRmdNa#Rx^gbh_ zG?fP%6$vzD{dF-!dHrz*l z@9E?5>1Fs|j{TXOTm`nesIoHUrSaLp;^NnIN|-SxGb3X^yD7|ONdN2Dw#&R{g{(fH zHv+N&I;jc3*i0>=rgg=l@X=~o^iaS8_^-gOHstR4$F2Pxy{M%161KASZ#lVJmnoGZ z`V$Kk7ny+X0i%Gym39}n{4&|2s^f2+~X9;KG-$><|?6fK%d~@{DY+X~oFUkP6JLO;0Uclkhw9 z6ZX5<-{TZ4AvRV4fj%w3KY%w$jhp@~ap1WrdAtl50?}0Gm0M7N3Oa)&-cbvuqfj

}d?a;?U%0B00D3ELZu-$qBAa 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 ai>*O!%Yn>Ly%&dI;Po@fop|4glJGAomMcvF diff --git a/Telegram/Resources/art/sprite_200x.png b/Telegram/Resources/art/sprite_200x.png index dcad41196350c6f3081b4a44a364adb8e1c66287..95f40861291b4f7c52535391bab466b424ad8c2f 100644 GIT binary patch delta 37241 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 hMx!}Yjakb_r9g-1w)n@5BX7D;?l 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 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 ee4bbf2b98..7736777751 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -684,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}"; diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index fea12175a1..b7d7095c39 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -959,17 +959,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)); } 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, MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100)))), 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, MTP_inputPhotoCrop(MTP_double(0), MTP_double(0), MTP_double(100)))), rpcDone(&AppClass::chatPhotoDone, id), rpcFail(&AppClass::peerPhotoFail, id), 0, 0, history->sendRequestId); } } } diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 848bd99553..f3d50db543 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -24,6 +24,7 @@ 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" @@ -37,54 +38,6 @@ 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) { - 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::writeArchivedStickers(); - Ui::showLayer(new StickersBox(archived), KeepOtherLayers); -} - -} // namespace Stickers - StickerSetInner::StickerSetInner(const MTPInputStickerSet &set) : TWidget() , _input(set) { connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); @@ -215,12 +168,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); } @@ -864,59 +818,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) { @@ -929,19 +837,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; } 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.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/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index a3a57fb2c8..f83fdb2895 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -35,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" @@ -3055,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()) { @@ -3130,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())); @@ -3157,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(); @@ -3166,7 +3168,7 @@ void HistoryWidget::start() { } void HistoryWidget::onStickersUpdated() { - _emojiPan.refreshStickers(); + _emojiPan->refreshStickers(); updateStickersByEmoji(); } @@ -3228,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(); @@ -3450,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) { @@ -4380,11 +4382,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() { @@ -4516,8 +4518,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(); @@ -4579,8 +4581,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); @@ -4715,8 +4717,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(); @@ -5257,8 +5259,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(); @@ -5586,7 +5588,7 @@ void HistoryWidget::onPhotoSelect() { _attachDocument.clearState(); _attachDocument.hide(); _attachPhoto.show(); - _attachType.fastHide(); + _attachType->fastHide(); if (cDefaultAttach() != dbidaPhoto) { cSetDefaultAttach(dbidaPhoto); @@ -5614,7 +5616,7 @@ void HistoryWidget::onDocumentSelect() { _attachPhoto.clearState(); _attachPhoto.hide(); _attachDocument.show(); - _attachType.fastHide(); + _attachType->fastHide(); if (cDefaultAttach() != dbidaDocument) { cSetDefaultAttach(dbidaDocument); @@ -5651,14 +5653,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(); } @@ -5706,7 +5708,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(); } @@ -5961,24 +5963,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); @@ -6460,8 +6462,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()); @@ -6491,7 +6493,7 @@ void HistoryWidget::clearInlineBot() { inlineBotChanged(); _field.finishPlaceholder(); } - _emojiPan.clearInlineBot(); + _emojiPan->clearInlineBot(); onCheckFieldAutocomplete(); } @@ -6958,15 +6960,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() { @@ -6980,7 +6982,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() { @@ -7015,25 +7017,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; } @@ -7530,8 +7532,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(); } @@ -7600,10 +7602,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; @@ -7701,8 +7703,8 @@ 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(); } @@ -7747,8 +7749,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 e73e2b87de..7d41334878 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 @@ -1124,10 +1127,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/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index af1ca0e980..8feba33bc5 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" diff --git a/Telegram/SourceFiles/stickers/emoji_pan.cpp b/Telegram/SourceFiles/stickers/emoji_pan.cpp new file mode 100644 index 0000000000..f84cc454b2 --- /dev/null +++ b/Telegram/SourceFiles/stickers/emoji_pan.cpp @@ -0,0 +1,3704 @@ +/* +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() : TWidget() +, _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::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)) +, _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(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; +} + +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); + } + + 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); + + 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) { + _lastMousePos = mapToGlobal(QPoint(-10, -10)); + 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 { + updateSelected(); + } +} + +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() { + clearSelection(true); + + _mySets.clear(); + _mySets.reserve(Global::StickerSetsOrder().size() + 1); + + refreshRecentStickers(false); + for_const (auto setId, Global::StickerSetsOrder()) { + appendSet(_mySets, setId); + } + + _featuredSets.clear(); + _featuredSets.reserve(Global::FeaturedStickerSetsOrder().size()); + + for_const (auto setId, Global::FeaturedStickerSetsOrder()) { + appendSet(_featuredSets, setId); + } + + if (_section == Section::Stickers) { + int h = countHeight(); + if (h != height()) resize(width(), h); + + _settings.setVisible(_mySets.isEmpty()); + } else { + _settings.hide(); + } + + 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 (_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 < _top + _maxHeight) && (top + _inlineRows.at(row).items.at(col)->height() > _top); +} + +bool StickerPanInner::ui_isInlineItemBeingChosen() { + return showingInlineItems(); +} + +void StickerPanInner::appendSet(Sets &to, 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; + + 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()) { + 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() && !Global::FeaturedStickerSetsOrder().empty()) { + 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 (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.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 (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..ff6d609b5e --- /dev/null +++ b/Telegram/SourceFiles/stickers/emoji_pan.h @@ -0,0 +1,657 @@ +/* +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 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; + + 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 _top = 0, _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 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 (_section == Section::Inlines); + } + int 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 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; + + 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); + + void appendSet(Sets &to, 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; + + int _top = 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..4f5412d623 --- /dev/null +++ b/Telegram/SourceFiles/stickers/stickers.cpp @@ -0,0 +1,143 @@ +/* +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 "lang.h" + +#include "boxes/stickersetbox.h" +#include "boxes/confirmbox.h" +#include "apiwrap.h" +#include "localstorage.h" +#include "mainwidget.h" + +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) { + 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); +} + +} // namespace Stickers diff --git a/Telegram/SourceFiles/stickers/stickers.h b/Telegram/SourceFiles/stickers/stickers.h new file mode 100644 index 0000000000..06df73d47f --- /dev/null +++ b/Telegram/SourceFiles/stickers/stickers.h @@ -0,0 +1,29 @@ +/* +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); + +} // namespace Stickers diff --git a/Telegram/SourceFiles/stickers/stickers.style b/Telegram/SourceFiles/stickers/stickers.style new file mode 100644 index 0000000000..11168295c3 --- /dev/null +++ b/Telegram/SourceFiles/stickers/stickers.style @@ -0,0 +1,39 @@ +/* +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; +} diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index 9bffcf52f4..e5938b1273 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -38,6 +38,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': [ @@ -377,6 +378,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', diff --git a/Telegram/gyp/telegram_win.gypi b/Telegram/gyp/telegram_win.gypi index 1973d650c0..ce23aa2e6d 100644 --- a/Telegram/gyp/telegram_win.gypi +++ b/Telegram/gyp/telegram_win.gypi @@ -39,11 +39,6 @@ 'lib_exif', 'OpenAL32', 'common', - 'libavformat/libavformat.a', - 'libavcodec/libavcodec.a', - 'libavutil/libavutil.a', - 'libswresample/libswresample.a', - 'libswscale/libswscale.a', 'opus', 'celt', 'silk_common', @@ -52,6 +47,17 @@ 'lib/exception_handler', 'lib/crash_generation_client', ], + 'msvs_settings': { + 'VCLinkerTool': { + 'AdditionalOptions': [ + 'libavformat/libavformat.a', + 'libavcodec/libavcodec.a', + 'libavutil/libavutil.a', + 'libswresample/libswresample.a', + 'libswscale/libswscale.a', + ], + }, + }, 'configurations': { 'Debug': { 'include_dirs': [ From 8419a56e1049c74b24f048459d41e31611bf2c3f Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 10 Sep 2016 23:54:59 +0300 Subject: [PATCH 07/10] Emoji display added to sticker preview. Reading featured sticker sets. Reading featured sticker sets one by one while scrolling through them, only when the row was fully visible and the image was already loaded. --- Telegram/SourceFiles/boxes/boxes.style | 19 --- Telegram/SourceFiles/boxes/stickersetbox.cpp | 111 ++++++++------- Telegram/SourceFiles/boxes/stickersetbox.h | 28 ++-- Telegram/SourceFiles/historywidget.cpp | 4 +- Telegram/SourceFiles/layerwidget.cpp | 64 ++++++++- Telegram/SourceFiles/layerwidget.h | 6 +- Telegram/SourceFiles/localstorage.cpp | 14 ++ Telegram/SourceFiles/mainwidget.cpp | 24 ++-- Telegram/SourceFiles/stickers/emoji_pan.cpp | 140 +++++++++++++++---- Telegram/SourceFiles/stickers/emoji_pan.h | 57 ++++---- Telegram/SourceFiles/stickers/stickers.cpp | 66 ++++++++- Telegram/SourceFiles/stickers/stickers.h | 20 +++ Telegram/SourceFiles/stickers/stickers.style | 21 +++ Telegram/SourceFiles/structs.cpp | 4 +- Telegram/SourceFiles/structs.h | 4 +- 15 files changed, 418 insertions(+), 164 deletions(-) diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 820e9a1e81..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; } diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index f3d50db543..5f094e7951 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -30,6 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localstorage.h" #include "dialogs/dialogs_layout.h" #include "styles/style_boxes.h" +#include "styles/style_stickers.h" namespace { @@ -423,7 +424,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)) @@ -436,7 +437,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()) @@ -451,10 +452,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); @@ -587,18 +593,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); @@ -967,19 +973,6 @@ void StickersInner::rebuild() { } App::api()->requestStickerSets(); updateSize(); - - if (_section == Section::Featured && Global::FeaturedStickerSetsUnreadCount()) { - Global::SetFeaturedStickerSetsUnreadCount(0); - QVector readIds; - readIds.reserve(Global::StickerSets().size()); - for (auto &set : Global::RefStickerSets()) { - if (set.flags & MTPDstickerSet_ClientFlag::f_unread) { - set.flags &= ~MTPDstickerSet_ClientFlag::f_unread; - readIds.push_back(MTP_long(set.id)); - } - } - MTP::send(MTPmessages_ReadFeaturedStickers(MTP_vector(readIds)), rpcDone(&StickersInner::readFeaturedDone), rpcFail(&StickersInner::readFeaturedFail)); - } } void StickersInner::updateSize() { @@ -1007,7 +1000,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); } } @@ -1031,6 +1024,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; } @@ -1046,10 +1040,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); } @@ -1097,11 +1092,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; } @@ -1117,35 +1116,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()); @@ -1169,6 +1144,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; } @@ -1329,9 +1330,16 @@ 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; @@ -1456,6 +1464,7 @@ void StickersBox::resizeEvent(QResizeEvent *e) { ItemListBox::resizeEvent(e); _inner->resize(width(), _inner->height()); _inner->setVisibleScrollbar((scrollArea()->scrollTopMax() > 0) ? (st::boxScroll.width - st::boxScroll.deltax) : 0); + updateVisibleTopBottom(); if (_topShadow) { _topShadow->setGeometry(0, st::boxTitleHeight + _aboutHeight, width(), st::lineWidth); } diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index ef73191b2e..99bc272b9f 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -28,7 +28,6 @@ class StickerSetInner : public TWidget, public RPCSender { Q_OBJECT public: - StickerSetInner(const MTPInputStickerSet &set); void mousePressEvent(QMouseEvent *e); @@ -49,16 +48,13 @@ public: ~StickerSetInner(); public slots: - void onPreview(); signals: - void updateButtons(); void installed(uint64 id); private: - int32 stickerFromGlobalPos(const QPoint &p) const; void gotSet(const MTPmessages_StickerSet &set); @@ -169,6 +165,7 @@ private: bool reorderFail(const RPCError &result); void saveOrder(); + void updateVisibleTopBottom(); void checkLoadMoreArchived(); void getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result); @@ -198,7 +195,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 +217,7 @@ public: Stickers::Order getDisabledSets() const; void setVisibleScrollbar(int32 width); + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; ~StickersInner(); @@ -239,6 +237,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 +250,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 +279,7 @@ private: DocumentData *sticker; int32 count; QString title; + int titleWidth; bool installed, official, unread, disabled, recent; int32 pixw, pixh; anim::ivalue yadd; @@ -286,7 +289,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 +300,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 +317,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/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index f83fdb2895..604c6c204e 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3836,14 +3836,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(); 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/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index b662396c9a..066004bc05 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -3215,6 +3215,7 @@ namespace Local { 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()) { @@ -3240,6 +3241,11 @@ namespace Local { 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; } } @@ -3287,6 +3293,8 @@ namespace Local { } void writeInstalledStickers() { + if (!Global::started()) return; + _writeStickerSets(_installedStickersKey, [](const Stickers::Set &set) { if (set.id == Stickers::CloudRecentSetId) { // separate file for recent return StickerSetCheckResult::Skip; @@ -3306,6 +3314,8 @@ namespace Local { } void writeFeaturedStickers() { + if (!Global::started()) return; + _writeStickerSets(_featuredStickersKey, [](const Stickers::Set &set) { if (set.id == Stickers::CloudRecentSetId) { // separate file for recent return StickerSetCheckResult::Skip; @@ -3323,6 +3333,8 @@ namespace Local { } void writeRecentStickers() { + if (!Global::started()) return; + _writeStickerSets(_recentStickersKey, [](const Stickers::Set &set) { if (set.id != Stickers::CloudRecentSetId || set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; @@ -3332,6 +3344,8 @@ namespace Local { } 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; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 8feba33bc5..cde8d0048d 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -4691,15 +4691,18 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { writeArchived = true; } } - - const auto &v(set.vdocuments.c_vector().v); + 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 (int32 i = 0, l = v.size(); i < l; ++i) { - DocumentData *doc = App::feedDocument(v.at(i)); + auto doc = App::feedDocument(v.at(i)); if (!doc || !doc->sticker()) continue; it->stickers.push_back(doc); + if (doc->sticker()->set.type() != mtpc_inputStickerSetID) { + doc->sticker()->set = inputSet; + } } it->emoji.clear(); auto &packs = set.vpacks.c_vector().v; @@ -4780,16 +4783,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/stickers/emoji_pan.cpp b/Telegram/SourceFiles/stickers/emoji_pan.cpp index f84cc454b2..a39f1e1944 100644 --- a/Telegram/SourceFiles/stickers/emoji_pan.cpp +++ b/Telegram/SourceFiles/stickers/emoji_pan.cpp @@ -287,7 +287,7 @@ void EmojiColorPicker::drawVariant(Painter &p, int variant) { p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojiLarge(), QRect(_variants[variant]->x * esize, _variants[variant]->y * esize, esize, esize)); } -EmojiPanInner::EmojiPanInner() : TWidget() +EmojiPanInner::EmojiPanInner() : ScrolledWidget() , _maxHeight(int(st::emojiPanMaxHeight) - st::rbEmoji.height) , _a_selected(animation(this, &EmojiPanInner::step_selected)) { resize(st::emojiPanWidth - st::emojiScroll.width, countHeight()); @@ -316,8 +316,9 @@ void EmojiPanInner::setMaxHeight(int32 h) { resize(st::emojiPanWidth - st::emojiScroll.width, countHeight()); } -void EmojiPanInner::setScrollTop(int top) { - _top = top; +void EmojiPanInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; } int EmojiPanInner::countHeight() { @@ -519,7 +520,7 @@ void EmojiPanInner::onShowPicker() { 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; + y -= _picker.height() - st::buttonRadius + _visibleTop; if (y < 0) { y += _picker.height() - st::buttonRadius + st::emojiPanSize.height() - st::buttonRadius; } @@ -788,7 +789,7 @@ void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) { update(); } -StickerPanInner::StickerPanInner() : TWidget() +StickerPanInner::StickerPanInner() : ScrolledWidget() , _a_selected(animation(this, &StickerPanInner::step_selected)) , _section(cShowingSavedGifs() ? Section::Gifs : Section::Stickers) , _addText(lang(lng_stickers_featured_add).toUpper()) @@ -800,7 +801,7 @@ StickerPanInner::StickerPanInner() : TWidget() setFocusPolicy(Qt::NoFocus); setAttribute(Qt::WA_OpaquePaintEvent); - connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update())); + connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(onImageLoaded())); connect(&_settings, SIGNAL(clicked()), this, SLOT(onSettings())); _previewTimer.setSingleShot(true); @@ -816,11 +817,47 @@ void StickerPanInner::setMaxHeight(int32 h) { _settings.moveToLeft((st::emojiPanWidth - _settings.width()) / 2, height() / 3); } -void StickerPanInner::setScrollTop(int top) { - if (top == _top) return; +void StickerPanInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { + _visibleBottom = visibleBottom; + if (_visibleTop != visibleTop) { + _visibleTop = visibleTop; + _lastScrolled = getms(); + } + if (_section == Section::Featured) { + readVisibleSets(); + } +} - _lastScrolled = getms(); - _top = top; +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 { @@ -973,6 +1010,9 @@ void StickerPanInner::paintStickers(Painter &p, const QRect &r) { 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); @@ -984,6 +1024,15 @@ void StickerPanInner::paintStickers(Painter &p, const QRect &r) { 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)); @@ -1246,7 +1295,6 @@ bool StickerPanInner::showSectionIcons() const { } void StickerPanInner::clearSelection(bool fast) { - _lastMousePos = mapToGlobal(QPoint(-10, -10)); if (fast) { if (showingInlineItems()) { if (_selected >= 0) { @@ -1287,7 +1335,10 @@ void StickerPanInner::clearSelection(bool fast) { _a_selected.stop(); update(); } else { + auto pos = _lastMousePos; + _lastMousePos = mapToGlobal(QPoint(-10, -10)); updateSelected(); + _lastMousePos = pos; } } @@ -1324,35 +1375,62 @@ void StickerPanInner::hideFinish(bool completely) { } void StickerPanInner::refreshStickers() { - clearSelection(true); + 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); + appendSet(_mySets, setId, AppendSkip::Archived); } _featuredSets.clear(); _featuredSets.reserve(Global::FeaturedStickerSetsOrder().size()); for_const (auto setId, Global::FeaturedStickerSetsOrder()) { - appendSet(_featuredSets, setId); + appendSet(_featuredSets, setId, AppendSkip::Installed); } - if (_section == Section::Stickers) { + if (stickersShown) { int h = countHeight(); if (h != height()) resize(width(), h); - _settings.setVisible(_mySets.isEmpty()); + _settings.setVisible(_section == Section::Stickers && _mySets.isEmpty()); } else { _settings.hide(); } emit refreshIcons(); - updateSelected(); + // 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) { @@ -1790,17 +1868,19 @@ bool StickerPanInner::ui_isInlineItemVisible(const InlineItem *layout) { top += _inlineRows.at(i).height; } - return (top < _top + _maxHeight) && (top + _inlineRows.at(row).items.at(col)->height() > _top); + return (top < _visibleTop + _maxHeight) && (top + _inlineRows[row].items[col]->height() > _visibleTop); } bool StickerPanInner::ui_isInlineItemBeingChosen() { return showingInlineItems(); } -void StickerPanInner::appendSet(Sets &to, uint64 setId) { +void StickerPanInner::appendSet(Sets &to, uint64 setId, AppendSkip skip) { auto &sets = Global::StickerSets(); auto it = sets.constFind(setId); - if (it == sets.cend() || (it->flags & MTPDstickerSet::Flag::f_archived) || it->stickers.isEmpty()) return; + 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)); } @@ -1879,7 +1959,7 @@ void StickerPanInner::fillIcons(QList &icons) { if (!cSavedGifs().isEmpty()) { icons.push_back(StickerIcon(Stickers::NoneSetId)); } - if (Global::FeaturedStickerSetsUnreadCount()) { + if (Global::FeaturedStickerSetsUnreadCount() && !_featuredSets.isEmpty()) { icons.push_back(StickerIcon(Stickers::FeaturedSetId)); } @@ -1906,7 +1986,7 @@ void StickerPanInner::fillIcons(QList &icons) { } } - if (!Global::FeaturedStickerSetsUnreadCount() && !Global::FeaturedStickerSetsOrder().empty()) { + if (!Global::FeaturedStickerSetsUnreadCount() && !_featuredSets.isEmpty()) { icons.push_back(StickerIcon(Stickers::FeaturedSetId)); } } @@ -2671,14 +2751,14 @@ void EmojiPan::paintEvent(QPaintEvent *e) { p.drawPixmapLeft(x + (st::rbEmoji.width - s.pixw) / 2, _iconsTop + (st::rbEmoji.height - s.pixh) / 2, width(), pix); x += st::rbEmoji.width; } else { - if (selxrel != x) { + 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 (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); } @@ -3330,7 +3410,7 @@ void EmojiPan::onScrollEmoji() { _noTabUpdate = false; } - e_inner.setScrollTop(st); + e_inner.setVisibleTopBottom(st, st + e_scroll.height()); } void EmojiPan::onScrollStickers() { @@ -3343,7 +3423,7 @@ void EmojiPan::onScrollStickers() { onInlineRequest(); } - s_inner.setScrollTop(st); + s_inner.setVisibleTopBottom(st, st + s_scroll.height()); } void EmojiPan::validateSelectedIcon(bool animated) { diff --git a/Telegram/SourceFiles/stickers/emoji_pan.h b/Telegram/SourceFiles/stickers/emoji_pan.h index ff6d609b5e..16c8edc775 100644 --- a/Telegram/SourceFiles/stickers/emoji_pan.h +++ b/Telegram/SourceFiles/stickers/emoji_pan.h @@ -113,7 +113,7 @@ private: }; class EmojiPanel; -class EmojiPanInner : public TWidget { +class EmojiPanInner : public ScrolledWidget { Q_OBJECT public: @@ -122,13 +122,6 @@ public: 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(); @@ -140,13 +133,20 @@ public: void refreshRecent(); - void setScrollTop(int top); + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; void fillPanels(QVector &panels); void refreshPanels(QVector &panels); -public slots: +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(); @@ -156,7 +156,6 @@ public slots: bool checkPickerHide(); signals: - void selected(EmojiPtr emoji); void switchToStickers(); @@ -168,7 +167,6 @@ signals: void saveConfigDelayed(int32 delay); private: - int32 _maxHeight; int countHeight(); @@ -180,7 +178,9 @@ private: Animations _animations; Animation _a_selected; - int _top = 0, _counts[emojiTabCount]; + int _visibleTop = 0; + int _visibleBottom = 0; + int _counts[emojiTabCount]; QVector _emojis[emojiTabCount]; QVector _hovers[emojiTabCount]; @@ -207,23 +207,15 @@ struct StickerIcon { int pixh = 0; }; -class StickerPanInner : public TWidget { +class StickerPanInner : public ScrolledWidget { 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); @@ -247,7 +239,7 @@ public: void fillPanels(QVector &panels); void refreshPanels(QVector &panels); - void setScrollTop(int top); + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; void preloadImages(); uint64 currentSet(int yOffset) const; @@ -264,12 +256,21 @@ public: ~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); @@ -310,6 +311,7 @@ private: return const_cast(this)->shownSets(); } int featuredRowHeight() const; + void readVisibleSets(); bool showingInlineItems() const { // Gifs or Inline results return (_section == Section::Inlines) || (_section == Section::Gifs); @@ -324,7 +326,11 @@ private: void refreshSwitchPmButton(const InlineCacheEntry *entry); - void appendSet(Sets &to, uint64 setId); + enum class AppendSkip { + Archived, + Installed, + }; + void appendSet(Sets &to, uint64 setId, AppendSkip skip); void selectEmoji(EmojiPtr emoji); QRect stickerRect(int tab, int sel); @@ -335,7 +341,8 @@ private: Animations _animations; Animation _a_selected; - int _top = 0; + int _visibleTop = 0; + int _visibleBottom = 0; Sets _mySets; Sets _featuredSets; diff --git a/Telegram/SourceFiles/stickers/stickers.cpp b/Telegram/SourceFiles/stickers/stickers.cpp index 4f5412d623..266ede52dc 100644 --- a/Telegram/SourceFiles/stickers/stickers.cpp +++ b/Telegram/SourceFiles/stickers/stickers.cpp @@ -19,15 +19,22 @@ 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 "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; @@ -140,4 +147,61 @@ void undoInstallLocally(uint64 setId) { 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 index 06df73d47f..c2449c6af4 100644 --- a/Telegram/SourceFiles/stickers/stickers.h +++ b/Telegram/SourceFiles/stickers/stickers.h @@ -25,5 +25,25 @@ 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 index 11168295c3..ae74a48f76 100644 --- a/Telegram/SourceFiles/stickers/stickers.style +++ b/Telegram/SourceFiles/stickers/stickers.style @@ -37,3 +37,24 @@ featuredStickersAdd: RoundButton(defaultActiveButton) { 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..459c483876 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -1155,7 +1155,9 @@ void DocumentData::setattributes(const QVector &attributes } 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 From c7e8b153bb8a45ed40412f68233d895ef71981c0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 11 Sep 2016 11:38:14 +0300 Subject: [PATCH 08/10] Enabled sending stickers from the sticker set preview box. --- Telegram/SourceFiles/boxes/stickersetbox.cpp | 76 +++++++++++++++++--- Telegram/SourceFiles/boxes/stickersetbox.h | 31 ++++---- Telegram/SourceFiles/historywidget.cpp | 11 +-- Telegram/SourceFiles/historywidget.h | 4 +- Telegram/SourceFiles/mainwidget.cpp | 4 ++ Telegram/SourceFiles/mainwidget.h | 1 + 6 files changed, 98 insertions(+), 29 deletions(-) diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 5f094e7951..93b1bbb96f 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -39,7 +39,7 @@ constexpr int kArchivedLimitPerPage = 30; } // namespace -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()) { @@ -49,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())); } @@ -56,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) { @@ -113,6 +120,8 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { } _loaded = true; + updateSelected(); + emit updateButtons(); } @@ -188,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)); @@ -205,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)); @@ -241,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(); @@ -272,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 { @@ -364,7 +418,9 @@ void StickerSetBox::onUpdateButtons() { } void StickerSetBox::onScroll() { - _inner.setScrollBottom(scrollArea()->scrollTop() + scrollArea()->height()); + auto scroll = scrollArea(); + auto scrollTop = scroll->scrollTop(); + _inner.setVisibleTopBottom(scrollTop, scrollTop + scroll->height()); } void StickerSetBox::showAll() { diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index 99bc272b9f..d5c4d2f81c 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -24,30 +24,30 @@ 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: @@ -55,7 +55,9 @@ signals: 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); @@ -63,6 +65,7 @@ private: void installDone(const MTPmessages_StickerSetInstallResult &result); bool installFail(const RPCError &error); + QVector _packOvers; StickerPack _pack; StickersByEmojiMap _emoji; bool _loaded = false; @@ -73,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 { diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 604c6c204e..adaadf6a0a 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -7458,8 +7458,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) { @@ -7646,14 +7646,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); @@ -7707,6 +7707,7 @@ void HistoryWidget::sendExistingDocument(DocumentData *doc, const QString &capti if (!_emojiPan->isHidden()) _emojiPan->hideStart(); _field.setFocus(); + return true; } void HistoryWidget::sendExistingPhoto(PhotoData *photo, const QString &caption) { diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index 7d41334878..e55451ae19 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -803,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); @@ -913,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); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index cde8d0048d..5bedb07b1c 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1712,6 +1712,10 @@ void MainWidget::onShareContactCancel() { _history->cancelShareContact(); } +bool MainWidget::onSendSticker(DocumentData *document) { + return _history->onStickerSend(document); +} + void MainWidget::dialogsCancelled() { if (_hider) { _hider->startHide(); 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); From 26ca5fafbbf21bf24038dd94261e31ba6e8be4ee Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 13 Sep 2016 13:03:21 +0300 Subject: [PATCH 09/10] Scheme updated. Dialogs list layout bug fixed. --- Telegram/SourceFiles/app.cpp | 3 +- Telegram/SourceFiles/application.cpp | 11 +- Telegram/SourceFiles/boxes/stickersetbox.cpp | 3 +- Telegram/SourceFiles/historywidget.cpp | 18 +- Telegram/SourceFiles/localimageloader.cpp | 6 +- Telegram/SourceFiles/localstorage.cpp | 3 +- Telegram/SourceFiles/mainwidget.cpp | 7 +- Telegram/SourceFiles/mtproto/scheme.tl | 36 +- Telegram/SourceFiles/mtproto/scheme_auto.cpp | 203 +++-- Telegram/SourceFiles/mtproto/scheme_auto.h | 756 ++++++++++++------ .../serialize/serialize_document.cpp | 9 +- Telegram/SourceFiles/structs.cpp | 4 +- 12 files changed, 712 insertions(+), 347 deletions(-) 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 258c17bf6c..3326b9c5f2 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -870,7 +870,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)) { @@ -963,13 +963,13 @@ void AppClass::photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputF if (i != photoUpdates.end()) { 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)) { auto history = App::history(id); - history->sendRequestId = MTP::send(MTPmessages_EditChatPhoto(history->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, history->sendRequestId); + 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)) { auto history = App::history(id); - history->sendRequestId = MTP::send(MTPchannels_EditPhoto(history->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, history->sendRequestId); + 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); } } } @@ -1046,7 +1046,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; diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 93b1bbb96f..c7e46a291b 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -791,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) { diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index adaadf6a0a..07372a93b9 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -3530,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) { @@ -6714,7 +6715,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); } } @@ -6732,7 +6735,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()) { @@ -6763,7 +6767,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); } } } @@ -6789,7 +6795,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); } } } 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 066004bc05..b48e63d257 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -3395,7 +3395,8 @@ namespace Local { if (type == AnimatedDocument) { attributes.push_back(MTP_documentAttributeAnimated()); } else if (type == StickerDocument) { - attributes.push_back(MTP_documentAttributeSticker(MTP_string(alt), MTP_inputStickerSetEmpty())); + 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))); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 5bedb07b1c..68f9c5b1df 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -2051,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(); @@ -2197,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())); } } diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index a944a0d772..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; @@ -258,7 +255,7 @@ 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; @@ -509,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; @@ -718,6 +716,11 @@ messages.stickerSetInstallResultArchive#35e410a8 sets:Vector 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--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -742,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; @@ -822,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; @@ -855,20 +858,21 @@ messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flag messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#2dacca4f hash:int = messages.FeaturedStickers; messages.readFeaturedStickers#5b118126 id:Vector = Bool; -messages.getRecentStickers#99197c2c hash:int = messages.RecentStickers; -messages.saveRecentSticker#348e39bf id:InputDocument unsave:Bool = Bool; -messages.clearRecentStickers#ab02e5d2 = 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; diff --git a/Telegram/SourceFiles/mtproto/scheme_auto.cpp b/Telegram/SourceFiles/mtproto/scheme_auto.cpp index a98f3e7996..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); @@ -1723,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 { @@ -1730,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; } } @@ -4034,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 { @@ -4041,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; } } @@ -4095,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(); } @@ -5948,6 +5952,48 @@ void _serialize_stickerSetMultiCovered(MTPStringLogger &to, int32 stage, int32 l } } +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); @@ -6129,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); @@ -6583,6 +6642,8 @@ void _serialize_messages_readFeaturedStickers(MTPStringLogger &to, int32 stage, } 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 { @@ -6590,14 +6651,28 @@ 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) { @@ -8118,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); @@ -8359,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 { @@ -8366,7 +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; + 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; } } @@ -8385,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(); } @@ -8429,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; } } @@ -8443,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; } } @@ -8669,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); @@ -8932,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); @@ -9072,6 +9145,9 @@ namespace { _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); @@ -9087,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); @@ -9228,7 +9305,6 @@ 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); @@ -9247,6 +9323,7 @@ namespace { _serializers.insert(mtpc_messages_getFeaturedStickers, _serialize_messages_getFeaturedStickers); _serializers.insert(mtpc_messages_getRecentStickers, _serialize_messages_getRecentStickers); _serializers.insert(mtpc_messages_getArchivedStickers, _serialize_messages_getArchivedStickers); + _serializers.insert(mtpc_messages_getAttachedStickers, _serialize_messages_getAttachedStickers); _serializers.insert(mtpc_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 075b88e64f..393e133ecd 100644 --- a/Telegram/SourceFiles/mtproto/scheme_auto.h +++ b/Telegram/SourceFiles/mtproto/scheme_auto.h @@ -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, @@ -184,7 +182,7 @@ enum { mtpc_messageActionGameScore = 0x3a14cfa5, mtpc_dialog = 0x66ffba14, mtpc_photoEmpty = 0x2331b22d, - mtpc_photo = 0xcded42fe, + mtpc_photo = 0x9288dd29, mtpc_photoSizeEmpty = 0xe17e23c, mtpc_photoSize = 0x77bfb61b, mtpc_photoCachedSize = 0xe9a734fa, @@ -374,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, @@ -518,6 +517,9 @@ enum { 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, @@ -539,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, @@ -615,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, @@ -648,18 +650,19 @@ enum { mtpc_messages_getAllDrafts = 0x6a3f8d65, mtpc_messages_getFeaturedStickers = 0x2dacca4f, mtpc_messages_readFeaturedStickers = 0x5b118126, - mtpc_messages_getRecentStickers = 0x99197c2c, - mtpc_messages_saveRecentSticker = 0x348e39bf, - mtpc_messages_clearRecentStickers = 0xab02e5d2, + 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, @@ -824,9 +827,6 @@ class MTPDinputFileLocation; class MTPDinputEncryptedFileLocation; class MTPDinputDocumentFileLocation; -class MTPinputPhotoCrop; -class MTPDinputPhotoCrop; - class MTPinputAppEvent; class MTPDinputAppEvent; @@ -1401,6 +1401,13 @@ class MTPstickerSetCovered; class MTPDstickerSetCovered; class MTPDstickerSetMultiCovered; +class MTPmaskCoords; +class MTPDmaskCoords; + +class MTPinputStickeredMedia; +class MTPDinputStickeredMediaPhoto; +class MTPDinputStickeredMediaDocument; + // Boxed types definitions typedef MTPBoxed MTPResPQ; @@ -1437,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; @@ -1580,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 @@ -2980,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(); @@ -9829,6 +9800,87 @@ private: }; 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 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 class MTPDresPQ : public mtpDataImpl { @@ -10245,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 { @@ -10289,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 { @@ -10376,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 { @@ -10451,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() { @@ -11248,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; @@ -13104,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 { @@ -14906,6 +15001,39 @@ public: 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' @@ -16187,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; @@ -19477,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; @@ -21063,6 +21188,16 @@ public: 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() { @@ -21070,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() { @@ -21096,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; @@ -21110,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() { @@ -21138,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() { @@ -21171,6 +21341,8 @@ public: } MTPmessages_ClearRecentStickers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } + MTPmessages_ClearRecentStickers(const MTPflags &_flags) : MTPBoxed(MTPmessages_clearRecentStickers(_flags)) { + } }; class MTPmessages_getArchivedStickers { // RPC method 'messages.getArchivedStickers' @@ -21381,6 +21553,45 @@ public: } }; +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() { @@ -21508,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; @@ -21543,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; @@ -21591,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)) { } }; @@ -23371,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)); @@ -23383,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)); @@ -23407,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); @@ -23434,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)); } @@ -23641,8 +23834,8 @@ public: 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)); @@ -24211,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)); @@ -24223,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); } @@ -24643,6 +24839,15 @@ public: 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 @@ -25892,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()); @@ -25908,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()); @@ -25948,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()); @@ -25972,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()); @@ -26026,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()); @@ -26046,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()); @@ -26131,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); @@ -26143,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); @@ -26169,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; @@ -26190,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"); } @@ -26206,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; } } @@ -26230,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 { @@ -26431,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()) { } @@ -28024,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; @@ -28044,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); @@ -28060,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); @@ -28081,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 { @@ -32470,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()); @@ -32504,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()); @@ -32528,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"); } } @@ -32540,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()); @@ -32571,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"); } } @@ -32590,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); @@ -32603,6 +32776,9 @@ inline MTPdocumentAttribute MTP_documentAttributeAudio(const MTPflags &_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/structs.cpp b/Telegram/SourceFiles/structs.cpp index 459c483876..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,7 +1148,7 @@ 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(); From 6d2fc5c64282db08800bed9a775b286b0c11234b Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 15 Sep 2016 22:15:49 +0300 Subject: [PATCH 10/10] Game bot confirmations added. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/boxes/confirmbox.cpp | 17 +- Telegram/SourceFiles/boxes/confirmbox.h | 15 + .../SourceFiles/core/click_handler_types.cpp | 15 + .../SourceFiles/core/click_handler_types.h | 11 + Telegram/SourceFiles/facades.cpp | 4 +- Telegram/SourceFiles/historywidget.cpp | 14 +- Telegram/SourceFiles/historywidget.h | 1 + Telegram/SourceFiles/localstorage.cpp | 7977 +++++++++-------- Telegram/SourceFiles/localstorage.h | 308 +- Telegram/SourceFiles/mainwidget.cpp | 119 +- 11 files changed, 4316 insertions(+), 4167 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7d8b42dcf2..9c2484f6d8 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -764,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"; 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/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/facades.cpp b/Telegram/SourceFiles/facades.cpp index c088ee535f..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); } @@ -81,7 +81,7 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) { case ButtonType::Url: { auto url = QString::fromUtf8(button->data); - UrlClickHandler(url).onClick(Qt::LeftButton); + HiddenUrlClickHandler(url).onClick(Qt::LeftButton); } break; case ButtonType::RequestLocation: { diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 07372a93b9..8d27947f31 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -5783,8 +5783,16 @@ void HistoryWidget::app_sendBotCallback(const HistoryMessageReplyMarkup::Button bool lastKeyboardUsed = (_keyboard.forMsgId() == FullMsgId(_channel, _history->lastKeyboardId)) && (_keyboard.forMsgId() == FullMsgId(_channel, msg->id)); + auto bot = msg->viaBot(); + if (!bot) { + bot = msg->from()->asUser(); + if (bot && !bot->botInfo) { + bot = nullptr; + } + } + using ButtonType = HistoryMessageReplyMarkup::Button::Type; - BotCallbackInfo info = { msg->fullId(), row, col, (button->type == ButtonType::Game) }; + BotCallbackInfo info = { bot, msg->fullId(), row, col, (button->type == ButtonType::Game) }; MTPmessages_GetBotCallbackAnswer::Flags flags = 0; QByteArray sendData; int32 sendGameId = 0; @@ -5833,8 +5841,10 @@ void HistoryWidget::botCallbackDone(BotCallbackInfo info, const MTPmessages_BotC 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); } - UrlClickHandler(url).onClick(Qt::LeftButton); } } } diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index e55451ae19..e875851208 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -961,6 +961,7 @@ private: void addMessagesToBack(PeerData *peer, const QVector &messages); struct BotCallbackInfo { + UserData *bot; FullMsgId msgId; int row, col; bool game; diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index e247a50831..28d1bece68 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -34,4274 +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(); - 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 (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); - if (!(set.flags & MTPDstickerSet_ClientFlag::f_special)) { - if (document->sticker()->set.type() != mtpc_inputStickerSetID) { - document->sticker()->set = inputSet; - } - } - ++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() { - 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 { - 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(); } - } - - void writeReportSpamStatuses() { - _writeReportSpamStatuses(); - } - - bool encrypt(const void *src, void *dst, uint32 len, const void *key128) { - if (!_localKey.created()) { - return false; + } else { + if (!_trustedBotsKey) { + _trustedBotsKey = genKey(); + _mapChanged = true; + _writeMap(WriteMapFast); } - 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; + 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); } - MTP::aesDecryptLocal(src, dst, len, &_localKey, key128); - return true; + + FileWriteDescriptor file(_trustedBotsKey); + file.writeEncrypted(data); + } +} + +void readTrustedBots() { + if (!_trustedBotsKey) return; + + FileReadDescriptor trusted; + if (!readEncryptedFile(trusted, _trustedBotsKey)) { + clearKey(_trustedBotsKey); + _trustedBotsKey = 0; + _writeMap(); + return; } - 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; + qint32 size = 0; + trusted.stream >> size; + for (int i = 0; i < size; ++i) { + quint64 botId = 0; + trusted.stream >> botId; + _trustedBots.insert(botId); } +} - bool ClearManager::addTask(int task) { - QMutexLocker lock(&data->mutex); - if (!data->working) return false; +void makeBotTrusted(UserData *bot) { + if (!isBotTrusted(bot)) { + _trustedBots.insert(bot->id); + writeTrustedBots(); + } +} - if (!data->tasks.isEmpty() && (data->tasks.at(0) == ClearManagerAll)) return true; - if (task == ClearManagerAll) { - data->tasks.clear(); +bool isBotTrusted(UserData *bot) { + if (!_trustedBotsRead) { + readTrustedBots(); + _trustedBotsRead = true; + } + return _trustedBots.contains(bot->id); +} + +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 5f53d4f4d4..d7b7e718da 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -22,168 +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(); - - bool encrypt(const void *src, void *dst, uint32 len, const void *key128); - bool decrypt(const void *src, void *dst, uint32 len, const void *key128); + QTimer _mapWriteTimer; + QTimer _locationsWriteTimer; }; + +} // namespace internal +} // namespace Local diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 68f9c5b1df..dfcfe94ab0 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -4680,72 +4680,73 @@ 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; - } - } - 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 (int32 i = 0, l = v.size(); i < l; ++i) { - auto doc = App::feedDocument(v.at(i)); - if (!doc || !doc->sticker()) continue; - - it->stickers.push_back(doc); - if (doc->sticker()->set.type() != mtpc_inputStickerSetID) { - doc->sticker()->set = inputSet; - } - } - 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; @@ -4756,7 +4757,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { auto &order = d.vorder.c_vector().v; auto &sets = Global::StickerSets(); Stickers::Order result; - for (int32 i = 0, l = order.size(); i < l; ++i) { + for (int i = 0, l = order.size(); i < l; ++i) { if (sets.constFind(order.at(i).v) == sets.cend()) { break; }